Refactor ID types to strings. Multiple UI fixes

This commit is contained in:
IRBorisov 2023-08-22 20:29:07 +03:00
parent 1cfab50d43
commit 503ad5d0dc
20 changed files with 161 additions and 147 deletions

View File

@ -42,7 +42,7 @@ export const getHoverTooltip = (items: IConstituenta[]) => {
start--;
while (end < to && /\w/.test(text[end - from]))
end++;
if (start == pos && side < 0 || end == pos && side > 0) {
if (start === pos && side < 0 || end === pos && side > 0) {
return null;
}
const alias = text.slice(start - from, end - from);

View File

@ -14,14 +14,14 @@ interface ILibraryContext {
filter: (params: ILibraryFilter) => IRSFormMeta[]
createSchema: (data: IRSFormCreateData, callback?: DataCallback<IRSFormMeta>) => void
cloneSchema: (target:number, data: IRSFormCreateData, callback: DataCallback<IRSFormData>) => void
destroySchema: (target: number, callback?: () => void) => void
cloneSchema: (target: string, data: IRSFormCreateData, callback: DataCallback<IRSFormData>) => void
destroySchema: (target: string, callback?: () => void) => void
}
const LibraryContext = createContext<ILibraryContext | null>(null)
export const useLibrary = (): ILibraryContext => {
const context = useContext(LibraryContext);
if (context == null) {
if (context === null) {
throw new Error(
'useLibrary has to be used within <LibraryState.Provider>'
);
@ -90,9 +90,9 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
}, [reload]);
const destroySchema = useCallback(
(target: number, callback?: () => void) => {
(target: string, callback?: () => void) => {
setError(undefined)
deleteRSForm(String(target), {
deleteRSForm(target, {
showError: true,
setLoading: setProcessing,
onError: error => setError(error),
@ -103,12 +103,12 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
}, [setError, reload]);
const cloneSchema = useCallback(
(target: number, data: IRSFormCreateData, callback: DataCallback<IRSFormData>) => {
(target: string, data: IRSFormCreateData, callback: DataCallback<IRSFormData>) => {
if (!user) {
return;
}
setError(undefined)
postCloneRSForm(String(target), {
postCloneRSForm(target, {
data: data,
showError: true,
setLoading: setProcessing,

View File

@ -50,7 +50,7 @@ interface IRSFormContext {
const RSFormContext = createContext<IRSFormContext | null>(null)
export const useRSForm = () => {
const context = useContext(RSFormContext)
if (context == null) {
if (context === null) {
throw new Error(
'useRSForm has to be used within <RSFormState.Provider>'
)
@ -205,7 +205,7 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
const cstUpdate = useCallback(
(data: ICstUpdateData, callback?: DataCallback<IConstituentaMeta>) => {
setError(undefined)
patchConstituenta(String(data.id), {
patchConstituenta(data.id, {
data: data,
showError: true,
setLoading: setProcessing,

View File

@ -12,7 +12,7 @@ interface IUsersContext {
const UsersContext = createContext<IUsersContext | null>(null)
export const useUsers = (): IUsersContext => {
const context = useContext(UsersContext);
if (context == null) {
if (context === null) {
throw new Error(
'useUsers has to be used within <UsersState.Provider>'
);

View File

@ -13,7 +13,7 @@ function useCheckExpression({ schema }: { schema?: IRSForm }) {
function checkExpression(expression: string, onSuccess?: DataCallback<IExpressionParse>) {
setError(undefined);
postCheckExpression(String(schema?.id), {
postCheckExpression(schema!.id, {
data: { expression: expression },
showError: true,
setLoading,

View File

@ -7,15 +7,15 @@ import { getCstLabel } from '../../utils/staticUI';
interface DlgDeleteCstProps {
hideWindow: () => void
selected: number[]
onDelete: (items: number[]) => void
selected: string[]
onDelete: (items: string[]) => void
}
function DlgDeleteCst({ hideWindow, selected, onDelete }: DlgDeleteCstProps) {
const { schema } = useRSForm();
const [ expandOut, setExpandOut ] = useState(false);
const expansion: number[] = useMemo(() => schema?.graph.expandOutputs(selected) ?? [], [selected, schema?.graph]);
const expansion: string[] = useMemo(() => schema?.graph.expandOutputs(selected) ?? [], [selected, schema?.graph]);
function handleSubmit() {
hideWindow();

View File

@ -28,7 +28,7 @@ function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) {
() => {
const result: GraphEdge[] = [];
syntaxTree.forEach(node => {
if (node.parent != node.uid) {
if (node.parent !== node.uid) {
result.push({
id: String(node.uid),
source: String(node.parent),

View File

@ -18,11 +18,11 @@ import ViewSideConstituents from './elements/ViewSideConstituents';
const UNFOLDED_HEIGHT = '59.1rem';
interface EditorConstituentaProps {
activeID?: number
onOpenEdit: (cstID: number) => void
activeID?: string
onOpenEdit: (cstID: string) => void
onShowAST: (expression: string, ast: SyntaxTree) => void
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void
onDeleteCst: (selected: number[], callback?: (items: number[]) => void) => void
onDeleteCst: (selected: string[], callback?: (items: string[]) => void) => void
}
function EditorConstituenta({ activeID, onShowAST, onCreateCst, onOpenEdit, onDeleteCst }: EditorConstituentaProps) {

View File

@ -14,15 +14,15 @@ import { CstType, IConstituenta, ICstCreateData, ICstMovetoData } from '../../ut
import { getCstTypePrefix, getCstTypeShortcut, getCstTypificationLabel, mapStatusInfo } from '../../utils/staticUI';
interface EditorItemsProps {
onOpenEdit: (cstID: number) => void
onOpenEdit: (cstID: string) => void
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void
onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void
onDeleteCst: (selected: string[], callback: (items: string[]) => void) => void
}
function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) {
const { schema, isEditable, cstMoveTo, resetAliases } = useRSForm();
const { noNavigation } = useConceptTheme();
const [selected, setSelected] = useState<number[]>([]);
const [selected, setSelected] = useState<string[]>([]);
const nothingSelected = useMemo(() => selected.length === 0, [selected]);
const [toggledClearRows, setToggledClearRows] = useState(false);

View File

@ -184,7 +184,7 @@ function EditorRSExpression({
}, [handleEdit]);
return (
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3 w-full'>
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3 w-full min-h-[15.75rem]'>
<div className='relative w-full'>
<div className='absolute top-[-0.3rem] right-0'>
<StatusBar

View File

@ -56,9 +56,9 @@ export interface GraphEditorParams {
}
interface EditorTermGraphProps {
onOpenEdit: (cstID: number) => void
onOpenEdit: (cstID: string) => void
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void
onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void
onDeleteCst: (selected: string[], callback: (items: string[]) => void) => void
}
function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGraphProps) {
@ -83,8 +83,8 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
const [ allowTheorem, setAllowTheorem ] = useLocalStorage('graph_allow_theorem', true);
const [ filtered, setFiltered ] = useState<Graph>(new Graph());
const [ dismissed, setDismissed ] = useState<number[]>([]);
const [ selectedDismissed, setSelectedDismissed ] = useState<number[]>([]);
const [ dismissed, setDismissed ] = useState<string[]>([]);
const [ selectedDismissed, setSelectedDismissed ] = useState<string[]>([]);
const graphRef = useRef<GraphCanvasRef | null>(null);
const [showOptions, setShowOptions] = useState(false);
const [toggleUpdate, setToggleUpdate] = useState(false);
@ -92,7 +92,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
const [hoverID, setHoverID] = useState<string | undefined>(undefined);
const hoverCst = useMemo(
() => {
return schema?.items.find(cst => String(cst.id) == hoverID);
return schema?.items.find(cst => String(cst.id) === hoverID);
}, [schema?.items, hoverID]);
const is3D = useMemo(() => layout.includes('3d'), [layout]);
@ -137,7 +137,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
}
});
}
const newDismissed: number[] = [];
const newDismissed: string[] = [];
schema.items.forEach(cst => {
if (!graph.nodes.has(cst.id)) {
newDismissed.push(cst.id);
@ -149,9 +149,9 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
setHoverID(undefined);
}, [schema, noHermits, noTransitive, noTemplates, allowedTypes, toggleUpdate]);
function toggleDismissed(cstID: number) {
function toggleDismissed(cstID: string) {
setSelectedDismissed(prev => {
const index = prev.findIndex(id => cstID == id);
const index = prev.findIndex(id => cstID === id);
if (index !== -1) {
prev.splice(index, 1);
} else {
@ -171,12 +171,13 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
const cst = schema.items.find(cst => cst.id === node.id);
if (cst) {
result.push({
id: String(node.id),
id: node.id,
fill: getCstNodeColor(cst, coloringScheme, darkMode),
label: cst.term.resolved && !noTerms ? `${cst.alias}: ${cst.term.resolved}` : cst.alias
});
}
});
console.log(result);
return result;
}, [schema, coloringScheme, filtered.nodes, darkMode, noTerms]);
@ -188,12 +189,13 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
source.outputs.forEach(target => {
result.push({
id: String(edgeID),
source: String(source.id),
target: String(target)
source: source.id,
target: target
});
edgeID += 1;
});
});
console.log(result);
return result;
}, [filtered.nodes]);
@ -216,7 +218,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
const allSelected: string[] = useMemo(
() => {
return [ ... selectedDismissed.map(id => String(id)), ... selections];
return [ ... selectedDismissed, ... selections];
}, [selectedDismissed, selections]);
const nothingSelected = useMemo(() => allSelected.length === 0, [allSelected]);
@ -241,7 +243,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
const handleNodeClick = useCallback(
(node: GraphNode) => {
if (selections.includes(node.id)) {
onOpenEdit(Number(node.id));
onOpenEdit(node.id);
return;
}
if (onNodeClick) onNodeClick(node);
@ -272,10 +274,10 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
}
const data: ICstCreateData = {
insert_after: null,
cst_type: allSelected.length == 0 ? CstType.BASE: CstType.TERM,
cst_type: allSelected.length === 0 ? CstType.BASE: CstType.TERM,
alias: '',
term_raw: '',
definition_formal: allSelected.map(id => schema.items.find(cst => cst.id === Number(id))!.alias).join(' '),
definition_formal: allSelected.map(id => schema.items.find(cst => cst.id === id)!.alias).join(' '),
definition_raw: '',
convention: '',
};
@ -286,7 +288,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
if (!schema) {
return;
}
onDeleteCst([... allSelected.map(id => Number(id))], () => {
onDeleteCst(allSelected, () => {
clearSelections();
setDismissed([]);
setSelectedDismissed([]);
@ -344,7 +346,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
}, [noNavigation]);
const dismissedStyle = useCallback(
(cstID: number) => {
(cstID: string) => {
return selectedDismissed.includes(cstID) ? {outlineWidth: '2px', outlineStyle: 'solid'}: {};
}, [selectedDismissed]);

View File

@ -40,7 +40,7 @@ function RSTabs() {
const { destroySchema } = useLibrary();
const [activeTab, setActiveTab] = useState(RSTabsList.CARD);
const [activeID, setActiveID] = useState<number | undefined>(undefined);
const [activeID, setActiveID] = useState<string | undefined>(undefined);
const [showUpload, setShowUpload] = useState(false);
const [showClone, setShowClone] = useState(false);
@ -49,8 +49,8 @@ function RSTabs() {
const [expression, setExpression] = useState('');
const [showAST, setShowAST] = useState(false);
const [afterDelete, setAfterDelete] = useState<((items: number[]) => void) | undefined>(undefined);
const [toBeDeleted, setToBeDeleted] = useState<number[]>([]);
const [afterDelete, setAfterDelete] = useState<((items: string[]) => void) | undefined>(undefined);
const [toBeDeleted, setToBeDeleted] = useState<string[]>([]);
const [showDeleteCst, setShowDeleteCst] = useState(false);
const [createInitialData, setCreateInitialData] = useState<ICstCreateData>();
@ -70,7 +70,7 @@ function RSTabs() {
const activeTab = Number(new URLSearchParams(search).get('tab')) ?? RSTabsList.CARD;
const cstQuery = new URLSearchParams(search).get('active');
setActiveTab(activeTab);
setActiveID(Number(cstQuery) ?? (schema && schema?.items.length > 0 && schema?.items[0]));
setActiveID(cstQuery ?? ((schema && schema?.items.length > 0) ? schema.items[0].id : undefined))
}, [search, setActiveTab, setActiveID, schema]);
function onSelectTab(index: number) {
@ -78,7 +78,7 @@ function RSTabs() {
}
const navigateTo = useCallback(
(tab: RSTabsList, activeID?: number) => {
(tab: RSTabsList, activeID?: string) => {
if (activeID) {
navigate(`/rsforms/${schema!.id}?tab=${tab}&active=${activeID}`, {
replace: tab === activeTab && tab !== RSTabsList.CST_EDIT
@ -97,7 +97,7 @@ function RSTabs() {
cstCreate(data, newCst => {
toast.success(`Конституента добавлена: ${newCst.alias}`);
navigateTo(activeTab, newCst.id);
if (activeTab === RSTabsList.CST_EDIT || activeTab == RSTabsList.CST_LIST) {
if (activeTab === RSTabsList.CST_EDIT || activeTab === RSTabsList.CST_LIST) {
setTimeout(() => {
const element = document.getElementById(`${prefixes.cst_list}${newCst.alias}`);
if (element) {
@ -123,7 +123,7 @@ function RSTabs() {
}, [handleCreateCst]);
const handleDeleteCst = useCallback(
(deleted: number[]) => {
(deleted: string[]) => {
if (!schema) {
return;
}
@ -148,9 +148,9 @@ function RSTabs() {
}, [afterDelete, cstDelete, schema, activeID, activeTab, navigateTo]);
const promptDeleteCst = useCallback(
(selected: number[], callback?: (items: number[]) => void) => {
(selected: string[], callback?: (items: string[]) => void) => {
setAfterDelete(() => (
(items: number[]) => {
(items: string[]) => {
if (callback) callback(items);
}));
setToBeDeleted(selected);
@ -165,7 +165,7 @@ function RSTabs() {
}, []);
const onOpenCst = useCallback(
(cstID: number) => {
(cstID: string) => {
navigateTo(RSTabsList.CST_EDIT, cstID)
}, [navigateTo]);
@ -217,19 +217,19 @@ function RSTabs() {
defaultFocus={true}
selectedTabClassName='font-bold'
>
<TabList className='flex items-start px-2 select-none w-fit clr-bg-pop'>
<TabList className='flex items-start pl-2 select-none w-fit clr-bg-pop'>
<RSTabsMenu
onDestroy={onDestroySchema}
showCloneDialog={() => setShowClone(true)}
showUploadDialog={() => setShowUpload(true)}
/>
<ConceptTab className='border-r-2'>Паспорт схемы</ConceptTab>
<ConceptTab className='border-r-2 min-w-[7.8rem]'>Паспорт схемы</ConceptTab>
<ConceptTab className='border-r-2 min-w-[10rem] flex justify-between gap-2'>
<span>Конституенты</span>
<span>{`${schema.stats?.count_errors ?? 0} | ${schema.stats?.count_all ?? 0}`}</span>
</ConceptTab>
<ConceptTab className='border-r-2'>Редактор</ConceptTab>
<ConceptTab>Граф термов</ConceptTab>
<ConceptTab className='border-r-2 min-w-[5.2rem]'>Редактор</ConceptTab>
<ConceptTab className='min-w-[6.5rem]'>Граф термов</ConceptTab>
</TabList>
<TabPanel className='flex items-start w-full gap-2 px-2'>

View File

@ -17,8 +17,12 @@ const LOCAL_NAVIGATION_H = '2.6rem';
interface ViewSideConstituentsProps {
expression: string
baseHeight: string
activeID?: number
onOpenEdit: (cstID: number) => void
activeID?: string
onOpenEdit: (cstID: string) => void
}
function isMockCst(cst: IConstituenta) {
return cst.id[0] === '-'
}
function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: ViewSideConstituentsProps) {
@ -45,7 +49,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
const diff = Array.from(aliases).filter(name => !names.includes(name));
if (diff.length > 0) {
diff.forEach(
(alias, index) => filtered.push(getMockConstituenta(-index, alias, CstType.BASE, 'Конституента отсутствует')));
(alias, index) => filtered.push(getMockConstituenta(`-${index}`, alias, CstType.BASE, 'Конституента отсутствует')));
}
} else if (!activeID) {
filtered = schema.items
@ -60,14 +64,14 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
const handleRowClicked = useCallback(
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
if (event.altKey && cst.id > 0) {
if (event.altKey && !isMockCst(cst)) {
onOpenEdit(cst.id);
}
}, [onOpenEdit]);
const handleDoubleClick = useCallback(
(cst: IConstituenta) => {
if (cst.id > 0) {
if (!isMockCst(cst)) {
onOpenEdit(cst.id);
}
}, [onOpenEdit]);
@ -108,7 +112,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
maxWidth: '65px',
conditionalCellStyles: [
{
when: (cst: IConstituenta) => cst.id <= 0,
when: (cst: IConstituenta) => isMockCst(cst),
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
}
]
@ -121,7 +125,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
wrap: true,
conditionalCellStyles: [
{
when: (cst: IConstituenta) => cst.id <= 0,
when: (cst: IConstituenta) => isMockCst(cst),
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
}
]
@ -136,7 +140,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
wrap: true,
conditionalCellStyles: [
{
when: (cst: IConstituenta) => cst.id <= 0,
when: (cst: IConstituenta) => isMockCst(cst),
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
}
]

View File

@ -64,9 +64,12 @@ function RegisterPage() {
onChange={event => { setPassword2(event.target.value); }}
/>
<div className='text-sm'>
<p>- минимум 8 символов</p>
<p>- используйте уникальный пароль</p>
<p>- портал функционирует в тестовом режиме</p>
<p>- безопасность информации пользователей не гарантируется</p>
{/* <p>- минимум 8 символов</p>
<p>- большие, маленькие буквы, цифры</p>
<p>- минимум 1 спец. символ</p>
<p>- минимум 1 спец. символ</p> */}
</div>
<TextInput id='email' label='email' type='text'
required

View File

@ -8,94 +8,94 @@ describe('Testing Graph constuction', () => {
test('adding edges should create nodes', () => {
const graph = new Graph();
graph.addEdge(13, 37);
expect([... graph.nodes.keys()]).toStrictEqual([13, 37]);
graph.addEdge('13', '37');
expect([... graph.nodes.keys()]).toStrictEqual(['13', '37']);
graph.addEdge(13, 38);
expect([... graph.nodes.keys()]).toStrictEqual([13, 37, 38]);
graph.addEdge('13', '38');
expect([... graph.nodes.keys()]).toStrictEqual(['13', '37', '38']);
});
test('creating from array', () => {
const graph = new Graph([[1, 2], [3], [4, 1]]);
expect([... graph.nodes.keys()]).toStrictEqual([1, 2, 3, 4]);
expect([... graph.nodes.get(1)!.outputs]).toStrictEqual([2]);
const graph = new Graph([['1', '2'], ['3'], ['4', '1']]);
expect([... graph.nodes.keys()]).toStrictEqual(['1', '2', '3', '4']);
expect([... graph.nodes.get('1')!.outputs]).toStrictEqual(['2']);
});
test('cloning', () => {
const graph = new Graph([[1, 2], [3], [4, 1]]);
const graph = new Graph([['1', '2'], ['3'], ['4', '1']]);
const clone = graph.clone();
expect([... graph.nodes.keys()]).toStrictEqual([... clone.nodes.keys()]);
expect([... graph.nodes.values()]).toStrictEqual([... clone.nodes.values()]);
clone.removeNode(3);
expect(clone.nodes.get(3)).toBeUndefined();
expect(graph.nodes.get(3)).not.toBeUndefined();
clone.removeNode('3');
expect(clone.nodes.get('3')).toBeUndefined();
expect(graph.nodes.get('3')).not.toBeUndefined();
});
});
describe('Testing Graph editing', () => {
test('removing edges should not remove nodes', () => {
const graph = new Graph([[1, 2], [3], [4, 1]]);
expect(graph.hasEdge(4, 1)).toBeTruthy();
const graph = new Graph([['1', '2'], ['3'], ['4', '1']]);
expect(graph.hasEdge('4', '1')).toBeTruthy();
graph.removeEdge(5, 0);
graph.removeEdge(4, 1);
graph.removeEdge('5', '0');
graph.removeEdge('4', '1');
expect([... graph.nodes.keys()]).toStrictEqual([1, 2, 3, 4]);
expect(graph.hasEdge(4, 1)).toBeFalsy();
expect([... graph.nodes.keys()]).toStrictEqual(['1', '2', '3', '4']);
expect(graph.hasEdge('4', '1')).toBeFalsy();
});
test('folding node redirectes edges', () => {
const graph = new Graph([[1, 3], [2, 3], [3, 4], [3, 5], [3, 3]]);
graph.foldNode(3);
expect(graph.hasNode(3)).toBeFalsy();
expect(graph.hasEdge(1, 4)).toBeTruthy();
expect(graph.hasEdge(1, 5)).toBeTruthy();
expect(graph.hasEdge(2, 4)).toBeTruthy();
expect(graph.hasEdge(2, 5)).toBeTruthy();
const graph = new Graph([['1', '3'], ['2', '3'], ['3', '4'], ['3', '5'], ['3', '3']]);
graph.foldNode('3');
expect(graph.hasNode('3')).toBeFalsy();
expect(graph.hasEdge('1', '4')).toBeTruthy();
expect(graph.hasEdge('1', '5')).toBeTruthy();
expect(graph.hasEdge('2', '4')).toBeTruthy();
expect(graph.hasEdge('2', '5')).toBeTruthy();
});
test('removing isolated nodes', () => {
const graph = new Graph([[9, 1], [9, 2], [2, 1], [4, 3], [5, 9], [7], [8]]);
const graph = new Graph([['9', '1'], ['9', '2'], ['2', '1'], ['4', '3'], ['5', '9'], ['7'], ['8']]);
graph.removeIsolated()
expect([... graph.nodes.keys()]).toStrictEqual([9, 1, 2, 4, 3, 5]);
expect([... graph.nodes.keys()]).toStrictEqual(['9', '1', '2', '4', '3', '5']);
});
test('transitive reduction', () => {
const graph = new Graph([[1, 3], [1, 2], [2, 3]]);
const graph = new Graph([['1', '3'], ['1', '2'], ['2', '3']]);
graph.transitiveReduction();
expect(graph.hasEdge(1, 2)).toBeTruthy();
expect(graph.hasEdge(2, 3)).toBeTruthy();
expect(graph.hasEdge(1, 3)).toBeFalsy();
expect(graph.hasEdge('1', '2')).toBeTruthy();
expect(graph.hasEdge('2', '3')).toBeTruthy();
expect(graph.hasEdge('1', '3')).toBeFalsy();
});
});
describe('Testing Graph sort', () => {
test('topological order', () => {
const graph = new Graph([[9, 1], [9, 2], [2, 1], [4, 3], [5, 9]]);
expect(graph.tolopogicalOrder()).toStrictEqual([5, 4, 3, 9, 2, 1]);
const graph = new Graph([['9', '1'], ['9', '2'], ['2', '1'], ['4', '3'], ['5', '9']]);
expect(graph.tolopogicalOrder()).toStrictEqual(['5', '4', '3', '9', '2', '1']);
});
});
describe('Testing Graph queries', () => {
test('expand outputs', () => {
const graph = new Graph([[1, 2], [2, 3], [2, 5], [5, 6], [6, 1], [7]]);
const graph = new Graph([['1', '2'], ['2', '3'], ['2', '5'], ['5', '6'], ['6', '1'], ['7']]);
expect(graph.expandOutputs([])).toStrictEqual([]);
expect(graph.expandOutputs([3])).toStrictEqual([]);
expect(graph.expandOutputs([7])).toStrictEqual([]);
expect(graph.expandOutputs([2, 5])).toStrictEqual([3, 6, 1]);
expect(graph.expandOutputs(['3'])).toStrictEqual([]);
expect(graph.expandOutputs(['7'])).toStrictEqual([]);
expect(graph.expandOutputs(['2', '5'])).toStrictEqual(['3', '6', '1']);
});
test('expand into unique array', () => {
const graph = new Graph([[1, 2], [1, 3], [2, 5], [3, 5]]);
expect(graph.expandOutputs([1])).toStrictEqual([2, 3 ,5]);
const graph = new Graph([['1', '2'], ['1', '3'], ['2', '5'], ['3', '5']]);
expect(graph.expandOutputs(['1'])).toStrictEqual(['2', '3' , '5']);
});
test('expand inputs', () => {
const graph = new Graph([[1, 2], [2, 3], [2, 5], [5, 6], [6, 1], [7]]);
const graph = new Graph([['1', '2'], ['2', '3'], ['2', '5'], ['5', '6'], ['6', '1'], ['7']]);
expect(graph.expandInputs([])).toStrictEqual([]);
expect(graph.expandInputs([7])).toStrictEqual([]);
expect(graph.expandInputs([6])).toStrictEqual([5, 2, 1]);
expect(graph.expandInputs(['7'])).toStrictEqual([]);
expect(graph.expandInputs(['6'])).toStrictEqual(['5', '2', '1']);
});
});

View File

@ -1,10 +1,10 @@
// ======== ID based fast Graph implementation =============
export class GraphNode {
id: number;
outputs: number[];
inputs: number[];
id: string;
outputs: string[];
inputs: string[];
constructor(id: number) {
constructor(id: string) {
this.id = id;
this.outputs = [];
this.inputs = [];
@ -17,34 +17,34 @@ export class GraphNode {
return result;
}
addOutput(node: number): void {
addOutput(node: string): void {
this.outputs.push(node);
}
addInput(node: number): void {
addInput(node: string): void {
this.inputs.push(node);
}
removeInput(target: number): number | null {
removeInput(target: string): string | null {
const index = this.inputs.findIndex(node => node === target);
return index > -1 ? this.inputs.splice(index, 1)[0] : null;
}
removeOutput(target: number): number | null {
removeOutput(target: string): string | null {
const index = this.outputs.findIndex(node => node === target);
return index > -1 ? this.outputs.splice(index, 1)[0] : null;
}
}
export class Graph {
nodes: Map<number, GraphNode> = new Map();
nodes: Map<string, GraphNode> = new Map();
constructor(arr?: number[][]) {
constructor(arr?: string[][]) {
if (!arr) {
return;
}
arr.forEach(edge => {
if (edge.length == 1) {
if (edge.length === 1) {
this.addNode(edge[0]);
} else {
this.addEdge(edge[0], edge[1]);
@ -58,7 +58,7 @@ export class Graph {
return result;
}
addNode(target: number): GraphNode {
addNode(target: string): GraphNode {
let node = this.nodes.get(target);
if (!node) {
node = new GraphNode(target);
@ -67,11 +67,11 @@ export class Graph {
return node;
}
hasNode(target: number): boolean {
hasNode(target: string): boolean {
return !!this.nodes.get(target);
}
removeNode(target: number): GraphNode | null {
removeNode(target: string): GraphNode | null {
const nodeToRemove = this.nodes.get(target);
if (!nodeToRemove) {
return null;
@ -84,7 +84,7 @@ export class Graph {
return nodeToRemove;
}
foldNode(target: number): GraphNode | null {
foldNode(target: string): GraphNode | null {
const nodeToRemove = this.nodes.get(target);
if (!nodeToRemove) {
return null;
@ -107,14 +107,14 @@ export class Graph {
return result;
}
addEdge(source: number, destination: number): void {
addEdge(source: string, destination: string): void {
const sourceNode = this.addNode(source);
const destinationNode = this.addNode(destination);
sourceNode.addOutput(destinationNode.id);
destinationNode.addInput(sourceNode.id);
}
removeEdge(source: number, destination: number): void {
removeEdge(source: string, destination: string): void {
const sourceNode = this.nodes.get(source);
const destinationNode = this.nodes.get(destination);
if (sourceNode && destinationNode) {
@ -123,7 +123,7 @@ export class Graph {
}
}
hasEdge(source: number, destination: number): boolean {
hasEdge(source: string, destination: string): boolean {
const sourceNode = this.nodes.get(source);
if (!sourceNode) {
return false;
@ -131,9 +131,9 @@ export class Graph {
return !!sourceNode.outputs.find(id => id === destination);
}
expandOutputs(origin: number[]): number[] {
const result: number[] = [];
const marked = new Map<number, boolean>();
expandOutputs(origin: string[]): string[] {
const result: string[] = [];
const marked = new Map<string, boolean>();
origin.forEach(id => marked.set(id, true));
origin.forEach(id => {
const node = this.nodes.get(id);
@ -161,9 +161,9 @@ export class Graph {
return result;
}
expandInputs(origin: number[]): number[] {
const result: number[] = [];
const marked = new Map<number, boolean>();
expandInputs(origin: string[]): string[] {
const result: string[] = [];
const marked = new Map<string, boolean>();
origin.forEach(id => marked.set(id, true));
origin.forEach(id => {
const node = this.nodes.get(id);
@ -191,10 +191,10 @@ export class Graph {
return result;
}
tolopogicalOrder(): number[] {
const result: number[] = [];
const marked = new Map<number, boolean>();
const toVisit: number[] = [];
tolopogicalOrder(): string[] {
const result: string[] = [];
const marked = new Map<string, boolean>();
const toVisit: string[] = [];
this.nodes.forEach(node => {
if (marked.get(node.id)) {
return;
@ -225,12 +225,12 @@ export class Graph {
transitiveReduction() {
const order = this.tolopogicalOrder();
const marked = new Map<number, boolean>();
const marked = new Map<string, boolean>();
order.forEach(nodeID => {
if (marked.get(nodeID)) {
return;
}
const stack: {id: number, parents: number[]}[] = [];
const stack: {id: string, parents: string[]}[] = [];
stack.push({id: nodeID, parents: []});
while (stack.length > 0) {
const item = stack.splice(0, 1)[0];

View File

@ -196,7 +196,7 @@ export function postNewConstituenta(schema: string, request: FrontExchange<ICstC
export function patchDeleteConstituenta(schema: string, request: FrontExchange<IConstituentaList, IRSFormData>) {
AxiosPatch({
title: `Delete Constituents for RSForm id=${schema}: ${request.data.items.map(item => String(item.id)).join(' ')}`,
title: `Delete Constituents for RSForm id=${schema}: ${request.data.items.map(item => item.id).join(' ')}`,
endpoint: `/api/rsforms/${schema}/cst-multidelete/`,
request: request
});

View File

@ -162,11 +162,11 @@ const ERRCODE_LEXER_MASK = 0x0200;
const ERRCODE_PARSER_MASK = 0x0400;
const ERRCODE_TYPE_MASK = 0x0800;
export function resolveErrorClass(error: RSErrorType): RSErrorClass {
if ((error & ERRCODE_LEXER_MASK) != 0) {
if ((error & ERRCODE_LEXER_MASK) !== 0) {
return RSErrorClass.LEXER;
} else if ((error & ERRCODE_PARSER_MASK) != 0) {
} else if ((error & ERRCODE_PARSER_MASK) !== 0) {
return RSErrorClass.PARSER;
} else if ((error & ERRCODE_TYPE_MASK) != 0) {
} else if ((error & ERRCODE_TYPE_MASK) !== 0) {
return RSErrorClass.SEMANTIC;
} else {
return RSErrorClass.UNKNOWN;

View File

@ -109,7 +109,7 @@ export enum CstClass {
}
export interface IConstituenta {
id: number
id: string
alias: string
cstType: CstType
convention: string
@ -138,8 +138,8 @@ export interface IConstituenta {
}
export interface IConstituentaMeta {
id: number
schema: number
id: string
schema: string
order: number
alias: string
convention: string
@ -158,7 +158,7 @@ export interface IConstituentaList {
export interface ICstCreateData
extends Pick<IConstituentaMeta, 'alias' | 'cst_type' | 'definition_raw' | 'term_raw' | 'convention' | 'definition_formal' > {
insert_after: number | null
insert_after: string | null
}
export interface ICstMovetoData extends IConstituentaList {
@ -168,6 +168,9 @@ export interface ICstMovetoData extends IConstituentaList {
export interface ICstUpdateData
extends Pick<IConstituentaMeta, 'id' | 'alias' | 'convention' | 'definition_formal' | 'definition_raw' | 'term_raw'> {}
export interface ICstRenameData
extends Pick<IConstituentaMeta, 'id' | 'alias' | 'cst_type' > {}
export interface ICstCreatedResponse {
new_cst: IConstituentaMeta
schema: IRSFormData
@ -193,7 +196,7 @@ export interface IRSFormStats {
}
export interface IRSForm {
id: number
id: string
title: string
alias: string
comment: string
@ -365,7 +368,9 @@ export function LoadRSFormData(schema: IRSFormData): IRSForm {
count_theorem: result.items.reduce(
(sum, cst) => sum + (cst.cstType === CstType.THEOREM ? 1 : 0), 0)
}
result.id = String(result.id)
result.items.forEach(cst => {
cst.id = String(cst.id)
cst.status = inferStatus(cst.parse.status, cst.parse.valueClass);
cst.isTemplate = inferTemplate(cst.definition.formal);
cst.cstClass = inferClass(cst.cstType, cst.isTemplate);
@ -411,11 +416,11 @@ export function matchRSFormMeta(query: string, target: IRSFormMeta) {
}
}
export function applyGraphFilter(schema: IRSForm, start: number, mode: DependencyMode): IConstituenta[] {
export function applyGraphFilter(schema: IRSForm, start: string, mode: DependencyMode): IConstituenta[] {
if (mode === DependencyMode.ALL) {
return schema.items;
}
let ids: number[] | undefined = undefined
let ids: string[] | undefined = undefined
switch (mode) {
case DependencyMode.OUTPUTS: { ids = schema.graph.nodes.get(start)?.outputs; break; }
case DependencyMode.INPUTS: { ids = schema.graph.nodes.get(start)?.inputs; break; }

View File

@ -407,7 +407,7 @@ export function createAliasFor(type: CstType, schema: IRSForm): string {
return `${prefix}${index}`;
}
export function getMockConstituenta(id: number, alias: string, type: CstType, comment: string): IConstituenta {
export function getMockConstituenta(id: string, alias: string, type: CstType, comment: string): IConstituenta {
return {
id: id,
alias: alias,