mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
P-UI: Fast language / theme / server selection (#5301)
* added language toggle component * added language and theme controls onto start page * moved host selection out of auth * optimized rendering * make server option less obvious * changed EditButton save symbol * longer welcome text * removed ColorToggle color schema * reduced code * disabled host selection when options are changing * fix type error * use GH reporter * fix tests? * compile frontend * fix assertation * revert unneeded change * split up into more components * separated functions / use cases for LanguageToggle more * moved color toggle to profile * moved language out of main menu into profile * remapped settings link
This commit is contained in:
parent
f70294b247
commit
f227315ad1
2
.github/workflows/qc_checks.yaml
vendored
2
.github/workflows/qc_checks.yaml
vendored
@ -414,7 +414,7 @@ jobs:
|
|||||||
- name: Set up test data
|
- name: Set up test data
|
||||||
run: invoke setup-test -i
|
run: invoke setup-test -i
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: cd src/frontend && yarn install
|
run: inv frontend-compile
|
||||||
- name: Install Playwright Browsers
|
- name: Install Playwright Browsers
|
||||||
run: cd src/frontend && npx playwright install --with-deps
|
run: cd src/frontend && npx playwright install --with-deps
|
||||||
- name: Run Playwright tests
|
- name: Run Playwright tests
|
||||||
|
@ -6,7 +6,7 @@ export default defineConfig({
|
|||||||
forbidOnly: !!process.env.CI,
|
forbidOnly: !!process.env.CI,
|
||||||
retries: process.env.CI ? 1 : 0,
|
retries: process.env.CI ? 1 : 0,
|
||||||
workers: process.env.CI ? 2 : undefined,
|
workers: process.env.CI ? 2 : undefined,
|
||||||
reporter: 'html',
|
reporter: process.env.CI ? 'github' : 'list',
|
||||||
|
|
||||||
/* Configure projects for major browsers */
|
/* Configure projects for major browsers */
|
||||||
projects: [
|
projects: [
|
||||||
|
25
src/frontend/src/components/forms/AuthFormOptions.tsx
Normal file
25
src/frontend/src/components/forms/AuthFormOptions.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Center, Group, Tooltip } from '@mantine/core';
|
||||||
|
import { IconServer } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
import { ColorToggle } from '../items/ColorToggle';
|
||||||
|
import { LanguageToggle } from '../items/LanguageToggle';
|
||||||
|
|
||||||
|
export function AuthFormOptions({
|
||||||
|
hostname,
|
||||||
|
toggleHostEdit
|
||||||
|
}: {
|
||||||
|
hostname: string;
|
||||||
|
toggleHostEdit: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Center mx={'md'}>
|
||||||
|
<Group>
|
||||||
|
<ColorToggle />
|
||||||
|
<LanguageToggle />
|
||||||
|
<Tooltip label={hostname}>
|
||||||
|
<IconServer onClick={toggleHostEdit} />
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
</Center>
|
||||||
|
);
|
||||||
|
}
|
@ -16,19 +16,8 @@ import { IconCheck } from '@tabler/icons-react';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { doClassicLogin, doSimpleLogin } from '../../functions/auth';
|
import { doClassicLogin, doSimpleLogin } from '../../functions/auth';
|
||||||
import { EditButton } from '../items/EditButton';
|
|
||||||
|
|
||||||
export function AuthenticationForm({
|
export function AuthenticationForm() {
|
||||||
hostname,
|
|
||||||
editing,
|
|
||||||
setEditing,
|
|
||||||
selectElement
|
|
||||||
}: {
|
|
||||||
hostname: string;
|
|
||||||
editing: boolean;
|
|
||||||
setEditing: (value?: React.SetStateAction<boolean> | undefined) => void;
|
|
||||||
selectElement: JSX.Element;
|
|
||||||
}) {
|
|
||||||
const classicForm = useForm({
|
const classicForm = useForm({
|
||||||
initialValues: { username: '', password: '' }
|
initialValues: { username: '', password: '' }
|
||||||
});
|
});
|
||||||
@ -82,10 +71,7 @@ export function AuthenticationForm({
|
|||||||
return (
|
return (
|
||||||
<Paper radius="md" p="xl" withBorder>
|
<Paper radius="md" p="xl" withBorder>
|
||||||
<Text size="lg" weight={500}>
|
<Text size="lg" weight={500}>
|
||||||
<Group>
|
<Trans>Welcome, log in below</Trans>
|
||||||
{!editing ? hostname : selectElement}
|
|
||||||
<EditButton setEditing={setEditing} editing={editing} />
|
|
||||||
</Group>
|
|
||||||
</Text>
|
</Text>
|
||||||
<form onSubmit={classicForm.onSubmit(() => {})}>
|
<form onSubmit={classicForm.onSubmit(() => {})}>
|
||||||
{classicLoginMode ? (
|
{classicLoginMode ? (
|
||||||
|
77
src/frontend/src/components/forms/InstanceOptions.tsx
Normal file
77
src/frontend/src/components/forms/InstanceOptions.tsx
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { Trans } from '@lingui/macro';
|
||||||
|
import { Divider, Group, Select, Text, Title } from '@mantine/core';
|
||||||
|
import { useToggle } from '@mantine/hooks';
|
||||||
|
import { IconCheck } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
import { useLocalState } from '../../states/LocalState';
|
||||||
|
import { HostList } from '../../states/states';
|
||||||
|
import { EditButton } from '../items/EditButton';
|
||||||
|
import { HostOptionsForm } from './HostOptionsForm';
|
||||||
|
|
||||||
|
export function InstanceOptions({
|
||||||
|
hostKey,
|
||||||
|
ChangeHost,
|
||||||
|
setHostEdit
|
||||||
|
}: {
|
||||||
|
hostKey: string;
|
||||||
|
ChangeHost: (newHost: string) => void;
|
||||||
|
setHostEdit: () => void;
|
||||||
|
}) {
|
||||||
|
const [HostListEdit, setHostListEdit] = useToggle([false, true] as const);
|
||||||
|
const [setHost, setHostList, hostList] = useLocalState((state) => [
|
||||||
|
state.setHost,
|
||||||
|
state.setHostList,
|
||||||
|
state.hostList
|
||||||
|
]);
|
||||||
|
|
||||||
|
const hostListData = Object.keys(hostList).map((key) => ({
|
||||||
|
value: key,
|
||||||
|
label: hostList[key].name
|
||||||
|
}));
|
||||||
|
|
||||||
|
function SaveOptions(newHostList: HostList): void {
|
||||||
|
setHostList(newHostList);
|
||||||
|
if (newHostList[hostKey] === undefined) {
|
||||||
|
setHost('', '');
|
||||||
|
}
|
||||||
|
setHostListEdit();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Title order={3}>
|
||||||
|
<Trans>Select destination instance</Trans>
|
||||||
|
</Title>
|
||||||
|
<Group>
|
||||||
|
<Group>
|
||||||
|
<Select
|
||||||
|
value={hostKey}
|
||||||
|
onChange={ChangeHost}
|
||||||
|
data={hostListData}
|
||||||
|
disabled={HostListEdit}
|
||||||
|
/>
|
||||||
|
<EditButton
|
||||||
|
setEditing={setHostListEdit}
|
||||||
|
editing={HostListEdit}
|
||||||
|
disabled={HostListEdit}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
<EditButton
|
||||||
|
setEditing={setHostEdit}
|
||||||
|
editing={true}
|
||||||
|
disabled={HostListEdit}
|
||||||
|
saveIcon={<IconCheck />}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
{HostListEdit && (
|
||||||
|
<>
|
||||||
|
<Divider my={'sm'} />
|
||||||
|
<Text>
|
||||||
|
<Trans>Edit possible host options</Trans>
|
||||||
|
</Text>
|
||||||
|
<HostOptionsForm data={hostList} saveOptions={SaveOptions} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -10,10 +10,6 @@ export function ColorToggle() {
|
|||||||
onClick={() => toggleColorScheme()}
|
onClick={() => toggleColorScheme()}
|
||||||
size="lg"
|
size="lg"
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
backgroundColor:
|
|
||||||
theme.colorScheme === 'dark'
|
|
||||||
? theme.colors.dark[6]
|
|
||||||
: theme.colors.gray[0],
|
|
||||||
color:
|
color:
|
||||||
theme.colorScheme === 'dark'
|
theme.colorScheme === 'dark'
|
||||||
? theme.colors.yellow[4]
|
? theme.colors.yellow[4]
|
||||||
|
@ -4,15 +4,18 @@ import { IconDeviceFloppy, IconEdit } from '@tabler/icons-react';
|
|||||||
export function EditButton({
|
export function EditButton({
|
||||||
setEditing,
|
setEditing,
|
||||||
editing,
|
editing,
|
||||||
disabled
|
disabled,
|
||||||
|
saveIcon
|
||||||
}: {
|
}: {
|
||||||
setEditing: (value?: React.SetStateAction<boolean> | undefined) => void;
|
setEditing: (value?: React.SetStateAction<boolean> | undefined) => void;
|
||||||
editing: boolean;
|
editing: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
saveIcon?: JSX.Element;
|
||||||
}) {
|
}) {
|
||||||
|
saveIcon = saveIcon || <IconDeviceFloppy />;
|
||||||
return (
|
return (
|
||||||
<ActionIcon onClick={() => setEditing()} disabled={disabled}>
|
<ActionIcon onClick={() => setEditing()} disabled={disabled}>
|
||||||
{editing ? <IconDeviceFloppy /> : <IconEdit />}
|
{editing ? saveIcon : <IconEdit />}
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
26
src/frontend/src/components/items/LanguageSelect.tsx
Normal file
26
src/frontend/src/components/items/LanguageSelect.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Select } from '@mantine/core';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { Locales, languages } from '../../contexts/LanguageContext';
|
||||||
|
import { useLocalState } from '../../states/LocalState';
|
||||||
|
|
||||||
|
export function LanguageSelect() {
|
||||||
|
const [value, setValue] = useState<string | null>(null);
|
||||||
|
const [locale, setLanguage] = useLocalState((state) => [
|
||||||
|
state.language,
|
||||||
|
state.setLanguage
|
||||||
|
]);
|
||||||
|
|
||||||
|
// change global language on change
|
||||||
|
useEffect(() => {
|
||||||
|
if (value === null) return;
|
||||||
|
setLanguage(value as Locales);
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
// set language on component load
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(locale);
|
||||||
|
}, [locale]);
|
||||||
|
|
||||||
|
return <Select w={80} data={languages} value={value} onChange={setValue} />;
|
||||||
|
}
|
29
src/frontend/src/components/items/LanguageToggle.tsx
Normal file
29
src/frontend/src/components/items/LanguageToggle.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { ActionIcon, Group } from '@mantine/core';
|
||||||
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
|
import { IconLanguage } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
import { LanguageSelect } from './LanguageSelect';
|
||||||
|
|
||||||
|
export function LanguageToggle() {
|
||||||
|
const [open, toggle] = useDisclosure();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group
|
||||||
|
position="center"
|
||||||
|
style={{
|
||||||
|
border: open === true ? `1px dashed ` : ``,
|
||||||
|
margin: open === true ? 2 : 12,
|
||||||
|
padding: open === true ? 8 : 0
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ActionIcon onClick={() => toggle.toggle()} size="lg">
|
||||||
|
<IconLanguage />
|
||||||
|
</ActionIcon>
|
||||||
|
{open && (
|
||||||
|
<Group>
|
||||||
|
<LanguageSelect />
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
}
|
@ -4,7 +4,6 @@ import { useNavigate, useParams } from 'react-router-dom';
|
|||||||
|
|
||||||
import { navTabs as mainNavTabs } from '../../defaults/links';
|
import { navTabs as mainNavTabs } from '../../defaults/links';
|
||||||
import { InvenTreeStyle } from '../../globalStyle';
|
import { InvenTreeStyle } from '../../globalStyle';
|
||||||
import { ColorToggle } from '../items/ColorToggle';
|
|
||||||
import { ScanButton } from '../items/ScanButton';
|
import { ScanButton } from '../items/ScanButton';
|
||||||
import { MainMenu } from './MainMenu';
|
import { MainMenu } from './MainMenu';
|
||||||
import { NavHoverMenu } from './NavHoverMenu';
|
import { NavHoverMenu } from './NavHoverMenu';
|
||||||
@ -25,7 +24,6 @@ export function Header() {
|
|||||||
</Group>
|
</Group>
|
||||||
<Group>
|
<Group>
|
||||||
<ScanButton />
|
<ScanButton />
|
||||||
<ColorToggle />
|
|
||||||
<MainMenu />
|
<MainMenu />
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
|
@ -3,35 +3,20 @@ import { Group, Menu, Skeleton, Text, UnstyledButton } from '@mantine/core';
|
|||||||
import {
|
import {
|
||||||
IconChevronDown,
|
IconChevronDown,
|
||||||
IconHeart,
|
IconHeart,
|
||||||
IconLanguage,
|
|
||||||
IconLogout,
|
IconLogout,
|
||||||
IconSettings,
|
IconSettings,
|
||||||
IconUserCircle
|
IconUserCircle
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { languages } from '../../contexts/LanguageContext';
|
|
||||||
import { doClassicLogout } from '../../functions/auth';
|
import { doClassicLogout } from '../../functions/auth';
|
||||||
import { InvenTreeStyle } from '../../globalStyle';
|
import { InvenTreeStyle } from '../../globalStyle';
|
||||||
import { useApiState } from '../../states/ApiState';
|
import { useApiState } from '../../states/ApiState';
|
||||||
import { useLocalState } from '../../states/LocalState';
|
|
||||||
import { PlaceholderPill } from '../items/Placeholder';
|
import { PlaceholderPill } from '../items/Placeholder';
|
||||||
|
|
||||||
export function MainMenu() {
|
export function MainMenu() {
|
||||||
const { classes, theme } = InvenTreeStyle();
|
const { classes, theme } = InvenTreeStyle();
|
||||||
const [username] = useApiState((state) => [state.user?.name]);
|
const [username] = useApiState((state) => [state.user?.name]);
|
||||||
const [locale] = useLocalState((state) => [state.language]);
|
|
||||||
|
|
||||||
// Language
|
|
||||||
function switchLanguage() {
|
|
||||||
useLocalState.setState({
|
|
||||||
language: languages[(languages.indexOf(locale) + 1) % languages.length]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function enablePsuedo() {
|
|
||||||
useLocalState.setState({ language: 'pseudo-LOCALE' });
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu width={260} position="bottom-end">
|
<Menu width={260} position="bottom-end">
|
||||||
<Menu.Target>
|
<Menu.Target>
|
||||||
@ -53,26 +38,15 @@ export function MainMenu() {
|
|||||||
<Trans>Notifications</Trans>
|
<Trans>Notifications</Trans>
|
||||||
<PlaceholderPill />
|
<PlaceholderPill />
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item icon={<IconUserCircle />}>
|
||||||
icon={<IconUserCircle />}
|
<Trans>Profile</Trans> <PlaceholderPill />
|
||||||
component={Link}
|
|
||||||
to="/profile/user"
|
|
||||||
>
|
|
||||||
<Trans>Profile</Trans>
|
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|
||||||
<Menu.Label>
|
<Menu.Label>
|
||||||
<Trans>Settings</Trans>
|
<Trans>Settings</Trans>
|
||||||
</Menu.Label>
|
</Menu.Label>
|
||||||
<Menu.Item icon={<IconLanguage />} onClick={switchLanguage}>
|
<Menu.Item icon={<IconSettings />} component={Link} to="/profile/user">
|
||||||
<Trans>Current language {locale}</Trans>
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item icon={<IconLanguage />} onClick={enablePsuedo}>
|
|
||||||
<Trans>Switch to pseudo language</Trans>
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item icon={<IconSettings />}>
|
|
||||||
<Trans>Account settings</Trans>
|
<Trans>Account settings</Trans>
|
||||||
<PlaceholderPill />
|
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
icon={<IconLogout />}
|
icon={<IconLogout />}
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import { Trans, t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Center, Container, Group, Select, Stack, Text } from '@mantine/core';
|
import { Center, Container } from '@mantine/core';
|
||||||
import { useToggle } from '@mantine/hooks';
|
import { useToggle } from '@mantine/hooks';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
import { AuthFormOptions } from '../../components/forms/AuthFormOptions';
|
||||||
import { AuthenticationForm } from '../../components/forms/AuthenticationForm';
|
import { AuthenticationForm } from '../../components/forms/AuthenticationForm';
|
||||||
import { HostOptionsForm } from '../../components/forms/HostOptionsForm';
|
import { InstanceOptions } from '../../components/forms/InstanceOptions';
|
||||||
import { EditButton } from '../../components/items/EditButton';
|
|
||||||
import { defaultHostKey } from '../../defaults/defaultHostList';
|
import { defaultHostKey } from '../../defaults/defaultHostList';
|
||||||
import { useLocalState } from '../../states/LocalState';
|
import { useLocalState } from '../../states/LocalState';
|
||||||
import { HostList } from '../../states/states';
|
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const [hostKey, setHost, hostList] = useLocalState((state) => [
|
const [hostKey, setHost, hostList] = useLocalState((state) => [
|
||||||
@ -19,24 +18,12 @@ export default function Login() {
|
|||||||
const hostname =
|
const hostname =
|
||||||
hostList[hostKey] === undefined ? t`No selection` : hostList[hostKey].name;
|
hostList[hostKey] === undefined ? t`No selection` : hostList[hostKey].name;
|
||||||
const [hostEdit, setHostEdit] = useToggle([false, true] as const);
|
const [hostEdit, setHostEdit] = useToggle([false, true] as const);
|
||||||
const hostListData = Object.keys(hostList).map((key) => ({
|
|
||||||
value: key,
|
|
||||||
label: hostList[key].name
|
|
||||||
}));
|
|
||||||
const [HostListEdit, setHostListEdit] = useToggle([false, true] as const);
|
|
||||||
|
|
||||||
// Data manipulation functions
|
// Data manipulation functions
|
||||||
function ChangeHost(newHost: string): void {
|
function ChangeHost(newHost: string): void {
|
||||||
setHost(hostList[newHost].host, newHost);
|
setHost(hostList[newHost].host, newHost);
|
||||||
setHostEdit(false);
|
|
||||||
}
|
|
||||||
function SaveOptions(newHostList: HostList): void {
|
|
||||||
useLocalState.setState({ hostList: newHostList });
|
|
||||||
if (newHostList[hostKey] === undefined) {
|
|
||||||
setHost('', '');
|
|
||||||
}
|
|
||||||
setHostListEdit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set default host to localhost if no host is selected
|
// Set default host to localhost if no host is selected
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hostKey === '') {
|
if (hostKey === '') {
|
||||||
@ -44,87 +31,23 @@ export default function Login() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Main rendering block
|
||||||
return (
|
return (
|
||||||
<Center mih="100vh">
|
<Center mih="100vh">
|
||||||
<Container w="md" miw={400}>
|
<Container w="md" miw={400}>
|
||||||
<Stack>
|
{hostEdit ? (
|
||||||
<EditHostList
|
<InstanceOptions
|
||||||
hostList={hostList}
|
hostKey={hostKey}
|
||||||
SaveOptions={SaveOptions}
|
ChangeHost={ChangeHost}
|
||||||
HostListEdit={HostListEdit}
|
setHostEdit={setHostEdit}
|
||||||
/>
|
/>
|
||||||
{!HostListEdit && (
|
) : (
|
||||||
<AuthenticationForm
|
<>
|
||||||
hostname={hostname}
|
<AuthenticationForm />
|
||||||
editing={hostEdit}
|
<AuthFormOptions hostname={hostname} toggleHostEdit={setHostEdit} />
|
||||||
setEditing={setHostEdit}
|
</>
|
||||||
selectElement={
|
)}
|
||||||
<SelectHost
|
|
||||||
hostKey={hostKey}
|
|
||||||
ChangeHost={ChangeHost}
|
|
||||||
hostListData={hostListData}
|
|
||||||
HostListEdit={HostListEdit}
|
|
||||||
hostEdit={hostEdit}
|
|
||||||
setHostListEdit={setHostListEdit}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Container>
|
</Container>
|
||||||
</Center>
|
</Center>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const SelectHost = ({
|
|
||||||
hostKey,
|
|
||||||
ChangeHost,
|
|
||||||
hostListData,
|
|
||||||
HostListEdit,
|
|
||||||
hostEdit,
|
|
||||||
setHostListEdit
|
|
||||||
}: {
|
|
||||||
hostKey: string;
|
|
||||||
ChangeHost: (newHost: string) => void;
|
|
||||||
hostListData: any;
|
|
||||||
HostListEdit: boolean;
|
|
||||||
hostEdit: boolean;
|
|
||||||
setHostListEdit: (value?: React.SetStateAction<boolean> | undefined) => void;
|
|
||||||
}) => {
|
|
||||||
if (!hostEdit) return <></>;
|
|
||||||
return (
|
|
||||||
<Group>
|
|
||||||
<Select
|
|
||||||
value={hostKey}
|
|
||||||
onChange={ChangeHost}
|
|
||||||
data={hostListData}
|
|
||||||
disabled={HostListEdit}
|
|
||||||
/>
|
|
||||||
<EditButton
|
|
||||||
setEditing={setHostListEdit}
|
|
||||||
editing={HostListEdit}
|
|
||||||
disabled={HostListEdit}
|
|
||||||
/>
|
|
||||||
</Group>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const EditHostList = ({
|
|
||||||
hostList,
|
|
||||||
SaveOptions,
|
|
||||||
HostListEdit
|
|
||||||
}: {
|
|
||||||
hostList: HostList;
|
|
||||||
SaveOptions: (newHostList: HostList) => void;
|
|
||||||
HostListEdit: boolean;
|
|
||||||
}) => {
|
|
||||||
if (!HostListEdit) return null;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Text>
|
|
||||||
<Trans>Edit host options</Trans>
|
|
||||||
</Text>
|
|
||||||
<HostOptionsForm data={hostList} saveOptions={SaveOptions} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
@ -16,7 +16,10 @@ import { useToggle } from '@mantine/hooks';
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { api, queryClient } from '../../../App';
|
import { api, queryClient } from '../../../App';
|
||||||
|
import { ColorToggle } from '../../../components/items/ColorToggle';
|
||||||
import { EditButton } from '../../../components/items/EditButton';
|
import { EditButton } from '../../../components/items/EditButton';
|
||||||
|
import { LanguageSelect } from '../../../components/items/LanguageSelect';
|
||||||
|
import { useLocalState } from '../../../states/LocalState';
|
||||||
import { UserTheme } from './UserTheme';
|
import { UserTheme } from './UserTheme';
|
||||||
|
|
||||||
export function UserPanel() {
|
export function UserPanel() {
|
||||||
@ -48,7 +51,7 @@ export function UserPanel() {
|
|||||||
<UserTheme height={SECONDARY_COL_HEIGHT} />
|
<UserTheme height={SECONDARY_COL_HEIGHT} />
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col span={6}>
|
<Grid.Col span={6}>
|
||||||
<Skeleton height={SECONDARY_COL_HEIGHT} />
|
<DisplaySettings height={SECONDARY_COL_HEIGHT} />
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col span={6}>
|
<Grid.Col span={6}>
|
||||||
<Skeleton height={SECONDARY_COL_HEIGHT} />
|
<Skeleton height={SECONDARY_COL_HEIGHT} />
|
||||||
@ -122,3 +125,34 @@ export function UserInfo({ data }: { data: any }) {
|
|||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function DisplaySettings({ height }: { height: number }) {
|
||||||
|
function enablePseudoLang(): void {
|
||||||
|
useLocalState.setState({ language: 'pseudo-LOCALE' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container w="100%" mih={height} p={0}>
|
||||||
|
<Title order={3}>
|
||||||
|
<Trans>Display Settings</Trans>
|
||||||
|
</Title>
|
||||||
|
<Group>
|
||||||
|
<Text>
|
||||||
|
<Trans>Color Mode</Trans>
|
||||||
|
</Text>
|
||||||
|
<ColorToggle />
|
||||||
|
</Group>
|
||||||
|
<Group align="top">
|
||||||
|
<Text>
|
||||||
|
<Trans>Language</Trans>
|
||||||
|
</Text>
|
||||||
|
<Stack>
|
||||||
|
<LanguageSelect />
|
||||||
|
<Button onClick={enablePseudoLang} variant="light">
|
||||||
|
<Trans>Use pseudo language</Trans>
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Group>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -13,7 +13,9 @@ interface LocalStateProps {
|
|||||||
setHost: (newHost: string, newHostKey: string) => void;
|
setHost: (newHost: string, newHostKey: string) => void;
|
||||||
hostKey: string;
|
hostKey: string;
|
||||||
hostList: HostList;
|
hostList: HostList;
|
||||||
|
setHostList: (newHostList: HostList) => void;
|
||||||
language: Locales;
|
language: Locales;
|
||||||
|
setLanguage: (newLanguage: Locales) => void;
|
||||||
// theme
|
// theme
|
||||||
primaryColor: string;
|
primaryColor: string;
|
||||||
whiteColor: string;
|
whiteColor: string;
|
||||||
@ -33,7 +35,9 @@ export const useLocalState = create<LocalStateProps>()(
|
|||||||
set({ host: newHost, hostKey: newHostKey }),
|
set({ host: newHost, hostKey: newHostKey }),
|
||||||
hostKey: '',
|
hostKey: '',
|
||||||
hostList: {},
|
hostList: {},
|
||||||
|
setHostList: (newHostList) => set({ hostList: newHostList }),
|
||||||
language: 'en',
|
language: 'en',
|
||||||
|
setLanguage: (newLanguage) => set({ language: newLanguage }),
|
||||||
//theme
|
//theme
|
||||||
primaryColor: 'indigo',
|
primaryColor: 'indigo',
|
||||||
whiteColor: '#fff',
|
whiteColor: '#fff',
|
||||||
|
2
tasks.py
2
tasks.py
@ -129,7 +129,7 @@ def node_available(versions: bool = False, bypass_yarn: bool = False):
|
|||||||
print('Node is available but yarn is not. Install yarn if you wish to build the frontend.')
|
print('Node is available but yarn is not. Install yarn if you wish to build the frontend.')
|
||||||
|
|
||||||
# Return the result
|
# Return the result
|
||||||
return ret((not yarn_passes or not node_version), node_version, yarn_version)
|
return ret(yarn_passes and node_version, node_version, yarn_version)
|
||||||
|
|
||||||
|
|
||||||
def check_file_existance(filename: str, overwrite: bool = False):
|
def check_file_existance(filename: str, overwrite: bool = False):
|
||||||
|
Loading…
Reference in New Issue
Block a user