mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
WIP [PUI] Migrate to Mantine v7 (#7028)
* bump deps
* upgrade all deps
* adapt theme context
* add vanilla extract
* add basic theme
* reformat global state
* fix imports
* fix spotlight
* update args
* adapt arg names
* fix more arg renames
* fix italic
* switch sx to style
* fix types
* fix theme refs
* misc fixes
* misc fixes
* fix type
* fix selects
* misc fixes
* bug fix
* update to new style
* set text args
* fix spotlight
* dumb spotlight down
* change ActionIcons back to default
* fix name
* fix test
* adjust test to new spotlight
* package fix
* fix new code to v7
* fix building
* fix group aligment
* remove unneeded imports
* add new type
* import cleanups
* add notification style
* move context to loadable
* reorder contexts
* make test less flaky
* fix missing theming
* fix color schema switcher
* increase timeouts
* update package refs
* add missing style for datatables
* fix missing nesting
* organize imports
* move language context around
* make sure license keys are unique
* add keys to badges
* fix import
* fix missing keys
* fix missing key issue in badge section
* update packages
* fix new code to v7 style
* dummy change
* fix up test
* fix btn style
* fix merge issues
* remove placeholders
* fix color schema usage
* fix usage of ColorScheme
* fix style issues
* fix test
* fix choice field to fit stricter validation
* make test more reproducible
* wait for dash before proceeding
* bump deps
* add missing style
* do loops
* fix css
* change carousel sizing
* fix merge for v7
* fix image ratio
* Revert "bump deps"
This reverts commit 91cdae5a3e
.
* fix userstate to ensure it always renders
* await dashboard loading before resuming wiht wuick login
* fix spotlight and remove testing changes
* Catch API error
* Update breadcrumb list
* Update panel icon
* Cleanup notification drawer
* Some more tweaks
* Fix for notification count indicator
* Fix stack prop
* fix type error
* fix double timeout key
* use div instead of text
---------
Co-authored-by: Oliver Walters <oliver.henry.walters@gmail.com>
This commit is contained in:
parent
08b1bdb564
commit
6700a4625d
@ -11,66 +11,78 @@
|
|||||||
"compile": "lingui compile --typescript"
|
"compile": "lingui compile --typescript"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": ">=6.0.0",
|
||||||
"@codemirror/lang-liquid": "^6.2.1",
|
"@codemirror/lang-liquid": "^6.2.1",
|
||||||
|
"@codemirror/language": ">=6.0.0",
|
||||||
|
"@codemirror/lint": ">=6.0.0",
|
||||||
|
"@codemirror/search": ">=6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/theme-one-dark": ">=6.0.0",
|
||||||
|
"@codemirror/view": ">=6.0.0",
|
||||||
"@emotion/react": "^11.11.4",
|
"@emotion/react": "^11.11.4",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.5.2",
|
"@fortawesome/fontawesome-svg-core": "^6.5.2",
|
||||||
"@fortawesome/free-regular-svg-icons": "^6.5.2",
|
"@fortawesome/free-regular-svg-icons": "^6.5.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.5.2",
|
"@fortawesome/free-solid-svg-icons": "^6.5.2",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@lingui/core": "^4.7.1",
|
"@lingui/core": "^4.10.0",
|
||||||
"@lingui/react": "^4.10.0",
|
"@lingui/react": "^4.10.0",
|
||||||
"@mantine/carousel": "<7",
|
"@mantine/carousel": "^7.8.0",
|
||||||
"@mantine/core": "<7",
|
"@mantine/core": "^7.8.0",
|
||||||
"@mantine/dates": "<7",
|
"@mantine/dates": "^7.8.0",
|
||||||
"@mantine/dropzone": "<7",
|
"@mantine/dropzone": "^7.8.0",
|
||||||
"@mantine/form": "<8",
|
"@mantine/form": "^7.8.0",
|
||||||
"@mantine/hooks": "<7",
|
"@mantine/hooks": "^7.8.0",
|
||||||
"@mantine/modals": "<7",
|
"@mantine/modals": "^7.8.0",
|
||||||
"@mantine/notifications": "<7",
|
"@mantine/notifications": "^7.8.0",
|
||||||
"@mantine/spotlight": "<7",
|
"@mantine/spotlight": "^7.8.0",
|
||||||
|
"@mantine/vanilla-extract": "^7.8.0",
|
||||||
"@naisutech/react-tree": "^3.1.0",
|
"@naisutech/react-tree": "^3.1.0",
|
||||||
"@sentry/react": "^7.109.0",
|
"@sentry/react": "^7.110.0",
|
||||||
"@tabler/icons-react": "^3.1.0",
|
"@tabler/icons-react": "^3.2.0",
|
||||||
"@tanstack/react-query": "^5.28.14",
|
"@tanstack/react-query": "^5.29.2",
|
||||||
"@uiw/codemirror-theme-vscode": "^4.21.25",
|
"@uiw/codemirror-theme-vscode": "^4.21.25",
|
||||||
"@uiw/react-codemirror": "^4.21.25",
|
"@uiw/react-codemirror": "^4.21.25",
|
||||||
"@uiw/react-split": "^5.9.3",
|
"@uiw/react-split": "^5.9.3",
|
||||||
"axios": "^1.6.7",
|
"@vanilla-extract/css": "^1.14.2",
|
||||||
|
"axios": "^1.6.8",
|
||||||
|
"clsx": "^2.1.0",
|
||||||
|
"codemirror": ">=6.0.0",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"easymde": "^2.18.0",
|
"easymde": "^2.18.0",
|
||||||
"embla-carousel-react": "^8.0.2",
|
"embla-carousel-react": "^8.0.2",
|
||||||
"html5-qrcode": "^2.3.8",
|
"html5-qrcode": "^2.3.8",
|
||||||
"mantine-datatable": "<7",
|
"mantine-datatable": "^7.8.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-grid-layout": "^1.4.4",
|
"react-grid-layout": "^1.4.4",
|
||||||
"react-hook-form": "^7.51.3",
|
"react-hook-form": "^7.51.3",
|
||||||
"react-is": "^18.2.0",
|
"react-is": "^18.2.0",
|
||||||
"react-router-dom": "^6.22.1",
|
"react-router-dom": "^6.22.3",
|
||||||
"react-select": "^5.8.0",
|
"react-select": "^5.8.0",
|
||||||
"react-simplemde-editor": "^5.2.0",
|
"react-simplemde-editor": "^5.2.0",
|
||||||
"recharts": "^2.12.4",
|
"recharts": "^2.12.4",
|
||||||
"styled-components": "^5.3.6",
|
"styled-components": "^6.1.8",
|
||||||
"zustand": "^4.5.1"
|
"zustand": "^4.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.23.9",
|
"@babel/core": "^7.24.4",
|
||||||
"@babel/preset-react": "^7.23.3",
|
"@babel/preset-react": "^7.24.1",
|
||||||
"@babel/preset-typescript": "^7.23.3",
|
"@babel/preset-typescript": "^7.24.1",
|
||||||
"@lingui/cli": "^4.7.2",
|
"@lingui/cli": "^4.10.0",
|
||||||
"@lingui/macro": "^4.10.0",
|
"@lingui/macro": "^4.10.0",
|
||||||
"@playwright/test": "^1.43.1",
|
"@playwright/test": "^1.43.1",
|
||||||
"@types/node": "^20.12.3",
|
"@types/node": "^20.12.7",
|
||||||
"@types/react": "^18.2.74",
|
"@types/react": "^18.2.79",
|
||||||
"@types/react-dom": "^18.2.23",
|
"@types/react-dom": "^18.2.25",
|
||||||
"@types/react-grid-layout": "^1.3.5",
|
"@types/react-grid-layout": "^1.3.5",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
|
"@vanilla-extract/vite-plugin": "^4.0.7",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"babel-plugin-macros": "^3.1.0",
|
"babel-plugin-macros": "^3.1.0",
|
||||||
"nyc": "^15.1.0",
|
"nyc": "^15.1.0",
|
||||||
"rollup-plugin-license": "^3.3.1",
|
"rollup-plugin-license": "^3.3.1",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.4.5",
|
||||||
"vite": "^5.2.7",
|
"vite": "^5.2.8",
|
||||||
"vite-plugin-babel-macros": "^1.0.6",
|
"vite-plugin-babel-macros": "^1.0.6",
|
||||||
"vite-plugin-istanbul": "^6.0.0"
|
"vite-plugin-istanbul": "^6.0.0"
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ export default defineConfig({
|
|||||||
fullyParallel: true,
|
fullyParallel: true,
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
forbidOnly: !!process.env.CI,
|
forbidOnly: !!process.env.CI,
|
||||||
timeout: 5 * 60 * 1000,
|
|
||||||
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: process.env.CI ? [['html', { open: 'never' }], ['github']] : 'list',
|
reporter: process.env.CI ? [['html', { open: 'never' }], ['github']] : 'list',
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { ActionIcon, Group, Tooltip } from '@mantine/core';
|
import { ActionIcon, FloatingPosition, Group, Tooltip } from '@mantine/core';
|
||||||
import { FloatingPosition } from '@mantine/core/lib/Floating';
|
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
import { notYetImplemented } from '../../functions/notifications';
|
import { notYetImplemented } from '../../functions/notifications';
|
||||||
@ -41,7 +40,7 @@ export function ActionButton(props: ActionButtonProps) {
|
|||||||
onClick={props.onClick ?? notYetImplemented}
|
onClick={props.onClick ?? notYetImplemented}
|
||||||
variant={props.variant ?? 'light'}
|
variant={props.variant ?? 'light'}
|
||||||
>
|
>
|
||||||
<Group spacing="xs" noWrap={true}>
|
<Group gap="xs" wrap="nowrap">
|
||||||
{props.icon}
|
{props.icon}
|
||||||
</Group>
|
</Group>
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
|
@ -18,7 +18,7 @@ export function ButtonMenu({
|
|||||||
return (
|
return (
|
||||||
<Menu shadow="xs">
|
<Menu shadow="xs">
|
||||||
<Menu.Target>
|
<Menu.Target>
|
||||||
<ActionIcon>
|
<ActionIcon variant="default">
|
||||||
<Tooltip label={tooltip}>{icon}</Tooltip>
|
<Tooltip label={tooltip}>{icon}</Tooltip>
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Menu.Target>
|
</Menu.Target>
|
||||||
|
@ -17,7 +17,7 @@ export function CopyButton({
|
|||||||
onClick={copy}
|
onClick={copy}
|
||||||
title={t`Copy to clipboard`}
|
title={t`Copy to clipboard`}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
compact
|
size="compact-md"
|
||||||
>
|
>
|
||||||
<IconCopy size={10} />
|
<IconCopy size={10} />
|
||||||
{label && <div> </div>}
|
{label && <div> </div>}
|
||||||
|
@ -14,7 +14,11 @@ export function EditButton({
|
|||||||
}) {
|
}) {
|
||||||
saveIcon = saveIcon || <IconDeviceFloppy />;
|
saveIcon = saveIcon || <IconDeviceFloppy />;
|
||||||
return (
|
return (
|
||||||
<ActionIcon onClick={() => setEditing()} disabled={disabled}>
|
<ActionIcon
|
||||||
|
onClick={() => setEditing()}
|
||||||
|
disabled={disabled}
|
||||||
|
variant="default"
|
||||||
|
>
|
||||||
{editing ? saveIcon : <IconEdit />}
|
{editing ? saveIcon : <IconEdit />}
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
);
|
);
|
||||||
|
@ -50,7 +50,7 @@ export function SsoButton({ provider }: { provider: Provider }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
leftIcon={getBrandIcon(provider)}
|
leftSection={getBrandIcon(provider)}
|
||||||
radius="xl"
|
radius="xl"
|
||||||
component="a"
|
component="a"
|
||||||
onClick={login}
|
onClick={login}
|
||||||
|
@ -16,6 +16,7 @@ export function ScanButton() {
|
|||||||
innerProps: {}
|
innerProps: {}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
variant="transparent"
|
||||||
title={t`Open QR code scanner`}
|
title={t`Open QR code scanner`}
|
||||||
>
|
>
|
||||||
<IconQrcode />
|
<IconQrcode />
|
||||||
|
19
src/frontend/src/components/buttons/SplitButton.css.ts
Normal file
19
src/frontend/src/components/buttons/SplitButton.css.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
import { vars } from '../../theme';
|
||||||
|
|
||||||
|
export const button = style({
|
||||||
|
borderTopRightRadius: 0,
|
||||||
|
borderBottomRightRadius: 0,
|
||||||
|
|
||||||
|
':before': {
|
||||||
|
borderRadius: '0 !important'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const icon = style({
|
||||||
|
borderTopLeftRadius: 0,
|
||||||
|
borderBottomLeftRadius: 0,
|
||||||
|
border: 0,
|
||||||
|
borderLeft: `1px solid ${vars.colors.primaryShade}`
|
||||||
|
});
|
@ -5,13 +5,13 @@ import {
|
|||||||
Menu,
|
Menu,
|
||||||
Text,
|
Text,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
createStyles,
|
|
||||||
useMantineTheme
|
useMantineTheme
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { IconChevronDown } from '@tabler/icons-react';
|
import { IconChevronDown } from '@tabler/icons-react';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { TablerIconType } from '../../functions/icons';
|
import { TablerIconType } from '../../functions/icons';
|
||||||
|
import * as classes from './SplitButton.css';
|
||||||
|
|
||||||
interface SplitButtonOption {
|
interface SplitButtonOption {
|
||||||
key: string;
|
key: string;
|
||||||
@ -30,22 +30,6 @@ interface SplitButtonProps {
|
|||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = createStyles((theme) => ({
|
|
||||||
button: {
|
|
||||||
borderTopRightRadius: 0,
|
|
||||||
borderBottomRightRadius: 0,
|
|
||||||
'&::before': {
|
|
||||||
borderRadius: '0 !important'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
borderTopLeftRadius: 0,
|
|
||||||
borderBottomLeftRadius: 0,
|
|
||||||
border: 0,
|
|
||||||
borderLeft: `1px solid ${theme.primaryShade}`
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
export function SplitButton({
|
export function SplitButton({
|
||||||
options,
|
options,
|
||||||
defaultSelected,
|
defaultSelected,
|
||||||
@ -54,7 +38,6 @@ export function SplitButton({
|
|||||||
loading
|
loading
|
||||||
}: Readonly<SplitButtonProps>) {
|
}: Readonly<SplitButtonProps>) {
|
||||||
const [current, setCurrent] = useState<string>(defaultSelected);
|
const [current, setCurrent] = useState<string>(defaultSelected);
|
||||||
const { classes } = useStyles();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelected?.(current);
|
setSelected?.(current);
|
||||||
@ -72,7 +55,7 @@ export function SplitButton({
|
|||||||
const theme = useMantineTheme();
|
const theme = useMantineTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group noWrap style={{ gap: 0 }}>
|
<Group wrap="nowrap" style={{ gap: 0 }}>
|
||||||
<Button
|
<Button
|
||||||
onClick={currentOption?.onClick}
|
onClick={currentOption?.onClick}
|
||||||
disabled={loading ? false : currentOption?.disabled}
|
disabled={loading ? false : currentOption?.disabled}
|
||||||
@ -106,7 +89,7 @@ export function SplitButton({
|
|||||||
option.onClick();
|
option.onClick();
|
||||||
}}
|
}}
|
||||||
disabled={option.disabled}
|
disabled={option.disabled}
|
||||||
icon={<option.icon />}
|
leftSection={<option.icon />}
|
||||||
>
|
>
|
||||||
<Tooltip label={option.tooltip} position="right">
|
<Tooltip label={option.tooltip} position="right">
|
||||||
<Text>{option.name}</Text>
|
<Text>{option.name}</Text>
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { ActionIcon } from '@mantine/core';
|
import { ActionIcon } from '@mantine/core';
|
||||||
import { spotlight } from '@mantine/spotlight';
|
|
||||||
import { IconCommand } from '@tabler/icons-react';
|
import { IconCommand } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
import { firstSpotlight } from '../nav/Layout';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A button which opens the quick command modal
|
* A button which opens the quick command modal
|
||||||
*/
|
*/
|
||||||
export function SpotlightButton() {
|
export function SpotlightButton() {
|
||||||
return (
|
return (
|
||||||
<ActionIcon onClick={() => spotlight.open()} title={t`Open spotlight`}>
|
<ActionIcon
|
||||||
|
onClick={() => firstSpotlight.open()}
|
||||||
|
title={t`Open spotlight`}
|
||||||
|
variant="transparent"
|
||||||
|
>
|
||||||
<IconCommand />
|
<IconCommand />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
);
|
);
|
||||||
|
@ -324,7 +324,12 @@ function CopyField({ value }: { value: string }) {
|
|||||||
<CopyButton value={value}>
|
<CopyButton value={value}>
|
||||||
{({ copied, copy }) => (
|
{({ copied, copy }) => (
|
||||||
<Tooltip label={copied ? t`Copied` : t`Copy`} withArrow>
|
<Tooltip label={copied ? t`Copied` : t`Copy`} withArrow>
|
||||||
<ActionIcon color={copied ? 'teal' : 'gray'} onClick={copy}>
|
<ActionIcon
|
||||||
|
color={copied ? 'teal' : 'gray'}
|
||||||
|
onClick={copy}
|
||||||
|
variant="transparent"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
{copied ? (
|
{copied ? (
|
||||||
<InvenTreeIcon icon="check" />
|
<InvenTreeIcon icon="check" />
|
||||||
) : (
|
) : (
|
||||||
@ -398,9 +403,14 @@ export function DetailsTable({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Paper p="xs" withBorder radius="xs">
|
<Paper p="xs" withBorder radius="xs">
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
{title && <StylishText size="lg">{title}</StylishText>}
|
{title && <StylishText size="lg">{title}</StylishText>}
|
||||||
<Table striped>
|
<Table
|
||||||
|
striped
|
||||||
|
verticalSpacing="sm"
|
||||||
|
horizontalSpacing="md"
|
||||||
|
withColumnBorders
|
||||||
|
>
|
||||||
<tbody>
|
<tbody>
|
||||||
{fields
|
{fields
|
||||||
.filter((field: DetailsField) => !field.hidden)
|
.filter((field: DetailsField) => !field.hidden)
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
Paper,
|
Paper,
|
||||||
Text,
|
Text,
|
||||||
rem,
|
rem,
|
||||||
useMantineTheme
|
useMantineColorScheme
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { Dropzone, FileWithPath, IMAGE_MIME_TYPE } from '@mantine/dropzone';
|
import { Dropzone, FileWithPath, IMAGE_MIME_TYPE } from '@mantine/dropzone';
|
||||||
import { useHover } from '@mantine/hooks';
|
import { useHover } from '@mantine/hooks';
|
||||||
@ -21,6 +21,7 @@ import { cancelEvent } from '../../functions/events';
|
|||||||
import { InvenTreeIcon } from '../../functions/icons';
|
import { InvenTreeIcon } from '../../functions/icons';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import { PartThumbTable } from '../../tables/part/PartThumbTable';
|
import { PartThumbTable } from '../../tables/part/PartThumbTable';
|
||||||
|
import { vars } from '../../theme';
|
||||||
import { ActionButton } from '../buttons/ActionButton';
|
import { ActionButton } from '../buttons/ActionButton';
|
||||||
import { ApiImage } from '../images/ApiImage';
|
import { ApiImage } from '../images/ApiImage';
|
||||||
import { StylishText } from '../items/StylishText';
|
import { StylishText } from '../items/StylishText';
|
||||||
@ -87,8 +88,6 @@ function UploadModal({
|
|||||||
const [file1, setFile] = useState<FileWithPath | null>(null);
|
const [file1, setFile] = useState<FileWithPath | null>(null);
|
||||||
let uploading = false;
|
let uploading = false;
|
||||||
|
|
||||||
const theme = useMantineTheme();
|
|
||||||
|
|
||||||
// Components to show in the Dropzone when no file is selected
|
// Components to show in the Dropzone when no file is selected
|
||||||
const noFileIdle = (
|
const noFileIdle = (
|
||||||
<Group>
|
<Group>
|
||||||
@ -122,7 +121,7 @@ function UploadModal({
|
|||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src={imageUrl}
|
src={imageUrl}
|
||||||
imageProps={{ onLoad: () => URL.revokeObjectURL(imageUrl) }}
|
onLoad={() => URL.revokeObjectURL(imageUrl)}
|
||||||
radius="sm"
|
radius="sm"
|
||||||
height={75}
|
height={75}
|
||||||
fit="contain"
|
fit="contain"
|
||||||
@ -160,12 +159,14 @@ function UploadModal({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { colorScheme } = useMantineColorScheme();
|
||||||
|
|
||||||
const primaryColor =
|
const primaryColor =
|
||||||
theme.colors[theme.primaryColor][theme.colorScheme === 'dark' ? 4 : 6];
|
vars.colors.primaryColors[colorScheme === 'dark' ? 4 : 6];
|
||||||
const redColor = theme.colors.red[theme.colorScheme === 'dark' ? 4 : 6];
|
const redColor = vars.colors.red[colorScheme === 'dark' ? 4 : 6];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper sx={{ height: '220px' }}>
|
<Paper style={{ height: '220px' }}>
|
||||||
<Dropzone
|
<Dropzone
|
||||||
onDrop={(files) => setFile(files[0])}
|
onDrop={(files) => setFile(files[0])}
|
||||||
maxFiles={1}
|
maxFiles={1}
|
||||||
@ -173,8 +174,8 @@ function UploadModal({
|
|||||||
loading={uploading}
|
loading={uploading}
|
||||||
>
|
>
|
||||||
<Group
|
<Group
|
||||||
position="center"
|
justify="center"
|
||||||
spacing="xl"
|
gap="xl"
|
||||||
style={{ minHeight: rem(140), pointerEvents: 'none' }}
|
style={{ minHeight: rem(140), pointerEvents: 'none' }}
|
||||||
>
|
>
|
||||||
<Dropzone.Accept>
|
<Dropzone.Accept>
|
||||||
@ -252,7 +253,7 @@ function ImageActionButtons({
|
|||||||
<>
|
<>
|
||||||
{visible && (
|
{visible && (
|
||||||
<Group
|
<Group
|
||||||
spacing="xs"
|
gap="xs"
|
||||||
style={{ zIndex: 2, position: 'absolute', top: '10px', left: '10px' }}
|
style={{ zIndex: 2, position: 'absolute', top: '10px', left: '10px' }}
|
||||||
>
|
>
|
||||||
{actions.selectExisting && (
|
{actions.selectExisting && (
|
||||||
@ -358,8 +359,8 @@ export function DetailsImage(props: Readonly<DetailImageProps>) {
|
|||||||
<>
|
<>
|
||||||
<ApiImage
|
<ApiImage
|
||||||
src={img}
|
src={img}
|
||||||
height={IMAGE_DIMENSION}
|
mah={IMAGE_DIMENSION}
|
||||||
width={IMAGE_DIMENSION}
|
maw={IMAGE_DIMENSION}
|
||||||
onClick={expandImage}
|
onClick={expandImage}
|
||||||
/>
|
/>
|
||||||
{permissions.hasChangeRole(props.appRole) &&
|
{permissions.hasChangeRole(props.appRole) &&
|
||||||
|
@ -222,7 +222,7 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
|
|||||||
<Split style={{ gap: '10px' }}>
|
<Split style={{ gap: '10px' }}>
|
||||||
<Tabs
|
<Tabs
|
||||||
value={editorValue}
|
value={editorValue}
|
||||||
onTabChange={async (v) => {
|
onChange={async (v) => {
|
||||||
codeRef.current = await getCodeFromEditor();
|
codeRef.current = await getCodeFromEditor();
|
||||||
setEditorValue(v);
|
setEditorValue(v);
|
||||||
}}
|
}}
|
||||||
@ -239,13 +239,13 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
|
|||||||
<Tabs.Tab
|
<Tabs.Tab
|
||||||
key={Editor.key}
|
key={Editor.key}
|
||||||
value={Editor.key}
|
value={Editor.key}
|
||||||
icon={<Editor.icon size="0.8rem" />}
|
leftSection={<Editor.icon size="0.8rem" />}
|
||||||
>
|
>
|
||||||
{Editor.name}
|
{Editor.name}
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<Group position="right" style={{ flex: '1' }} noWrap>
|
<Group justify="right" style={{ flex: '1' }} wrap="nowrap">
|
||||||
<SplitButton
|
<SplitButton
|
||||||
loading={isPreviewLoading}
|
loading={isPreviewLoading}
|
||||||
defaultSelected="preview_save"
|
defaultSelected="preview_save"
|
||||||
@ -288,7 +288,7 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
|
|||||||
|
|
||||||
<Tabs
|
<Tabs
|
||||||
value={previewValue}
|
value={previewValue}
|
||||||
onTabChange={setPreviewValue}
|
onChange={setPreviewValue}
|
||||||
style={{
|
style={{
|
||||||
minWidth: '200px',
|
minWidth: '200px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -300,7 +300,7 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
|
|||||||
<Tabs.Tab
|
<Tabs.Tab
|
||||||
key={PreviewArea.key}
|
key={PreviewArea.key}
|
||||||
value={PreviewArea.key}
|
value={PreviewArea.key}
|
||||||
icon={<PreviewArea.icon size="0.8rem" />}
|
leftSection={<PreviewArea.icon size="0.8rem" />}
|
||||||
>
|
>
|
||||||
{PreviewArea.name}
|
{PreviewArea.name}
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
|
@ -482,11 +482,11 @@ export function ApiForm({
|
|||||||
<Paper mah={'65vh'} style={{ overflowY: 'auto' }}>
|
<Paper mah={'65vh'} style={{ overflowY: 'auto' }}>
|
||||||
<div>
|
<div>
|
||||||
{/* Form Fields */}
|
{/* Form Fields */}
|
||||||
<Stack spacing="sm">
|
<Stack gap="sm">
|
||||||
{(!isValid || nonFieldErrors.length > 0) && (
|
{(!isValid || nonFieldErrors.length > 0) && (
|
||||||
<Alert radius="sm" color="red" title={t`Form Errors Exist`}>
|
<Alert radius="sm" color="red" title={t`Form Errors Exist`}>
|
||||||
{nonFieldErrors.length > 0 && (
|
{nonFieldErrors.length > 0 && (
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
{nonFieldErrors.map((message) => (
|
{nonFieldErrors.map((message) => (
|
||||||
<Text key={message}>{message}</Text>
|
<Text key={message}>{message}</Text>
|
||||||
))}
|
))}
|
||||||
@ -509,7 +509,7 @@ export function ApiForm({
|
|||||||
</Boundary>
|
</Boundary>
|
||||||
<Boundary label={`ApiForm-${id}-FormContent`}>
|
<Boundary label={`ApiForm-${id}-FormContent`}>
|
||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
{!optionsLoading &&
|
{!optionsLoading &&
|
||||||
Object.entries(fields).map(([fieldName, field]) => (
|
Object.entries(fields).map(([fieldName, field]) => (
|
||||||
<ApiFormField
|
<ApiFormField
|
||||||
@ -532,7 +532,7 @@ export function ApiForm({
|
|||||||
{/* Footer with Action Buttons */}
|
{/* Footer with Action Buttons */}
|
||||||
<Divider />
|
<Divider />
|
||||||
<div>
|
<div>
|
||||||
<Group position="right">
|
<Group justify="right">
|
||||||
{props.actions?.map((action, i) => (
|
{props.actions?.map((action, i) => (
|
||||||
<Button
|
<Button
|
||||||
key={i}
|
key={i}
|
||||||
|
@ -100,7 +100,7 @@ export function AuthenticationForm() {
|
|||||||
) : null}
|
) : null}
|
||||||
<form onSubmit={classicForm.onSubmit(() => {})}>
|
<form onSubmit={classicForm.onSubmit(() => {})}>
|
||||||
{classicLoginMode ? (
|
{classicLoginMode ? (
|
||||||
<Stack spacing={0}>
|
<Stack gap={0}>
|
||||||
<TextInput
|
<TextInput
|
||||||
required
|
required
|
||||||
label={t`Username`}
|
label={t`Username`}
|
||||||
@ -114,7 +114,7 @@ export function AuthenticationForm() {
|
|||||||
{...classicForm.getInputProps('password')}
|
{...classicForm.getInputProps('password')}
|
||||||
/>
|
/>
|
||||||
{auth_settings?.password_forgotten_enabled === true && (
|
{auth_settings?.password_forgotten_enabled === true && (
|
||||||
<Group position="apart" mt="0">
|
<Group justify="space-between" mt="0">
|
||||||
<Anchor
|
<Anchor
|
||||||
component="button"
|
component="button"
|
||||||
type="button"
|
type="button"
|
||||||
@ -139,7 +139,7 @@ export function AuthenticationForm() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Group position="apart" mt="xl">
|
<Group justify="space-between" mt="xl">
|
||||||
<Anchor
|
<Anchor
|
||||||
component="button"
|
component="button"
|
||||||
type="button"
|
type="button"
|
||||||
@ -221,7 +221,7 @@ export function RegistrationForm() {
|
|||||||
<>
|
<>
|
||||||
{auth_settings?.registration_enabled && (
|
{auth_settings?.registration_enabled && (
|
||||||
<form onSubmit={registrationForm.onSubmit(() => {})}>
|
<form onSubmit={registrationForm.onSubmit(() => {})}>
|
||||||
<Stack spacing={0}>
|
<Stack gap={0}>
|
||||||
<TextInput
|
<TextInput
|
||||||
required
|
required
|
||||||
label={t`Username`}
|
label={t`Username`}
|
||||||
@ -249,7 +249,7 @@ export function RegistrationForm() {
|
|||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Group position="apart" mt="xl">
|
<Group justify="space-between" mt="xl">
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isRegistering}
|
disabled={isRegistering}
|
||||||
|
@ -35,13 +35,13 @@ export function HostOptionsForm({
|
|||||||
<TextInput
|
<TextInput
|
||||||
placeholder={t`Host`}
|
placeholder={t`Host`}
|
||||||
withAsterisk
|
withAsterisk
|
||||||
sx={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
{...form.getInputProps(`${key}.host`)}
|
{...form.getInputProps(`${key}.host`)}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder={t`Name`}
|
placeholder={t`Name`}
|
||||||
withAsterisk
|
withAsterisk
|
||||||
sx={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
{...form.getInputProps(`${key}.name`)}
|
{...form.getInputProps(`${key}.name`)}
|
||||||
/>
|
/>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
@ -49,6 +49,7 @@ export function HostOptionsForm({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteItem(key);
|
deleteItem(key);
|
||||||
}}
|
}}
|
||||||
|
variant="default"
|
||||||
>
|
>
|
||||||
<IconTrash />
|
<IconTrash />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
@ -59,18 +60,18 @@ export function HostOptionsForm({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={form.onSubmit(saveOptions)}>
|
<form onSubmit={form.onSubmit(saveOptions)}>
|
||||||
<Box sx={{ maxWidth: 500 }} mx="auto">
|
<Box style={{ maxWidth: 500 }} mx="auto">
|
||||||
{fields.length > 0 ? (
|
{fields.length > 0 ? (
|
||||||
<Group mb="xs">
|
<Group mb="xs">
|
||||||
<Text weight={500} size="sm" sx={{ flex: 1 }}>
|
<Text fw={500} size="sm" style={{ flex: 1 }}>
|
||||||
<Trans>Host</Trans>
|
<Trans>Host</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
<Text weight={500} size="sm" sx={{ flex: 1 }}>
|
<Text fw={500} size="sm" style={{ flex: 1 }}>
|
||||||
<Trans>Name</Trans>
|
<Trans>Name</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
) : (
|
) : (
|
||||||
<Text color="dimmed" align="center">
|
<Text c="dimmed" ta="center">
|
||||||
<Trans>No one here...</Trans>
|
<Trans>No one here...</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
@ -84,7 +85,7 @@ export function HostOptionsForm({
|
|||||||
<IconSquarePlus />
|
<IconSquarePlus />
|
||||||
<Trans>Add Host</Trans>
|
<Trans>Add Host</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
<Space sx={{ flex: 1 }} />
|
<Space style={{ flex: 1 }} />
|
||||||
<Button type="submit">
|
<Button type="submit">
|
||||||
<Trans>Save</Trans>
|
<Trans>Save</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -15,7 +15,7 @@ export function InstanceOptions({
|
|||||||
setHostEdit
|
setHostEdit
|
||||||
}: {
|
}: {
|
||||||
hostKey: string;
|
hostKey: string;
|
||||||
ChangeHost: (newHost: string) => void;
|
ChangeHost: (newHost: string | null) => void;
|
||||||
setHostEdit: () => void;
|
setHostEdit: () => void;
|
||||||
}) {
|
}) {
|
||||||
const [HostListEdit, setHostListEdit] = useToggle([false, true] as const);
|
const [HostListEdit, setHostListEdit] = useToggle([false, true] as const);
|
||||||
|
@ -230,9 +230,8 @@ export function ApiFormField({
|
|||||||
id={fieldId}
|
id={fieldId}
|
||||||
value={numericalValue}
|
value={numericalValue}
|
||||||
error={error?.message}
|
error={error?.message}
|
||||||
precision={definition.field_type == 'integer' ? 0 : 10}
|
decimalScale={definition.field_type == 'integer' ? 0 : 10}
|
||||||
onChange={(value: number) => onChange(value)}
|
onChange={(value: number | string | null) => onChange(value)}
|
||||||
removeTrailingZeros
|
|
||||||
step={1}
|
step={1}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -31,8 +31,8 @@ export function ChoiceField({
|
|||||||
|
|
||||||
return choices.map((choice) => {
|
return choices.map((choice) => {
|
||||||
return {
|
return {
|
||||||
value: choice.value,
|
value: choice.value.toString(),
|
||||||
label: choice.display_name
|
label: choice.display_name.toString()
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, [definition.choices]);
|
}, [definition.choices]);
|
||||||
@ -62,8 +62,8 @@ export function ChoiceField({
|
|||||||
placeholder={definition.placeholder}
|
placeholder={definition.placeholder}
|
||||||
required={definition.required}
|
required={definition.required}
|
||||||
disabled={definition.disabled}
|
disabled={definition.disabled}
|
||||||
icon={definition.icon}
|
leftSection={definition.icon}
|
||||||
withinPortal={true}
|
comboboxProps={{ withinPortal: true }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ export default function DateField({
|
|||||||
label={definition.label}
|
label={definition.label}
|
||||||
description={definition.description}
|
description={definition.description}
|
||||||
placeholder={definition.placeholder}
|
placeholder={definition.placeholder}
|
||||||
icon={definition.icon}
|
leftSection={definition.icon}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,8 @@ export function NestedObjectField({
|
|||||||
<Text>{definition.label}</Text>
|
<Text>{definition.label}</Text>
|
||||||
</Accordion.Control>
|
</Accordion.Control>
|
||||||
<Accordion.Panel>
|
<Accordion.Panel>
|
||||||
<Divider sx={{ marginTop: '-10px', marginBottom: '10px' }} />
|
<Divider style={{ marginTop: '-10px', marginBottom: '10px' }} />
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
{Object.entries(definition.children ?? {}).map(
|
{Object.entries(definition.children ?? {}).map(
|
||||||
([childFieldName, field]) => (
|
([childFieldName, field]) => (
|
||||||
<ApiFormField
|
<ApiFormField
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Input, useMantineTheme } from '@mantine/core';
|
import {
|
||||||
|
Input,
|
||||||
|
darken,
|
||||||
|
useMantineColorScheme,
|
||||||
|
useMantineTheme
|
||||||
|
} from '@mantine/core';
|
||||||
import { useDebouncedValue, useId } from '@mantine/hooks';
|
import { useDebouncedValue, useId } from '@mantine/hooks';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
@ -11,6 +16,7 @@ import {
|
|||||||
import Select from 'react-select';
|
import Select from 'react-select';
|
||||||
|
|
||||||
import { api } from '../../../App';
|
import { api } from '../../../App';
|
||||||
|
import { vars } from '../../../theme';
|
||||||
import { RenderInstance } from '../../render/Instance';
|
import { RenderInstance } from '../../render/Instance';
|
||||||
import { ApiFormFieldType } from './ApiFormField';
|
import { ApiFormFieldType } from './ApiFormField';
|
||||||
|
|
||||||
@ -217,43 +223,46 @@ export function RelatedModelField({
|
|||||||
// Define color theme to pass to field based on Mantine theme
|
// Define color theme to pass to field based on Mantine theme
|
||||||
const theme = useMantineTheme();
|
const theme = useMantineTheme();
|
||||||
|
|
||||||
|
const colorschema = vars.colors.primaryColors;
|
||||||
|
const { colorScheme } = useMantineColorScheme();
|
||||||
|
|
||||||
const colors = useMemo(() => {
|
const colors = useMemo(() => {
|
||||||
let colors: any;
|
let colors: any;
|
||||||
if (theme.colorScheme === 'dark') {
|
if (colorScheme === 'dark') {
|
||||||
colors = {
|
colors = {
|
||||||
neutral0: theme.colors[theme.colorScheme][6],
|
neutral0: colorschema[6],
|
||||||
neutral5: theme.colors[theme.colorScheme][4],
|
neutral5: colorschema[4],
|
||||||
neutral10: theme.colors[theme.colorScheme][4],
|
neutral10: colorschema[4],
|
||||||
neutral20: theme.colors[theme.colorScheme][4],
|
neutral20: colorschema[4],
|
||||||
neutral30: theme.colors[theme.colorScheme][3],
|
neutral30: colorschema[3],
|
||||||
neutral40: theme.colors[theme.colorScheme][2],
|
neutral40: colorschema[2],
|
||||||
neutral50: theme.colors[theme.colorScheme][1],
|
neutral50: colorschema[1],
|
||||||
neutral60: theme.colors[theme.colorScheme][0],
|
neutral60: colorschema[0],
|
||||||
neutral70: theme.colors[theme.colorScheme][0],
|
neutral70: colorschema[0],
|
||||||
neutral80: theme.colors[theme.colorScheme][0],
|
neutral80: colorschema[0],
|
||||||
neutral90: theme.colors[theme.colorScheme][0],
|
neutral90: colorschema[0],
|
||||||
primary: theme.colors[theme.primaryColor][7],
|
primary: vars.colors.primaryColors[7],
|
||||||
primary25: theme.colors[theme.primaryColor][6],
|
primary25: vars.colors.primaryColors[6],
|
||||||
primary50: theme.colors[theme.primaryColor][5],
|
primary50: vars.colors.primaryColors[5],
|
||||||
primary75: theme.colors[theme.primaryColor][4]
|
primary75: vars.colors.primaryColors[4]
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
colors = {
|
colors = {
|
||||||
neutral0: theme.white,
|
neutral0: vars.colors.white,
|
||||||
neutral5: theme.fn.darken(theme.white, 0.05),
|
neutral5: darken(vars.colors.white, 0.05),
|
||||||
neutral10: theme.fn.darken(theme.white, 0.1),
|
neutral10: darken(vars.colors.white, 0.1),
|
||||||
neutral20: theme.fn.darken(theme.white, 0.2),
|
neutral20: darken(vars.colors.white, 0.2),
|
||||||
neutral30: theme.fn.darken(theme.white, 0.3),
|
neutral30: darken(vars.colors.white, 0.3),
|
||||||
neutral40: theme.fn.darken(theme.white, 0.4),
|
neutral40: darken(vars.colors.white, 0.4),
|
||||||
neutral50: theme.fn.darken(theme.white, 0.5),
|
neutral50: darken(vars.colors.white, 0.5),
|
||||||
neutral60: theme.fn.darken(theme.white, 0.6),
|
neutral60: darken(vars.colors.white, 0.6),
|
||||||
neutral70: theme.fn.darken(theme.white, 0.7),
|
neutral70: darken(vars.colors.white, 0.7),
|
||||||
neutral80: theme.fn.darken(theme.white, 0.8),
|
neutral80: darken(vars.colors.white, 0.8),
|
||||||
neutral90: theme.fn.darken(theme.white, 0.9),
|
neutral90: darken(vars.colors.white, 0.9),
|
||||||
primary: theme.colors[theme.primaryColor][7],
|
primary: vars.colors.primaryColors[7],
|
||||||
primary25: theme.colors[theme.primaryColor][4],
|
primary25: vars.colors.primaryColors[4],
|
||||||
primary50: theme.colors[theme.primaryColor][5],
|
primary50: vars.colors.primaryColors[5],
|
||||||
primary75: theme.colors[theme.primaryColor][6]
|
primary75: vars.colors.primaryColors[6]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return colors;
|
return colors;
|
||||||
|
@ -8,10 +8,14 @@ import { useMemo } from 'react';
|
|||||||
|
|
||||||
import { useLocalState } from '../../states/LocalState';
|
import { useLocalState } from '../../states/LocalState';
|
||||||
|
|
||||||
|
interface ApiImageProps extends ImageProps {
|
||||||
|
onClick?: (event: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct an image container which will load and display the image
|
* Construct an image container which will load and display the image
|
||||||
*/
|
*/
|
||||||
export function ApiImage(props: ImageProps) {
|
export function ApiImage(props: Readonly<ApiImageProps>) {
|
||||||
const { host } = useLocalState.getState();
|
const { host } = useLocalState.getState();
|
||||||
|
|
||||||
const imageUrl = useMemo(() => {
|
const imageUrl = useMemo(() => {
|
||||||
@ -21,12 +25,9 @@ export function ApiImage(props: ImageProps) {
|
|||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
{imageUrl ? (
|
{imageUrl ? (
|
||||||
<Image {...props} src={imageUrl} withPlaceholder fit="contain" />
|
<Image {...props} src={imageUrl} fit="contain" />
|
||||||
) : (
|
) : (
|
||||||
<Skeleton
|
<Skeleton h={props?.h ?? props.w} w={props?.w ?? props.h} />
|
||||||
height={props?.height ?? props.width}
|
|
||||||
width={props?.width ?? props.height}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
@ -37,19 +37,14 @@ export function Thumbnail({
|
|||||||
}, [link, text]);
|
}, [link, text]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group align={align ?? 'left'} spacing="xs" noWrap={true}>
|
<Group align={align ?? 'left'} gap="xs" wrap="nowrap">
|
||||||
<ApiImage
|
<ApiImage
|
||||||
src={src || backup_image}
|
src={src || backup_image}
|
||||||
alt={alt}
|
aria-label={alt}
|
||||||
width={size}
|
w={size}
|
||||||
fit="contain"
|
fit="contain"
|
||||||
radius="xs"
|
radius="xs"
|
||||||
withPlaceholder
|
style={{ maxHeight: size }}
|
||||||
imageProps={{
|
|
||||||
style: {
|
|
||||||
maxHeight: size
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{inner}
|
{inner}
|
||||||
</Group>
|
</Group>
|
||||||
@ -71,7 +66,7 @@ export function ThumbnailHoverCard({
|
|||||||
}) {
|
}) {
|
||||||
const card = useMemo(() => {
|
const card = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<Group position="left" spacing={10} noWrap={true}>
|
<Group justify="left" gap={10} wrap="nowrap">
|
||||||
<Thumbnail src={src} alt={alt} size={size} />
|
<Thumbnail src={src} alt={alt} size={size} />
|
||||||
<Text>{text}</Text>
|
<Text>{text}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
|
@ -79,7 +79,7 @@ export function ActionDropdown({
|
|||||||
>
|
>
|
||||||
<Tooltip label={action.tooltip}>
|
<Tooltip label={action.tooltip}>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
icon={action.icon}
|
leftSection={action.icon}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (action.onClick != undefined) {
|
if (action.onClick != undefined) {
|
||||||
action.onClick();
|
action.onClick();
|
||||||
|
@ -72,7 +72,7 @@ export function AttachmentLink({
|
|||||||
}, [host, attachment, external]);
|
}, [host, attachment, external]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group position="left" spacing="sm">
|
<Group justify="left" gap="sm">
|
||||||
{external ? <IconLink /> : attachmentIcon(attachment)}
|
{external ? <IconLink /> : attachmentIcon(attachment)}
|
||||||
<Anchor href={url} target="_blank" rel="noopener noreferrer">
|
<Anchor href={url} target="_blank" rel="noopener noreferrer">
|
||||||
{text}
|
{text}
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
import { ActionIcon, Group, useMantineColorScheme } from '@mantine/core';
|
import { ActionIcon, Group, useMantineColorScheme } from '@mantine/core';
|
||||||
import { IconMoonStars, IconSun } from '@tabler/icons-react';
|
import { IconMoonStars, IconSun } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
import { vars } from '../../theme';
|
||||||
|
|
||||||
export function ColorToggle() {
|
export function ColorToggle() {
|
||||||
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
|
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group position="center">
|
<Group justify="center">
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={() => toggleColorScheme()}
|
onClick={toggleColorScheme}
|
||||||
size="lg"
|
size="lg"
|
||||||
sx={(theme) => ({
|
style={{
|
||||||
color:
|
color:
|
||||||
theme.colorScheme === 'dark'
|
colorScheme === 'dark' ? vars.colors.yellow[4] : vars.colors.blue[6]
|
||||||
? theme.colors.yellow[4]
|
}}
|
||||||
: theme.colors.blue[6]
|
variant="transparent"
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
{colorScheme === 'dark' ? <IconSun /> : <IconMoonStars />}
|
{colorScheme === 'dark' ? <IconSun /> : <IconMoonStars />}
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Group, LoadingOverlay, Paper, Text } from '@mantine/core';
|
import { Group, LoadingOverlay, Paper, Text } from '@mantine/core';
|
||||||
|
|
||||||
import { InvenTreeStyle } from '../../globalStyle';
|
import * as classes from '../../main.css';
|
||||||
|
|
||||||
export interface StatisticItemProps {
|
export interface StatisticItemProps {
|
||||||
title: string;
|
title: string;
|
||||||
@ -16,18 +16,16 @@ export function StatisticItem({
|
|||||||
data: StatisticItemProps;
|
data: StatisticItemProps;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { classes } = InvenTreeStyle();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper withBorder p="xs" key={id} pos="relative">
|
<Paper withBorder p="xs" key={id} pos="relative">
|
||||||
<LoadingOverlay visible={isLoading} overlayBlur={2} />
|
<LoadingOverlay visible={isLoading} overlayProps={{ blur: 2 }} />
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
<Text size="xs" color="dimmed" className={classes.dashboardItemTitle}>
|
<Text size="xs" color="dimmed" className={classes.dashboardItemTitle}>
|
||||||
{data.title}
|
{data.title}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Group align="flex-end" spacing="xs" mt={25}>
|
<Group align="flex-end" gap="xs" mt={25}>
|
||||||
<Text className={classes.dashboardItemValue}>{data.value}</Text>
|
<Text className={classes.dashboardItemValue}>{data.value}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
@ -2,7 +2,7 @@ import { Trans } from '@lingui/macro';
|
|||||||
import { Anchor, Container, HoverCard, ScrollArea, Text } from '@mantine/core';
|
import { Anchor, Container, HoverCard, ScrollArea, Text } from '@mantine/core';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { InvenTreeStyle } from '../../globalStyle';
|
import * as classes from '../../main.css';
|
||||||
|
|
||||||
export interface BaseDocProps {
|
export interface BaseDocProps {
|
||||||
text: string | JSX.Element;
|
text: string | JSX.Element;
|
||||||
@ -22,8 +22,6 @@ export function DocTooltip({
|
|||||||
link,
|
link,
|
||||||
docchildren
|
docchildren
|
||||||
}: Readonly<DocTooltipProps>) {
|
}: Readonly<DocTooltipProps>) {
|
||||||
const { classes } = InvenTreeStyle();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HoverCard
|
<HoverCard
|
||||||
shadow="md"
|
shadow="md"
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
import { rem } from '@mantine/core';
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
import { vars } from '../../theme';
|
||||||
|
|
||||||
|
export const card = style({
|
||||||
|
height: rem(170),
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center'
|
||||||
|
});
|
||||||
|
|
||||||
|
export const title = style({
|
||||||
|
fontWeight: 900,
|
||||||
|
lineHeight: 1.2,
|
||||||
|
fontSize: rem(32),
|
||||||
|
marginTop: 0,
|
||||||
|
[vars.lightSelector]: { color: vars.colors.dark[5] },
|
||||||
|
[vars.darkSelector]: { color: vars.colors.white[0] }
|
||||||
|
});
|
||||||
|
|
||||||
|
export const category = style({
|
||||||
|
opacity: 0.7,
|
||||||
|
fontWeight: 700,
|
||||||
|
[vars.lightSelector]: { color: vars.colors.dark[5] },
|
||||||
|
[vars.darkSelector]: { color: vars.colors.white[0] }
|
||||||
|
});
|
@ -1,54 +1,17 @@
|
|||||||
import { Trans } from '@lingui/macro';
|
import { Trans } from '@lingui/macro';
|
||||||
import { Carousel } from '@mantine/carousel';
|
import { Carousel } from '@mantine/carousel';
|
||||||
import {
|
import { Anchor, Button, Paper, Text, Title, rem } from '@mantine/core';
|
||||||
Anchor,
|
|
||||||
Button,
|
|
||||||
Paper,
|
|
||||||
Text,
|
|
||||||
Title,
|
|
||||||
createStyles,
|
|
||||||
rem
|
|
||||||
} from '@mantine/core';
|
|
||||||
|
|
||||||
import { DocumentationLinkItem } from './DocumentationLinks';
|
import { DocumentationLinkItem } from './DocumentationLinks';
|
||||||
|
import * as classes from './GettingStartedCarousel.css';
|
||||||
import { PlaceholderPill } from './Placeholder';
|
import { PlaceholderPill } from './Placeholder';
|
||||||
|
|
||||||
const useStyles = createStyles((theme) => ({
|
|
||||||
card: {
|
|
||||||
height: rem(170),
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
backgroundSize: 'cover',
|
|
||||||
backgroundPosition: 'center'
|
|
||||||
},
|
|
||||||
|
|
||||||
title: {
|
|
||||||
fontWeight: 900,
|
|
||||||
color:
|
|
||||||
theme.colorScheme === 'dark' ? theme.colors.white : theme.colors.dark,
|
|
||||||
lineHeight: 1.2,
|
|
||||||
fontSize: rem(32),
|
|
||||||
marginTop: 0
|
|
||||||
},
|
|
||||||
|
|
||||||
category: {
|
|
||||||
color:
|
|
||||||
theme.colorScheme === 'dark' ? theme.colors.white : theme.colors.dark,
|
|
||||||
opacity: 0.7,
|
|
||||||
fontWeight: 700
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
function StartedCard({
|
function StartedCard({
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
link,
|
link,
|
||||||
placeholder
|
placeholder
|
||||||
}: DocumentationLinkItem) {
|
}: DocumentationLinkItem) {
|
||||||
const { classes } = useStyles();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper shadow="md" p="xl" radius="md" className={classes.card}>
|
<Paper shadow="md" p="xl" radius="md" className={classes.card}>
|
||||||
<div>
|
<div>
|
||||||
@ -81,10 +44,11 @@ export function GettingStartedCarousel({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Carousel
|
<Carousel
|
||||||
slideSize="50%"
|
slideSize={{ base: '100%', sm: '50%', md: '33.333333%' }}
|
||||||
breakpoints={[{ maxWidth: 'sm', slideSize: '100%', slideGap: rem(2) }]}
|
slideGap={{ base: 0, sm: 'md' }}
|
||||||
slideGap="xl"
|
slidesToScroll={3}
|
||||||
align="start"
|
align="start"
|
||||||
|
loop
|
||||||
>
|
>
|
||||||
{slides}
|
{slides}
|
||||||
</Carousel>
|
</Carousel>
|
||||||
|
@ -43,7 +43,7 @@ export function InfoItem({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
<Text fz="sm" fw={700}>
|
<Text fz="sm" fw={700}>
|
||||||
{name}:
|
{name}:
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -10,7 +10,7 @@ export const InvenTreeLogoHomeButton = forwardRef<HTMLDivElement>(
|
|||||||
return (
|
return (
|
||||||
<div ref={ref} {...props}>
|
<div ref={ref} {...props}>
|
||||||
<NavLink to={'/'}>
|
<NavLink to={'/'}>
|
||||||
<ActionIcon size={28}>
|
<ActionIcon size={28} variant="transparent">
|
||||||
<InvenTreeLogo />
|
<InvenTreeLogo />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Select, SelectItem } from '@mantine/core';
|
import { Select } from '@mantine/core';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { getSupportedLanguages } from '../../contexts/LanguageContext';
|
import { getSupportedLanguages } from '../../contexts/LanguageContext';
|
||||||
@ -10,7 +10,7 @@ export function LanguageSelect({ width = 80 }: { width?: number }) {
|
|||||||
state.language,
|
state.language,
|
||||||
state.setLanguage
|
state.setLanguage
|
||||||
]);
|
]);
|
||||||
const [langOptions, setLangOptions] = useState<SelectItem[]>([]);
|
const [langOptions, setLangOptions] = useState<any[]>([]);
|
||||||
|
|
||||||
// change global language on change
|
// change global language on change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -9,14 +9,18 @@ export function LanguageToggle() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Group
|
<Group
|
||||||
position="center"
|
justify="center"
|
||||||
style={{
|
style={{
|
||||||
border: open === true ? `1px dashed ` : ``,
|
border: open === true ? `1px dashed ` : ``,
|
||||||
margin: open === true ? 2 : 12,
|
margin: open === true ? 2 : 12,
|
||||||
padding: open === true ? 8 : 0
|
padding: open === true ? 8 : 0
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ActionIcon onClick={() => toggle.toggle()} size="lg">
|
<ActionIcon
|
||||||
|
onClick={() => toggle.toggle()}
|
||||||
|
size="lg"
|
||||||
|
variant="transparent"
|
||||||
|
>
|
||||||
<IconLanguage />
|
<IconLanguage />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
{open && (
|
{open && (
|
||||||
|
@ -2,7 +2,7 @@ import { SimpleGrid, Text, UnstyledButton } from '@mantine/core';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { InvenTreeStyle } from '../../globalStyle';
|
import * as classes from '../../main.css';
|
||||||
import { DocTooltip } from './DocTooltip';
|
import { DocTooltip } from './DocTooltip';
|
||||||
|
|
||||||
export interface MenuLinkItem {
|
export interface MenuLinkItem {
|
||||||
@ -50,8 +50,6 @@ export function MenuLinks({
|
|||||||
links: MenuLinkItem[];
|
links: MenuLinkItem[];
|
||||||
highlighted?: boolean;
|
highlighted?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { classes } = InvenTreeStyle();
|
|
||||||
|
|
||||||
const filteredLinks = links.filter(
|
const filteredLinks = links.filter(
|
||||||
(item) => !highlighted || item.highlight === true
|
(item) => !highlighted || item.highlight === true
|
||||||
);
|
);
|
||||||
|
@ -9,7 +9,7 @@ export function PlaceholderPill() {
|
|||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
multiline
|
multiline
|
||||||
width={220}
|
w={220}
|
||||||
withArrow
|
withArrow
|
||||||
label={t`This feature/button/site is a placeholder for a feature that is not implemented, only partial or intended for testing.`}
|
label={t`This feature/button/site is a placeholder for a feature that is not implemented, only partial or intended for testing.`}
|
||||||
>
|
>
|
||||||
@ -31,7 +31,7 @@ export function PlaceholderPanel() {
|
|||||||
title={t`This panel is a placeholder.`}
|
title={t`This panel is a placeholder.`}
|
||||||
icon={<IconInfoCircle />}
|
icon={<IconInfoCircle />}
|
||||||
>
|
>
|
||||||
<Text color="gray">This panel has not yet been implemented</Text>
|
<Text c="gray">This panel has not yet been implemented</Text>
|
||||||
</Alert>
|
</Alert>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
@ -23,9 +23,9 @@ export function ProgressBar(props: Readonly<ProgressBarProps>) {
|
|||||||
}, [props]);
|
}, [props]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={2} style={{ flexGrow: 1, minWidth: '100px' }}>
|
<Stack gap={2} style={{ flexGrow: 1, minWidth: '100px' }}>
|
||||||
{props.progressLabel && (
|
{props.progressLabel && (
|
||||||
<Text align="center" size="xs">
|
<Text ta="center" size="xs">
|
||||||
{props.value} / {props.maximum}
|
{props.value} / {props.maximum}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Text } from '@mantine/core';
|
import { Text } from '@mantine/core';
|
||||||
|
|
||||||
import { InvenTreeStyle } from '../../globalStyle';
|
import * as classes from '../../main.css';
|
||||||
|
|
||||||
export function StylishText({
|
export function StylishText({
|
||||||
children,
|
children,
|
||||||
@ -9,7 +9,6 @@ export function StylishText({
|
|||||||
children: JSX.Element | string;
|
children: JSX.Element | string;
|
||||||
size?: string;
|
size?: string;
|
||||||
}) {
|
}) {
|
||||||
const { classes } = InvenTreeStyle();
|
|
||||||
return (
|
return (
|
||||||
<Text size={size} className={classes.signText} variant="gradient">
|
<Text size={size} className={classes.signText} variant="gradient">
|
||||||
{children}
|
{children}
|
||||||
|
@ -59,7 +59,7 @@ export function AboutInvenTreeModal({
|
|||||||
<tr key={idx}>
|
<tr key={idx}>
|
||||||
<td>{map.title}</td>
|
<td>{map.title}</td>
|
||||||
<td>
|
<td>
|
||||||
<Group position="apart" spacing="xs">
|
<Group justify="space-between" gap="xs">
|
||||||
{alwaysLink ? (
|
{alwaysLink ? (
|
||||||
<Anchor href={data[map.ref]} target="_blank">
|
<Anchor href={data[map.ref]} target="_blank">
|
||||||
{data[map.ref]}
|
{data[map.ref]}
|
||||||
@ -177,7 +177,7 @@ export function AboutInvenTreeModal({
|
|||||||
</tbody>
|
</tbody>
|
||||||
</Table>
|
</Table>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
<CopyButton
|
<CopyButton
|
||||||
value={copyval}
|
value={copyval}
|
||||||
label={<Trans>Copy version information</Trans>}
|
label={<Trans>Copy version information</Trans>}
|
||||||
|
@ -18,7 +18,7 @@ import { apiUrl } from '../../states/ApiState';
|
|||||||
|
|
||||||
export function LicenceView(entries: Readonly<any[]>) {
|
export function LicenceView(entries: Readonly<any[]>) {
|
||||||
return (
|
return (
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
<Divider />
|
<Divider />
|
||||||
{entries?.length > 0 ? (
|
{entries?.length > 0 ? (
|
||||||
<Accordion variant="contained" defaultValue="-">
|
<Accordion variant="contained" defaultValue="-">
|
||||||
@ -28,7 +28,7 @@ export function LicenceView(entries: Readonly<any[]>) {
|
|||||||
value={`entry-${index}`}
|
value={`entry-${index}`}
|
||||||
>
|
>
|
||||||
<Accordion.Control>
|
<Accordion.Control>
|
||||||
<Group position="apart" grow>
|
<Group justify="space-between" grow>
|
||||||
<Text>{entry.name}</Text>
|
<Text>{entry.name}</Text>
|
||||||
<Text>{entry.license}</Text>
|
<Text>{entry.license}</Text>
|
||||||
<Space />
|
<Space />
|
||||||
@ -63,7 +63,7 @@ export function LicenseModal() {
|
|||||||
const rspdata = !data ? [] : Object.keys(data ?? {});
|
const rspdata = !data ? [] : Object.keys(data ?? {});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
<Divider />
|
<Divider />
|
||||||
<LoadingOverlay visible={isFetching} />
|
<LoadingOverlay visible={isFetching} />
|
||||||
{isFetching && (
|
{isFetching && (
|
||||||
|
@ -150,7 +150,7 @@ export function QrCodeModal({
|
|||||||
<Stack>
|
<Stack>
|
||||||
<Group>
|
<Group>
|
||||||
<Text size="sm">{camId?.label}</Text>
|
<Text size="sm">{camId?.label}</Text>
|
||||||
<Space sx={{ flex: 1 }} />
|
<Space style={{ flex: 1 }} />
|
||||||
<Badge>{ScanningEnabled ? t`Scanning` : t`Not scanning`}</Badge>
|
<Badge>{ScanningEnabled ? t`Scanning` : t`Not scanning`}</Badge>
|
||||||
</Group>
|
</Group>
|
||||||
<Container px={0} id="reader" w={'100%'} mih="300px" />
|
<Container px={0} id="reader" w={'100%'} mih="300px" />
|
||||||
@ -162,14 +162,14 @@ export function QrCodeModal({
|
|||||||
<>
|
<>
|
||||||
<Group>
|
<Group>
|
||||||
<Button
|
<Button
|
||||||
sx={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
onClick={() => startScanning()}
|
onClick={() => startScanning()}
|
||||||
disabled={camId != undefined && ScanningEnabled}
|
disabled={camId != undefined && ScanningEnabled}
|
||||||
>
|
>
|
||||||
<Trans>Start scanning</Trans>
|
<Trans>Start scanning</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
sx={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
onClick={() => stopScanning()}
|
onClick={() => stopScanning()}
|
||||||
disabled={!ScanningEnabled}
|
disabled={!ScanningEnabled}
|
||||||
>
|
>
|
||||||
@ -177,11 +177,11 @@ export function QrCodeModal({
|
|||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
{values.length == 0 ? (
|
{values.length == 0 ? (
|
||||||
<Text color={'grey'}>
|
<Text c={'grey'}>
|
||||||
<Trans>No scans yet!</Trans>
|
<Trans>No scans yet!</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<ScrollArea sx={{ height: 200 }} type="auto" offsetScrollbars>
|
<ScrollArea style={{ height: 200 }} type="auto" offsetScrollbars>
|
||||||
{values.map((value, index) => (
|
{values.map((value, index) => (
|
||||||
<div key={index}>{value}</div>
|
<div key={index}>{value}</div>
|
||||||
))}
|
))}
|
||||||
|
@ -137,7 +137,7 @@ export function ServerInfoModal({
|
|||||||
</tbody>
|
</tbody>
|
||||||
</Table>
|
</Table>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Group position="right">
|
<Group justify="right">
|
||||||
<Button
|
<Button
|
||||||
color="red"
|
color="red"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -41,10 +41,14 @@ export function BreadcrumbList({
|
|||||||
}, [breadcrumbs]);
|
}, [breadcrumbs]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper p="3" radius="xs">
|
<Paper p="7" radius="xs" shadow="xs">
|
||||||
<Group spacing="xs">
|
<Group gap="xs">
|
||||||
{navCallback && (
|
{navCallback && (
|
||||||
<ActionIcon key="nav-action" onClick={navCallback}>
|
<ActionIcon
|
||||||
|
key="nav-action"
|
||||||
|
onClick={navCallback}
|
||||||
|
variant="transparent"
|
||||||
|
>
|
||||||
<IconMenu2 />
|
<IconMenu2 />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
)}
|
)}
|
||||||
|
6
src/frontend/src/components/nav/DetailDrawer.css.ts
Normal file
6
src/frontend/src/components/nav/DetailDrawer.css.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const flex = style({
|
||||||
|
display: 'flex',
|
||||||
|
flex: 1
|
||||||
|
});
|
@ -1,19 +1,12 @@
|
|||||||
import {
|
import { ActionIcon, Divider, Drawer, Group, Stack, Text } from '@mantine/core';
|
||||||
ActionIcon,
|
|
||||||
Divider,
|
|
||||||
Drawer,
|
|
||||||
Group,
|
|
||||||
MantineNumberSize,
|
|
||||||
Stack,
|
|
||||||
Text,
|
|
||||||
createStyles
|
|
||||||
} from '@mantine/core';
|
|
||||||
import { IconChevronLeft } from '@tabler/icons-react';
|
import { IconChevronLeft } from '@tabler/icons-react';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { Link, Route, Routes, useNavigate, useParams } from 'react-router-dom';
|
import { Link, Route, Routes, useNavigate, useParams } from 'react-router-dom';
|
||||||
import type { To } from 'react-router-dom';
|
import type { To } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { UiSizeType } from '../../defaults/formatters';
|
||||||
import { useLocalState } from '../../states/LocalState';
|
import { useLocalState } from '../../states/LocalState';
|
||||||
|
import * as classes from './DetailDrawer.css';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param title - drawer title
|
* @param title - drawer title
|
||||||
@ -26,17 +19,10 @@ export interface DrawerProps {
|
|||||||
position?: 'right' | 'left';
|
position?: 'right' | 'left';
|
||||||
renderContent: (id?: string) => React.ReactNode;
|
renderContent: (id?: string) => React.ReactNode;
|
||||||
urlPrefix?: string;
|
urlPrefix?: string;
|
||||||
size?: MantineNumberSize;
|
size?: UiSizeType;
|
||||||
closeOnEscape?: boolean;
|
closeOnEscape?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = createStyles(() => ({
|
|
||||||
flex: {
|
|
||||||
display: 'flex',
|
|
||||||
flex: 1
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
function DetailDrawerComponent({
|
function DetailDrawerComponent({
|
||||||
title,
|
title,
|
||||||
position = 'right',
|
position = 'right',
|
||||||
@ -46,7 +32,6 @@ function DetailDrawerComponent({
|
|||||||
}: Readonly<DrawerProps>) {
|
}: Readonly<DrawerProps>) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const { classes } = useStyles();
|
|
||||||
|
|
||||||
const content = renderContent(id);
|
const content = renderContent(id);
|
||||||
const opened = useMemo(() => !!id && !!content, [id, content]);
|
const opened = useMemo(() => !!id && !!content, [id, content]);
|
||||||
@ -87,7 +72,7 @@ function DetailDrawerComponent({
|
|||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Stack spacing={'xs'} className={classes.flex}>
|
<Stack gap={'xs'} className={classes.flex}>
|
||||||
<Divider />
|
<Divider />
|
||||||
{content}
|
{content}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import { Anchor, Container, Group } from '@mantine/core';
|
import { Anchor, Container, Group } from '@mantine/core';
|
||||||
|
|
||||||
import { footerLinks } from '../../defaults/links';
|
import { footerLinks } from '../../defaults/links';
|
||||||
import { InvenTreeStyle } from '../../globalStyle';
|
import * as classes from '../../main.css';
|
||||||
import { InvenTreeLogoHomeButton } from '../items/InvenTreeLogo';
|
import { InvenTreeLogoHomeButton } from '../items/InvenTreeLogo';
|
||||||
|
|
||||||
export function Footer() {
|
export function Footer() {
|
||||||
const { classes } = InvenTreeStyle();
|
|
||||||
const items = footerLinks.map((link) => (
|
const items = footerLinks.map((link) => (
|
||||||
<Anchor<'a'>
|
<Anchor<'a'>
|
||||||
color="dimmed"
|
c="dimmed"
|
||||||
key={link.key}
|
key={link.key}
|
||||||
href={link.link}
|
href={link.link}
|
||||||
onClick={(event) => event.preventDefault()}
|
onClick={(event) => event.preventDefault()}
|
||||||
|
@ -8,7 +8,7 @@ import { useMatch, useNavigate } from 'react-router-dom';
|
|||||||
import { api } from '../../App';
|
import { api } from '../../App';
|
||||||
import { navTabs as mainNavTabs } from '../../defaults/links';
|
import { navTabs as mainNavTabs } from '../../defaults/links';
|
||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
import { InvenTreeStyle } from '../../globalStyle';
|
import * as classes from '../../main.css';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
import { useLocalState } from '../../states/LocalState';
|
import { useLocalState } from '../../states/LocalState';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
@ -21,7 +21,6 @@ import { NotificationDrawer } from './NotificationDrawer';
|
|||||||
import { SearchDrawer } from './SearchDrawer';
|
import { SearchDrawer } from './SearchDrawer';
|
||||||
|
|
||||||
export function Header() {
|
export function Header() {
|
||||||
const { classes } = InvenTreeStyle();
|
|
||||||
const [setNavigationOpen, navigationOpen] = useLocalState((state) => [
|
const [setNavigationOpen, navigationOpen] = useLocalState((state) => [
|
||||||
state.setNavigationOpen,
|
state.setNavigationOpen,
|
||||||
state.navigationOpen
|
state.navigationOpen
|
||||||
@ -54,12 +53,13 @@ export function Header() {
|
|||||||
limit: 1
|
limit: 1
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let response = await api.get(
|
let response = await api
|
||||||
apiUrl(ApiEndpoints.notifications_list),
|
.get(apiUrl(ApiEndpoints.notifications_list), params)
|
||||||
params
|
.catch(() => {
|
||||||
);
|
return null;
|
||||||
setNotificationCount(response.data.count);
|
});
|
||||||
return response.data;
|
setNotificationCount(response?.data?.count ?? 0);
|
||||||
|
return response?.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
@ -93,28 +93,32 @@ export function Header() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Container className={classes.layoutHeaderSection} size="100%">
|
<Container className={classes.layoutHeaderSection} size="100%">
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
<Group>
|
<Group>
|
||||||
<NavHoverMenu openDrawer={openNavDrawer} />
|
<NavHoverMenu openDrawer={openNavDrawer} />
|
||||||
<NavTabs />
|
<NavTabs />
|
||||||
</Group>
|
</Group>
|
||||||
<Group>
|
<Group>
|
||||||
<ActionIcon onClick={openSearchDrawer}>
|
<ActionIcon onClick={openSearchDrawer} variant="transparent">
|
||||||
<IconSearch />
|
<IconSearch />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
<SpotlightButton />
|
<SpotlightButton />
|
||||||
<ScanButton />
|
<ScanButton />
|
||||||
<ActionIcon onClick={openNotificationDrawer}>
|
<Indicator
|
||||||
<Indicator
|
radius="lg"
|
||||||
radius="lg"
|
size="18"
|
||||||
size="18"
|
label={notificationCount}
|
||||||
label={notificationCount}
|
color="red"
|
||||||
color="red"
|
disabled={notificationCount <= 0}
|
||||||
disabled={notificationCount <= 0}
|
inline
|
||||||
|
>
|
||||||
|
<ActionIcon
|
||||||
|
onClick={openNotificationDrawer}
|
||||||
|
variant="transparent"
|
||||||
>
|
>
|
||||||
<IconBell />
|
<IconBell />
|
||||||
</Indicator>
|
</ActionIcon>
|
||||||
</ActionIcon>
|
</Indicator>
|
||||||
<MainMenu />
|
<MainMenu />
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
@ -124,7 +128,6 @@ export function Header() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function NavTabs() {
|
function NavTabs() {
|
||||||
const { classes } = InvenTreeStyle();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const match = useMatch(':tabName/*');
|
const match = useMatch(':tabName/*');
|
||||||
const tabValue = match?.params.tabName;
|
const tabValue = match?.params.tabName;
|
||||||
@ -134,11 +137,11 @@ function NavTabs() {
|
|||||||
defaultValue="home"
|
defaultValue="home"
|
||||||
classNames={{
|
classNames={{
|
||||||
root: classes.tabs,
|
root: classes.tabs,
|
||||||
tabsList: classes.tabsList,
|
list: classes.tabsList,
|
||||||
tab: classes.tab
|
tab: classes.tab
|
||||||
}}
|
}}
|
||||||
value={tabValue}
|
value={tabValue}
|
||||||
onTabChange={(value) =>
|
onChange={(value) =>
|
||||||
value == '/' ? navigate('/') : navigate(`/${value}`)
|
value == '/' ? navigate('/') : navigate(`/${value}`)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Container, Flex, Space } from '@mantine/core';
|
import { Container, Flex, Space } from '@mantine/core';
|
||||||
import { SpotlightProvider } from '@mantine/spotlight';
|
import { Spotlight, createSpotlight } from '@mantine/spotlight';
|
||||||
import { IconSearch } from '@tabler/icons-react';
|
import { IconSearch } from '@tabler/icons-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Navigate, Outlet, useLocation, useNavigate } from 'react-router-dom';
|
import { Navigate, Outlet, useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { getActions } from '../../defaults/actions';
|
import { getActions } from '../../defaults/actions';
|
||||||
import { InvenTreeStyle } from '../../globalStyle';
|
import * as classes from '../../main.css';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import { Boundary } from '../Boundary';
|
import { Boundary } from '../Boundary';
|
||||||
import { Footer } from './Footer';
|
import { Footer } from './Footer';
|
||||||
@ -25,8 +25,9 @@ export const ProtectedRoute = ({ children }: { children: JSX.Element }) => {
|
|||||||
return children;
|
return children;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const [firstStore, firstSpotlight] = createSpotlight();
|
||||||
|
|
||||||
export default function LayoutComponent() {
|
export default function LayoutComponent() {
|
||||||
const { classes } = InvenTreeStyle();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
@ -38,6 +39,9 @@ export default function LayoutComponent() {
|
|||||||
if (change.length > defaultActions.length) setCustomActions(true);
|
if (change.length > defaultActions.length) setCustomActions(true);
|
||||||
setActions(change);
|
setActions(change);
|
||||||
}
|
}
|
||||||
|
// firstStore.subscribe(actionsAreChanging);
|
||||||
|
|
||||||
|
// clear additional actions on location change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (customActions) {
|
if (customActions) {
|
||||||
setActions(defaultActions);
|
setActions(defaultActions);
|
||||||
@ -47,26 +51,28 @@ export default function LayoutComponent() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ProtectedRoute>
|
<ProtectedRoute>
|
||||||
<SpotlightProvider
|
<Flex direction="column" mih="100vh">
|
||||||
actions={actions}
|
<Header />
|
||||||
onActionsChange={actionsAreChanging}
|
<Container className={classes.layoutContent} size="100%">
|
||||||
searchIcon={<IconSearch size="1.2rem" />}
|
<Boundary label={'layout'}>
|
||||||
searchPlaceholder={t`Search...`}
|
<Outlet />
|
||||||
shortcut={['mod + K', '/']}
|
</Boundary>
|
||||||
nothingFoundMessage={t`Nothing found...`}
|
{/* </ErrorBoundary> */}
|
||||||
>
|
</Container>
|
||||||
<Flex direction="column" mih="100vh">
|
<Space h="xl" />
|
||||||
<Header />
|
<Footer />
|
||||||
<Container className={classes.layoutContent} size="100%">
|
<Spotlight
|
||||||
<Boundary label={'layout'}>
|
actions={actions}
|
||||||
<Outlet />
|
store={firstStore}
|
||||||
</Boundary>
|
highlightQuery
|
||||||
{/* </ErrorBoundary> */}
|
searchProps={{
|
||||||
</Container>
|
leftSection: <IconSearch size="1.2rem" />,
|
||||||
<Space h="xl" />
|
placeholder: t`Search...`
|
||||||
<Footer />
|
}}
|
||||||
</Flex>
|
shortcut={['mod + K', '/']}
|
||||||
</SpotlightProvider>
|
nothingFound={t`Nothing found...`}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -10,26 +10,29 @@ import {
|
|||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { doLogout } from '../../functions/auth';
|
import { doLogout } from '../../functions/auth';
|
||||||
import { InvenTreeStyle } from '../../globalStyle';
|
import * as classes from '../../main.css';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
|
import { vars } from '../../theme';
|
||||||
|
|
||||||
export function MainMenu() {
|
export function MainMenu() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { classes, theme } = InvenTreeStyle();
|
const [user, username] = useUserState((state) => [
|
||||||
const userState = useUserState();
|
state.user,
|
||||||
|
state.username
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu width={260} position="bottom-end">
|
<Menu width={260} position="bottom-end">
|
||||||
<Menu.Target>
|
<Menu.Target>
|
||||||
<UnstyledButton className={classes.layoutHeaderUser}>
|
<UnstyledButton className={classes.layoutHeaderUser}>
|
||||||
<Group spacing={7}>
|
<Group gap={7}>
|
||||||
<Text weight={500} size="sm" sx={{ lineHeight: 1 }} mr={3}>
|
{username() ? (
|
||||||
{userState.username() ? (
|
<Text fw={500} size="sm" style={{ lineHeight: 1 }} mr={3}>
|
||||||
userState.username()
|
{username()}
|
||||||
) : (
|
</Text>
|
||||||
<Skeleton height={20} width={40} radius={theme.defaultRadius} />
|
) : (
|
||||||
)}
|
<Skeleton height={20} width={40} radius={vars.radiusDefault} />
|
||||||
</Text>
|
)}
|
||||||
<IconChevronDown />
|
<IconChevronDown />
|
||||||
</Group>
|
</Group>
|
||||||
</UnstyledButton>
|
</UnstyledButton>
|
||||||
@ -38,22 +41,26 @@ export function MainMenu() {
|
|||||||
<Menu.Label>
|
<Menu.Label>
|
||||||
<Trans>Settings</Trans>
|
<Trans>Settings</Trans>
|
||||||
</Menu.Label>
|
</Menu.Label>
|
||||||
<Menu.Item icon={<IconUserCog />} component={Link} to="/settings/user">
|
<Menu.Item
|
||||||
|
leftSection={<IconUserCog />}
|
||||||
|
component={Link}
|
||||||
|
to="/settings/user"
|
||||||
|
>
|
||||||
<Trans>Account settings</Trans>
|
<Trans>Account settings</Trans>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
{userState.user?.is_staff && (
|
{user?.is_staff && (
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
icon={<IconSettings />}
|
leftSection={<IconSettings />}
|
||||||
component={Link}
|
component={Link}
|
||||||
to="/settings/system"
|
to="/settings/system"
|
||||||
>
|
>
|
||||||
<Trans>System Settings</Trans>
|
<Trans>System Settings</Trans>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
)}
|
)}
|
||||||
{userState.user?.is_staff && <Menu.Divider />}
|
{user?.is_staff && <Menu.Divider />}
|
||||||
{userState.user?.is_staff && (
|
{user?.is_staff && (
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
icon={<IconUserBolt />}
|
leftSection={<IconUserBolt />}
|
||||||
component={Link}
|
component={Link}
|
||||||
to="/settings/admin"
|
to="/settings/admin"
|
||||||
>
|
>
|
||||||
@ -62,7 +69,7 @@ export function MainMenu() {
|
|||||||
)}
|
)}
|
||||||
<Menu.Divider />
|
<Menu.Divider />
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
icon={<IconLogout />}
|
leftSection={<IconLogout />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
doLogout(navigate);
|
doLogout(navigate);
|
||||||
}}
|
}}
|
||||||
|
@ -8,15 +8,17 @@ import {
|
|||||||
HoverCard,
|
HoverCard,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
Text,
|
Text,
|
||||||
UnstyledButton
|
UnstyledButton,
|
||||||
|
useMantineColorScheme
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { IconLayoutSidebar } from '@tabler/icons-react';
|
import { IconLayoutSidebar } from '@tabler/icons-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { menuItems } from '../../defaults/menuItems';
|
import { menuItems } from '../../defaults/menuItems';
|
||||||
import { InvenTreeStyle } from '../../globalStyle';
|
import * as classes from '../../main.css';
|
||||||
import { useServerApiState } from '../../states/ApiState';
|
import { useServerApiState } from '../../states/ApiState';
|
||||||
import { useLocalState } from '../../states/LocalState';
|
import { useLocalState } from '../../states/LocalState';
|
||||||
|
import { vars } from '../../theme';
|
||||||
import { InvenTreeLogo } from '../items/InvenTreeLogo';
|
import { InvenTreeLogo } from '../items/InvenTreeLogo';
|
||||||
import { MenuLinks } from '../items/MenuLinks';
|
import { MenuLinks } from '../items/MenuLinks';
|
||||||
|
|
||||||
@ -27,13 +29,13 @@ export function NavHoverMenu({
|
|||||||
}: {
|
}: {
|
||||||
openDrawer: () => void;
|
openDrawer: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { classes, theme } = InvenTreeStyle();
|
|
||||||
const [hostKey, hostList] = useLocalState((state) => [
|
const [hostKey, hostList] = useLocalState((state) => [
|
||||||
state.hostKey,
|
state.hostKey,
|
||||||
state.hostList
|
state.hostList
|
||||||
]);
|
]);
|
||||||
const [servername] = useServerApiState((state) => [state.server.instance]);
|
const [servername] = useServerApiState((state) => [state.server.instance]);
|
||||||
const [instanceName, setInstanceName] = useState<string>();
|
const [instanceName, setInstanceName] = useState<string>();
|
||||||
|
const { colorScheme } = useMantineColorScheme();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hostKey && hostList[hostKey]) {
|
if (hostKey && hostList[hostKey]) {
|
||||||
@ -55,26 +57,27 @@ export function NavHoverMenu({
|
|||||||
</UnstyledButton>
|
</UnstyledButton>
|
||||||
</HoverCard.Target>
|
</HoverCard.Target>
|
||||||
|
|
||||||
<HoverCard.Dropdown sx={{ overflow: 'hidden' }}>
|
<HoverCard.Dropdown style={{ overflow: 'hidden' }}>
|
||||||
<Group position="apart" px="md">
|
<Group justify="space-between" px="md">
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={openDrawer}
|
onClick={openDrawer}
|
||||||
onMouseOver={openDrawer}
|
onMouseOver={openDrawer}
|
||||||
title={t`Open Navigation`}
|
title={t`Open Navigation`}
|
||||||
|
variant="default"
|
||||||
>
|
>
|
||||||
<IconLayoutSidebar />
|
<IconLayoutSidebar />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
<Group spacing={'xs'}>
|
<Group gap={'xs'}>
|
||||||
{instanceName ? (
|
{instanceName ? (
|
||||||
instanceName
|
instanceName
|
||||||
) : (
|
) : (
|
||||||
<Skeleton height={20} width={40} radius={theme.defaultRadius} />
|
<Skeleton height={20} width={40} radius={vars.radiusDefault} />
|
||||||
)}{' '}
|
)}{' '}
|
||||||
|{' '}
|
|{' '}
|
||||||
{servername ? (
|
{servername ? (
|
||||||
servername
|
servername
|
||||||
) : (
|
) : (
|
||||||
<Skeleton height={20} width={40} radius={theme.defaultRadius} />
|
<Skeleton height={20} width={40} radius={vars.radiusDefault} />
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
<Anchor href="#" fz="xs" onClick={openDrawer}>
|
<Anchor href="#" fz="xs" onClick={openDrawer}>
|
||||||
@ -85,11 +88,13 @@ export function NavHoverMenu({
|
|||||||
<Divider
|
<Divider
|
||||||
my="sm"
|
my="sm"
|
||||||
mx="-md"
|
mx="-md"
|
||||||
color={theme.colorScheme === 'dark' ? 'dark.5' : 'gray.1'}
|
color={
|
||||||
|
colorScheme === 'dark' ? vars.colors.dark[5] : vars.colors.gray[1]
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<MenuLinks links={onlyItems} highlighted={true} />
|
<MenuLinks links={onlyItems} highlighted={true} />
|
||||||
<div className={classes.headerDropdownFooter}>
|
<div className={classes.headerDropdownFooter}>
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
<div>
|
<div>
|
||||||
<Text fw={500} fz="sm">
|
<Text fw={500} fz="sm">
|
||||||
<Trans>Get started</Trans>
|
<Trans>Get started</Trans>
|
||||||
|
@ -12,7 +12,7 @@ import { useEffect, useRef, useState } from 'react';
|
|||||||
|
|
||||||
import { aboutLinks, navDocLinks } from '../../defaults/links';
|
import { aboutLinks, navDocLinks } from '../../defaults/links';
|
||||||
import { menuItems } from '../../defaults/menuItems';
|
import { menuItems } from '../../defaults/menuItems';
|
||||||
import { InvenTreeStyle } from '../../globalStyle';
|
import * as classes from '../../main.css';
|
||||||
import { DocumentationLinks } from '../items/DocumentationLinks';
|
import { DocumentationLinks } from '../items/DocumentationLinks';
|
||||||
import { MenuLinkItem, MenuLinks } from '../items/MenuLinks';
|
import { MenuLinkItem, MenuLinks } from '../items/MenuLinks';
|
||||||
|
|
||||||
@ -27,8 +27,6 @@ export function NavigationDrawer({
|
|||||||
opened: boolean;
|
opened: boolean;
|
||||||
close: () => void;
|
close: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { classes } = InvenTreeStyle();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
opened={opened}
|
opened={opened}
|
||||||
@ -44,7 +42,6 @@ export function NavigationDrawer({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
function DrawerContent() {
|
function DrawerContent() {
|
||||||
const { classes } = InvenTreeStyle();
|
|
||||||
const [scrollHeight, setScrollHeight] = useState(0);
|
const [scrollHeight, setScrollHeight] = useState(0);
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
const { height } = useViewportSize();
|
const { height } = useViewportSize();
|
||||||
|
@ -2,9 +2,11 @@ import { t } from '@lingui/macro';
|
|||||||
import {
|
import {
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
Alert,
|
Alert,
|
||||||
|
Center,
|
||||||
Divider,
|
Divider,
|
||||||
Drawer,
|
Drawer,
|
||||||
Group,
|
Group,
|
||||||
|
Loader,
|
||||||
LoadingOverlay,
|
LoadingOverlay,
|
||||||
Space,
|
Space,
|
||||||
Stack,
|
Stack,
|
||||||
@ -13,6 +15,7 @@ import {
|
|||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { IconBellCheck, IconBellPlus } from '@tabler/icons-react';
|
import { IconBellCheck, IconBellPlus } from '@tabler/icons-react';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { useMemo } from 'react';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { api } from '../../App';
|
import { api } from '../../App';
|
||||||
@ -51,6 +54,10 @@ export function NotificationDrawer({
|
|||||||
refetchOnWindowFocus: false
|
refetchOnWindowFocus: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const hasNotifications: boolean = useMemo(() => {
|
||||||
|
return (notificationQuery.data?.results?.length ?? 0) > 0;
|
||||||
|
}, [notificationQuery.data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
opened={opened}
|
opened={opened}
|
||||||
@ -67,74 +74,80 @@ export function NotificationDrawer({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
title={
|
title={
|
||||||
<Group position="apart" noWrap={true}>
|
<Group justify="space-between" wrap="nowrap">
|
||||||
<StylishText size="lg">{t`Notifications`}</StylishText>
|
<StylishText size="lg">{t`Notifications`}</StylishText>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onClose();
|
onClose();
|
||||||
navigate('/notifications/unread');
|
navigate('/notifications/unread');
|
||||||
}}
|
}}
|
||||||
|
variant="transparent"
|
||||||
>
|
>
|
||||||
<IconBellPlus />
|
<IconBellPlus />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
<Divider />
|
<Divider />
|
||||||
<LoadingOverlay visible={notificationQuery.isFetching} />
|
{!hasNotifications && (
|
||||||
{(notificationQuery.data?.results?.length ?? 0) == 0 && (
|
|
||||||
<Alert color="green">
|
<Alert color="green">
|
||||||
<Text size="sm">{t`You have no unread notifications.`}</Text>
|
<Text size="sm">{t`You have no unread notifications.`}</Text>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
{notificationQuery.data?.results?.map((notification: any) => (
|
{hasNotifications &&
|
||||||
<Group position="apart" key={notification.pk}>
|
notificationQuery.data?.results?.map((notification: any) => (
|
||||||
<Stack spacing="3">
|
<Group justify="space-between" key={notification.pk}>
|
||||||
{notification?.target?.link ? (
|
<Stack gap="3">
|
||||||
<Text
|
{notification?.target?.link ? (
|
||||||
size="sm"
|
<Text
|
||||||
component={Link}
|
size="sm"
|
||||||
to={notification?.target?.link}
|
component={Link}
|
||||||
target="_blank"
|
to={notification?.target?.link}
|
||||||
>
|
target="_blank"
|
||||||
{notification.target?.name ??
|
>
|
||||||
notification.name ??
|
{notification.target?.name ??
|
||||||
t`Notification`}
|
notification.name ??
|
||||||
</Text>
|
t`Notification`}
|
||||||
) : (
|
</Text>
|
||||||
<Text size="sm">
|
) : (
|
||||||
{notification.target?.name ??
|
<Text size="sm">
|
||||||
notification.name ??
|
{notification.target?.name ??
|
||||||
t`Notification`}
|
notification.name ??
|
||||||
</Text>
|
t`Notification`}
|
||||||
)}
|
</Text>
|
||||||
<Text size="xs">{notification.age_human ?? ''}</Text>
|
)}
|
||||||
</Stack>
|
<Text size="xs">{notification.age_human ?? ''}</Text>
|
||||||
<Space />
|
</Stack>
|
||||||
<ActionIcon
|
<Space />
|
||||||
color="gray"
|
<ActionIcon
|
||||||
variant="hover"
|
color="gray"
|
||||||
onClick={() => {
|
variant="hover"
|
||||||
let url = apiUrl(
|
onClick={() => {
|
||||||
ApiEndpoints.notifications_list,
|
let url = apiUrl(
|
||||||
notification.pk
|
ApiEndpoints.notifications_list,
|
||||||
);
|
notification.pk
|
||||||
api
|
);
|
||||||
.patch(url, {
|
api
|
||||||
read: true
|
.patch(url, {
|
||||||
})
|
read: true
|
||||||
.then((response) => {
|
})
|
||||||
notificationQuery.refetch();
|
.then((response) => {
|
||||||
});
|
notificationQuery.refetch();
|
||||||
}}
|
});
|
||||||
>
|
}}
|
||||||
<Tooltip label={t`Mark as read`}>
|
>
|
||||||
<IconBellCheck />
|
<Tooltip label={t`Mark as read`}>
|
||||||
</Tooltip>
|
<IconBellCheck />
|
||||||
</ActionIcon>
|
</Tooltip>
|
||||||
</Group>
|
</ActionIcon>
|
||||||
))}
|
</Group>
|
||||||
|
))}
|
||||||
|
{notificationQuery.isFetching && (
|
||||||
|
<Center>
|
||||||
|
<Loader size="sm" />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
|
@ -33,23 +33,21 @@ export function PageDetail({
|
|||||||
actions
|
actions
|
||||||
}: Readonly<PageDetailInterface>) {
|
}: Readonly<PageDetailInterface>) {
|
||||||
return (
|
return (
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
{breadcrumbs && breadcrumbs.length > 0 && (
|
{breadcrumbs && breadcrumbs.length > 0 && (
|
||||||
<Paper p="xs" radius="xs" shadow="xs">
|
<BreadcrumbList
|
||||||
<BreadcrumbList
|
navCallback={breadcrumbAction}
|
||||||
navCallback={breadcrumbAction}
|
breadcrumbs={breadcrumbs}
|
||||||
breadcrumbs={breadcrumbs}
|
/>
|
||||||
/>
|
|
||||||
</Paper>
|
|
||||||
)}
|
)}
|
||||||
<Paper p="xs" radius="xs" shadow="xs">
|
<Paper p="xs" radius="xs" shadow="xs">
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
<Group position="apart" noWrap={true}>
|
<Group justify="space-between" wrap="nowrap">
|
||||||
<Group position="left" noWrap={true}>
|
<Group justify="left" wrap="nowrap">
|
||||||
{imageUrl && (
|
{imageUrl && (
|
||||||
<ApiImage src={imageUrl} radius="sm" height={64} width={64} />
|
<ApiImage src={imageUrl} radius="sm" h={64} w={64} />
|
||||||
)}
|
)}
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
{title && <StylishText size="lg">{title}</StylishText>}
|
{title && <StylishText size="lg">{title}</StylishText>}
|
||||||
{subtitle && (
|
{subtitle && (
|
||||||
<Text size="md" truncate>
|
<Text size="md" truncate>
|
||||||
@ -60,14 +58,14 @@ export function PageDetail({
|
|||||||
</Group>
|
</Group>
|
||||||
<Space />
|
<Space />
|
||||||
{detail}
|
{detail}
|
||||||
<Group position="right" spacing="xs" noWrap>
|
<Group justify="right" gap="xs" wrap="nowrap">
|
||||||
{badges?.map((badge, idx) => (
|
{badges?.map((badge, idx) => (
|
||||||
<Fragment key={idx}>{badge}</Fragment>
|
<Fragment key={idx}>{badge}</Fragment>
|
||||||
))}
|
))}
|
||||||
</Group>
|
</Group>
|
||||||
<Space />
|
<Space />
|
||||||
{actions && (
|
{actions && (
|
||||||
<Group spacing={5} position="right">
|
<Group gap={5} justify="right">
|
||||||
{actions.map((action, idx) => (
|
{actions.map((action, idx) => (
|
||||||
<Fragment key={idx}>{action}</Fragment>
|
<Fragment key={idx}>{action}</Fragment>
|
||||||
))}
|
))}
|
||||||
|
@ -72,7 +72,7 @@ function BasePanelGroup({
|
|||||||
}, [setLastUsedPanel]);
|
}, [setLastUsedPanel]);
|
||||||
|
|
||||||
// Callback when the active panel changes
|
// Callback when the active panel changes
|
||||||
function handlePanelChange(panel: string) {
|
function handlePanelChange(panel: string | null) {
|
||||||
if (activePanels.findIndex((p) => p.name === panel) === -1) {
|
if (activePanels.findIndex((p) => p.name === panel) === -1) {
|
||||||
setLastUsedPanel('');
|
setLastUsedPanel('');
|
||||||
return navigate('../');
|
return navigate('../');
|
||||||
@ -81,7 +81,7 @@ function BasePanelGroup({
|
|||||||
navigate(`../${panel}`);
|
navigate(`../${panel}`);
|
||||||
|
|
||||||
// Optionally call external callback hook
|
// Optionally call external callback hook
|
||||||
if (onPanelChange) {
|
if (panel && onPanelChange) {
|
||||||
onPanelChange(panel);
|
onPanelChange(panel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,10 +109,10 @@ function BasePanelGroup({
|
|||||||
<Tabs
|
<Tabs
|
||||||
value={panel}
|
value={panel}
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
onTabChange={handlePanelChange}
|
onChange={handlePanelChange}
|
||||||
keepMounted={false}
|
keepMounted={false}
|
||||||
>
|
>
|
||||||
<Tabs.List position="left">
|
<Tabs.List justify="left">
|
||||||
{panels.map(
|
{panels.map(
|
||||||
(panel) =>
|
(panel) =>
|
||||||
!panel.hidden && (
|
!panel.hidden && (
|
||||||
@ -125,7 +125,7 @@ function BasePanelGroup({
|
|||||||
p="xs"
|
p="xs"
|
||||||
value={panel.name}
|
value={panel.name}
|
||||||
// icon={(<InvenTreeIcon icon={panel.name}/>)} // Enable when implementing Icon manager everywhere
|
// icon={(<InvenTreeIcon icon={panel.name}/>)} // Enable when implementing Icon manager everywhere
|
||||||
icon={panel.icon}
|
leftSection={panel.icon}
|
||||||
hidden={panel.hidden}
|
hidden={panel.hidden}
|
||||||
disabled={panel.disabled}
|
disabled={panel.disabled}
|
||||||
style={{ cursor: panel.disabled ? 'unset' : 'pointer' }}
|
style={{ cursor: panel.disabled ? 'unset' : 'pointer' }}
|
||||||
@ -141,6 +141,8 @@ function BasePanelGroup({
|
|||||||
paddingLeft: '10px'
|
paddingLeft: '10px'
|
||||||
}}
|
}}
|
||||||
onClick={() => setExpanded(!expanded)}
|
onClick={() => setExpanded(!expanded)}
|
||||||
|
variant="transparent"
|
||||||
|
size="md"
|
||||||
>
|
>
|
||||||
{expanded ? (
|
{expanded ? (
|
||||||
<IconLayoutSidebarLeftCollapse opacity={0.5} />
|
<IconLayoutSidebarLeftCollapse opacity={0.5} />
|
||||||
@ -162,7 +164,7 @@ function BasePanelGroup({
|
|||||||
width: '100%'
|
width: '100%'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack spacing="md">
|
<Stack gap="md">
|
||||||
{panel.showHeadline !== false && (
|
{panel.showHeadline !== false && (
|
||||||
<>
|
<>
|
||||||
<StylishText size="xl">{panel.label}</StylishText>
|
<StylishText size="xl">{panel.label}</StylishText>
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
LoadingOverlay,
|
LoadingOverlay,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
useMantineTheme
|
useMantineColorScheme
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { ReactTree, ThemeSettings } from '@naisutech/react-tree';
|
import { ReactTree, ThemeSettings } from '@naisutech/react-tree';
|
||||||
import {
|
import {
|
||||||
@ -20,6 +20,7 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import { api } from '../../App';
|
import { api } from '../../App';
|
||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
|
import { theme, vars } from '../../theme';
|
||||||
import { StylishText } from '../items/StylishText';
|
import { StylishText } from '../items/StylishText';
|
||||||
|
|
||||||
export function PartCategoryTree({
|
export function PartCategoryTree({
|
||||||
@ -59,9 +60,9 @@ export function PartCategoryTree({
|
|||||||
function renderNode({ node }: { node: any }) {
|
function renderNode({ node }: { node: any }) {
|
||||||
return (
|
return (
|
||||||
<Group
|
<Group
|
||||||
position="apart"
|
justify="space-between"
|
||||||
key={node.id}
|
key={node.id}
|
||||||
noWrap={true}
|
wrap="nowrap"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onClose();
|
onClose();
|
||||||
navigate(`/part/category/${node.id}`);
|
navigate(`/part/category/${node.id}`);
|
||||||
@ -80,57 +81,61 @@ export function PartCategoryTree({
|
|||||||
return open ? <IconChevronDown /> : <IconChevronRight />;
|
return open ? <IconChevronDown /> : <IconChevronRight />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mantineTheme = useMantineTheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
|
|
||||||
const themes: ThemeSettings = useMemo(() => {
|
const themes: ThemeSettings = useMemo(() => {
|
||||||
const currentTheme =
|
const currentTheme =
|
||||||
mantineTheme.colorScheme === 'dark'
|
colorScheme === 'dark'
|
||||||
? mantineTheme.colorScheme
|
? vars.colors.defaultColor
|
||||||
: mantineTheme.primaryColor;
|
: vars.colors.primaryColors;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dark: {
|
dark: {
|
||||||
text: {
|
text: {
|
||||||
...mantineTheme.fn.fontStyles()
|
fontFamily: vars.fontFamily,
|
||||||
|
//fontSize: vars.fontSizes.md,
|
||||||
|
color: vars.colors.text
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
height: '2.5rem',
|
height: '2.5rem',
|
||||||
folder: {
|
folder: {
|
||||||
selectedBgColor: mantineTheme.colors[currentTheme][4],
|
selectedBgColor: currentTheme[4],
|
||||||
hoverBgColor: mantineTheme.colors[currentTheme][6]
|
hoverBgColor: currentTheme[6]
|
||||||
},
|
},
|
||||||
leaf: {
|
leaf: {
|
||||||
selectedBgColor: mantineTheme.colors[currentTheme][4],
|
selectedBgColor: currentTheme[4],
|
||||||
hoverBgColor: mantineTheme.colors[currentTheme][6]
|
hoverBgColor: currentTheme[6]
|
||||||
},
|
},
|
||||||
icons: {
|
icons: {
|
||||||
folderColor: mantineTheme.colors[currentTheme][3],
|
folderColor: currentTheme[3],
|
||||||
leafColor: mantineTheme.colors[currentTheme][3]
|
leafColor: currentTheme[3]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
light: {
|
light: {
|
||||||
text: {
|
text: {
|
||||||
...mantineTheme.fn.fontStyles()
|
fontFamily: vars.fontFamily,
|
||||||
|
//fontSize: vars.fontSizes.md,
|
||||||
|
color: vars.colors.text
|
||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
height: '2.5rem',
|
height: '2.5rem',
|
||||||
folder: {
|
folder: {
|
||||||
selectedBgColor: mantineTheme.colors[currentTheme][4],
|
selectedBgColor: currentTheme[4],
|
||||||
hoverBgColor: mantineTheme.colors[currentTheme][2]
|
hoverBgColor: currentTheme[2]
|
||||||
},
|
},
|
||||||
leaf: {
|
leaf: {
|
||||||
selectedBgColor: mantineTheme.colors[currentTheme][4],
|
selectedBgColor: currentTheme[4],
|
||||||
hoverBgColor: mantineTheme.colors[currentTheme][2]
|
hoverBgColor: currentTheme[2]
|
||||||
},
|
},
|
||||||
icons: {
|
icons: {
|
||||||
folderColor: mantineTheme.colors[currentTheme][8],
|
folderColor: currentTheme[8],
|
||||||
leafColor: mantineTheme.colors[currentTheme][6]
|
leafColor: currentTheme[6]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [mantineTheme]);
|
}, [theme]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
@ -148,13 +153,13 @@ export function PartCategoryTree({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
title={
|
title={
|
||||||
<Group position="left" p="ms" spacing="md" noWrap={true}>
|
<Group justify="left" p="ms" gap="md" wrap="nowrap">
|
||||||
<IconSitemap />
|
<IconSitemap />
|
||||||
<StylishText size="lg">{t`Part Categories`}</StylishText>
|
<StylishText size="lg">{t`Part Categories`}</StylishText>
|
||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
<LoadingOverlay visible={treeQuery.isFetching} />
|
<LoadingOverlay visible={treeQuery.isFetching} />
|
||||||
<ReactTree
|
<ReactTree
|
||||||
nodes={treeQuery.data ?? []}
|
nodes={treeQuery.data ?? []}
|
||||||
@ -162,7 +167,7 @@ export function PartCategoryTree({
|
|||||||
RenderIcon={renderIcon}
|
RenderIcon={renderIcon}
|
||||||
defaultSelectedNodes={selectedCategory ? [selectedCategory] : []}
|
defaultSelectedNodes={selectedCategory ? [selectedCategory] : []}
|
||||||
showEmptyItems={false}
|
showEmptyItems={false}
|
||||||
theme={mantineTheme.colorScheme}
|
theme={colorScheme}
|
||||||
themes={themes}
|
themes={themes}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -69,10 +69,10 @@ function QueryResultGroup({
|
|||||||
return (
|
return (
|
||||||
<Paper shadow="sm" radius="xs" p="md" key={`paper-${query.model}`}>
|
<Paper shadow="sm" radius="xs" p="md" key={`paper-${query.model}`}>
|
||||||
<Stack key={`stack-${query.model}`}>
|
<Stack key={`stack-${query.model}`}>
|
||||||
<Group position="apart" noWrap={true}>
|
<Group justify="space-between" wrap="nowrap">
|
||||||
<Group position="left" spacing={5} noWrap={true}>
|
<Group justify="left" gap={5} wrap="nowrap">
|
||||||
<Text size="lg">{model.label_multiple}</Text>
|
<Text size="lg">{model.label_multiple}</Text>
|
||||||
<Text size="sm" italic>
|
<Text size="sm" style={{ fontStyle: 'italic' }}>
|
||||||
{' '}
|
{' '}
|
||||||
- {query.results.count} <Trans>results</Trans>
|
- {query.results.count} <Trans>results</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
@ -332,13 +332,13 @@ export function SearchDrawer({
|
|||||||
withCloseButton={false}
|
withCloseButton={false}
|
||||||
styles={{ header: { width: '100%' }, title: { width: '100%' } }}
|
styles={{ header: { width: '100%' }, title: { width: '100%' } }}
|
||||||
title={
|
title={
|
||||||
<Group position="apart" spacing={1} noWrap={true}>
|
<Group justify="space-between" gap={1} wrap="nowrap">
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder={t`Enter search text`}
|
placeholder={t`Enter search text`}
|
||||||
radius="xs"
|
radius="xs"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(event) => setValue(event.currentTarget.value)}
|
onChange={(event) => setValue(event.currentTarget.value)}
|
||||||
icon={<IconSearch size="0.8rem" />}
|
leftSection={<IconSearch size="0.8rem" />}
|
||||||
rightSection={
|
rightSection={
|
||||||
value && (
|
value && (
|
||||||
<IconBackspace color="red" onClick={() => setValue('')} />
|
<IconBackspace color="red" onClick={() => setValue('')} />
|
||||||
@ -394,7 +394,7 @@ export function SearchDrawer({
|
|||||||
</Center>
|
</Center>
|
||||||
)}
|
)}
|
||||||
{!searchQuery.isFetching && !searchQuery.isError && (
|
{!searchQuery.isFetching && !searchQuery.isError && (
|
||||||
<Stack spacing="md">
|
<Stack gap="md">
|
||||||
{queryResults.map((query, idx) => (
|
{queryResults.map((query, idx) => (
|
||||||
<QueryResultGroup
|
<QueryResultGroup
|
||||||
key={idx}
|
key={idx}
|
||||||
|
@ -24,7 +24,7 @@ export function SettingsHeader({
|
|||||||
switch_link
|
switch_link
|
||||||
}: Readonly<SettingsHeaderInterface>) {
|
}: Readonly<SettingsHeaderInterface>) {
|
||||||
return (
|
return (
|
||||||
<Stack spacing="0" ml={'sm'}>
|
<Stack gap="0" ml={'sm'}>
|
||||||
<Group>
|
<Group>
|
||||||
<Title order={3}>{title}</Title>
|
<Title order={3}>{title}</Title>
|
||||||
{shorthand && <Text c="dimmed">({shorthand})</Text>}
|
{shorthand && <Text c="dimmed">({shorthand})</Text>}
|
||||||
|
@ -51,9 +51,9 @@ export function StockLocationTree({
|
|||||||
function renderNode({ node }: { node: any }) {
|
function renderNode({ node }: { node: any }) {
|
||||||
return (
|
return (
|
||||||
<Group
|
<Group
|
||||||
position="apart"
|
justify="space-between"
|
||||||
key={node.id}
|
key={node.id}
|
||||||
noWrap={true}
|
wrap="nowrap"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onClose();
|
onClose();
|
||||||
navigate(`/stock/location/${node.id}`);
|
navigate(`/stock/location/${node.id}`);
|
||||||
@ -88,13 +88,13 @@ export function StockLocationTree({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
title={
|
title={
|
||||||
<Group position="left" noWrap={true} spacing="md" p="md">
|
<Group justify="left" wrap="nowrap" gap="md" p="md">
|
||||||
<IconSitemap />
|
<IconSitemap />
|
||||||
<StylishText size="lg">{t`Stock Locations`}</StylishText>
|
<StylishText size="lg">{t`Stock Locations`}</StylishText>
|
||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
<LoadingOverlay visible={treeQuery.isFetching} />
|
<LoadingOverlay visible={treeQuery.isFetching} />
|
||||||
<ReactTree
|
<ReactTree
|
||||||
nodes={treeQuery.data ?? []}
|
nodes={treeQuery.data ?? []}
|
||||||
|
@ -112,8 +112,8 @@ export function RenderInlineModel({
|
|||||||
// TODO: Handle URL
|
// TODO: Handle URL
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group spacing="xs" position="apart" noWrap={true}>
|
<Group gap="xs" justify="space-between" wrap="nowrap">
|
||||||
<Group spacing="xs" position="left" noWrap={true}>
|
<Group gap="xs" justify="left" wrap="nowrap">
|
||||||
{image && Thumbnail({ src: image, size: 18 })}
|
{image && Thumbnail({ src: image, size: 18 })}
|
||||||
<Text size="sm">{primary}</Text>
|
<Text size="sm">{primary}</Text>
|
||||||
{secondary && <Text size="xs">{secondary}</Text>}
|
{secondary && <Text size="xs">{secondary}</Text>}
|
||||||
@ -121,7 +121,7 @@ export function RenderInlineModel({
|
|||||||
{suffix && (
|
{suffix && (
|
||||||
<>
|
<>
|
||||||
<Space />
|
<Space />
|
||||||
<Text size="xs">{suffix}</Text>
|
<div style={{ fontSize: 'xs', lineHeight: 'xs' }}>{suffix}</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
Stack,
|
Stack,
|
||||||
Switch,
|
Switch,
|
||||||
Text,
|
Text,
|
||||||
useMantineTheme
|
useMantineColorScheme
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { showNotification } from '@mantine/notifications';
|
import { showNotification } from '@mantine/notifications';
|
||||||
import { IconEdit } from '@tabler/icons-react';
|
import { IconEdit } from '@tabler/icons-react';
|
||||||
@ -19,6 +19,7 @@ import { openModalApiForm } from '../../functions/forms';
|
|||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
import { SettingsStateProps } from '../../states/SettingsState';
|
import { SettingsStateProps } from '../../states/SettingsState';
|
||||||
import { Setting, SettingType } from '../../states/states';
|
import { Setting, SettingType } from '../../states/states';
|
||||||
|
import { vars } from '../../theme';
|
||||||
import { ApiFormFieldType } from '../forms/fields/ApiFormField';
|
import { ApiFormFieldType } from '../forms/fields/ApiFormField';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -137,7 +138,7 @@ function SettingValue({
|
|||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return valueText ? (
|
return valueText ? (
|
||||||
<Group spacing="xs" position="right">
|
<Group gap="xs" justify="right">
|
||||||
<Space />
|
<Space />
|
||||||
<Button variant="subtle" onClick={onEditButton}>
|
<Button variant="subtle" onClick={onEditButton}>
|
||||||
{valueText}
|
{valueText}
|
||||||
@ -165,20 +166,18 @@ export function SettingItem({
|
|||||||
shaded: boolean;
|
shaded: boolean;
|
||||||
onChange?: () => void;
|
onChange?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const theme = useMantineTheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
|
|
||||||
const style: Record<string, string> = { paddingLeft: '8px' };
|
const style: Record<string, string> = { paddingLeft: '8px' };
|
||||||
if (shaded) {
|
if (shaded) {
|
||||||
style['backgroundColor'] =
|
style['backgroundColor'] =
|
||||||
theme.colorScheme === 'light'
|
colorScheme === 'light' ? vars.colors.gray[1] : vars.colors.gray[9];
|
||||||
? theme.colors.gray[1]
|
|
||||||
: theme.colors.gray[9];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper style={style}>
|
<Paper style={style}>
|
||||||
<Group position="apart" p="3">
|
<Group justify="space-between" p="3">
|
||||||
<Stack spacing="2" p="4px">
|
<Stack gap="2" p="4px">
|
||||||
<Text>
|
<Text>
|
||||||
{setting.name}
|
{setting.name}
|
||||||
{setting.required ? ' *' : ''}
|
{setting.required ? ' *' : ''}
|
||||||
|
@ -35,7 +35,7 @@ export function SettingList({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
{(keys || allKeys).map((key, i) => {
|
{(keys || allKeys).map((key, i) => {
|
||||||
const setting = settingsState?.settings?.find(
|
const setting = settingsState?.settings?.find(
|
||||||
(s: any) => s.key === key
|
(s: any) => s.key === key
|
||||||
@ -51,7 +51,7 @@ export function SettingList({
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Text size="sm" italic color="red">
|
<Text size="sm" style={{ fontStyle: 'italic' }} color="red">
|
||||||
Setting {key} not found
|
Setting {key} not found
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
@ -59,7 +59,7 @@ export function SettingList({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{(keys || allKeys).length === 0 && (
|
{(keys || allKeys).length === 0 && (
|
||||||
<Text italic>
|
<Text style={{ fontStyle: 'italic' }}>
|
||||||
<Trans>No settings specified</Trans>
|
<Trans>No settings specified</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
import { Trans } from '@lingui/macro';
|
import { Trans } from '@lingui/macro';
|
||||||
import { Button, Stack, Title } from '@mantine/core';
|
import { Button, Stack, Title, useMantineColorScheme } from '@mantine/core';
|
||||||
import { IconExternalLink } from '@tabler/icons-react';
|
import { IconExternalLink } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
import { vars } from '../../theme';
|
||||||
|
|
||||||
export default function FeedbackWidget() {
|
export default function FeedbackWidget() {
|
||||||
|
const { colorScheme } = useMantineColorScheme();
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
sx={(theme) => ({
|
style={{
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
theme.colorScheme === 'dark'
|
colorScheme === 'dark' ? vars.colors.gray[9] : vars.colors.gray[1],
|
||||||
? theme.colors.gray[9]
|
borderRadius: vars.radius.md
|
||||||
: theme.colors.gray[1],
|
}}
|
||||||
borderRadius: theme.radius.md
|
|
||||||
})}
|
|
||||||
p={15}
|
p={15}
|
||||||
>
|
>
|
||||||
<Title order={5}>
|
<Title order={5}>
|
||||||
@ -26,7 +27,7 @@ export default function FeedbackWidget() {
|
|||||||
component="a"
|
component="a"
|
||||||
href="https://github.com/inventree/InvenTree/discussions/5328"
|
href="https://github.com/inventree/InvenTree/discussions/5328"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
leftIcon={<IconExternalLink size="0.9rem" />}
|
leftSection={<IconExternalLink size="0.9rem" />}
|
||||||
>
|
>
|
||||||
<Trans>Provide Feedback</Trans>
|
<Trans>Provide Feedback</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
|
16
src/frontend/src/components/widgets/WidgetLayout.css.ts
Normal file
16
src/frontend/src/components/widgets/WidgetLayout.css.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
import { vars } from '../../theme';
|
||||||
|
|
||||||
|
export const backgroundItem = style({
|
||||||
|
maxWidth: '100%',
|
||||||
|
padding: '8px',
|
||||||
|
boxShadow: vars.shadows.md,
|
||||||
|
[vars.lightSelector]: { backgroundColor: vars.colors.white },
|
||||||
|
[vars.darkSelector]: { backgroundColor: vars.colors.dark[5] }
|
||||||
|
});
|
||||||
|
|
||||||
|
export const baseItem = style({
|
||||||
|
maxWidth: '100%',
|
||||||
|
padding: '8px'
|
||||||
|
});
|
@ -5,8 +5,7 @@ import {
|
|||||||
Group,
|
Group,
|
||||||
Indicator,
|
Indicator,
|
||||||
Menu,
|
Menu,
|
||||||
Text,
|
Text
|
||||||
createStyles
|
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useDisclosure, useHotkeys } from '@mantine/hooks';
|
import { useDisclosure, useHotkeys } from '@mantine/hooks';
|
||||||
import {
|
import {
|
||||||
@ -19,6 +18,8 @@ import {
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Responsive, WidthProvider } from 'react-grid-layout';
|
import { Responsive, WidthProvider } from 'react-grid-layout';
|
||||||
|
|
||||||
|
import * as classes from './WidgetLayout.css';
|
||||||
|
|
||||||
const ReactGridLayout = WidthProvider(Responsive);
|
const ReactGridLayout = WidthProvider(Responsive);
|
||||||
|
|
||||||
interface LayoutStorage {
|
interface LayoutStorage {
|
||||||
@ -27,21 +28,6 @@ interface LayoutStorage {
|
|||||||
|
|
||||||
const compactType = 'vertical';
|
const compactType = 'vertical';
|
||||||
|
|
||||||
const useItemStyle = createStyles((theme) => ({
|
|
||||||
backgroundItem: {
|
|
||||||
backgroundColor:
|
|
||||||
theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.white,
|
|
||||||
maxWidth: '100%',
|
|
||||||
padding: '8px',
|
|
||||||
boxShadow: theme.shadows.md
|
|
||||||
},
|
|
||||||
|
|
||||||
baseItem: {
|
|
||||||
maxWidth: '100%',
|
|
||||||
padding: '8px'
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
export interface LayoutItemType {
|
export interface LayoutItemType {
|
||||||
i: number;
|
i: number;
|
||||||
val: string | JSX.Element | JSX.Element[] | (() => JSX.Element);
|
val: string | JSX.Element | JSX.Element[] | (() => JSX.Element);
|
||||||
@ -66,7 +52,6 @@ export function WidgetLayout({
|
|||||||
const [layouts, setLayouts] = useState({});
|
const [layouts, setLayouts] = useState({});
|
||||||
const [editable, setEditable] = useDisclosure(false);
|
const [editable, setEditable] = useDisclosure(false);
|
||||||
const [boxShown, setBoxShown] = useDisclosure(true);
|
const [boxShown, setBoxShown] = useDisclosure(true);
|
||||||
const { classes } = useItemStyle();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let layout = getFromLS('layouts') || [];
|
let layout = getFromLS('layouts') || [];
|
||||||
@ -155,7 +140,7 @@ function WidgetControlBar({
|
|||||||
useHotkeys([['mod+E', () => editFnc()]]);
|
useHotkeys([['mod+E', () => editFnc()]]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group position="right">
|
<Group justify="right">
|
||||||
<Menu
|
<Menu
|
||||||
shadow="md"
|
shadow="md"
|
||||||
width={200}
|
width={200}
|
||||||
@ -181,13 +166,13 @@ function WidgetControlBar({
|
|||||||
<Trans>Layout</Trans>
|
<Trans>Layout</Trans>
|
||||||
</Menu.Label>
|
</Menu.Label>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
icon={<IconArrowBackUpDouble size={14} />}
|
leftSection={<IconArrowBackUpDouble size={14} />}
|
||||||
onClick={resetLayout}
|
onClick={resetLayout}
|
||||||
>
|
>
|
||||||
<Trans>Reset Layout</Trans>
|
<Trans>Reset Layout</Trans>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
icon={
|
leftSection={
|
||||||
<IconLayout2 size={14} color={editable ? 'red' : undefined} />
|
<IconLayout2 size={14} color={editable ? 'red' : undefined} />
|
||||||
}
|
}
|
||||||
onClick={editFnc}
|
onClick={editFnc}
|
||||||
@ -206,7 +191,7 @@ function WidgetControlBar({
|
|||||||
<Trans>Appearance</Trans>
|
<Trans>Appearance</Trans>
|
||||||
</Menu.Label>
|
</Menu.Label>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
icon={
|
leftSection={
|
||||||
boxShown ? (
|
boxShown ? (
|
||||||
<IconSquareCheck size={14} />
|
<IconSquareCheck size={14} />
|
||||||
) : (
|
) : (
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
import { QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClientProvider } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { queryClient } from '../App';
|
import { queryClient } from '../App';
|
||||||
import { LanguageContext } from './LanguageContext';
|
|
||||||
import { ThemeContext } from './ThemeContext';
|
import { ThemeContext } from './ThemeContext';
|
||||||
|
|
||||||
export const BaseContext = ({ children }: { children: any }) => {
|
export const BaseContext = ({ children }: { children: any }) => {
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<LanguageContext>
|
<ThemeContext>{children}</ThemeContext>
|
||||||
<ThemeContext>{children}</ThemeContext>
|
|
||||||
</LanguageContext>
|
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import {
|
import { MantineProvider, createTheme } from '@mantine/core';
|
||||||
ColorScheme,
|
|
||||||
ColorSchemeProvider,
|
|
||||||
MantineProvider,
|
|
||||||
MantineThemeOverride
|
|
||||||
} from '@mantine/core';
|
|
||||||
import { useColorScheme, useLocalStorage } from '@mantine/hooks';
|
|
||||||
import { ModalsProvider } from '@mantine/modals';
|
import { ModalsProvider } from '@mantine/modals';
|
||||||
import { Notifications } from '@mantine/notifications';
|
import { Notifications } from '@mantine/notifications';
|
||||||
|
|
||||||
@ -14,36 +8,24 @@ import { LicenseModal } from '../components/modals/LicenseModal';
|
|||||||
import { QrCodeModal } from '../components/modals/QrCodeModal';
|
import { QrCodeModal } from '../components/modals/QrCodeModal';
|
||||||
import { ServerInfoModal } from '../components/modals/ServerInfoModal';
|
import { ServerInfoModal } from '../components/modals/ServerInfoModal';
|
||||||
import { useLocalState } from '../states/LocalState';
|
import { useLocalState } from '../states/LocalState';
|
||||||
|
import { LanguageContext } from './LanguageContext';
|
||||||
|
import { colorSchema } from './colorSchema';
|
||||||
|
|
||||||
export function ThemeContext({ children }: { children: JSX.Element }) {
|
export function ThemeContext({ children }: { children: JSX.Element }) {
|
||||||
const [primaryColor, whiteColor, blackColor, radius, loader] = useLocalState(
|
const [primaryColor, whiteColor, blackColor, radius] = useLocalState(
|
||||||
(state) => [
|
(state) => [
|
||||||
state.primaryColor,
|
state.primaryColor,
|
||||||
state.whiteColor,
|
state.whiteColor,
|
||||||
state.blackColor,
|
state.blackColor,
|
||||||
state.radius,
|
state.radius
|
||||||
state.loader
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Color Scheme
|
|
||||||
const preferredColorScheme = useColorScheme();
|
|
||||||
const [colorScheme, setColorScheme] = useLocalStorage<ColorScheme>({
|
|
||||||
key: 'scheme',
|
|
||||||
defaultValue: preferredColorScheme
|
|
||||||
});
|
|
||||||
const toggleColorScheme = (value?: ColorScheme) => {
|
|
||||||
setColorScheme(value || (colorScheme === 'dark' ? 'light' : 'dark'));
|
|
||||||
myTheme.colorScheme = colorScheme;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Theme
|
// Theme
|
||||||
const myTheme: MantineThemeOverride = {
|
const myTheme = createTheme({
|
||||||
colorScheme: colorScheme,
|
|
||||||
primaryColor: primaryColor,
|
primaryColor: primaryColor,
|
||||||
white: whiteColor,
|
white: whiteColor,
|
||||||
black: blackColor,
|
black: blackColor,
|
||||||
loader: loader,
|
|
||||||
defaultRadius: radius,
|
defaultRadius: radius,
|
||||||
breakpoints: {
|
breakpoints: {
|
||||||
xs: '30em',
|
xs: '30em',
|
||||||
@ -52,15 +34,11 @@ export function ThemeContext({ children }: { children: JSX.Element }) {
|
|||||||
lg: '74em',
|
lg: '74em',
|
||||||
xl: '90em'
|
xl: '90em'
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ColorSchemeProvider
|
<MantineProvider theme={myTheme} colorSchemeManager={colorSchema}>
|
||||||
colorScheme={colorScheme}
|
<LanguageContext>
|
||||||
toggleColorScheme={toggleColorScheme}
|
|
||||||
>
|
|
||||||
<MantineProvider theme={myTheme} withGlobalStyles withNormalizeCSS>
|
|
||||||
<Notifications />
|
|
||||||
<ModalsProvider
|
<ModalsProvider
|
||||||
labels={{ confirm: t`Submit`, cancel: t`Cancel` }}
|
labels={{ confirm: t`Submit`, cancel: t`Cancel` }}
|
||||||
modals={{
|
modals={{
|
||||||
@ -70,9 +48,10 @@ export function ThemeContext({ children }: { children: JSX.Element }) {
|
|||||||
license: LicenseModal
|
license: LicenseModal
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Notifications />
|
||||||
{children}
|
{children}
|
||||||
</ModalsProvider>
|
</ModalsProvider>
|
||||||
</MantineProvider>
|
</LanguageContext>
|
||||||
</ColorSchemeProvider>
|
</MantineProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
67
src/frontend/src/contexts/colorSchema.tsx
Normal file
67
src/frontend/src/contexts/colorSchema.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import {
|
||||||
|
MantineColorScheme,
|
||||||
|
MantineColorSchemeManager,
|
||||||
|
isMantineColorScheme
|
||||||
|
} from '@mantine/core';
|
||||||
|
|
||||||
|
export interface LocalStorageColorSchemeManagerOptions {
|
||||||
|
/** Local storage key used to retrieve value with `localStorage.getItem(key)`, `mantine-color-scheme` by default */
|
||||||
|
key?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function localStorageColorSchemeManager({
|
||||||
|
key = 'mantine-color-scheme'
|
||||||
|
}: LocalStorageColorSchemeManagerOptions = {}): MantineColorSchemeManager {
|
||||||
|
let handleStorageEvent: (event: StorageEvent) => void;
|
||||||
|
|
||||||
|
return {
|
||||||
|
get: (defaultValue) => {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return (
|
||||||
|
(window.localStorage.getItem(key) as MantineColorScheme) ||
|
||||||
|
defaultValue
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
set: (value) => {
|
||||||
|
try {
|
||||||
|
window.localStorage.setItem(key, value);
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn(
|
||||||
|
'[@mantine/core] Local storage color scheme manager was unable to save color scheme.',
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
subscribe: (onUpdate) => {
|
||||||
|
handleStorageEvent = (event) => {
|
||||||
|
if (event.storageArea === window.localStorage && event.key === key) {
|
||||||
|
isMantineColorScheme(event.newValue) && onUpdate(event.newValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('storage', handleStorageEvent);
|
||||||
|
},
|
||||||
|
|
||||||
|
unsubscribe: () => {
|
||||||
|
window.removeEventListener('storage', handleStorageEvent);
|
||||||
|
},
|
||||||
|
|
||||||
|
clear: () => {
|
||||||
|
window.localStorage.removeItem(key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const colorSchema = localStorageColorSchemeManager({
|
||||||
|
key: 'scheme'
|
||||||
|
});
|
@ -1,5 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import type { SpotlightAction } from '@mantine/spotlight';
|
import type { SpotlightActionData } from '@mantine/spotlight';
|
||||||
import { IconHome, IconLink, IconPointer } from '@tabler/icons-react';
|
import { IconHome, IconLink, IconPointer } from '@tabler/icons-react';
|
||||||
import { NavigateFunction } from 'react-router-dom';
|
import { NavigateFunction } from 'react-router-dom';
|
||||||
|
|
||||||
@ -10,48 +10,55 @@ import { menuItems } from './menuItems';
|
|||||||
export function getActions(navigate: NavigateFunction) {
|
export function getActions(navigate: NavigateFunction) {
|
||||||
const setNavigationOpen = useLocalState((state) => state.setNavigationOpen);
|
const setNavigationOpen = useLocalState((state) => state.setNavigationOpen);
|
||||||
|
|
||||||
const actions: SpotlightAction[] = [
|
const actions: SpotlightActionData[] = [
|
||||||
{
|
{
|
||||||
title: t`Home`,
|
id: 'home',
|
||||||
|
label: t`Home`,
|
||||||
description: `Go to the home page`,
|
description: `Go to the home page`,
|
||||||
onTrigger: () => navigate(menuItems.home.link),
|
onClick: () => navigate(menuItems.home.link),
|
||||||
icon: <IconHome size="1.2rem" />
|
leftSection: <IconHome size="1.2rem" />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t`Dashboard`,
|
id: 'dashboard',
|
||||||
|
label: t`Dashboard`,
|
||||||
description: t`Go to the InvenTree dashboard`,
|
description: t`Go to the InvenTree dashboard`,
|
||||||
onTrigger: () => navigate(menuItems.dashboard.link),
|
onClick: () => navigate(menuItems.dashboard.link),
|
||||||
icon: <IconLink size="1.2rem" />
|
leftSection: <IconLink size="1.2rem" />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t`Documentation`,
|
id: 'documentation',
|
||||||
|
label: t`Documentation`,
|
||||||
description: t`Visit the documentation to learn more about InvenTree`,
|
description: t`Visit the documentation to learn more about InvenTree`,
|
||||||
onTrigger: () => (window.location.href = docLinks.faq),
|
onClick: () => (window.location.href = docLinks.faq),
|
||||||
icon: <IconLink size="1.2rem" />
|
leftSection: <IconLink size="1.2rem" />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t`About InvenTree`,
|
id: 'about',
|
||||||
|
label: t`About InvenTree`,
|
||||||
description: t`About the InvenTree org`,
|
description: t`About the InvenTree org`,
|
||||||
onTrigger: () => aboutInvenTree(),
|
onClick: () => aboutInvenTree(),
|
||||||
icon: <IconLink size="1.2rem" />
|
leftSection: <IconLink size="1.2rem" />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t`Server Information`,
|
id: 'server-info',
|
||||||
|
label: t`Server Information`,
|
||||||
description: t`About this Inventree instance`,
|
description: t`About this Inventree instance`,
|
||||||
onTrigger: () => serverInfo(),
|
onClick: () => serverInfo(),
|
||||||
icon: <IconLink size="1.2rem" />
|
leftSection: <IconLink size="1.2rem" />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t`License Information`,
|
id: 'license-info',
|
||||||
|
label: t`License Information`,
|
||||||
description: t`Licenses for dependencies of the service`,
|
description: t`Licenses for dependencies of the service`,
|
||||||
onTrigger: () => licenseInfo(),
|
onClick: () => licenseInfo(),
|
||||||
icon: <IconLink size="1.2rem" />
|
leftSection: <IconLink size="1.2rem" />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t`Open Navigation`,
|
id: 'navigation',
|
||||||
|
label: t`Open Navigation`,
|
||||||
description: t`Open the main navigation menu`,
|
description: t`Open the main navigation menu`,
|
||||||
onTrigger: () => setNavigationOpen(true),
|
onClick: () => setNavigationOpen(true),
|
||||||
icon: <IconPointer size="1.2rem" />
|
leftSection: <IconPointer size="1.2rem" />
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { MantineSize } from '@mantine/core';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -154,3 +155,5 @@ export function renderDate(
|
|||||||
return date;
|
return date;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type UiSizeType = MantineSize | string | number;
|
||||||
|
@ -392,7 +392,7 @@ export function useCancelBuildOutputsForm({
|
|||||||
|
|
||||||
const preFormContent = useMemo(() => {
|
const preFormContent = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
<Alert color="red" title={t`Cancel Build Outputs`}>
|
<Alert color="red" title={t`Cancel Build Outputs`}>
|
||||||
<Text>{t`Selected build outputs will be deleted`}</Text>
|
<Text>{t`Selected build outputs will be deleted`}</Text>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
@ -311,7 +311,7 @@ function StockOperationsRow({
|
|||||||
<td>{record.location ? record.location_detail?.pathstring : '-'}</td>
|
<td>{record.location ? record.location_detail?.pathstring : '-'}</td>
|
||||||
<td>
|
<td>
|
||||||
<Flex align="center" gap="xs">
|
<Flex align="center" gap="xs">
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
<Text>{stockString}</Text>
|
<Text>{stockString}</Text>
|
||||||
<StatusRenderer status={record.status} type={ModelType.stockitem} />
|
<StatusRenderer status={record.status} type={ModelType.stockitem} />
|
||||||
</Group>
|
</Group>
|
||||||
|
@ -244,7 +244,7 @@ export function openModalApiForm(props: OpenApiFormProps) {
|
|||||||
props.onClose ? props.onClose() : null;
|
props.onClose ? props.onClose() : null;
|
||||||
},
|
},
|
||||||
children: (
|
children: (
|
||||||
<Stack spacing={'xs'}>
|
<Stack gap={'xs'}>
|
||||||
<Divider />
|
<Divider />
|
||||||
<ApiForm id={modalId} props={props} optionsLoading={false} />
|
<ApiForm id={modalId} props={props} optionsLoading={false} />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
import { Center, Loader, Stack } from '@mantine/core';
|
import { Center, Loader, MantineProvider, Stack } from '@mantine/core';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
|
import { colorSchema } from '../contexts/colorSchema';
|
||||||
|
import { theme } from '../theme';
|
||||||
|
|
||||||
function LoadingFallback() {
|
function LoadingFallback() {
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<MantineProvider theme={theme} colorSchemeManager={colorSchema}>
|
||||||
<Center>
|
<Stack>
|
||||||
<Loader />
|
<Center>
|
||||||
</Center>
|
<Loader />
|
||||||
</Stack>
|
</Center>
|
||||||
|
</Stack>
|
||||||
|
</MantineProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,184 +0,0 @@
|
|||||||
import { createStyles, rem } from '@mantine/core';
|
|
||||||
|
|
||||||
export const InvenTreeStyle = createStyles((theme) => ({
|
|
||||||
layoutHeader: {
|
|
||||||
paddingTop: theme.spacing.sm,
|
|
||||||
backgroundColor:
|
|
||||||
theme.colorScheme === 'dark'
|
|
||||||
? theme.colors.dark[6]
|
|
||||||
: theme.colors.gray[0],
|
|
||||||
borderBottom: `1px solid ${
|
|
||||||
theme.colorScheme === 'dark' ? 'transparent' : theme.colors.gray[2]
|
|
||||||
}`,
|
|
||||||
marginBottom: 10
|
|
||||||
},
|
|
||||||
|
|
||||||
layoutFooter: {
|
|
||||||
marginTop: 10,
|
|
||||||
borderTop: `1px solid ${
|
|
||||||
theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[2]
|
|
||||||
}`
|
|
||||||
},
|
|
||||||
|
|
||||||
layoutHeaderSection: {
|
|
||||||
paddingBottom: theme.spacing.sm
|
|
||||||
},
|
|
||||||
|
|
||||||
layoutHeaderUser: {
|
|
||||||
color: theme.colorScheme === 'dark' ? theme.colors.dark[0] : theme.black,
|
|
||||||
padding: `${theme.spacing.xs}px ${theme.spacing.sm}px`,
|
|
||||||
borderRadius: theme.defaultRadius,
|
|
||||||
transition: 'background-color 100ms ease',
|
|
||||||
|
|
||||||
[theme.fn.smallerThan('xs')]: {
|
|
||||||
display: 'none'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
headerDropdownFooter: {
|
|
||||||
backgroundColor:
|
|
||||||
theme.colorScheme === 'dark'
|
|
||||||
? theme.colors.dark[7]
|
|
||||||
: theme.colors.gray[0],
|
|
||||||
margin: `calc(${theme.spacing.md} * -1)`,
|
|
||||||
marginTop: theme.spacing.sm,
|
|
||||||
padding: `${theme.spacing.md} calc(${theme.spacing.md} * 2)`,
|
|
||||||
paddingBottom: theme.spacing.xl,
|
|
||||||
borderTop: `${rem(1)} solid ${
|
|
||||||
theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1]
|
|
||||||
}`
|
|
||||||
},
|
|
||||||
|
|
||||||
link: {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
height: '100%',
|
|
||||||
paddingLeft: theme.spacing.md,
|
|
||||||
paddingRight: theme.spacing.md,
|
|
||||||
textDecoration: 'none',
|
|
||||||
color: theme.colorScheme === 'dark' ? theme.white : theme.black,
|
|
||||||
fontWeight: 500,
|
|
||||||
fontSize: theme.fontSizes.sm,
|
|
||||||
|
|
||||||
[theme.fn.smallerThan('sm')]: {
|
|
||||||
height: rem(42),
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
width: '100%'
|
|
||||||
},
|
|
||||||
|
|
||||||
...theme.fn.hover({
|
|
||||||
backgroundColor:
|
|
||||||
theme.colorScheme === 'dark'
|
|
||||||
? theme.colors.dark[6]
|
|
||||||
: theme.colors.gray[0]
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
subLink: {
|
|
||||||
width: '100%',
|
|
||||||
padding: `${theme.spacing.xs} ${theme.spacing.md}`,
|
|
||||||
borderRadius: theme.defaultRadius,
|
|
||||||
|
|
||||||
...theme.fn.hover({
|
|
||||||
backgroundColor:
|
|
||||||
theme.colorScheme === 'dark'
|
|
||||||
? theme.colors.dark[7]
|
|
||||||
: theme.colors.gray[0]
|
|
||||||
}),
|
|
||||||
|
|
||||||
'&:active': theme.activeStyles
|
|
||||||
},
|
|
||||||
|
|
||||||
docHover: {
|
|
||||||
border: `1px dashed `
|
|
||||||
},
|
|
||||||
|
|
||||||
layoutContent: {
|
|
||||||
flex: 1,
|
|
||||||
width: '100%'
|
|
||||||
},
|
|
||||||
|
|
||||||
layoutFooterLinks: {
|
|
||||||
[theme.fn.smallerThan('xs')]: {
|
|
||||||
marginTop: theme.spacing.md
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
layoutFooterInner: {
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingTop: theme.spacing.xl,
|
|
||||||
paddingBottom: theme.spacing.xl,
|
|
||||||
|
|
||||||
[theme.fn.smallerThan('xs')]: {
|
|
||||||
flexDirection: 'column'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
tabs: {
|
|
||||||
[theme.fn.smallerThan('sm')]: {
|
|
||||||
display: 'none'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
tabsList: {
|
|
||||||
borderBottom: '0 !important',
|
|
||||||
'& > button:first-of-type': {
|
|
||||||
paddingLeft: '0 !important'
|
|
||||||
},
|
|
||||||
|
|
||||||
'& > button:last-of-type': {
|
|
||||||
paddingRight: '0 !important'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
tab: {
|
|
||||||
fontWeight: 500,
|
|
||||||
height: 38,
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor:
|
|
||||||
theme.colorScheme === 'dark'
|
|
||||||
? theme.colors.dark[5]
|
|
||||||
: theme.colors.gray[1]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
signText: {
|
|
||||||
fontSize: 'xl',
|
|
||||||
fontWeight: 700
|
|
||||||
},
|
|
||||||
|
|
||||||
error: {
|
|
||||||
backgroundColor: theme.colors.gray[0],
|
|
||||||
color: theme.colors.red[6]
|
|
||||||
},
|
|
||||||
|
|
||||||
dashboardItemValue: {
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: 700,
|
|
||||||
lineHeight: 1
|
|
||||||
},
|
|
||||||
|
|
||||||
dashboardItemTitle: {
|
|
||||||
fontWeight: 700
|
|
||||||
},
|
|
||||||
|
|
||||||
card: {
|
|
||||||
backgroundColor:
|
|
||||||
theme.colorScheme === 'dark' ? theme.colors.dark[7] : theme.white
|
|
||||||
},
|
|
||||||
|
|
||||||
itemTopBorder: {
|
|
||||||
borderTop: `1px solid ${
|
|
||||||
theme.colorScheme === 'dark' ? theme.colors.dark[4] : theme.colors.gray[2]
|
|
||||||
}`
|
|
||||||
},
|
|
||||||
|
|
||||||
navigationDrawer: {
|
|
||||||
padding: 0
|
|
||||||
}
|
|
||||||
}));
|
|
@ -1,9 +1,10 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Alert, Divider, MantineNumberSize, Stack } from '@mantine/core';
|
import { Alert, Divider, Stack } from '@mantine/core';
|
||||||
import { useId } from '@mantine/hooks';
|
import { useId } from '@mantine/hooks';
|
||||||
import { useEffect, useMemo, useRef } from 'react';
|
import { useEffect, useMemo, useRef } from 'react';
|
||||||
|
|
||||||
import { ApiFormProps, OptionsApiForm } from '../components/forms/ApiForm';
|
import { ApiFormProps, OptionsApiForm } from '../components/forms/ApiForm';
|
||||||
|
import { UiSizeType } from '../defaults/formatters';
|
||||||
import { useModal } from './UseModal';
|
import { useModal } from './UseModal';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,7 +21,7 @@ export interface ApiFormModalProps extends ApiFormProps {
|
|||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
onOpen?: () => void;
|
onOpen?: () => void;
|
||||||
closeOnClickOutside?: boolean;
|
closeOnClickOutside?: boolean;
|
||||||
size?: MantineNumberSize;
|
size?: UiSizeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,7 +63,7 @@ export function useApiFormModal(props: ApiFormModalProps) {
|
|||||||
closeOnClickOutside: formProps.closeOnClickOutside,
|
closeOnClickOutside: formProps.closeOnClickOutside,
|
||||||
size: props.size ?? 'xl',
|
size: props.size ?? 'xl',
|
||||||
children: (
|
children: (
|
||||||
<Stack spacing={'xs'}>
|
<Stack gap={'xs'}>
|
||||||
<Divider />
|
<Divider />
|
||||||
<OptionsApiForm props={formProps} id={id} />
|
<OptionsApiForm props={formProps} id={id} />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { MantineNumberSize, Modal } from '@mantine/core';
|
import { Modal } from '@mantine/core';
|
||||||
import { useDisclosure } from '@mantine/hooks';
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
import { StylishText } from '../components/items/StylishText';
|
import { StylishText } from '../components/items/StylishText';
|
||||||
|
import { UiSizeType } from '../defaults/formatters';
|
||||||
|
|
||||||
export interface UseModalProps {
|
export interface UseModalProps {
|
||||||
title: string;
|
title: string;
|
||||||
children: React.ReactElement;
|
children: React.ReactElement;
|
||||||
size?: MantineNumberSize;
|
size?: UiSizeType;
|
||||||
onOpen?: () => void;
|
onOpen?: () => void;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
closeOnClickOutside?: boolean;
|
closeOnClickOutside?: boolean;
|
||||||
|
180
src/frontend/src/main.css.ts
Normal file
180
src/frontend/src/main.css.ts
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
import { rem } from '@mantine/core';
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
import { vars } from './theme';
|
||||||
|
|
||||||
|
export const layoutHeader = style({
|
||||||
|
paddingTop: vars.spacing.sm,
|
||||||
|
marginBottom: 10,
|
||||||
|
|
||||||
|
[vars.lightSelector]: {
|
||||||
|
backgroundColor: vars.colors.gray[0],
|
||||||
|
borderBottom: `${rem(1)} solid ${vars.colors.gray[2]}`
|
||||||
|
},
|
||||||
|
[vars.darkSelector]: {
|
||||||
|
backgroundColor: vars.colors.dark[6],
|
||||||
|
borderBottom: `${rem(1)} solid transparent`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const layoutFooter = style({
|
||||||
|
marginTop: 10,
|
||||||
|
[vars.lightSelector]: { borderTop: `1px solid ${vars.colors.gray[2]}` },
|
||||||
|
[vars.darkSelector]: { borderTop: `1px solid ${vars.colors.dark[5]}` }
|
||||||
|
});
|
||||||
|
|
||||||
|
export const layoutHeaderSection = style({
|
||||||
|
paddingBottom: vars.spacing.sm
|
||||||
|
});
|
||||||
|
|
||||||
|
export const layoutHeaderUser = style({
|
||||||
|
padding: `${vars.spacing.xs}px ${vars.spacing.sm}px`,
|
||||||
|
borderRadius: vars.radiusDefault,
|
||||||
|
transition: 'background-color 100ms ease',
|
||||||
|
|
||||||
|
[vars.lightSelector]: { color: vars.colors.black },
|
||||||
|
[vars.darkSelector]: { color: vars.colors.dark[0] },
|
||||||
|
|
||||||
|
[vars.smallerThan('xs')]: {
|
||||||
|
display: 'none'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const headerDropdownFooter = style({
|
||||||
|
margin: `calc(${vars.spacing.md} * -1)`,
|
||||||
|
marginTop: vars.spacing.sm,
|
||||||
|
padding: `${vars.spacing.md} calc(${vars.spacing.md} * 2)`,
|
||||||
|
paddingBottom: vars.spacing.xl,
|
||||||
|
|
||||||
|
[vars.lightSelector]: {
|
||||||
|
backgroundColor: vars.colors.gray[0],
|
||||||
|
borderTop: `${rem(1)} solid ${vars.colors.gray[1]}`
|
||||||
|
},
|
||||||
|
[vars.darkSelector]: {
|
||||||
|
backgroundColor: vars.colors.dark[7],
|
||||||
|
borderTop: `${rem(1)} solid ${vars.colors.dark[5]}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const link = style({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '100%',
|
||||||
|
paddingLeft: vars.spacing.md,
|
||||||
|
paddingRight: vars.spacing.md,
|
||||||
|
textDecoration: 'none',
|
||||||
|
fontWeight: 500,
|
||||||
|
fontSize: vars.fontSizes.sm,
|
||||||
|
|
||||||
|
[vars.lightSelector]: { color: vars.colors.black },
|
||||||
|
[vars.darkSelector]: { color: vars.colors.white },
|
||||||
|
|
||||||
|
[vars.smallerThan('sm')]: {
|
||||||
|
height: rem(42),
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: '100%'
|
||||||
|
},
|
||||||
|
|
||||||
|
':hover': {
|
||||||
|
[vars.lightSelector]: { backgroundColor: vars.colors.gray[0] },
|
||||||
|
[vars.darkSelector]: { backgroundColor: vars.colors.dark[6] }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const subLink = style({
|
||||||
|
width: '100%',
|
||||||
|
padding: `${vars.spacing.xs} ${vars.spacing.md}`,
|
||||||
|
borderRadius: vars.radiusDefault,
|
||||||
|
|
||||||
|
':hover': {
|
||||||
|
[vars.lightSelector]: { backgroundColor: vars.colors.gray[0] },
|
||||||
|
[vars.darkSelector]: { backgroundColor: vars.colors.dark[7] }
|
||||||
|
},
|
||||||
|
|
||||||
|
':active': {
|
||||||
|
color: vars.colors.defaultHover
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const docHover = style({
|
||||||
|
border: `1px dashed `
|
||||||
|
});
|
||||||
|
|
||||||
|
export const layoutContent = style({
|
||||||
|
flex: 1,
|
||||||
|
width: '100%'
|
||||||
|
});
|
||||||
|
|
||||||
|
export const layoutFooterLinks = style({
|
||||||
|
[vars.smallerThan('xs')]: {
|
||||||
|
marginTop: vars.spacing.md
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const layoutFooterInner = style({
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingTop: vars.spacing.xl,
|
||||||
|
paddingBottom: vars.spacing.xl,
|
||||||
|
|
||||||
|
[vars.smallerThan('xs')]: {
|
||||||
|
flexDirection: 'column'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tabs = style({
|
||||||
|
[vars.smallerThan('sm')]: {
|
||||||
|
display: 'none'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tabsList = style({
|
||||||
|
borderBottom: '0 !important'
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tab = style({
|
||||||
|
fontWeight: 500,
|
||||||
|
height: 38,
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
|
||||||
|
':hover': {
|
||||||
|
[vars.lightSelector]: { backgroundColor: vars.colors.gray[1] },
|
||||||
|
[vars.darkSelector]: { backgroundColor: vars.colors.dark[5] }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const signText = style({
|
||||||
|
fontSize: 'xl',
|
||||||
|
fontWeight: 700
|
||||||
|
});
|
||||||
|
|
||||||
|
export const error = style({
|
||||||
|
backgroundColor: vars.colors.gray[0],
|
||||||
|
color: vars.colors.red[6]
|
||||||
|
});
|
||||||
|
|
||||||
|
export const dashboardItemValue = style({
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 700,
|
||||||
|
lineHeight: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
export const dashboardItemTitle = style({
|
||||||
|
fontWeight: 700
|
||||||
|
});
|
||||||
|
|
||||||
|
export const card = style({
|
||||||
|
[vars.lightSelector]: { backgroundColor: vars.colors.white },
|
||||||
|
[vars.darkSelector]: { backgroundColor: vars.colors.dark[7] }
|
||||||
|
});
|
||||||
|
|
||||||
|
export const itemTopBorder = style({
|
||||||
|
[vars.lightSelector]: { borderTop: `1px solid ${vars.colors.gray[2]}` },
|
||||||
|
[vars.darkSelector]: { borderTop: `1px solid ${vars.colors.dark[4]}` }
|
||||||
|
});
|
||||||
|
|
||||||
|
export const navigationDrawer = style({
|
||||||
|
padding: 0
|
||||||
|
});
|
@ -1,4 +1,9 @@
|
|||||||
|
import '@mantine/carousel/styles.css';
|
||||||
|
import '@mantine/core/styles.css';
|
||||||
|
import '@mantine/notifications/styles.css';
|
||||||
|
import '@mantine/spotlight/styles.css';
|
||||||
import * as Sentry from '@sentry/react';
|
import * as Sentry from '@sentry/react';
|
||||||
|
import 'mantine-datatable/styles.css';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import 'react-grid-layout/css/styles.css';
|
import 'react-grid-layout/css/styles.css';
|
||||||
|
@ -22,7 +22,7 @@ export default function Logged_In() {
|
|||||||
<Text size="lg">
|
<Text size="lg">
|
||||||
<Trans>Checking if you are already logged in</Trans>
|
<Trans>Checking if you are already logged in</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
<Group position="center">
|
<Group justify="center">
|
||||||
<Loader />
|
<Loader />
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -36,7 +36,8 @@ export default function Login() {
|
|||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
// Data manipulation functions
|
// Data manipulation functions
|
||||||
function ChangeHost(newHost: string): void {
|
function ChangeHost(newHost: string | null): void {
|
||||||
|
if (newHost === null) return;
|
||||||
setHost(hostList[newHost]?.host, newHost);
|
setHost(hostList[newHost]?.host, newHost);
|
||||||
setApiDefaults();
|
setApiDefaults();
|
||||||
fetchServerApiState();
|
fetchServerApiState();
|
||||||
@ -81,7 +82,7 @@ export default function Login() {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Paper radius="md" p="xl" withBorder>
|
<Paper radius="md" p="xl" withBorder>
|
||||||
<Text size="lg" weight={500}>
|
<Text size="lg" fw={500}>
|
||||||
{loginMode ? (
|
{loginMode ? (
|
||||||
<Trans>Welcome, log in below</Trans>
|
<Trans>Welcome, log in below</Trans>
|
||||||
) : (
|
) : (
|
||||||
|
@ -22,7 +22,7 @@ export default function Logout() {
|
|||||||
<Text size="lg">
|
<Text size="lg">
|
||||||
<Trans>Logging out</Trans>
|
<Trans>Logging out</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
<Group position="center">
|
<Group justify="center">
|
||||||
<Loader />
|
<Loader />
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -8,13 +8,14 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
TextInput
|
TextInput
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { spotlight } from '@mantine/spotlight';
|
import { SpotlightActionData } from '@mantine/spotlight';
|
||||||
import { IconAlien } from '@tabler/icons-react';
|
import { IconAlien } from '@tabler/icons-react';
|
||||||
import { ReactNode, useMemo, useState } from 'react';
|
import { ReactNode, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { OptionsApiForm } from '../../components/forms/ApiForm';
|
import { OptionsApiForm } from '../../components/forms/ApiForm';
|
||||||
import { PlaceholderPill } from '../../components/items/Placeholder';
|
import { PlaceholderPill } from '../../components/items/Placeholder';
|
||||||
import { StylishText } from '../../components/items/StylishText';
|
import { StylishText } from '../../components/items/StylishText';
|
||||||
|
import { firstSpotlight } from '../../components/nav/Layout';
|
||||||
import { StatusRenderer } from '../../components/render/StatusRenderer';
|
import { StatusRenderer } from '../../components/render/StatusRenderer';
|
||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
@ -135,7 +136,7 @@ function ApiFormsPlayground() {
|
|||||||
<Button onClick={() => openCreatePart()}>Create Part new Modal</Button>
|
<Button onClick={() => openCreatePart()}>Create Part new Modal</Button>
|
||||||
{createPartModal}
|
{createPartModal}
|
||||||
</Group>
|
</Group>
|
||||||
<Card sx={{ padding: '30px' }}>
|
<Card style={{ padding: '30px' }}>
|
||||||
<OptionsApiForm
|
<OptionsApiForm
|
||||||
props={{
|
props={{
|
||||||
url: ApiEndpoints.part_list,
|
url: ApiEndpoints.part_list,
|
||||||
@ -181,25 +182,28 @@ function SpotlighPlayground() {
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
spotlight.registerActions([
|
const setAdditionalActions = (value: SpotlightActionData[]) => {
|
||||||
|
console.log('would add', value);
|
||||||
|
};
|
||||||
|
setAdditionalActions([
|
||||||
{
|
{
|
||||||
id: 'secret-action-1',
|
id: 'secret-action-1',
|
||||||
title: 'Secret action',
|
title: 'Secret action',
|
||||||
description: 'It was registered with a button click',
|
description: 'It was registered with a button click',
|
||||||
icon: <IconAlien size="1.2rem" />,
|
leftSection: <IconAlien size="1.2rem" />,
|
||||||
onTrigger: () => console.log('Secret')
|
onClick: () => console.log('Secret')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'secret-action-2',
|
id: 'secret-action-2',
|
||||||
title: 'Another secret action',
|
title: 'Another secret action',
|
||||||
description:
|
description:
|
||||||
'You can register multiple actions with just one command',
|
'You can register multiple actions with just one command',
|
||||||
icon: <IconAlien size="1.2rem" />,
|
leftSection: <IconAlien size="1.2rem" />,
|
||||||
onTrigger: () => console.log('Secret')
|
onClick: () => console.log('Secret')
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
console.log('registed');
|
console.log('registed');
|
||||||
spotlight.open();
|
firstSpotlight.open();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Register extra actions
|
Register extra actions
|
||||||
|
@ -4,7 +4,6 @@ import {
|
|||||||
Badge,
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Col,
|
|
||||||
Container,
|
Container,
|
||||||
Grid,
|
Grid,
|
||||||
Group,
|
Group,
|
||||||
@ -42,7 +41,7 @@ import {
|
|||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { Html5Qrcode } from 'html5-qrcode';
|
import { Html5Qrcode } from 'html5-qrcode';
|
||||||
import { CameraDevice } from 'html5-qrcode/camera/core';
|
import { CameraDevice } from 'html5-qrcode/camera/core';
|
||||||
import { ReactNode, useEffect, useState } from 'react';
|
import { ReactNode, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { api } from '../../App';
|
import { api } from '../../App';
|
||||||
import { DocInfo } from '../../components/items/DocInfo';
|
import { DocInfo } from '../../components/items/DocInfo';
|
||||||
@ -243,14 +242,14 @@ export default function Scan() {
|
|||||||
|
|
||||||
if (uniqueObjectTypes.length === 0) {
|
if (uniqueObjectTypes.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Group spacing={0}>
|
<Group gap={0}>
|
||||||
<IconQuestionMark color="orange" />
|
<IconQuestionMark color="orange" />
|
||||||
<Trans>Selected elements are not known</Trans>
|
<Trans>Selected elements are not known</Trans>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
} else if (uniqueObjectTypes.length > 1) {
|
} else if (uniqueObjectTypes.length > 1) {
|
||||||
return (
|
return (
|
||||||
<Group spacing={0}>
|
<Group gap={0}>
|
||||||
<IconAlertCircle color="orange" />
|
<IconAlertCircle color="orange" />
|
||||||
<Trans>Multiple object types selected</Trans>
|
<Trans>Multiple object types selected</Trans>
|
||||||
</Group>
|
</Group>
|
||||||
@ -262,7 +261,11 @@ export default function Scan() {
|
|||||||
<Trans>Actions for {uniqueObjectTypes[0]} </Trans>
|
<Trans>Actions for {uniqueObjectTypes[0]} </Trans>
|
||||||
</Text>
|
</Text>
|
||||||
<Group>
|
<Group>
|
||||||
<ActionIcon onClick={notYetImplemented} title={t`Count`}>
|
<ActionIcon
|
||||||
|
onClick={notYetImplemented}
|
||||||
|
title={t`Count`}
|
||||||
|
variant="default"
|
||||||
|
>
|
||||||
<IconNumber />
|
<IconNumber />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Group>
|
</Group>
|
||||||
@ -273,8 +276,8 @@ export default function Scan() {
|
|||||||
// rendering
|
// rendering
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
<Group position="left">
|
<Group justify="left">
|
||||||
<StylishText>
|
<StylishText>
|
||||||
<Trans>Scan Page</Trans>
|
<Trans>Scan Page</Trans>
|
||||||
</StylishText>
|
</StylishText>
|
||||||
@ -293,10 +296,10 @@ export default function Scan() {
|
|||||||
</Group>
|
</Group>
|
||||||
<Space h={'md'} />
|
<Space h={'md'} />
|
||||||
<Grid maw={'100%'}>
|
<Grid maw={'100%'}>
|
||||||
<Col span={4}>
|
<Grid.Col span={4}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
<TitleWithDoc
|
<TitleWithDoc
|
||||||
order={3}
|
order={3}
|
||||||
text={t`Select the input method you want to use to scan items.`}
|
text={t`Select the input method you want to use to scan items.`}
|
||||||
@ -309,12 +312,12 @@ export default function Scan() {
|
|||||||
data={inputOptions}
|
data={inputOptions}
|
||||||
searchable
|
searchable
|
||||||
placeholder={t`Select input method`}
|
placeholder={t`Select input method`}
|
||||||
nothingFound={t`Nothing found`}
|
nothingFoundMessage={t`Nothing found`}
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
{inp}
|
{inp}
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack spacing={0}>
|
<Stack gap={0}>
|
||||||
<TitleWithDoc
|
<TitleWithDoc
|
||||||
order={3}
|
order={3}
|
||||||
text={t`Depending on the selected parts actions will be shown here. Not all barcode types are supported currently.`}
|
text={t`Depending on the selected parts actions will be shown here. Not all barcode types are supported currently.`}
|
||||||
@ -338,6 +341,7 @@ export default function Scan() {
|
|||||||
color="red"
|
color="red"
|
||||||
onClick={btnDeleteHistory}
|
onClick={btnDeleteHistory}
|
||||||
title={t`Delete`}
|
title={t`Delete`}
|
||||||
|
variant="default"
|
||||||
>
|
>
|
||||||
<IconTrash />
|
<IconTrash />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
@ -345,6 +349,7 @@ export default function Scan() {
|
|||||||
onClick={btnRunSelectedBarcode}
|
onClick={btnRunSelectedBarcode}
|
||||||
disabled={selection.length > 1}
|
disabled={selection.length > 1}
|
||||||
title={t`Lookup part`}
|
title={t`Lookup part`}
|
||||||
|
variant="default"
|
||||||
>
|
>
|
||||||
<IconSearch />
|
<IconSearch />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
@ -352,6 +357,7 @@ export default function Scan() {
|
|||||||
onClick={btnOpenSelectedLink}
|
onClick={btnOpenSelectedLink}
|
||||||
disabled={!selectionLinked}
|
disabled={!selectionLinked}
|
||||||
title={t`Open Link`}
|
title={t`Open Link`}
|
||||||
|
variant="default"
|
||||||
>
|
>
|
||||||
<IconLink />
|
<IconLink />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
@ -361,9 +367,9 @@ export default function Scan() {
|
|||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Col>
|
</Grid.Col>
|
||||||
<Col span={8}>
|
<Grid.Col span={8}>
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
<TitleWithDoc
|
<TitleWithDoc
|
||||||
order={3}
|
order={3}
|
||||||
text={t`History is locally kept in this browser.`}
|
text={t`History is locally kept in this browser.`}
|
||||||
@ -374,6 +380,7 @@ export default function Scan() {
|
|||||||
<ActionIcon
|
<ActionIcon
|
||||||
color="red"
|
color="red"
|
||||||
onClick={btnDeleteFullHistory}
|
onClick={btnDeleteFullHistory}
|
||||||
|
variant="default"
|
||||||
title={t`Delete History`}
|
title={t`Delete History`}
|
||||||
>
|
>
|
||||||
<IconTrash />
|
<IconTrash />
|
||||||
@ -384,7 +391,7 @@ export default function Scan() {
|
|||||||
selection={selection}
|
selection={selection}
|
||||||
setSelection={setSelection}
|
setSelection={setSelection}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Grid.Col>
|
||||||
</Grid>
|
</Grid>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -409,34 +416,30 @@ function HistoryTable({
|
|||||||
setSelection((current) =>
|
setSelection((current) =>
|
||||||
current.length === data.length ? [] : data.map((item) => item.id)
|
current.length === data.length ? [] : data.map((item) => item.id)
|
||||||
);
|
);
|
||||||
const [rows, setRows] = useState<ReactNode>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
const rows = useMemo(() => {
|
||||||
setRows(
|
return data.map((item) => {
|
||||||
data.map((item) => {
|
return (
|
||||||
return (
|
<tr key={item.id}>
|
||||||
<tr key={item.id}>
|
<td>
|
||||||
<td>
|
<Checkbox
|
||||||
<Checkbox
|
checked={selection.includes(item.id)}
|
||||||
checked={selection.includes(item.id)}
|
onChange={() => toggleRow(item.id)}
|
||||||
onChange={() => toggleRow(item.id)}
|
/>
|
||||||
transitionDuration={0}
|
</td>
|
||||||
/>
|
<td>
|
||||||
</td>
|
{item.pk && item.model && item.instance ? (
|
||||||
<td>
|
<RenderInstance model={item.model} instance={item.instance} />
|
||||||
{item.pk && item.model && item.instance ? (
|
) : (
|
||||||
<RenderInstance model={item.model} instance={item.instance} />
|
item.ref
|
||||||
) : (
|
)}
|
||||||
item.ref
|
</td>
|
||||||
)}
|
<td>{item.model}</td>
|
||||||
</td>
|
<td>{item.source}</td>
|
||||||
<td>{item.model}</td>
|
<td>{item.timestamp?.toString()}</td>
|
||||||
<td>{item.source}</td>
|
</tr>
|
||||||
<td>{item.timestamp?.toString()}</td>
|
);
|
||||||
</tr>
|
});
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}, [data, selection]);
|
}, [data, selection]);
|
||||||
|
|
||||||
// rendering
|
// rendering
|
||||||
@ -458,7 +461,6 @@ function HistoryTable({
|
|||||||
indeterminate={
|
indeterminate={
|
||||||
selection.length > 0 && selection.length !== data.length
|
selection.length > 0 && selection.length !== data.length
|
||||||
}
|
}
|
||||||
transitionDuration={0}
|
|
||||||
/>
|
/>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
@ -528,7 +530,7 @@ function InputManual({ action }: Readonly<ScanInputInterface>) {
|
|||||||
onChange={(event) => setValue(event.currentTarget.value)}
|
onChange={(event) => setValue(event.currentTarget.value)}
|
||||||
onKeyDown={getHotkeyHandler([['Enter', btnAddItem]])}
|
onKeyDown={getHotkeyHandler([['Enter', btnAddItem]])}
|
||||||
/>
|
/>
|
||||||
<ActionIcon onClick={btnAddItem} w={16}>
|
<ActionIcon onClick={btnAddItem} w={16} variant="default">
|
||||||
<IconPlus />
|
<IconPlus />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Group>
|
</Group>
|
||||||
@ -712,8 +714,8 @@ function InputImageBarcode({ action }: Readonly<ScanInputInterface>) {
|
|||||||
}, [cameraValue]);
|
}, [cameraValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
<Group spacing="xs">
|
<Group gap="xs">
|
||||||
<Select
|
<Select
|
||||||
value={cameraValue}
|
value={cameraValue}
|
||||||
onChange={setCameraValue}
|
onChange={setCameraValue}
|
||||||
@ -723,7 +725,11 @@ function InputImageBarcode({ action }: Readonly<ScanInputInterface>) {
|
|||||||
size="sm"
|
size="sm"
|
||||||
/>
|
/>
|
||||||
{ScanningEnabled ? (
|
{ScanningEnabled ? (
|
||||||
<ActionIcon onClick={btnStopScanning} title={t`Stop scanning`}>
|
<ActionIcon
|
||||||
|
onClick={btnStopScanning}
|
||||||
|
title={t`Stop scanning`}
|
||||||
|
variant="default"
|
||||||
|
>
|
||||||
<IconPlayerStopFilled />
|
<IconPlayerStopFilled />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
) : (
|
) : (
|
||||||
@ -731,11 +737,12 @@ function InputImageBarcode({ action }: Readonly<ScanInputInterface>) {
|
|||||||
onClick={btnStartScanning}
|
onClick={btnStartScanning}
|
||||||
title={t`Start scanning`}
|
title={t`Start scanning`}
|
||||||
disabled={!camId}
|
disabled={!camId}
|
||||||
|
variant="default"
|
||||||
>
|
>
|
||||||
<IconPlayerPlayFilled />
|
<IconPlayerPlayFilled />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
)}
|
)}
|
||||||
<Space sx={{ flex: 1 }} />
|
<Space style={{ flex: 1 }} />
|
||||||
<Badge color={ScanningEnabled ? 'green' : 'orange'}>
|
<Badge color={ScanningEnabled ? 'green' : 'orange'}>
|
||||||
{ScanningEnabled ? t`Scanning` : t`Not scanning`}
|
{ScanningEnabled ? t`Scanning` : t`Not scanning`}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
@ -40,7 +40,7 @@ export function AccountDetailPanel() {
|
|||||||
</Group>
|
</Group>
|
||||||
<Group>
|
<Group>
|
||||||
{editing ? (
|
{editing ? (
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
<TextInput
|
<TextInput
|
||||||
label="first name"
|
label="first name"
|
||||||
placeholder={t`First name`}
|
placeholder={t`First name`}
|
||||||
@ -51,14 +51,14 @@ export function AccountDetailPanel() {
|
|||||||
placeholder={t`Last name`}
|
placeholder={t`Last name`}
|
||||||
{...form.getInputProps('last_name')}
|
{...form.getInputProps('last_name')}
|
||||||
/>
|
/>
|
||||||
<Group position="right" mt="md">
|
<Group justify="right" mt="md">
|
||||||
<Button type="submit">
|
<Button type="submit">
|
||||||
<Trans>Submit</Trans>
|
<Trans>Submit</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
) : (
|
) : (
|
||||||
<Stack spacing="0">
|
<Stack gap="0">
|
||||||
<Text>
|
<Text>
|
||||||
<Trans>First name: </Trans>
|
<Trans>First name: </Trans>
|
||||||
{form.values.first_name}
|
{form.values.first_name}
|
||||||
|
@ -137,7 +137,7 @@ function EmailContent({}: {}) {
|
|||||||
key={link.id}
|
key={link.id}
|
||||||
value={String(link.id)}
|
value={String(link.id)}
|
||||||
label={
|
label={
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
{link.email}
|
{link.email}
|
||||||
{link.primary && (
|
{link.primary && (
|
||||||
<Badge color="blue">
|
<Badge color="blue">
|
||||||
@ -168,7 +168,7 @@ function EmailContent({}: {}) {
|
|||||||
<TextInput
|
<TextInput
|
||||||
label={t`E-Mail`}
|
label={t`E-Mail`}
|
||||||
placeholder={t`E-Mail address`}
|
placeholder={t`E-Mail address`}
|
||||||
icon={<IconAt />}
|
leftSection={<IconAt />}
|
||||||
value={newEmailValue}
|
value={newEmailValue}
|
||||||
onChange={(event) => setNewEmailValue(event.currentTarget.value)}
|
onChange={(event) => setNewEmailValue(event.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
@ -251,7 +251,7 @@ function SsoContent({ dataProvider }: { dataProvider: any | undefined }) {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
disabled={!provider.configured}
|
disabled={!provider.configured}
|
||||||
>
|
>
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
{provider.display_name}
|
{provider.display_name}
|
||||||
{provider.configured == false && <IconAlertCircle />}
|
{provider.configured == false && <IconAlertCircle />}
|
||||||
</Group>
|
</Group>
|
||||||
@ -308,7 +308,7 @@ function SsoContent({ dataProvider }: { dataProvider: any | undefined }) {
|
|||||||
{currentProviders === undefined ? (
|
{currentProviders === undefined ? (
|
||||||
<Trans>Loading</Trans>
|
<Trans>Loading</Trans>
|
||||||
) : (
|
) : (
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
{currentProviders.map((provider: any) => (
|
{currentProviders.map((provider: any) => (
|
||||||
<ProviderButton key={provider.id} provider={provider} />
|
<ProviderButton key={provider.id} provider={provider} />
|
||||||
))}
|
))}
|
||||||
|
@ -11,12 +11,11 @@ import {
|
|||||||
Table,
|
Table,
|
||||||
Title
|
Title
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { LoaderType } from '@mantine/styles/lib/theme/types/MantineTheme';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { SizeMarks } from '../../../../defaults/defaults';
|
import { SizeMarks } from '../../../../defaults/defaults';
|
||||||
import { InvenTreeStyle } from '../../../../globalStyle';
|
|
||||||
import { useLocalState } from '../../../../states/LocalState';
|
import { useLocalState } from '../../../../states/LocalState';
|
||||||
|
import { theme } from '../../../../theme';
|
||||||
|
|
||||||
function getLkp(color: string) {
|
function getLkp(color: string) {
|
||||||
return { [DEFAULT_THEME.colors[color][6]]: color };
|
return { [DEFAULT_THEME.colors[color][6]]: color };
|
||||||
@ -27,8 +26,6 @@ const LOOKUP = Object.assign(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export function UserTheme({ height }: { height: number }) {
|
export function UserTheme({ height }: { height: number }) {
|
||||||
const { theme } = InvenTreeStyle();
|
|
||||||
|
|
||||||
// primary color
|
// primary color
|
||||||
function changePrimary(color: string) {
|
function changePrimary(color: string) {
|
||||||
useLocalState.setState({ primaryColor: LOOKUP[color] });
|
useLocalState.setState({ primaryColor: LOOKUP[color] });
|
||||||
@ -69,10 +66,13 @@ export function UserTheme({ height }: { height: number }) {
|
|||||||
{ value: 'oval', label: t`oval` },
|
{ value: 'oval', label: t`oval` },
|
||||||
{ value: 'dots', label: t`dots` }
|
{ value: 'dots', label: t`dots` }
|
||||||
];
|
];
|
||||||
const [loader, setLoader] = useState<LoaderType>(theme.loader);
|
const [themeLoader, setThemeLoader] = useLocalState((state) => [
|
||||||
function changeLoader(value: LoaderType) {
|
state.loader,
|
||||||
setLoader(value);
|
state.setLoader
|
||||||
useLocalState.setState({ loader: value });
|
]);
|
||||||
|
function changeLoader(value: string | null) {
|
||||||
|
if (value === null) return;
|
||||||
|
setThemeLoader(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -135,10 +135,10 @@ export function UserTheme({ height }: { height: number }) {
|
|||||||
<Group align="center">
|
<Group align="center">
|
||||||
<Select
|
<Select
|
||||||
data={loaderDate}
|
data={loaderDate}
|
||||||
value={loader}
|
value={themeLoader}
|
||||||
onChange={changeLoader}
|
onChange={changeLoader}
|
||||||
/>
|
/>
|
||||||
<Loader type={loader} mah={18} />
|
<Loader type={themeLoader} mah={18} />
|
||||||
</Group>
|
</Group>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -97,7 +97,7 @@ export default function AdminCenter() {
|
|||||||
label: t`Project Codes`,
|
label: t`Project Codes`,
|
||||||
icon: <IconListDetails />,
|
icon: <IconListDetails />,
|
||||||
content: (
|
content: (
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
<GlobalSettingList keys={['PROJECT_CODES_ENABLED']} />
|
<GlobalSettingList keys={['PROJECT_CODES_ENABLED']} />
|
||||||
<Divider />
|
<Divider />
|
||||||
<ProjectCodeTable />
|
<ProjectCodeTable />
|
||||||
@ -144,7 +144,7 @@ export default function AdminCenter() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const QuickAction = () => (
|
const QuickAction = () => (
|
||||||
<Stack spacing={'xs'} ml={'sm'}>
|
<Stack gap={'xs'} ml={'sm'}>
|
||||||
<Title order={5}>
|
<Title order={5}>
|
||||||
<Trans>Quick Actions</Trans>
|
<Trans>Quick Actions</Trans>
|
||||||
</Title>
|
</Title>
|
||||||
@ -167,7 +167,7 @@ export default function AdminCenter() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
<SettingsHeader
|
<SettingsHeader
|
||||||
title={t`Admin Center`}
|
title={t`Admin Center`}
|
||||||
subtitle={t`Advanced Options`}
|
subtitle={t`Advanced Options`}
|
||||||
|
@ -38,7 +38,7 @@ export default function MachineManagementPanel() {
|
|||||||
|
|
||||||
<Space h="10px" />
|
<Space h="10px" />
|
||||||
|
|
||||||
<Stack spacing={'xs'}>
|
<Stack gap={'xs'}>
|
||||||
<Title order={5}>
|
<Title order={5}>
|
||||||
<Trans>Machine types</Trans>
|
<Trans>Machine types</Trans>
|
||||||
</Title>
|
</Title>
|
||||||
@ -47,7 +47,7 @@ export default function MachineManagementPanel() {
|
|||||||
|
|
||||||
<Space h="10px" />
|
<Space h="10px" />
|
||||||
|
|
||||||
<Stack spacing={'xs'}>
|
<Stack gap={'xs'}>
|
||||||
<Group>
|
<Group>
|
||||||
<Title order={5}>
|
<Title order={5}>
|
||||||
<Trans>Machine Error Stack</Trans>
|
<Trans>Machine Error Stack</Trans>
|
||||||
@ -58,7 +58,7 @@ export default function MachineManagementPanel() {
|
|||||||
</Group>
|
</Group>
|
||||||
{registryStatus?.registry_errors &&
|
{registryStatus?.registry_errors &&
|
||||||
registryStatus.registry_errors.length === 0 ? (
|
registryStatus.registry_errors.length === 0 ? (
|
||||||
<Text italic>
|
<Text style={{ fontStyle: 'italic' }}>
|
||||||
<Trans>There are no machine registry errors.</Trans>
|
<Trans>There are no machine registry errors.</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
|
@ -8,7 +8,7 @@ import { UserTable } from '../../../../tables/settings/UserTable';
|
|||||||
|
|
||||||
export default function UserManagementPanel() {
|
export default function UserManagementPanel() {
|
||||||
return (
|
return (
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
<Title order={5}>
|
<Title order={5}>
|
||||||
<Trans>Users</Trans>
|
<Trans>Users</Trans>
|
||||||
</Title>
|
</Title>
|
||||||
@ -21,7 +21,7 @@ export default function UserManagementPanel() {
|
|||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<Stack spacing={0}>
|
<Stack gap={0}>
|
||||||
<Text>
|
<Text>
|
||||||
<Trans>Settings</Trans>
|
<Trans>Settings</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -287,7 +287,7 @@ export default function SystemSettings() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
<SettingsHeader
|
<SettingsHeader
|
||||||
title={t`System Settings`}
|
title={t`System Settings`}
|
||||||
subtitle={server.instance || ''}
|
subtitle={server.instance || ''}
|
||||||
|
@ -110,7 +110,7 @@ export default function UserSettings() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
<SettingsHeader
|
<SettingsHeader
|
||||||
title={t`Account Settings`}
|
title={t`Account Settings`}
|
||||||
subtitle={`${user?.first_name} ${user?.last_name}`}
|
subtitle={`${user?.first_name} ${user?.last_name}`}
|
||||||
|
@ -415,7 +415,7 @@ export default function BuildDetail() {
|
|||||||
{editBuild.modal}
|
{editBuild.modal}
|
||||||
{duplicateBuild.modal}
|
{duplicateBuild.modal}
|
||||||
{cancelBuild.modal}
|
{cancelBuild.modal}
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||||
<PageDetail
|
<PageDetail
|
||||||
title={build.reference}
|
title={build.reference}
|
||||||
|
@ -81,7 +81,8 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
|
|||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
name: 'description',
|
name: 'description',
|
||||||
label: t`Description`
|
label: t`Description`,
|
||||||
|
copy: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'link',
|
type: 'link',
|
||||||
@ -314,7 +315,7 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{editCompany.modal}
|
{editCompany.modal}
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||||
<PageDetail
|
<PageDetail
|
||||||
title={t`Company` + `: ${company.name}`}
|
title={t`Company` + `: ${company.name}`}
|
||||||
|
@ -241,8 +241,7 @@ export default function ManufacturerPartDetail() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{editManufacturerPart.modal}
|
{editManufacturerPart.modal}
|
||||||
{duplicateManufacturerPart.modal}
|
<Stack gap="xs">
|
||||||
<Stack spacing="xs">
|
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||||
<PageDetail
|
<PageDetail
|
||||||
title={t`ManufacturerPart`}
|
title={t`ManufacturerPart`}
|
||||||
|
@ -311,8 +311,7 @@ export default function SupplierPartDetail() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{editSuppliertPart.modal}
|
{editSuppliertPart.modal}
|
||||||
{duplicateSupplierPart.modal}
|
<Stack gap="xs">
|
||||||
<Stack spacing="xs">
|
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||||
<PageDetail
|
<PageDetail
|
||||||
title={t`Supplier Part`}
|
title={t`Supplier Part`}
|
||||||
|
@ -222,7 +222,7 @@ export default function CategoryDetail({}: {}) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{editCategory.modal}
|
{editCategory.modal}
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||||
<PartCategoryTree
|
<PartCategoryTree
|
||||||
opened={treeOpen}
|
opened={treeOpen}
|
||||||
|
@ -442,7 +442,7 @@ export default function PartDetail() {
|
|||||||
/>
|
/>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col span={8}>
|
<Grid.Col span={8}>
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
@ -782,7 +782,7 @@ export default function PartDetail() {
|
|||||||
<>
|
<>
|
||||||
{duplicatePart.modal}
|
{duplicatePart.modal}
|
||||||
{editPart.modal}
|
{editPart.modal}
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||||
<PartCategoryTree
|
<PartCategoryTree
|
||||||
opened={treeOpen}
|
opened={treeOpen}
|
||||||
|
@ -65,7 +65,7 @@ export default function PartPricingPanel({ part }: { part: any }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
<LoadingOverlay visible={instanceQuery.isLoading} />
|
<LoadingOverlay visible={instanceQuery.isLoading} />
|
||||||
{!pricing && !instanceQuery.isLoading && (
|
{!pricing && !instanceQuery.isLoading && (
|
||||||
<Alert color="ref" title={t`Error`}>
|
<Alert color="ref" title={t`Error`}>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user