From 26fe49b3520108f99067bd46ffc51cfd920f7431 Mon Sep 17 00:00:00 2001 From: IRBorisov <8611739+IRBorisov@users.noreply.github.com> Date: Sun, 7 Apr 2024 15:38:24 +0300 Subject: [PATCH] Refactoring: use cached maps to access constituents --- .../src/components/RSInput/RSInput.tsx | 4 +- .../src/components/RSInput/tooltip.ts | 10 +- .../src/components/RefsInput/RefsInput.tsx | 16 +- .../src/components/RefsInput/tooltip.ts | 10 +- .../src/components/info/InfoConstituenta.tsx | 8 +- .../frontend/src/context/LibraryContext.tsx | 4 +- .../DlgConstituentaTemplate/TemplateTab.tsx | 2 +- .../dialogs/DlgDeleteCst/ConstituentsList.tsx | 8 +- .../src/dialogs/DlgDeleteCst/DlgDeleteCst.tsx | 9 +- .../DlgEditReference/DlgEditReference.tsx | 8 +- .../dialogs/DlgEditReference/EntityTab.tsx | 14 +- .../frontend/src/hooks/useRSFormDetails.ts | 4 +- rsconcept/frontend/src/models/RSFormLoader.ts | 178 ++++++++++++++++++ rsconcept/frontend/src/models/rsform.ts | 31 ++- rsconcept/frontend/src/models/rsformAPI.ts | 129 +------------ .../EditorConstituenta/FormConstituenta.tsx | 4 +- .../EditorTermGraph/EditorTermGraph.tsx | 12 +- .../RSFormPage/EditorTermGraph/TermGraph.tsx | 3 +- .../RSFormPage/EditorTermGraph/ViewHidden.tsx | 4 +- .../EditorTermGraph/useGraphFilter.ts | 2 +- .../src/pages/RSFormPage/RSEditContext.tsx | 2 +- .../frontend/src/pages/RSFormPage/RSTabs.tsx | 4 +- .../ViewConstituents/ConstituentsTable.tsx | 4 +- rsconcept/frontend/src/utils/codemirror.ts | 8 +- rsconcept/frontend/src/utils/labels.ts | 4 +- rsconcept/frontend/src/utils/selectors.ts | 4 +- 26 files changed, 276 insertions(+), 210 deletions(-) create mode 100644 rsconcept/frontend/src/models/RSFormLoader.ts diff --git a/rsconcept/frontend/src/components/RSInput/RSInput.tsx b/rsconcept/frontend/src/components/RSInput/RSInput.tsx index a1941c17..42912106 100644 --- a/rsconcept/frontend/src/components/RSInput/RSInput.tsx +++ b/rsconcept/frontend/src/components/RSInput/RSInput.tsx @@ -86,9 +86,9 @@ const RSInput = forwardRef( EditorView.lineWrapping, RSLanguage, ccBracketMatching(darkMode), - ...(noTooltip ? [] : [rsHoverTooltip(schema?.items || [])]) + ...(noTooltip || !schema ? [] : [rsHoverTooltip(schema)]) ], - [darkMode, schema?.items, noTooltip] + [darkMode, schema, noTooltip] ); const handleInput = useCallback( diff --git a/rsconcept/frontend/src/components/RSInput/tooltip.ts b/rsconcept/frontend/src/components/RSInput/tooltip.ts index 82afe4c5..341a6942 100644 --- a/rsconcept/frontend/src/components/RSInput/tooltip.ts +++ b/rsconcept/frontend/src/components/RSInput/tooltip.ts @@ -3,7 +3,7 @@ import { Extension } from '@codemirror/state'; import { hoverTooltip } from '@codemirror/view'; import { EditorState } from '@uiw/react-codemirror'; -import { IConstituenta } from '@/models/rsform'; +import { IRSForm } from '@/models/rsform'; import { findEnvelopingNodes } from '@/utils/codemirror'; import { domTooltipConstituenta } from '@/utils/codemirror'; @@ -25,13 +25,13 @@ function findAliasAt(pos: number, state: EditorState) { return { alias, start, end }; } -const globalsHoverTooltip = (items: IConstituenta[]) => { +const globalsHoverTooltip = (schema: IRSForm) => { return hoverTooltip((view, pos) => { const { alias, start, end } = findAliasAt(pos, view.state); if (!alias) { return null; } - const cst = items.find(cst => cst.alias === alias); + const cst = schema.cstByAlias.get(alias); return { pos: start, end: end, @@ -41,6 +41,6 @@ const globalsHoverTooltip = (items: IConstituenta[]) => { }); }; -export function rsHoverTooltip(items: IConstituenta[]): Extension { - return [globalsHoverTooltip(items)]; +export function rsHoverTooltip(schema: IRSForm): Extension { + return [globalsHoverTooltip(schema)]; } diff --git a/rsconcept/frontend/src/components/RefsInput/RefsInput.tsx b/rsconcept/frontend/src/components/RefsInput/RefsInput.tsx index c0c396b3..309622f8 100644 --- a/rsconcept/frontend/src/components/RefsInput/RefsInput.tsx +++ b/rsconcept/frontend/src/components/RefsInput/RefsInput.tsx @@ -11,10 +11,9 @@ import { forwardRef, useCallback, useMemo, useRef, useState } from 'react'; import Label from '@/components/ui/Label'; import { useConceptOptions } from '@/context/OptionsContext'; -import { useRSForm } from '@/context/RSFormContext'; import DlgEditReference from '@/dialogs/DlgEditReference'; import { ReferenceType } from '@/models/language'; -import { IConstituenta } from '@/models/rsform'; +import { IRSForm } from '@/models/rsform'; import { CodeMirrorWrapper } from '@/utils/codemirror'; import { NaturalLanguage, ReferenceTokens } from './parse'; @@ -53,7 +52,7 @@ interface RefsInputInputProps extends Pick { label?: string; onChange?: (newValue: string) => void; - items?: IConstituenta[]; + schema?: IRSForm; disabled?: boolean; initialValue?: string; @@ -62,9 +61,8 @@ interface RefsInputInputProps } const RefsInput = forwardRef( - ({ id, label, disabled, items, initialValue, value, resolved, onFocus, onBlur, onChange, ...restProps }, ref) => { + ({ id, label, disabled, schema, initialValue, value, resolved, onFocus, onBlur, onChange, ...restProps }, ref) => { const { darkMode, colors } = useConceptOptions(); - const { schema } = useRSForm(); const [isFocused, setIsFocused] = useState(false); @@ -104,9 +102,9 @@ const RefsInput = forwardRef( EditorView.lineWrapping, EditorView.contentAttributes.of({ spellcheck: 'true' }), NaturalLanguage, - refsHoverTooltip(schema?.items || [], colors) + ...(schema ? [refsHoverTooltip(schema, colors)] : []) ], - [schema?.items, colors] + [schema, colors] ); function handleChange(newValue: string) { @@ -170,10 +168,10 @@ const RefsInput = forwardRef( return (
- {showEditor ? ( + {showEditor && schema ? ( setShowEditor(false)} - items={items ?? []} + schema={schema} initial={{ type: currentType, refRaw: refText, diff --git a/rsconcept/frontend/src/components/RefsInput/tooltip.ts b/rsconcept/frontend/src/components/RefsInput/tooltip.ts index 9b1999e9..bddca913 100644 --- a/rsconcept/frontend/src/components/RefsInput/tooltip.ts +++ b/rsconcept/frontend/src/components/RefsInput/tooltip.ts @@ -3,7 +3,7 @@ import { Extension } from '@codemirror/state'; import { hoverTooltip } from '@codemirror/view'; import { parseEntityReference, parseSyntacticReference } from '@/models/languageAPI'; -import { IConstituenta } from '@/models/rsform'; +import { IRSForm } from '@/models/rsform'; import { IColorTheme } from '@/styling/color'; import { domTooltipEntityReference, @@ -15,7 +15,7 @@ import { import { ReferenceTokens } from './parse'; import { RefEntity, RefSyntactic } from './parse/parser.terms'; -export const globalsHoverTooltip = (items: IConstituenta[], colors: IColorTheme) => { +export const globalsHoverTooltip = (schema: IRSForm, colors: IColorTheme) => { return hoverTooltip((view, pos) => { const nodes = findEnvelopingNodes(pos, pos, syntaxTree(view.state), ReferenceTokens); if (nodes.length !== 1) { @@ -26,7 +26,7 @@ export const globalsHoverTooltip = (items: IConstituenta[], colors: IColorTheme) const text = view.state.doc.sliceString(start, end); if (nodes[0].type.id === RefEntity) { const ref = parseEntityReference(text); - const cst = items.find(cst => cst.alias === ref.entity); + const cst = schema.cstByAlias.get(ref.entity); return { pos: start, end: end, @@ -61,6 +61,6 @@ export const globalsHoverTooltip = (items: IConstituenta[], colors: IColorTheme) }); }; -export function refsHoverTooltip(items: IConstituenta[], colors: IColorTheme): Extension { - return [globalsHoverTooltip(items, colors)]; +export function refsHoverTooltip(schema: IRSForm, colors: IColorTheme): Extension { + return [globalsHoverTooltip(schema, colors)]; } diff --git a/rsconcept/frontend/src/components/info/InfoConstituenta.tsx b/rsconcept/frontend/src/components/info/InfoConstituenta.tsx index 50996a7e..e59362ee 100644 --- a/rsconcept/frontend/src/components/info/InfoConstituenta.tsx +++ b/rsconcept/frontend/src/components/info/InfoConstituenta.tsx @@ -36,16 +36,16 @@ function InfoConstituenta({ data, className, ...restProps }: InfoConstituentaPro {data.definition_resolved}

) : null} - {data.derived_from_alias ? ( + {data.parent_alias ? (

Основание: - {data.derived_from_alias} + {data.parent_alias}

) : null} - {data.derived_children_alias.length > 0 ? ( + {data.children_alias.length > 0 ? (

Порождает: - {data.derived_children_alias.join(', ')} + {data.children_alias.join(', ')}

) : null} {data.convention ? ( diff --git a/rsconcept/frontend/src/context/LibraryContext.tsx b/rsconcept/frontend/src/context/LibraryContext.tsx index 8dee2198..4210c7d0 100644 --- a/rsconcept/frontend/src/context/LibraryContext.tsx +++ b/rsconcept/frontend/src/context/LibraryContext.tsx @@ -7,7 +7,7 @@ import { ILibraryItem } from '@/models/library'; import { matchLibraryItem } from '@/models/libraryAPI'; import { ILibraryFilter } from '@/models/miscellaneous'; import { IRSForm, IRSFormCloneData, IRSFormCreateData, IRSFormData } from '@/models/rsform'; -import { loadRSFormData } from '@/models/rsformAPI'; +import { RSFormLoader } from '@/models/RSFormLoader'; import { DataCallback, deleteLibraryItem, @@ -100,7 +100,7 @@ export const LibraryState = ({ children }: LibraryStateProps) => { setLoading: setProcessing, onError: setError, onSuccess: data => { - const schema = loadRSFormData(data); + const schema = new RSFormLoader(data).produceRSForm(); setCachedTemplates(prev => [...prev, schema]); callback(schema); } diff --git a/rsconcept/frontend/src/dialogs/DlgConstituentaTemplate/TemplateTab.tsx b/rsconcept/frontend/src/dialogs/DlgConstituentaTemplate/TemplateTab.tsx index 9f45dd1d..8bb3b6ba 100644 --- a/rsconcept/frontend/src/dialogs/DlgConstituentaTemplate/TemplateTab.tsx +++ b/rsconcept/frontend/src/dialogs/DlgConstituentaTemplate/TemplateTab.tsx @@ -98,7 +98,7 @@ function TemplateTab({ state, partialUpdate }: TemplateTabProps) { } : null } - onChange={data => partialUpdate({ filterCategory: category?.items.find(cst => cst.id === data?.value) })} + onChange={data => partialUpdate({ filterCategory: data ? category?.cstByID.get(data?.value) : undefined })} isClearable /> {title ? ( @@ -20,7 +20,7 @@ function ConstituentsList({ list, items, title, prefix }: ConstituentsListProps) ) : null}
{list.map(id => { - const cst = items.find(cst => cst.id === id); + const cst = schema.cstByID.get(id); return cst ?

{labelConstituenta(cst)}

: null; })}
diff --git a/rsconcept/frontend/src/dialogs/DlgDeleteCst/DlgDeleteCst.tsx b/rsconcept/frontend/src/dialogs/DlgDeleteCst/DlgDeleteCst.tsx index eca25765..e8d9c327 100644 --- a/rsconcept/frontend/src/dialogs/DlgDeleteCst/DlgDeleteCst.tsx +++ b/rsconcept/frontend/src/dialogs/DlgDeleteCst/DlgDeleteCst.tsx @@ -38,16 +38,11 @@ function DlgDeleteCst({ hideWindow, selected, schema, onDelete }: DlgDeleteCstPr onSubmit={handleSubmit} className={clsx('cc-column', 'max-w-[60vw] min-w-[30rem]', 'px-6')} > - + setExpandOut(value)} /> diff --git a/rsconcept/frontend/src/dialogs/DlgEditReference/DlgEditReference.tsx b/rsconcept/frontend/src/dialogs/DlgEditReference/DlgEditReference.tsx index 094aa19e..fcbc97ae 100644 --- a/rsconcept/frontend/src/dialogs/DlgEditReference/DlgEditReference.tsx +++ b/rsconcept/frontend/src/dialogs/DlgEditReference/DlgEditReference.tsx @@ -10,7 +10,7 @@ import Overlay from '@/components/ui/Overlay'; import TabLabel from '@/components/ui/TabLabel'; import { ReferenceType } from '@/models/language'; import { HelpTopic } from '@/models/miscellaneous'; -import { IConstituenta } from '@/models/rsform'; +import { IRSForm } from '@/models/rsform'; import { labelReferenceType } from '@/utils/labels'; import EntityTab from './EntityTab'; @@ -26,7 +26,7 @@ export interface IReferenceInputState { interface DlgEditReferenceProps { hideWindow: () => void; - items: IConstituenta[]; + schema: IRSForm; initial: IReferenceInputState; onSave: (newRef: string) => void; } @@ -36,7 +36,7 @@ export enum TabID { SYNTACTIC = 1 } -function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferenceProps) { +function DlgEditReference({ hideWindow, schema, initial, onSave }: DlgEditReferenceProps) { const [activeTab, setActiveTab] = useState(initial.type === ReferenceType.ENTITY ? TabID.ENTITY : TabID.SYNTACTIC); const [reference, setReference] = useState(''); @@ -77,7 +77,7 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen - + diff --git a/rsconcept/frontend/src/dialogs/DlgEditReference/EntityTab.tsx b/rsconcept/frontend/src/dialogs/DlgEditReference/EntityTab.tsx index 6f561e58..1d83cd52 100644 --- a/rsconcept/frontend/src/dialogs/DlgEditReference/EntityTab.tsx +++ b/rsconcept/frontend/src/dialogs/DlgEditReference/EntityTab.tsx @@ -10,7 +10,7 @@ import TextInput from '@/components/ui/TextInput'; import { ReferenceType } from '@/models/language'; import { parseEntityReference, parseGrammemes } from '@/models/languageAPI'; import { CstMatchMode } from '@/models/miscellaneous'; -import { IConstituenta } from '@/models/rsform'; +import { IConstituenta, IRSForm } from '@/models/rsform'; import { matchConstituenta } from '@/models/rsformAPI'; import { prefixes } from '@/utils/constants'; import { IGrammemeOption, SelectorGrammemes } from '@/utils/selectors'; @@ -20,12 +20,12 @@ import SelectWordForm from './SelectWordForm'; interface EntityTabProps { initial: IReferenceInputState; - items: IConstituenta[]; + schema: IRSForm; setIsValid: React.Dispatch>; setReference: React.Dispatch>; } -function EntityTab({ initial, items, setIsValid, setReference }: EntityTabProps) { +function EntityTab({ initial, schema, setIsValid, setReference }: EntityTabProps) { const [selectedCst, setSelectedCst] = useState(undefined); const [alias, setAlias] = useState(''); const [term, setTerm] = useState(''); @@ -39,7 +39,7 @@ function EntityTab({ initial, items, setIsValid, setReference }: EntityTabProps) const grams = parseGrammemes(ref.form); setSelectedGrams(SelectorGrammemes.filter(data => grams.includes(data.value))); } - }, [initial, items]); + }, [initial, schema.items]); // Produce result useEffect(() => { @@ -49,9 +49,9 @@ function EntityTab({ initial, items, setIsValid, setReference }: EntityTabProps) // Update term when alias changes useEffect(() => { - const cst = items.find(item => item.alias === alias); + const cst = schema.cstByAlias.get(alias); setTerm(cst?.term_resolved ?? ''); - }, [alias, term, items]); + }, [alias, term, schema]); function handleSelectConstituenta(cst: IConstituenta) { setAlias(cst.alias); @@ -64,7 +64,7 @@ function EntityTab({ initial, items, setIsValid, setReference }: EntityTabProps) id='dlg_reference_entity_picker' initialFilter={initial.text} value={selectedCst} - data={items} + data={schema.items} onSelectValue={handleSelectConstituenta} prefixID={prefixes.cst_modal_list} describeFunc={cst => cst.term_resolved} diff --git a/rsconcept/frontend/src/hooks/useRSFormDetails.ts b/rsconcept/frontend/src/hooks/useRSFormDetails.ts index eef4b11f..9dd56284 100644 --- a/rsconcept/frontend/src/hooks/useRSFormDetails.ts +++ b/rsconcept/frontend/src/hooks/useRSFormDetails.ts @@ -4,7 +4,7 @@ import { useCallback, useEffect, useState } from 'react'; import { type ErrorData } from '@/components/info/InfoError'; import { IRSForm, IRSFormData } from '@/models/rsform'; -import { loadRSFormData } from '@/models/rsformAPI'; +import { RSFormLoader } from '@/models/RSFormLoader'; import { getRSFormDetails } from '@/utils/backendAPI'; function useRSFormDetails({ target, version }: { target?: string; version?: string }) { @@ -17,7 +17,7 @@ function useRSFormDetails({ target, version }: { target?: string; version?: stri setInnerSchema(undefined); return; } - const schema = loadRSFormData(data); + const schema = new RSFormLoader(data).produceRSForm(); setInnerSchema(schema); } diff --git a/rsconcept/frontend/src/models/RSFormLoader.ts b/rsconcept/frontend/src/models/RSFormLoader.ts new file mode 100644 index 00000000..6985bae6 --- /dev/null +++ b/rsconcept/frontend/src/models/RSFormLoader.ts @@ -0,0 +1,178 @@ +/** + * Module: RSForm data loading and processing. + */ + +import { Graph } from './Graph'; +import { ConstituentaID, CstType, IConstituenta, IRSForm, IRSFormData, IRSFormStats } from './rsform'; +import { inferClass, inferStatus, inferTemplate, isBaseSet, isFunctional } from './rsformAPI'; +import { ParsingStatus, ValueClass } from './rslang'; +import { extractGlobals, isSimpleExpression, splitTemplateDefinition } from './rslangAPI'; + +/** + * Loads data into an {@link IRSForm} based on {@link IRSFormData}. + * + * @remarks + * This function processes the provided input, initializes the IRSForm, and calculates statistics + * based on the loaded data. It also establishes dependencies between concepts in the graph. + */ +export class RSFormLoader { + private schema: IRSFormData; + private graph: Graph = new Graph(); + private cstByAlias: Map = new Map(); + private cstByID: Map = new Map(); + + constructor(input: IRSFormData) { + this.schema = input; + } + + produceRSForm(): IRSForm { + this.prepareLookups(); + this.createGraph(); + this.inferCstAttributes(); + + const result = this.schema as IRSForm; + result.stats = this.calculateStats(); + result.graph = this.graph; + result.cstByAlias = this.cstByAlias; + result.cstByID = this.cstByID; + return result; + } + + private prepareLookups() { + this.schema.items.forEach(cst => { + this.cstByAlias.set(cst.alias, cst as IConstituenta); + this.cstByID.set(cst.id, cst as IConstituenta); + this.graph.addNode(cst.id); + }); + } + + private createGraph() { + this.schema.items.forEach(cst => { + const dependencies = extractGlobals(cst.definition_formal); + dependencies.forEach(alias => { + const source = this.cstByAlias.get(alias); + if (source) { + this.graph.addEdge(source.id, cst.id); + } + }); + }); + } + + private inferCstAttributes() { + this.graph.topologicalOrder().forEach(cstID => { + const cst = this.cstByID.get(cstID)!; + cst.status = inferStatus(cst.parse.status, cst.parse.valueClass); + cst.is_template = inferTemplate(cst.definition_formal); + cst.cst_class = inferClass(cst.cst_type, cst.is_template); + cst.children = []; + cst.children_alias = []; + cst.is_simple_expression = this.inferSimpleExpression(cst); + if (!cst.is_simple_expression || cst.cst_type === CstType.STRUCTURED) { + return; + } + cst.parent = this.inferParent(cst); + if (cst.parent) { + const parent = this.cstByID.get(cst.parent)!; + cst.parent_alias = parent.alias; + parent.children.push(cst.id); + parent.children_alias.push(cst.alias); + } + }); + } + + private inferSimpleExpression(target: IConstituenta): boolean { + if (target.cst_type === CstType.STRUCTURED || isBaseSet(target.cst_type)) { + return false; + } + const dependencies = this.graph.at(target.id)!.inputs; + const hasComplexDependency = dependencies.some(id => { + const cst = this.cstByID.get(id)!; + return cst.is_template && !cst.is_simple_expression; + }); + if (hasComplexDependency) { + return false; + } + const expression = isFunctional(target.cst_type) + ? splitTemplateDefinition(target.definition_formal).body + : target.definition_formal; + return isSimpleExpression(expression); + } + + private inferParent(target: IConstituenta): ConstituentaID | undefined { + const sources = this.extractSources(target); + if (sources.size !== 1 || sources.has(target.id)) { + return undefined; + } + return sources.values().next().value as ConstituentaID; + } + + private extractSources(target: IConstituenta): Set { + const sources: Set = new Set(); + if (!isFunctional(target.cst_type)) { + const node = this.graph.at(target.id)!; + node.inputs.forEach(id => { + const parent = this.cstByID.get(id)!; + if (!parent.is_template || !parent.is_simple_expression) { + sources.add(parent.parent ?? id); + } + }); + return sources; + } + + const expression = splitTemplateDefinition(target.definition_formal); + const bodyDependencies = extractGlobals(expression.body); + bodyDependencies.forEach(alias => { + const parent = this.cstByAlias.get(alias); + if (parent && (!parent.is_template || !parent.is_simple_expression)) { + sources.add(this.cstByID.get(parent.id)!.parent ?? parent.id); + } + }); + const needCheckHead = () => { + if (sources.size === 0) { + return true; + } else if (sources.size !== 1) { + return false; + } else { + const base = this.cstByID.get(sources.values().next().value as ConstituentaID)!; + return !isFunctional(base.cst_type) || splitTemplateDefinition(base.definition_formal).head !== expression.head; + } + }; + if (needCheckHead()) { + const headDependencies = extractGlobals(expression.head); + headDependencies.forEach(alias => { + const parent = this.cstByAlias.get(alias); + if (parent && !isBaseSet(parent.cst_type) && (!parent.is_template || !parent.is_simple_expression)) { + sources.add(parent.parent ?? parent.id); + } + }); + } + return sources; + } + + private calculateStats(): IRSFormStats { + const items = this.schema.items; + return { + count_all: items.length, + count_errors: items.reduce((sum, cst) => sum + (cst.parse.status === ParsingStatus.INCORRECT ? 1 : 0), 0), + count_property: items.reduce((sum, cst) => sum + (cst.parse.valueClass === ValueClass.PROPERTY ? 1 : 0), 0), + count_incalculable: items.reduce( + (sum, cst) => + sum + (cst.parse.status === ParsingStatus.VERIFIED && cst.parse.valueClass === ValueClass.INVALID ? 1 : 0), + 0 + ), + + count_text_term: items.reduce((sum, cst) => sum + (cst.term_raw ? 1 : 0), 0), + count_definition: items.reduce((sum, cst) => sum + (cst.definition_raw ? 1 : 0), 0), + count_convention: items.reduce((sum, cst) => sum + (cst.convention ? 1 : 0), 0), + + count_base: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.BASE ? 1 : 0), 0), + count_constant: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.CONSTANT ? 1 : 0), 0), + count_structured: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.STRUCTURED ? 1 : 0), 0), + count_axiom: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.AXIOM ? 1 : 0), 0), + count_term: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.TERM ? 1 : 0), 0), + count_function: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.FUNCTION ? 1 : 0), 0), + count_predicate: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.PREDICATE ? 1 : 0), 0), + count_theorem: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.THEOREM ? 1 : 0), 0) + }; + } +} diff --git a/rsconcept/frontend/src/models/rsform.ts b/rsconcept/frontend/src/models/rsform.ts index d066feff..22d363ab 100644 --- a/rsconcept/frontend/src/models/rsform.ts +++ b/rsconcept/frontend/src/models/rsform.ts @@ -90,16 +90,9 @@ export interface ICstTarget { } /** - * Represents Constituenta. + * Represents Constituenta data from server. */ -export interface IConstituenta extends IConstituentaMeta { - cst_class: CstClass; - status: ExpressionStatus; - is_template: boolean; - derived_from: ConstituentaID; - derived_from_alias?: string; - derived_children: number[]; - derived_children_alias: string[]; +export interface IConstituentaData extends IConstituentaMeta { parse: { status: ParsingStatus; valueClass: ValueClass; @@ -109,6 +102,20 @@ export interface IConstituenta extends IConstituentaMeta { }; } +/** + * Represents Constituenta. + */ +export interface IConstituenta extends IConstituentaData { + cst_class: CstClass; + status: ExpressionStatus; + is_template: boolean; + is_simple_expression: boolean; + parent?: ConstituentaID; + parent_alias?: string; + children: number[]; + children_alias: string[]; +} + /** * Represents Constituenta list. */ @@ -223,12 +230,16 @@ export interface IRSForm extends ILibraryItemEx { items: IConstituenta[]; stats: IRSFormStats; graph: Graph; + cstByAlias: Map; + cstByID: Map; } /** * Represents data for {@link IRSForm} provided by backend. */ -export interface IRSFormData extends Omit {} +export interface IRSFormData extends ILibraryItemEx { + items: IConstituentaData[]; +} /** * Represents data, used for creating {@link IRSForm}. diff --git a/rsconcept/frontend/src/models/rsformAPI.ts b/rsconcept/frontend/src/models/rsformAPI.ts index 407de29c..66285f34 100644 --- a/rsconcept/frontend/src/models/rsformAPI.ts +++ b/rsconcept/frontend/src/models/rsformAPI.ts @@ -2,7 +2,6 @@ * Module: API for formal representation for systems of concepts. */ -import { Graph } from '@/models/Graph'; import { TextMatcher } from '@/utils/utils'; import { CstMatchMode } from './miscellaneous'; @@ -13,124 +12,9 @@ import { CstType, ExpressionStatus, IConstituenta, - IRSForm, - IRSFormData, - IRSFormStats + IRSForm } from './rsform'; import { ParsingStatus, ValueClass } from './rslang'; -import { extractGlobals, isSimpleExpression, splitTemplateDefinition } from './rslangAPI'; - -/** - * Loads data into an {@link IRSForm} based on {@link IRSFormData}. - * - * @remarks - * This function processes the provided input, initializes the IRSForm, and calculates statistics - * based on the loaded data. It also establishes dependencies between concepts in the graph. - */ -export function loadRSFormData(input: IRSFormData): IRSForm { - const result = input as IRSForm; - result.graph = new Graph(); - result.stats = calculateStats(result.items); - - result.items.forEach(cst => { - cst.derived_from = cst.id; - cst.derived_children = []; - cst.derived_children_alias = []; - cst.status = inferStatus(cst.parse.status, cst.parse.valueClass); - cst.is_template = inferTemplate(cst.definition_formal); - cst.cst_class = inferClass(cst.cst_type, cst.is_template); - result.graph.addNode(cst.id); - const dependencies = extractGlobals(cst.definition_formal); - dependencies.forEach(value => { - const source = input.items.find(cst => cst.alias === value); - if (source) { - result.graph.addEdge(source.id, cst.id); - } - }); - }); - // Calculate derivation of constituents based on formal definition analysis - const derivationLookup: Map = new Map(); - result.graph.topologicalOrder().forEach(id => { - derivationLookup.set(id, id); - const cst = result.items.find(item => item.id === id)!; - if (cst.cst_type === CstType.STRUCTURED) { - return; - } - const resolvedInput: Set = new Set(); - let definition = ''; - if (!isFunctional(cst.cst_type)) { - const node = result.graph.at(id)!; - node.inputs.forEach(id => resolvedInput.add(derivationLookup.get(id)!)); - definition = cst.definition_formal; - } else { - const expression = splitTemplateDefinition(cst.definition_formal); - definition = expression.body; - const bodyDependencies = extractGlobals(definition); - bodyDependencies.forEach(alias => { - const targetCst = result.items.find(item => item.alias === alias); - if (targetCst) { - resolvedInput.add(derivationLookup.get(targetCst.id)!); - } - }); - const needCheckHead = () => { - if (resolvedInput.size === 0) { - return true; - } else if (resolvedInput.size !== 1) { - return false; - } else { - const base = result.items.find(item => item.id === resolvedInput.values().next().value)!; - return ( - !isFunctional(base.cst_type) || splitTemplateDefinition(base.definition_formal).head !== expression.head - ); - } - }; - if (needCheckHead()) { - const headDependencies = extractGlobals(expression.head); - headDependencies.forEach(alias => { - const targetCst = result.items.find(item => item.alias === alias); - if (targetCst && !isBaseSet(targetCst.cst_type)) { - resolvedInput.add(derivationLookup.get(targetCst.id)!); - } - }); - } - } - if (resolvedInput.size === 1 && isSimpleExpression(definition)) { - const parent = result.items.find(item => item.id === resolvedInput.values().next().value)!; - cst.derived_from = parent.id; - cst.derived_from_alias = parent.alias; - parent.derived_children_alias.push(cst.alias); - parent.derived_children.push(cst.id); - derivationLookup.set(cst.id, parent.id); - } - }); - return result; -} - -function calculateStats(items: IConstituenta[]): IRSFormStats { - return { - count_all: items.length, - count_errors: items.reduce((sum, cst) => sum + (cst.parse?.status === ParsingStatus.INCORRECT ? 1 : 0), 0), - count_property: items.reduce((sum, cst) => sum + (cst.parse?.valueClass === ValueClass.PROPERTY ? 1 : 0), 0), - count_incalculable: items.reduce( - (sum, cst) => - sum + (cst.parse.status === ParsingStatus.VERIFIED && cst.parse.valueClass === ValueClass.INVALID ? 1 : 0), - 0 - ), - - count_text_term: items.reduce((sum, cst) => sum + (cst.term_raw ? 1 : 0), 0), - count_definition: items.reduce((sum, cst) => sum + (cst.definition_raw ? 1 : 0), 0), - count_convention: items.reduce((sum, cst) => sum + (cst.convention ? 1 : 0), 0), - - count_base: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.BASE ? 1 : 0), 0), - count_constant: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.CONSTANT ? 1 : 0), 0), - count_structured: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.STRUCTURED ? 1 : 0), 0), - count_axiom: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.AXIOM ? 1 : 0), 0), - count_term: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.TERM ? 1 : 0), 0), - count_function: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.FUNCTION ? 1 : 0), 0), - count_predicate: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.PREDICATE ? 1 : 0), 0), - count_theorem: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.THEOREM ? 1 : 0), 0) - }; -} /** * Checks if a given target {@link IConstituenta} matches the specified query using the provided matching mode. @@ -232,9 +116,10 @@ export function inferClass(type: CstType, isTemplate: boolean): CstClass { export function createMockConstituenta(id: ConstituentaID, alias: string, comment: string): IConstituenta { return { id: id, - derived_from: id, - derived_children: [], - derived_children_alias: [], + parent: id, + children: [], + children_alias: [], + is_simple_expression: false, order: -1, schema: -1, alias: alias, @@ -269,7 +154,7 @@ export function isMockCst(cst: IConstituenta) { /** * Apply filter based on start {@link IConstituenta} type. */ -export function applyFilterCategory(start: IConstituenta, schema: IRSFormData): IConstituenta[] { +export function applyFilterCategory(start: IConstituenta, schema: IRSForm): IConstituenta[] { const nextCategory = schema.items.find(cst => cst.order > start.order && cst.cst_type === CATEGORY_CST_TYPE); return schema.items.filter(cst => cst.order >= start.order && (!nextCategory || cst.order < nextCategory.order)); } @@ -367,7 +252,7 @@ export function isFunctional(type: CstType): boolean { * Validate new alias against {@link CstType} and {@link IRSForm}. */ export function validateNewAlias(alias: string, type: CstType, schema: IRSForm): boolean { - return alias.length >= 2 && alias[0] == getCstTypePrefix(type) && !schema.items.find(cst => cst.alias === alias); + return alias.length >= 2 && alias[0] == getCstTypePrefix(type) && !schema.cstByAlias.has(alias); } /** diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx index 3a3029ae..aa85a6d9 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx @@ -146,7 +146,7 @@ function FormConstituenta({ id='cst_term' label='Термин' placeholder='Обозначение, используемое в текстовых определениях' - items={schema?.items} + schema={schema} value={term} initialValue={state?.term_raw ?? ''} resolved={state?.term_resolved ?? ''} @@ -198,7 +198,7 @@ function FormConstituenta({ label='Текстовое определение' placeholder='Текстовая интерпретация формального выражения' height='3.8rem' - items={schema?.items} + schema={schema} value={textDefinition} initialValue={state?.definition_raw ?? ''} resolved={state?.definition_resolved ?? ''} diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/EditorTermGraph.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/EditorTermGraph.tsx index 3093a302..77ec0d66 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/EditorTermGraph.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/EditorTermGraph.tsx @@ -62,8 +62,8 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) { const [hoverID, setHoverID] = useState(undefined); const hoverCst = useMemo(() => { - return controller.schema?.items.find(cst => cst.id === hoverID); - }, [controller.schema?.items, hoverID]); + return hoverID && controller.schema?.cstByID.get(hoverID); + }, [controller.schema?.cstByID, hoverID]); const [toggleResetView, setToggleResetView] = useState(false); @@ -87,14 +87,14 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) { return result; } filtered.nodes.forEach(node => { - const cst = controller.schema!.items.find(cst => cst.id === node.id); + const cst = controller.schema!.cstByID.get(node.id); if (cst) { result.push({ id: String(node.id), fill: colorBgGraphNode(cst, coloringScheme, colors), label: cst.alias, subLabel: !filterParams.noText ? cst.term_resolved : undefined, - size: cst.derived_from_alias ? 1 : 2 + size: cst.parent_alias ? 1 : 2 }); } }); @@ -121,9 +121,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) { if (!controller.schema) { return; } - const definition = controller.selected - .map(id => controller.schema!.items.find(cst => cst.id === id)!.alias) - .join(' '); + const definition = controller.selected.map(id => controller.schema!.cstByID.get(id)!.alias).join(' '); controller.createCst(controller.nothingSelected ? CstType.BASE : CstType.TERM, false, definition); } diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/TermGraph.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/TermGraph.tsx index 6709add8..684182f6 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/TermGraph.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/TermGraph.tsx @@ -45,7 +45,8 @@ function TermGraph({ ref: graphRef, nodes: nodes, edges: edges, - type: 'multi' + type: 'multi', + focusOnSelect: false }); const handleHoverIn = useCallback( diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/ViewHidden.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/ViewHidden.tsx index 33c43475..3a4f955e 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/ViewHidden.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/ViewHidden.tsx @@ -33,7 +33,7 @@ function ViewHidden({ items, selected, toggleSelection, schema, coloringScheme, [selected] ); - if (items.length <= 0) { + if (!schema || items.length <= 0) { return null; } return ( @@ -43,7 +43,7 @@ function ViewHidden({ items, selected, toggleSelection, schema, coloringScheme,

{items.map(cstID => { - const cst = schema!.items.find(cst => cst.id === cstID)!; + const cst = schema.cstByID.get(cstID)!; const adjustedColoring = coloringScheme === 'none' ? 'status' : coloringScheme; const id = `${prefixes.cst_hidden_list}${cst.alias}`; return ( diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/useGraphFilter.ts b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/useGraphFilter.ts index 38382b24..7de5ad13 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/useGraphFilter.ts +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/useGraphFilter.ts @@ -48,7 +48,7 @@ function useGraphFilter(schema: IRSForm | undefined, params: GraphFilterParams) } if (params.foldDerived) { schema.items.forEach(cst => { - if (cst.derived_from_alias) { + if (cst.parent_alias) { graph.foldNode(cst.id); } }); diff --git a/rsconcept/frontend/src/pages/RSFormPage/RSEditContext.tsx b/rsconcept/frontend/src/pages/RSFormPage/RSEditContext.tsx index 9fa96cf5..0f78325d 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/RSEditContext.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/RSEditContext.tsx @@ -205,7 +205,7 @@ export const RSEditState = ({ items: deleted }; - const deletedNames = deleted.map(id => model.schema?.items.find(cst => cst.id === id)?.alias).join(', '); + const deletedNames = deleted.map(id => model.schema!.cstByID.get(id)!.alias).join(', '); const isEmpty = deleted.length === model.schema.items.length; const nextActive = isEmpty ? undefined : getNextActiveOnDelete(activeCst?.id, model.schema.items, deleted); diff --git a/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx b/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx index dfd8a5c2..aaf2eee1 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx @@ -50,7 +50,7 @@ function RSTabs() { if (!schema || selected.length === 0) { return undefined; } else { - return schema.items.find(cst => cst.id === selected.at(-1)); + return schema.cstByID.get(selected.at(-1)!); } }, [schema, selected]); @@ -69,7 +69,7 @@ function RSTabs() { setIsModified(false); if (activeTab === RSTabID.CST_EDIT) { const cstID = Number(cstQuery); - if (cstID && schema && schema.items.find(cst => cst.id === cstID)) { + if (cstID && schema && schema.cstByID.has(cstID)) { setSelected([cstID]); } else if (schema && schema?.items.length > 0) { setSelected([schema.items[0].id]); diff --git a/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsTable.tsx b/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsTable.tsx index 18c5395a..bcf0795a 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsTable.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsTable.tsx @@ -110,13 +110,13 @@ function ConstituentsTable({ items, activeID, onOpenEdit, maxHeight, denseThresh } }, { - when: (cst: IConstituenta) => cst.derived_from === activeID && cst.id !== activeID, + when: (cst: IConstituenta) => cst.parent === activeID && cst.id !== activeID, style: { backgroundColor: colors.bgOrange50 } }, { - when: (cst: IConstituenta) => activeID !== undefined && cst.derived_children.includes(activeID), + when: (cst: IConstituenta) => activeID !== undefined && cst.children.includes(activeID), style: { backgroundColor: colors.bgGreen50 } diff --git a/rsconcept/frontend/src/utils/codemirror.ts b/rsconcept/frontend/src/utils/codemirror.ts index 671966b1..3f9c6613 100644 --- a/rsconcept/frontend/src/utils/codemirror.ts +++ b/rsconcept/frontend/src/utils/codemirror.ts @@ -170,15 +170,15 @@ export function domTooltipConstituenta(cst?: IConstituenta) { dom.appendChild(convention); } - if (cst.derived_from_alias) { + if (cst.parent_alias) { const derived = document.createElement('p'); - derived.innerHTML = `Основание: ${cst.derived_from_alias}`; + derived.innerHTML = `Основание: ${cst.parent_alias}`; dom.appendChild(derived); } - if (cst.derived_children_alias.length > 0) { + if (cst.children_alias.length > 0) { const children = document.createElement('p'); - children.innerHTML = `Порождает: ${cst.derived_children_alias.join(', ')}`; + children.innerHTML = `Порождает: ${cst.children_alias.join(', ')}`; dom.appendChild(children); } } else { diff --git a/rsconcept/frontend/src/utils/labels.ts b/rsconcept/frontend/src/utils/labels.ts index 9439488d..e0afd477 100644 --- a/rsconcept/frontend/src/utils/labels.ts +++ b/rsconcept/frontend/src/utils/labels.ts @@ -292,8 +292,8 @@ export const mapLabelLayout: Map = new Map([ ['treeTd3d', 'Граф: ДеревоВер 3D'], ['treeLr2d', 'Граф: ДеревоГор 2D'], ['treeLr3d', 'Граф: ДеревоГор 3D'], - ['radialOut2d', 'Граф: Радиальная 2D'], - ['radialOut3d', 'Граф: Радиальная 3D'], + ['radialOut2d', 'Граф: Радиус 2D'], + ['radialOut3d', 'Граф: Радиус 3D'], ['circular2d', 'Граф: Круговая'], ['hierarchicalTd', 'Граф: ИерархияВер'], ['hierarchicalLr', 'Граф: ИерархияГор'], diff --git a/rsconcept/frontend/src/utils/selectors.ts b/rsconcept/frontend/src/utils/selectors.ts index a4c640ae..f657cf87 100644 --- a/rsconcept/frontend/src/utils/selectors.ts +++ b/rsconcept/frontend/src/utils/selectors.ts @@ -22,8 +22,8 @@ export const SelectorGraphLayout: { value: LayoutTypes; label: string }[] = [ { value: 'forceDirected3d', label: 'Граф: Силы 3D' }, { value: 'treeLr2d', label: 'Граф: ДеревоГ 2D' }, { value: 'treeLr3d', label: 'Граф: ДеревоГ 3D' }, - { value: 'radialOut2d', label: 'Граф: Радиальная 2D' }, - { value: 'radialOut3d', label: 'Граф: Радиальная 3D' } + { value: 'radialOut2d', label: 'Граф: Радиус 2D' }, + { value: 'radialOut3d', label: 'Граф: Радиус 3D' } // { value: 'circular2d', label: 'circular2d'}, // { value: 'nooverlap', label: 'nooverlap'}, // { value: 'hierarchicalTd', label: 'hierarchicalTd'},