# Conflicts:
#	rsconcept/frontend/src/index.css
This commit is contained in:
Ulle9 2023-09-09 12:21:27 +03:00
commit d8002923cb
34 changed files with 1462 additions and 1243 deletions

View File

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

View File

@ -9,12 +9,11 @@ For more specific TODOs see comments in code
- блок организации библиотеки моделей
- проектный модуль?
- обратная связь - система баг репортов
- система обработки ошибок backend
[Tech]
- create custom Select component
- reload react-data-table-component
[deployment]
- database backup daemon
- logs collection
- status dashboard for servers

View File

@ -1,5 +1,5 @@
# ======== Multi-stage base ==========
FROM node:bullseye-slim as node-base
FROM node:18-bullseye-slim as node-base
RUN apt-get update -qq && \
apt-get upgrade -y && \
rm -rf /var/lib/apt/lists/*

File diff suppressed because it is too large Load Diff

View File

@ -12,44 +12,44 @@
"preview": "vite preview"
},
"dependencies": {
"@lezer/lr": "^1.3.9",
"@uiw/codemirror-themes": "^4.21.9",
"@uiw/react-codemirror": "^4.21.9",
"axios": "^1.4.0",
"@lezer/lr": "^1.3.10",
"@uiw/codemirror-themes": "^4.21.13",
"@uiw/react-codemirror": "^4.21.13",
"axios": "^1.5.0",
"js-file-download": "^0.4.12",
"react": "^18.2.0",
"react-data-table-component": "^7.5.3",
"react-data-table-component": "^7.5.4",
"react-dom": "^18.2.0",
"react-dropdown-select": "^4.10.0",
"react-error-boundary": "^4.0.10",
"react-error-boundary": "^4.0.11",
"react-intl": "^6.4.4",
"react-loader-spinner": "^5.3.4",
"react-router-dom": "^6.14.2",
"react-loader-spinner": "^5.4.5",
"react-router-dom": "^6.15.0",
"react-select": "^5.7.4",
"react-tabs": "^6.0.2",
"react-toastify": "^9.1.3",
"react-tooltip": "^5.19.0",
"reagraph": "^4.11.1"
"react-tooltip": "^5.21.3",
"reagraph": "^4.13.0"
},
"devDependencies": {
"@lezer/generator": "^1.4.0",
"@types/jest": "^29.5.3",
"@types/node": "^20.4.5",
"@types/react": "^18.2.15",
"@lezer/generator": "^1.5.0",
"@types/jest": "^29.5.4",
"@types/node": "^20.5.9",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitejs/plugin-react": "^4.0.3",
"autoprefixer": "^10.4.14",
"eslint": "^8.45.0",
"@typescript-eslint/eslint-plugin": "^6.6.0",
"@typescript-eslint/parser": "^6.6.0",
"@vitejs/plugin-react": "^4.0.4",
"autoprefixer": "^10.4.15",
"eslint": "^8.48.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"eslint-plugin-simple-import-sort": "^10.0.0",
"jest": "^29.6.2",
"postcss": "^8.4.27",
"jest": "^29.6.4",
"postcss": "^8.4.29",
"tailwindcss": "^3.3.3",
"ts-jest": "^29.1.1",
"typescript": "^5.0.2",
"vite": "^4.4.5"
"typescript": "^5.2.2",
"vite": "^4.4.9"
},
"jest": {
"preset": "ts-jest",

View File

@ -1,5 +1,6 @@
import { useRef } from 'react';
import { useMemo } from 'react';
import { CheckboxChecked } from '../Icons';
import Label from './Label';
export interface CheckboxProps {
@ -9,51 +10,52 @@ export interface CheckboxProps {
disabled?: boolean
widthClass?: string
tooltip?: string
value?: boolean
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
value: boolean
setValue?: (newValue: boolean) => void
}
function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-fit', value, onChange }: CheckboxProps) {
const inputRef = useRef<HTMLInputElement | null>(null);
const cursor = disabled ? 'cursor-not-allowed' : 'cursor-pointer';
function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-fit', value, setValue }: CheckboxProps) {
const cursor = useMemo(
() => {
if (disabled) {
return 'cursor-not-allowed';
} else if (setValue) {
return 'cursor-pointer';
} else {
return ''
}
}, [disabled, setValue]);
const bgColor = useMemo(
() => {
return value !== false ? 'clr-primary' : 'clr-app'
}, [value]);
function handleClick(event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void {
event.preventDefault();
if (!disabled) {
inputRef.current?.click();
if (disabled || !setValue) {
return;
}
setValue(!value);
}
return (
<button
className={'flex [&:not(:first-child)]:mt-3 clr-outline focus:outline-dotted focus:outline-1 ' + widthClass}
id={id}
className={`flex items-center [&:not(:first-child)]:mt-3 clr-outline focus:outline-dotted focus:outline-1 ${widthClass}`}
title={tooltip}
disabled={disabled}
onClick={handleClick}
>
<input id={id} type='checkbox' ref={inputRef}
className={`relative peer w-4 h-4 shrink-0 mt-0.5 border rounded-sm appearance-none clr-checkbox ${cursor}`}
required={required}
disabled={disabled}
checked={value}
onChange={onChange}
tabIndex={-1}
/>
<div className={`relative peer w-4 h-4 shrink-0 mt-0.5 border rounded-sm appearance-none ${bgColor} ${cursor}`} />
{ label &&
<Label
className={`${cursor} px-2`}
className={`${cursor} px-2 text-start`}
text={label}
required={required}
htmlFor={id}
/>}
<svg
className='absolute hidden w-3 h-3 mt-1 ml-0.5 text-white pointer-events-none peer-checked:block'
viewBox='0 0 512 512'
fill='currentColor'
>
<path d='M470.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L192 338.7l233.4-233.3c12.5-12.5 32.8-12.5 45.3 0z' />
</svg>
{value && <CheckboxChecked />}
</button>
);
}

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,15 +1,15 @@
import Checkbox from './Checkbox';
interface DropdownCheckboxProps {
value: boolean
label?: string
tooltip?: string
disabled?: boolean
value?: boolean
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
setValue?: (newValue: boolean) => void
}
function DropdownCheckbox({ tooltip, onChange, disabled, ...props }: DropdownCheckboxProps) {
const behavior = (onChange && !disabled ? 'clr-hover' : '');
function DropdownCheckbox({ tooltip, setValue, disabled, ...props }: DropdownCheckboxProps) {
const behavior = (setValue && !disabled ? 'clr-hover' : '');
return (
<div
title={tooltip}
@ -18,7 +18,7 @@ function DropdownCheckbox({ tooltip, onChange, disabled, ...props }: DropdownChe
<Checkbox
widthClass='w-full'
disabled={disabled}
onChange={onChange}
setValue={setValue}
{...props}
/>
</div>

View File

@ -4,18 +4,22 @@ import { UploadIcon } from '../Icons';
import Button from './Button';
import Label from './Label';
interface FileInputProps {
id?: string
required?: boolean
interface FileInputProps
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'className' | 'title' | 'style' | 'accept' | 'type'> {
label: string
tooltip?: string
acceptType?: string
widthClass?: string
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
}
function FileInput({ id, required, label, acceptType, widthClass = 'w-full', onChange }: FileInputProps) {
function FileInput({
label, acceptType, tooltip,
widthClass = 'w-fit', onChange,
...props
}: FileInputProps) {
const inputRef = useRef<HTMLInputElement | null>(null);
const [labelText, setLabelText] = useState('');
const [fileName, setFileName] = useState('');
const handleUploadClick = () => {
inputRef.current?.click();
@ -23,9 +27,9 @@ function FileInput({ id, required, label, acceptType, widthClass = 'w-full', onC
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files && event.target.files.length > 0) {
setLabelText(event.target.files[0].name)
setFileName(event.target.files[0].name)
} else {
setLabelText('')
setFileName('')
}
if (onChange) {
onChange(event);
@ -33,21 +37,22 @@ function FileInput({ id, required, label, acceptType, widthClass = 'w-full', onC
};
return (
<div className={'flex flex-col gap-2 py-2 [&:not(:first-child)]:mt-3 items-start ' + widthClass}>
<input id={id} type='file'
<div className={`flex flex-col gap-2 py-2 mt-3 items-start ${widthClass}`}>
<input type='file'
ref={inputRef}
required={required}
style={{ display: 'none' }}
accept={acceptType}
onChange={handleFileChange}
{...props}
/>
<Button
text={label}
icon={<UploadIcon/>}
onClick={handleUploadClick}
tooltip={tooltip}
/>
<Label
text={labelText}
text={fileName}
/>
</div>
);

View File

@ -14,7 +14,7 @@ function SubmitButton({
return (
<button type='submit'
title={tooltip}
className={`px-4 py-2 inline-flex items-center gap-2 align-middle justify-center font-bold select-none disabled:cursor-not-allowed border rounded clr-btn-primary ${widthClass} ${loading ? ' cursor-progress' : ''}`}
className={`px-4 py-2 inline-flex items-center gap-2 align-middle justify-center font-semibold select-none disabled:cursor-not-allowed border rounded clr-btn-primary ${widthClass} ${loading ? ' cursor-progress' : ''}`}
disabled={disabled ?? loading}
>
{icon && <span>{icon}</span>}

View File

@ -1,9 +1,7 @@
import { type InputHTMLAttributes } from 'react';
import Label from './Label';
interface TextInputProps
extends Omit<InputHTMLAttributes<HTMLInputElement>, 'className' | 'title'> {
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'className' | 'title'> {
id: string
label: string
tooltip?: string

View File

@ -0,0 +1,64 @@
import { useMemo } from 'react';
import { CheckboxChecked, CheckboxNull } from '../Icons';
import { CheckboxProps } from './Checkbox';
import Label from './Label';
export interface TristateProps
extends Omit<CheckboxProps, 'value' | 'setValue'> {
value: boolean | null
setValue?: (newValue: boolean | null) => void
}
function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-fit', value, setValue }: TristateProps) {
const cursor = useMemo(
() => {
if (disabled) {
return 'cursor-not-allowed';
} else if (setValue) {
return 'cursor-pointer';
} else {
return ''
}
}, [disabled, setValue]);
const bgColor = useMemo(
() => {
return value !== false ? 'clr-primary' : 'clr-app'
}, [value]);
function handleClick(event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void {
event.preventDefault();
if (disabled || !setValue) {
return;
}
if (value === false) {
setValue(null);
} else {
setValue(!value);
}
}
return (
<button
id={id}
className={`flex items-center [&:not(:first-child)]:mt-3 clr-outline focus:outline-dotted focus:outline-1 ${widthClass}`}
title={tooltip}
disabled={disabled}
onClick={handleClick}
>
<div className={`relative peer w-4 h-4 shrink-0 mt-0.5 border rounded-sm appearance-none ${bgColor} ${cursor}`} />
{ label &&
<Label
className={`${cursor} px-2 text-start`}
text={label}
required={required}
htmlFor={id}
/>}
{value && <CheckboxChecked />}
{value === null && <CheckboxNull />}
</button>
);
}
export default Checkbox;

View File

@ -317,3 +317,27 @@ export function InDoor(props: IconProps) {
</IconSVG>
);
}
export function CheckboxChecked() {
return (
<svg
className='absolute w-3 h-3 mt-1 ml-0.5'
viewBox='0 0 512 512'
fill='#ffffff'
>
<path d='M470.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L192 338.7l233.4-233.3c12.5-12.5 32.8-12.5 45.3 0z' />
</svg>
);
}
export function CheckboxNull() {
return (
<svg
className='absolute w-3 h-3 mt-1 ml-0.5'
viewBox='0 0 512 512'
fill='#ffffff'
>
<path d='M2 7.75A.75.75 0 012.75 7h10a.75.75 0 010 1.5h-10A.75.75 0 012 7.75z' />
</svg>
);
}

View File

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

View File

@ -6,60 +6,48 @@
:root {
/* Light Theme */
--cl-bg-120: #ffffff;
--cl-bg-100: #f9fafb;
--cl-bg-80: #f3f4f6;
--cl-bg-60: #e5e7eb;
--cl-bg-40: #d1d5db;
--cl-bg-120: hsl(000, 000%, 100%);
--cl-bg-100: hsl(220, 020%, 098%);
--cl-bg-80: hsl(220, 014%, 096%);
--cl-bg-60: hsl(220, 013%, 091%);
--cl-bg-40: hsl(216, 012%, 084%);
--cl-fg-40: #8c8c8c;
--cl-fg-60: #777777;
--cl-fg-80: #333333;
--cl-fg-100: #000000;
--cl-fg-60: hsl(000, 000%, 055%);
--cl-fg-80: hsl(000, 000%, 047%);
--cl-fg-100: hsl(000, 000%, 000%);
--cl-prim-bg-100: #3377ff;
--cl-prim-bg-80: #ccddff;
--cl-prim-bg-60: #e0ebff;
--cl-prim-bg-100: hsl(220, 100%, 060%);
--cl-prim-bg-80: hsl(220, 100%, 090%);
--cl-prim-bg-60: hsl(220, 100%, 094%);
--cl-prim-fg-60: #1a63ff;
--cl-prim-fg-80: #0051ff;
--cl-prim-fg-100: #ffffff;
--cl-prim-fg-80: hsl(220, 100%, 050%);
--cl-prim-fg-100: hsl(000, 000%, 100%);
--cl-red-bg-100: #ffe5e5;
--cl-red-fg-100: #dc2626;
--cl-green-fg-100: #4ade80;
--cl-red-bg-100: hsl(000, 100%, 095%);
--cl-red-fg-100: hsl(000, 072%, 051%);
--cl-green-fg-100: hsl(120, 080%, 058%);
/* Dark Theme */
--cd-bg-120: #0d0d0d;
--cd-bg-100: #181818;
--cd-bg-80: #272727;
--cd-bg-60: #383838;
--cd-bg-40: #595959;
--cd-bg-120: hsl(000, 000%, 005%);
--cd-bg-100: hsl(000, 000%, 009%);
--cd-bg-80: hsl(000, 000%, 015%);
--cd-bg-60: hsl(000, 000%, 022%);
--cd-bg-40: hsl(000, 000%, 035%);
--cd-fg-40: #878792;
--cd-fg-60: #bcbcc2;
--cd-fg-80: #d4d4d8;
--cd-fg-100: #e4e4e7;
--cd-fg-60: hsl(000, 000%, 055%);
--cd-fg-80: hsl(000, 000%, 080%);
--cd-fg-100: hsl(000, 000%, 093%);
/* --cd-prim-bg-100: #e66000;
--cd-prim-bg-80: #cc7700;
--cd-prim-bg-60: #995900;
--cd-prim-bg-100: hsl(025, 079%, 052%);
--cd-prim-bg-80: hsl(035, 080%, 043%);
--cd-prim-bg-60: hsl(045, 080%, 031%);
--cd-prim-fg-60: #ffa666;
--cd-prim-fg-80: #ff6a00;
--cd-prim-fg-100: #ffffff; */
--cd-prim-fg-80: hsl(025, 080%, 050%);
--cd-prim-fg-100: hsl(000, 000%, 100%);
--cd-prim-bg-100: hsl(267, 90%, 60%);
--cd-prim-bg-80: hsl(267, 90%, 45%);
--cd-prim-bg-60: hsl(269, 90%, 30%);
--cd-prim-fg-60: hsl(267, 90%, 45%);
--cd-prim-fg-80: hsl(267, 90%, 60%);
--cd-prim-fg-100: #ffffff;
--cd-red-bg-100: #4d0000;
--cd-red-fg-100: #ff334b;
--cd-green-fg-100: #22c55e;
--cd-red-bg-100: hsl(000, 100%, 015%);
--cd-red-fg-100: hsl(000, 100%, 060%);
--cd-green-fg-100: hsl(120, 080%, 040%);
/* Import overrides */
--toastify-color-dark: var(--cd-bg-60);
@ -148,7 +136,6 @@
.clr-footer,
.clr-modal-backdrop,
.clr-btn-nav,
.clr-checkbox,
.clr-input:disabled
) {
background-color: var(--cl-bg-100);
@ -176,8 +163,7 @@
:is(.clr-primary,
.clr-btn-primary:hover,
.clr-btn-primary:focus,
.clr-checkbox:checked
.clr-btn-primary:focus
) {
color: var(--cl-prim-fg-100);
background-color: var(--cl-prim-bg-100);
@ -202,10 +188,10 @@
.clr-btn-default,
.clr-btn-primary
):disabled {
color: var(--cl-fg-60);
color: var(--cl-fg-80);
background-color: var(--cl-bg-60);
.dark & {
color: var(--cd-fg-60);
color: var(--cd-fg-80);
background-color: var(--cd-bg-60);
}
}
@ -230,9 +216,9 @@
):focus {
outline-width: 2px;
outline-style: solid;
outline-color: var(--cl-bg-40);
outline-color: var(--cl-prim-bg-100);
.dark & {
outline-color: var(--cd-bg-40);
outline-color: var(--cd-prim-bg-100);
}
}
@ -246,28 +232,28 @@
}
.clr-footer {
color: var(--cl-fg-40);
color: var(--cl-fg-60);
.dark & {
color: var(--cd-fg-40);
color: var(--cd-fg-60);
}
}
.clr-btn-nav {
color: var(--cl-fg-60);
color: var(--cl-fg-80);
.dark & {
color: var(--cd-fg-60);
color: var(--cd-fg-80);
}
}
.clr-btn-clear {
color: var(--cl-fg-60);
color: var(--cl-fg-80);
&:disabled {
color: var(--cl-fg-40);
color: var(--cl-fg-60);
}
.dark & {
color: var(--cd-fg-60);
color: var(--cd-fg-80);
&:disabled {
color: var(--cd-fg-40);
color: var(--cd-fg-60);
}
}
}
@ -303,10 +289,10 @@
}
.cm-editor.cm-focused {
border-color: var(--cl-bg-40);
outline-color: var(--cl-bg-40);
outline-color: var(--cl-prim-bg-100);
.dark & {
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
}
@ -314,22 +300,3 @@
.rdt_TableCell{
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,18 +1,21 @@
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { toast } from 'react-toastify';
import BackendError from '../components/BackendError';
import Button from '../components/Common/Button';
import Checkbox from '../components/Common/Checkbox';
import FileInput from '../components/Common/FileInput';
import Form from '../components/Common/Form';
import Label from '../components/Common/Label';
import MiniButton from '../components/Common/MiniButton';
import SubmitButton from '../components/Common/SubmitButton';
import TextArea from '../components/Common/TextArea';
import TextInput from '../components/Common/TextInput';
import { UploadIcon } from '../components/Icons';
import RequireAuth from '../components/RequireAuth';
import { useLibrary } from '../context/LibraryContext';
import { useConceptNavigation } from '../context/NagivationContext';
import { EXTEOR_TRS_FILE } from '../utils/constants';
import { IRSFormCreateData, LibraryItemType } from '../utils/models';
function CreateRSFormPage() {
@ -24,20 +27,15 @@ function CreateRSFormPage() {
const [alias, setAlias] = useState('');
const [comment, setComment] = useState('');
const [common, setCommon] = useState(false);
const [file, setFile] = useState<File | undefined>()
const [fileName, setFileName] = useState('');
const [file, setFile] = useState<File | undefined>();
const inputRef = useRef<HTMLInputElement | null>(null);
useEffect(() => {
setError(undefined);
}, [title, alias, setError]);
function handleFile(event: React.ChangeEvent<HTMLInputElement>) {
if (event.target.files && event.target.files.length > 0) {
setFile(event.target.files[0]);
} else {
setFile(undefined);
}
}
function handleCancel() {
if (location.key !== 'default') {
navigateHistory(-1);
@ -67,12 +65,39 @@ function CreateRSFormPage() {
});
}
function handleFileChange(event: React.ChangeEvent<HTMLInputElement>) {
if (event.target.files && event.target.files.length > 0) {
setFileName(event.target.files[0].name);
setFile(event.target.files[0]);
} else {
setFileName('');
setFile(undefined);
}
}
return (
<RequireAuth>
<Form title='Создание концептуальной схемы'
onSubmit={handleSubmit}
widthClass='max-w-lg w-full mt-4'
>
<div className='relative w-full'>
<div className='absolute top-[-2.4rem] right-[-1rem] flex'>
<input
type='file'
ref={inputRef}
style={{ display: 'none' }}
accept={EXTEOR_TRS_FILE}
onChange={handleFileChange}
/>
<MiniButton
tooltip='Загрузить из Экстеор'
icon={<UploadIcon size={5} color='text-primary'/>}
onClick={() => inputRef.current?.click()}
/>
</div>
</div>
{ fileName && <Label text={`Загружен файл: ${fileName}`} />}
<TextInput id='title' label='Полное название' type='text'
required={!file}
placeholder={file && 'Загрузить из файла'}
@ -93,13 +118,8 @@ function CreateRSFormPage() {
/>
<Checkbox id='common' label='Общедоступная схема'
value={common}
onChange={event => setCommon(event.target.checked)}
setValue={value => setCommon(value ?? false)}
/>
<FileInput id='trs' label='Загрузить из Экстеор'
acceptType='.trs'
onChange={handleFile}
/>
<div className='flex items-center justify-center gap-4 py-2 mt-4'>
<SubmitButton
text='Создать схему'

View File

@ -36,38 +36,38 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) {
{ pickerMenu.isActive &&
<Dropdown>
<DropdownCheckbox
onChange={() => handleChange(LibraryFilterStrategy.MANUAL)}
setValue={() => handleChange(LibraryFilterStrategy.MANUAL)}
value={value === LibraryFilterStrategy.MANUAL}
label='Отображать все'
/>
<DropdownCheckbox
onChange={() => handleChange(LibraryFilterStrategy.COMMON)}
setValue={() => handleChange(LibraryFilterStrategy.COMMON)}
value={value === LibraryFilterStrategy.COMMON}
label='Общедоступные'
tooltip='Отображать только общедоступные схемы'
/>
<DropdownCheckbox
onChange={() => handleChange(LibraryFilterStrategy.CANONICAL)}
setValue={() => handleChange(LibraryFilterStrategy.CANONICAL)}
value={value === LibraryFilterStrategy.CANONICAL}
label='Неизменные'
tooltip='Отображать только стандартные схемы'
/>
<DropdownCheckbox
onChange={() => handleChange(LibraryFilterStrategy.PERSONAL)}
setValue={() => handleChange(LibraryFilterStrategy.PERSONAL)}
value={value === LibraryFilterStrategy.PERSONAL}
label='Личные'
disabled={!user}
tooltip='Отображать только подписки и владеемые схемы'
/>
<DropdownCheckbox
onChange={() => handleChange(LibraryFilterStrategy.SUBSCRIBE)}
setValue={() => handleChange(LibraryFilterStrategy.SUBSCRIBE)}
value={value === LibraryFilterStrategy.SUBSCRIBE}
label='Подписки'
disabled={!user}
tooltip='Отображать только подписки'
/>
<DropdownCheckbox
onChange={() => handleChange(LibraryFilterStrategy.OWNED)}
setValue={() => handleChange(LibraryFilterStrategy.OWNED)}
value={value === LibraryFilterStrategy.OWNED}
disabled={!user}
label='Я - Владелец!'

View File

@ -79,7 +79,7 @@ function DlgCloneRSForm({ hideWindow }: DlgCloneRSFormProps) {
/>
<Checkbox id='common' label='Общедоступная схема'
value={common}
onChange={event => setCommon(event.target.checked)}
setValue={value => setCommon(value)}
/>
</Modal>
);

View File

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

View File

@ -52,7 +52,7 @@ function DlgDeleteCst({ hideWindow, selected, onDelete }: DlgDeleteCstProps) {
<Checkbox
label='Удалить зависимые конституенты'
value={expandOut}
onChange={data => setExpandOut(data.target.checked)}
setValue={value => setExpandOut(value)}
/>
</div>
</Modal>

View File

@ -81,25 +81,25 @@ function DlgGraphOptions({ hideWindow, initial, onConfirm }:DlgGraphOptionsProps
label='Скрыть текст'
tooltip='Не отображать термины'
value={noTerms}
onChange={ event => setNoTerms(event.target.checked) }
setValue={ value => setNoTerms(value) }
/>
<Checkbox
label='Скрыть несвязанные'
tooltip='Неиспользуемые конституенты'
value={noHermits}
onChange={ event => setNoHermits(event.target.checked) }
setValue={ value => setNoHermits(value) }
/>
<Checkbox
label='Скрыть шаблоны'
tooltip='Терм-функции и предикат-функции с параметризованными аргументами'
value={noTemplates}
onChange={ event => setNoTemplates(event.target.checked) }
setValue={ value => setNoTemplates(value) }
/>
<Checkbox
label='Транзитивная редукция'
tooltip='Удалить связи, образующие транзитивные пути в графе'
value={noTransitive}
onChange={ event => setNoTransitive(event.target.checked) }
setValue={ value => setNoTransitive(value) }
/>
</div>
<div className='flex flex-col'>
@ -107,42 +107,42 @@ function DlgGraphOptions({ hideWindow, initial, onConfirm }:DlgGraphOptionsProps
<Checkbox
label={getCstTypeLabel(CstType.BASE)}
value={allowBase}
onChange={ event => setAllowBase(event.target.checked) }
setValue={ value => setAllowBase(value) }
/>
<Checkbox
label={getCstTypeLabel(CstType.STRUCTURED)}
value={allowStruct}
onChange={ event => setAllowStruct(event.target.checked) }
setValue={ value => setAllowStruct(value) }
/>
<Checkbox
label={getCstTypeLabel(CstType.TERM)}
value={allowTerm}
onChange={ event => setAllowTerm(event.target.checked) }
setValue={ value => setAllowTerm(value) }
/>
<Checkbox
label={getCstTypeLabel(CstType.AXIOM)}
value={allowAxiom}
onChange={ event => setAllowAxiom(event.target.checked) }
setValue={ value => setAllowAxiom(value) }
/>
<Checkbox
label={getCstTypeLabel(CstType.FUNCTION)}
value={allowFunction}
onChange={ event => setAllowFunction(event.target.checked) }
setValue={ value => setAllowFunction(value) }
/>
<Checkbox
label={getCstTypeLabel(CstType.PREDICATE)}
value={allowPredicate}
onChange={ event => setAllowPredicate(event.target.checked) }
setValue={ value => setAllowPredicate(value) }
/>
<Checkbox
label={getCstTypeLabel(CstType.CONSTANT)}
value={allowConstant}
onChange={ event => setAllowConstant(event.target.checked) }
setValue={ value => setAllowConstant(value) }
/>
<Checkbox
label={getCstTypeLabel(CstType.THEOREM)}
value={allowTheorem}
onChange={ event => setAllowTheorem(event.target.checked) }
setValue ={ value => setAllowTheorem(value) }
/>
</div>
</div>

View File

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

View File

@ -5,6 +5,7 @@ import Checkbox from '../../components/Common/Checkbox';
import FileInput from '../../components/Common/FileInput';
import Modal from '../../components/Common/Modal';
import { useRSForm } from '../../context/RSFormContext';
import { EXTEOR_TRS_FILE } from '../../utils/constants';
import { IRSFormUploadData } from '../../utils/models';
interface DlgUploadRSFormProps {
@ -44,16 +45,17 @@ function DlgUploadRSForm({ hideWindow }: DlgUploadRSFormProps) {
onSubmit={handleSubmit}
submitText='Загрузить'
>
<div className='max-w-[20rem]'>
<div className='flex flex-col items-center'>
<FileInput
label='Выбрать файл'
acceptType='.trs'
acceptType={EXTEOR_TRS_FILE}
onChange={handleFile}
/>
<Checkbox
label='Загружать название и комментарий'
value={loadMetadata}
onChange={event => setLoadMetadata(event.target.checked)}
setValue={value => setLoadMetadata(value)}
widthClass='w-fit pb-2'
/>
</div>
</Modal>

View File

@ -136,14 +136,14 @@ function EditorRSForm({ onDestroy, onClaim, onShare, isModified, setIsModified,
value={common}
widthClass='w-fit mt-3'
disabled={!isEditable}
onChange={event => setCommon(event.target.checked)}
setValue={value => setCommon(value)}
/>
<Checkbox id='canonical' label='Неизменная схема'
widthClass='w-fit'
value={canonical}
tooltip='Только администраторы могут присваивать схемам неизменный статус'
disabled={!isEditable || !isForceAdmin}
onChange={event => setCanonical(event.target.checked)}
setValue={value => setCanonical(value)}
/>
</div>

View File

@ -5,7 +5,7 @@ import { GraphCanvas, GraphCanvasRef, GraphEdge,
import Button from '../../components/Common/Button';
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 Divider from '../../components/Common/Divider';
import MiniButton from '../../components/Common/MiniButton';
@ -16,7 +16,7 @@ import { useRSForm } from '../../context/RSFormContext';
import { useConceptTheme } from '../../context/ThemeContext';
import useLocalStorage from '../../hooks/useLocalStorage';
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 { CstType, IConstituenta, ICstCreateData } from '../../utils/models';
import { getCstClassColor, getCstStatusColor,
@ -220,9 +220,9 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
}, [selectedDismissed, selections]);
const nothingSelected = useMemo(() => allSelected.length === 0, [allSelected]);
const handleRecreate = useCallback(
const handleResetViewpoint = useCallback(
() => {
graphRef.current?.resetControls();
graphRef.current?.resetControls(true);
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() {
return {
noHermits: noHermits,
@ -394,39 +404,39 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
widthClass='h-full'
onClick={() => setShowOptions(true)}
/>
<ConceptSelect
<ConceptSelectSingle
className='min-w-[9.8rem]'
options={GraphColoringSelector}
searchable={false}
isSearchable={false}
placeholder='Выберите цвет'
values={coloringScheme ? [{ value: coloringScheme, label: mapColoringLabels.get(coloringScheme) }] : []}
onChange={data => setColoringScheme(data.length > 0 ? data[0].value : GraphColoringSelector[0].value)}
value={coloringScheme ? { value: coloringScheme, label: mapColoringLabels.get(coloringScheme) } : null}
onChange={data => setColoringScheme(data?.value ?? GraphColoringSelector[0].value)}
/>
</div>
<ConceptSelect
<ConceptSelectSingle
className='w-full mt-1'
options={GraphLayoutSelector}
searchable={false}
isSearchable={false}
placeholder='Способ расположения'
values={layout ? [{ value: layout, label: mapLayoutLabels.get(layout) }] : []}
onChange={data => setLayout(data.length > 0 ? data[0].value : GraphLayoutSelector[0].value)}
value={layout ? { value: layout, label: mapLayoutLabels.get(layout) } : null}
onChange={data => handleChangeLayout(data?.value ?? GraphLayoutSelector[0].value)}
/>
<Checkbox
label='Скрыть текст'
value={noTerms}
onChange={ event => setNoTerms(event.target.checked) }
setValue={ value => setNoTerms(value) }
/>
<Checkbox
label='Транзитивная редукция'
value={noTransitive}
onChange={ event => setNoTransitive(event.target.checked) }
setValue={ value => setNoTransitive(value) }
/>
<Checkbox
disabled={!is3D}
label='Анимация вращения'
value={orbit}
onChange={ event => setOrbit(event.target.checked) }
setValue={ value => setOrbit(value) }
/>
<Divider margins='mt-3 mb-2' />
@ -471,8 +481,8 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
</div>
<MiniButton
icon={<ArrowsRotateIcon size={5} />}
tooltip='Пересоздать граф'
onClick={handleRecreate}
tooltip='Восстановить камеру'
onClick={handleResetViewpoint}
/>
</div>
<ConceptTooltip anchorSelect='#items-graph-help'>

View File

@ -14,7 +14,7 @@ import { useConceptNavigation } from '../../context/NagivationContext';
import { useRSForm } from '../../context/RSFormContext';
import { useConceptTheme } from '../../context/ThemeContext';
import useModificationPrompt from '../../hooks/useModificationPrompt';
import { prefixes, TIMEOUT_UI_REFRESH } from '../../utils/constants';
import { EXTEOR_TRS_FILE, prefixes, TIMEOUT_UI_REFRESH } from '../../utils/constants';
import { ICstCreateData, ICstRenameData, SyntaxTree } from '../../utils/models';
import { createAliasFor } from '../../utils/staticUI';
import DlgCloneRSForm from './DlgCloneRSForm';
@ -256,7 +256,7 @@ function RSTabs() {
return;
}
}
const fileName = (schema?.alias ?? 'Schema') + '.trs';
const fileName = (schema?.alias ?? 'Schema') + EXTEOR_TRS_FILE;
download(
(data) => {
try {

View File

@ -146,14 +146,14 @@ function RSTabsMenu({
{(isOwned || user?.is_staff) &&
<DropdownCheckbox
value={isReadonly}
onChange={toggleReadonly}
setValue={toggleReadonly}
label='Я — читатель!'
tooltip='Режим чтения'
/>}
{user?.is_staff &&
<DropdownCheckbox
value={isForceAdmin}
onChange={toggleForceAdmin}
setValue={toggleForceAdmin}
label='Я — администратор!'
tooltip='Режим редактирования для администраторов'
/>}

View File

@ -1,70 +1,107 @@
export interface IColorTheme {
red: string
green: string
blue: string
teal: string
orange: string
// =========== Modules contains all dynamic color definitions ==========
// ============= MAIN COLOR THEMES ==========
export interface IColorTheme {
bgDefault: string
bgInput: string
bgControls: string
bgDisabled: string
bgHover: string
bgPrimary: string
bgSelected: string
bgHover: string
bgWarning: string
border: string
fgDefault: string
fgSelected: string
fgDisabled: 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 = {
red: '#ffc9c9',
green: '#aaff80',
blue: '#b3bdff',
teal: '#a5e9fa',
orange: '#ffbb80',
bgDefault: 'var(--cl-bg-100)',
bgInput: 'var(--cl-bg-120)',
bgControls: 'var(--cl-bg-80)',
bgDisabled: 'var(--cl-bg-60)',
bgHover: 'var(--cl-prim-bg-60)',
bgPrimary: 'var(--cl-prim-bg-100)',
bgSelected: 'var(--cl-prim-bg-80)',
bgHover: 'var(--cl-prim-bg-60)',
bgWarning: 'var(--cl-red-bg-100)',
border: 'var(--cl-bg-40)',
fgDefault: 'var(--cl-fg-100)',
fgDisabled: 'var(--cl-fg-60)',
fgWarning: 'var(--cl-red-fg-100)'
fgSelected: 'var(--cl-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 = {
red: '#bf0d00',
green: '#2b8000',
blue: '#394bbf',
teal: '#007a99',
orange: '#964600',
bgDefault: 'var(--cd-bg-100)',
bgInput: 'var(--cd-bg-120)',
bgControls: 'var(--cd-bg-80)',
bgDisabled: 'var(--cd-bg-60)',
bgHover: 'var(--cd-prim-bg-60)',
bgPrimary: 'var(--cd-prim-bg-100)',
bgSelected: 'var(--cd-prim-bg-80)',
bgHover: 'var(--cd-prim-bg-60)',
bgWarning: 'var(--cd-red-bg-100)',
border: 'var(--cd-bg-40)',
fgDefault: 'var(--cd-fg-100)',
fgDisabled: 'var(--cd-fg-60)',
fgWarning: 'var(--cd-red-fg-100)'
};
fgSelected: 'var(--cd-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 ========
export const dataTableLightT = {
@ -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 ==========
export const graphLightT = {
canvas: {
@ -221,20 +303,22 @@ export const graphDarkT = {
// ======== Bracket Matching Themes ===========
export const bracketsLightT = {
'.cc-nonmatchingBracket': {
color: lightT.fgWarning,
color: lightT.fgRed,
fontWeight: 700,
},
'&.cm-focused .cc-matchingBracket': {
backgroundColor: lightT.bgSelected,
color: lightT.fgSelected
},
};
export const bracketsDarkT = {
'.cc-nonmatchingBracket': {
color: darkT.fgWarning,
color: darkT.fgRed,
fontWeight: 700,
},
'&.cm-focused .cc-matchingBracket': {
backgroundColor: darkT.bgSelected,
color: darkT.fgSelected
},
};

View File

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

View File

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

View File

@ -1,7 +1,7 @@
# Create venv and install dependencies + imports
$backend = Resolve-Path -Path "$PSScriptRoot\..\..\rsconcept\backend"
$frontend = Resolve-Path -Path "$PSScriptRoot\..\..\rsconcept\fronted"
$frontend = Resolve-Path -Path "$PSScriptRoot\..\..\rsconcept\frontend"
$envPath = "$backend\venv"
$python = "$envPath\Scripts\python.exe"
@ -12,7 +12,7 @@ function LocalDevelopmentSetup() {
function FrontendSetup() {
Set-Location $frontend
& npm install
& npm install --only=dev
}
function BackendSetup() {

View File

@ -46,4 +46,7 @@ DjangoDump()
EnsureLocation
PostgreDump
DjangoDump
echo "Backup created at: $destination"
green="\033[0;32m"
noColor='\033[0m'
echo -e "${green}Backup created at: ${destination}${noColor}"

View File

@ -1,3 +1,4 @@
git pull
/bin/bash ./scripts/prod/CreateBackup.sh
docker compose -f "docker-compose-prod.yml" up --build -d
docker image prune -a -f