Refactor UI elements

This commit is contained in:
IRBorisov 2023-09-14 16:53:38 +03:00
parent cf57f975f0
commit fbf2d0ba4d
13 changed files with 259 additions and 67 deletions

View File

@ -42,7 +42,7 @@ function Modal({
ref={ref}
className='fixed bottom-1/2 left-1/2 translate-y-1/2 -translate-x-1/2 px-6 py-4 flex flex-col justify-start w-fit max-w-[calc(100vw-2rem)] h-fit z-modal clr-app border shadow-md'
>
{ title && <h1 className='mb-2 text-xl'>{title}</h1> }
{ title && <h1 className='mb-2 text-xl select-none'>{title}</h1> }
<div className='max-h-[calc(100vh-8rem)]'>
{children}
</div>

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 SelectMultiProps<
Option,
Group extends GroupBase<Option> = GroupBase<Option>
>
extends Omit<Props<Option, true, Group>, 'theme'> {
}
function SelectMulti<
Option,
Group extends GroupBase<Option> = GroupBase<Option>
> ({ ...props }: SelectMultiProps<Option, Group>) {
const { darkMode, colors } = useConceptTheme();
const themeColors = useMemo(
() => !darkMode ? selectLightT : selectDarkT
, [darkMode]);
const adjustedStyles: StylesConfig<Option, true, Group> = useMemo(
() => ({
control: (styles, { isDisabled }) => ({
...styles,
borderRadius: '0.25rem',
cursor: isDisabled ? 'not-allowed' : 'pointer'
}),
option: (styles, { isSelected }) => ({
...styles,
backgroundColor: isSelected ? colors.bgSelected : styles.backgroundColor,
color: isSelected ? colors.fgSelected : styles.color,
borderWidth: '1px',
borderColor: colors.border
}),
input: (styles) => ({...styles}),
placeholder: (styles) => ({...styles}),
multiValue: styles => ({
...styles,
borderRadius: '0.5rem',
backgroundColor: colors.bgSelected,
}),
}), [colors]);
return (
<Select
noOptionsMessage={() => 'Список пуст'}
theme={theme => ({
...theme,
borderRadius: 0,
colors: {
...theme.colors,
...themeColors
},
})}
isMulti
styles={adjustedStyles}
{...props}
/>
);
}
export default SelectMulti;

View File

@ -4,17 +4,17 @@ import Select, { GroupBase, Props, StylesConfig } from 'react-select';
import { useConceptTheme } from '../../context/ThemeContext';
import { selectDarkT, selectLightT } from '../../utils/color';
interface ConceptSelectSingleProps<
interface SelectSingleProps<
Option,
Group extends GroupBase<Option> = GroupBase<Option>
>
extends Omit<Props<Option, false, Group>, 'theme'> {
}
function ConceptSelectSingle<
function SelectSingle<
Option,
Group extends GroupBase<Option> = GroupBase<Option>
> ({ ...props }: ConceptSelectSingleProps<Option, Group>) {
> ({ ...props }: SelectSingleProps<Option, Group>) {
const { darkMode, colors } = useConceptTheme();
const themeColors = useMemo(
() => !darkMode ? selectLightT : selectDarkT
@ -56,4 +56,4 @@ function ConceptSelectSingle<
);
}
export default ConceptSelectSingle;
export default SelectSingle;

View File

@ -2,8 +2,8 @@ import Label from './Label';
interface TextInputProps
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'className' | 'title'> {
id: string
label: string
id?: string
label?: string
tooltip?: string
widthClass?: string
colorClass?: string
@ -18,11 +18,11 @@ function TextInput({
}: TextInputProps) {
return (
<div className={`flex [&:not(:first-child)]:mt-3 ${singleRow ? 'items-center gap-4 ' + widthClass : 'flex-col items-start'}`}>
<Label
{label && <Label
text={label}
required={!props.disabled && required}
htmlFor={id}
/>
/>}
<input id={id}
title={tooltip}
className={`px-3 py-2 leading-tight border shadow truncate hover:text-clip clr-outline ${colorClass} ${singleRow ? '' : 'mt-2 ' + widthClass}`}

View File

@ -1,9 +1,5 @@
// Module: Natural language model declarations.
// ====== Text morphology ========
// ====== Reference resolution =====
export interface IRefsText {
text: string
@ -40,3 +36,103 @@ export interface IReferenceData {
output: string
refs: IResolvedReference[]
}
// ====== Morphology ========
export enum Morpheme {
// Части речи
NOUN = 'NOUN',
ADJF = 'ADJF',
ADJS = 'ADJS',
COMP = 'COMP',
VERB = 'VERB',
INFN = 'INFN',
PRTF = 'PRTF',
PRTS = 'PRTS',
GRND = 'GRND',
NUMR = 'NUMR',
ADVB = 'ADVB',
NPRO = 'NPRO',
PRED = 'PRED',
PREP = 'PREP',
CONJ = 'CONJ',
PRCL = 'PRCL',
INTJ = 'INTJ',
PNCT = 'PNCT',
// Одушевленность
anim = 'anim',
inan = 'inan',
// Род
masc = 'masc',
femn = 'femn',
neut = 'neut',
// Число
sing = 'sing',
plur = 'plur',
// Падеж (основные)
nomn = 'nomn',
gent = 'gent',
datv = 'datv',
accs = 'accs',
ablt = 'ablt',
loct = 'loct',
// Совершенный / несовершенный вид
perf = 'perf',
impf = 'impf',
// Переходность
tran = 'tran',
intr = 'intr',
// Время
pres = 'pres',
past = 'past',
futr = 'futr',
// Лицо
per1 = '1per',
per2 = '2per',
per3 = '3per',
// Наклонение
indc = 'indc',
impr = 'impr',
// Включение говорящего в действие
incl = 'incl',
excl = 'excl',
// Залог
actv = 'actv',
pssv = 'pssv',
// Стиль речи
Infr = 'Infr', // Неформальный
Slng = 'Slng', // Жаргон
Arch = 'Arch', // Устаревший
Litr = 'Litr', // Литературный
// Аббревиатура
Abbr = 'Abbr'
}
export const PartOfSpeech = [
Morpheme.NOUN, Morpheme.ADJF, Morpheme.ADJS, Morpheme.COMP,
Morpheme.VERB, Morpheme.INFN, Morpheme.PRTF, Morpheme.PRTS,
Morpheme.GRND, Morpheme.ADVB, Morpheme.NPRO, Morpheme.PRED,
Morpheme.PREP, Morpheme.CONJ, Morpheme.PRCL, Morpheme.INTJ,
Morpheme.PNCT
]
export const Gender = [
Morpheme.masc, Morpheme.femn, Morpheme.neut
]
export const Case = [
Morpheme.nomn, Morpheme.gent, Morpheme.datv,
Morpheme.accs, Morpheme.ablt, Morpheme.loct
]

View File

@ -1,11 +1,12 @@
import { useEffect, useState } from 'react';
import ConceptSelectSingle from '../../components/Common/ConceptSelectSingle';
import Modal, { ModalProps } from '../../components/Common/Modal';
import SelectSingle from '../../components/Common/SelectSingle';
import TextArea from '../../components/Common/TextArea';
import RSInput from '../../components/RSInput';
import { CstType,ICstCreateData } from '../../models/rsform';
import { CstTypeSelector, getCstTypeLabel } from '../../utils/staticUI';
import { SelectorCstType } from '../../utils/selectors';
import { getCstTypeLabel } from '../../utils/staticUI';
interface DlgCreateCstProps
extends Pick<ModalProps, 'hideWindow'> {
@ -57,9 +58,9 @@ 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'>
<ConceptSelectSingle
<SelectSingle
className='my-2 min-w-[15rem] self-center'
options={CstTypeSelector}
options={SelectorCstType}
placeholder='Выберите тип'
value={selectedType ? { value: selectedType, label: getCstTypeLabel(selectedType) } : null}
onChange={data => setSelectedType(data?.value ?? CstType.BASE)}

View File

@ -1,8 +1,12 @@
import { useLayoutEffect, useState } from 'react';
import Divider from '../../components/Common/Divider';
import Modal from '../../components/Common/Modal';
import SelectMulti from '../../components/Common/SelectMulti';
import TextArea from '../../components/Common/TextArea';
import TextInput from '../../components/Common/TextInput';
import { IConstituenta } from '../../models/rsform';
import { SelectorGraphLayout } from '../../utils/selectors';
interface DlgEditTermProps {
hideWindow: () => void
@ -28,19 +32,36 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
return (
<Modal
title='Редактирование термина'
title='Редактирование словоформ'
hideWindow={hideWindow}
submitText='Сохранить данные'
canSubmit
onSubmit={handleSubmit}
>
<div className='min-w-[40rem]'>
<TextArea id='nominal' label='Начальная форма'
placeholder='Начальная форма'
rows={2}
value={term}
disabled={true}
spellCheck
/>
<Divider margins='my-4' />
<div className='flex items-center justify-start gap-2 w-full'>
<SelectMulti
className='z-modal-top min-w-[10rem]'
options={SelectorGraphLayout}
placeholder='Способ расположения'
// value={null}
// onChange={data => handleChangeLayout(data?.value ?? SelectorGraphLayout[0].value)}
/>
<TextInput
/>
</div>
</div>
</Modal>
);
}

View File

@ -1,11 +1,12 @@
import { useLayoutEffect, useState } from 'react';
import ConceptSelectSingle from '../../components/Common/ConceptSelectSingle';
import Modal, { ModalProps } from '../../components/Common/Modal';
import SelectSingle from '../../components/Common/SelectSingle';
import TextInput from '../../components/Common/TextInput';
import { useRSForm } from '../../context/RSFormContext';
import { CstType, ICstRenameData } from '../../models/rsform';
import { createAliasFor, CstTypeSelector, getCstTypeLabel, getCstTypePrefix } from '../../utils/staticUI';
import { createAliasFor, getCstTypeLabel, getCstTypePrefix } from '../../utils/staticUI';
import { SelectorCstType } from '../../utils/selectors';
interface DlgRenameCstProps
extends Pick<ModalProps, 'hideWindow'> {
@ -67,9 +68,9 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
submitText='Переименовать'
>
<div className='flex items-center gap-4 px-2 my-2 h-fit min-w-[25rem]'>
<ConceptSelectSingle
<SelectSingle
className='min-w-[14rem] self-center z-modal-top'
options={CstTypeSelector}
options={SelectorCstType}
placeholder='Выберите тип'
value={cstType ? { value: cstType, label: getCstTypeLabel(cstType) } : null}
onChange={data => setCstType(data?.value ?? CstType.BASE)}

View File

@ -40,7 +40,7 @@ function EditorConstituenta({
onShowAST, onCreateCst, onRenameCst, onOpenEdit, onDeleteCst
}: EditorConstituentaProps) {
const windowSize = useWindowSize();
const { schema, processing, isEditable, cstUpdate, isForceAdmin } = useRSForm();
const { schema, processing, isEditable, cstUpdate } = useRSForm();
const [editMode, setEditMode] = useState(EditMode.TEXT);
@ -183,7 +183,7 @@ function EditorConstituenta({
icon={<PenIcon size={4} color={isEnabled ? 'text-primary' : ''} />}
/>
</div>
{isForceAdmin && <div className='relative'>
<div className='relative'>
<div className='absolute left-[3.2rem] top-[0.5rem]'>
<MiniButton
tooltip='Редактировать словоформы термина'
@ -193,7 +193,7 @@ function EditorConstituenta({
icon={<PenIcon size={4} color={isEnabled ? 'text-primary' : ''} />}
/>
</div>
</div>}
</div>
<ReferenceInput id='term' label='Термин'
placeholder='Обозначение, используемое в текстовых определениях данной схемы'
rows={2}

View File

@ -5,10 +5,10 @@ import { GraphCanvas, GraphCanvasRef, GraphEdge,
import Button from '../../components/Common/Button';
import Checkbox from '../../components/Common/Checkbox';
import ConceptSelectSingle from '../../components/Common/ConceptSelectSingle';
import ConceptTooltip from '../../components/Common/ConceptTooltip';
import Divider from '../../components/Common/Divider';
import MiniButton from '../../components/Common/MiniButton';
import SelectSingle from '../../components/Common/SelectSingle';
import HelpTermGraph from '../../components/Help/HelpTermGraph';
import InfoConstituenta from '../../components/Help/InfoConstituenta';
import { ArrowsRotateIcon, DumpBinIcon, FilterCogIcon, HelpIcon, SmallPlusIcon } from '../../components/Icons';
@ -19,8 +19,9 @@ import { CstType, IConstituenta, ICstCreateData } from '../../models/rsform';
import { graphDarkT, graphLightT, IColorTheme } from '../../utils/color';
import { prefixes, resources, TIMEOUT_GRAPH_REFRESH } from '../../utils/constants';
import { Graph } from '../../utils/Graph';
import { SelectorGraphLayout } from '../../utils/selectors';
import { SelectorGraphColoring } from '../../utils/selectors';
import { getCstClassColor, getCstStatusBgColor,
GraphColoringSelector, GraphLayoutSelector,
mapColoringLabels, mapLayoutLabels
} from '../../utils/staticUI';
import DlgGraphOptions from './DlgGraphOptions';
@ -404,23 +405,23 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
widthClass='h-full'
onClick={() => setShowOptions(true)}
/>
<ConceptSelectSingle
<SelectSingle
className='min-w-[9.8rem]'
options={GraphColoringSelector}
options={SelectorGraphColoring}
isSearchable={false}
placeholder='Выберите цвет'
value={coloringScheme ? { value: coloringScheme, label: mapColoringLabels.get(coloringScheme) } : null}
onChange={data => setColoringScheme(data?.value ?? GraphColoringSelector[0].value)}
onChange={data => setColoringScheme(data?.value ?? SelectorGraphColoring[0].value)}
/>
</div>
<ConceptSelectSingle
<SelectSingle
className='w-full mt-1'
options={GraphLayoutSelector}
options={SelectorGraphLayout}
isSearchable={false}
placeholder='Способ расположения'
value={layout ? { value: layout, label: mapLayoutLabels.get(layout) } : null}
onChange={data => handleChangeLayout(data?.value ?? GraphLayoutSelector[0].value)}
onChange={data => handleChangeLayout(data?.value ?? SelectorGraphLayout[0].value)}
/>
<Checkbox
label='Скрыть текст'

View File

@ -119,7 +119,7 @@ export const selectLightT = {
neutral20: lightT.border,
neutral30: lightT.border,
neutral40: lightT.fgDisabled,
neutral50: lightT.fgWarning,
neutral50: lightT.fgDisabled, // placeholder
neutral60: lightT.fgDefault,
neutral70: lightT.fgWarning,
neutral80: lightT.fgDefault,
@ -141,7 +141,7 @@ export const selectDarkT = {
neutral20: darkT.border,
neutral30: darkT.border,
neutral40: darkT.fgDisabled,
neutral50: darkT.fgWarning,
neutral50: darkT.fgDisabled, // placeholder
neutral60: darkT.fgDefault,
neutral70: darkT.fgWarning,
neutral80: darkT.fgDefault,

View File

@ -0,0 +1,38 @@
// Module: Selector maps
import { LayoutTypes } from 'reagraph';
import { CstType } from '../models/rsform';
import { ColoringScheme } from '../pages/RSFormPage/EditorTermGraph';
import { getCstTypeLabel } from './staticUI';
export const SelectorGraphLayout: { value: LayoutTypes; label: string; }[] = [
{ value: 'treeTd2d', label: 'Граф: ДеревоВ 2D' },
{ value: 'treeTd3d', label: 'Граф: ДеревоВ 3D' },
{ value: 'forceatlas2', label: 'Граф: Атлас 2D' },
{ value: 'forceDirected2d', label: 'Граф: Силы 2D' },
{ value: 'forceDirected3d', label: 'Граф: Силы 3D' },
{ value: 'treeLr2d', label: 'Граф: ДеревоГ 2D' },
{ value: 'treeLr3d', label: 'Граф: ДеревоГ 3D' },
{ value: 'radialOut2d', label: 'Граф: Радиальная 2D' },
{ value: 'radialOut3d', label: 'Граф: Радиальная 3D' },
// { value: 'circular2d', label: 'circular2d'},
// { value: 'nooverlap', label: 'nooverlap'},
// { value: 'hierarchicalTd', label: 'hierarchicalTd'},
// { value: 'hierarchicalLr', label: 'hierarchicalLr'}
];
export const SelectorGraphColoring: { value: ColoringScheme; label: string; }[] = [
{ value: 'none', label: 'Цвет: моно' },
{ value: 'status', label: 'Цвет: статус' },
{ value: 'type', label: 'Цвет: класс' },
];
export const SelectorCstType = (
Object.values(CstType)).map(
typeStr => ({
value: typeStr as CstType,
label: getCstTypeLabel(typeStr as CstType)
})
);

View File

@ -1,4 +1,3 @@
import { LayoutTypes } from 'reagraph';
import { DependencyMode } from '../models/miscelanious';
import { HelpTopic } from '../models/miscelanious';
@ -7,7 +6,6 @@ import { ExpressionStatus } from '../models/rsform';
import { CstClass, CstType, IConstituenta, IRSForm } from '../models/rsform';
import { IFunctionArg, IRSErrorDescription, ISyntaxTreeNode, ParsingStatus, ValueClass } from '../models/rslang';
import { resolveErrorClass, RSErrorClass, RSErrorType, TokenID } from '../models/rslang';
import { ColoringScheme } from '../pages/RSFormPage/EditorTermGraph';
import { IColorTheme } from './color';
export interface IDescriptor {
@ -242,14 +240,6 @@ export function getCstTypeShortcut(type: CstType) {
}
}
export const CstTypeSelector = (
Object.values(CstType)).map(
typeStr => ({
value: typeStr as CstType,
label: getCstTypeLabel(typeStr as CstType)
})
);
export function getCstCompareLabel(mode: CstMatchMode): string {
switch(mode) {
case CstMatchMode.ALL: return 'везде';
@ -271,22 +261,6 @@ export function getDependencyLabel(mode: DependencyMode): string {
}
}
export const GraphLayoutSelector: {value: LayoutTypes, label: string}[] = [
{ value: 'treeTd2d', label: 'Граф: ДеревоВ 2D'},
{ value: 'treeTd3d', label: 'Граф: ДеревоВ 3D'},
{ value: 'forceatlas2', label: 'Граф: Атлас 2D'},
{ value: 'forceDirected2d', label: 'Граф: Силы 2D'},
{ value: 'forceDirected3d', label: 'Граф: Силы 3D'},
{ value: 'treeLr2d', label: 'Граф: ДеревоГ 2D'},
{ value: 'treeLr3d', label: 'Граф: ДеревоГ 3D'},
{ value: 'radialOut2d', label: 'Граф: Радиальная 2D'},
{ value: 'radialOut3d', label: 'Граф: Радиальная 3D'},
// { value: 'circular2d', label: 'circular2d'},
// { value: 'nooverlap', label: 'nooverlap'},
// { value: 'hierarchicalTd', label: 'hierarchicalTd'},
// { value: 'hierarchicalLr', label: 'hierarchicalLr'}
];
export const mapLayoutLabels: Map<string, string> = new Map([
['forceatlas2', 'Граф: Атлас 2D'],
['forceDirected2d', 'Граф: Силы 2D'],
@ -309,12 +283,6 @@ export const mapColoringLabels: Map<string, string> = new Map([
['type', 'Цвет: класс'],
]);
export const GraphColoringSelector: {value: ColoringScheme, label: string}[] = [
{ value: 'none', label: 'Цвет: моно'},
{ value: 'status', label: 'Цвет: статус'},
{ value: 'type', label: 'Цвет: класс'},
];
export function getCstStatusBgColor(status: ExpressionStatus, colors: IColorTheme): string {
switch (status) {
case ExpressionStatus.VERIFIED: return colors.bgGreen;