Major UI refactoring: change Select component + colors

This commit is contained in:
IRBorisov 2023-09-08 02:15:20 +03:00
parent 5b006f54dd
commit cc81cc4b6b
13 changed files with 385 additions and 250 deletions

View File

@ -20,7 +20,7 @@ This readme file is used mostly to document project dependencies
- react-tabs - react-tabs
- react-intl - react-intl
- react-data-table-component - react-data-table-component
- react-dropdown-select - react-select
- react-error-boundary - react-error-boundary
- reagraph - reagraph
- react-tooltip - react-tooltip

View File

@ -16,11 +16,11 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-data-table-component": "^7.5.4", "react-data-table-component": "^7.5.4",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-dropdown-select": "^4.10.0",
"react-error-boundary": "^4.0.11", "react-error-boundary": "^4.0.11",
"react-intl": "^6.4.4", "react-intl": "^6.4.4",
"react-loader-spinner": "^5.4.5", "react-loader-spinner": "^5.4.5",
"react-router-dom": "^6.15.0", "react-router-dom": "^6.15.0",
"react-select": "^5.7.4",
"react-tabs": "^6.0.2", "react-tabs": "^6.0.2",
"react-toastify": "^9.1.3", "react-toastify": "^9.1.3",
"react-tooltip": "^5.21.3", "react-tooltip": "^5.21.3",
@ -2240,28 +2240,6 @@
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz",
"integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA=="
}, },
"node_modules/@emotion/styled": {
"version": "11.11.0",
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz",
"integrity": "sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==",
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.11.0",
"@emotion/is-prop-valid": "^1.2.1",
"@emotion/serialize": "^1.1.2",
"@emotion/use-insertion-effect-with-fallbacks": "^1.0.1",
"@emotion/utils": "^1.2.1"
},
"peerDependencies": {
"@emotion/react": "^11.0.0-rc.0",
"react": ">=16.8.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@emotion/stylis": { "node_modules/@emotion/stylis": {
"version": "0.8.5", "version": "0.8.5",
"resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
@ -4111,6 +4089,14 @@
"@types/react": "*" "@types/react": "*"
} }
}, },
"node_modules/@types/react-transition-group": {
"version": "4.4.6",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz",
"integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/scheduler": { "node_modules/@types/scheduler": {
"version": "0.16.3", "version": "0.16.3",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
@ -5565,6 +5551,15 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"dependencies": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"node_modules/draco3d": { "node_modules/draco3d": {
"version": "1.5.6", "version": "1.5.6",
"resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.6.tgz", "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.6.tgz",
@ -8572,6 +8567,11 @@
"tmpl": "1.0.5" "tmpl": "1.0.5"
} }
}, },
"node_modules/memoize-one": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
},
"node_modules/merge-stream": { "node_modules/merge-stream": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@ -9342,20 +9342,6 @@
"react": "^18.2.0" "react": "^18.2.0"
} }
}, },
"node_modules/react-dropdown-select": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/react-dropdown-select/-/react-dropdown-select-4.10.0.tgz",
"integrity": "sha512-GiUeZZqN8Z/PQFWyihFeuLwlUQ2IJxiu4ep8Y6bonF8ynUk8dAdLgjg3O4YIa3gtS8/Paeli8AtH46AcaPzWVg==",
"dependencies": {
"@emotion/react": "11.11.1",
"@emotion/styled": "11.11.0"
},
"peerDependencies": {
"prop-types": ">=15.7.2",
"react": ">=16.x",
"react-dom": ">=16.x"
}
},
"node_modules/react-error-boundary": { "node_modules/react-error-boundary": {
"version": "4.0.11", "version": "4.0.11",
"resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.11.tgz", "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.11.tgz",
@ -9517,6 +9503,26 @@
"react-dom": ">=16.8" "react-dom": ">=16.8"
} }
}, },
"node_modules/react-select": {
"version": "5.7.4",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.7.4.tgz",
"integrity": "sha512-NhuE56X+p9QDFh4BgeygHFIvJJszO1i1KSkg/JPcIJrbovyRtI+GuOEa4XzFCEpZRAEoEI8u/cAHK+jG/PgUzQ==",
"dependencies": {
"@babel/runtime": "^7.12.0",
"@emotion/cache": "^11.4.0",
"@emotion/react": "^11.8.1",
"@floating-ui/dom": "^1.0.1",
"@types/react-transition-group": "^4.4.0",
"memoize-one": "^6.0.0",
"prop-types": "^15.6.0",
"react-transition-group": "^4.3.0",
"use-isomorphic-layout-effect": "^1.1.2"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-tabs": { "node_modules/react-tabs": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-6.0.2.tgz", "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-6.0.2.tgz",
@ -9562,6 +9568,21 @@
"react-dom": ">=16.14.0" "react-dom": ">=16.14.0"
} }
}, },
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
"dependencies": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": ">=16.6.0",
"react-dom": ">=16.6.0"
}
},
"node_modules/react-use-gesture": { "node_modules/react-use-gesture": {
"version": "9.1.3", "version": "9.1.3",
"resolved": "https://registry.npmjs.org/react-use-gesture/-/react-use-gesture-9.1.3.tgz", "resolved": "https://registry.npmjs.org/react-use-gesture/-/react-use-gesture-9.1.3.tgz",
@ -10576,6 +10597,19 @@
"punycode": "^2.1.0" "punycode": "^2.1.0"
} }
}, },
"node_modules/use-isomorphic-layout-effect": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
"integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/use-sync-external-store": { "node_modules/use-sync-external-store": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",

View File

@ -20,11 +20,11 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-data-table-component": "^7.5.4", "react-data-table-component": "^7.5.4",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-dropdown-select": "^4.10.0",
"react-error-boundary": "^4.0.11", "react-error-boundary": "^4.0.11",
"react-intl": "^6.4.4", "react-intl": "^6.4.4",
"react-loader-spinner": "^5.4.5", "react-loader-spinner": "^5.4.5",
"react-router-dom": "^6.15.0", "react-router-dom": "^6.15.0",
"react-select": "^5.7.4",
"react-tabs": "^6.0.2", "react-tabs": "^6.0.2",
"react-toastify": "^9.1.3", "react-toastify": "^9.1.3",
"react-tooltip": "^5.21.3", "react-tooltip": "^5.21.3",

View File

@ -1,19 +0,0 @@
import { PropsWithRef } from 'react';
import Select, { SelectProps } from 'react-dropdown-select';
interface ConceptSelectProps<T>
extends Omit<PropsWithRef<SelectProps<T>>, 'noDataLabel'> {
}
function ConceptSelect<T extends object | string>({ className, ...props }: ConceptSelectProps<T>) {
return (
<Select
className={`overflow-x-ellipsis whitespace-nowrap clr-border clr-input outline-none ${className}`}
{...props}
noDataLabel='Список пуст'
/>
);
}
export default ConceptSelect;

View File

@ -0,0 +1,66 @@
import { useMemo } from 'react';
import Select, { GroupBase, Props, StylesConfig } from 'react-select';
import { useConceptTheme } from '../../context/ThemeContext';
import { selectDarkT, selectLightT } from '../../utils/color';
interface ConceptSelectSingleProps<
Option,
Group extends GroupBase<Option> = GroupBase<Option>
>
extends Omit<Props<Option, false, Group>, 'theme'> {
}
function ConceptSelectSingle<
Option,
Group extends GroupBase<Option> = GroupBase<Option>
> ({ ...props }: ConceptSelectSingleProps<Option, Group>) {
const { darkMode, colors } = useConceptTheme();
const themeColors = useMemo(
() => {
return !darkMode ? selectLightT : selectDarkT;
}, [darkMode]);
const adjustedStyles: StylesConfig<Option, false, Group> = useMemo(
() => {
return {
control: (styles, { isDisabled }) => {
return {
...styles,
borderRadius: '0.25rem',
cursor: isDisabled ? 'not-allowed' : 'pointer'
};
},
option: (styles, { isSelected }) => {
return {
...styles,
backgroundColor: isSelected ? colors.bgSelected : styles.backgroundColor,
color: isSelected ? colors.fgSelected : styles.color,
borderWidth: '1px',
borderColor: colors.border
};
},
input: (styles) => ({...styles}),
placeholder: (styles) => ({...styles}),
singleValue: (styles) => ({...styles}),
};
}, [colors]);
return (
<Select
noOptionsMessage={() => 'Список пуст'}
theme={theme => ({
...theme,
borderRadius: 0,
colors: {
...theme.colors,
...themeColors
},
})}
styles={adjustedStyles}
{...props}
/>
);
}
export default ConceptSelectSingle;

View File

@ -1,6 +1,6 @@
import { Extension } from '@codemirror/state'; import { Extension } from '@codemirror/state';
import { tags as t } from '@lezer/highlight'; import { tags } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes'; import { createTheme } from '@uiw/codemirror-themes';
import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror'; import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror';
import { EditorView } from 'codemirror'; import { EditorView } from 'codemirror';
@ -64,9 +64,9 @@ function RSInput({
}, [internalRef, innerref]); }, [internalRef, innerref]);
const cursor = useMemo(() => editable ? 'cursor-text': 'cursor-default', [editable]); const cursor = useMemo(() => editable ? 'cursor-text': 'cursor-default', [editable]);
const lightTheme: Extension = useMemo( const customTheme: Extension = useMemo(
() => createTheme({ () => createTheme({
theme: 'light', theme: darkMode ? 'dark' : 'light',
settings: { settings: {
fontFamily: 'inherit', fontFamily: 'inherit',
background: editable ? colors.bgInput : colors.bgDefault, background: editable ? colors.bgInput : colors.bgDefault,
@ -74,35 +74,15 @@ function RSInput({
selection: colors.bgHover selection: colors.bgHover
}, },
styles: [ styles: [
{ tag: t.name, class: 'text-[#b266ff] cursor-default' }, // GlobalID { tag: tags.name, color: colors.fgPurple, cursor: 'default' }, // GlobalID
{ tag: t.variableName, class: 'text-[#24821a]' }, // LocalID { tag: tags.variableName, color: colors.fgGreen }, // LocalID
{ tag: t.propertyName, class: '' }, // Radical { tag: tags.propertyName, color: colors.fgTeal }, // Radical
{ tag: t.keyword, class: 'text-[#001aff]' }, // keywords { tag: tags.keyword, color: colors.fgBlue }, // keywords
{ tag: t.literal, class: 'text-[#001aff]' }, // literals { tag: tags.literal, color: colors.fgBlue }, // literals
{ tag: t.controlKeyword, class: 'font-semibold'}, // R | I | D { tag: tags.controlKeyword, fontWeight: '500'}, // R | I | D
{ tag: t.unit, class: 'text-[0.75rem]' }, // indicies { tag: tags.unit, fontSize: '0.75rem' }, // indicies
] ]
}), [editable, colors]); }), [editable, colors, darkMode]);
const darkTheme: Extension = useMemo(
() => createTheme({
theme: 'dark',
settings: {
fontFamily: 'inherit',
background: editable ? colors.bgInput : colors.bgDefault,
foreground: colors.fgDefault,
selection: colors.bgHover
},
styles: [
{ tag: t.name, class: 'text-[#dfbfff] cursor-default' }, // GlobalID
{ tag: t.variableName, class: 'text-[#69bf60]' }, // LocalID
{ tag: t.propertyName, class: '' }, // Radical
{ tag: t.keyword, class: 'text-[#808dff]' }, // keywords
{ tag: t.literal, class: 'text-[#808dff]' }, // literals
{ tag: t.controlKeyword, class: 'font-semibold'}, // R | I | D
{ tag: t.unit, class: 'text-[0.75rem]' }, // indicies
]
}), [editable, colors]);
const editorExtensions = useMemo( const editorExtensions = useMemo(
() => [ () => [
@ -137,7 +117,7 @@ function RSInput({
}, [thisRef]); }, [thisRef]);
return ( return (
<div className={`flex flex-col w-full ${cursor}`}> <div className={`flex flex-col w-full ${cursor}`}>
{label && {label &&
<Label <Label
text={label} text={label}
@ -148,7 +128,7 @@ function RSInput({
<CodeMirror id={id} <CodeMirror id={id}
ref={thisRef} ref={thisRef}
basicSetup={editorSetup} basicSetup={editorSetup}
theme={darkMode ? darkTheme : lightTheme} theme={customTheme}
extensions={editorExtensions} extensions={editorExtensions}
indentWithTab={false} indentWithTab={false}
onChange={onChange} onChange={onChange}

View File

@ -6,52 +6,48 @@
:root { :root {
/* Light Theme */ /* Light Theme */
--cl-bg-120: #ffffff; --cl-bg-120: hsl(000, 000%, 100%);
--cl-bg-100: #f9fafb; --cl-bg-100: hsl(220, 020%, 098%);
--cl-bg-80: #f3f4f6; --cl-bg-80: hsl(220, 014%, 096%);
--cl-bg-60: #e5e7eb; --cl-bg-60: hsl(220, 013%, 091%);
--cl-bg-40: #d1d5db; --cl-bg-40: hsl(216, 012%, 084%);
--cl-fg-40: #8c8c8c; --cl-fg-60: hsl(000, 000%, 055%);
--cl-fg-60: #777777; --cl-fg-80: hsl(000, 000%, 047%);
--cl-fg-80: #333333; --cl-fg-100: hsl(000, 000%, 000%);
--cl-fg-100: #000000;
--cl-prim-bg-100: #3377ff; --cl-prim-bg-100: hsl(220, 100%, 060%);
--cl-prim-bg-80: #ccddff; --cl-prim-bg-80: hsl(220, 100%, 090%);
--cl-prim-bg-60: #e0ebff; --cl-prim-bg-60: hsl(220, 100%, 094%);
--cl-prim-fg-60: #1a63ff; --cl-prim-fg-80: hsl(220, 100%, 050%);
--cl-prim-fg-80: #0051ff; --cl-prim-fg-100: hsl(000, 000%, 100%);
--cl-prim-fg-100: #ffffff;
--cl-red-bg-100: #ffe5e5; --cl-red-bg-100: hsl(000, 100%, 095%);
--cl-red-fg-100: #dc2626; --cl-red-fg-100: hsl(000, 072%, 051%);
--cl-green-fg-100: #4ade80; --cl-green-fg-100: hsl(120, 080%, 058%);
/* Dark Theme */ /* Dark Theme */
--cd-bg-120: #0d0d0d; --cd-bg-120: hsl(000, 000%, 005%);
--cd-bg-100: #181818; --cd-bg-100: hsl(000, 000%, 009%);
--cd-bg-80: #272727; --cd-bg-80: hsl(000, 000%, 015%);
--cd-bg-60: #383838; --cd-bg-60: hsl(000, 000%, 022%);
--cd-bg-40: #595959; --cd-bg-40: hsl(000, 000%, 035%);
--cd-fg-40: #878792; --cd-fg-60: hsl(000, 000%, 055%);
--cd-fg-60: #bcbcc2; --cd-fg-80: hsl(000, 000%, 080%);
--cd-fg-80: #d4d4d8; --cd-fg-100: hsl(000, 000%, 093%);
--cd-fg-100: #e4e4e7;
--cd-prim-bg-100: #e66000; --cd-prim-bg-100: hsl(025, 079%, 052%);
--cd-prim-bg-80: #cc7700; --cd-prim-bg-80: hsl(035, 080%, 043%);
--cd-prim-bg-60: #995900; --cd-prim-bg-60: hsl(045, 080%, 031%);
--cd-prim-fg-60: #ffa666; --cd-prim-fg-80: hsl(025, 080%, 050%);
--cd-prim-fg-80: #ff6a00; --cd-prim-fg-100: hsl(000, 000%, 100%);
--cd-prim-fg-100: #ffffff;
--cd-red-bg-100: #4d0000; --cd-red-bg-100: hsl(000, 100%, 015%);
--cd-red-fg-100: #ff334b; --cd-red-fg-100: hsl(000, 100%, 060%);
--cd-green-fg-100: #22c55e; --cd-green-fg-100: hsl(120, 080%, 040%);
/* Import overrides */ /* Import overrides */
--toastify-color-dark: var(--cd-bg-60); --toastify-color-dark: var(--cd-bg-60);
@ -192,10 +188,10 @@
.clr-btn-default, .clr-btn-default,
.clr-btn-primary .clr-btn-primary
):disabled { ):disabled {
color: var(--cl-fg-60); color: var(--cl-fg-80);
background-color: var(--cl-bg-60); background-color: var(--cl-bg-60);
.dark & { .dark & {
color: var(--cd-fg-60); color: var(--cd-fg-80);
background-color: var(--cd-bg-60); background-color: var(--cd-bg-60);
} }
} }
@ -220,9 +216,9 @@
):focus { ):focus {
outline-width: 2px; outline-width: 2px;
outline-style: solid; outline-style: solid;
outline-color: var(--cl-bg-40); outline-color: var(--cl-prim-bg-100);
.dark & { .dark & {
outline-color: var(--cd-bg-40); outline-color: var(--cd-prim-bg-100);
} }
} }
@ -236,28 +232,28 @@
} }
.clr-footer { .clr-footer {
color: var(--cl-fg-40); color: var(--cl-fg-60);
.dark & { .dark & {
color: var(--cd-fg-40); color: var(--cd-fg-60);
} }
} }
.clr-btn-nav { .clr-btn-nav {
color: var(--cl-fg-60); color: var(--cl-fg-80);
.dark & { .dark & {
color: var(--cd-fg-60); color: var(--cd-fg-80);
} }
} }
.clr-btn-clear { .clr-btn-clear {
color: var(--cl-fg-60); color: var(--cl-fg-80);
&:disabled { &:disabled {
color: var(--cl-fg-40); color: var(--cl-fg-60);
} }
.dark & { .dark & {
color: var(--cd-fg-60); color: var(--cd-fg-80);
&:disabled { &:disabled {
color: var(--cd-fg-40); color: var(--cd-fg-60);
} }
} }
} }
@ -293,10 +289,10 @@
} }
.cm-editor.cm-focused { .cm-editor.cm-focused {
border-color: var(--cl-bg-40); border-color: var(--cl-bg-40);
outline-color: var(--cl-bg-40); outline-color: var(--cl-prim-bg-100);
.dark & { .dark & {
border-color: var(--cd-bg-40); border-color: var(--cd-bg-40);
outline-color: var(--cd-bg-40); outline-color: var(--cd-prim-bg-100);
} }
@apply border shadow rounded outline-2 outline @apply border shadow rounded outline-2 outline
} }
@ -304,22 +300,3 @@
.rdt_TableCell{ .rdt_TableCell{
font-size: 0.875rem; font-size: 0.875rem;
} }
.react-dropdown-select-item {
color: var(--cl-fg-100);
border-color: var(--cl-bg-40);
background-color: var(--cl-bg-100);
&:hover {
background-color: var(--cl-prim-bg-60);
}
.dark & {
color: var(--cd-fg-100);
border-color: var(--cd-bg-40);
background-color: var(--cd-bg-100);
&:hover {
background-color: var(--cd-prim-bg-60);
}
}
}

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import ConceptSelect from '../../components/Common/ConceptSelect'; import ConceptSelectSingle from '../../components/Common/ConceptSelectSingle';
import Modal from '../../components/Common/Modal'; import Modal from '../../components/Common/Modal';
import TextArea from '../../components/Common/TextArea'; import TextArea from '../../components/Common/TextArea';
import RSInput from '../../components/RSInput'; import RSInput from '../../components/RSInput';
@ -55,15 +55,15 @@ function DlgCreateCst({ hideWindow, initial, onCreate }: DlgCreateCstProps) {
canSubmit={validated} canSubmit={validated}
onSubmit={handleSubmit} onSubmit={handleSubmit}
> >
<div className='h-fit w-[35rem] px-2 mb-2 flex flex-col justify-stretch'> <div className='h-fit w-[35rem] px-2 mb-2 flex flex-col justify-stretch'>
<div className='flex justify-center w-full'> <div className='flex justify-center w-full'>
<ConceptSelect <ConceptSelectSingle
className='my-2 min-w-[15rem] self-center' className='my-2 min-w-[15rem] self-center'
options={CstTypeSelector} options={CstTypeSelector}
placeholder='Выберите тип' placeholder='Выберите тип'
values={selectedType ? [{ value: selectedType, label: getCstTypeLabel(selectedType) }] : []} value={selectedType ? { value: selectedType, label: getCstTypeLabel(selectedType) } : null}
onChange={data => setSelectedType(data.length > 0 ? data[0].value : CstType.BASE)} onChange={data => setSelectedType(data?.value ?? CstType.BASE)}
/> />
</div> </div>
<TextArea id='term' label='Термин' <TextArea id='term' label='Термин'
placeholder='Схемный или предметный термин, обозначающий данное понятие или утверждение' placeholder='Схемный или предметный термин, обозначающий данное понятие или утверждение'
@ -72,12 +72,14 @@ function DlgCreateCst({ hideWindow, initial, onCreate }: DlgCreateCstProps) {
spellCheck spellCheck
onChange={event => setTerm(event.target.value)} onChange={event => setTerm(event.target.value)}
/> />
<RSInput id='expression' label='Формальное выражение' <div className='mt-3'>
editable <RSInput id='expression' label='Формальное выражение'
height='5.5rem' editable
value={expression} height='5.5rem'
onChange={value => setExpression(value)} value={expression}
/> onChange={value => setExpression(value)}
/>
</div>
<TextArea id='definition' label='Текстовое определение' <TextArea id='definition' label='Текстовое определение'
placeholder='Лингвистическая интерпретация формального выражения' placeholder='Лингвистическая интерпретация формального выражения'
rows={2} rows={2}
@ -92,7 +94,7 @@ function DlgCreateCst({ hideWindow, initial, onCreate }: DlgCreateCstProps) {
spellCheck spellCheck
onChange={event => setConvention(event.target.value)} onChange={event => setConvention(event.target.value)}
/> />
</div> </div>
</Modal> </Modal>
); );
} }

View File

@ -1,6 +1,6 @@
import { useLayoutEffect, useState } from 'react'; import { useLayoutEffect, useState } from 'react';
import ConceptSelect from '../../components/Common/ConceptSelect'; import ConceptSelectSingle from '../../components/Common/ConceptSelectSingle';
import Modal from '../../components/Common/Modal'; import Modal from '../../components/Common/Modal';
import TextInput from '../../components/Common/TextInput'; import TextInput from '../../components/Common/TextInput';
import { useRSForm } from '../../context/RSFormContext'; import { useRSForm } from '../../context/RSFormContext';
@ -67,12 +67,12 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
submitText='Переименовать' submitText='Переименовать'
> >
<div className='flex items-center gap-4 px-2 my-2 h-fit min-w-[25rem]'> <div className='flex items-center gap-4 px-2 my-2 h-fit min-w-[25rem]'>
<ConceptSelect <ConceptSelectSingle
className='min-w-[14rem] self-center' className='min-w-[14rem] self-center'
options={CstTypeSelector} options={CstTypeSelector}
placeholder='Выберите тип' placeholder='Выберите тип'
values={cstType ? [{ value: cstType, label: getCstTypeLabel(cstType) }] : []} value={cstType ? { value: cstType, label: getCstTypeLabel(cstType) } : null}
onChange={data => setCstType(data.length > 0 ? data[0].value : CstType.BASE)} onChange={data => setCstType(data?.value ?? CstType.BASE)}
/> />
<div> <div>
<TextInput id='alias' label='Имя' <TextInput id='alias' label='Имя'

View File

@ -5,7 +5,7 @@ import { GraphCanvas, GraphCanvasRef, GraphEdge,
import Button from '../../components/Common/Button'; import Button from '../../components/Common/Button';
import Checkbox from '../../components/Common/Checkbox'; import Checkbox from '../../components/Common/Checkbox';
import ConceptSelect from '../../components/Common/ConceptSelect'; import ConceptSelectSingle from '../../components/Common/ConceptSelectSingle';
import ConceptTooltip from '../../components/Common/ConceptTooltip'; import ConceptTooltip from '../../components/Common/ConceptTooltip';
import Divider from '../../components/Common/Divider'; import Divider from '../../components/Common/Divider';
import MiniButton from '../../components/Common/MiniButton'; import MiniButton from '../../components/Common/MiniButton';
@ -16,7 +16,7 @@ import { useRSForm } from '../../context/RSFormContext';
import { useConceptTheme } from '../../context/ThemeContext'; import { useConceptTheme } from '../../context/ThemeContext';
import useLocalStorage from '../../hooks/useLocalStorage'; import useLocalStorage from '../../hooks/useLocalStorage';
import { graphDarkT, graphLightT, IColorTheme } from '../../utils/color'; import { graphDarkT, graphLightT, IColorTheme } from '../../utils/color';
import { prefixes, resources } from '../../utils/constants'; import { prefixes, resources, TIMEOUT_GRAPH_REFRESH } from '../../utils/constants';
import { Graph } from '../../utils/Graph'; import { Graph } from '../../utils/Graph';
import { CstType, IConstituenta, ICstCreateData } from '../../utils/models'; import { CstType, IConstituenta, ICstCreateData } from '../../utils/models';
import { getCstClassColor, getCstStatusColor, import { getCstClassColor, getCstStatusColor,
@ -220,9 +220,9 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
}, [selectedDismissed, selections]); }, [selectedDismissed, selections]);
const nothingSelected = useMemo(() => allSelected.length === 0, [allSelected]); const nothingSelected = useMemo(() => allSelected.length === 0, [allSelected]);
const handleRecreate = useCallback( const handleResetViewpoint = useCallback(
() => { () => {
graphRef.current?.resetControls(); graphRef.current?.resetControls(true);
graphRef.current?.centerGraph(); graphRef.current?.centerGraph();
}, []); }, []);
@ -293,6 +293,16 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
}); });
} }
function handleChangeLayout(newLayout: LayoutTypes) {
if (newLayout === layout) {
return;
}
setLayout(newLayout);
setTimeout(() => {
handleResetViewpoint();
}, TIMEOUT_GRAPH_REFRESH);
}
function getOptions() { function getOptions() {
return { return {
noHermits: noHermits, noHermits: noHermits,
@ -394,23 +404,23 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
widthClass='h-full' widthClass='h-full'
onClick={() => setShowOptions(true)} onClick={() => setShowOptions(true)}
/> />
<ConceptSelect <ConceptSelectSingle
className='min-w-[9.8rem]' className='min-w-[9.8rem]'
options={GraphColoringSelector} options={GraphColoringSelector}
searchable={false} isSearchable={false}
placeholder='Выберите цвет' placeholder='Выберите цвет'
values={coloringScheme ? [{ value: coloringScheme, label: mapColoringLabels.get(coloringScheme) }] : []} value={coloringScheme ? { value: coloringScheme, label: mapColoringLabels.get(coloringScheme) } : null}
onChange={data => setColoringScheme(data.length > 0 ? data[0].value : GraphColoringSelector[0].value)} onChange={data => setColoringScheme(data?.value ?? GraphColoringSelector[0].value)}
/> />
</div> </div>
<ConceptSelect <ConceptSelectSingle
className='w-full mt-1' className='w-full mt-1'
options={GraphLayoutSelector} options={GraphLayoutSelector}
searchable={false} isSearchable={false}
placeholder='Способ расположения' placeholder='Способ расположения'
values={layout ? [{ value: layout, label: mapLayoutLabels.get(layout) }] : []} value={layout ? { value: layout, label: mapLayoutLabels.get(layout) } : null}
onChange={data => setLayout(data.length > 0 ? data[0].value : GraphLayoutSelector[0].value)} onChange={data => handleChangeLayout(data?.value ?? GraphLayoutSelector[0].value)}
/> />
<Checkbox <Checkbox
label='Скрыть текст' label='Скрыть текст'
@ -471,8 +481,8 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
</div> </div>
<MiniButton <MiniButton
icon={<ArrowsRotateIcon size={5} />} icon={<ArrowsRotateIcon size={5} />}
tooltip='Пересоздать граф' tooltip='Восстановить камеру'
onClick={handleRecreate} onClick={handleResetViewpoint}
/> />
</div> </div>
<ConceptTooltip anchorSelect='#items-graph-help'> <ConceptTooltip anchorSelect='#items-graph-help'>

View File

@ -1,71 +1,108 @@
export interface IColorTheme { // =========== Modules contains all dynamic color definitions ==========
red: string
green: string
blue: string
teal: string
orange: string
// ============= MAIN COLOR THEMES ==========
export interface IColorTheme {
bgDefault: string bgDefault: string
bgInput: string bgInput: string
bgControls: string bgControls: string
bgDisabled: string bgDisabled: string
bgHover: string bgPrimary: string
bgSelected: string bgSelected: string
bgHover: string
bgWarning: string bgWarning: string
border: string border: string
fgDefault: string fgDefault: string
fgSelected: string
fgDisabled: string fgDisabled: string
fgWarning: string fgWarning: string
// Hightlight syntax accents
bgRed: string
bgGreen: string
bgBlue: string
bgPurple: string
bgTeal: string
bgOrange: string
fgRed: string
fgGreen: string
fgBlue: string
fgPurple: string
fgTeal: string
fgOrange: string
} }
// =========== GENERAL THEMES ========= // ======= Light =======
export const lightT: IColorTheme = { export const lightT: IColorTheme = {
red: '#ffc9c9', bgDefault: 'var(--cl-bg-100)',
green: '#aaff80', bgInput: 'var(--cl-bg-120)',
blue: '#b3bdff',
teal: '#a5e9fa',
orange: '#ffbb80',
bgDefault: 'var(--cl-bg-100)',
bgInput: 'var(--cl-bg-120)',
bgControls: 'var(--cl-bg-80)', bgControls: 'var(--cl-bg-80)',
bgDisabled: 'var(--cl-bg-60)', bgDisabled: 'var(--cl-bg-60)',
bgHover: 'var(--cl-prim-bg-60)', bgPrimary: 'var(--cl-prim-bg-100)',
bgSelected: 'var(--cl-prim-bg-80)', bgSelected: 'var(--cl-prim-bg-80)',
bgWarning: 'var(--cl-red-bg-100)', bgHover: 'var(--cl-prim-bg-60)',
bgWarning: 'var(--cl-red-bg-100)',
border: 'var(--cl-bg-40)', border: 'var(--cl-bg-40)',
fgDefault: 'var(--cl-fg-100)', fgDefault: 'var(--cl-fg-100)',
fgDisabled: 'var(--cl-fg-60)', fgSelected: 'var(--cl-fg-100)',
fgWarning: 'var(--cl-red-fg-100)' fgDisabled: 'var(--cl-fg-80)',
fgWarning: 'var(--cl-red-fg-100)',
// Hightlight syntax accents
bgRed: 'hsl(000, 100%, 089%)',
bgGreen: 'hsl(100, 100%, 075%)',
bgBlue: 'hsl(235, 100%, 085%)',
bgPurple: 'hsl(274, 089%, 081%)',
bgTeal: 'hsl(192, 089%, 081%)',
bgOrange: 'hsl(028, 100%, 075%)',
fgRed: 'hsl(000, 090%, 045%)',
fgGreen: 'hsl(100, 090%, 035%)',
fgBlue: 'hsl(235, 100%, 050%)',
fgPurple: 'hsl(270, 100%, 070%)',
fgTeal: 'hsl(192, 090%, 040%)',
fgOrange: 'hsl(030, 090%, 055%)'
}; };
// ======= DARK ========
export const darkT: IColorTheme = { export const darkT: IColorTheme = {
red: '#bf0d00', bgDefault: 'var(--cd-bg-100)',
green: '#2b8000', bgInput: 'var(--cd-bg-120)',
blue: '#394bbf',
teal: '#007a99',
orange: '#964600',
bgDefault: 'var(--cd-bg-100)',
bgInput: 'var(--cd-bg-120)',
bgControls: 'var(--cd-bg-80)', bgControls: 'var(--cd-bg-80)',
bgDisabled: 'var(--cd-bg-60)', bgDisabled: 'var(--cd-bg-60)',
bgHover: 'var(--cd-prim-bg-60)', bgPrimary: 'var(--cd-prim-bg-100)',
bgSelected: 'var(--cd-prim-bg-80)', bgSelected: 'var(--cd-prim-bg-80)',
bgWarning: 'var(--cd-red-bg-100)', bgHover: 'var(--cd-prim-bg-60)',
bgWarning: 'var(--cd-red-bg-100)',
border: 'var(--cd-bg-40)', border: 'var(--cd-bg-40)',
fgDefault: 'var(--cd-fg-100)', fgDefault: 'var(--cd-fg-100)',
fgDisabled: 'var(--cd-fg-60)', fgSelected: 'var(--cd-fg-100)',
fgWarning: 'var(--cd-red-fg-100)' fgDisabled: 'var(--cd-fg-80)',
fgWarning: 'var(--cd-red-fg-100)',
// Hightlight syntax accents
bgRed: 'hsl(000, 080%, 037%)',
bgGreen: 'hsl(100, 080%, 025%)',
bgBlue: 'hsl(235, 054%, 049%)',
bgPurple: 'hsl(270, 080%, 050%)',
bgTeal: 'hsl(192, 080%, 030%)',
bgOrange: 'hsl(035, 100%, 035%)',
fgRed: 'hsl(000, 080%, 050%)',
fgGreen: 'hsl(100, 080%, 040%)',
fgBlue: 'hsl(235, 100%, 080%)',
fgPurple: 'hsl(270, 100%, 080%)',
fgTeal: 'hsl(192, 100%, 030%)',
fgOrange: 'hsl(035, 100%, 050%)'
}; };
// ========= DATA TABLE THEMES ======== // ========= DATA TABLE THEMES ========
export const dataTableLightT = { export const dataTableLightT = {
text: { text: {
@ -119,6 +156,51 @@ export const dataTableDarkT = {
} }
}; };
// ============ SELECT THEMES ==========
export const selectLightT = {
primary: lightT.bgPrimary,
primary75: lightT.bgSelected,
primary50: lightT.bgHover,
primary25: lightT.bgHover,
danger: lightT.fgWarning,
dangerLight: lightT.bgWarning,
neutral0: lightT.bgInput,
neutral5: lightT.bgDefault,
neutral10: lightT.border,
neutral20: lightT.border,
neutral30: lightT.border,
neutral40: lightT.fgDisabled,
neutral50: lightT.fgWarning,
neutral60: lightT.fgDefault,
neutral70: lightT.fgWarning,
neutral80: lightT.fgDefault,
neutral90: lightT.fgWarning
}
export const selectDarkT = {
primary: darkT.bgPrimary,
primary75: darkT.bgSelected,
primary50: darkT.bgHover,
primary25: darkT.bgHover,
danger: darkT.fgWarning,
dangerLight: darkT.bgWarning,
neutral0: darkT.bgInput,
neutral5: darkT.bgDefault,
neutral10: darkT.border,
neutral20: darkT.border,
neutral30: darkT.border,
neutral40: darkT.fgDisabled,
neutral50: darkT.fgWarning,
neutral60: darkT.fgDefault,
neutral70: darkT.fgWarning,
neutral80: darkT.fgDefault,
neutral90: darkT.fgWarning
}
// ============ GRAPH THEMES ========== // ============ GRAPH THEMES ==========
export const graphLightT = { export const graphLightT = {
canvas: { canvas: {
@ -221,20 +303,22 @@ export const graphDarkT = {
// ======== Bracket Matching Themes =========== // ======== Bracket Matching Themes ===========
export const bracketsLightT = { export const bracketsLightT = {
'.cc-nonmatchingBracket': { '.cc-nonmatchingBracket': {
color: lightT.fgWarning, color: lightT.fgRed,
fontWeight: 700, fontWeight: 700,
}, },
'&.cm-focused .cc-matchingBracket': { '&.cm-focused .cc-matchingBracket': {
backgroundColor: lightT.bgSelected, backgroundColor: lightT.bgSelected,
color: lightT.fgSelected
}, },
}; };
export const bracketsDarkT = { export const bracketsDarkT = {
'.cc-nonmatchingBracket': { '.cc-nonmatchingBracket': {
color: darkT.fgWarning, color: darkT.fgRed,
fontWeight: 700, fontWeight: 700,
}, },
'&.cm-focused .cc-matchingBracket': { '&.cm-focused .cc-matchingBracket': {
backgroundColor: darkT.bgSelected, backgroundColor: darkT.bgSelected,
color: darkT.fgSelected
}, },
}; };

View File

@ -3,6 +3,7 @@ export const config = {
backend: import.meta.env.VITE_PORTAL_BACKEND as string backend: import.meta.env.VITE_PORTAL_BACKEND as string
}; };
export const TIMEOUT_UI_REFRESH = 100; export const TIMEOUT_UI_REFRESH = 100;
export const TIMEOUT_GRAPH_REFRESH = 200;
export const youtube = { export const youtube = {
intro: '0Ty9mu9sOJo' intro: '0Ty9mu9sOJo'

View File

@ -315,12 +315,12 @@ export const GraphColoringSelector: {value: ColoringScheme, label: string}[] = [
export function getCstStatusColor(status: ExpressionStatus, colors: IColorTheme): string { export function getCstStatusColor(status: ExpressionStatus, colors: IColorTheme): string {
switch (status) { switch (status) {
case ExpressionStatus.VERIFIED: return colors.green; case ExpressionStatus.VERIFIED: return colors.bgGreen;
case ExpressionStatus.INCORRECT: return colors.red; case ExpressionStatus.INCORRECT: return colors.bgRed;
case ExpressionStatus.INCALCULABLE: return colors.orange; case ExpressionStatus.INCALCULABLE: return colors.bgOrange;
case ExpressionStatus.PROPERTY: return colors.teal; case ExpressionStatus.PROPERTY: return colors.bgTeal;
case ExpressionStatus.UNKNOWN: return colors.blue; case ExpressionStatus.UNKNOWN: return colors.bgBlue;
case ExpressionStatus.UNDEFINED: return colors.blue; case ExpressionStatus.UNDEFINED: return colors.bgBlue;
} }
} }
@ -392,10 +392,10 @@ export const mapTopicInfo: Map<HelpTopic, IDescriptor> = new Map([
export function getCstClassColor(cstClass: CstClass, colors: IColorTheme): string { export function getCstClassColor(cstClass: CstClass, colors: IColorTheme): string {
switch (cstClass) { switch (cstClass) {
case CstClass.BASIC: return colors.green; case CstClass.BASIC: return colors.bgGreen;
case CstClass.DERIVED: return colors.blue; case CstClass.DERIVED: return colors.bgBlue;
case CstClass.STATEMENT: return colors.red; case CstClass.STATEMENT: return colors.bgRed;
case CstClass.TEMPLATE: return colors.teal; case CstClass.TEMPLATE: return colors.bgTeal;
} }
} }
@ -684,7 +684,7 @@ export function getASTNodeColor(node: ISyntaxTreeNode, colors: IColorTheme): str
case TokenID.PUNC_DEFINE: case TokenID.PUNC_DEFINE:
case TokenID.PUNC_STRUCT: case TokenID.PUNC_STRUCT:
case TokenID.ID_LOCAL: case TokenID.ID_LOCAL:
return colors.green; return colors.bgGreen;
case TokenID.ID_GLOBAL: case TokenID.ID_GLOBAL:
case TokenID.ID_FUNCTION: case TokenID.ID_FUNCTION:
@ -693,7 +693,7 @@ export function getASTNodeColor(node: ISyntaxTreeNode, colors: IColorTheme): str
case TokenID.LIT_INTEGER: case TokenID.LIT_INTEGER:
case TokenID.LIT_EMPTYSET: case TokenID.LIT_EMPTYSET:
case TokenID.LIT_INTSET: case TokenID.LIT_INTSET:
return colors.teal; return colors.bgTeal;
case TokenID.FORALL: case TokenID.FORALL:
case TokenID.EXISTS: case TokenID.EXISTS:
@ -713,7 +713,7 @@ export function getASTNodeColor(node: ISyntaxTreeNode, colors: IColorTheme): str
case TokenID.SUBSET_OR_EQ: case TokenID.SUBSET_OR_EQ:
case TokenID.SUBSET: case TokenID.SUBSET:
case TokenID.NOTSUBSET: case TokenID.NOTSUBSET:
return colors.orange; return colors.bgOrange;
case TokenID.NT_TUPLE: case TokenID.NT_TUPLE:
case TokenID.NT_ENUMERATION: case TokenID.NT_ENUMERATION:
@ -733,7 +733,7 @@ export function getASTNodeColor(node: ISyntaxTreeNode, colors: IColorTheme): str
case TokenID.CARD: case TokenID.CARD:
case TokenID.BOOL: case TokenID.BOOL:
case TokenID.DEBOOL: case TokenID.DEBOOL:
return colors.blue; return colors.bgBlue;
case TokenID.NT_FUNC_DEFINITION: case TokenID.NT_FUNC_DEFINITION:
case TokenID.NT_DECLARATIVE_EXPR: case TokenID.NT_DECLARATIVE_EXPR:
@ -752,8 +752,8 @@ export function getASTNodeColor(node: ISyntaxTreeNode, colors: IColorTheme): str
case TokenID.PUNC_ASSIGN: case TokenID.PUNC_ASSIGN:
case TokenID.PUNC_ITERATE: case TokenID.PUNC_ITERATE:
return colors.red; return colors.bgRed;
} }
// node // node
return colors.red; return colors.bgRed;
} }