Compare commits

...

18 Commits

Author SHA1 Message Date
Ivan
c05759901a F: Filter out users without schemas
Some checks failed
Backend CI / build (3.12) (push) Has been cancelled
Frontend CI / build (22.x) (push) Has been cancelled
Backend CI / notify-failure (push) Has been cancelled
Frontend CI / notify-failure (push) Has been cancelled
2025-06-11 11:00:40 +03:00
Ivan
ac50a4fb05 B: Fix token validation issue 2025-06-10 23:20:13 +03:00
Ivan
2b7acfbd27 R: Replace constant with formula 2025-06-10 22:16:44 +03:00
Ivan
313d5de706 B: Fix operation precedences 2025-06-10 21:49:23 +03:00
Ivan
c166a4478a B: Do not change state in render 2025-06-10 21:28:21 +03:00
Ivan
5dcaae4f51 Update text-editing.ts 2025-06-10 21:27:41 +03:00
Ivan
f0f41a4dcb M: Improve double boolean editing 2025-06-10 21:20:22 +03:00
Ivan
2b65b19d08 R: Fix onSuccess typing for react-query 2025-06-05 15:46:01 +03:00
Ivan
53d8dfc3d6 npm update 2025-06-05 15:29:52 +03:00
Ivan
ece60e565d F: Refactor node ID and improve layout for new items 2025-06-05 15:26:06 +03:00
Ivan
841a72af4e update dependencies 2025-05-29 15:46:56 +03:00
Ivan
8452bf4d55 M: Small fixes 2025-05-29 15:38:39 +03:00
Ivan
32eee7ca0b M: Fix ENV format 2025-05-15 12:37:11 +03:00
Ivan
addcd52cb6 B: Fix list HTML structure 2025-05-14 12:29:53 +03:00
Ivan
c442ca8094 M: Minor UI fixes 2025-05-14 11:48:14 +03:00
Ivan
a26dd4bc7b B: Fix context menu position 2025-05-14 11:18:49 +03:00
Ivan
16a74bf4f8 R: Improve layouting commands 2025-05-14 10:56:40 +03:00
Ivan
5f2239dc26 Update text-editing.ts 2025-05-14 10:52:54 +03:00
93 changed files with 2281 additions and 3388 deletions

View File

@ -58,14 +58,6 @@
"depth": 2
}
],
"colorize.include": [".tsx", ".jsx", ".ts", ".js"],
"colorize.languages": [
"typescript",
"javascript",
"css",
"typescriptreact",
"javascriptreact"
],
"cSpell.words": [
"ablt",
"acconcept",

View File

@ -28,8 +28,8 @@ RUN apt-get update -qq && \
FROM python-base AS builder
# Set env variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
COPY ./requirements.txt ./
RUN python3.12 -m pip wheel \

View File

@ -7410,13 +7410,12 @@
"pk": 1,
"fields": {
"oss": 41,
"parent": null,
"operation_type": "input",
"result": 38,
"alias": "КС Вещества",
"title": "Вещества и смеси",
"description": "",
"position_x": 530.0,
"position_y": 370.0
"description": ""
}
},
{
@ -7424,13 +7423,12 @@
"pk": 2,
"fields": {
"oss": 41,
"parent": null,
"operation_type": "input",
"result": 39,
"alias": "КС ООО",
"title": "Объект-объектные отношения",
"description": "",
"position_x": 710.0,
"position_y": 370.0
"description": ""
}
},
{
@ -7438,13 +7436,12 @@
"pk": 4,
"fields": {
"oss": 41,
"parent": null,
"operation_type": "input",
"result": 40,
"alias": "КС Процессы",
"title": "Процессы",
"description": "",
"position_x": 890.0,
"position_y": 370.0
"description": ""
}
},
{
@ -7452,13 +7449,12 @@
"pk": 9,
"fields": {
"oss": 41,
"parent": null,
"operation_type": "synthesis",
"result": 43,
"alias": "КС Объект-сред",
"title": "Объектная среда",
"description": "",
"position_x": 620.0,
"position_y": 470.0
"description": ""
}
},
{
@ -7466,13 +7462,49 @@
"pk": 10,
"fields": {
"oss": 41,
"parent": null,
"operation_type": "synthesis",
"result": 44,
"alias": "КС Проц-сред",
"title": "Процессные среды",
"description": "",
"position_x": 760.0,
"position_y": 570.0
"description": ""
}
},
{
"model": "oss.layout",
"pk": 1,
"fields": {
"oss": 41,
"data": {
"blocks": [],
"operations": [
{
"x": 530.0,
"y": 370.0,
"id": 1
},
{
"x": 710.0,
"y": 370.0,
"id": 2
},
{
"x": 890.0,
"y": 370.0,
"id": 4
},
{
"x": 620.0,
"y": 470.0,
"id": 9
},
{
"x": 760.0,
"y": 570.0,
"id": 10
}
]
}
}
}
]

View File

@ -1,10 +1,10 @@
tzdata==2025.2
Django==5.1.7
djangorestframework==3.15.2
Django==5.2.1
djangorestframework==3.16.0
django-cors-headers==4.7.0
django-filter==25.1
drf-spectacular==0.28.0
drf-spectacular-sidecar==2025.3.1
drf-spectacular-sidecar==2025.5.1
coreapi==2.3.3
django-rest-passwordreset==1.5.0
cctext==0.1.4
@ -13,9 +13,9 @@ pyconcept==0.1.12
psycopg2-binary==2.9.10
gunicorn==23.0.0
djangorestframework-stubs==3.15.3
django-extensions==3.2.3
django-stubs==5.1.3
djangorestframework-stubs==3.16.0
django-extensions==4.1
django-stubs==5.2.0
mypy==1.15.0
pylint==3.3.6
coverage==7.7.1
pylint==3.3.7
coverage==7.8.2

View File

@ -1,10 +1,10 @@
tzdata==2025.2
Django==5.1.7
djangorestframework==3.15.2
Django==5.2.1
djangorestframework==3.16.0
django-cors-headers==4.7.0
django-filter==25.1
drf-spectacular==0.28.0
drf-spectacular-sidecar==2025.3.1
drf-spectacular-sidecar==2025.5.1
coreapi==2.3.3
django-rest-passwordreset==1.5.0
cctext==0.1.4

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
"version": "1.0.0",
"type": "module",
"scripts": {
"generate": "lezer-generator src/components/rs-input/rslang/rslang-fast.grammar -o src/components/rs-input/rslang/parser.ts && lezer-generator src/components/rs-input/rslang/rslang-ast.grammar -o src/components/rs-input/rslang/parserAST.ts && lezer-generator src/components/refs-input/parse/refs-text.grammar -o src/components/refs-input/parse/parser.ts",
"generate": "lezer-generator src/features/rsform/components/rs-input/rslang/rslang-fast.grammar -o src/features/rsform/components/rs-input/rslang/parser.ts && lezer-generator src/features/rsform/components/rs-input/rslang/rslang-ast.grammar -o src/features/rsform/components/rs-input/rslang/parser-ast.ts && lezer-generator src/features/rsform/components/refs-input/parse/refs-text.grammar -o src/features/rsform/components/refs-input/parse/parser.ts",
"test": "jest",
"test:e2e": "playwright test",
"dev": "vite --host",
@ -17,10 +17,10 @@
"@dagrejs/dagre": "^1.1.4",
"@hookform/resolvers": "^5.0.1",
"@lezer/lr": "^1.4.2",
"@radix-ui/react-popover": "^1.1.13",
"@radix-ui/react-select": "^2.2.4",
"@tanstack/react-query": "^5.76.0",
"@tanstack/react-query-devtools": "^5.76.0",
"@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-select": "^2.2.5",
"@tanstack/react-query": "^5.80.5",
"@tanstack/react-query-devtools": "^5.80.5",
"@tanstack/react-table": "^8.21.3",
"@uiw/codemirror-themes": "^4.23.12",
"@uiw/react-codemirror": "^4.23.12",
@ -30,56 +30,56 @@
"cmdk": "^1.1.1",
"global": "^4.4.0",
"js-file-download": "^0.4.12",
"lucide-react": "^0.510.0",
"lucide-react": "^0.511.0",
"qrcode.react": "^4.2.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-error-boundary": "^6.0.0",
"react-hook-form": "^7.56.3",
"react-hook-form": "^7.57.0",
"react-icons": "^5.5.0",
"react-intl": "^7.1.11",
"react-router": "^7.6.0",
"react-scan": "^0.3.3",
"react-router": "^7.6.2",
"react-scan": "^0.3.4",
"react-tabs": "^6.1.0",
"react-toastify": "^11.0.5",
"react-tooltip": "^5.28.1",
"react-zoom-pan-pinch": "^3.7.0",
"reactflow": "^11.11.4",
"tailwind-merge": "^3.3.0",
"tw-animate-css": "^1.2.9",
"tw-animate-css": "^1.3.4",
"use-debounce": "^10.0.4",
"zod": "^3.24.4",
"zustand": "^5.0.4"
"zod": "^3.25.51",
"zustand": "^5.0.5"
},
"devDependencies": {
"@lezer/generator": "^1.7.3",
"@playwright/test": "^1.52.0",
"@tailwindcss/vite": "^4.1.6",
"@tailwindcss/vite": "^4.1.8",
"@types/jest": "^29.5.14",
"@types/node": "^22.15.17",
"@types/react": "^19.1.4",
"@types/react-dom": "^19.1.5",
"@types/node": "^22.15.29",
"@types/react": "^19.1.6",
"@types/react-dom": "^19.1.6",
"@typescript-eslint/eslint-plugin": "^8.0.1",
"@typescript-eslint/parser": "^8.0.1",
"@vitejs/plugin-react": "^4.4.1",
"@vitejs/plugin-react": "^4.5.1",
"babel-plugin-react-compiler": "^19.1.0-rc.1",
"eslint": "^9.26.0",
"eslint": "^9.28.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-playwright": "^2.2.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-compiler": "^19.1.0-rc.1",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"globals": "^16.1.0",
"globals": "^16.2.0",
"jest": "^29.7.0",
"stylelint": "^16.19.1",
"stylelint": "^16.20.0",
"stylelint-config-recommended": "^16.0.0",
"stylelint-config-standard": "^38.0.0",
"stylelint-config-tailwindcss": "^1.0.0",
"tailwindcss": "^4.0.7",
"ts-jest": "^29.3.2",
"ts-jest": "^29.3.4",
"typescript": "^5.8.3",
"typescript-eslint": "^8.32.1",
"typescript-eslint": "^8.33.1",
"vite": "^6.3.5"
},
"jest": {

View File

@ -47,6 +47,7 @@ export function TextArea({
<textarea
id={id}
className={cn(
'min-h-0',
'px-3 py-2',
'leading-tight',
'overflow-x-hidden overflow-y-auto',

View File

@ -19,8 +19,8 @@ function useTokenValidation(token: string, isPending: boolean) {
const validate = async () => {
if (!isTokenValidating && !isPending) {
await validateToken({ token });
setIsTokenValidating(true);
await validateToken({ token });
}
};
return { isTokenValidating, validate };
@ -44,10 +44,7 @@ export function Component() {
void resetPassword({
password: newPassword,
token: token
}).then(() => {
router.replace({ path: urls.home });
router.push({ path: urls.login });
});
}).then(() => router.replace({ path: urls.login }));
}
}
@ -83,6 +80,9 @@ export function Component() {
setNewPasswordRepeat(event.target.value);
}}
/>
{newPasswordRepeat && newPassword !== newPasswordRepeat ? (
<div className='text-sm text-destructive'>Пароли не совпадают</div>
) : null}
<SubmitButton
text='Установить пароль'

View File

@ -35,6 +35,7 @@ export function Component() {
<form className='cc-column w-96 mx-auto px-6 mt-3' onSubmit={handleSubmit} onChange={clearServerError}>
<TextInput
id='email'
type='email'
autoComplete='email'
required
allowEnter

View File

@ -20,6 +20,7 @@ export function HelpConceptSynthesis() {
<p>
Расширение выразительной способности достигается несколькими способами в зависимости от соотношения
синтезируемых точек зрения:
<ul>
<li>
<b>аспектный синтез</b> характеризуется отождествлением общих понятий в случае, когда часть неопределяемых
понятий является общей для двух точек зрения;
@ -33,11 +34,12 @@ export function HelpConceptSynthesis() {
интерпретации) схему для соединения понятий из двух операндов путем введения нового неопределяемого понятия,
моделирующего отношения между синтезируемыми схемами.
</li>
</ul>
</p>
<p>
Возможно использование комбинации описанных подходов в рамках одного синтеза. Более подробно про реализацию
операций в родоструктурной форме можно прочитать в{' '}
<LinkTopic text='разделе Операции' topic={HelpTopic.RSL_OPERATIONS} />
Поддерживается использование комбинации описанных подходов в рамках одного синтеза. Более подробно про
реализацию операций в родоструктурной форме можно прочитать в{' '}
<LinkTopic text='разделе Операции' topic={HelpTopic.RSL_OPERATIONS} />.
</p>
<p>
Для управления совокупностью синтезов используются{' '}

View File

@ -9,12 +9,13 @@ export function HelpAccess() {
<p>
Доступ к контенту на Портале может быть ограничен владельцем каждой схемы в рамках <b>политики доступа</b>.
</p>
<ul>
<li>
<IconPublic className='inline-icon icon-green' /> публичная политика не ограничивает чтение схемы
</li>
<li>
<IconProtected className='inline-icon icon-blue' /> защитная политика запрещает доступ для всех кроме редакторов
и владельца схемы
<IconProtected className='inline-icon icon-blue' /> защитная политика запрещает доступ для всех кроме
редакторов и владельца схемы
</li>
<li>
<IconPrivate className='inline-icon icon-red' /> личная политика оставляет доступ к схеме только владельцу
@ -26,6 +27,7 @@ export function HelpAccess() {
<li>
<IconImmutable className='inline-icon' /> режим защиты от редактирования предохраняет от случайных изменений
</li>
</ul>
</div>
);
}

View File

@ -24,6 +24,7 @@ export function HelpExteor() {
</p>
<h2>Основные функции</h2>
<ul>
<li>Работа с РС-формой системы понятий</li>
<li>Автоматическое определение типизации выражений</li>
<li>Проверка корректности РС-формы</li>
@ -35,6 +36,7 @@ export function HelpExteor() {
<li>Вычисление объектной интерпретации</li>
<li>Выгрузка концептуальных схем в Word</li>
<li>Импорт/экспорт интерпретаций через Excel</li>
</ul>
</div>
);
}

View File

@ -31,6 +31,7 @@ export function HelpInterface() {
</p>
<h2>Навигация и настройки</h2>
<ul>
<li>
<kbd>Ctrl + клик</kbd> на объект навигации откроет новую вкладку
</li>
@ -46,8 +47,8 @@ export function HelpInterface() {
<IconLogin size='1.25rem' className='inline-icon' /> вход в систему / регистрация нового пользователя
</li>
<li>
<IconUser2 size='1.25rem' className='inline-icon' /> меню пользователя содержит ряд настроек и переход к профилю
пользователя
<IconUser2 size='1.25rem' className='inline-icon' /> меню пользователя содержит ряд настроек и переход к
профилю пользователя
</li>
<li>
@ -57,6 +58,7 @@ export function HelpInterface() {
<li>
<IconLogout className='inline-icon' /> выход из системы
</li>
</ul>
<Subtopics headTopic={HelpTopic.INTERFACE} />
</div>

View File

@ -22,6 +22,7 @@ export function HelpMain() {
<details>
<summary className='text-center font-semibold'>Разделы Справки</summary>
<ul>
{[
HelpTopic.THESAURUS,
HelpTopic.INTERFACE,
@ -35,9 +36,11 @@ export function HelpMain() {
].map(topic => (
<TopicItem key={`${prefixes.topic_item}${topic}`} topic={topic} />
))}
</ul>
</details>
<h2>Лицензирование и раскрытие информации</h2>
<ul>
<li>Пользователи Портала сохраняют авторские права на создаваемый ими контент</li>
<li>
Политика обработки данных доступна по <LinkTopic text='ссылке' topic={HelpTopic.INFO_PRIVACY} />
@ -50,6 +53,7 @@ export function HelpMain() {
Данный сайт использует доменное имя и серверные мощности{' '}
<TextURL text='Центра Концепт' href={external_urls.concept} />
</li>
</ul>
<h2>Поддержка</h2>
<p>

View File

@ -11,6 +11,7 @@ export function HelpVersions() {
<p>После создания версии ее содержание изменить нельзя.</p>
<h2>Действия</h2>
<ul>
<li>
<IconShare size='1.25rem' className='inline-icon' /> Поделиться включает версию в ссылку
</li>
@ -25,6 +26,7 @@ export function HelpVersions() {
<li>
<IconVersions size='1.25rem' className='inline-icon' /> Редактировать атрибуты версий
</li>
</ul>
</div>
);
}

View File

@ -17,8 +17,8 @@ export function HelpContributors() {
В списке указан год окончания работ над соответствующим результатом или год публикации соответствующей статьи.
Курсивом выделены комментарии к значимости указанного результата.
</p>
<p>Любые добавления и поправки приветствуются.</p>
<div className='flex flex-col gap-3'>
<p>Добавления и корректировки приветствуются.</p>
<ul className='flex flex-col gap-3'>
<li>1973 Никаноров С.П., Персиц Д.Б. Формальное проектирование целостных СОУ.</li>
<li>
19751981 Никаноров С.П., Персиц Д.Б., Айзенштат А.В., Закс Б.А. Экспериментальная система пакетов прикладных
@ -48,18 +48,18 @@ export function HelpContributors() {
решений.
</li>
<li>
1989 Кучкаров З.А., Остапов А.В. Методические вопросы концептуализации предметных областей,{' '}
1989 Остапов А.В., Кучкаров З.А. Методические вопросы концептуализации предметных областей,{' '}
<i>
как пример одной из работ Остапова, значительно расширившего технику экспликации и практику применения
"бескванторных" выражений.
</i>
</li>
<li>
1990 Никитина Н.К., Постников В.В. Синтаксический анализатор текста рода структуры для МАКС,{' '}
1990 Постников В.В., Никитина Н.К. Синтаксический анализатор текста рода структуры для МАКС,{' '}
<i>являющийся первой попыткой реализовать автоматизированную проверку синтаксиса родов структур.</i>
</li>
<li>
1993 Костюк А.В., Никитина Н.К., Юдкин Ю.Ю. Программа визуализации М-графов, представляющих родовую структуру.
1993 Юдкин Ю.Ю., Костюк А.В., Никитина Н.К. Программа визуализации М-графов, представляющих родовую структуру.
</li>
<li>1993 Никитина Н.К., Чувашов Е.В. Система проектирования баз данных по их концептуальной модели.</li>
<li>
@ -72,11 +72,11 @@ export function HelpContributors() {
PROLOG-программ, формирующих предметные интерпретации родоструктурных экспликаций Инттеор.
</li>
<li>
1994 Кучкаров З.А., Ким В.Л. Разработка родоструктурных конструктов для библиотеки моделей и исследование
1994 Ким В.Л., Кучкаров З.А. Разработка родоструктурных конструктов для библиотеки моделей и исследование
возможностей их развития.
</li>
<li>
1994 Коваль А.Г., Воробей П.Н. Редактор Программного комплекса Экстеор 1.5,{' '}
1994 Воробей П.Н., Коваль А.Г. Редактор Программного комплекса Экстеор 1.5,{' '}
<i>упростивший механизм печати экспликаций и улучшивший синтаксический анализ формального выражения.</i>
</li>
<li>
@ -84,16 +84,17 @@ export function HelpContributors() {
родоструктурного синтеза операционализированных терминальных концептуальных моделей Экстеор 2,{' '}
<i>ставшая первой версией реализации родоструктурного аппарата на C++ под Windows.</i>
</li>
<li>
1996 Никаноров С.П., Никитина Н.К., Климишин В.В. Автоматизированная система "Библиотека концептуальных схем",{' '}
1996 Климишин В.В., Никаноров С.П., Никитина Н.К. Автоматизированная система "Библиотека концептуальных схем",{' '}
<i>впервые определившая паспорт концептуальной схемы.</i>
</li>
<li>
1997 Никитина Н.К., Юрьев О.И. Система поддержки процессов концептуального анализа и проектирования ПРОКСИМА
1997 Юрьев О.И., Никитина Н.К. Система поддержки процессов концептуального анализа и проектирования ПРОКСИМА
1.
</li>
<li>
1998 Никитина Н.К., Гараева Ю.Р. Синтаксический анализатор выражений на языке родоструктурной экспликации для
1998 Гараева Ю.Р., Никитина Н.К. Синтаксический анализатор выражений на языке родоструктурной экспликации для
ПРОКСИМА 1.
</li>
<li>
@ -101,12 +102,12 @@ export function HelpContributors() {
концептуального проектирования.
</li>
<li>
1999 Кучкаров З.А., Кононенко А.А. Программа преобразования родоструктурного синтеза операционализированных
1999 Кононенко А.А., Кучкаров З.А. Программа преобразования родоструктурного синтеза операционализированных
терминальных концептуальных моделей Экстеор 3,{' '}
<i>впервые включившая операционную схему синтеза (дерево синтеза).</i>
</li>
<li>
1999 Никитина Н.К., Ландин Н.А. Разработка автоматизированной подсистемы, реализующей операции отслоения и
1999 Ландин Н.А., Никитина Н.К. Разработка автоматизированной подсистемы, реализующей операции отслоения и
рассечения над концептуальными схемами.
</li>
<li>
@ -122,7 +123,7 @@ export function HelpContributors() {
</i>
</li>
<li>
2000 Кононенко А.А., Майоров В.А. Программа автоматизированной генерации структуры данных и их визуализации по
2000 Майоров В.А., Кононенко А.А. Программа автоматизированной генерации структуры данных и их визуализации по
концептуальной модели БДтеор,{' '}
<i>
определившая проблемы интерфейса наполнения концептуальной модели в сложных ступенях и предложившая
@ -136,11 +137,11 @@ export function HelpContributors() {
</li>
<li>2000 Ключников А.В. Эквивалентность теорий родов структур.</li>
<li>
2001 Кучкаров З.А., Никитин А.В. Исследование и построение типологии изменений теоретико-множественных
2001 Никитин А.В., Кучкаров З.А. Исследование и построение типологии изменений теоретико-множественных
интерпретаций класса декартового произведения.
</li>
<li>
2001 Кононенко А.А., Майоров В.А. Программа преобразования сети процедур из формата Оргтеор в формат BPWin
2001 Майоров В.А., Кононенко А.А. Программа преобразования сети процедур из формата Оргтеор в формат BPWin
(IDEF0).
</li>
<li>
@ -158,6 +159,7 @@ export function HelpContributors() {
2003 Юдкин Ю.Ю., Кудюкин Д.А. Разработка и испытание компьютерной программы, формирующей
теоретико-множественную интерпретацию терма частной родоструктурной теории.
</li>
<li>
2004 Кононенко А.А. Генерация кода на языке программирования C++ по тексту концептуальной схемы,
эксплицированной в родах структур.
@ -182,25 +184,25 @@ export function HelpContributors() {
2008 Пономарев И.Н. Об эквивалентной представимости рода структуры с помощью заданной типовой характеристики.
</li>
<li>
2010 Кононенко А.А., Грязнов А.Д. Исследование и построение транслятора концептуальной схемы в концептуальную
2010 Грязнов А.Д., Кононенко А.А. Исследование и построение транслятора концептуальной схемы в концептуальную
модель.
</li>
<li>2010 Никаноров С.П. Введение в аппарат ступеней.</li>
<li>
2012 Кононенко А.А., Елисов Д.Н. Использование механизма XSD-схем для хранения и операционализации
2012 Елисов Д.Н., Кононенко А.А. Использование механизма XSD-схем для хранения и операционализации
концептуальных схем и концептуальных моделей с помощью XML.
</li>
<li>
2013 Кононенко А.А., Борисов И.Р. Исследование, разработка и экспериментальная программная реализация операций
2013 Борисов И.Р., Кононенко А.А. Исследование, разработка и экспериментальная программная реализация операций
над концептуальными моделями,{' '}
<i>
впервые реализовавшая модуль прямого вычисления интерпретации формального выражения, встроенный в Экстеор
3.5.
</i>
</li>
<li>2013 Пономарев И.Н., Липатов А.А. Операции над родами структур и пример автоматизации их выполнения.</li>
<li>2013 Липатов А.А., Пономарев И.Н. Операции над родами структур и пример автоматизации их выполнения.</li>
<li>
2014 Борисов И.Р., Баширов Р.М. Исследования и программная реализации оптимальной структуры данных для
2014 Баширов Р.М., Борисов И.Р. Исследования и программная реализации оптимальной структуры данных для
вычисления интерпретации концептуальных схем.
</li>
<li>
@ -220,7 +222,7 @@ export function HelpContributors() {
</li>
<li>2015 Иванов А.Ю. Аппарат ступеней С.П. Никанорова и возможное развитие идей по его использованию.</li>
<li>
2016 Борисов И.Р., Баширов Р.М. Исследование области компьютерной лингвистики и разработка модулей
2016 Баширов Р.М., Борисов И.Р. Исследование области компьютерной лингвистики и разработка модулей
терминологического контроля в Экстеор 4 и Microsoft Office Word,{' '}
<i>
являющееся основой библиотеки <TextURL text='cctext' href={external_urls.git_cctext} />.
@ -239,25 +241,25 @@ export function HelpContributors() {
теории (на примере родственных отношений).
</li>
<li>
2017 Борисов И.Р., Мурадов А.К. Организация операций над системами понятий посредством графических
2017 Мурадов А.К., Борисов И.Р. Организация операций над системами понятий посредством графических
интерфейсов, <i>заложивший основу для технологии Концепт.Блоки и блока графического синтеза.</i>
</li>
<li>
2018 Борисов И.Р., Князев А.В. Изучение методов концептуальной расчистки, разметки текстов и разработка
2018 Князев А.В., Борисов И.Р. Изучение методов концептуальной расчистки, разметки текстов и разработка
программных средств их автоматизации,{' '}
<i> &mdash; диплом, сформировавший основу для технологий Концепт.Разметка и Концепт.Майнинг.</i>
</li>
<li>
2018 Никитин А.В., Болотин П.В. Исследование типологии изменения теоретико-множественной интерпретации класса
2018 Болотин П.В., Никитин А.В. Исследование типологии изменения теоретико-множественной интерпретации класса
множества подмножеств.
</li>
<li>
2019 Борисов И.Р., Широкова Л.Р. Исследование возможностей применения методов машинного обучения для решения
2019 Широкова Л.Р., Борисов И.Р. Исследование возможностей применения методов машинного обучения для решения
задач расчистки текстов. Разработка прототипа программного модуля, &mdash;{' '}
<i>первая попытка внедрения технологий ИИ в текстовый модуль.</i>
</li>
<li>
2020 Борисов И.Р., Пакулина Т.А. Исследование применения методов машинного обучения для выделения именованных
2020 Пакулина Т.А., Борисов И.Р. Исследование применения методов машинного обучения для выделения именованных
сущностей в текстах интервью. Экспериментальная разработка программного модуля расчистки текстов,{' '}
<i>ставшего расширением технологии Концепт.Расчистка.</i>
</li>
@ -266,12 +268,12 @@ export function HelpContributors() {
структур (рекурсивные и императивные выражения, фильтры, ASCII синтаксис).
</li>
<li>
2021 Борисов И.Р., Демешко А.Б. Исследование и разработка программного модуля формирования текстов функций на
2021 Демешко А.Б., Борисов И.Р. Исследование и разработка программного модуля формирования текстов функций на
основе концепта функциональная структура,{' '}
<i>дополнившего текстовый модуль возможностью работы с глагольными формами.</i>
</li>
<li>
2023 Борисов И.Р., Тулисов А.В. Разработка инструмента экспликации концептуальных схем в родоструктурной форме
2023 Тулисов А.В., Борисов И.Р. Разработка инструмента экспликации концептуальных схем в родоструктурной форме
через веб-интерфейс, &mdash; <i>разработка прототипа интерфейса КонцептПортал.</i>
</li>
<li>
@ -283,11 +285,16 @@ export function HelpContributors() {
</i>
</li>
<li>
2024 Борисов И.Р., Хаданович Б.А. Исследование механизмов проведения сквозных изменений в операционной схеме
2024 Хаданович Б.А., Борисов И.Р. Исследование механизмов проведения сквозных изменений в операционной схеме
синтеза. Разработка прототипа веб-интерфейса синтеза концептуальных схем.
<i> Прототип графического интерфейса для синтеза концептуальных схем.</i>
</li>
</div>
<li>
2024 Викентьев М.И., Борисов И.Р. Исследование использования современных web-интерфейсов для визуализации
отношений для применения в рамках концептуального синтеза.{' '}
<i> Визуализации смешанных представлений концептуальной схемы.</i>
</li>
</ul>
</div>
);
}

View File

@ -28,6 +28,7 @@ export function HelpRules() {
</p>
<h2>Ожидаемое поведение</h2>
<ul>
<li>взаимное уважением, поддержка в отношениях с участниками Портала.</li>
<li>
пожелания по доработке, найденные ошибки и иные предложения следует направлять по адресу email:{' '}
@ -50,6 +51,7 @@ export function HelpRules() {
осложнение создания (и/или поддержания) контента, созданного другими участниками.
</li>
<li>нарушение работоспособности Портала, в том числе путем использования уязвимостей и ошибок в коде.</li>
</ul>
</div>
);
}

View File

@ -19,7 +19,7 @@ export function HelpRSLangOperations() {
<h2>
<IconSortList size='1.25rem' className='inline-icon' /> Упорядочение
</h2>
<p>
<ul>
Упорядочение списка конституент по следующим правилам
<li>базисные и константные множества объявляются первыми</li>
<li>
@ -32,7 +32,7 @@ export function HelpRSLangOperations() {
<LinkTopic text='порожденные' topic={HelpTopic.CC_RELATIONS} /> конституенты следуют сразу за исходной
</li>
<li>максимальное сохранение исходного порядка при выполнении предыдущих правил</li>
</p>
</ul>
<h2>
<IconGenerateNames size='1.25rem' className='inline-icon' /> Порядковые имена

View File

@ -10,6 +10,7 @@ export function HelpRSLangTemplates() {
Источником шаблонов является <b>Банк выражений</b>, содержащий параметризованные понятия и утверждения,
сгруппированные по разделам
</p>
<ul>
<li>Сначала выбирается шаблон выражения (вкладка Шаблон)</li>
<li>
Далее для аргументов можно зафиксировать значения, выбрав из конституент текущей схемы или указав выражения
@ -21,6 +22,7 @@ export function HelpRSLangTemplates() {
<li>
Кнопка <b>Создать</b> инициирует добавление выбранной конституенты в схему
</li>
</ul>
</div>
);
}

View File

@ -2,7 +2,7 @@ export function HelpRSLangTypes() {
return (
<div>
<h1>Типизация</h1>
<p>
<ul>
Родоструктурное выражение <code>ξ</code> обладает типизацией (структурой), если выполнено ξH,
<br />
где <code>H</code> корректное выражение <b>ступени</b>, задаваемой следующими правилами:
@ -18,7 +18,7 @@ export function HelpRSLangTypes() {
<li>
<code>(H)</code> ступень, называемая <b>множеством</b>.
</li>
</p>
</ul>
<p>Пустое множество имеет типизацию (R0) множество с произвольной структурой элемента</p>
<p>
Для обобщения понятия типизация на логические и параметризованные выражения вводится ряд дополнительных

View File

@ -4,11 +4,15 @@ export function HelpFormulaTree() {
<h1>Дерево разбора выражения</h1>
<p>Дерево получено путем семантических преобразований дерева синтаксического разбора.</p>
<p>Оно отражает структуру грамматически корректного выражения языка родов структур.</p>
<ul>
<li>Порядок узлов в рамках одного уровня может отличаться от их порядка в выражении</li>
<li>При наведении курсора на узел в тексте выделяется соответствующий ему фрагмент</li>
<li>Текст в узле дерева соответствует элементу языка</li>
</ul>
<h2>Виды узлов</h2>
<ul>
<li>
<span className='bg-(--acc-bg-green)'>объявление идентификатора</span>
</li>
@ -27,6 +31,14 @@ export function HelpFormulaTree() {
<li>
<span className='bg-secondary'>составные выражения</span>
</li>
</ul>
<h2>Команды</h2>
<ul>
<li>
<kbd>Space</kbd> перемещение экрана
</li>
</ul>
</div>
);
}

View File

@ -31,6 +31,7 @@ export function HelpLibrary() {
<LinkTopic text='операционные схемы синтеза' topic={HelpTopic.CC_OSS} /> (ОСС).
</p>
<ul>
<li>
<span className='text-(--acc-fg-green)'>зеленым текстом</span> выделены ОСС
</li>
@ -63,8 +64,10 @@ export function HelpLibrary() {
<li>
<IconFolderTree size='1rem' className='inline-icon' /> переключение между Проводник и Поиск
</li>
</ul>
<h2>Режим: Проводник</h2>
<ul>
<li>
<IconFolderEdit size='1rem' className='inline-icon' /> переименовать выбранную
</li>
@ -95,6 +98,7 @@ export function HelpLibrary() {
<li>
<IconFolderOpened size='1rem' className='inline-icon icon-green' /> развернутая папка
</li>
</ul>
</div>
);
}

View File

@ -32,7 +32,8 @@ export function HelpOssGraph() {
<h1 className='sm:pr-24'>Граф синтеза</h1>
<div className='flex flex-col sm:flex-row'>
<div className='sm:w-56'>
<h1>Настройка графа</h1>
<h2>Настройка графа</h2>
<ul>
<li>
<IconReset className='inline-icon' /> Сбросить изменения
</li>
@ -64,12 +65,14 @@ export function HelpOssGraph() {
<li>
черта слева - КС <LinkTopic text='внешняя' topic={HelpTopic.CC_OSS} />
</li>
</ul>
</div>
<Divider vertical margins='mx-3 mt-3' className='hidden sm:block' />
<div className='sm:w-84'>
<h1>Изменение узлов</h1>
<h2>Изменение узлов</h2>
<ul>
<li>
<kbd>Клик</kbd> на операцию выделение
</li>
@ -91,14 +94,16 @@ export function HelpOssGraph() {
<li>
<IconDestroy className='inline-icon icon-red' /> <kbd>Delete</kbd> удалить выбранные
</li>
</ul>
</div>
</div>
<Divider margins='my-3' className='hidden sm:block' />
<Divider margins='my-2' className='hidden sm:block' />
<div className='flex flex-col-reverse mb-3 sm:flex-row'>
<div className='sm:w-56'>
<h1>Общие</h1>
<h2>Общие</h2>
<ul>
<li>
<IconSave className='inline-icon' /> Сохранить положения
</li>
@ -108,12 +113,14 @@ export function HelpOssGraph() {
<li>
<kbd>Shift</kbd> перемещение выделенных элементов в границах родителя
</li>
</ul>
</div>
<Divider vertical margins='mx-3' className='hidden sm:block' />
<div className='dense w-84'>
<h1>Контекстное меню</h1>
<h2>Контекстное меню</h2>
<ul>
<li>
<IconRSForm className='inline-icon icon-green' /> Статус связанной{' '}
<LinkTopic text='КС' topic={HelpTopic.CC_SYSTEM} />
@ -135,6 +142,7 @@ export function HelpOssGraph() {
<li>
<IconExecute className='inline-icon icon-green' /> Активировать операцию
</li>
</ul>
</div>
</div>
</div>

View File

@ -12,6 +12,7 @@ export function HelpRelocateCst() {
другую КС (целевую) в рамках одной <IconOSS size='1rem' className='inline-icon' />{' '}
<LinkTopic text='операционной схемы синтеза' topic={HelpTopic.CC_OSS} />.
</p>
<ul>
<li>
только для <IconPredecessor size='1rem' className='inline-icon' /> собственных конституент источника
</li>
@ -19,16 +20,21 @@ export function HelpRelocateCst() {
<IconMoveUp size='1rem' className='inline-icon' />
<IconMoveDown size='1rem' className='inline-icon' /> направление переноса - вверх или вниз по дереву синтеза
</li>
</ul>
<h2>Перенос вверх</h2>
<ul>
<li>выбранные конституенты становятся наследованными, а их копии добавляются в целевую КС</li>
<li>нельзя выбирать конституенты, зависящие от конституент других концептуальных схем</li>
</ul>
<h2>Перенос вниз</h2>
<ul>
<li>
выбранные конституенты становятся собственными конституентами целевой КС, удаляются из исходной КС и ее
наследников
</li>
</ul>
</div>
);
}

View File

@ -30,6 +30,7 @@ export function HelpRSCard() {
</p>
<h2>Управление</h2>
<ul>
<li>
<IconOSS className='inline-icon' /> переход к связанной <LinkTopic text='ОСС' topic={HelpTopic.CC_OSS} />
</li>
@ -57,6 +58,7 @@ export function HelpRSCard() {
<li>
<IconDestroy className='inline-icon icon-red' /> Удалить полностью удаляет схему из базы Портала
</li>
</ul>
</div>
);
}

View File

@ -27,7 +27,9 @@ export function HelpRSEditor() {
<div className='dense'>
<h1>Редактор конституенты</h1>
<div className='flex flex-col sm:flex-row sm:gap-3'>
<div className='flex flex-col'>
<div>
<h2>Команды</h2>
<ul>
<li>
<IconOSS className='inline-icon' /> переход к <LinkTopic text='ОСС' topic={HelpTopic.CC_OSS} />
</li>
@ -52,10 +54,12 @@ export function HelpRSEditor() {
<li>
<IconDestroy className='inline-icon icon-red' /> удалить
</li>
</ul>
</div>
<div className='flex flex-col'>
<div>
<h2>Список конституент</h2>
<ul>
<li>
<IconMoveDown className='inline-icon' />
<IconMoveUp className='inline-icon' /> <kbd>Alt + вверх/вниз</kbd>
@ -80,10 +84,12 @@ export function HelpRSEditor() {
<LinkTopic text='порожденные' topic={HelpTopic.CC_RELATIONS} /> текущей
</span>
</li>
</ul>
</div>
</div>
<h2>Формальное определение</h2>
<ul>
<li>
<IconStatusOK className='inline-icon' /> индикатор статуса определения сверху
</li>
@ -101,15 +107,19 @@ export function HelpRSEditor() {
<li>
<kbd>Ctrl + Пробел</kbd> вставка незанятого имени / замена проекции
</li>
</ul>
<h2>Термин и Текстовое определение</h2>
<ul>
<li>
<IconEdit className='inline-icon' /> редактирование <LinkTopic text='Имени' topic={HelpTopic.CC_CONSTITUENTA} />{' '}
/ <LinkTopic text='Термина' topic={HelpTopic.CC_CONSTITUENTA} />
<IconEdit className='inline-icon' /> редактирование{' '}
<LinkTopic text='Имени' topic={HelpTopic.CC_CONSTITUENTA} /> /{' '}
<LinkTopic text='Термина' topic={HelpTopic.CC_CONSTITUENTA} />
</li>
<li>
<kbd>Ctrl + Пробел</kbd> открывает редактирование отсылок
</li>
</ul>
</div>
);
}

View File

@ -30,7 +30,8 @@ export function HelpRSGraphTerm() {
<h1>Граф термов</h1>
<div className='flex flex-col sm:flex-row'>
<div className='sm:w-56'>
<h1>Настройка графа</h1>
<h2>Настройка графа</h2>
<ul>
<li>Цвет покраска узлов</li>
<li>
<IconText className='inline-icon' /> Отображение текста
@ -41,12 +42,14 @@ export function HelpRSGraphTerm() {
<li>
<IconRotate3D className='inline-icon' /> Вращение 3D
</li>
</ul>
</div>
<Divider vertical margins='mx-3 mt-3' className='hidden sm:block' />
<div className='sm:w-84'>
<h1>Изменение узлов</h1>
<h2>Изменение узлов</h2>
<ul>
<li>Клик на узел выделение</li>
<li>Левый клик выбор фокус-конституенты</li>
<li>
@ -61,6 +64,7 @@ export function HelpRSGraphTerm() {
<li>
<IconNewItem className='inline-icon icon-green' /> Новая со ссылками на выделенные
</li>
</ul>
</div>
</div>
@ -68,7 +72,11 @@ export function HelpRSGraphTerm() {
<div className='flex flex-col-reverse mb-3 sm:flex-row'>
<div className='sm:w-56'>
<h1>Общие</h1>
<h2>Общие</h2>
<ul>
<li>
<kbd>Space</kbd> перемещение экрана
</li>
<li>
<IconOSS className='inline-icon' /> переход к связанной <LinkTopic text='ОСС' topic={HelpTopic.CC_OSS} />
</li>
@ -85,12 +93,14 @@ export function HelpRSGraphTerm() {
<IconTypeGraph className='inline-icon' /> Открыть{' '}
<LinkTopic text='граф ступеней' topic={HelpTopic.UI_TYPE_GRAPH} />
</li>
</ul>
</div>
<Divider vertical margins='mx-3' className='hidden sm:block' />
<div className='dense w-84'>
<h1>Выделение</h1>
<h2>Выделение</h2>
<ul>
<li>
<IconGraphCollapse className='inline-icon' /> все влияющие
</li>
@ -113,6 +123,7 @@ export function HelpRSGraphTerm() {
<IconPredecessor className='inline-icon' /> выделить{' '}
<LinkTopic text='собственные' topic={HelpTopic.CC_PROPAGATION} />
</li>
</ul>
</div>
</div>
</div>

View File

@ -19,6 +19,7 @@ export function HelpRSList() {
return (
<div className='dense'>
<h1>Список конституент</h1>
<ul>
<li>
<IconAlias className='inline-icon' />
Конституенты обладают уникальным <LinkTopic text='Именем' topic={HelpTopic.CC_CONSTITUENTA} />
@ -27,8 +28,10 @@ export function HelpRSList() {
<li>
пунктиром отображаются <LinkTopic text='наследованные' topic={HelpTopic.CC_OSS} /> конституенты
</li>
</ul>
<h2>Управление списком</h2>
<ul>
<li>
<IconOSS className='inline-icon' /> переход к связанной <LinkTopic text='ОСС' topic={HelpTopic.CC_OSS} />
</li>
@ -62,6 +65,7 @@ export function HelpRSList() {
<li>
<IconDestroy className='inline-icon icon-red' /> удаление выделенных: <kbd>Delete</kbd>
</li>
</ul>
<Divider margins='my-2' />

View File

@ -30,6 +30,7 @@ export function HelpRSMenu() {
</p>
<h2>Вкладки</h2>
<ul>
<li>
<LinkTopic text='Карточка' topic={HelpTopic.UI_RS_CARD} /> редактирование атрибутов схемы и версии
</li>
@ -41,12 +42,15 @@ export function HelpRSMenu() {
<LinkTopic text='Конституенты' topic={HelpTopic.CC_CONSTITUENTA} />
</li>
<li>
<LinkTopic text='Граф термов' topic={HelpTopic.UI_GRAPH_TERM} /> графическое представление связей конституент
<LinkTopic text='Граф термов' topic={HelpTopic.UI_GRAPH_TERM} /> графическое представление связей
конституент
</li>
</ul>
<div className='flex my-3'>
<div>
<h2>Меню схемы</h2>
<ul>
<li>
<IconMenu size='1.25rem' className='inline-icon' /> Меню схемы выпадающее меню с общими функциями
</li>
@ -71,18 +75,21 @@ export function HelpRSMenu() {
<li>
<IconDestroy className='inline-icon icon-red' /> Удалить полностью удаляет схему из базы Портала
</li>
</ul>
</div>
<Divider vertical margins='mx-3' />
<div className='w-72'>
<h2>Режимы работы</h2>
<ul>
<li>
<IconAlert size='1.25rem' className='inline-icon icon-red' /> работа в анонимном режиме. Переход на страницу
логина
<IconAlert size='1.25rem' className='inline-icon icon-red' /> работа в анонимном режиме. Переход на
страницу логина
</li>
<li>
<IconArchive size='1.25rem' className='inline-icon' /> просмотр архивной версии. Переход к актуальной версии
<IconArchive size='1.25rem' className='inline-icon' /> просмотр архивной версии. Переход к актуальной
версии
</li>
<li>
<IconReader size='1.25rem' className='inline-icon' /> режим Читатель
@ -96,6 +103,7 @@ export function HelpRSMenu() {
<li>
<IconAdmin size='1.25rem' className='inline-icon' /> режим Администратор
</li>
</ul>
</div>
</div>

View File

@ -5,6 +5,7 @@ export function HelpSubstitutions() {
<p>Пара отождествлений, обозначает замену вхождений одной конституенты на другую.</p>
<p>
Таблица отождествлений накладывает следующие ограничения:
<ul>
<li>конституента может быть удаляемой только в одном отождествлении</li>
<li>удаляемые конституенты не могут быть замещающими в отождествлениях</li>
<li>базисные множества могут замещать только другие базисные множества</li>
@ -15,6 +16,7 @@ export function HelpSubstitutions() {
</li>
<li>логические выражения могут замещать только другие логические выражения</li>
<li>при отождествлении параметризованных конституент количество и типизации аргументов должно совпадать</li>
</ul>
</p>
</div>
);

View File

@ -7,17 +7,23 @@ export function HelpTypeGraph() {
<h1>Граф ступеней</h1>
<p>
Граф связей между ступенями, используемыми в данном выражении или{' '}
<LinkTopic text='КС' topic={HelpTopic.CC_OSS} />. Исторически отображался в форме мультиграфа (М-граф). В
Портале кратные ребра представлены перечислением индексов компонент произведения.
<LinkTopic text='КС' topic={HelpTopic.CC_OSS} />.<br />
Исторически отображался в форме мультиграфа (М-граф).
<br />
Кратные ребра представлены перечислением индексов компонент произведения.
</p>
<ul>
<li>ребра без надписей означают взятие булеана</li>
<li>цифры на ребрах означают номера компонент декартова произведения</li>
<li>цифры на узлах означают количество конституент в данной ступени</li>
<li>основаниями дерева являются ступени базисных, константных множеств</li>
<li>ступень терм-функции - произведение ступеней результата и аргументов</li>
<li>ступень предикат-функции - произведение ступеней аргументов</li>
</ul>
<h2>Виды узлов</h2>
<h2>Цвета узлов</h2>
<ul>
<li>
<span className='bg-secondary'>ступень-основание</span>
</li>
@ -27,6 +33,14 @@ export function HelpTypeGraph() {
<li>
<span className='bg-accent-orange'>ступень декартова произведения</span>
</li>
</ul>
<h2>Команды</h2>
<ul>
<li>
<kbd>Space</kbd> перемещение экрана
</li>
</ul>
</div>
);
}

View File

@ -10,12 +10,13 @@ export const useRenameLocation = () => {
const mutation = useMutation({
mutationKey: [libraryApi.baseKey, 'rename-location'],
mutationFn: libraryApi.renameLocation,
onSuccess: () =>
Promise.allSettled([
onSuccess: async () => {
await Promise.allSettled([
client.invalidateQueries({ queryKey: [KEYS.library] }),
client.invalidateQueries({ queryKey: [KEYS.rsform] }),
client.invalidateQueries({ queryKey: [KEYS.oss] })
]),
]);
},
onError: () => client.invalidateQueries()
});
return {

View File

@ -16,12 +16,12 @@ export const useSetAccessPolicy = () => {
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, libraryApi.baseKey, 'set-location'],
mutationFn: libraryApi.setAccessPolicy,
onSuccess: (_, variables) => {
onSuccess: async (_, variables) => {
const ossKey = KEYS.composite.ossItem({ itemID: variables.itemID });
const ossData: IOperationSchemaDTO | undefined = client.getQueryData(ossKey);
if (ossData) {
client.setQueryData(ossKey, { ...ossData, access_policy: variables.policy });
return Promise.allSettled([
await Promise.allSettled([
client.invalidateQueries({ queryKey: KEYS.composite.libraryList }),
...ossData.operations
.map(item => {
@ -33,6 +33,7 @@ export const useSetAccessPolicy = () => {
})
.filter(item => !!item)
]);
return;
}
const rsKey = KEYS.composite.rsItem({ itemID: variables.itemID });

View File

@ -12,12 +12,12 @@ export const useSetEditors = () => {
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, libraryApi.baseKey, 'set-location'],
mutationFn: libraryApi.setEditors,
onSuccess: (_, variables) => {
onSuccess: async (_, variables) => {
const ossKey = KEYS.composite.ossItem({ itemID: variables.itemID });
const ossData: IOperationSchemaDTO | undefined = client.getQueryData(ossKey);
if (ossData) {
client.setQueryData(ossKey, { ...ossData, editors: variables.editors });
return Promise.allSettled(
await Promise.allSettled(
ossData.operations
.map(item => {
if (!item.result) {
@ -28,6 +28,7 @@ export const useSetEditors = () => {
})
.filter(item => !!item)
);
return;
}
const rsKey = KEYS.composite.rsItem({ itemID: variables.itemID });

View File

@ -16,12 +16,12 @@ export const useSetLocation = () => {
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, libraryApi.baseKey, 'set-location'],
mutationFn: libraryApi.setLocation,
onSuccess: (_, variables) => {
onSuccess: async (_, variables) => {
const ossKey = KEYS.composite.ossItem({ itemID: variables.itemID });
const ossData: IOperationSchemaDTO | undefined = client.getQueryData(ossKey);
if (ossData) {
client.setQueryData(ossKey, { ...ossData, location: variables.location });
return Promise.allSettled([
await Promise.allSettled([
client.invalidateQueries({ queryKey: libraryApi.libraryListKey }),
...ossData.operations
.map(item => {
@ -33,6 +33,7 @@ export const useSetLocation = () => {
})
.filter(item => !!item)
]);
return;
}
const rsKey = KEYS.composite.rsItem({ itemID: variables.itemID });

View File

@ -16,12 +16,12 @@ export const useSetOwner = () => {
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, libraryApi.baseKey, 'set-owner'],
mutationFn: libraryApi.setOwner,
onSuccess: (_, variables) => {
onSuccess: async (_, variables) => {
const ossKey = KEYS.composite.ossItem({ itemID: variables.itemID });
const ossData: IOperationSchemaDTO | undefined = client.getQueryData(ossKey);
if (ossData) {
client.setQueryData(ossKey, { ...ossData, owner: variables.owner });
return Promise.allSettled([
await Promise.allSettled([
client.invalidateQueries({ queryKey: libraryApi.libraryListKey }),
...ossData.operations
.map(item => {

View File

@ -16,7 +16,7 @@ export const useUpdateItem = () => {
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, libraryApi.baseKey, 'update-item'],
mutationFn: libraryApi.updateItem,
onSuccess: (data: ILibraryItem) => {
onSuccess: async (data: ILibraryItem) => {
const itemKey =
data.item_type === LibraryItemType.RSFORM
? KEYS.composite.rsItem({ itemID: data.id })
@ -30,7 +30,7 @@ export const useUpdateItem = () => {
if (data.item_type === LibraryItemType.RSFORM) {
const schema: IRSFormDTO | undefined = client.getQueryData(itemKey);
if (schema) {
return Promise.allSettled(
await Promise.allSettled(
schema.oss.map(item => client.invalidateQueries({ queryKey: KEYS.composite.ossItem({ itemID: item.id }) }))
);
}

View File

@ -20,6 +20,7 @@ import { cn } from '@/components/utils';
import { prefixes } from '@/utils/constants';
import { tripleToggleColor } from '@/utils/utils';
import { useLibrarySuspense } from '../../backend/use-library';
import { IconItemVisibility } from '../../components/icon-item-visibility';
import { IconLocationHead } from '../../components/icon-location-head';
import { describeLocationHead, labelLocationHead } from '../../labels';
@ -33,6 +34,7 @@ interface ToolbarSearchProps {
}
export function ToolbarSearch({ className, total, filtered }: ToolbarSearchProps) {
const { items } = useLibrarySuspense();
const userMenu = useDropdown();
const headMenu = useDropdown();
@ -58,6 +60,10 @@ export function ToolbarSearch({ className, total, filtered }: ToolbarSearchProps
const userActive = isOwned !== null || isEditor !== null || filterUser !== null;
function filterNonEmptyUsers(userID: number): boolean {
return items.some(item => item.owner === userID);
}
function handleChange(newValue: LocationHead | null) {
headMenu.hide();
setHead(newValue);
@ -114,6 +120,7 @@ export function ToolbarSearch({ className, total, filtered }: ToolbarSearchProps
placeholder='Выберите владельца'
noBorder
className='min-w-60 mx-1 mb-1'
filter={filterNonEmptyUsers}
value={filterUser}
onChange={setFilterUser}
/>

View File

@ -7,7 +7,15 @@ import { type ILibraryItem } from '@/features/library';
import { Graph } from '@/models/graph';
import { type RO } from '@/utils/meta';
import { type IBlock, type IOperation, type IOperationSchema, type IOperationSchemaStats } from '../models/oss';
import {
type IBlock,
type IOperation,
type IOperationSchema,
type IOperationSchemaStats,
type IOssItem,
NodeType
} from '../models/oss';
import { constructNodeID } from '../models/oss-api';
import { BLOCK_NODE_MIN_HEIGHT, BLOCK_NODE_MIN_WIDTH } from '../pages/oss-page/editor-oss-graph/graph/block-node';
import { type IOperationSchemaDTO, OperationType } from './types';
@ -16,8 +24,9 @@ import { type IOperationSchemaDTO, OperationType } from './types';
export class OssLoader {
private oss: IOperationSchema;
private graph: Graph = new Graph();
private hierarchy: Graph = new Graph();
private hierarchy: Graph<string> = new Graph<string>();
private operationByID = new Map<number, IOperation>();
private itemByNodeID = new Map<string, IOssItem>();
private blockByID = new Map<number, IBlock>();
private schemaIDs: number[] = [];
private items: RO<ILibraryItem[]>;
@ -37,6 +46,7 @@ export class OssLoader {
result.operationByID = this.operationByID;
result.blockByID = this.blockByID;
result.itemByNodeID = this.itemByNodeID;
result.graph = this.graph;
result.hierarchy = this.hierarchy;
result.schemas = this.schemaIDs;
@ -46,18 +56,24 @@ export class OssLoader {
private prepareLookups() {
this.oss.operations.forEach(operation => {
operation.nodeID = constructNodeID(NodeType.OPERATION, operation.id);
operation.nodeType = NodeType.OPERATION;
this.itemByNodeID.set(operation.nodeID, operation);
this.operationByID.set(operation.id, operation);
this.graph.addNode(operation.id);
this.hierarchy.addNode(operation.id);
this.hierarchy.addNode(operation.nodeID);
if (operation.parent) {
this.hierarchy.addEdge(-operation.parent, operation.id);
this.hierarchy.addEdge(constructNodeID(NodeType.BLOCK, operation.parent), operation.nodeID);
}
});
this.oss.blocks.forEach(block => {
block.nodeID = constructNodeID(NodeType.BLOCK, block.id);
block.nodeType = NodeType.BLOCK;
this.itemByNodeID.set(block.nodeID, block);
this.blockByID.set(block.id, block);
this.hierarchy.addNode(-block.id);
this.hierarchy.addNode(block.nodeID);
if (block.parent) {
this.hierarchy.addEdge(-block.parent, -block.id);
this.hierarchy.addEdge(constructNodeID(NodeType.BLOCK, block.parent), block.nodeID);
}
});
}

View File

@ -72,6 +72,12 @@ export type IRelocateConstituentsDTO = z.infer<typeof schemaRelocateConstituents
/** Represents {@link IConstituenta} reference. */
export type IConstituentaReference = z.infer<typeof schemaConstituentaReference>;
/** Represents {@link IOperation} position. */
export type IOperationPosition = z.infer<typeof schemaOperationPosition>;
/** Represents {@link IBlock} position. */
export type IBlockPosition = z.infer<typeof schemaBlockPosition>;
// ====== Schemas ======
export const schemaOperationType = z.enum(Object.values(OperationType) as [OperationType, ...OperationType[]]);

View File

@ -10,9 +10,9 @@ export const useCreateInput = () => {
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'create-input'],
mutationFn: ossApi.createInput,
onSuccess: data => {
onSuccess: async data => {
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.oss.id }).queryKey, data.oss);
return Promise.allSettled([
await Promise.allSettled([
client.invalidateQueries({ queryKey: KEYS.composite.libraryList }),
client.invalidateQueries({ queryKey: [KEYS.rsform] })
]);

View File

@ -10,9 +10,9 @@ export const useDeleteBlock = () => {
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'delete-block'],
mutationFn: ossApi.deleteBlock,
onSuccess: data => {
onSuccess: async data => {
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
return Promise.allSettled([
await Promise.allSettled([
client.invalidateQueries({ queryKey: KEYS.composite.libraryList }),
client.invalidateQueries({ queryKey: [KEYS.rsform] })
]);

View File

@ -10,9 +10,9 @@ export const useDeleteOperation = () => {
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'delete-operation'],
mutationFn: ossApi.deleteOperation,
onSuccess: data => {
onSuccess: async data => {
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
return Promise.allSettled([
await Promise.allSettled([
client.invalidateQueries({ queryKey: KEYS.composite.libraryList }),
client.invalidateQueries({ queryKey: [KEYS.rsform] })
]);

View File

@ -10,9 +10,9 @@ export const useExecuteOperation = () => {
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'execute-operation'],
mutationFn: ossApi.executeOperation,
onSuccess: data => {
onSuccess: async data => {
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
return Promise.allSettled([
await Promise.allSettled([
client.invalidateQueries({ queryKey: KEYS.composite.libraryList }),
client.invalidateQueries({ queryKey: [KEYS.rsform] })
]);

View File

@ -10,9 +10,9 @@ export const useMoveItems = () => {
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'move-items'],
mutationFn: ossApi.moveItems,
onSuccess: data => {
onSuccess: async data => {
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
return Promise.allSettled([
await Promise.allSettled([
client.invalidateQueries({ queryKey: KEYS.composite.libraryList }),
client.invalidateQueries({ queryKey: [KEYS.rsform] })
]);

View File

@ -10,9 +10,9 @@ export const useRelocateConstituents = () => {
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'relocate-constituents'],
mutationFn: ossApi.relocateConstituents,
onSuccess: data => {
onSuccess: async data => {
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
return Promise.allSettled([
await Promise.allSettled([
client.invalidateQueries({ queryKey: KEYS.composite.libraryList }),
client.invalidateQueries({ queryKey: [KEYS.rsform] })
]);

View File

@ -10,9 +10,9 @@ export const useUpdateInput = () => {
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'update-input'],
mutationFn: ossApi.updateInput,
onSuccess: data => {
onSuccess: async data => {
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
return Promise.allSettled([
await Promise.allSettled([
client.invalidateQueries({ queryKey: KEYS.composite.libraryList }),
client.invalidateQueries({ queryKey: [KEYS.rsform] })
]);

View File

@ -9,24 +9,22 @@ import { ComboBox } from '@/components/input/combo-box';
import { type Styling } from '@/components/props';
import { cn } from '@/components/utils';
import { NoData } from '@/components/view';
import { type RO } from '@/utils/meta';
import { labelOssItem } from '../labels';
import { type IOperationSchema, type IOssItem } from '../models/oss';
import { getItemID, isOperation } from '../models/oss-api';
import { type IOperationSchema, type IOssItem, NodeType } from '../models/oss';
const SELECTION_CLEAR_TIMEOUT = 1000;
interface PickMultiOperationProps extends Styling {
value: number[];
onChange: (newValue: number[]) => void;
interface PickContentsProps extends Styling {
value: IOssItem[];
onChange: (newValue: IOssItem[]) => void;
schema: IOperationSchema;
rows?: number;
exclude?: number[];
exclude?: IOssItem[];
disallowBlocks?: boolean;
}
const columnHelper = createColumnHelper<RO<IOssItem>>();
const columnHelper = createColumnHelper<IOssItem>();
export function PickContents({
rows,
@ -37,29 +35,26 @@ export function PickContents({
onChange,
className,
...restProps
}: PickMultiOperationProps) {
const selectedItems = value
.map(itemID => (itemID > 0 ? schema.operationByID.get(itemID) : schema.blockByID.get(-itemID)))
.filter(item => item !== undefined);
const [lastSelected, setLastSelected] = useState<RO<IOssItem> | null>(null);
const items = [
...(disallowBlocks ? [] : schema.blocks.filter(item => !value.includes(-item.id) && !exclude?.includes(-item.id))),
...schema.operations.filter(item => !value.includes(item.id) && !exclude?.includes(item.id))
}: PickContentsProps) {
const [lastSelected, setLastSelected] = useState<IOssItem | null>(null);
const items: IOssItem[] = [
...(disallowBlocks ? [] : schema.blocks.filter(item => !value.includes(item) && !exclude?.includes(item))),
...schema.operations.filter(item => !value.includes(item) && !exclude?.includes(item))
];
function handleDelete(target: number) {
function handleDelete(target: IOssItem) {
onChange(value.filter(item => item !== target));
}
function handleSelect(target: RO<IOssItem> | null) {
function handleSelect(target: IOssItem | null) {
if (target) {
setLastSelected(target);
onChange([...value, getItemID(target)]);
onChange([...value, target]);
setTimeout(() => setLastSelected(null), SELECTION_CLEAR_TIMEOUT);
}
}
function handleMoveUp(target: number) {
function handleMoveUp(target: IOssItem) {
const index = value.indexOf(target);
if (index > 0) {
const newSelected = [...value];
@ -69,7 +64,7 @@ export function PickContents({
}
}
function handleMoveDown(target: number) {
function handleMoveDown(target: IOssItem) {
const index = value.indexOf(target);
if (index < value.length - 1) {
const newSelected = [...value];
@ -80,13 +75,13 @@ export function PickContents({
}
const columns = [
columnHelper.accessor(item => isOperation(item), {
columnHelper.accessor(item => item.nodeType === NodeType.OPERATION, {
id: 'type',
header: 'Тип',
size: 150,
minSize: 150,
maxSize: 150,
cell: props => <div>{isOperation(props.row.original) ? 'Операция' : 'Блок'}</div>
cell: props => <div>{props.getValue() ? 'Операция' : 'Блок'}</div>
}),
columnHelper.accessor('title', {
id: 'title',
@ -106,21 +101,21 @@ export function PickContents({
noHover
className='px-0'
icon={<IconRemove size='1rem' className='icon-red' />}
onClick={() => handleDelete(getItemID(props.row.original))}
onClick={() => handleDelete(props.row.original)}
/>
<MiniButton
title='Переместить выше'
noHover
className='px-0'
icon={<IconMoveUp size='1rem' className='icon-primary' />}
onClick={() => handleMoveUp(getItemID(props.row.original))}
onClick={() => handleMoveUp(props.row.original)}
/>
<MiniButton
title='Переместить ниже'
noHover
className='px-0'
icon={<IconMoveDown size='1rem' className='icon-primary' />}
onClick={() => handleMoveDown(getItemID(props.row.original))}
onClick={() => handleMoveDown(props.row.original)}
/>
</div>
)
@ -134,7 +129,7 @@ export function PickContents({
items={items}
value={lastSelected}
placeholder='Выберите операцию или блок'
idFunc={item => String(getItemID(item))}
idFunc={item => item.nodeID}
labelValueFunc={item => labelOssItem(item)}
labelOptionFunc={item => labelOssItem(item)}
onChange={handleSelect}
@ -145,7 +140,7 @@ export function PickContents({
rows={rows}
contentHeight='1.3rem'
className='cc-scroll-y text-sm select-none border-y rounded-b-md'
data={selectedItems}
data={value}
columns={columns}
headPosition='0rem'
noDataComponent={

View File

@ -1,8 +1,7 @@
import { Tooltip } from '@/components/container';
import { globalIDs } from '@/utils/constants';
import { type IBlock, type IOperation } from '../models/oss';
import { isOperation } from '../models/oss-api';
import { NodeType } from '../models/oss';
import { useOperationTooltipStore } from '../stores/operation-tooltip';
import { InfoBlock } from './info-block';
@ -10,7 +9,7 @@ import { InfoOperation } from './info-operation';
export function OperationTooltip() {
const hoverItem = useOperationTooltipStore(state => state.hoverItem);
const isOperationNode = isOperation(hoverItem);
const isOperationNode = hoverItem?.nodeType === NodeType.OPERATION;
return (
<Tooltip
@ -20,8 +19,8 @@ export function OperationTooltip() {
className='max-w-140 dense max-h-120! overflow-y-auto!'
hidden={!hoverItem}
>
{hoverItem && isOperationNode ? <InfoOperation operation={hoverItem as IOperation} /> : null}
{hoverItem && !isOperationNode ? <InfoBlock block={hoverItem as IBlock} /> : null}
{hoverItem && isOperationNode ? <InfoOperation operation={hoverItem} /> : null}
{hoverItem && !isOperationNode ? <InfoBlock block={hoverItem} /> : null}
</Tooltip>
);
}

View File

@ -12,6 +12,7 @@ import { useDialogsStore } from '@/stores/dialogs';
import { type ICreateBlockDTO, schemaCreateBlock } from '../../backend/types';
import { useCreateBlock } from '../../backend/use-create-block';
import { type IOssItem, NodeType } from '../../models/oss';
import { type LayoutManager } from '../../models/oss-layout-api';
import { BLOCK_NODE_MIN_HEIGHT, BLOCK_NODE_MIN_WIDTH } from '../../pages/oss-page/editor-oss-graph/graph/block-node';
@ -20,7 +21,7 @@ import { TabBlockChildren } from './tab-block-children';
export interface DlgCreateBlockProps {
manager: LayoutManager;
initialChildren: number[];
initialChildren: IOssItem[];
initialParent: number | null;
defaultX: number;
defaultY: number;
@ -52,8 +53,8 @@ export function DlgCreateBlock() {
position_y: defaultY,
width: BLOCK_NODE_MIN_WIDTH,
height: BLOCK_NODE_MIN_HEIGHT,
children_blocks: initialChildren.filter(id => id < 0).map(id => -id),
children_operations: initialChildren.filter(id => id > 0),
children_blocks: initialChildren.filter(item => item.nodeType === NodeType.BLOCK).map(item => item.id),
children_operations: initialChildren.filter(item => item.nodeType === NodeType.OPERATION).map(item => item.id),
layout: manager.layout
},
mode: 'onChange'
@ -65,11 +66,12 @@ export function DlgCreateBlock() {
const isValid = !!title && !manager.oss.blocks.some(block => block.title === title);
function onSubmit(data: ICreateBlockDTO) {
const rectangle = manager.calculateNewBlockPosition(data);
const rectangle = manager.newBlockPosition(data);
data.position_x = rectangle.x;
data.position_y = rectangle.y;
data.width = rectangle.width;
data.height = rectangle.height;
data.layout = manager.layout;
void createBlock({ itemID: manager.oss.id, data: data }).then(response => onCreate?.(response.new_block.id));
}

View File

@ -7,6 +7,8 @@ import { useDialogsStore } from '@/stores/dialogs';
import { type ICreateBlockDTO } from '../../backend/types';
import { SelectParent } from '../../components/select-parent';
import { NodeType } from '../../models/oss';
import { constructNodeID } from '../../models/oss-api';
import { type DlgCreateBlockProps } from './dlg-create-block';
@ -18,10 +20,8 @@ export function TabBlockCard() {
formState: { errors }
} = useFormContext<ICreateBlockDTO>();
const children_blocks = useWatch({ control, name: 'children_blocks' });
const all_children = [
...children_blocks,
...manager.oss.hierarchy.expandAllOutputs(children_blocks.filter(id => id < 0).map(id => -id)).map(id => -id)
];
const block_ids = children_blocks.map(id => constructNodeID(NodeType.BLOCK, id));
const all_children = [...block_ids, ...manager.oss.hierarchy.expandAllOutputs(block_ids)];
return (
<div className='cc-fade-in cc-column'>
@ -36,7 +36,7 @@ export function TabBlockCard() {
control={control}
render={({ field }) => (
<SelectParent
items={manager.oss.blocks.filter(block => !all_children.includes(block.id))}
items={manager.oss.blocks.filter(block => !all_children.includes(block.nodeID))}
value={field.value ? manager.oss.blockByID.get(field.value) ?? null : null}
placeholder='Блок содержания не выбран'
onChange={value => field.onChange(value ? value.id : null)}

View File

@ -6,6 +6,7 @@ import { useDialogsStore } from '@/stores/dialogs';
import { type ICreateBlockDTO } from '../../backend/types';
import { PickContents } from '../../components/pick-contents';
import { type IOssItem, NodeType } from '../../models/oss';
import { type DlgCreateBlockProps } from './dlg-create-block';
@ -15,19 +16,31 @@ export function TabBlockChildren() {
const parent = useWatch({ control, name: 'item_data.parent' });
const children_blocks = useWatch({ control, name: 'children_blocks' });
const children_operations = useWatch({ control, name: 'children_operations' });
const exclude = parent ? [-parent, ...manager.oss.hierarchy.expandAllInputs([-parent]).filter(id => id < 0)] : [];
const value = [...children_blocks.map(id => -id), ...children_operations];
const parentItem = parent ? manager.oss.blockByID.get(parent) : null;
const internalBlocks = parentItem
? manager.oss.hierarchy
.expandAllInputs([parentItem.nodeID])
.map(id => manager.oss.itemByNodeID.get(id))
.filter(item => item !== null && item?.nodeType === NodeType.BLOCK)
: [];
function handleChangeSelected(newValue: number[]) {
const exclude = parentItem ? [parentItem, ...internalBlocks] : [];
const value = [
...children_blocks.map(id => manager.oss.blockByID.get(id)!),
...children_operations.map(id => manager.oss.operationByID.get(id)!)
];
function handleChangeSelected(newValue: IOssItem[]) {
setValue(
'children_blocks',
newValue.filter(id => id < 0).map(id => -id),
newValue.filter(item => item.nodeType === NodeType.BLOCK).map(item => item.id),
{ shouldValidate: true }
);
setValue(
'children_operations',
newValue.filter(id => id > 0),
newValue.filter(item => item.nodeType === NodeType.OPERATION).map(item => item.id),
{ shouldValidate: true }
);
}

View File

@ -64,9 +64,10 @@ export function DlgCreateOperation() {
const isValid = !!alias && !manager.oss.operations.some(operation => operation.alias === alias);
function onSubmit(data: ICreateOperationDTO) {
const target = manager.calculateNewOperationPosition(data);
const target = manager.newOperationPosition(data);
data.position_x = target.x;
data.position_y = target.y;
data.layout = manager.layout;
void createOperation({ itemID: manager.oss.id, data: data }).then(response =>
onCreate?.(response.new_operation.id)
);

View File

@ -42,6 +42,10 @@ export function DlgEditBlock() {
});
function onSubmit(data: IUpdateBlockDTO) {
if (data.item_data.parent !== target.parent) {
manager.onBlockChangeParent(data.target, data.item_data.parent);
data.layout = manager.layout;
}
return updateBlock({ itemID: manager.oss.id, data });
}

View File

@ -58,9 +58,10 @@ export function DlgEditOperation() {
const [activeTab, setActiveTab] = useState<TabID>(TabID.CARD);
function onSubmit(data: IUpdateOperationDTO) {
// if (data.item_data.parent !== target.parent) {
// data.layout = updateLayoutOnOperationChange(data.target, data.item_data.parent, data.layout);
// }
if (data.item_data.parent !== target.parent) {
manager.onOperationChangeParent(data.target, data.item_data.parent);
data.layout = manager.layout;
}
return updateOperation({ itemID: manager.oss.id, data });
}

View File

@ -5,9 +5,9 @@ import {
type IOperation,
type IOssItem,
type ISubstitutionErrorDescription,
NodeType,
SubstitutionErrorType
} from './models/oss';
import { isOperation } from './models/oss-api';
/** Retrieves label for {@link OperationType}. */
export function labelOperationType(itemType: OperationType): string {
@ -58,7 +58,7 @@ export function describeSubstitutionError(error: RO<ISubstitutionErrorDescriptio
/** Retrieves label for {@link IOssItem}. */
export function labelOssItem(item: RO<IOssItem>): string {
if (isOperation(item)) {
if (item.nodeType === NodeType.OPERATION) {
return `${(item as IOperation).alias}: ${item.title}`;
} else {
return `Блок: ${item.title}`;

View File

@ -20,23 +20,16 @@ import {
} from '@/features/rsform/models/rslang-api';
import { infoMsg } from '@/utils/labels';
import { type RO } from '@/utils/meta';
import { Graph } from '../../../models/graph';
import { describeSubstitutionError } from '../labels';
import { type IOperationSchema, type IOssItem, SubstitutionErrorType } from './oss';
import { type IOperationSchema, NodeType, SubstitutionErrorType } from './oss';
const STARTING_SUB_INDEX = 900; // max semantic index for starting substitution
/** Checks if element is {@link IOperation} or {@link IBlock}. */
export function isOperation(item: RO<IOssItem> | null): boolean {
return !!item && 'arguments' in item;
}
/** Extract contiguous ID of {@link IOperation} or {@link IBlock}. */
export function getItemID(item: RO<IOssItem>): number {
return isOperation(item) ? item.id : -item.id;
export function constructNodeID(type: NodeType, itemID: number): string {
return type === NodeType.OPERATION ? 'o' + String(itemID) : 'b' + String(itemID);
}
/** Sorts library items relevant for the specified {@link IOperationSchema}. */

View File

@ -1,12 +1,16 @@
import { type ICreateBlockDTO, type ICreateOperationDTO, type IOssLayout } from '../backend/types';
import {
type IBlockPosition,
type ICreateBlockDTO,
type ICreateOperationDTO,
type IOperationPosition,
type IOssLayout
} from '../backend/types';
import { type IOperationSchema } from './oss';
import { type Position2D, type Rectangle2D } from './oss-layout';
export const GRID_SIZE = 10; // pixels - size of OSS grid
const MIN_DISTANCE = 2 * GRID_SIZE; // pixels - minimum distance between nodes
const DISTANCE_X = 180; // pixels - insert x-distance between node centers
const DISTANCE_Y = 100; // pixels - insert y-distance between node centers
const OPERATION_NODE_WIDTH = 150;
const OPERATION_NODE_HEIGHT = 40;
@ -26,93 +30,236 @@ export class LayoutManager {
}
/** Calculate insert position for a new {@link IOperation} */
calculateNewOperationPosition(data: ICreateOperationDTO): Position2D {
// TODO: check parent node
const result = { x: data.position_x, y: data.position_y };
newOperationPosition(data: ICreateOperationDTO): Position2D {
let result = { x: data.position_x, y: data.position_y };
const operations = this.layout.operations;
const parentNode = this.layout.blocks.find(pos => pos.id === data.item_data.parent);
if (operations.length === 0) {
return result;
}
if (data.arguments.length === 0) {
let inputsPositions = operations.filter(pos =>
this.oss.operations.find(operation => operation.arguments.length === 0 && operation.id === pos.id)
);
if (inputsPositions.length === 0) {
inputsPositions = operations;
}
const maxX = Math.max(...inputsPositions.map(node => node.x));
const minY = Math.min(...inputsPositions.map(node => node.y));
result.x = maxX + DISTANCE_X;
result.y = minY;
if (data.arguments.length !== 0) {
result = calculatePositionFromArgs(data.arguments, operations);
} else if (parentNode) {
result.x = parentNode.x + MIN_DISTANCE;
result.y = parentNode.y + MIN_DISTANCE;
} else {
const argNodes = operations.filter(pos => data.arguments.includes(pos.id));
const maxY = Math.max(...argNodes.map(node => node.y));
const minX = Math.min(...argNodes.map(node => node.x));
const maxX = Math.max(...argNodes.map(node => node.x));
result.x = Math.ceil((maxX + minX) / 2 / GRID_SIZE) * GRID_SIZE;
result.y = maxY + DISTANCE_Y;
result = this.calculatePositionForFreeOperation(result);
}
let flagIntersect = false;
do {
flagIntersect = operations.some(
position => Math.abs(position.x - result.x) < MIN_DISTANCE && Math.abs(position.y - result.y) < MIN_DISTANCE
result = preventOverlap(
{ ...result, width: OPERATION_NODE_WIDTH, height: OPERATION_NODE_HEIGHT },
operations.map(node => ({ ...node, width: OPERATION_NODE_WIDTH, height: OPERATION_NODE_HEIGHT }))
);
if (flagIntersect) {
result.x += MIN_DISTANCE;
result.y += MIN_DISTANCE;
if (parentNode) {
const borderX = result.x + OPERATION_NODE_WIDTH + MIN_DISTANCE;
const borderY = result.y + OPERATION_NODE_HEIGHT + MIN_DISTANCE;
if (borderX > parentNode.x + parentNode.width) {
parentNode.width = borderX - parentNode.x;
}
} while (flagIntersect);
return result;
if (borderY > parentNode.y + parentNode.height) {
parentNode.height = borderY - parentNode.y;
}
// TODO: trigger cascading updates
}
return { x: result.x, y: result.y };
}
/** Calculate insert position for a new {@link IBlock} */
calculateNewBlockPosition(data: ICreateBlockDTO): Rectangle2D {
newBlockPosition(data: ICreateBlockDTO): Rectangle2D {
const block_nodes = data.children_blocks
.map(id => this.layout.blocks.find(block => block.id === id))
.filter(node => !!node);
const operation_nodes = data.children_operations
.map(id => this.layout.operations.find(operation => operation.id === id))
.filter(node => !!node);
const parentNode = this.layout.blocks.find(pos => pos.id === data.item_data.parent);
if (block_nodes.length === 0 && operation_nodes.length === 0) {
return { x: data.position_x, y: data.position_y, width: data.width, height: data.height };
let result: Rectangle2D = { x: data.position_x, y: data.position_y, width: data.width, height: data.height };
if (block_nodes.length !== 0 || operation_nodes.length !== 0) {
result = calculatePositionFromChildren(
{ x: data.position_x, y: data.position_y, width: data.width, height: data.height },
operation_nodes,
block_nodes
);
} else if (parentNode) {
result = {
x: parentNode.x + MIN_DISTANCE,
y: parentNode.y + MIN_DISTANCE,
width: data.width,
height: data.height
};
} else {
result = this.calculatePositionForFreeBlock(result);
}
if (block_nodes.length === 0 && operation_nodes.length === 0) {
if (parentNode) {
const siblings = this.oss.blocks.filter(block => block.parent === parentNode.id).map(block => block.id);
if (siblings.length > 0) {
result = preventOverlap(
result,
this.layout.blocks.filter(block => siblings.includes(block.id))
);
}
} else {
const rootBlocks = this.oss.blocks.filter(block => block.parent === null).map(block => block.id);
if (rootBlocks.length > 0) {
result = preventOverlap(
result,
this.layout.blocks.filter(block => rootBlocks.includes(block.id))
);
}
}
}
if (parentNode) {
const borderX = result.x + result.width + MIN_DISTANCE;
const borderY = result.y + result.height + MIN_DISTANCE;
if (borderX > parentNode.x + parentNode.width) {
parentNode.width = borderX - parentNode.x;
}
if (borderY > parentNode.y + parentNode.height) {
parentNode.height = borderY - parentNode.y;
}
// TODO: trigger cascading updates
}
return result;
}
/** Update layout when parent changes */
onOperationChangeParent(targetID: number, newParent: number | null) {
console.error('not implemented', targetID, newParent);
}
/** Update layout when parent changes */
onBlockChangeParent(targetID: number, newParent: number | null) {
console.error('not implemented', targetID, newParent);
}
private calculatePositionForFreeOperation(initial: Position2D): Position2D {
const operations = this.layout.operations;
if (operations.length === 0) {
return initial;
}
const freeInputs = this.oss.operations
.filter(operation => operation.arguments.length === 0 && operation.parent === null)
.map(operation => operation.id);
let inputsPositions = operations.filter(pos => freeInputs.includes(pos.id));
if (inputsPositions.length === 0) {
inputsPositions = operations;
}
const maxX = Math.max(...inputsPositions.map(node => node.x));
const minY = Math.min(...inputsPositions.map(node => node.y));
return {
x: maxX + OPERATION_NODE_WIDTH + MIN_DISTANCE + GRID_SIZE,
y: minY
};
}
private calculatePositionForFreeBlock(initial: Rectangle2D): Rectangle2D {
const rootBlocks = this.oss.blocks.filter(block => block.parent === null).map(block => block.id);
const blocksPositions = this.layout.blocks.filter(pos => rootBlocks.includes(pos.id));
if (blocksPositions.length === 0) {
return initial;
}
const maxX = Math.max(...blocksPositions.map(node => node.x + node.width));
const minY = Math.min(...blocksPositions.map(node => node.y));
return { ...initial, x: maxX + MIN_DISTANCE, y: minY };
}
}
// ======= Internals =======
function rectanglesOverlap(a: Rectangle2D, b: Rectangle2D): boolean {
return !(
a.x + a.width + MIN_DISTANCE <= b.x ||
b.x + b.width + MIN_DISTANCE <= a.x ||
a.y + a.height + MIN_DISTANCE <= b.y ||
b.y + b.height + MIN_DISTANCE <= a.y
);
}
function getOverlapAmount(a: Rectangle2D, b: Rectangle2D): Position2D {
const xOverlap = Math.max(0, Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x));
const yOverlap = Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y));
return { x: xOverlap, y: yOverlap };
}
function preventOverlap(target: Rectangle2D, fixedRectangles: Rectangle2D[]): Rectangle2D {
let hasOverlap: boolean;
do {
hasOverlap = false;
for (const fixed of fixedRectangles) {
if (rectanglesOverlap(target, fixed)) {
hasOverlap = true;
const overlap = getOverlapAmount(target, fixed);
if (overlap.x >= overlap.y) {
target.x += overlap.x + MIN_DISTANCE;
} else {
target.y += overlap.y + MIN_DISTANCE;
}
break;
}
}
} while (hasOverlap);
return target;
}
function calculatePositionFromArgs(args: number[], operations: IOperationPosition[]): Position2D {
const argNodes = operations.filter(pos => args.includes(pos.id));
const maxY = Math.max(...argNodes.map(node => node.y));
const minX = Math.min(...argNodes.map(node => node.x));
const maxX = Math.max(...argNodes.map(node => node.x));
return {
x: Math.ceil((maxX + minX) / 2 / GRID_SIZE) * GRID_SIZE,
y: maxY + 2 * OPERATION_NODE_HEIGHT + MIN_DISTANCE
};
}
function calculatePositionFromChildren(
initial: Rectangle2D,
operations: IOperationPosition[],
blocks: IBlockPosition[]
): Rectangle2D {
let left = undefined;
let top = undefined;
let right = undefined;
let bottom = undefined;
for (const block of block_nodes) {
left = !left ? block.x - MIN_DISTANCE : Math.min(left, block.x - MIN_DISTANCE);
top = !top ? block.y - MIN_DISTANCE : Math.min(top, block.y - MIN_DISTANCE);
right = !right
? Math.max(left + data.width, block.x + block.width + MIN_DISTANCE)
for (const block of blocks) {
left = left === undefined ? block.x - MIN_DISTANCE : Math.min(left, block.x - MIN_DISTANCE);
top = top === undefined ? block.y - MIN_DISTANCE : Math.min(top, block.y - MIN_DISTANCE);
right =
right === undefined
? Math.max(left + initial.width, block.x + block.width + MIN_DISTANCE)
: Math.max(right, block.x + block.width + MIN_DISTANCE);
bottom = !bottom
? Math.max(top + data.height, block.y + block.height + MIN_DISTANCE)
? Math.max(top + initial.height, block.y + block.height + MIN_DISTANCE)
: Math.max(bottom, block.y + block.height + MIN_DISTANCE);
}
for (const operation of operation_nodes) {
left = !left ? operation.x - MIN_DISTANCE : Math.min(left, operation.x - MIN_DISTANCE);
top = !top ? operation.y - MIN_DISTANCE : Math.min(top, operation.y - MIN_DISTANCE);
right = !right
? Math.max(left + data.width, operation.x + OPERATION_NODE_WIDTH + MIN_DISTANCE)
for (const operation of operations) {
left = left === undefined ? operation.x - MIN_DISTANCE : Math.min(left, operation.x - MIN_DISTANCE);
top = top === undefined ? operation.y - MIN_DISTANCE : Math.min(top, operation.y - MIN_DISTANCE);
right =
right === undefined
? Math.max(left + initial.width, operation.x + OPERATION_NODE_WIDTH + MIN_DISTANCE)
: Math.max(right, operation.x + OPERATION_NODE_WIDTH + MIN_DISTANCE);
bottom = !bottom
? Math.max(top + data.height, operation.y + OPERATION_NODE_HEIGHT + MIN_DISTANCE)
? Math.max(top + initial.height, operation.y + OPERATION_NODE_HEIGHT + MIN_DISTANCE)
: Math.max(bottom, operation.y + OPERATION_NODE_HEIGHT + MIN_DISTANCE);
}
return {
x: left ?? data.position_x,
y: top ?? data.position_y,
width: right && left ? right - left : data.width,
height: bottom && top ? bottom - top : data.height
x: left ?? initial.x,
y: top ?? initial.y,
width: right !== undefined && left !== undefined ? right - left : initial.width,
height: bottom !== undefined && top !== undefined ? bottom - top : initial.height
};
}
}

View File

@ -11,8 +11,17 @@ import {
type IOperationSchemaDTO
} from '../backend/types';
/** Represents OSS node type. */
export const NodeType = {
OPERATION: 1,
BLOCK: 2
} as const;
export type NodeType = (typeof NodeType)[keyof typeof NodeType];
/** Represents Operation. */
export interface IOperation extends IOperationDTO {
nodeID: string;
nodeType: typeof NodeType.OPERATION;
x: number;
y: number;
is_owned: boolean;
@ -23,12 +32,17 @@ export interface IOperation extends IOperationDTO {
/** Represents Block. */
export interface IBlock extends IBlockDTO {
nodeID: string;
nodeType: typeof NodeType.BLOCK;
x: number;
y: number;
width: number;
height: number;
}
/** Represents item of OperationSchema. */
export type IOssItem = IOperation | IBlock;
/** Represents {@link IOperationSchema} statistics. */
export interface IOperationSchemaStats {
count_all: number;
@ -45,16 +59,14 @@ export interface IOperationSchema extends IOperationSchemaDTO {
blocks: IBlock[];
graph: Graph;
hierarchy: Graph;
hierarchy: Graph<string>;
schemas: number[];
stats: IOperationSchemaStats;
operationByID: Map<number, IOperation>;
blockByID: Map<number, IBlock>;
itemByNodeID: Map<string, IOssItem>;
}
/** Represents item of OperationSchema. */
export type IOssItem = IOperation | IBlock;
/** Represents substitution error description. */
export interface ISubstitutionErrorDescription {
errorType: SubstitutionErrorType;

View File

@ -1,7 +1,7 @@
'use no memo'; // TODO: remove when react hook forms are compliant with react compiler
'use client';
import { useRef } from 'react';
import { useEffect, useRef } from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
@ -47,10 +47,12 @@ export function FormOSS() {
const readOnly = useWatch({ control, name: 'read_only' });
const prevDirty = useRef(isDirty);
useEffect(() => {
if (prevDirty.current !== isDirty) {
prevDirty.current = isDirty;
setIsModified(isDirty);
}
}, [isDirty, setIsModified]);
function onSubmit(data: IUpdateLibraryItemDTO) {
return updateOss(data).then(() => reset({ ...data }));

View File

@ -4,8 +4,7 @@ import { useRef } from 'react';
import { Dropdown } from '@/components/dropdown';
import { type IBlock, type IOperation, type IOssItem } from '../../../../models/oss';
import { isOperation } from '../../../../models/oss-api';
import { type IOssItem, NodeType } from '../../../../models/oss';
import { MenuBlock } from './menu-block';
import { MenuOperation } from './menu-operation';
@ -27,7 +26,6 @@ interface ContextMenuProps extends ContextMenuData {
export function ContextMenu({ isOpen, item, cursorX, cursorY, onHide }: ContextMenuProps) {
const ref = useRef<HTMLDivElement>(null);
const isOperationNode = isOperation(item);
function handleBlur(event: React.FocusEvent<HTMLDivElement>) {
if (!ref.current?.contains(event.relatedTarget as Node)) {
@ -39,21 +37,20 @@ export function ContextMenu({ isOpen, item, cursorX, cursorY, onHide }: ContextM
<div
ref={ref}
onBlur={handleBlur}
className='relative'
style={{ top: `calc(${cursorY}px - 2.5rem)`, left: cursorX }}
className='fixed z-tooltip'
style={{ top: `calc(${cursorY}px + 0.5rem)`, left: cursorX }}
>
<Dropdown
className='z-navigation!'
isOpen={isOpen}
stretchLeft={cursorX >= window.innerWidth - MENU_WIDTH}
stretchTop={cursorY >= window.innerHeight - MENU_HEIGHT}
margin={cursorY >= window.innerHeight - MENU_HEIGHT ? 'mb-3' : 'mt-3'}
>
{!!item ? (
isOperationNode ? (
<MenuOperation operation={item as IOperation} onHide={onHide} />
item.nodeType === NodeType.OPERATION ? (
<MenuOperation operation={item} onHide={onHide} />
) : (
<MenuBlock block={item as IBlock} onHide={onHide} />
<MenuBlock block={item} onHide={onHide} />
)
) : null}
</Dropdown>

View File

@ -16,15 +16,15 @@ export const BLOCK_NODE_MIN_WIDTH = 160;
export const BLOCK_NODE_MIN_HEIGHT = 100;
export function BlockNode(node: BlockInternalNode) {
const { selected, schema } = useOssEdit();
const { selectedItems, schema } = useOssEdit();
const dropTarget = useDraggingStore(state => state.dropTarget);
const isDragging = useDraggingStore(state => state.isDragging);
const showCoordinates = useOSSGraphStore(state => state.showCoordinates);
const setHover = useOperationTooltipStore(state => state.setHoverItem);
const focus = selected.length === 1 ? selected[0] : null;
const isParent = (!!focus && schema.hierarchy.at(focus)?.inputs.includes(-node.data.block.id)) ?? false;
const isChild = (!!focus && schema.hierarchy.at(focus)?.outputs.includes(-node.data.block.id)) ?? false;
const focus = selectedItems.length === 1 ? selectedItems[0] : null;
const isParent = (!!focus && schema.hierarchy.at(focus.nodeID)?.inputs.includes(node.data.block.nodeID)) ?? false;
const isChild = (!!focus && schema.hierarchy.at(focus.nodeID)?.outputs.includes(node.data.block.nodeID)) ?? false;
return (
<>
<NodeResizeControl minWidth={BLOCK_NODE_MIN_WIDTH} minHeight={BLOCK_NODE_MIN_HEIGHT}>

View File

@ -21,9 +21,9 @@ interface NodeCoreProps {
}
export function NodeCore({ node }: NodeCoreProps) {
const { selected, schema } = useOssEdit();
const focus = selected.length === 1 ? selected[0] : null;
const isChild = (!!focus && schema.hierarchy.at(focus)?.outputs.includes(node.data.operation.id)) ?? false;
const { selectedItems, schema } = useOssEdit();
const focus = selectedItems.length === 1 ? selectedItems[0] : null;
const isChild = (!!focus && schema.hierarchy.at(focus.nodeID)?.outputs.includes(node.data.operation.nodeID)) ?? false;
const setHover = useOperationTooltipStore(state => state.setHoverItem);
const showCoordinates = useOSSGraphStore(state => state.showCoordinates);

View File

@ -3,12 +3,12 @@
import { useCallback, useEffect, useState } from 'react';
import { type Edge, type Node, useEdgesState, useNodesState, useOnSelectionChange, useReactFlow } from 'reactflow';
import { type IOperationSchema } from '@/features/oss/models/oss';
import { type Position2D } from '@/features/oss/models/oss-layout';
import { useOSSGraphStore } from '@/features/oss/stores/oss-graph';
import { PARAMETER } from '@/utils/constants';
import { type IOperationSchema, NodeType } from '../../../models/oss';
import { constructNodeID } from '../../../models/oss-api';
import { type Position2D } from '../../../models/oss-layout';
import { useOSSGraphStore } from '../../../stores/oss-graph';
import { useOssEdit } from '../oss-edit-context';
import { flowOptions } from './oss-flow';
@ -29,10 +29,10 @@ export const OssFlowState = ({ children }: React.PropsWithChildren) => {
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);
function onSelectionChange({ nodes }: { nodes: Node[] }) {
const ids = nodes.map(node => Number(node.id));
const ids = nodes.map(node => node.id);
setSelected(prev => [
...prev.filter(nodeID => ids.includes(nodeID)),
...ids.filter(nodeID => !prev.includes(Number(nodeID)))
...ids.filter(nodeID => !prev.includes(nodeID))
]);
}
@ -41,46 +41,45 @@ export const OssFlowState = ({ children }: React.PropsWithChildren) => {
});
const resetGraph = useCallback(() => {
const newNodes: Node[] = [
...schema.hierarchy
.topologicalOrder()
.filter(id => id < 0)
.map(id => {
const block = schema.blockByID.get(-id)!;
const newNodes: Node[] = schema.hierarchy.topologicalOrder().map(nodeID => {
const item = schema.itemByNodeID.get(nodeID)!;
if (item.nodeType === NodeType.BLOCK) {
return {
id: String(id),
id: nodeID,
type: 'block',
data: { label: block.title, block: block },
position: computeRelativePosition(schema, { x: block.x, y: block.y }, block.parent),
data: { label: item.title, block: item },
position: computeRelativePosition(schema, { x: item.x, y: item.y }, item.parent),
style: {
width: block.width,
height: block.height
width: item.width,
height: item.height
},
parentId: block.parent ? `-${block.parent}` : undefined,
parentId: item.parent ? constructNodeID(NodeType.BLOCK, item.parent) : undefined,
zIndex: Z_BLOCK
};
}),
...schema.operations.map(operation => ({
id: String(operation.id),
type: operation.operation_type.toString(),
data: { label: operation.alias, operation: operation },
position: computeRelativePosition(schema, { x: operation.x, y: operation.y }, operation.parent),
parentId: operation.parent ? `-${operation.parent}` : undefined,
} else {
return {
id: item.nodeID,
type: item.operation_type.toString(),
data: { label: item.alias, operation: item },
position: computeRelativePosition(schema, { x: item.x, y: item.y }, item.parent),
parentId: item.parent ? constructNodeID(NodeType.BLOCK, item.parent) : undefined,
zIndex: Z_SCHEMA
}))
];
};
}
});
const newEdges: Edge[] = schema.arguments.map((argument, index) => ({
const newEdges: Edge[] = schema.arguments.map((argument, index) => {
const source = schema.operationByID.get(argument.argument)!;
const target = schema.operationByID.get(argument.operation)!;
return {
id: String(index),
source: String(argument.argument),
target: String(argument.operation),
source: source.nodeID,
target: target.nodeID,
type: edgeStraight ? 'straight' : 'simplebezier',
animated: edgeAnimate,
targetHandle:
schema.operationByID.get(argument.argument)!.x > schema.operationByID.get(argument.operation)!.x
? 'right'
: 'left'
}));
targetHandle: source.x > target.x ? 'right' : 'left'
};
});
setNodes(newNodes);
setEdges(newEdges);

View File

@ -12,6 +12,7 @@ import { promptText } from '@/utils/labels';
import { useDeleteBlock } from '../../../backend/use-delete-block';
import { useMutatingOss } from '../../../backend/use-mutating-oss';
import { useUpdateLayout } from '../../../backend/use-update-layout';
import { type IOssItem, NodeType } from '../../../models/oss';
import { type OssNode, type Position2D } from '../../../models/oss-layout';
import { GRID_SIZE, LayoutManager } from '../../../models/oss-layout-api';
import { useOSSGraphStore } from '../../../stores/oss-graph';
@ -41,7 +42,7 @@ export const flowOptions = {
export function OssFlow() {
const mainHeight = useMainHeight();
const { navigateOperationSchema, schema, selected, isMutable, canDeleteOperation } = useOssEdit();
const { navigateOperationSchema, schema, selected, selectedItems, isMutable, canDeleteOperation } = useOssEdit();
const { screenToFlowPosition } = useReactFlow();
const { containMovement, nodes, onNodesChange, edges, onEdgesChange, resetGraph, resetView } = useOssFlow();
const store = useStoreApi();
@ -76,20 +77,21 @@ export function OssFlow() {
manager: new LayoutManager(schema, getLayout()),
defaultX: targetPosition.x,
defaultY: targetPosition.y,
initialInputs: selected.filter(id => id > 0),
initialParent: extractSingleBlock(selected),
initialInputs: selectedItems.filter(item => item?.nodeType === NodeType.OPERATION).map(item => item.id),
initialParent: extractBlockParent(selectedItems),
onCreate: resetView
});
}
function handleCreateBlock() {
const targetPosition = screenToFlowPosition({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
const parent = extractSingleBlock(selected);
const parent = extractBlockParent(selectedItems);
showCreateBlock({
manager: new LayoutManager(schema, getLayout()),
defaultX: targetPosition.x,
defaultY: targetPosition.y,
initialChildren: parent !== null ? [] : selected,
initialChildren:
parent !== null && selectedItems.length === 1 && parent === selectedItems[0].id ? [] : selectedItems,
initialParent: parent,
onCreate: resetView
});
@ -99,25 +101,24 @@ export function OssFlow() {
if (selected.length !== 1) {
return;
}
if (selected[0] > 0) {
const operation = schema.operationByID.get(selected[0]);
if (!operation || !canDeleteOperation(operation)) {
const item = schema.itemByNodeID.get(selected[0]);
if (!item) {
return;
}
if (item.nodeType === NodeType.OPERATION) {
if (!canDeleteOperation(item)) {
return;
}
showDeleteOperation({
oss: schema,
target: operation,
target: item,
layout: getLayout()
});
} else {
const block = schema.blockByID.get(-selected[0]);
if (!block) {
return;
}
if (!window.confirm(promptText.deleteBlock)) {
return;
}
void deleteBlock({ itemID: schema.id, data: { target: block.id, layout: getLayout() } });
void deleteBlock({ itemID: schema.id, data: { target: item.id, layout: getLayout() } });
}
}
@ -219,7 +220,10 @@ export function OssFlow() {
}
// -------- Internals --------
function extractSingleBlock(selected: number[]): number | null {
const blocks = selected.filter(id => id < 0);
return blocks.length === 1 ? -blocks[0] : null;
function extractBlockParent(selectedItems: IOssItem[]): number | null {
if (selectedItems.length === 1 && selectedItems[0].nodeType === NodeType.BLOCK) {
return selectedItems[0].id;
}
const parents = selectedItems.map(item => item.parent).filter(id => id !== null);
return parents.length === 0 ? null : parents[0];
}

View File

@ -27,6 +27,7 @@ import { OperationType } from '../../../backend/types';
import { useExecuteOperation } from '../../../backend/use-execute-operation';
import { useMutatingOss } from '../../../backend/use-mutating-oss';
import { useUpdateLayout } from '../../../backend/use-update-layout';
import { NodeType } from '../../../models/oss';
import { LayoutManager } from '../../../models/oss-layout-api';
import { useOssEdit } from '../oss-edit-context';
@ -48,11 +49,13 @@ export function ToolbarOssGraph({
className,
...restProps
}: ToolbarOssGraphProps) {
const { schema, selected, isMutable, canDeleteOperation: canDelete } = useOssEdit();
const { schema, selectedItems, isMutable, canDeleteOperation: canDelete } = useOssEdit();
const isProcessing = useMutatingOss();
const { resetView } = useOssFlow();
const selectedOperation = selected.length !== 1 ? null : schema.operationByID.get(selected[0]) ?? null;
const selectedBlock = selected.length !== 1 ? null : schema.blockByID.get(-selected[0]) ?? null;
const selectedOperation =
selectedItems.length === 1 && selectedItems[0].nodeType === NodeType.OPERATION ? selectedItems[0] : null;
const selectedBlock =
selectedItems.length === 1 && selectedItems[0].nodeType === NodeType.BLOCK ? selectedItems[0] : null;
const getLayout = useGetLayout();
const { updateLayout } = useUpdateLayout();
@ -145,7 +148,9 @@ export function ToolbarOssGraph({
title='Исправить позиции узлов'
icon={<IconFixLayout size='1.25rem' className='icon-primary' />}
onClick={handleFixLayout}
disabled={selected.length > 1 || selected[0] > 0}
disabled={
selectedItems.length > 1 || (selectedItems.length > 0 && selectedItems[0].nodeType === NodeType.OPERATION)
}
/>
<MiniButton
title='Настройки отображения'
@ -181,14 +186,14 @@ export function ToolbarOssGraph({
title='Активировать операцию'
icon={<IconExecute size='1.25rem' className='icon-green' />}
onClick={handleOperationExecute}
disabled={isProcessing || selected.length !== 1 || !readyForSynthesis}
disabled={isProcessing || selectedItems.length !== 1 || !readyForSynthesis}
/>
<MiniButton
titleHtml={prepareTooltip('Редактировать выбранную', 'Двойной клик')}
aria-label='Редактировать выбранную'
icon={<IconEdit2 size='1.25rem' className='icon-primary' />}
onClick={handleEditItem}
disabled={selected.length !== 1 || isProcessing}
disabled={selectedItems.length !== 1 || isProcessing}
/>
<MiniButton
titleHtml={prepareTooltip('Удалить выбранную', 'Delete')}

View File

@ -1,10 +1,10 @@
import { type Node } from 'reactflow';
import { useMoveItems } from '@/features/oss/backend/use-move-items';
import { useThrottleCallback } from '@/hooks/use-throttle-callback';
import { useDraggingStore } from '@/stores/dragging';
import { useMoveItems } from '../../../backend/use-move-items';
import { NodeType } from '../../../models/oss';
import { useOssEdit } from '../oss-edit-context';
import { useOssFlow } from './oss-flow-context';
@ -21,6 +21,7 @@ interface DraggingProps {
export function useDragging({ hideContextMenu }: DraggingProps) {
const { setContainMovement, containMovement, setNodes } = useOssFlow();
const setIsDragging = useDraggingStore(state => state.setIsDragging);
const isDragging = useDraggingStore(state => state.isDragging);
const getLayout = useGetLayout();
const { selected, schema } = useOssEdit();
const dropTarget = useDropTarget();
@ -43,7 +44,7 @@ export function useDragging({ hideContextMenu }: DraggingProps) {
function handleDragStart(event: React.MouseEvent, target: Node) {
if (event.shiftKey) {
setContainMovement(true);
applyContainMovement([target.id, ...selected.map(id => String(id))], true);
applyContainMovement([target.id, ...selected], true);
} else {
setContainMovement(false);
dropTarget.update(event);
@ -61,24 +62,20 @@ export function useDragging({ hideContextMenu }: DraggingProps) {
function handleDragStop(event: React.MouseEvent, target: Node) {
if (containMovement) {
applyContainMovement([target.id, ...selected.map(id => String(id))], false);
applyContainMovement([target.id, ...selected], false);
} else {
event.preventDefault();
event.stopPropagation();
if (isDragging) {
setIsDragging(false);
const new_parent = dropTarget.evaluate(event);
const allSelected = [...selected.filter(id => id != Number(target.id)), Number(target.id)];
const operations = allSelected
.filter(id => id > 0)
.map(id => schema.operationByID.get(id))
.filter(operation => !!operation);
const blocks = allSelected
.filter(id => id < 0)
.map(id => schema.blockByID.get(-id))
.filter(operation => !!operation);
const parents = new Set(
[...blocks.map(block => block.parent), ...operations.map(operation => operation.parent)].filter(id => !!id)
const allSelected = [...selected.filter(id => id != target.id), target.id].map(id =>
schema.itemByNodeID.get(id)
);
const parents = new Set(allSelected.map(item => item?.parent).filter(id => !!id));
const operations = allSelected.filter(item => item?.nodeType === NodeType.OPERATION);
const blocks = allSelected.filter(item => item?.nodeType === NodeType.BLOCK);
if (
(parents.size !== 1 || parents.values().next().value !== new_parent) &&
(parents.size !== 0 || new_parent !== null)
@ -94,8 +91,8 @@ export function useDragging({ hideContextMenu }: DraggingProps) {
});
}
}
}
setIsDragging(false);
setContainMovement(false);
dropTarget.reset();
}

View File

@ -1,5 +1,7 @@
import { useReactFlow } from 'reactflow';
import { NodeType } from '@/features/oss/models/oss';
import { useDraggingStore } from '@/stores/dragging';
import { useOssEdit } from '../oss-edit-context';
@ -7,7 +9,7 @@ import { useOssEdit } from '../oss-edit-context';
/** Hook to encapsulate drop target logic. */
export function useDropTarget() {
const { getIntersectingNodes, screenToFlowPosition } = useReactFlow();
const { selected, schema } = useOssEdit();
const { selectedItems, selected, schema } = useOssEdit();
const dropTarget = useDraggingStore(state => state.dropTarget);
const setDropTarget = useDraggingStore(state => state.setDropTarget);
@ -19,17 +21,16 @@ export function useDropTarget() {
width: 1,
height: 1
})
.map(node => Number(node.id))
.filter(id => id < 0 && !selected.includes(id))
.map(id => schema.blockByID.get(-id))
.filter(block => !!block);
.filter(node => !selected.includes(node.id))
.map(node => schema.itemByNodeID.get(node.id))
.filter(item => item?.nodeType === NodeType.BLOCK);
if (blocks.length === 0) {
return null;
}
const successors = schema.hierarchy.expandAllOutputs([...selected]).filter(id => id < 0);
blocks = blocks.filter(block => !successors.includes(-block.id));
const successors = schema.hierarchy.expandAllOutputs(selectedItems.map(item => item.nodeID));
blocks = blocks.filter(block => !successors.includes(block.nodeID));
if (blocks.length === 0) {
return null;
}

View File

@ -18,13 +18,13 @@ export function useGetLayout() {
operations: nodes
.filter(node => node.type !== 'block')
.map(node => ({
id: Number(node.id),
id: schema.itemByNodeID.get(node.id)!.id,
...computeAbsolutePosition(node, schema, nodeById)
})),
blocks: nodes
.filter(node => node.type === 'block')
.map(node => ({
id: -Number(node.id),
id: schema.itemByNodeID.get(node.id)!.id,
...computeAbsolutePosition(node, schema, nodeById),
width: node.width ?? BLOCK_NODE_MIN_WIDTH,
height: node.height ?? BLOCK_NODE_MIN_HEIGHT
@ -35,7 +35,7 @@ export function useGetLayout() {
// ------- Internals -------
function computeAbsolutePosition(target: Node, schema: IOperationSchema, nodeById: Map<string, Node>): Position2D {
const nodes = schema.hierarchy.expandAllInputs([Number(target.id)]);
const nodes = schema.hierarchy.expandAllInputs([target.id]);
let x = target.position.x;
let y = target.position.y;
for (const nodeID of nodes) {

View File

@ -2,7 +2,7 @@
import { createContext, use } from 'react';
import { type IOperation, type IOperationSchema } from '../../models/oss';
import { type IOperation, type IOperationSchema, type IOssItem } from '../../models/oss';
export const OssTabID = {
CARD: 0,
@ -12,7 +12,8 @@ export type OssTabID = (typeof OssTabID)[keyof typeof OssTabID];
interface IOssEditContext {
schema: IOperationSchema;
selected: number[];
selected: string[];
selectedItems: IOssItem[];
isOwned: boolean;
isMutable: boolean;
@ -22,7 +23,7 @@ interface IOssEditContext {
canDeleteOperation: (target: IOperation) => boolean;
deleteSchema: () => void;
setSelected: React.Dispatch<React.SetStateAction<number[]>>;
setSelected: React.Dispatch<React.SetStateAction<string[]>>;
}
export const OssEditContext = createContext<IOssEditContext | null>(null);

View File

@ -38,7 +38,8 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
const isMutable = role > UserRole.READER && !schema.read_only;
const isEditor = !!user.id && schema.editors.includes(user.id);
const [selected, setSelected] = useState<number[]>([]);
const [selected, setSelected] = useState<string[]>([]);
const selectedItems = selected.map(id => schema.itemByNodeID.get(id)).filter(item => !!item);
const { deleteItem } = useDeleteItem();
@ -92,6 +93,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
value={{
schema,
selected,
selectedItems,
isOwned,
isMutable,

View File

@ -13,11 +13,11 @@ export const useCreateConstituenta = () => {
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'create-constituenta'],
mutationFn: rsformsApi.createConstituenta,
onSuccess: data => {
onSuccess: async data => {
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.schema.id }).queryKey, data.schema);
updateTimestamp(data.schema.id);
return Promise.allSettled([
await Promise.allSettled([
client.invalidateQueries({ queryKey: [KEYS.oss] }),
client.invalidateQueries({
queryKey: [rsformsApi.baseKey],

View File

@ -13,11 +13,11 @@ export const useDeleteConstituents = () => {
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'delete-constituents'],
mutationFn: rsformsApi.deleteConstituents,
onSuccess: data => {
onSuccess: async data => {
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
updateTimestamp(data.id);
return Promise.allSettled([
await Promise.allSettled([
client.invalidateQueries({ queryKey: [KEYS.oss] }),
client.invalidateQueries({
queryKey: [rsformsApi.baseKey],

View File

@ -13,11 +13,11 @@ export const useInlineSynthesis = () => {
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'inline-synthesis'],
mutationFn: rsformsApi.inlineSynthesis,
onSuccess: data => {
onSuccess: async data => {
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
updateTimestamp(data.id);
return Promise.allSettled([
await Promise.allSettled([
client.invalidateQueries({ queryKey: [KEYS.oss] }),
client.invalidateQueries({
queryKey: [rsformsApi.baseKey],

View File

@ -12,11 +12,11 @@ export const useProduceStructure = () => {
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'produce-structure'],
mutationFn: rsformsApi.produceStructure,
onSuccess: data => {
onSuccess: async data => {
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.schema.id }).queryKey, data.schema);
updateTimestamp(data.schema.id);
return Promise.allSettled([
await Promise.allSettled([
client.invalidateQueries({ queryKey: [KEYS.oss] }),
client.invalidateQueries({
queryKey: [rsformsApi.baseKey],

View File

@ -13,11 +13,11 @@ export const useRenameConstituenta = () => {
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'rename-constituenta'],
mutationFn: rsformsApi.renameConstituenta,
onSuccess: data => {
onSuccess: async data => {
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.schema.id }).queryKey, data.schema);
updateTimestamp(data.schema.id);
return Promise.allSettled([
await Promise.allSettled([
client.invalidateQueries({ queryKey: [KEYS.oss] }),
client.invalidateQueries({
queryKey: [rsformsApi.baseKey],

View File

@ -12,11 +12,11 @@ export const useResetAliases = () => {
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'reset-aliases'],
mutationFn: rsformsApi.resetAliases,
onSuccess: data => {
onSuccess: async data => {
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
updateTimestamp(data.id);
return Promise.allSettled([
await Promise.allSettled([
client.invalidateQueries({ queryKey: [KEYS.oss] }),
client.invalidateQueries({
queryKey: [rsformsApi.baseKey],

View File

@ -13,11 +13,11 @@ export const useSubstituteConstituents = () => {
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'substitute-constituents'],
mutationFn: rsformsApi.substituteConstituents,
onSuccess: data => {
onSuccess: async data => {
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
updateTimestamp(data.id);
return Promise.allSettled([
await Promise.allSettled([
client.invalidateQueries({ queryKey: [KEYS.oss] }),
client.invalidateQueries({
queryKey: [rsformsApi.baseKey],

View File

@ -13,10 +13,10 @@ export const useUpdateConstituenta = () => {
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'update-constituenta'],
mutationFn: rsformsApi.updateConstituenta,
onSuccess: (_, variables) => {
onSuccess: async (_, variables) => {
updateTimestamp(variables.itemID);
return Promise.allSettled([
await Promise.allSettled([
client.invalidateQueries({ queryKey: [KEYS.oss] }),
client.invalidateQueries({ queryKey: [rsformsApi.baseKey] })
]);

View File

@ -12,13 +12,13 @@ export const useUploadTRS = () => {
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'load-trs'],
mutationFn: rsformsApi.upload,
onSuccess: data => {
onSuccess: async data => {
client.setQueryData(KEYS.composite.rsItem({ itemID: data.id }), data);
client.setQueryData(KEYS.composite.libraryList, (prev: ILibraryItem[] | undefined) =>
prev?.map(item => (item.id === data.id ? data : item))
);
return Promise.allSettled([
await Promise.allSettled([
client.invalidateQueries({ queryKey: [KEYS.oss] }),
client.invalidateQueries({
queryKey: [rsformsApi.baseKey],

View File

@ -1,20 +1,18 @@
// This file was generated by lezer-generator. You probably shouldn't edit it.
import { LRParser } from '@lezer/lr';
import { highlighting } from './highlight';
import {LRParser} from "@lezer/lr"
import {highlighting} from "./highlight"
export const parser = LRParser.deserialize({
version: 14,
states:
"$nQVQPOOObQQO'#C^OOQO'#Cl'#ClOjQPO'#CuOxQPO'#CdOOQO'#Ce'#CeOOQO'#Ck'#CkOOQO'#Cf'#CfQVQPOOO}QPO,58xO!SQPO,58{OOQO,59a,59aO!XQPO,59OOOQO-E6d-E6dO!^QSO1G.dO!cQPO1G.gOOQO1G.j1G.jO!hQQO'#CpOOQO'#C`'#C`O!pQPO7+$OOOQO'#Cg'#CgO!uQPO'#CcO!}QPO7+$RO!^QSO,59[OOQO<<Gj<<GjOOQO-E6e-E6eOOQO<<Gm<<GmOOQO1G.v1G.v",
stateData: '#V~O^OS~ObPOgROhSO~ORXOUYO~OgRO[iXbiXhiX~OgRO~Oc^O~Oc_O~Oh`O~OeaO~OgdO~OfgOadX~OahO~OgdOaVX~OajO~Og^~',
goto: '!kjPPkPokPruuy!PPPP!VuPPP!ZPPPP!aTQOWRc^Rf_TUOWQWOR]WQe_RieTVOWQb^RkgSTOWQZRR[S',
nodeNames: '⚠ Text RefEntity Global Grams RefSyntactic Offset Nominal Error Filler',
states: "$nQVQPOOObQQO'#C^OOQO'#Cl'#ClOjQPO'#CuOxQPO'#CdOOQO'#Ce'#CeOOQO'#Ck'#CkOOQO'#Cf'#CfQVQPOOO}QPO,58xO!SQPO,58{OOQO,59a,59aO!XQPO,59OOOQO-E6d-E6dO!^QSO1G.dO!cQPO1G.gOOQO1G.j1G.jO!hQQO'#CpOOQO'#C`'#C`O!pQPO7+$OOOQO'#Cg'#CgO!uQPO'#CcO!}QPO7+$RO!^QSO,59[OOQO<<Gj<<GjOOQO-E6e-E6eOOQO<<Gm<<GmOOQO1G.v1G.v",
stateData: "#V~O^OS~ObPOgROhSO~ORXOUYO~OgRO[iXbiXhiX~OgRO~Oc^O~Oc_O~Oh`O~OeaO~OgdO~OfgOadX~OahO~OgdOaVX~OajO~Og^~",
goto: "!kjPPkPokPruuy!PPPP!VuPPP!ZPPPP!aTQOWRc^Rf_TUOWQWOR]WQe_RieTVOWQb^RkgSTOWQZRR[S",
nodeNames: "⚠ Text RefEntity Global Grams RefSyntactic Offset Nominal Error Filler",
maxTerm: 25,
propSources: [highlighting],
skippedNodes: [0],
repeatNodeCount: 2,
tokenData:
".V~R!UOX$eXZ%VZ^%z^p$epq%Vqr'sr|$e|}'x}!O(f!O!Q$e!Q!R)i!R![*i![!b$e!b!c+k!c!d+v!d!e)i!e!f+v!f!g+v!g!h)i!h!i+v!i!r)i!r!s+v!s!t)i!t!u+v!u!v+v!v!w+v!w!z)i!z!{+v!{!})i!}#T$e#T#o)i#p#q-{#q#r.Q#r#y$e#y#z%z#z$f$e$f$g%z$g#BY$e#BY#BZ%z#BZ$IS$e$IS$I_%z$I_$I|$e$I|$JO%z$JO$JT$e$JT$JU%z$JU$KV$e$KV$KW%z$KW&FU$e&FU&FV%z&FV;'S$e;'S;=`%P<%lO$eP$jVgPOX$eZp$er!b$e!c#o$e#r;'S$e;'S;=`%P<%lO$eP%SP;=`<%l$e~%[Y^~X^%Vpq%V#y#z%V$f$g%V#BY#BZ%V$IS$I_%V$I|$JO%V$JT$JU%V$KV$KW%V&FU&FV%V~&RjgP^~OX$eXZ%VZ^%z^p$epq%Vr!b$e!c#o$e#r#y$e#y#z%z#z$f$e$f$g%z$g#BY$e#BY#BZ%z#BZ$IS$e$IS$I_%z$I_$I|$e$I|$JO%z$JO$JT$e$JT$JU%z$JU$KV$e$KV$KW%z$KW&FU$e&FU&FV%z&FV;'S$e;'S;=`%P<%lO$e~'xOh~R(PVfQgPOX$eZp$er!b$e!c#o$e#r;'S$e;'S;=`%P<%lO$eV(m^eSgPOX$eZp$er}$e}!O)i!O!Q$e!Q!R)i!R![*i![!b$e!c!})i!}#T$e#T#o)i#r;'S$e;'S;=`%P<%lO$eT)p]eSgPOX$eZp$er}$e}!O)i!O!Q$e!Q![)i![!b$e!c!})i!}#T$e#T#o)i#r;'S$e;'S;=`%P<%lO$eV*r]UQeSgPOX$eZp$er}$e}!O)i!O!Q$e!Q![*i![!b$e!c!})i!}#T$e#T#o)i#r;'S$e;'S;=`%P<%lO$e~+nP#o#p+q~+vOb~V+}^eSgPOX$eZp$er}$e}!O)i!O!Q$e!Q!R)i!R![,y![!b$e!c!})i!}#T$e#T#o)i#r;'S$e;'S;=`%P<%lO$eV-S]RQeSgPOX$eZp$er}$e}!O)i!O!Q$e!Q![,y![!b$e!c!})i!}#T$e#T#o)i#r;'S$e;'S;=`%P<%lO$e~.QOc~~.VOa~",
tokenData: ".V~R!UOX$eXZ%VZ^%z^p$epq%Vqr'sr|$e|}'x}!O(f!O!Q$e!Q!R)i!R![*i![!b$e!b!c+k!c!d+v!d!e)i!e!f+v!f!g+v!g!h)i!h!i+v!i!r)i!r!s+v!s!t)i!t!u+v!u!v+v!v!w+v!w!z)i!z!{+v!{!})i!}#T$e#T#o)i#p#q-{#q#r.Q#r#y$e#y#z%z#z$f$e$f$g%z$g#BY$e#BY#BZ%z#BZ$IS$e$IS$I_%z$I_$I|$e$I|$JO%z$JO$JT$e$JT$JU%z$JU$KV$e$KV$KW%z$KW&FU$e&FU&FV%z&FV;'S$e;'S;=`%P<%lO$eP$jVgPOX$eZp$er!b$e!c#o$e#r;'S$e;'S;=`%P<%lO$eP%SP;=`<%l$e~%[Y^~X^%Vpq%V#y#z%V$f$g%V#BY#BZ%V$IS$I_%V$I|$JO%V$JT$JU%V$KV$KW%V&FU&FV%V~&RjgP^~OX$eXZ%VZ^%z^p$epq%Vr!b$e!c#o$e#r#y$e#y#z%z#z$f$e$f$g%z$g#BY$e#BY#BZ%z#BZ$IS$e$IS$I_%z$I_$I|$e$I|$JO%z$JO$JT$e$JT$JU%z$JU$KV$e$KV$KW%z$KW&FU$e&FU&FV%z&FV;'S$e;'S;=`%P<%lO$e~'xOh~R(PVfQgPOX$eZp$er!b$e!c#o$e#r;'S$e;'S;=`%P<%lO$eV(m^eSgPOX$eZp$er}$e}!O)i!O!Q$e!Q!R)i!R![*i![!b$e!c!})i!}#T$e#T#o)i#r;'S$e;'S;=`%P<%lO$eT)p]eSgPOX$eZp$er}$e}!O)i!O!Q$e!Q![)i![!b$e!c!})i!}#T$e#T#o)i#r;'S$e;'S;=`%P<%lO$eV*r]UQeSgPOX$eZp$er}$e}!O)i!O!Q$e!Q![*i![!b$e!c!})i!}#T$e#T#o)i#r;'S$e;'S;=`%P<%lO$e~+nP#o#p+q~+vOb~V+}^eSgPOX$eZp$er}$e}!O)i!O!Q$e!Q!R)i!R![,y![!b$e!c!})i!}#T$e#T#o)i#r;'S$e;'S;=`%P<%lO$eV-S]RQeSgPOX$eZp$er}$e}!O)i!O!Q$e!Q![,y![!b$e!c!})i!}#T$e#T#o)i#r;'S$e;'S;=`%P<%lO$e~.QOc~~.VOa~",
tokenizers: [0, 1, 2],
topRules: { Text: [0, 1] },
topRules: {"Text":[0,1]},
tokenPrec: 96
});
})

View File

@ -1,6 +1,5 @@
// This file was generated by lezer-generator. You probably shouldn't edit it.
export const
Expression = 1,
export const Expression = 1,
Logic = 2,
Logic_predicates = 3,
Variable = 4,
@ -43,4 +42,4 @@ export const
Logic_binary = 76,
Function_decl = 81,
Arguments = 82,
Declaration = 83
Declaration = 83;

View File

@ -1,20 +1,24 @@
// This file was generated by lezer-generator. You probably shouldn't edit it.
import {LRParser} from "@lezer/lr"
import { LRParser } from '@lezer/lr';
export const parser = LRParser.deserialize({
version: 14,
states: "2xO!sQPOOOOQO'#Ch'#ChO#lQPO'#EWOOQO'#EW'#EWO%yQPO'#EVOVQPO'#CnO'_QPO'#CzO'fQPO'#C{O'nQPO'#C}O'sQPO'#DRO'xQPO'#DWO'}QPO'#D[OOQO'#Cw'#CwO(SQPO'#CwOOQO'#D_'#D_OOQO'#Cg'#CgO)pQPO'#CgO)uQPO'#CgO)zQPO'#C_OVQPO'#DsO*SQPO'#DvOOQO'#Dr'#DrO*[QPO'#DrO*aQPO'#EVOOQO'#C^'#C^O*rQPO'#EPQOQPOOO*wQPO,59YO*wQPO,59YO*wQPO,59YO*wQPO,59YO*wQPO,59YO*wQPO,59YO*wQPO,59YO*wQPO,59YO*wQPO,58yO+OQPO'#CfO+VQPO,59YO,jQPO,59OO,rQPO'#CeO,wQPO,58xO-YQPO,59mO-{QPO'#CfO*wQPO'#CnO.YQPO,59fOOQO'#Cf'#CfO*wQPO,59gOOQO,59g,59gO*wQPO,59iO*SQPO,59mO*wQPO,59rO*SQPO,59vO*wQPO,59RO*wQPO,59RO$lQPO'#C_OOQO,5:_,5:_O*wQPO'#CdOOQO'#C`'#C`OOQO'#Dx'#DxO.bQPO,5:bO*wQPO,5:^OVQPO,5:fOVQPO,5:fOVQPO,5:fOVQPO,5:fO.jQPO'#EROOQO'#EQ'#EQO.oQPO,5:kOOQO1G.t1G.tO2`QPO1G.tO2gQPO1G.tO6VQPO1G.tO9uQPO1G.tO9|QPO1G.tO:TQPO1G.tO:[QPO1G.tO=vQPO1G.eOOQO1G.j1G.jO*wQPO,59POOQO1G.d1G.dO*wQPO1G/XOOQO1G/Q1G/QO>gQPO1G/RO>nQPO1G/TO>vQPO1G/XO>{QPO1G/^O?SQPO1G/bO?XQPO1G.mO?aQPO1G.mO*SQPO,5:dO*wQPO1G/|O?hQPO1G/xOOQO1G0Q1G0QO@[QPO1G0QO@cQPO1G0QO@jQPO1G0QO*wQPO,5:mO*rQPO,5:lOVQPO1G0VOAXQPO1G.kOAiQPO7+$sOOQO7+$m7+$mOApQPO7+$oO*wQPO7+$sOVQPO7+$xO*wQPO7+$|OOQO7+$X7+$XOOQO1G0O1G0OOAuQPO7+%hOOQO7+%d7+%dOBcQPO1G0XOOQO1G0W1G0WOOQO7+%q7+%qOVQPO<<H_O*wQPO<<HZOBmQPO<<H_OBtQPO'#DXOCYQPO<<HdOCbQPO<<HhOCiQPO<<ISODWQPOAN=yODiQPOAN=uOVQPOAN=yOVQPO,59sOOQOAN>OAN>OOVQPOAN>SOOQOG23eG23eOOQOG23aG23aODpQPOG23eOERQPO1G/_OEgQPOG23nOEnQPOG23nOOQOLD)PLD)POOQOLD)YLD)YO*wQPOLD)YOFPQPO!$'LtOOQO!)9B`!)9B`",
stateData: "Fi~O!xOS~OTQOVTO]PO^PO_PO`ROaROmUOpVOrWOxXOyYO}ZO!Q`O!S^O!T^O!U^O!V^O!W^O!X^O!hcO!ifO!kdO!mdO~OsiO~PVOc!zXd!zXe!zXf!zXg!zXh!zXi!zXj!zX!Y!zXl!zX~Ov!zX!PSX!ZSX![!zX!]!zX!^!zX!_!zX!`!zX!a!zX!b!zX!c!zX!d!zX!e!zX!v!zX~P!zOckOdlOemOfnOgoOhpOiqOjrOvsO![sO!]sO!^sO!_sO!`sO!asO!bsO!csO!dsO!esO~O!v!yX~P$lOV{O]PO^PO_PO`ROaROmUOpVOrWOxXOyYO}ZO!Q`O!S^O!T^O!U^O!V^O!W^O!X^O~OTyO~P&QOV!OOpVO~Os!QO~Om!RO~Om!SO~Om!TO~OckXdkXekXfkXgkXhkXikXjkXvkX!PSX!ZSX![kX!]kX!^kX!_kX!`kX!akX!bkX!ckX!dkX!ekX!vkX!YkXlkX~Os!UO~OV!VO~O!PsO!ZsO~OT!ZOV!YO~Os!^O~O!o!_O!p!`O!q!aO!r!bO!v!yX~OT!cO~OTRO~P&QO!YYX~P$lOU!fOcZXdZXeZXfZXgZXhZXiZXjZXvZX!YZX![ZX!]ZX!^ZX!_ZX!`ZX!aZX!bZX!cZX!dZX!eZX~OU!oO!YYX~O!Y!pO~OU!qO!o!_O!p!`O!q!aO!r!bO~Ov!rO~P!zOckOdlOemOfnOgoOhpOiqOjrO~OlYX!YYXtYX~P-aOl!sO!Y!pO~Ov!|O!Y!{O~Ov#SO~Ot#UO!Y#TO~OckOebifbigbihbiibijbivbi![bi!]bi!^bi!_bi!`bi!abi!bbi!cbi!dbi!ebi!vbiUbi!Ybilbi!obi!pbi!qbi!rbitbiwbiTbiVbi]bi^bi_bi`biabimbipbirbixbiybi}bi!Qbi!Sbi!Tbi!Ubi!Vbi!Wbi!Xbi!hbi!ibi!kbi!mbi|bi~Odbi~P.wOdlO~P.wOckOdlOemOjrOgbihbiibivbi![bi!]bi!^bi!_bi!`bi!abi!bbi!cbi!dbi!ebi!vbiUbi!Ybilbi!obi!pbi!qbi!rbitbiwbiTbiVbi]bi^bi_bi`biabimbipbirbixbiybi}bi!Qbi!Sbi!Tbi!Ubi!Vbi!Wbi!Xbi!hbi!ibi!kbi!mbi|bi~Ofbi~P2nOckOdlOemOfnOiqOjrOhbivbi![bi!]bi!^bi!_bi!`bi!abi!bbi!cbi!dbi!ebi!vbiUbi!Ybilbi!obi!pbi!qbi!rbitbiwbiTbiVbi]bi^bi_bi`biabimbipbirbixbiybi}bi!Qbi!Sbi!Tbi!Ubi!Vbi!Wbi!Xbi!hbi!ibi!kbi!mbi|bi~Ogbi~P6^OgoO~P6^OfnO~P2nOckOdlOemOfbigbihbiibijbivbi![bi!]bi!^bi!_bi!`bi!abi!bbi!cbi!dbi!ebi!vbiUbi!Ybilbi!obi!pbi!qbi!rbitbiwbiTbiVbi]bi^bi_bi`biabimbipbirbixbiybi}bi!Qbi!Sbi!Tbi!Ubi!Vbi!Wbi!Xbi!hbi!ibi!kbi!mbi|bi~O!oRi!pRi!qRi!rRi!vRiURilRi|RiwRi~P-aOU#XO~P-aOt#YO!Y!pO~Ov#ZO~Ow#[O~P-aO!P#]O~Ot#^O!Y!pO~OU#^O~P-aOt#aO!Y!pO~O!o!_O!q!ni!r!ni!v!niU!nil!ni|!niw!ni~O!p!ni~P?pO!p!`O~P?pO!o!_O!p!`O!q!aO!r!ni!v!niU!nil!ni|!niw!ni~OUXi!YXilXitXi~P-aOw#eO~P-aOV#fO~OckOdlOemOfnOgoOhpOiqOjrO~PVOt!ui!Y!ui~P-aOw#nO~P-aO!o!_O!p!`O!q!aO!r!bOl{X|{X~Ol#pO|#oO~Ow#qO~P-aO!o!_O!p!`O!q!aO!r!bO!v!jyU!jyl!jy|!jyw!jy~Ol#rO!o!_O!p!`O!q!aO!r!bO~OU#sO~P-aOl#xO!o!_O!p!`O!q!aO!r!bO~O!o!_O!p!`O!q!aO!r!bOl{i|{i~Ol#yO~P$lOw#zO!o!_O!p!`O!q!aO!r!bO~Ol#|O~P-aOr!S!i!Q`a!U!V!W!X!TT}yxy~",
goto: "-}!{PP!|#v$WPPP$t%x&T&f(dPPPPP)`PPPPPPPP(dPP*^+YP*^PPP*^PPPP*^,XPP*^PP,[PPPPPPPPPPPPPPPPPP#v-WPP-WP-hP#vPPPP-k-n-qPPP-w(dSgO#UQxTQ!XcQ#O!_Q#P!`Q#Q!aQ#R!bQ#h#[Q#k#`Q#l#eQ#t#nQ#u#oR#w#qmhOTc!_!`!a!b#U#[#`#e#n#o#qlbOTc!_!`!a!b#U#[#`#e#n#o#qQ![dQ!v!RQ!x!TR#_!{!U[Uklmnopqrs{!O!Q!S!U!V!Y!^!p!r!|#S#Z#]#f#zl]OTc!_!`!a!b#U#[#`#e#n#o#qX!Zd!R!T!{UvT{!YX}U!Q!U!^UwT{!YQ|UQ!u!QQ!y!UR!}!^SSO#UQtT[zU{!Q!U!Y!^d!Wc!_!`!a!b#[#`#e#n#oQ!fkQ!glQ!hmQ!inQ!joQ!kpQ!lqQ!mrQ!nsQ!t!OQ!w!SQ!z!VQ#V!pQ#W!rQ#`!|Q#b#SQ#g#ZQ#j#]Q#m#fQ#v#qR#{#z!s_OTUcklmnopqrs{!O!Q!S!U!V!Y!^!_!`!a!b!p!r!|#S#U#Z#[#]#`#e#f#n#o#q#z!n_OUcklmnopqrs!O!Q!S!U!V!Y!^!_!`!a!b!p!r!|#S#U#Z#[#]#`#e#f#n#o#q#zTuT{!s[OTUcklmnopqrs{!O!Q!S!U!V!Y!^!_!`!a!b!p!r!|#S#U#Z#[#]#`#e#f#n#o#q#z!r[OTUcklmnopqrs{!O!Q!S!U!V!Y!^!_!`!a!b!p!r!|#S#U#Z#[#]#`#e#f#n#o#q#zR!PVR#i#[!saOTUcklmnopqrs{!O!Q!S!U!V!Y!^!_!`!a!b!p!r!|#S#U#Z#[#]#`#e#f#n#o#q#zmeOTc!_!`!a!b#U#[#`#e#n#o#qR!]dRjOR!eiQ!diR#c#TQjOR#d#U",
nodeNames: "⚠ Expression Logic Logic_predicates Variable Local ) ( Tuple Setexpr_enum_min2 Setexpr_enum Setexpr Literal Integer EmptySet IntegerSet Global Radical Setexpr_binary + - * \\ ∆ ∩ × Setexpr_generators } { Enumeration Boolean Filter_expression Filter [ ] Declarative ∈ | PrefixD PrefixI Imperative Imp_blocks ; PrefixR Recursion := Function TextFunction BigPr SmallPr Card Bool Debool Red , :∈ ∉ ⊆ ⊄ ⊂ > ≥ < ≤ ≠ = Logic_unary Negation ¬ Predicate Logic_quantor ∀ Variable_pack ∃ Logic_binary ⇔ ⇒ & Function_decl Arguments Declaration",
states:
"2xO!sQPOOOOQO'#Ch'#ChO#lQPO'#EWOOQO'#EW'#EWO%yQPO'#EVOVQPO'#CnO'_QPO'#CzO'fQPO'#C{O'nQPO'#C}O'sQPO'#DRO'xQPO'#DWO'}QPO'#D[OOQO'#Cw'#CwO(SQPO'#CwOOQO'#D_'#D_OOQO'#Cg'#CgO)pQPO'#CgO)uQPO'#CgO)zQPO'#C_OVQPO'#DsO*SQPO'#DvOOQO'#Dr'#DrO*[QPO'#DrO*aQPO'#EVOOQO'#C^'#C^O*rQPO'#EPQOQPOOO*wQPO,59YO*wQPO,59YO*wQPO,59YO*wQPO,59YO*wQPO,59YO*wQPO,59YO*wQPO,59YO*wQPO,59YO*wQPO,58yO+OQPO'#CfO+VQPO,59YO,jQPO,59OO,rQPO'#CeO,wQPO,58xO-YQPO,59mO-{QPO'#CfO*wQPO'#CnO.YQPO,59fOOQO'#Cf'#CfO*wQPO,59gOOQO,59g,59gO*wQPO,59iO*SQPO,59mO*wQPO,59rO*SQPO,59vO*wQPO,59RO*wQPO,59RO$lQPO'#C_O.bQPO,5:_O*wQPO'#CdOOQO'#C`'#C`OOQO'#Dx'#DxO/PQPO,5:bO*wQPO,5:^OVQPO,5:fOVQPO,5:fOVQPO,5:fOVQPO,5:fO/XQPO'#EROOQO'#EQ'#EQO/^QPO,5:kOOQO1G.t1G.tO2}QPO1G.tO3UQPO1G.tO6tQPO1G.tO:dQPO1G.tO:kQPO1G.tO:rQPO1G.tO:yQPO1G.tO>eQPO1G.eOOQO1G.j1G.jO*wQPO,59POOQO1G.d1G.dO*wQPO1G/XOOQO1G/Q1G/QO?UQPO1G/RO?]QPO1G/TO?eQPO1G/XO?jQPO1G/^O?qQPO1G/bO?vQPO1G.mO@OQPO1G.mO*SQPO,5:dO*wQPO1G/|O@VQPO1G/xO@yQPO1G0QOAQQPO1G0QOAXQPO1G0QOOQO1G0Q1G0QO*wQPO,5:mO*rQPO,5:lOVQPO1G0VOAvQPO1G.kOBWQPO7+$sOOQO7+$m7+$mOB_QPO7+$oO*wQPO7+$sOVQPO7+$xO*wQPO7+$|OOQO7+$X7+$XOOQO1G0O1G0OOBdQPO7+%hOOQO7+%d7+%dOCQQPO1G0XOOQO1G0W1G0WOOQO7+%q7+%qOVQPO<<H_O*wQPO<<HZOC[QPO<<H_OCcQPO'#DXOCwQPO<<HdODPQPO<<HhODWQPO<<ISODuQPOAN=yOEWQPOAN=uOVQPOAN=yOVQPO,59sOOQOAN>OAN>OOVQPOAN>SOOQOG23eG23eOOQOG23aG23aOE_QPOG23eOEpQPO1G/_OFUQPOG23nOF]QPOG23nOOQOLD)PLD)POOQOLD)YLD)YO*wQPOLD)YOFnQPO!$'LtOOQO!)9B`!)9B`",
stateData:
'GW~O!xOS~OTQOVTO]PO^PO_PO`ROaROmUOpVOrWOxXOyYO}ZO!Q`O!S^O!T^O!U^O!V^O!W^O!X^O!hcO!ifO!kdO!mdO~OsiO~PVOc!zXd!zXe!zXf!zXg!zXh!zXi!zXj!zX!Y!zXl!zX~Ov!zX!PSX!ZSX![!zX!]!zX!^!zX!_!zX!`!zX!a!zX!b!zX!c!zX!d!zX!e!zX!v!zX~P!zOckOdlOemOfnOgoOhpOiqOjrOvsO![sO!]sO!^sO!_sO!`sO!asO!bsO!csO!dsO!esO~O!v!yX~P$lOV{O]PO^PO_PO`ROaROmUOpVOrWOxXOyYO}ZO!Q`O!S^O!T^O!U^O!V^O!W^O!X^O~OTyO~P&QOV!OOpVO~Os!QO~Om!RO~Om!SO~Om!TO~OckXdkXekXfkXgkXhkXikXjkXvkX!PSX!ZSX![kX!]kX!^kX!_kX!`kX!akX!bkX!ckX!dkX!ekX!vkX!YkXlkX~Os!UO~OV!VO~O!PsO!ZsO~OT!ZOV!YO~Os!^O~O!o!_O!p!`O!q!aO!r!bO!v!yX~OT!cO~OTRO~P&QO!YYX~P$lOU!fOcZXdZXeZXfZXgZXhZXiZXjZXvZX!YZX![ZX!]ZX!^ZX!_ZX!`ZX!aZX!bZX!cZX!dZX!eZX~OU!oO!YYX~O!Y!pO~OU!qO!o!_O!p!`O!q!aO!r!bO~Ov!rO~P!zOckOdlOemOfnOgoOhpOiqOjrO~OlYX!YYXtYX~P-aOl!sO!Y!pO~O!o!_O!p!`O!q!aO!r!bO!v!gaU!gal!ga|!gaw!ga~Ov!|O!Y!{O~Ov#SO~Ot#UO!Y#TO~OckOebifbigbihbiibijbivbi![bi!]bi!^bi!_bi!`bi!abi!bbi!cbi!dbi!ebi!vbiUbi!Ybilbi!obi!pbi!qbi!rbitbiwbiTbiVbi]bi^bi_bi`biabimbipbirbixbiybi}bi!Qbi!Sbi!Tbi!Ubi!Vbi!Wbi!Xbi!hbi!ibi!kbi!mbi|bi~Odbi~P/fOdlO~P/fOckOdlOemOjrOgbihbiibivbi![bi!]bi!^bi!_bi!`bi!abi!bbi!cbi!dbi!ebi!vbiUbi!Ybilbi!obi!pbi!qbi!rbitbiwbiTbiVbi]bi^bi_bi`biabimbipbirbixbiybi}bi!Qbi!Sbi!Tbi!Ubi!Vbi!Wbi!Xbi!hbi!ibi!kbi!mbi|bi~Ofbi~P3]OckOdlOemOfnOiqOjrOhbivbi![bi!]bi!^bi!_bi!`bi!abi!bbi!cbi!dbi!ebi!vbiUbi!Ybilbi!obi!pbi!qbi!rbitbiwbiTbiVbi]bi^bi_bi`biabimbipbirbixbiybi}bi!Qbi!Sbi!Tbi!Ubi!Vbi!Wbi!Xbi!hbi!ibi!kbi!mbi|bi~Ogbi~P6{OgoO~P6{OfnO~P3]OckOdlOemOfbigbihbiibijbivbi![bi!]bi!^bi!_bi!`bi!abi!bbi!cbi!dbi!ebi!vbiUbi!Ybilbi!obi!pbi!qbi!rbitbiwbiTbiVbi]bi^bi_bi`biabimbipbirbixbiybi}bi!Qbi!Sbi!Tbi!Ubi!Vbi!Wbi!Xbi!hbi!ibi!kbi!mbi|bi~O!oRi!pRi!qRi!rRi!vRiURilRi|RiwRi~P-aOU#XO~P-aOt#YO!Y!pO~Ov#ZO~Ow#[O~P-aO!P#]O~Ot#^O!Y!pO~OU#^O~P-aOt#aO!Y!pO~O!q!aO!r!bO!o!ni!v!niU!nil!ni|!niw!ni~O!p!`O~P@_O!p!ni~P@_O!r!bO!o!ni!p!ni!q!ni!v!niU!nil!ni|!niw!ni~OUXi!YXilXitXi~P-aOw#eO~P-aOV#fO~OckOdlOemOfnOgoOhpOiqOjrO~PVOt!ui!Y!ui~P-aOw#nO~P-aO!o!_O!p!`O!q!aO!r!bOl{X|{X~Ol#pO|#oO~Ow#qO~P-aO!o!_O!p!`O!q!aO!r!bO!v!jyU!jyl!jy|!jyw!jy~Ol#rO!o!_O!p!`O!q!aO!r!bO~OU#sO~P-aOl#xO!o!_O!p!`O!q!aO!r!bO~O!o!_O!p!`O!q!aO!r!bOl{i|{i~Ol#yO~P$lOw#zO!o!_O!p!`O!q!aO!r!bO~Ol#|O~P-aOr!S!i!Q`a!U!V!W!X!TT}yxy~',
goto: '-}!{PP!|#v$WPPP$t%x&T&f(dPPPPP)`PPPPPPPP(dPP*^+YP*^PPP*^PPPP*^,XPP*^PP,[PPPPPPPPPPPPPPPPPP#v-WPP-WP-hP#vPPPP-k-n-qPPP-w(dSgO#UQxTQ!XcQ#O!_Q#P!`Q#Q!aQ#R!bQ#h#[Q#k#`Q#l#eQ#t#nQ#u#oR#w#qmhOTc!_!`!a!b#U#[#`#e#n#o#qlbOTc!_!`!a!b#U#[#`#e#n#o#qQ![dQ!v!RQ!x!TR#_!{!U[Uklmnopqrs{!O!Q!S!U!V!Y!^!p!r!|#S#Z#]#f#zl]OTc!_!`!a!b#U#[#`#e#n#o#qX!Zd!R!T!{UvT{!YX}U!Q!U!^UwT{!YQ|UQ!u!QQ!y!UR!}!^SSO#UQtT[zU{!Q!U!Y!^d!Wc!_!`!a!b#[#`#e#n#oQ!fkQ!glQ!hmQ!inQ!joQ!kpQ!lqQ!mrQ!nsQ!t!OQ!w!SQ!z!VQ#V!pQ#W!rQ#`!|Q#b#SQ#g#ZQ#j#]Q#m#fQ#v#qR#{#z!s_OTUcklmnopqrs{!O!Q!S!U!V!Y!^!_!`!a!b!p!r!|#S#U#Z#[#]#`#e#f#n#o#q#z!n_OUcklmnopqrs!O!Q!S!U!V!Y!^!_!`!a!b!p!r!|#S#U#Z#[#]#`#e#f#n#o#q#zTuT{!s[OTUcklmnopqrs{!O!Q!S!U!V!Y!^!_!`!a!b!p!r!|#S#U#Z#[#]#`#e#f#n#o#q#z!r[OTUcklmnopqrs{!O!Q!S!U!V!Y!^!_!`!a!b!p!r!|#S#U#Z#[#]#`#e#f#n#o#q#zR!PVR#i#[!saOTUcklmnopqrs{!O!Q!S!U!V!Y!^!_!`!a!b!p!r!|#S#U#Z#[#]#`#e#f#n#o#q#zmeOTc!_!`!a!b#U#[#`#e#n#o#qR!]dRjOR!eiQ!diR#c#TQjOR#d#U',
nodeNames:
'⚠ Expression Logic Logic_predicates Variable Local ) ( Tuple Setexpr_enum_min2 Setexpr_enum Setexpr Literal Integer EmptySet IntegerSet Global Radical Setexpr_binary * + - \\ ∆ ∩ × Setexpr_generators } { Enumeration Boolean Filter_expression Filter [ ] Declarative ∈ | PrefixD PrefixI Imperative Imp_blocks ; PrefixR Recursion := Function TextFunction BigPr SmallPr Card Bool Debool Red , :∈ ∉ ⊆ ⊄ ⊂ > ≥ < ≤ ≠ = Logic_unary Negation ¬ Predicate Logic_quantor ∀ Variable_pack ∃ Logic_binary ⇔ ⇒ & Function_decl Arguments Declaration',
maxTerm: 88,
nodeProps: [
["openedBy", 6,"(",28,"{"],
["closedBy", 7,")",29,"}"]
['openedBy', 6, '(', 28, '{'],
['closedBy', 7, ')', 29, '}']
],
skippedNodes: [0],
repeatNodeCount: 0,
tokenData: "6g~R!iX^%ppq%pvw&exy&jyz&oz{&t{|&y|}'O}!O'T!Q!['Y![!]'b!]!^'u!^!_'z!_!`(P!`!a(U!c!d(Z!e!f(Z!f!g(i!h!i(q!k!l)e!r!s)j!t!u*^!u!v(Z!v!w(Z!z!{(Z!|!}*n!}#O*s#O#P*x#P#Q*}#R#S+S#T#U+S#U#V+j#V#W-Y#W#X.u#X#d+S#d#e1_#e#f+S#f#g2t#g#o+S#o#p4O#p#q4T#q#r4Y#y#z%p$f$g%p$r$s4_%o%p4d5i6S+S#BY#BZ%p$IS$I_%p$I|$JO%p$JT$JU%p$KV$KW%p% l% m4i%%Y%%Z4n%%[%%]4s%&Y%&Z4x%&]%&^4}%&_%&`5S%&`%&a5X%&b%&c5^%&c%&d5c%'S%'T5h%'T%'U5m%'U%'V5r%(^%(_5w%(b%(c5|%(c%(d6R%)Q%)R6W%)S%)T6]%)U%)V6b&FU&FV%p~%uY!x~X^%ppq%p#y#z%p$f$g%p#BY#BZ%p$IS$I_%p$I|$JO%p$JT$JU%p$KV$KW%p&FU&FV%p~&jO!r~~&oOV~~&tOU~~&yOe~~'OOc~~'TO!Y~~'YOd~~'_P]~!Q!['Y~'eQ!_!`'k%&b%&c'p~'pO!P~~'uO!Z~~'zO|~~(PO!b~~(UO!e~~(ZO!`~~(^P!Q![(a~(fP`~!Q![(a~(nPx~!Q![(a~(tQ!Q![(z#]#^)S~)PP!Q~!Q![(z~)VP!R![)Y~)_Qr~|})S!Q![)Y~)jOy~~)mQ!Q![)s#f#g){~)xP!i~!Q![)s~*OP!R![*R~*WQ!S~|}){!Q![*R~*cP}~!Q![*f~*kPa~!Q![*f~*sO_~~*xOs~~*}Og~~+SOt~~+XRT~!Q![+b#T#o+S5i6S+S~+gPT~!Q![+b~+oTT~!Q![+b#T#c+S#c#d,O#d#o+S5i6S+S~,TTT~!Q![+b#T#c+S#c#d,d#d#o+S5i6S+S~,iTT~!Q![+b#T#`+S#`#a,x#a#o+S5i6S+S~-PR!V~T~!Q![+b#T#o+S5i6S+S~-_ST~!Q![+b#T#U-k#U#o+S5i6S+S~-pTT~!Q![+b#T#f+S#f#g.P#g#o+S5i6S+S~.UTT~!Q![+b#T#W+S#W#X.e#X#o+S5i6S+S~.lR!U~T~!Q![+b#T#o+S5i6S+S~.zTT~!Q![+b#T#X+S#X#Y/Z#Y#o+S5i6S+S~/`TT~!Q![+b#T#U+S#U#V/o#V#o+S5i6S+S~/tTT~!Q![+b#T#c+S#c#d0T#d#o+S5i6S+S~0YTT~!Q![+b#T#c+S#c#d0i#d#o+S5i6S+S~0nTT~!Q![+b#T#`+S#`#a0}#a#o+S5i6S+S~1UR!W~T~!Q![+b#T#o+S5i6S+S~1dTT~!Q![+b#T#f+S#f#g1s#g#o+S5i6S+S~1xST~!Q!R+b!R![2U#T#o+S5i6S+S~2]Q!T~T~|}2c!Q![2U~2fP!R![2i~2nQ!T~|}2c!Q![2i~2yTT~!Q![+b#T#X+S#X#Y3Y#Y#o+S5i6S+S~3_TT~!Q![+b#T#W+S#W#X3n#X#o+S5i6S+S~3uR!X~T~!Q![+b#T#o+S5i6S+S~4TOm~~4YOw~~4_Ol~~4dO!h~~4iOj~~4nOp~~4sO!p~~4xO!o~~4}O!k~~5SO!m~~5XO^~~5^Oh~~5cOv~~5hO![~~5mO!q~~5rOi~~5wOf~~5|O!d~~6RO!c~~6WO!a~~6]O!_~~6bO!^~~6gO!]~",
tokenData:
"6g~R!iX^%ppq%pvw&exy&jyz&oz{&t{|&y|}'O}!O'T!Q!['Y![!]'b!]!^'u!^!_'z!_!`(P!`!a(U!c!d(Z!e!f(Z!f!g(i!h!i(q!k!l)e!r!s)j!t!u*^!u!v(Z!v!w(Z!z!{(Z!|!}*n!}#O*s#O#P*x#P#Q*}#R#S+S#T#U+S#U#V+j#V#W-Y#W#X.u#X#d+S#d#e1_#e#f+S#f#g2t#g#o+S#o#p4O#p#q4T#q#r4Y#y#z%p$f$g%p$r$s4_%o%p4d5i6S+S#BY#BZ%p$IS$I_%p$I|$JO%p$JT$JU%p$KV$KW%p% l% m4i%%Y%%Z4n%%[%%]4s%&Y%&Z4x%&]%&^4}%&_%&`5S%&`%&a5X%&b%&c5^%&c%&d5c%'S%'T5h%'T%'U5m%'U%'V5r%(^%(_5w%(b%(c5|%(c%(d6R%)Q%)R6W%)S%)T6]%)U%)V6b&FU&FV%p~%uY!x~X^%ppq%p#y#z%p$f$g%p#BY#BZ%p$IS$I_%p$I|$JO%p$JT$JU%p$KV$KW%p&FU&FV%p~&jO!r~~&oOV~~&tOU~~&yOc~~'OOd~~'TO!Y~~'YOe~~'_P]~!Q!['Y~'eQ!_!`'k%&b%&c'p~'pO!P~~'uO!Z~~'zO|~~(PO!b~~(UO!e~~(ZO!`~~(^P!Q![(a~(fP`~!Q![(a~(nPx~!Q![(a~(tQ!Q![(z#]#^)S~)PP!Q~!Q![(z~)VP!R![)Y~)_Qr~|})S!Q![)Y~)jOy~~)mQ!Q![)s#f#g){~)xP!i~!Q![)s~*OP!R![*R~*WQ!S~|}){!Q![*R~*cP}~!Q![*f~*kPa~!Q![*f~*sO_~~*xOs~~*}Og~~+SOt~~+XRT~!Q![+b#T#o+S5i6S+S~+gPT~!Q![+b~+oTT~!Q![+b#T#c+S#c#d,O#d#o+S5i6S+S~,TTT~!Q![+b#T#c+S#c#d,d#d#o+S5i6S+S~,iTT~!Q![+b#T#`+S#`#a,x#a#o+S5i6S+S~-PR!V~T~!Q![+b#T#o+S5i6S+S~-_ST~!Q![+b#T#U-k#U#o+S5i6S+S~-pTT~!Q![+b#T#f+S#f#g.P#g#o+S5i6S+S~.UTT~!Q![+b#T#W+S#W#X.e#X#o+S5i6S+S~.lR!U~T~!Q![+b#T#o+S5i6S+S~.zTT~!Q![+b#T#X+S#X#Y/Z#Y#o+S5i6S+S~/`TT~!Q![+b#T#U+S#U#V/o#V#o+S5i6S+S~/tTT~!Q![+b#T#c+S#c#d0T#d#o+S5i6S+S~0YTT~!Q![+b#T#c+S#c#d0i#d#o+S5i6S+S~0nTT~!Q![+b#T#`+S#`#a0}#a#o+S5i6S+S~1UR!W~T~!Q![+b#T#o+S5i6S+S~1dTT~!Q![+b#T#f+S#f#g1s#g#o+S5i6S+S~1xST~!Q!R+b!R![2U#T#o+S5i6S+S~2]Q!T~T~|}2c!Q![2U~2fP!R![2i~2nQ!T~|}2c!Q![2i~2yTT~!Q![+b#T#X+S#X#Y3Y#Y#o+S5i6S+S~3_TT~!Q![+b#T#W+S#W#X3n#X#o+S5i6S+S~3uR!X~T~!Q![+b#T#o+S5i6S+S~4TOm~~4YOw~~4_Ol~~4dO!h~~4iOj~~4nOp~~4sO!p~~4xO!o~~4}O!k~~5SO!m~~5XO^~~5^Oh~~5cOv~~5hO![~~5mO!q~~5rOi~~5wOf~~5|O!d~~6RO!c~~6WO!a~~6]O!_~~6bO!^~~6gO!]~",
tokenizers: [0],
topRules: {"Expression":[0,1]},
tokenPrec: 1710
})
topRules: { Expression: [0, 1] },
tokenPrec: 1739
});

View File

@ -1,6 +1,5 @@
// This file was generated by lezer-generator. You probably shouldn't edit it.
export const
Expression = 1,
export const Expression = 1,
Local = 2,
Literal = 4,
Global = 5,
@ -12,4 +11,4 @@ export const
PrefixR = 28,
Function = 30,
TextFunction = 31,
Predicate = 44
Predicate = 44;

View File

@ -4,19 +4,19 @@ import { highlighting } from './highlight';
export const parser = LRParser.deserialize({
version: 14,
states:
"3UO!sQPOOOOQO'#C`'#C`O#lQPO'#DoOOQO'#Do'#DoO%yQPO'#DdOVQPO'#DpO'_QPO'#DrO'fQPO'#DsO'nQQO'#DtO'sQPO'#DuO'xQPO'#DvO'}QPO'#DxOOQO'#Dq'#DqO(SQPO'#DqO)pQQO'#C{OOQO'#C{'#C{OOQO'#Dk'#DkO)uQPO'#DkO)zQPO'#DkO*PQPO'#DfOVQPO'#EQO*XQPO'#EQO*^QPO'#EQO*fQPO'#DdOOQO'#De'#DeO*wQPO'#ETQOQPOOO*|QPO,5:[O*|QPO,5:[O*|QPO,5:[O*|QPO,5:[O*|QPO,5:[O*|QPO,5:[O*|QPO,5:[O*|QPO,5:[O*|QPO,5:QO+TQPO'#DjO+[QPO,5:[O,oQPO,5:SO,wQPO'#DiO,|QPO,5:PO-_QPO,5:aO.QQPO'#DjO*|QPO'#DpO._QPO,5:^OOQO'#Dj'#DjO*|QPO,5:_OOQO,5:_,5:_O.gQPO,5:`O*^QPO,5:aO*|QPO,5:bO*^QPO,5:dOOQO,59g,59gO*|QPO,5:VO*|QPO,5:VO$lQPO'#DfOOQO,5:l,5:lO*|QPO,5:lO*|QPO'#DhOOQO'#Dg'#DgOOQO'#ER'#ERO.lQPO,5:lOVQPO,5:nOVQPO,5:nOVQPO,5:nOVQPO,5:nO.tQPO'#EVOOQO'#EU'#EUO.yQPO,5:oOOQO1G/v1G/vO2jQPO1G/vO2qQPO1G/vO6aQPO1G/vO:PQPO1G/vO:WQPO1G/vO:_QPO1G/vO:fQPO1G/vO>QQPO1G/lOOQO1G/n1G/nO*|QPO,5:TOOQO1G/k1G/kO*|QPO1G/{OOQO1G/x1G/xO>qQPO1G/yO*|QPO1G/zO>xQPO1G/{O>}QPO1G/|O?UQPO1G0OO?ZQPO1G/qO?cQPO1G/qO?jQPO1G0WO*^QPO,5:mO*|QPO1G0WOOQO1G0Y1G0YO@^QPO1G0YO@eQPO1G0YO@lQPO1G0YO*|QPO,5:qO*wQPO,5:pOVQPO1G0ZOAZQPO1G/oOAkQPO7+%gOOQO7+%e7+%eOArQPO7+%fO*|QPO7+%gOVQPO7+%hO*|QPO7+%jOOQO7+%]7+%]OOQO7+%r7+%rOOQO1G0X1G0XOAzQPO7+%rOBhQPO1G0]OOQO1G0[1G0[OOQO7+%u7+%uOVQPO<<IROBrQPO<<IQOBwQPO<<IROCOQPO'#DwOCdQPO<<ISOClQPO<<IUOCsQPO<<I^ODbQPOAN>mO*|QPOAN>lOVQPOAN>mOVQPO,5:cOOQOAN>nAN>nOVQPOAN>pOOQOG24XG24XODsQPOG24WODzQPOG24XOE]QPO1G/}OEqQPOG24[OExQPOG24[OOQOLD)rLD)rOOQOLD)sLD)sOOQOLD)vLD)vO*|QPOLD)vOFZQPO!$'MbOOQO!)9B|!)9B|",
"3UO!sQPOOOOQO'#C`'#C`O#lQPO'#DoOOQO'#Do'#DoO%yQPO'#DdOVQPO'#DpO'_QPO'#DrO'fQPO'#DsO'nQQO'#DtO'sQPO'#DuO'xQPO'#DvO'}QPO'#DxOOQO'#Dq'#DqO(SQPO'#DqO)pQQO'#C{OOQO'#C{'#C{OOQO'#Dk'#DkO)uQPO'#DkO)zQPO'#DkO*PQPO'#DfOVQPO'#EQO*XQPO'#EQO*^QPO'#EQO*fQPO'#DdOOQO'#De'#DeO*wQPO'#ETQOQPOOO*|QPO,5:[O*|QPO,5:[O*|QPO,5:[O*|QPO,5:[O*|QPO,5:[O*|QPO,5:[O*|QPO,5:[O*|QPO,5:[O*|QPO,5:QO+TQPO'#DjO+[QPO,5:[O,oQPO,5:SO,wQPO'#DiO,|QPO,5:PO-_QPO,5:aO.QQPO'#DjO*|QPO'#DpO._QPO,5:^OOQO'#Dj'#DjO*|QPO,5:_OOQO,5:_,5:_O.gQPO,5:`O*^QPO,5:aO*|QPO,5:bO*^QPO,5:dOOQO,59g,59gO*|QPO,5:VO*|QPO,5:VO$lQPO'#DfO.lQPO,5:lO*|QPO,5:lO*|QPO'#DhOOQO'#Dg'#DgOOQO'#ER'#ERO/ZQPO,5:lOVQPO,5:nOVQPO,5:nOVQPO,5:nOVQPO,5:nO/cQPO'#EVOOQO'#EU'#EUO/hQPO,5:oOOQO1G/v1G/vO3XQPO1G/vO3`QPO1G/vO7OQPO1G/vO:nQPO1G/vO:uQPO1G/vO:|QPO1G/vO;TQPO1G/vO>oQPO1G/lOOQO1G/n1G/nO*|QPO,5:TOOQO1G/k1G/kO*|QPO1G/{OOQO1G/x1G/xO?`QPO1G/yO*|QPO1G/zO?gQPO1G/{O?lQPO1G/|O?sQPO1G0OO?xQPO1G/qO@QQPO1G/qO@XQPO1G0WO*^QPO,5:mO*|QPO1G0WO@{QPO1G0YOASQPO1G0YOAZQPO1G0YOOQO1G0Y1G0YO*|QPO,5:qO*wQPO,5:pOVQPO1G0ZOAxQPO1G/oOBYQPO7+%gOOQO7+%e7+%eOBaQPO7+%fO*|QPO7+%gOVQPO7+%hO*|QPO7+%jOOQO7+%]7+%]OOQO7+%r7+%rOOQO1G0X1G0XOBiQPO7+%rOCVQPO1G0]OOQO1G0[1G0[OOQO7+%u7+%uOVQPO<<IROCaQPO<<IQOCfQPO<<IROCmQPO'#DwODRQPO<<ISODZQPO<<IUODbQPO<<I^OEPQPOAN>mO*|QPOAN>lOVQPOAN>mOVQPO,5:cOOQOAN>nAN>nOVQPOAN>pOOQOG24XG24XOEbQPOG24WOEiQPOG24XOEzQPO1G/}OF`QPOG24[OFgQPOG24[OOQOLD)rLD)rOOQOLD)sLD)sOOQOLD)vLD)vO*|QPOLD)vOFxQPO!$'MbOOQO!)9B|!)9B|",
stateData:
'Fs~O!VOS~OQQORTOTROURO`UObVOcWOiXOjYOlZOnaO{dO|eO}fO!OfO!`PO!aPO!bPO!m^O!n^O!o_O!p_O!q_O!r_O~OeiO~PVOV!cXW!cXX!cXY!cXZ!cX[!cX]!cX^!cX!s!cXa!cX~Og!cXm!ZXp!ZXq!cXr!cXs!cXt!cXu!cXv!cXw!cXx!cXy!cXz!cX!T!cX~P!zOVkOWlOXmOYnOZoO[pO]qO^rOgsOqsOrsOssOtsOusOvsOwsOxsOysOzsO~O!T!WX~P$lOR{OTROURO`UObVOcWOiXOjYOlZOnaO!`PO!aPO!bPO!m^O!n^O!o_O!p_O!q_O!r_O~OQyO~P&QOR!OObVO~Od!QO~O`!RO~O`!SO~O`!TO~OV!eXW!eXX!eXY!eXZ!eX[!eX]!eX^!eXg!eXm!ZXp!ZXq!eXr!eXs!eXt!eXu!eXv!eXw!eXx!eXy!eXz!eX!T!eX!s!eXa!eX~Od!UO~Oe!VO~OR!WO~OmsOpsO~Oe!ZO~OQ!]OR![O~O!P!`O!Q!aO!R!bO!S!cO!T!WX~OQ!dO~OQRO~P&QO!s!^X~P$lO_!gOV!_XW!_XX!_XY!_XZ!_X[!_X]!_X^!_Xg!_Xq!_Xr!_Xs!_Xt!_Xu!_Xv!_Xw!_Xx!_Xy!_Xz!_X!s!_X~O_!pO!s!^X~O!s!qO~O_!rO!P!`O!Q!aO!R!bO!S!cO~Og!sO~P!zOVkOWlOXmOYnOZoO[pO]qO^rO~Oa!^X!s!^Xf!^X~P-fOa!tO!s!qO~Oe!vO~Og#OO!s!}O~Og#TO~Of#VO!s#UO~OVkOX!diY!diZ!di[!di]!di^!dig!diq!dir!dis!dit!diu!div!diw!dix!diy!diz!di!T!di_!di!s!dia!di!P!di!Q!di!R!di!S!dih!dif!diQ!diR!diT!diU!di`!dib!dic!dii!dij!dil!din!di{!di|!di}!di!O!di!`!di!a!di!b!di!m!di!n!di!o!di!p!di!q!di!r!dik!di~OW!di~P/ROWlO~P/ROVkOWlOXmO^rOZ!di[!di]!dig!diq!dir!dis!dit!diu!div!diw!dix!diy!diz!di!T!di_!di!s!dia!di!P!di!Q!di!R!di!S!dih!dif!diQ!diR!diT!diU!di`!dib!dic!dii!dij!dil!din!di{!di|!di}!di!O!di!`!di!a!di!b!di!m!di!n!di!o!di!p!di!q!di!r!dik!di~OY!di~P2xOVkOWlOXmOYnO]qO^rO[!dig!diq!dir!dis!dit!diu!div!diw!dix!diy!diz!di!T!di_!di!s!dia!di!P!di!Q!di!R!di!S!dih!dif!diQ!diR!diT!diU!di`!dib!dic!dii!dij!dil!din!di{!di|!di}!di!O!di!`!di!a!di!b!di!m!di!n!di!o!di!p!di!q!di!r!dik!di~OZ!di~P6hOZoO~P6hOYnO~P2xOVkOWlOXmOY!diZ!di[!di]!di^!dig!diq!dir!dis!dit!diu!div!diw!dix!diy!diz!di!T!di_!di!s!dia!di!P!di!Q!di!R!di!S!dih!dif!diQ!diR!diT!diU!di`!dib!dic!dii!dij!dil!din!di{!di|!di}!di!O!di!`!di!a!di!b!di!m!di!n!di!o!di!p!di!q!di!r!dik!di~O!P!Yi!Q!Yi!R!Yi!S!Yi!T!Yi_!Yia!Yik!Yih!Yi~P-fO_#YO~P-fOg#[O~Oh#]O~P-fOm#^O~Of#_O!s!qO~O_#_O~P-fOf#`O!s!qO~O!P!`O!R!vi!S!vi!T!vi_!via!vik!vih!vi~O!Q!vi~P?rO!Q!aO~P?rO!P!`O!Q!aO!R!bO!S!vi!T!vi_!via!vik!vih!vi~O_!]i!s!]ia!]if!]i~P-fOh#fO~P-fOf#gO!s!qO~OVkOWlOXmOYnOZoO[pO]qO^rO~PVOf!yi!s!yi~P-fOR#nO~Oh#oO~P-fO!P!`O!Q!aO!R!bO!S!cOa!kXk!kX~Oa#qOk#pO~Oh#rO~P-fO!P!`O!Q!aO!R!bO!S!cO!T!ty_!tya!tyk!tyh!ty~Oa#sO!P!`O!Q!aO!R!bO!S!cO~O_#yO~P-fOa#zO!P!`O!Q!aO!R!bO!S!cO~O!P!`O!Q!aO!R!bO!S!cOa!kik!ki~Oa#{O~P$lOh#|O!P!`O!Q!aO!R!bO!S!cO~Oa$OO~P-fOc!m|nTU!o!p!q!r!nQljij~',
'Gb~O!VOS~OQQORTOTROURO`UObVOcWOiXOjYOlZOnaO{dO|eO}fO!OfO!`PO!aPO!bPO!m^O!n^O!o_O!p_O!q_O!r_O~OeiO~PVOV!cXW!cXX!cXY!cXZ!cX[!cX]!cX^!cX!s!cXa!cX~Og!cXm!ZXp!ZXq!cXr!cXs!cXt!cXu!cXv!cXw!cXx!cXy!cXz!cX!T!cX~P!zOVkOWlOXmOYnOZoO[pO]qO^rOgsOqsOrsOssOtsOusOvsOwsOxsOysOzsO~O!T!WX~P$lOR{OTROURO`UObVOcWOiXOjYOlZOnaO!`PO!aPO!bPO!m^O!n^O!o_O!p_O!q_O!r_O~OQyO~P&QOR!OObVO~Od!QO~O`!RO~O`!SO~O`!TO~OV!eXW!eXX!eXY!eXZ!eX[!eX]!eX^!eXg!eXm!ZXp!ZXq!eXr!eXs!eXt!eXu!eXv!eXw!eXx!eXy!eXz!eX!T!eX!s!eXa!eX~Od!UO~Oe!VO~OR!WO~OmsOpsO~Oe!ZO~OQ!]OR![O~O!P!`O!Q!aO!R!bO!S!cO!T!WX~OQ!dO~OQRO~P&QO!s!^X~P$lO_!gOV!_XW!_XX!_XY!_XZ!_X[!_X]!_X^!_Xg!_Xq!_Xr!_Xs!_Xt!_Xu!_Xv!_Xw!_Xx!_Xy!_Xz!_X!s!_X~O_!pO!s!^X~O!s!qO~O_!rO!P!`O!Q!aO!R!bO!S!cO~Og!sO~P!zOVkOWlOXmOYnOZoO[pO]qO^rO~Oa!^X!s!^Xf!^X~P-fOa!tO!s!qO~Oe!vO~O!P!`O!Q!aO!R!bO!S!cO!T!ta_!taa!tak!tah!ta~Og#OO!s!}O~Og#TO~Of#VO!s#UO~OVkOX!diY!diZ!di[!di]!di^!dig!diq!dir!dis!dit!diu!div!diw!dix!diy!diz!di!T!di_!di!s!dia!di!P!di!Q!di!R!di!S!dih!dif!diQ!diR!diT!diU!di`!dib!dic!dii!dij!dil!din!di{!di|!di}!di!O!di!`!di!a!di!b!di!m!di!n!di!o!di!p!di!q!di!r!dik!di~OW!di~P/pOWlO~P/pOVkOWlOXmO^rOZ!di[!di]!dig!diq!dir!dis!dit!diu!div!diw!dix!diy!diz!di!T!di_!di!s!dia!di!P!di!Q!di!R!di!S!dih!dif!diQ!diR!diT!diU!di`!dib!dic!dii!dij!dil!din!di{!di|!di}!di!O!di!`!di!a!di!b!di!m!di!n!di!o!di!p!di!q!di!r!dik!di~OY!di~P3gOVkOWlOXmOYnO]qO^rO[!dig!diq!dir!dis!dit!diu!div!diw!dix!diy!diz!di!T!di_!di!s!dia!di!P!di!Q!di!R!di!S!dih!dif!diQ!diR!diT!diU!di`!dib!dic!dii!dij!dil!din!di{!di|!di}!di!O!di!`!di!a!di!b!di!m!di!n!di!o!di!p!di!q!di!r!dik!di~OZ!di~P7VOZoO~P7VOYnO~P3gOVkOWlOXmOY!diZ!di[!di]!di^!dig!diq!dir!dis!dit!diu!div!diw!dix!diy!diz!di!T!di_!di!s!dia!di!P!di!Q!di!R!di!S!dih!dif!diQ!diR!diT!diU!di`!dib!dic!dii!dij!dil!din!di{!di|!di}!di!O!di!`!di!a!di!b!di!m!di!n!di!o!di!p!di!q!di!r!dik!di~O!P!Yi!Q!Yi!R!Yi!S!Yi!T!Yi_!Yia!Yik!Yih!Yi~P-fO_#YO~P-fOg#[O~Oh#]O~P-fOm#^O~Of#_O!s!qO~O_#_O~P-fOf#`O!s!qO~O!R!bO!S!cO!P!vi!T!vi_!via!vik!vih!vi~O!Q!aO~P@aO!Q!vi~P@aO!S!cO!P!vi!Q!vi!R!vi!T!vi_!via!vik!vih!vi~O_!]i!s!]ia!]if!]i~P-fOh#fO~P-fOf#gO!s!qO~OVkOWlOXmOYnOZoO[pO]qO^rO~PVOf!yi!s!yi~P-fOR#nO~Oh#oO~P-fO!P!`O!Q!aO!R!bO!S!cOa!kXk!kX~Oa#qOk#pO~Oh#rO~P-fO!P!`O!Q!aO!R!bO!S!cO!T!ty_!tya!tyk!tyh!ty~Oa#sO!P!`O!Q!aO!R!bO!S!cO~O_#yO~P-fOa#zO!P!`O!Q!aO!R!bO!S!cO~O!P!`O!Q!aO!R!bO!S!cOa!kik!ki~Oa#{O~P$lOh#|O!P!`O!Q!aO!R!bO!S!cO~Oa$OO~P-fOc!m|nTU!o!p!q!r!nQljij~',
goto: "-l!zPPPP!{PPPPPPPPPPPPPPPPPPPPPPPPPP#wPPPPPPPPPPPPPPPPPPPPPP$s$y%s&T&q'u(Q(cPPP!{*a!{+_,Z+_+_+_-Y+_PPPPPPP%s-]%s-`-c-f!s`OTUdklmnopqrs{!O!S!V!W!Z![!`!a!b!c!q!s!v#O#T#V#[#]#^#b#f#n#o#p#r#|!sbOTUdklmnopqrs{!O!S!V!W!Z![!`!a!b!c!q!s!v#O#T#V#[#]#^#b#f#n#o#p#r#|QjOR#e#VSgO#VQxTQ!YdQ#P!`Q#Q!aQ#R!bQ#S!cQ#i#]Q#l#bQ#m#fQ#u#oQ#v#pR#x#rmhOTd!`!a!b!c#V#]#b#f#o#p#rlcOTd!`!a!b!c#V#]#b#f#o#p#rQ!^fQ!w!RQ!y!TR#a!}!U[Uklmnopqrs{!O!S!V!W!Z![!q!s!v#O#T#[#^#n#|l]OTd!`!a!b!c#V#]#b#f#o#p#rX!]f!R!T!}UvT{![X}U!V!Z!vUwT{![Q|UQ!z!VQ!|!ZR#Z!vSSO#VQtT[zU{!V!Z![!vd!Xd!`!a!b!c#]#b#f#o#pQ!gkQ!hlQ!imQ!jnQ!koQ!lpQ!mqQ!nrQ!osQ!u!OQ!x!SQ!{!WQ#W!qQ#X!sQ#b#OQ#c#TQ#h#[Q#k#^Q#t#nQ#w#rR#}#|!n`OUdklmnopqrs!O!S!V!W!Z![!`!a!b!c!q!s!v#O#T#V#[#]#^#b#f#n#o#p#r#|TuT{!s[OTUdklmnopqrs{!O!S!V!W!Z![!`!a!b!c!q!s!v#O#T#V#[#]#^#b#f#n#o#p#r#|!r[OTUdklmnopqrs{!O!S!V!W!Z![!`!a!b!c!q!s!v#O#T#V#[#]#^#b#f#n#o#p#r#|R!PVR#j#]R!_fRjOR!fiQ!eiR#d#U",
nodeNames:
'⚠ Expression Local ( Literal Global Radical + - * \\ ∆ ∩ × ) { } Filter ComplexIndex [ ] ∈ | PrefixD PrefixI ; PrefixR := Function TextFunction :∈ ∉ ⊆ ⊄ ⊂ > ≥ < ≤ ≠ = ¬ Predicate ∀ ∃ ⇔ ⇒ &',
'⚠ Expression Local ( Literal Global Radical * + - \\ ∆ ∩ × ) { } Filter ComplexIndex [ ] ∈ | PrefixD PrefixI ; PrefixR := Function TextFunction :∈ ∉ ⊆ ⊄ ⊂ > ≥ < ≤ ≠ = ¬ Predicate ∀ ∃ ⇔ ⇒ &',
maxTerm: 87,
propSources: [highlighting],
skippedNodes: [0],
repeatNodeCount: 0,
tokenData:
"6P~R!jX^%spq%svw&hxy&myz&rz{&w{|&||}'R}!O'W!Q!R']!R!['e![!](T!]!^(h!^!_(m!_!`(r!`!a(w!c!d(|!e!f(|!f!g)[!h!i)d!k!l)z!r!s*P!t!u*g!u!v(|!v!w(|!z!{(|!|!}*w!}#O*|#O#P+R#P#Q+W#R#S+]#T#U+]#U#V+s#V#W-c#W#X/O#X#d+]#d#e1h#e#f+]#f#g2^#g#o+]#o#p3h#p#q3m#q#r3r#y#z%s$f$g%s$r$s3w%o%p3|5i6S+]#BY#BZ%s$IS$I_%s$I|$JO%s$JT$JU%s$KV$KW%s% l% m4R%%Y%%Z4W%%[%%]4]%&Y%&Z4b%&]%&^4g%&_%&`4l%&`%&a4q%&b%&c4v%&c%&d4{%'S%'T5Q%'T%'U5V%'U%'V5[%(^%(_5a%(b%(c5f%(c%(d5k%)Q%)R5p%)S%)T5u%)U%)V5z&FU&FV%s~%xY!V~X^%spq%s#y#z%s$f$g%s#BY#BZ%s$IS$I_%s$I|$JO%s$JT$JU%s$KV$KW%s&FU&FV%s~&mO!S~~&rOR~~&wO_~~&|OX~~'ROV~~'WO!s~~']OW~P'bP!`P!Q![']R'lQdQ!`P|}'r!Q!['eQ'uP!R!['xQ'}QdQ|}'r!Q!['x~(WQ!_!`(^%&b%&c(c~(cOm~~(hOp~~(mOk~~(rOw~~(wOz~~(|Ou~~)PP!Q![)S~)XPT~!Q![)S~)aPi~!Q![)S~)gQ!Q![)m#]#^)u~)rPn~!Q![)m~)zOc~~*POj~~*SQ!Q![*Y#f#g*b~*_P|~!Q![*Y~*gO!m~~*lPl~!Q![*o~*tPU~!Q![*o~*|O!b~~+ROe~~+WOZ~~+]Of~~+bRQ~!Q![+k#T#o+]5i6S+]~+pPQ~!Q![+k~+xTQ~!Q![+k#T#c+]#c#d,X#d#o+]5i6S+]~,^TQ~!Q![+k#T#c+]#c#d,m#d#o+]5i6S+]~,rTQ~!Q![+k#T#`+]#`#a-R#a#o+]5i6S+]~-YR!p~Q~!Q![+k#T#o+]5i6S+]~-hSQ~!Q![+k#T#U-t#U#o+]5i6S+]~-yTQ~!Q![+k#T#f+]#f#g.Y#g#o+]5i6S+]~._TQ~!Q![+k#T#W+]#W#X.n#X#o+]5i6S+]~.uR!o~Q~!Q![+k#T#o+]5i6S+]~/TTQ~!Q![+k#T#X+]#X#Y/d#Y#o+]5i6S+]~/iTQ~!Q![+k#T#U+]#U#V/x#V#o+]5i6S+]~/}TQ~!Q![+k#T#c+]#c#d0^#d#o+]5i6S+]~0cTQ~!Q![+k#T#c+]#c#d0r#d#o+]5i6S+]~0wTQ~!Q![+k#T#`+]#`#a1W#a#o+]5i6S+]~1_R!q~Q~!Q![+k#T#o+]5i6S+]~1mTQ~!Q![+k#T#f+]#f#g1|#g#o+]5i6S+]~2TR!n~Q~!Q![+k#T#o+]5i6S+]~2cTQ~!Q![+k#T#X+]#X#Y2r#Y#o+]5i6S+]~2wTQ~!Q![+k#T#W+]#W#X3W#X#o+]5i6S+]~3_R!r~Q~!Q![+k#T#o+]5i6S+]~3mO`~~3rOh~~3wOa~~3|O{~~4RO^~~4WOb~~4]O!Q~~4bO!P~~4gO}~~4lO!O~~4qO!a~~4vO[~~4{Og~~5QOq~~5VO!R~~5[O]~~5aOY~~5fOy~~5kOx~~5pOv~~5uOt~~5zOs~~6POr~",
"6P~R!jX^%spq%svw&hxy&myz&rz{&w{|&||}'R}!O'W!Q!R']!R!['e![!](T!]!^(h!^!_(m!_!`(r!`!a(w!c!d(|!e!f(|!f!g)[!h!i)d!k!l)z!r!s*P!t!u*g!u!v(|!v!w(|!z!{(|!|!}*w!}#O*|#O#P+R#P#Q+W#R#S+]#T#U+]#U#V+s#V#W-c#W#X/O#X#d+]#d#e1h#e#f+]#f#g2^#g#o+]#o#p3h#p#q3m#q#r3r#y#z%s$f$g%s$r$s3w%o%p3|5i6S+]#BY#BZ%s$IS$I_%s$I|$JO%s$JT$JU%s$KV$KW%s% l% m4R%%Y%%Z4W%%[%%]4]%&Y%&Z4b%&]%&^4g%&_%&`4l%&`%&a4q%&b%&c4v%&c%&d4{%'S%'T5Q%'T%'U5V%'U%'V5[%(^%(_5a%(b%(c5f%(c%(d5k%)Q%)R5p%)S%)T5u%)U%)V5z&FU&FV%s~%xY!V~X^%spq%s#y#z%s$f$g%s#BY#BZ%s$IS$I_%s$I|$JO%s$JT$JU%s$KV$KW%s&FU&FV%s~&mO!S~~&rOR~~&wO_~~&|OV~~'ROW~~'WO!s~~']OX~P'bP!`P!Q![']R'lQdQ!`P|}'r!Q!['eQ'uP!R!['xQ'}QdQ|}'r!Q!['x~(WQ!_!`(^%&b%&c(c~(cOm~~(hOp~~(mOk~~(rOw~~(wOz~~(|Ou~~)PP!Q![)S~)XPT~!Q![)S~)aPi~!Q![)S~)gQ!Q![)m#]#^)u~)rPn~!Q![)m~)zOc~~*POj~~*SQ!Q![*Y#f#g*b~*_P|~!Q![*Y~*gO!m~~*lPl~!Q![*o~*tPU~!Q![*o~*|O!b~~+ROe~~+WOZ~~+]Of~~+bRQ~!Q![+k#T#o+]5i6S+]~+pPQ~!Q![+k~+xTQ~!Q![+k#T#c+]#c#d,X#d#o+]5i6S+]~,^TQ~!Q![+k#T#c+]#c#d,m#d#o+]5i6S+]~,rTQ~!Q![+k#T#`+]#`#a-R#a#o+]5i6S+]~-YR!p~Q~!Q![+k#T#o+]5i6S+]~-hSQ~!Q![+k#T#U-t#U#o+]5i6S+]~-yTQ~!Q![+k#T#f+]#f#g.Y#g#o+]5i6S+]~._TQ~!Q![+k#T#W+]#W#X.n#X#o+]5i6S+]~.uR!o~Q~!Q![+k#T#o+]5i6S+]~/TTQ~!Q![+k#T#X+]#X#Y/d#Y#o+]5i6S+]~/iTQ~!Q![+k#T#U+]#U#V/x#V#o+]5i6S+]~/}TQ~!Q![+k#T#c+]#c#d0^#d#o+]5i6S+]~0cTQ~!Q![+k#T#c+]#c#d0r#d#o+]5i6S+]~0wTQ~!Q![+k#T#`+]#`#a1W#a#o+]5i6S+]~1_R!q~Q~!Q![+k#T#o+]5i6S+]~1mTQ~!Q![+k#T#f+]#f#g1|#g#o+]5i6S+]~2TR!n~Q~!Q![+k#T#o+]5i6S+]~2cTQ~!Q![+k#T#X+]#X#Y2r#Y#o+]5i6S+]~2wTQ~!Q![+k#T#W+]#W#X3W#X#o+]5i6S+]~3_R!r~Q~!Q![+k#T#o+]5i6S+]~3mO`~~3rOh~~3wOa~~3|O{~~4RO^~~4WOb~~4]O!Q~~4bO!P~~4gO}~~4lO!O~~4qO!a~~4vO[~~4{Og~~5QOq~~5VO!R~~5[O]~~5aOY~~5fOy~~5kOx~~5pOv~~5uOt~~5zOs~~6POr~",
tokenizers: [0, 1],
topRules: { Expression: [0, 1] },
tokenPrec: 1720
tokenPrec: 1749
});

View File

@ -7,16 +7,20 @@
// ------------- Precedence Definitions --------------------
///////////////////////////////////////////////////////////
@precedence {
plus @left minus @left,
times @left,
not @right,
log_equiv @left,
log_impl @left,
log_or @left,
log_and @left,
log_or @left,
log_impl @left,
log_equiv @left,
times @left,
plus @left minus @left,
set_decart @left set_union @left set_intersect @left set_minus @left set_symminus @left,
set_bool @right,
quant @right
quant @right,
not @right
}
///////////////////////////////////////////////////////////
@ -197,9 +201,9 @@ identifier {
}
Setexpr_binary {
Setexpr !times "*" Setexpr |
Setexpr !plus "+" Setexpr |
Setexpr !minus "-" Setexpr |
Setexpr !times "*" Setexpr |
Setexpr !set_union "" Setexpr |
Setexpr !set_minus "\\" Setexpr |
Setexpr !set_symminus "∆" Setexpr |

View File

@ -8,16 +8,20 @@
// ------------- Precedence Definitions --------------------
///////////////////////////////////////////////////////////
@precedence {
plus @left minus @left,
times @left,
not @right,
log_equiv @left,
log_impl @left,
log_or @left,
log_and @left,
log_or @left,
log_impl @left,
log_equiv @left,
times @left,
plus @left minus @left,
set_decart @left set_union @left set_intersect @left set_minus @left set_symminus @left,
set_bool @right,
quant @right
quant @right,
not @right
}
///////////////////////////////////////////////////////////
@ -195,9 +199,9 @@ identifier {
}
setexpr_binary {
setexpr !times "*" setexpr |
setexpr !plus "+" setexpr |
setexpr !minus "-" setexpr |
setexpr !times "*" setexpr |
setexpr !set_union "" setexpr |
setexpr !set_minus "\\" setexpr |
setexpr !set_symminus "∆" setexpr |

View File

@ -145,6 +145,15 @@ export class RSTextWrapper extends CodeMirrorWrapper {
return true;
}
case TokenID.BOOLEAN: {
if (!hasSelection && selection.from >= 2) {
const enclosingText = this.ref.view.state.sliceDoc(selection.from - 2, selection.from + 1);
if (enclosingText === '()') {
this.ref.view.dispatch({
changes: [{ from: selection.from - 2, insert: '' }]
});
return true;
}
}
if (hasSelection && this.startsWithBoolean(selection)) {
this.envelopeWith('', '');
} else {
@ -290,7 +299,7 @@ export class RSTextWrapper extends CodeMirrorWrapper {
if (!text.startsWith('') || !text.endsWith(')')) {
return false;
}
const openParenIndex = text.indexOf('(', 1); // start after ''
const openParenIndex = text.indexOf('(', 1);
if (openParenIndex === -1) {
return false;
}

View File

@ -1,7 +1,7 @@
'use no memo'; // TODO: remove when react hook forms are compliant with react compiler
'use client';
import { useMemo, useRef, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { toast } from 'react-toastify';
import { zodResolver } from '@hookform/resolvers/zod';
@ -121,10 +121,12 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
}
const prevDirty = useRef(isDirty);
useEffect(() => {
if (prevDirty.current !== isDirty) {
prevDirty.current = isDirty;
setIsModified(isDirty);
}
}, [isDirty, setIsModified]);
function onSubmit(data: IUpdateConstituentaDTO) {
return cstUpdate({ itemID: schema.id, data }).then(() => {

View File

@ -1,7 +1,7 @@
'use no memo'; // TODO: remove when react hook forms are compliant with react compiler
'use client';
import { useRef } from 'react';
import { useEffect, useRef } from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
@ -70,10 +70,12 @@ export function FormRSForm() {
}
const prevDirty = useRef(isDirty);
useEffect(() => {
if (prevDirty.current !== isDirty) {
prevDirty.current = isDirty;
setIsModified(isDirty);
}
}, [isDirty, setIsModified]);
function handleSelectVersion(version: CurrentVersion) {
router.push({ path: urls.schema(schema.id, version === 'latest' ? undefined : version) });

View File

@ -2,6 +2,7 @@
import { useState } from 'react';
import { toast } from 'react-toastify';
import clsx from 'clsx';
import fileDownload from 'js-file-download';
import { MiniButton } from '@/components/control';
@ -123,7 +124,7 @@ export function EditorRSList() {
return false;
}
const tableHeight = useFitHeight('4rem + 5px');
const tableHeight = useFitHeight(isContentEditable ? '4rem + 5px' : '2rem');
return (
<div tabIndex={-1} onKeyDown={handleKeyDown} className='relative pt-8'>
@ -146,7 +147,7 @@ export function EditorRSList() {
) : null}
<MiniButton
className='absolute z-pop top-18 right-4 hidden sm:block'
className={clsx('absolute z-pop right-4 hidden sm:block', isContentEditable ? 'top-18' : 'top-8')}
title='Выгрузить в формате CSV'
icon={<IconCSV size='1.25rem' className='icon-green' />}
onClick={handleDownloadCSV}

View File

@ -5,41 +5,41 @@
/**
* Represents single node of a {@link Graph}, as implemented by storing outgoing and incoming connections.
*/
export class GraphNode {
export class GraphNode<NodeID> {
/** Unique identifier of the node. */
id: number;
id: NodeID;
/** List of outgoing nodes. */
outputs: number[];
outputs: NodeID[];
/** List of incoming nodes. */
inputs: number[];
inputs: NodeID[];
constructor(id: number) {
constructor(id: NodeID) {
this.id = id;
this.outputs = [];
this.inputs = [];
}
clone(): GraphNode {
clone(): GraphNode<NodeID> {
const result = new GraphNode(this.id);
result.outputs = [...this.outputs];
result.inputs = [...this.inputs];
return result;
}
addOutput(node: number): void {
addOutput(node: NodeID): void {
this.outputs.push(node);
}
addInput(node: number): void {
addInput(node: NodeID): void {
this.inputs.push(node);
}
removeInput(target: number): number | null {
removeInput(target: NodeID): NodeID | 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: NodeID): NodeID | null {
const index = this.outputs.findIndex(node => node === target);
return index > -1 ? this.outputs.splice(index, 1)[0] : null;
}
@ -50,11 +50,11 @@ export class GraphNode {
*
* This class is optimized for TermGraph use case and not supposed to be used as generic graph implementation.
*/
export class Graph {
export class Graph<NodeID = number> {
/** Map of nodes. */
nodes = new Map<number, GraphNode>();
nodes = new Map<NodeID, GraphNode<NodeID>>();
constructor(arr?: number[][]) {
constructor(arr?: NodeID[][]) {
if (!arr) {
return;
}
@ -67,17 +67,17 @@ export class Graph {
});
}
clone(): Graph {
const result = new Graph();
clone(): Graph<NodeID> {
const result = new Graph<NodeID>();
this.nodes.forEach(node => result.nodes.set(node.id, node.clone()));
return result;
}
at(target: number): GraphNode | undefined {
at(target: NodeID): GraphNode<NodeID> | undefined {
return this.nodes.get(target);
}
addNode(target: number): GraphNode {
addNode(target: NodeID): GraphNode<NodeID> {
let node = this.nodes.get(target);
if (!node) {
node = new GraphNode(target);
@ -86,11 +86,11 @@ export class Graph {
return node;
}
hasNode(target: number): boolean {
hasNode(target: NodeID): boolean {
return !!this.nodes.get(target);
}
removeNode(target: number): void {
removeNode(target: NodeID): void {
this.nodes.forEach(node => {
node.removeInput(target);
node.removeOutput(target);
@ -98,7 +98,7 @@ export class Graph {
this.nodes.delete(target);
}
foldNode(target: number): void {
foldNode(target: NodeID): void {
const nodeToRemove = this.nodes.get(target);
if (!nodeToRemove) {
return;
@ -111,8 +111,8 @@ export class Graph {
this.removeNode(target);
}
removeIsolated(): GraphNode[] {
const result: GraphNode[] = [];
removeIsolated(): GraphNode<NodeID>[] {
const result: GraphNode<NodeID>[] = [];
this.nodes.forEach(node => {
if (node.outputs.length === 0 && node.inputs.length === 0) {
result.push(node);
@ -122,7 +122,7 @@ export class Graph {
return result;
}
addEdge(source: number, destination: number): void {
addEdge(source: NodeID, destination: NodeID): void {
if (this.hasEdge(source, destination)) {
return;
}
@ -132,7 +132,7 @@ export class Graph {
destinationNode.addInput(sourceNode.id);
}
removeEdge(source: number, destination: number): void {
removeEdge(source: NodeID, destination: NodeID): void {
const sourceNode = this.nodes.get(source);
const destinationNode = this.nodes.get(destination);
if (sourceNode && destinationNode) {
@ -141,7 +141,7 @@ export class Graph {
}
}
hasEdge(source: number, destination: number): boolean {
hasEdge(source: NodeID, destination: NodeID): boolean {
const sourceNode = this.nodes.get(source);
if (!sourceNode) {
return false;
@ -149,8 +149,8 @@ export class Graph {
return !!sourceNode.outputs.find(id => id === destination);
}
expandOutputs(origin: number[]): number[] {
const result: number[] = [];
expandOutputs(origin: NodeID[]): NodeID[] {
const result: NodeID[] = [];
origin.forEach(id => {
const node = this.nodes.get(id);
if (node) {
@ -164,8 +164,8 @@ export class Graph {
return result;
}
expandInputs(origin: number[]): number[] {
const result: number[] = [];
expandInputs(origin: NodeID[]): NodeID[] {
const result: NodeID[] = [];
origin.forEach(id => {
const node = this.nodes.get(id);
if (node) {
@ -179,13 +179,13 @@ export class Graph {
return result;
}
expandAllOutputs(origin: number[]): number[] {
const result: number[] = this.expandOutputs(origin);
expandAllOutputs(origin: NodeID[]): NodeID[] {
const result: NodeID[] = this.expandOutputs(origin);
if (result.length === 0) {
return [];
}
const marked = new Map<number, boolean>();
const marked = new Map<NodeID, boolean>();
origin.forEach(id => marked.set(id, true));
let position = 0;
while (position < result.length) {
@ -203,13 +203,13 @@ export class Graph {
return result;
}
expandAllInputs(origin: number[]): number[] {
const result: number[] = this.expandInputs(origin);
expandAllInputs(origin: NodeID[]): NodeID[] {
const result: NodeID[] = this.expandInputs(origin);
if (result.length === 0) {
return [];
}
const marked = new Map<number, boolean>();
const marked = new Map<NodeID, boolean>();
origin.forEach(id => marked.set(id, true));
let position = 0;
while (position < result.length) {
@ -227,8 +227,8 @@ export class Graph {
return result;
}
maximizePart(origin: number[]): number[] {
const outputs: number[] = this.expandAllOutputs(origin);
maximizePart(origin: NodeID[]): NodeID[] {
const outputs: NodeID[] = this.expandAllOutputs(origin);
const result = [...origin];
this.topologicalOrder()
.filter(id => outputs.includes(id))
@ -241,10 +241,10 @@ export class Graph {
return result;
}
topologicalOrder(): number[] {
const result: number[] = [];
const marked = new Set<number>();
const nodeStack: number[] = [];
topologicalOrder(): NodeID[] {
const result: NodeID[] = [];
const marked = new Set<NodeID>();
const nodeStack: NodeID[] = [];
this.nodes.forEach(node => {
if (marked.has(node.id)) {
return;
@ -275,12 +275,12 @@ export class Graph {
transitiveReduction() {
const order = this.topologicalOrder();
const marked = new Map<number, boolean>();
const marked = new Map<NodeID, boolean>();
order.forEach(nodeID => {
if (marked.get(nodeID)) {
return;
}
const stack: { id: number; parents: number[] }[] = [];
const stack: { id: NodeID; parents: NodeID[] }[] = [];
stack.push({ id: nodeID, parents: [] });
while (stack.length > 0) {
const item = stack.splice(0, 1)[0];
@ -299,20 +299,20 @@ export class Graph {
/**
* Finds a cycle in the graph.
*
* @returns {number[] | null} The cycle if found, otherwise `null`.
* @returns {NodeID[] | null} The cycle if found, otherwise `null`.
* Uses non-recursive DFS.
*/
findCycle(): number[] | null {
const visited = new Set<number>();
const nodeStack = new Set<number>();
const parents = new Map<number, number>();
findCycle(): NodeID[] | null {
const visited = new Set<NodeID>();
const nodeStack = new Set<NodeID>();
const parents = new Map<NodeID, NodeID>();
for (const nodeId of this.nodes.keys()) {
if (visited.has(nodeId)) {
continue;
}
const callStack: { nodeId: number; parentId: number | null }[] = [];
const callStack: { nodeId: NodeID; parentId: NodeID | null }[] = [];
callStack.push({ nodeId: nodeId, parentId: null });
while (callStack.length > 0) {
const { nodeId, parentId } = callStack[callStack.length - 1];
@ -336,7 +336,7 @@ export class Graph {
if (!nodeStack.has(child)) {
continue;
}
const cycle: number[] = [];
const cycle: NodeID[] = [];
let current = nodeId;
cycle.push(child);
while (current !== child) {