Merge branch 'main' into synthesis

This commit is contained in:
IRBorisov 2024-06-21 19:37:56 +03:00
commit 91865e54f0
140 changed files with 2632 additions and 1156 deletions

31
.vscode/settings.json vendored
View File

@ -84,6 +84,7 @@
"Grammeme",
"Grammemes",
"GRND",
"IDEF",
"impr",
"inan",
"incapsulation",
@ -150,39 +151,67 @@
"viewsets",
"wordform",
"Wordforms",
"Айзенштат",
"Акименков",
"Астрина",
"Ашихмин",
"Биективная",
"биективной",
"Булеан",
"Бурбаки",
"Бурбакизатор",
"Версионирование",
"Владельцом",
"Демешко",
"Десинглетон",
"доксинг",
"Закс",
"интерпретируемости",
"интерпретируемость",
"Инттеор",
"Климишин",
"компаратив",
"Кононенко",
"конституент",
"Конституента",
"конституентами",
"конституенте",
"конституенту",
"конституенты",
"Крайнев",
"Кучкаров",
"Кучкарова",
"неинтерпретируемый",
"неитерируемого",
"Никанорова",
"операционализации",
"операционализированных",
"Оргтеор",
"Пакулина",
"пересинтез",
"Персиц",
"Присакарь",
"ПРОКСИМА",
"Родоструктурная",
"родоструктурного",
"Родоструктурное",
"родоструктурной",
"родоструктурном",
"родоструктурных",
"Савелов",
"Синглетон",
"твор",
"Терминологизация",
"троллинг",
"Тулисов",
"Цермелло",
"ЦИВТ",
"Чувашов",
"Шульпекин",
"Экстеор",
"Экстеора",
"Экстеоре"
"Экстеоре",
"Юдкин"
],
"cSpell.language": "en,ru",
"cSpell.ignorePaths": ["node_modules/**", "*.json"]

View File

@ -2,25 +2,24 @@
For more specific TODOs see comments in code
[Functionality - PROGRESS]
- Landing page
- Home page (user specific)
- Operational synthesis schema as LibraryItem ?
- Draggable rows in constituents table
- Clickable IDs in RSEditor tooltips
- Library organization, search and exploration. Consider new user experience
- Private projects and permissions. Consider cooperative editing
- Rework access setup: project-based, user-based, enable sharing. Prevent enumerating access to private schemas by default
[Functionality - PENDING]
- Search functionality for manuals
- User notifications on edit - consider spam prevention and change aggregation
- Static analyzer for RSForm
- Content based search in Library
- User profile: Settings + settings persistency
- Landing page
- Home page (user specific)
- Draggable rows in constituents table
- Export PDF (Items list, Graph)
- ARIA (accessibility considerations) - for now machine reading not supported
- Internationalization - at least english version. Consider react.intl
@ -42,6 +41,8 @@ For more specific TODOs see comments in code
[Security]
- password-reset leaks info of email being used
- improve nginx config. Consider DDOS and other types of attacks on infrastructure
- recaptcha for create user and rest password
https://yandex.cloud/ru/docs/smartcaptcha
[Research]

View File

@ -4,11 +4,11 @@ djangorestframework==3.15.1
django-cors-headers==4.3.1
django-filter==24.2
drf-spectacular==0.27.2
drf-spectacular-sidecar==2024.5.1
drf-spectacular-sidecar==2024.6.1
coreapi==2.3.3
django-rest-passwordreset==1.4.1
cctext==0.1.3
pyconcept==0.1.5
pyconcept==0.1.6
psycopg2-binary==2.9.9
gunicorn==22.0.0

View File

@ -16,7 +16,7 @@ Styling conventions
- border: borer-2 outline-none shadow-md
- colors: clr-controls
- text: text-start text-sm font-semibold whitespace-nowrap
- behavior modifiers: select-none disabled:cursor-not-allowed
- behavior modifiers: select-none disabled:cursor-auto
- transitions:
</pre>
</details>

View File

@ -22,11 +22,14 @@
<title>Концепт Портал</title>
<script>
if (
localStorage.getItem('darkMode') === 'true' ||
localStorage.getItem('color-theme') === 'dark' ||
(!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
) {
let isDark = false;
if ('portal.theme.dark' in localStorage) {
isDark = localStorage.getItem('portal.theme.dark') === 'true';
} else if (window.matchMedia) {
isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
localStorage.setItem('portal.theme.dark', isDark ? 'true' : 'false');
}
if (isDark) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');

View File

@ -11,8 +11,8 @@
"@lezer/lr": "^1.4.1",
"@reactflow/core": "^11.8.3",
"@tanstack/react-table": "^8.17.3",
"@uiw/codemirror-themes": "^4.22.1",
"@uiw/react-codemirror": "^4.22.1",
"@uiw/codemirror-themes": "^4.22.2",
"@uiw/react-codemirror": "^4.22.2",
"axios": "^1.7.2",
"clsx": "^2.1.1",
"framer-motion": "^10.18.0",
@ -23,24 +23,24 @@
"react-icons": "^4.12.0",
"react-intl": "^6.6.8",
"react-loader-spinner": "^5.4.5",
"react-pdf": "^7.7.3",
"react-pdf": "^9.0.0",
"react-router-dom": "^6.23.1",
"react-select": "^5.8.0",
"react-tabs": "^6.0.2",
"react-toastify": "^9.1.3",
"react-tooltip": "^5.26.4",
"reagraph": "^4.19.0",
"react-tooltip": "^5.27.0",
"reagraph": "^4.19.1",
"use-debounce": "^10.0.1"
},
"devDependencies": {
"@lezer/generator": "^1.7.0",
"@types/jest": "^29.5.12",
"@types/node": "^20.14.0",
"@types/node": "^20.14.5",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"@vitejs/plugin-react": "^4.3.0",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.19",
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.2",
@ -49,8 +49,8 @@
"eslint-plugin-tsdoc": "^0.2.17",
"jest": "^29.7.0",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.3",
"ts-jest": "^29.1.4",
"tailwindcss": "^3.4.4",
"ts-jest": "^29.1.5",
"typescript": "^5.4.5",
"vite": "^4.5.3"
}
@ -82,12 +82,12 @@
}
},
"node_modules/@babel/code-frame": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.6.tgz",
"integrity": "sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
"integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
"license": "MIT",
"dependencies": {
"@babel/highlight": "^7.24.6",
"@babel/highlight": "^7.24.7",
"picocolors": "^1.0.0"
},
"engines": {
@ -95,30 +95,30 @@
}
},
"node_modules/@babel/compat-data": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.6.tgz",
"integrity": "sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz",
"integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.6.tgz",
"integrity": "sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz",
"integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==",
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.24.6",
"@babel/generator": "^7.24.6",
"@babel/helper-compilation-targets": "^7.24.6",
"@babel/helper-module-transforms": "^7.24.6",
"@babel/helpers": "^7.24.6",
"@babel/parser": "^7.24.6",
"@babel/template": "^7.24.6",
"@babel/traverse": "^7.24.6",
"@babel/types": "^7.24.6",
"@babel/code-frame": "^7.24.7",
"@babel/generator": "^7.24.7",
"@babel/helper-compilation-targets": "^7.24.7",
"@babel/helper-module-transforms": "^7.24.7",
"@babel/helpers": "^7.24.7",
"@babel/parser": "^7.24.7",
"@babel/template": "^7.24.7",
"@babel/traverse": "^7.24.7",
"@babel/types": "^7.24.7",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@ -143,12 +143,12 @@
}
},
"node_modules/@babel/generator": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.6.tgz",
"integrity": "sha512-S7m4eNa6YAPJRHmKsLHIDJhNAGNKoWNiWefz1MBbpnt8g9lvMDl1hir4P9bo/57bQEmuwEhnRU/AMWsD0G/Fbg==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz",
"integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.24.6",
"@babel/types": "^7.24.7",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^2.5.1"
@ -158,25 +158,25 @@
}
},
"node_modules/@babel/helper-annotate-as-pure": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.6.tgz",
"integrity": "sha512-DitEzDfOMnd13kZnDqns1ccmftwJTS9DMkyn9pYTxulS7bZxUxpMly3Nf23QQ6NwA4UB8lAqjbqWtyvElEMAkg==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz",
"integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.24.6"
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-compilation-targets": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.6.tgz",
"integrity": "sha512-VZQ57UsDGlX/5fFA7GkVPplZhHsVc+vuErWgdOiysI9Ksnw0Pbbd6pnPiR/mmJyKHgyIW0c7KT32gmhiF+cirg==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz",
"integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==",
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.24.6",
"@babel/helper-validator-option": "^7.24.6",
"@babel/compat-data": "^7.24.7",
"@babel/helper-validator-option": "^7.24.7",
"browserslist": "^4.22.2",
"lru-cache": "^5.1.1",
"semver": "^6.3.1"
@ -195,62 +195,66 @@
}
},
"node_modules/@babel/helper-environment-visitor": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz",
"integrity": "sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz",
"integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-function-name": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.6.tgz",
"integrity": "sha512-xpeLqeeRkbxhnYimfr2PC+iA0Q7ljX/d1eZ9/inYbmfG2jpl8Lu3DyXvpOAnrS5kxkfOWJjioIMQsaMBXFI05w==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz",
"integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==",
"license": "MIT",
"dependencies": {
"@babel/template": "^7.24.6",
"@babel/types": "^7.24.6"
"@babel/template": "^7.24.7",
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-hoist-variables": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.6.tgz",
"integrity": "sha512-SF/EMrC3OD7dSta1bLJIlrsVxwtd0UpjRJqLno6125epQMJ/kyFmpTT4pbvPbdQHzCHg+biQ7Syo8lnDtbR+uA==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz",
"integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.24.6"
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-imports": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.6.tgz",
"integrity": "sha512-a26dmxFJBF62rRO9mmpgrfTLsAuyHk4e1hKTUkD/fcMfynt8gvEKwQPQDVxWhca8dHoDck+55DFt42zV0QMw5g==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz",
"integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.24.6"
"@babel/traverse": "^7.24.7",
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.6.tgz",
"integrity": "sha512-Y/YMPm83mV2HJTbX1Qh2sjgjqcacvOlhbzdCCsSlblOKjSYmQqEbO6rUniWQyRo9ncyfjT8hnUjlG06RXDEmcA==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz",
"integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-environment-visitor": "^7.24.6",
"@babel/helper-module-imports": "^7.24.6",
"@babel/helper-simple-access": "^7.24.6",
"@babel/helper-split-export-declaration": "^7.24.6",
"@babel/helper-validator-identifier": "^7.24.6"
"@babel/helper-environment-visitor": "^7.24.7",
"@babel/helper-module-imports": "^7.24.7",
"@babel/helper-simple-access": "^7.24.7",
"@babel/helper-split-export-declaration": "^7.24.7",
"@babel/helper-validator-identifier": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@ -260,85 +264,86 @@
}
},
"node_modules/@babel/helper-plugin-utils": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.6.tgz",
"integrity": "sha512-MZG/JcWfxybKwsA9N9PmtF2lOSFSEMVCpIRrbxccZFLJPrJciJdG/UhSh5W96GEteJI2ARqm5UAHxISwRDLSNg==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz",
"integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-simple-access": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.6.tgz",
"integrity": "sha512-nZzcMMD4ZhmB35MOOzQuiGO5RzL6tJbsT37Zx8M5L/i9KSrukGXWTjLe1knIbb/RmxoJE9GON9soq0c0VEMM5g==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz",
"integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.24.6"
"@babel/traverse": "^7.24.7",
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-split-export-declaration": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.6.tgz",
"integrity": "sha512-CvLSkwXGWnYlF9+J3iZUvwgAxKiYzK3BWuo+mLzD/MDGOZDj7Gq8+hqaOkMxmJwmlv0iu86uH5fdADd9Hxkymw==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz",
"integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.24.6"
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.6.tgz",
"integrity": "sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz",
"integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz",
"integrity": "sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-option": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.6.tgz",
"integrity": "sha512-Jktc8KkF3zIkePb48QO+IapbXlSapOW9S+ogZZkcO6bABgYAxtZcjZ/O005111YLf+j4M84uEgwYoidDkXbCkQ==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz",
"integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helpers": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.6.tgz",
"integrity": "sha512-V2PI+NqnyFu1i0GyTd/O/cTpxzQCYioSkUIRmgo7gFEHKKCg5w46+r/A6WeUR1+P3TeQ49dspGPNd/E3n9AnnA==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz",
"integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==",
"license": "MIT",
"dependencies": {
"@babel/template": "^7.24.6",
"@babel/types": "^7.24.6"
"@babel/template": "^7.24.7",
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/highlight": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.6.tgz",
"integrity": "sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
"integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.24.6",
"@babel/helper-validator-identifier": "^7.24.7",
"chalk": "^2.4.2",
"js-tokens": "^4.0.0",
"picocolors": "^1.0.0"
@ -348,9 +353,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.6.tgz",
"integrity": "sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz",
"integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==",
"license": "MIT",
"bin": {
"parser": "bin/babel-parser.js"
@ -425,12 +430,12 @@
}
},
"node_modules/@babel/plugin-syntax-jsx": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.6.tgz",
"integrity": "sha512-lWfvAIFNWMlCsU0DRUun2GpFwZdGTukLaHJqRh1JRb80NdAP5Sb1HDHB5X9P9OtgZHQl089UzQkpYlBq2VTPRw==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz",
"integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.6"
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@ -534,13 +539,13 @@
}
},
"node_modules/@babel/plugin-syntax-typescript": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.6.tgz",
"integrity": "sha512-TzCtxGgVTEJWWwcYwQhCIQ6WaKlo80/B+Onsk4RRCcYqpYGFcG9etPW94VToGte5AAcxRrhjPUFvUS3Y2qKi4A==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz",
"integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.6"
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@ -550,13 +555,13 @@
}
},
"node_modules/@babel/plugin-transform-react-jsx-self": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.6.tgz",
"integrity": "sha512-FfZfHXtQ5jYPQsCRyLpOv2GeLIIJhs8aydpNh39vRDjhD411XcfWDni5i7OjP/Rs8GAtTn7sWFFELJSHqkIxYg==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz",
"integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.6"
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@ -566,13 +571,13 @@
}
},
"node_modules/@babel/plugin-transform-react-jsx-source": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.6.tgz",
"integrity": "sha512-BQTBCXmFRreU3oTUXcGKuPOfXAGb1liNY4AvvFKsOBAJ89RKcTsIrSsnMYkj59fNa66OFKnSa4AJZfy5Y4B9WA==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz",
"integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.6"
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@ -582,9 +587,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.6.tgz",
"integrity": "sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz",
"integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==",
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
@ -594,33 +599,33 @@
}
},
"node_modules/@babel/template": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.6.tgz",
"integrity": "sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz",
"integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.24.6",
"@babel/parser": "^7.24.6",
"@babel/types": "^7.24.6"
"@babel/code-frame": "^7.24.7",
"@babel/parser": "^7.24.7",
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.6.tgz",
"integrity": "sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz",
"integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.24.6",
"@babel/generator": "^7.24.6",
"@babel/helper-environment-visitor": "^7.24.6",
"@babel/helper-function-name": "^7.24.6",
"@babel/helper-hoist-variables": "^7.24.6",
"@babel/helper-split-export-declaration": "^7.24.6",
"@babel/parser": "^7.24.6",
"@babel/types": "^7.24.6",
"@babel/code-frame": "^7.24.7",
"@babel/generator": "^7.24.7",
"@babel/helper-environment-visitor": "^7.24.7",
"@babel/helper-function-name": "^7.24.7",
"@babel/helper-hoist-variables": "^7.24.7",
"@babel/helper-split-export-declaration": "^7.24.7",
"@babel/parser": "^7.24.7",
"@babel/types": "^7.24.7",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@ -629,13 +634,13 @@
}
},
"node_modules/@babel/types": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.6.tgz",
"integrity": "sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz",
"integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.24.6",
"@babel/helper-validator-identifier": "^7.24.6",
"@babel/helper-string-parser": "^7.24.7",
"@babel/helper-validator-identifier": "^7.24.7",
"to-fast-properties": "^2.0.0"
},
"engines": {
@ -668,21 +673,21 @@
}
},
"node_modules/@codemirror/commands": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.5.0.tgz",
"integrity": "sha512-rK+sj4fCAN/QfcY9BEzYMgp4wwL/q5aj/VfNSoH1RWPF9XS/dUwBkvlL3hpWgEjOqlpdN1uLC9UkjJ4tmyjJYg==",
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.6.0.tgz",
"integrity": "sha512-qnY+b7j1UNcTS31Eenuc/5YJB6gQOzkUoNmJQc0rznwqSRpeaWWpjkWy2C/MPTcePpsKJEM26hXrOXl1+nceXg==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.4.0",
"@codemirror/view": "^6.0.0",
"@codemirror/view": "^6.27.0",
"@lezer/common": "^1.1.0"
}
},
"node_modules/@codemirror/language": {
"version": "6.10.1",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.1.tgz",
"integrity": "sha512-5GrXzrhq6k+gL5fjkAwt90nYDmjlzTIJV8THnxNFtNKWotMIlzzN+CpqxqwXOECnUdOndmSeWntVrVcv5axWRQ==",
"version": "6.10.2",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.2.tgz",
"integrity": "sha512-kgbTYTo0Au6dCSc/TFy7fK3fpJmgHDv1sG1KNQKJXVi+xBTEeBPY/M30YXiU6mMXeH+YIDLsbrT4ZwNRdtF+SA==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
@ -734,9 +739,9 @@
}
},
"node_modules/@codemirror/view": {
"version": "6.26.3",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.3.tgz",
"integrity": "sha512-gmqxkPALZjkgSxIeeweY/wGQXBfwTUaLs8h7OKtSwfbj9Ct3L11lD+u1sS7XHppxFQoMDiMDp07P9f3I2jWOHw==",
"version": "6.28.1",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.28.1.tgz",
"integrity": "sha512-BUWr+zCJpMkA/u69HlJmR+YkV4yPpM81HeMkOMZuwFa8iM5uJdEPKAs1icIRZKkKmy0Ub1x9/G3PQLTXdpBxrQ==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.4.0",
@ -1311,9 +1316,9 @@
}
},
"node_modules/@eslint-community/regexpp": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz",
"integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==",
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz",
"integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==",
"dev": true,
"license": "MIT",
"engines": {
@ -1530,6 +1535,7 @@
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
"deprecated": "Use @eslint/config-array instead",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@ -1583,6 +1589,7 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
"integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
"deprecated": "Use @eslint/object-schema instead",
"dev": true,
"license": "BSD-3-Clause"
},
@ -3294,9 +3301,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "20.14.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.0.tgz",
"integrity": "sha512-5cHBxFGJx6L4s56Bubp4fglrEpmyJypsqI6RgzMfBHWUJQGWAAi8cWcgetEbZXHYXo9C2Fa4EEds/uSyS4cxmA==",
"version": "20.14.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.5.tgz",
"integrity": "sha512-aoRR+fJkZT2l0aGOJhuA8frnCSoNX6W7U2mpNq63+BxBIj5BQFt8rHy627kijCmm63ijdSdwvGgpUsU6MBsZZA==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -3394,9 +3401,9 @@
}
},
"node_modules/@types/webxr": {
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.16.tgz",
"integrity": "sha512-0E0Cl84FECtzrB4qG19TNTqpunw0F1YF0QZZnFMF6pDw1kNKJtrlTKlVB34stGIsHbZsYQ7H0tNjPfZftkHHoA==",
"version": "0.5.17",
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.17.tgz",
"integrity": "sha512-JYcclaQIlisHRXM9dMF7SeVvQ54kcYc7QK1eKCExCTLKWnZDxP4cp/rXH4Uoa1j5+5oQJ0Cc2sZC/PWiiG4q2g==",
"license": "MIT"
},
"node_modules/@types/yargs": {
@ -3615,9 +3622,9 @@
}
},
"node_modules/@uiw/codemirror-extensions-basic-setup": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.22.1.tgz",
"integrity": "sha512-Iz8eFaZBNrwjaAADszOxOv2byDMn4rqob/luuSPAzJjTrSn5KawRXcoNLoWGPGNO6Mils6bIly/g2LaU34otNw==",
"version": "4.22.2",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.22.2.tgz",
"integrity": "sha512-zcHGkldLFN3cGoI5XdOGAkeW24yaAgrDEYoyPyWHODmPiNwybQQoZGnH3qUdzZwUaXtAcLWoAeOPzfNRW2yGww==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
@ -3642,9 +3649,9 @@
}
},
"node_modules/@uiw/codemirror-themes": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.22.1.tgz",
"integrity": "sha512-5TeB8wCc0aNd3YEhzOvgekpAFQfEm4fCTUcGmEIQqaRNgKAM83HYNpE1JF2j7x2oDFugdiO0yJynS6bo1zVOuw==",
"version": "4.22.2",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.22.2.tgz",
"integrity": "sha512-gsLHn6SUuV5iboBvGrM7YimzLFHQmsNlkGIYs3UaVUJTo/A/ZrKoSJNyPziShLRjBXA2UwKdBTIU6VhHyyaChw==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
@ -3661,16 +3668,16 @@
}
},
"node_modules/@uiw/react-codemirror": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.22.1.tgz",
"integrity": "sha512-yrq9FdGZ6E4Rh+7W0xyirSEeESGyG/k54/DfFqSk40fqel/3x/3fqjIImEZUYPxxgFPmZ3RtP+O0Em46nwRvgg==",
"version": "4.22.2",
"resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.22.2.tgz",
"integrity": "sha512-okCSl+WJG63gRx8Fdz7v0C6RakBQnbb3pHhuzIgDB+fwhipgFodSnu2n9oOsQesJ5YQ7mSOcKMgX0JEsu4nnfQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.18.6",
"@codemirror/commands": "^6.1.0",
"@codemirror/state": "^6.1.1",
"@codemirror/theme-one-dark": "^6.0.0",
"@uiw/codemirror-extensions-basic-setup": "4.22.1",
"@uiw/codemirror-extensions-basic-setup": "4.22.2",
"codemirror": "^6.0.0"
},
"funding": {
@ -3712,9 +3719,9 @@
}
},
"node_modules/@vitejs/plugin-react": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.0.tgz",
"integrity": "sha512-KcEbMsn4Dpk+LIbHMj7gDPRKaTMStxxWRkRmxsg/jVdFdJCZWt1SchZcf0M4t8lIKdwwMsEyzhrcOXRrDPtOBw==",
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz",
"integrity": "sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -3745,9 +3752,9 @@
"optional": true
},
"node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz",
"integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==",
"dev": true,
"license": "MIT",
"bin": {
@ -4240,9 +4247,9 @@
}
},
"node_modules/browserslist": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
"integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
"version": "4.23.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz",
"integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==",
"funding": [
{
"type": "opencollective",
@ -4259,10 +4266,10 @@
],
"license": "MIT",
"dependencies": {
"caniuse-lite": "^1.0.30001587",
"electron-to-chromium": "^1.4.668",
"caniuse-lite": "^1.0.30001629",
"electron-to-chromium": "^1.4.796",
"node-releases": "^2.0.14",
"update-browserslist-db": "^1.0.13"
"update-browserslist-db": "^1.0.16"
},
"bin": {
"browserslist": "cli.js"
@ -4340,18 +4347,18 @@
}
},
"node_modules/camera-controls": {
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-2.8.4.tgz",
"integrity": "sha512-pzVKpeZCRXIx2VOMB+E4OPjOhErHqhxrHYxcRLofOVgBeCeKSb8QAC2toc1onMllrxldRWXR8bl4K50hkrtwsg==",
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-2.8.5.tgz",
"integrity": "sha512-7VTwRk7Nu1nRKsY7bEt9HVBfKt8DETvzyYhLN4OW26OByBayMDB5fUaNcPI+z++vG23RH5yqn6ZRhZcgLQy2rA==",
"license": "MIT",
"peerDependencies": {
"three": ">=0.126.1"
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001626",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001626.tgz",
"integrity": "sha512-JRW7kAH8PFJzoPCJhLSHgDgKg5348hsQ68aqb+slnzuB5QFERv846oA/mRChmlLAOdEDeOkRn3ynb1gSFnjt3w==",
"version": "1.0.30001636",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz",
"integrity": "sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==",
"funding": [
{
"type": "opencollective",
@ -5217,9 +5224,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.4.788",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.788.tgz",
"integrity": "sha512-ubp5+Ev/VV8KuRoWnfP2QF2Bg+O2ZFdb49DiiNbz2VmgkIqrnyYaqIOqj8A6K/3p1xV0QcU5hBQ1+BmB6ot1OA==",
"version": "1.4.805",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.805.tgz",
"integrity": "sha512-8W4UJwX/w9T0QSzINJckTKG6CYpAUTqsaWcWIsdud3I1FYJcMgW9QqT1/4CBff/pP/TihWh13OmiyY8neto6vw==",
"license": "ISC"
},
"node_modules/ellipsize": {
@ -5891,9 +5898,9 @@
}
},
"node_modules/foreground-child": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
"integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz",
"integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==",
"dev": true,
"license": "ISC",
"dependencies": {
@ -6331,9 +6338,9 @@
}
},
"node_modules/graphology-metrics": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/graphology-metrics/-/graphology-metrics-2.2.0.tgz",
"integrity": "sha512-eZZFRLGGyyI+iD+XwQvc+lLM3EKCoqUvVjvF/14Htgy4grB2m95OytToYq3saWuHfuf22VVnj9GBHv/pTzKuTw==",
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/graphology-metrics/-/graphology-metrics-2.3.0.tgz",
"integrity": "sha512-ayDWSnSlq2wIL1QIrRQ3cIUsS7V/kRdBzciLPkGsI8Vl/MDQkxiU9WDt9cUAMzAE6Q1n1pWT5SXPkKeZdKCjzA==",
"license": "MIT",
"dependencies": {
"graphology-shortest-path": "^2.0.0",
@ -6780,9 +6787,9 @@
}
},
"node_modules/jackspeak": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.1.2.tgz",
"integrity": "sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz",
"integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
@ -8474,9 +8481,9 @@
}
},
"node_modules/jiti": {
"version": "1.21.0",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz",
"integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==",
"version": "1.21.6",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz",
"integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==",
"dev": true,
"license": "MIT",
"bin": {
@ -8785,9 +8792,9 @@
}
},
"node_modules/meshline": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/meshline/-/meshline-3.2.1.tgz",
"integrity": "sha512-/Zrhq1sbQCtqSsrud0hN/U8wOdKKOcxCWefEowZRHsosIcy1p87+2PlWSNO4s9zOoT/zjrQR8YikXYao8XCqVQ==",
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/meshline/-/meshline-3.3.1.tgz",
"integrity": "sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==",
"license": "MIT",
"peerDependencies": {
"three": ">=0.137"
@ -8958,9 +8965,9 @@
}
},
"node_modules/nan": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz",
"integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==",
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz",
"integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==",
"license": "MIT",
"optional": true
},
@ -9308,27 +9315,27 @@
"node": ">=8"
}
},
"node_modules/path2d-polyfill": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz",
"integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==",
"node_modules/path2d": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/path2d/-/path2d-0.2.0.tgz",
"integrity": "sha512-KdPAykQX6kmLSOO6Jpu2KNcCED7CKjmaBNGGNuctOsG0hgYO1OdYQaan6cYXJiG0WmXOwZZPILPBimu5QAIw3A==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=8"
"node": ">=6"
}
},
"node_modules/pdfjs-dist": {
"version": "3.11.174",
"resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz",
"integrity": "sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA==",
"version": "4.3.136",
"resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.3.136.tgz",
"integrity": "sha512-gzfnt1qc4yA+U46golPGYtU4WM2ssqP2MvFjKga8GEKOrEnzRPrA/9jogLLPYHiA3sGBPJ+p7BdAq+ytmw3jEg==",
"license": "Apache-2.0",
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"canvas": "^2.11.2",
"path2d-polyfill": "^2.0.1"
"path2d": "^0.2.0"
}
},
"node_modules/picocolors": {
@ -9542,9 +9549,9 @@
}
},
"node_modules/postcss-load-config/node_modules/lilconfig": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz",
"integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz",
"integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==",
"dev": true,
"license": "MIT",
"engines": {
@ -9555,9 +9562,9 @@
}
},
"node_modules/postcss-load-config/node_modules/yaml": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.3.tgz",
"integrity": "sha512-sntgmxj8o7DE7g/Qi60cqpLBA3HG3STcDA0kO+WfB05jEKhZMbY7umNm2rBpQvsmZ16/lPXCJGW2672dgOUkrg==",
"version": "2.4.5",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz",
"integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==",
"dev": true,
"license": "ISC",
"bin": {
@ -9853,18 +9860,17 @@
}
},
"node_modules/react-pdf": {
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-7.7.3.tgz",
"integrity": "sha512-a2VfDl8hiGjugpqezBTUzJHYLNB7IS7a2t7GD52xMI9xHg8LdVaTMsnM9ZlNmKadnStT/tvX5IfV0yLn+JvYmw==",
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-9.0.0.tgz",
"integrity": "sha512-J+pza8R2p9oNEOJOHIQJI4o5rFK7ji7bBl2IvsHvz1OOyphvuzVDo5tOJwWAFAbxYauCH3Kt8jOvcMJUOpxYZQ==",
"license": "MIT",
"dependencies": {
"clsx": "^2.0.0",
"dequal": "^2.0.3",
"make-cancellable-promise": "^1.3.1",
"make-event-props": "^1.6.0",
"merge-refs": "^1.2.1",
"pdfjs-dist": "3.11.174",
"prop-types": "^15.6.2",
"merge-refs": "^1.3.0",
"pdfjs-dist": "4.3.136",
"tiny-invariant": "^1.0.0",
"warning": "^4.0.0"
},
@ -9872,9 +9878,9 @@
"url": "https://github.com/wojtekmaj/react-pdf?sponsor=1"
},
"peerDependencies": {
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
@ -10006,9 +10012,9 @@
}
},
"node_modules/react-tooltip": {
"version": "5.26.4",
"resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.26.4.tgz",
"integrity": "sha512-5WyDrsfw1+6qNVSr3IjqElqJ+cCwE8+44b+HpJ8qRLv7v0a3mcKf8wvv+NfgALFS6QpksGFqTLV2JQ60c+okZQ==",
"version": "5.27.0",
"resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.27.0.tgz",
"integrity": "sha512-JXROcdfCEbCqkAkh8LyTSP3guQ0dG53iY2E2o4fw3D8clKzziMpE6QG6CclDaHELEKTzpMSeAOsdtg0ahoQosw==",
"license": "MIT",
"dependencies": {
"@floating-ui/dom": "^1.6.1",
@ -10097,9 +10103,9 @@
}
},
"node_modules/reagraph": {
"version": "4.19.0",
"resolved": "https://registry.npmjs.org/reagraph/-/reagraph-4.19.0.tgz",
"integrity": "sha512-hQZjA8coxFS6UjDhlP5Ho/woc36sM1dqsliwvTtThN/1ZoGbAuxt5QntVka4jesh0HhNB3ZafYgCIqXoo2sq8Q==",
"version": "4.19.1",
"resolved": "https://registry.npmjs.org/reagraph/-/reagraph-4.19.1.tgz",
"integrity": "sha512-BMSfZ2CoLSWsxe+vowHbCZSqeJJlH2UcbQA6hM1JHI0PmZ/3UHIXCQUHXrsROtYIIH5AnFEj9pUAZh2FGnfGhw==",
"license": "Apache-2.0",
"dependencies": {
"@react-spring/three": "9.6.1",
@ -10806,9 +10812,9 @@
}
},
"node_modules/tailwindcss": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz",
"integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==",
"version": "3.4.4",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz",
"integrity": "sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -11086,9 +11092,9 @@
"license": "Apache-2.0"
},
"node_modules/ts-jest": {
"version": "29.1.4",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.4.tgz",
"integrity": "sha512-YiHwDhSvCiItoAgsKtoLFCuakDzDsJ1DLDnSouTaTmdOcOwIkSzbLXduaQ6M5DRVhuZC/NYaaZ/mtHbWMv/S6Q==",
"version": "29.1.5",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.5.tgz",
"integrity": "sha512-UuClSYxM7byvvYfyWdFI+/2UxMmwNyJb0NPkZPQE2hew3RurV7l7zURgOHAd/1I1ZdPpe3GUsXNXAcN8TFKSIg==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -11134,9 +11140,9 @@
}
},
"node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
"license": "0BSD"
},
"node_modules/tunnel-rat": {

View File

@ -14,8 +14,8 @@
"dependencies": {
"@lezer/lr": "^1.4.1",
"@tanstack/react-table": "^8.17.3",
"@uiw/codemirror-themes": "^4.22.1",
"@uiw/react-codemirror": "^4.22.1",
"@uiw/codemirror-themes": "^4.22.2",
"@uiw/react-codemirror": "^4.22.2",
"axios": "^1.7.2",
"@reactflow/core": "^11.8.3",
"clsx": "^2.1.1",
@ -27,24 +27,24 @@
"react-icons": "^4.12.0",
"react-intl": "^6.6.8",
"react-loader-spinner": "^5.4.5",
"react-pdf": "^7.7.3",
"react-pdf": "^9.0.0",
"react-router-dom": "^6.23.1",
"react-select": "^5.8.0",
"react-tabs": "^6.0.2",
"react-toastify": "^9.1.3",
"react-tooltip": "^5.26.4",
"reagraph": "^4.19.0",
"react-tooltip": "^5.27.0",
"reagraph": "^4.19.1",
"use-debounce": "^10.0.1"
},
"devDependencies": {
"@lezer/generator": "^1.7.0",
"@types/jest": "^29.5.12",
"@types/node": "^20.14.0",
"@types/node": "^20.14.5",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"@vitejs/plugin-react": "^4.3.0",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.19",
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.2",
@ -53,8 +53,8 @@
"eslint-plugin-tsdoc": "^0.2.17",
"jest": "^29.7.0",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.3",
"ts-jest": "^29.1.4",
"tailwindcss": "^3.4.4",
"ts-jest": "^29.1.5",
"typescript": "^5.4.5",
"vite": "^4.5.3"
},

View File

@ -1,3 +1,2 @@
User-agent: *
Allow: /
Disallow: /library

View File

@ -15,7 +15,7 @@ function Footer() {
className={clsx(
'z-navigation',
'mx-auto',
'sm:px-4 sm:py-2 flex flex-col items-center gap-1',
'px-3 py-2 flex flex-col items-center gap-1',
'text-xs sm:text-sm select-none whitespace-nowrap'
)}
>

View File

@ -11,7 +11,7 @@ import { UsersState } from '@/context/UsersContext';
import ErrorFallback from './ErrorFallback';
pdfjs.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.js', import.meta.url).toString();
pdfjs.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
const resetState = () => {
console.log('Resetting state after error fallback');

View File

@ -47,7 +47,7 @@ function Navigation() {
<div tabIndex={-1} className='flex items-center mr-auto cursor-pointer' onClick={navigateHome}>
<Logo />
</div>
<div className='flex'>
<div className='flex gap-1 py-[0.3rem]'>
<NavigationButton
text='Новая схема'
title='Создать новую схему'

View File

@ -23,6 +23,8 @@ function NavigationButton({ icon, title, titleHtml, hideTitle, onClick, text }:
'mr-1 h-full', // prettier: split lines
'flex items-center gap-1',
'clr-btn-nav',
'rounded-xl',
'transition duration-500',
'font-controls whitespace-nowrap',
{
'px-2': text,

View File

@ -14,8 +14,9 @@ function ToggleNavigationButton() {
tabIndex={-1}
className={clsx(
'absolute top-0 right-0 z-navigation flex items-center justify-center',
'clr-btn-nav',
'select-none disabled:cursor-not-allowed'
'clr-hover',
'select-none',
'min-h-[2rem]'
)}
onClick={toggleNoNavigation}
initial={false}

View File

@ -1,5 +1,6 @@
import { AccessPolicy, LibraryItemType, LocationHead } from '@/models/library';
import { CstMatchMode, DependencyMode } from '@/models/miscellaneous';
import { ExpressionStatus } from '@/models/rsform';
import {
IconAlias,
@ -21,6 +22,10 @@ import {
IconRSForm,
IconSettings,
IconShow,
IconStatusError,
IconStatusIncalculable,
IconStatusOK,
IconStatusUnknown,
IconTemplates,
IconTerm,
IconText,
@ -80,34 +85,51 @@ export function LocationIcon({ value, size = '1.25rem', className }: DomIconProp
}
}
export function DependencyIcon(mode: DependencyMode, size: string, color?: string) {
switch (mode) {
export function DependencyIcon({ value, size = '1.25rem', className }: DomIconProps<DependencyMode>) {
switch (value) {
case DependencyMode.ALL:
return <IconSettings size={size} className={color} />;
return <IconSettings size={size} className={className} />;
case DependencyMode.EXPRESSION:
return <IconText size={size} className={color} />;
return <IconText size={size} className={className ?? 'clr-text-primary'} />;
case DependencyMode.OUTPUTS:
return <IconGraphOutputs size={size} className={color} />;
return <IconGraphOutputs size={size} className={className ?? 'clr-text-primary'} />;
case DependencyMode.INPUTS:
return <IconGraphInputs size={size} className={color} />;
return <IconGraphInputs size={size} className={className ?? 'clr-text-primary'} />;
case DependencyMode.EXPAND_OUTPUTS:
return <IconGraphExpand size={size} className={color} />;
return <IconGraphExpand size={size} className={className ?? 'clr-text-primary'} />;
case DependencyMode.EXPAND_INPUTS:
return <IconGraphCollapse size={size} className={color} />;
return <IconGraphCollapse size={size} className={className ?? 'clr-text-primary'} />;
}
}
export function MatchModeIcon(mode: CstMatchMode, size: string, color?: string) {
switch (mode) {
export function MatchModeIcon({ value, size = '1.25rem', className }: DomIconProps<CstMatchMode>) {
switch (value) {
case CstMatchMode.ALL:
return <IconFilter size={size} className={color} />;
return <IconFilter size={size} className={className} />;
case CstMatchMode.TEXT:
return <IconText size={size} className={color} />;
return <IconText size={size} className={className ?? 'clr-text-primary'} />;
case CstMatchMode.EXPR:
return <IconFormula size={size} className={color} />;
return <IconFormula size={size} className={className ?? 'clr-text-primary'} />;
case CstMatchMode.TERM:
return <IconTerm size={size} className={color} />;
return <IconTerm size={size} className={className ?? 'clr-text-primary'} />;
case CstMatchMode.NAME:
return <IconAlias size={size} className={color} />;
return <IconAlias size={size} className={className ?? 'clr-text-primary'} />;
}
}
export function StatusIcon({ value, size = '1.25rem', className }: DomIconProps<ExpressionStatus>) {
switch (value) {
case ExpressionStatus.VERIFIED:
case ExpressionStatus.PROPERTY:
return <IconStatusOK size={size} className={className} />;
case ExpressionStatus.UNKNOWN:
return <IconStatusUnknown size={size} className={className} />;
case ExpressionStatus.INCALCULABLE:
return <IconStatusIncalculable size={size} className={className} />;
case ExpressionStatus.INCORRECT:
case ExpressionStatus.UNDEFINED:
return <IconStatusError size={size} className={className} />;
}
}

View File

@ -18,7 +18,8 @@ export { BiCog as IconSettings } from 'react-icons/bi';
export { TbEye as IconShow } from 'react-icons/tb';
export { TbEyeX as IconHide } from 'react-icons/tb';
export { BiShareAlt as IconShare } from 'react-icons/bi';
export { BiFilterAlt as IconFilter } from 'react-icons/bi';
export { LuFilter as IconFilter } from 'react-icons/lu';
export { LuFilterX as IconFilterReset } from 'react-icons/lu';
export {BiDownArrowCircle as IconOpenList } from 'react-icons/bi';
export { LuAlertTriangle as IconAlert } from 'react-icons/lu';
@ -30,7 +31,11 @@ export { RiMenuFoldFill as IconMenuFold } from 'react-icons/ri';
export { RiMenuUnfoldFill as IconMenuUnfold } from 'react-icons/ri';
export { LuMoon as IconDarkTheme } from 'react-icons/lu';
export { LuSun as IconLightTheme } from 'react-icons/lu';
export { FaRegFolder as IconFolder } from 'react-icons/fa';
export { LuFolderTree as IconFolderTree } from 'react-icons/lu';
export { LuFolder as IconFolder } from 'react-icons/lu';
export { LuFolderOpen as IconFolderOpened } from 'react-icons/lu';
export { LuFolderClosed as IconFolderClosed } from 'react-icons/lu';
export { LuFolderDot as IconFolderEmpty } from 'react-icons/lu';
export { LuLightbulb as IconHelp } from 'react-icons/lu';
export { LuLightbulbOff as IconHelpOff } from 'react-icons/lu';
export { RiPushpinFill as IconPin } from 'react-icons/ri';

View File

@ -10,12 +10,13 @@ import { forwardRef, useCallback, useMemo, useRef } from 'react';
import Label from '@/components/ui/Label';
import { useConceptOptions } from '@/context/OptionsContext';
import { useRSForm } from '@/context/RSFormContext';
import { getFontClassName } from '@/models/miscellaneousAPI';
import { ConstituentaID, IRSForm } from '@/models/rsform';
import { generateAlias, getCstTypePrefix, guessCstType } from '@/models/rsformAPI';
import { extractGlobals } from '@/models/rslangAPI';
import { ccBracketMatching } from './bracketMatching';
import { rsNavigation } from './clickNavigation';
import { RSLanguage } from './rslang';
import { getSymbolSubstitute, RSTextWrapper } from './textEditing';
import { rsHoverTooltip } from './tooltip';
@ -39,6 +40,8 @@ interface RSInputProps
noTooltip?: boolean;
onChange?: (newValue: string) => void;
onAnalyze?: () => void;
schema?: IRSForm;
onOpenEdit?: (cstID: ConstituentaID) => void;
}
const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
@ -49,6 +52,9 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
disabled,
noTooltip,
schema,
onOpenEdit,
className,
style,
@ -59,7 +65,6 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
ref
) => {
const { darkMode, colors, mathFont } = useConceptOptions();
const { schema } = useRSForm();
const internalRef = useRef<ReactCodeMirrorRef>(null);
const thisRef = useMemo(() => (!ref || typeof ref === 'function' ? internalRef : ref), [internalRef, ref]);
@ -77,7 +82,7 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
caret: colors.fgDefault
},
styles: [
{ tag: tags.name, color: colors.fgPurple, cursor: 'default' }, // GlobalID
{ tag: tags.name, color: colors.fgPurple, cursor: schema ? 'default' : cursor }, // GlobalID
{ tag: tags.variableName, color: colors.fgGreen }, // LocalID
{ tag: tags.propertyName, color: colors.fgTeal }, // Radical
{ tag: tags.keyword, color: colors.fgBlue }, // keywords
@ -87,7 +92,7 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
{ tag: tags.brace, color: colors.fgPurple, fontWeight: '600' } // braces (curly brackets)
]
}),
[disabled, colors, darkMode]
[disabled, colors, darkMode, schema, cursor]
);
const editorExtensions = useMemo(
@ -95,9 +100,10 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
EditorView.lineWrapping,
RSLanguage,
ccBracketMatching(darkMode),
...(noTooltip || !schema ? [] : [rsHoverTooltip(schema)])
...(!schema || !onOpenEdit ? [] : [rsNavigation(schema, onOpenEdit)]),
...(noTooltip || !schema ? [] : [rsHoverTooltip(schema, onOpenEdit !== undefined)])
],
[darkMode, schema, noTooltip]
[darkMode, schema, noTooltip, onOpenEdit]
);
const handleInput = useCallback(

View File

@ -0,0 +1,38 @@
import { Extension } from '@codemirror/state';
import { EditorView } from '@uiw/react-codemirror';
import { ConstituentaID, IRSForm } from '@/models/rsform';
import { findAliasAt } from '@/utils/codemirror';
const navigationProducer = (schema: IRSForm, onOpenEdit: (cstID: ConstituentaID) => void) => {
return EditorView.domEventHandlers({
click: (event: MouseEvent, view: EditorView) => {
if (!event.ctrlKey && !event.metaKey) {
return;
}
const pos = view.posAtCoords({ x: event.clientX, y: event.clientY });
if (!pos) {
return;
}
const { alias } = findAliasAt(pos, view.state);
if (!alias) {
return;
}
const cst = schema.cstByAlias.get(alias);
if (!cst) {
return;
}
event.preventDefault();
event.stopPropagation();
onOpenEdit(cst.id);
}
});
};
export function rsNavigation(schema: IRSForm, onOpenEdit: (cstID: ConstituentaID) => void): Extension {
return [navigationProducer(schema, onOpenEdit)];
}

View File

@ -1,31 +1,11 @@
import { syntaxTree } from '@codemirror/language';
import { Extension } from '@codemirror/state';
import { hoverTooltip } from '@codemirror/view';
import { EditorState } from '@uiw/react-codemirror';
import { IRSForm } from '@/models/rsform';
import { findEnvelopingNodes } from '@/utils/codemirror';
import { findAliasAt } from '@/utils/codemirror';
import { domTooltipConstituenta } from '@/utils/codemirror';
import { GlobalTokens } from './rslang';
function findAliasAt(pos: number, state: EditorState) {
const { from: lineStart, to: lineEnd, text } = state.doc.lineAt(pos);
const nodes = findEnvelopingNodes(pos, pos, syntaxTree(state), GlobalTokens);
let alias = '';
let start = 0;
let end = 0;
nodes.forEach(node => {
if (node.to <= lineEnd && node.from >= lineStart) {
alias = text.slice(node.from - lineStart, node.to - lineStart);
start = node.from;
end = node.to;
}
});
return { alias, start, end };
}
const globalsHoverTooltip = (schema: IRSForm) => {
const tooltipProducer = (schema: IRSForm, canClick?: boolean) => {
return hoverTooltip((view, pos) => {
const { alias, start, end } = findAliasAt(pos, view.state);
if (!alias) {
@ -36,11 +16,11 @@ const globalsHoverTooltip = (schema: IRSForm) => {
pos: start,
end: end,
above: false,
create: () => domTooltipConstituenta(cst)
create: () => domTooltipConstituenta(cst, canClick)
};
});
};
export function rsHoverTooltip(schema: IRSForm): Extension {
return [globalsHoverTooltip(schema)];
export function rsHoverTooltip(schema: IRSForm, canClick?: boolean): Extension {
return [tooltipProducer(schema, canClick)];
}

View File

@ -13,10 +13,11 @@ import Label from '@/components/ui/Label';
import { useConceptOptions } from '@/context/OptionsContext';
import DlgEditReference from '@/dialogs/DlgEditReference';
import { ReferenceType } from '@/models/language';
import { IRSForm } from '@/models/rsform';
import { ConstituentaID, IRSForm } from '@/models/rsform';
import { CodeMirrorWrapper } from '@/utils/codemirror';
import { PARAMETER } from '@/utils/constants';
import { refsNavigation } from './clickNavigation';
import { NaturalLanguage, ReferenceTokens } from './parse';
import { RefEntity } from './parse/parser.terms';
import { refsHoverTooltip } from './tooltip';
@ -65,6 +66,7 @@ interface RefsInputInputProps
label?: string;
onChange?: (newValue: string) => void;
schema?: IRSForm;
onOpenEdit?: (cstID: ConstituentaID) => void;
disabled?: boolean;
initialValue?: string;
@ -73,7 +75,23 @@ interface RefsInputInputProps
}
const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
({ id, label, disabled, schema, initialValue, value, resolved, onFocus, onBlur, onChange, ...restProps }, ref) => {
(
{
id, // prettier: split-lines
label,
disabled,
schema,
onOpenEdit,
initialValue,
value,
resolved,
onFocus,
onBlur,
onChange,
...restProps
},
ref
) => {
const { darkMode, colors } = useConceptOptions();
const [isFocused, setIsFocused] = useState(false);
@ -114,9 +132,10 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
EditorView.lineWrapping,
EditorView.contentAttributes.of({ spellcheck: 'true' }),
NaturalLanguage,
...(schema ? [refsHoverTooltip(schema, colors)] : [])
...(!schema || !onOpenEdit ? [] : [refsNavigation(schema, onOpenEdit)]),
...(schema ? [refsHoverTooltip(schema, colors, onOpenEdit !== undefined)] : [])
],
[schema, colors]
[schema, colors, onOpenEdit]
);
function handleChange(newValue: string) {

View File

@ -0,0 +1,38 @@
import { Extension } from '@codemirror/state';
import { EditorView } from '@uiw/react-codemirror';
import { ConstituentaID, IRSForm } from '@/models/rsform';
import { findReferenceAt } from '@/utils/codemirror';
const navigationProducer = (schema: IRSForm, onOpenEdit: (cstID: ConstituentaID) => void) => {
return EditorView.domEventHandlers({
click: (event: MouseEvent, view: EditorView) => {
if (!event.ctrlKey && !event.metaKey) {
return;
}
const pos = view.posAtCoords({ x: event.clientX, y: event.clientY });
if (!pos) {
return;
}
const parse = findReferenceAt(pos, view.state);
if (!parse || !('entity' in parse.ref)) {
return;
}
const cst = schema.cstByAlias.get(parse.ref.entity);
if (!cst) {
return;
}
event.preventDefault();
event.stopPropagation();
onOpenEdit(cst.id);
}
});
};
export function refsNavigation(schema: IRSForm, onOpenEdit: (cstID: ConstituentaID) => void): Extension {
return [navigationProducer(schema, onOpenEdit)];
}

View File

@ -2,65 +2,58 @@ import { syntaxTree } from '@codemirror/language';
import { Extension } from '@codemirror/state';
import { hoverTooltip } from '@codemirror/view';
import { parseEntityReference, parseSyntacticReference } from '@/models/languageAPI';
import { IEntityReference, ISyntacticReference } from '@/models/language';
import { IRSForm } from '@/models/rsform';
import { IColorTheme } from '@/styling/color';
import {
domTooltipEntityReference,
domTooltipSyntacticReference,
findContainedNodes,
findEnvelopingNodes
findReferenceAt
} from '@/utils/codemirror';
import { ReferenceTokens } from './parse';
import { RefEntity, RefSyntactic } from './parse/parser.terms';
import { RefEntity } from './parse/parser.terms';
export const globalsHoverTooltip = (schema: IRSForm, colors: IColorTheme) => {
export const tooltipProducer = (schema: IRSForm, colors: IColorTheme, canClick?: boolean) => {
return hoverTooltip((view, pos) => {
const nodes = findEnvelopingNodes(pos, pos, syntaxTree(view.state), ReferenceTokens);
if (nodes.length !== 1) {
const parse = findReferenceAt(pos, view.state);
if (!parse) {
return null;
}
const start = nodes[0].from;
const end = nodes[0].to;
const text = view.state.doc.sliceString(start, end);
if (nodes[0].type.id === RefEntity) {
const ref = parseEntityReference(text);
const cst = schema.cstByAlias.get(ref.entity);
if ('entity' in parse.ref) {
const cst = schema.cstByAlias.get(parse.ref.entity);
return {
pos: start,
end: end,
pos: parse.start,
end: parse.end,
above: false,
create: () => domTooltipEntityReference(ref, cst, colors)
create: () => domTooltipEntityReference(parse.ref as IEntityReference, cst, colors, canClick)
};
} else if (nodes[0].type.id === RefSyntactic) {
const ref = parseSyntacticReference(text);
} else {
let masterText: string | undefined = undefined;
if (ref.offset > 0) {
const entities = findContainedNodes(end, view.state.doc.length, syntaxTree(view.state), [RefEntity]);
if (ref.offset <= entities.length) {
const master = entities[ref.offset - 1];
if (parse.ref.offset > 0) {
const entities = findContainedNodes(parse.end, view.state.doc.length, syntaxTree(view.state), [RefEntity]);
if (parse.ref.offset <= entities.length) {
const master = entities[parse.ref.offset - 1];
masterText = view.state.doc.sliceString(master.from, master.to);
}
} else {
const entities = findContainedNodes(0, start, syntaxTree(view.state), [RefEntity]);
if (-ref.offset <= entities.length) {
const master = entities[-ref.offset - 1];
const entities = findContainedNodes(0, parse.start, syntaxTree(view.state), [RefEntity]);
if (-parse.ref.offset <= entities.length) {
const master = entities[-parse.ref.offset - 1];
masterText = view.state.doc.sliceString(master.from, master.to);
}
}
return {
pos: start,
end: end,
pos: parse.start,
end: parse.end,
above: false,
create: () => domTooltipSyntacticReference(ref, masterText)
create: () => domTooltipSyntacticReference(parse.ref as ISyntacticReference, masterText, canClick)
};
} else {
return null;
}
});
};
export function refsHoverTooltip(schema: IRSForm, colors: IColorTheme): Extension {
return [globalsHoverTooltip(schema, colors)];
export function refsHoverTooltip(schema: IRSForm, colors: IColorTheme, canClick?: boolean): Extension {
return [tooltipProducer(schema, colors, canClick)];
}

View File

@ -50,7 +50,7 @@ function GraphSelectionToolbar({
disabled={emptySelection}
/>
<MiniButton
titleHtml='<b>Максимизация</b> - дополнение выделения конституентами, зависимыми только от выделенных'
titleHtml='<b>Максимизация</b> <br/>дополнение выделения конституентами, <br/>зависимыми только от выделенных'
icon={<IconGraphMaximize size='1.25rem' className='icon-primary' />}
onClick={() => setSelected(prev => graph.maximizePart(prev))}
disabled={emptySelection}

View File

@ -12,7 +12,7 @@ import { prefixes } from '@/utils/constants';
import { describeConstituenta } from '@/utils/labels';
import BadgeConstituenta from '../info/BadgeConstituenta';
import FlexColumn from '../ui/FlexColumn';
import NoData from '../ui/NoData';
interface PickConstituentaProps {
id?: string;
@ -106,10 +106,10 @@ function PickConstituenta({
columns={columns}
conditionalRowStyles={conditionalRowStyles}
noDataComponent={
<FlexColumn className='p-3 items-center min-h-[6rem]'>
<NoData className='min-h-[6rem]'>
<p>Список конституент пуст</p>
<p>Измените параметры фильтра</p>
</FlexColumn>
</NoData>
}
onRowClicked={onSelectValue}
/>

View File

@ -10,7 +10,7 @@ import { isBasicConcept } from '@/models/rsformAPI';
import { describeConstituenta } from '@/utils/labels';
import BadgeConstituenta from '../info/BadgeConstituenta';
import FlexColumn from '../ui/FlexColumn';
import NoData from '../ui/NoData';
import GraphSelectionToolbar from './GraphSelectionToolbar';
interface PickMultiConstituentaProps {
@ -103,9 +103,9 @@ function PickMultiConstituenta({ id, schema, prefixID, rows, selected, setSelect
rowSelection={rowSelection}
onRowSelectionChange={handleRowSelection}
noDataComponent={
<FlexColumn className='items-center p-3'>
<NoData>
<p>Список пуст</p>
</FlexColumn>
</NoData>
}
/>
</div>

View File

@ -5,7 +5,7 @@ import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/u
import SearchBar from '@/components/ui/SearchBar';
import { useLibrary } from '@/context/LibraryContext';
import { useConceptOptions } from '@/context/OptionsContext';
import { ILibraryItem, LibraryItemID } from '@/models/library';
import { ILibraryItem, LibraryItemID, LibraryItemType } from '@/models/library';
import { ILibraryFilter } from '@/models/miscellaneous';
import FlexColumn from '../ui/FlexColumn';
@ -32,7 +32,8 @@ function PickSchema({ id, initialFilter = '', rows = 4, value, onSelectValue }:
useLayoutEffect(() => {
setFilter({
query: filterText
query: filterText,
type: LibraryItemType.RSFORM
});
}, [filterText]);
@ -103,7 +104,7 @@ function PickSchema({ id, initialFilter = '', rows = 4, value, onSelectValue }:
columns={columns}
conditionalRowStyles={conditionalRowStyles}
noDataComponent={
<FlexColumn className='p-3 items-center min-h-[6rem]'>
<FlexColumn className='dense p-3 items-center min-h-[6rem]'>
<p>Список схем пуст</p>
<p>Измените параметры фильтра</p>
</FlexColumn>

View File

@ -23,6 +23,7 @@ import {
IconRemove,
IconReplace
} from '../Icons';
import NoData from '../ui/NoData';
interface PickSubstitutionsProps {
prefixID: string;
@ -259,10 +260,10 @@ function PickSubstitutions({
columns={columns}
headPosition='0'
noDataComponent={
<span className='p-2 text-center min-h-[2rem]'>
<NoData className='min-h-[2rem]'>
<p>Список пуст</p>
<p>Добавьте отождествление</p>
</span>
</NoData>
}
/>
</div>

View File

@ -37,7 +37,7 @@ function SelectAccessPolicy({ value, disabled, stretchLeft, onChange }: SelectAc
<MiniButton
title={`Доступ: ${labelAccessPolicy(value)}`}
hideTitle={menu.isOpen}
className='h-full disabled:cursor-auto'
className='h-full'
icon={<PolicyIcon value={value} size='1.25rem' />}
onClick={menu.toggle}
disabled={disabled}

View File

@ -36,10 +36,10 @@ function SelectGraphFilter({ value, dense, onChange }: SelectGraphFilterProps) {
<SelectorButton
transparent
tabIndex={-1}
title='Настройка фильтрации по графу термов'
titleHtml='Настройка фильтрации <br/>по графу термов'
hideTitle={menu.isOpen}
className='h-full pr-2'
icon={DependencyIcon(value, '1rem', value !== DependencyMode.ALL ? 'icon-primary' : '')}
icon={<DependencyIcon value={value} size='1rem' />}
text={dense || size.isSmall ? undefined : labelCstSource(value)}
onClick={menu.toggle}
/>
@ -55,7 +55,7 @@ function SelectGraphFilter({ value, dense, onChange }: SelectGraphFilterProps) {
onClick={() => handleChange(source)}
>
<div className='inline-flex items-center gap-1'>
{DependencyIcon(source, '1rem')}
{<DependencyIcon value={source} size='1rem' />}
{!dense ? (
<span>
<b>{labelCstSource(source)}:</b> {describeCstSource(source)}

View File

@ -38,7 +38,7 @@ function SelectItemType({ value, disabled, stretchLeft, onChange }: SelectItemTy
transparent
title={describeLibraryItemType(value)}
hideTitle={menu.isOpen}
className='h-full py-1 px-2 disabled:cursor-auto rounded-lg'
className='h-full px-2 py-1 rounded-lg'
icon={<ItemTypeIcon value={value} size='1.25rem' />}
text={labelLibraryItemType(value)}
onClick={menu.toggle}

View File

@ -0,0 +1,42 @@
'use client';
import { useCallback } from 'react';
import useDropdown from '@/hooks/useDropdown';
import { FolderTree } from '@/models/FolderTree';
import { IconFolderTree } from '../Icons';
import MiniButton from '../ui/MiniButton';
interface SelectLocationProps {
value: string;
onChange: (newValue: string) => void;
folderTree: FolderTree;
}
function SelectLocation({ value, onChange, folderTree }: SelectLocationProps) {
const menu = useDropdown();
const handleChange = useCallback(
(newValue: string) => {
console.log(folderTree.roots.size);
console.log(value);
menu.hide();
onChange(newValue);
},
[menu, onChange, value, folderTree]
);
return (
<div ref={menu.ref} className='h-full text-right'>
<MiniButton
title='Проводник...'
icon={<IconFolderTree size='1.25rem' className='icon-green' />}
onClick={() => handleChange('/U/test')}
/>
</div>
);
}
export default SelectLocation;

View File

@ -14,7 +14,7 @@ import DropdownButton from '../ui/DropdownButton';
interface SelectLocationHeadProps {
value: LocationHead;
onChange: (value: LocationHead) => void;
onChange: (newValue: LocationHead) => void;
excluded?: LocationHead[];
}

View File

@ -35,10 +35,10 @@ function SelectMatchMode({ value, dense, onChange }: SelectMatchModeProps) {
<div ref={menu.ref}>
<SelectorButton
transparent
title='Настройка фильтрации по проверяемым атрибутам'
titleHtml='Настройка фильтрации <br/>по проверяемым атрибутам'
hideTitle={menu.isOpen}
className='h-full pr-2'
icon={MatchModeIcon(value, '1rem', value !== CstMatchMode.ALL ? 'icon-primary' : '')}
icon={<MatchModeIcon value={value} size='1rem' />}
text={dense || size.isSmall ? undefined : labelCstMatchMode(value)}
onClick={menu.toggle}
/>
@ -54,7 +54,7 @@ function SelectMatchMode({ value, dense, onChange }: SelectMatchModeProps) {
onClick={() => handleChange(matchMode)}
>
<div className='inline-flex items-center gap-1'>
{MatchModeIcon(matchMode, '1rem')}
{<MatchModeIcon value={matchMode} size='1rem' />}
{!dense ? (
<span>
<b>{labelCstMatchMode(matchMode)}:</b> {describeCstMatchMode(matchMode)}

View File

@ -33,7 +33,7 @@ function Button({
disabled={disabled ?? loading}
className={clsx(
'inline-flex gap-2 items-center justify-center',
'select-none disabled:cursor-not-allowed',
'select-none disabled:cursor-auto',
{
'border rounded': !noBorder,
'px-1': dense,

View File

@ -27,7 +27,7 @@ function Checkbox({
}: CheckboxProps) {
const cursor = useMemo(() => {
if (disabled) {
return 'cursor-not-allowed';
return 'cursor-auto';
} else if (setValue) {
return 'cursor-pointer';
} else {

View File

@ -25,7 +25,7 @@ function CheckboxTristate({
}: CheckboxTristateProps) {
const cursor = useMemo(() => {
if (disabled) {
return 'cursor-not-allowed';
return 'cursor-auto';
} else if (setValue) {
return 'cursor-pointer';
} else {

View File

@ -35,7 +35,7 @@ function DropdownButton({
'disabled:clr-text-controls',
{
'clr-hover': onClick,
'cursor-pointer disabled:cursor-not-allowed': onClick,
'cursor-pointer disabled:cursor-auto': onClick,
'cursor-default': !onClick
},
className

View File

@ -2,9 +2,7 @@ import clsx from 'clsx';
import { CProps } from '../props';
export interface FlexColumnProps extends CProps.Div {}
function FlexColumn({ className, children, ...restProps }: FlexColumnProps) {
function FlexColumn({ className, children, ...restProps }: CProps.Div) {
return (
<div className={clsx('cc-column', className)} {...restProps}>
{children}

View File

@ -28,7 +28,7 @@ function MiniButton({
className={clsx(
'rounded-lg',
'clr-btn-clear',
'cursor-pointer disabled:cursor-not-allowed',
'cursor-pointer disabled:cursor-auto',
{
'px-1 py-1': !noPadding,
'outline-none': noHover,

View File

@ -0,0 +1,13 @@
import clsx from 'clsx';
import { CProps } from '../props';
function NoData({ className, children, ...restProps }: CProps.Div) {
return (
<div className={clsx('p-3 flex flex-col items-center text-center select-none w-full', className)} {...restProps}>
{children}
</div>
);
}
export default NoData;

View File

@ -11,21 +11,23 @@ import Overlay from '../Overlay';
import PageControls from './PageControls';
const MAXIMUM_WIDTH = 1000;
const MINIMUM_WIDTH = 320;
const MINIMUM_WIDTH = 300;
interface PDFViewerProps {
file?: string | ArrayBuffer | Blob;
offsetXpx?: number;
minWidth?: number;
}
function PDFViewer({ file }: PDFViewerProps) {
function PDFViewer({ file, offsetXpx, minWidth = MINIMUM_WIDTH }: PDFViewerProps) {
const windowSize = useWindowSize();
const [pageCount, setPageCount] = useState(0);
const [pageNumber, setPageNumber] = useState(1);
const pageWidth = useMemo(() => {
return Math.max(MINIMUM_WIDTH, Math.min((windowSize?.width ?? 0) - 10, MAXIMUM_WIDTH));
}, [windowSize]);
return Math.max(minWidth, Math.min((windowSize?.width ?? 0) - (offsetXpx ?? 0) - 10, MAXIMUM_WIDTH));
}, [windowSize, offsetXpx, minWidth]);
function onDocumentLoadSuccess({ numPages }: PDFDocumentProxy) {
setPageCount(numPages);
@ -42,14 +44,14 @@ function PDFViewer({ file }: PDFViewerProps) {
<PageControls pageCount={pageCount} pageNumber={pageNumber} setPageNumber={setPageNumber} />
</Overlay>
<Page
className='pointer-events-none select-none sm:translate-x-0'
className='overflow-hidden pointer-events-none select-none'
renderTextLayer={false}
renderAnnotationLayer={false}
pageNumber={pageNumber}
width={pageWidth}
canvasBackground={graphLightT.canvas.background}
/>
<Overlay position='bottom-6 left-1/2 -translate-x-1/2' className='flex select-none'>
<Overlay position='bottom-3 left-1/2 -translate-x-1/2' className='flex select-none'>
<PageControls pageCount={pageCount} pageNumber={pageNumber} setPageNumber={setPageNumber} />
</Overlay>
</Document>

View File

@ -8,7 +8,7 @@ interface PageControlsProps {
function PageControls({ pageNumber, pageCount, setPageNumber }: PageControlsProps) {
return (
<>
<div className='flex items-center'>
<button
type='button'
className='clr-hover clr-text-controls'
@ -25,9 +25,9 @@ function PageControls({ pageNumber, pageCount, setPageNumber }: PageControlsProp
>
<IconPageLeft size='1.5rem' />
</button>
<p className='px-3 text-black text-nowrap'>
<div className='px-3 text-nowrap'>
Страница {pageNumber} из {pageCount}
</p>
</div>
<button
type='button'
className='clr-hover clr-text-controls'
@ -44,7 +44,7 @@ function PageControls({ pageNumber, pageCount, setPageNumber }: PageControlsProp
>
<IconPageLast size='1.5rem' />
</button>
</>
</div>
);
}

View File

@ -92,7 +92,7 @@ function SelectTree<ItemType>({
value === item && 'clr-selected'
)}
data-tooltip-id={globals.tooltip}
data-tooltip-content={getDescription(item)}
data-tooltip-html={getDescription(item)}
onClick={event => handleSetValue(event, item)}
initial={{ ...animateSideAppear.initial }}
animate={{ ...animateSideAppear.animate }}

View File

@ -31,7 +31,7 @@ function SelectorButton({
'px-1 flex flex-start items-center gap-1',
'text-sm font-controls select-none',
'text-btn clr-text-controls',
'disabled:cursor-not-allowed cursor-pointer',
'disabled:cursor-auto cursor-pointer',
{
'clr-hover': transparent,
'border': !transparent

View File

@ -17,7 +17,7 @@ function SubmitButton({ text = 'ОК', icon, disabled, loading, className, ...re
'border',
'font-medium',
'clr-btn-primary',
'select-none disabled:cursor-not-allowed',
'select-none disabled:cursor-auto',
loading && 'cursor-progress',
className
)}

View File

@ -18,9 +18,9 @@ function TextURL({ text, href, title, color = 'clr-text-url', onClick }: TextURL
);
} else if (onClick) {
return (
<span tabIndex={-1} className={design} onClick={onClick}>
<button type='button' tabIndex={-1} className={design} onClick={onClick}>
{text}
</span>
</button>
);
} else {
return null;

View File

@ -33,7 +33,8 @@ function Tooltip({
delayHide={100}
opacity={0.97}
className={clsx(
'overflow-auto sm:overflow-hidden overscroll-contain',
'max-h-[calc(100svh-6rem)]',
'overflow-y-auto overflow-x-hidden sm:overflow-hidden overscroll-contain',
'border shadow-md',
'text-balance',
layer,

View File

@ -1,27 +1,34 @@
'use client';
import { AnimatePresence } from 'framer-motion';
import { useAuth } from '@/context/AuthContext';
import Loader from '../ui/Loader';
import TextURL from '../ui/TextURL';
import AnimateFade from './AnimateFade';
interface RequireAuthProps {
children: React.ReactNode;
}
function RequireAuth({ children }: RequireAuthProps) {
const { user } = useAuth();
if (user) {
return children;
} else {
return (
<div className='flex flex-col items-center gap-1 mt-2'>
<p className='mb-2'>Пожалуйста войдите в систему</p>
<TextURL text='Войти в Портал' href='/login' />
<TextURL text='Зарегистрироваться' href='/signup' />
<TextURL text='Начальная страница' href='/' />
</div>
);
}
const { user, loading } = useAuth();
return (
<AnimatePresence mode='wait'>
{loading ? <Loader key='auth-loader' /> : null}
{!loading && user ? <AnimateFade key='auth-data'>{children}</AnimateFade> : null}
{!loading && !user ? (
<AnimateFade key='auth-no-user' className='flex flex-col items-center gap-1 mt-2'>
<p className='mb-2'>Пожалуйста войдите в систему</p>
<TextURL text='Войти в Портал' href='/login' />
<TextURL text='Зарегистрироваться' href='/signup' />
<TextURL text='Начальная страница' href='/' />
</AnimateFade>
) : null}
</AnimatePresence>
);
}
export default RequireAuth;

View File

@ -3,6 +3,7 @@
import { createContext, useContext, useState } from 'react';
import { UserLevel } from '@/models/user';
import { contextOutsideScope } from '@/utils/labels';
interface IAccessModeContext {
accessLevel: UserLevel;
@ -13,7 +14,7 @@ const AccessContext = createContext<IAccessModeContext | null>(null);
export const useAccessMode = () => {
const context = useContext(AccessContext);
if (!context) {
throw new Error('useAccessMode has to be used within <AccessModeState.Provider>');
throw new Error(contextOutsideScope('useAccessMode', 'AccessModeState'));
}
return context;
};

View File

@ -25,6 +25,7 @@ import {
IUserSignupData,
IUserUpdatePassword
} from '@/models/user';
import { contextOutsideScope } from '@/utils/labels';
import { useUsers } from './UsersContext';
@ -46,7 +47,7 @@ const AuthContext = createContext<IAuthContext | null>(null);
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth has to be used within <AuthState.Provider>');
throw new Error(contextOutsideScope('useAuth', 'AuthState'));
}
return context;
};

View File

@ -1,6 +1,6 @@
'use client';
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import {
DataCallback,
@ -14,12 +14,14 @@ import {
postRSFormFromFile
} from '@/app/backendAPI';
import { ErrorData } from '@/components/info/InfoError';
import { ILibraryItem, LibraryItemID } from '@/models/library';
import { FolderTree } from '@/models/FolderTree';
import { ILibraryItem, LibraryItemID, LocationHead } from '@/models/library';
import { ILibraryCreateData } from '@/models/library';
import { matchLibraryItem, matchLibraryItemLocation } from '@/models/libraryAPI';
import { ILibraryFilter } from '@/models/miscellaneous';
import { IRSForm, IRSFormCloneData, IRSFormData } from '@/models/rsform';
import { RSFormLoader } from '@/models/RSFormLoader';
import { contextOutsideScope } from '@/utils/labels';
import { useAuth } from './AuthContext';
import { useConceptOptions } from './OptionsContext';
@ -27,10 +29,17 @@ import { useConceptOptions } from './OptionsContext';
interface ILibraryContext {
items: ILibraryItem[];
templates: ILibraryItem[];
folders: FolderTree;
loading: boolean;
loadingError: ErrorData;
setLoadingError: (error: ErrorData) => void;
processing: boolean;
error: ErrorData;
setError: (error: ErrorData) => void;
processingError: ErrorData;
setProcessingError: (error: ErrorData) => void;
reloadItems: (callback?: () => void) => void;
applyFilter: (params: ILibraryFilter) => ILibraryItem[];
retrieveTemplate: (templateID: LibraryItemID, callback: (schema: IRSForm) => void) => void;
@ -46,7 +55,7 @@ const LibraryContext = createContext<ILibraryContext | null>(null);
export const useLibrary = (): ILibraryContext => {
const context = useContext(LibraryContext);
if (context === null) {
throw new Error('useLibrary has to be used within <LibraryState.Provider>');
throw new Error(contextOutsideScope('useLibrary', 'LibraryState'));
}
return context;
};
@ -63,15 +72,32 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
const [templates, setTemplates] = useState<ILibraryItem[]>([]);
const [loading, setLoading] = useState(false);
const [processing, setProcessing] = useState(false);
const [error, setError] = useState<ErrorData>(undefined);
const [loadingError, setLoadingError] = useState<ErrorData>(undefined);
const [processingError, setProcessingError] = useState<ErrorData>(undefined);
const [cachedTemplates, setCachedTemplates] = useState<IRSForm[]>([]);
const folders = useMemo(() => {
const result = new FolderTree();
result.addPath(LocationHead.USER, 0);
result.addPath(LocationHead.COMMON, 0);
result.addPath(LocationHead.LIBRARY, 0);
result.addPath(LocationHead.PROJECTS, 0);
items.forEach(item => result.addPath(item.location));
return result;
}, [items]);
const applyFilter = useCallback(
(filter: ILibraryFilter) => {
let result = items;
if (filter.head) {
if (!filter.folderMode && filter.head) {
result = result.filter(item => item.location.startsWith(filter.head!));
}
if (filter.folderMode && filter.folder) {
result = result.filter(item => item.location == filter.folder);
}
if (filter.type) {
result = result.filter(item => item.item_type === filter.type);
}
if (filter.isVisible !== undefined) {
result = result.filter(item => filter.isVisible === item.visible);
}
@ -84,12 +110,12 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
if (filter.isEditor !== undefined) {
result = result.filter(item => filter.isEditor == user?.editor.includes(item.id));
}
if (!filter.folderMode && filter.path) {
result = result.filter(item => matchLibraryItemLocation(item, filter.path!));
}
if (filter.query) {
result = result.filter(item => matchLibraryItem(item, filter.query!));
}
if (filter.path) {
result = result.filter(item => matchLibraryItemLocation(item, filter.path!));
}
return result;
},
[items, user]
@ -102,11 +128,11 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
callback(cached);
return;
}
setError(undefined);
setProcessingError(undefined);
getRSFormDetails(String(templateID), '', {
showError: true,
setLoading: setProcessing,
onError: setError,
onError: setProcessingError,
onSuccess: data => {
const schema = new RSFormLoader(data).produceRSForm();
setCachedTemplates(prev => [...prev, schema]);
@ -120,12 +146,12 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
const reloadItems = useCallback(
(callback?: () => void) => {
setItems([]);
setError(undefined);
setLoadingError(undefined);
if (user?.is_staff && adminMode) {
getAdminLibrary({
setLoading: setLoading,
showError: true,
onError: setError,
onError: setLoadingError,
onSuccess: newData => {
setItems(newData);
if (callback) callback();
@ -135,7 +161,7 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
getLibrary({
setLoading: setLoading,
showError: true,
onError: setError,
onError: setLoadingError,
onSuccess: newData => {
setItems(newData);
if (callback) callback();
@ -149,6 +175,8 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
const reloadTemplates = useCallback(() => {
setTemplates([]);
getTemplates({
setLoading: setLoading,
onError: setLoadingError,
showError: true,
onSuccess: newData => setTemplates(newData)
});
@ -189,13 +217,13 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
}
if (callback) callback(newSchema);
});
setError(undefined);
setProcessingError(undefined);
if (data.file) {
postRSFormFromFile({
data: data,
showError: true,
setLoading: setProcessing,
onError: setError,
onError: setProcessingError,
onSuccess: onSuccess
});
} else {
@ -203,7 +231,7 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
data: data,
showError: true,
setLoading: setProcessing,
onError: setError,
onError: setProcessingError,
onSuccess: onSuccess
});
}
@ -213,11 +241,11 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
const destroyItem = useCallback(
(target: LibraryItemID, callback?: () => void) => {
setError(undefined);
setProcessingError(undefined);
deleteLibraryItem(String(target), {
showError: true,
setLoading: setProcessing,
onError: setError,
onError: setProcessingError,
onSuccess: () =>
reloadItems(() => {
if (user && user.subscriptions.includes(target)) {
@ -230,7 +258,7 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
})
});
},
[setError, reloadItems, user]
[reloadItems, user]
);
const cloneItem = useCallback(
@ -238,12 +266,12 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
if (!user) {
return;
}
setError(undefined);
setProcessingError(undefined);
postCloneLibraryItem(String(target), {
data: data,
showError: true,
setLoading: setProcessing,
onError: setError,
onError: setProcessingError,
onSuccess: newSchema =>
reloadItems(() => {
if (user && !user.subscriptions.includes(newSchema.id)) {
@ -253,18 +281,26 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
})
});
},
[reloadItems, setError, user]
[reloadItems, user]
);
return (
<LibraryContext.Provider
value={{
items,
folders,
templates,
loading,
loadingError,
setLoadingError,
processing,
error,
setError,
processingError,
setProcessingError,
reloadItems,
applyFilter,
createItem,
cloneItem,

View File

@ -4,6 +4,7 @@ import { createContext, useCallback, useContext, useEffect, useState } from 'rea
import { useLocation, useNavigate } from 'react-router-dom';
import { globals } from '@/utils/constants';
import { contextOutsideScope } from '@/utils/labels';
interface INavigationContext {
push: (path: string, newTab?: boolean) => void;
@ -21,7 +22,7 @@ const NavigationContext = createContext<INavigationContext | null>(null);
export const useConceptNavigation = () => {
const context = useContext(NavigationContext);
if (!context) {
throw new Error('useConceptNavigation has to be used within <NavigationState.Provider>');
throw new Error(contextOutsideScope('useConceptNavigation', 'NavigationState'));
}
return context;
};

View File

@ -9,6 +9,7 @@ import { FontStyle } from '@/models/miscellaneous';
import { animationDuration } from '@/styling/animations';
import { darkT, IColorTheme, lightT } from '@/styling/color';
import { globals, storage } from '@/utils/constants';
import { contextOutsideScope } from '@/utils/labels';
interface IOptionsContext {
viewportHeight: string;
@ -45,7 +46,7 @@ const OptionsContext = createContext<IOptionsContext | null>(null);
export const useConceptOptions = () => {
const context = useContext(OptionsContext);
if (!context) {
throw new Error('useConceptTheme has to be used within <ThemeState.Provider>');
throw new Error(contextOutsideScope('useConceptTheme', 'ThemeState'));
}
return context;
};

View File

@ -18,6 +18,7 @@ import { AccessPolicy, ILibraryItem } from '@/models/library';
import { ILibraryUpdateData } from '@/models/library';
import { IOperationSchema } from '@/models/oss';
import { UserID } from '@/models/user';
import { contextOutsideScope } from '@/utils/labels';
import { useAuth } from './AuthContext';
import { useLibrary } from './LibraryContext';
@ -48,7 +49,7 @@ const OssContext = createContext<IOssContext | null>(null);
export const useOSS = () => {
const context = useContext(OssContext);
if (context === null) {
throw new Error('useOSS has to be used within <OssState.Provider>');
throw new Error(contextOutsideScope('useOSS', 'OssState'));
}
return context;
};

View File

@ -48,6 +48,7 @@ import {
ITargetCst
} from '@/models/rsform';
import { UserID } from '@/models/user';
import { contextOutsideScope } from '@/utils/labels';
import { useAuth } from './AuthContext';
import { useLibrary } from './LibraryContext';
@ -99,7 +100,7 @@ const RSFormContext = createContext<IRSFormContext | null>(null);
export const useRSForm = () => {
const context = useContext(RSFormContext);
if (context === null) {
throw new Error('useRSForm has to be used within <RSFormState.Provider>');
throw new Error(contextOutsideScope('useRSForm', 'RSFormState'));
}
return context;
};
@ -295,8 +296,7 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
onError: setProcessingError,
onSuccess: () => {
schema.location = newLocation;
library.localUpdateItem(schema);
if (callback) callback();
library.reloadItems(callback);
}
});
},
@ -336,7 +336,7 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
setLoading: setProcessing,
onError: setProcessingError,
onSuccess: newData => {
setSchema(Object.assign(schema, newData));
setSchema(newData);
library.localUpdateTimestamp(newData.id);
if (callback) callback();
}
@ -356,7 +356,7 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
setLoading: setProcessing,
onError: setProcessingError,
onSuccess: newData => {
setSchema(Object.assign(schema, newData));
setSchema(newData);
library.localUpdateTimestamp(newData.id);
if (callback) callback();
}
@ -572,14 +572,14 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
showError: true,
setLoading: setProcessing,
onError: setProcessingError,
onSuccess: () => {
setSchema(schema);
library.localUpdateItem(schema!);
onSuccess: newData => {
setSchema(newData);
library.localUpdateItem(newData);
if (callback) callback();
}
});
},
[schema, setSchema, library]
[setSchema, library]
);
const inlineSynthesis = useCallback(

View File

@ -5,6 +5,7 @@ import { createContext, useCallback, useContext, useEffect, useState } from 'rea
import { DataCallback, getProfile, patchProfile } from '@/app/backendAPI';
import { ErrorData } from '@/components/info/InfoError';
import { IUserProfile, IUserUpdateData } from '@/models/user';
import { contextOutsideScope } from '@/utils/labels';
import { useUsers } from './UsersContext';
@ -23,7 +24,7 @@ const ProfileContext = createContext<IUserProfileContext | null>(null);
export const useUserProfile = () => {
const context = useContext(ProfileContext);
if (!context) {
throw new Error('useUserProfile has to be used within <UserProfileState.Provider>');
throw new Error(contextOutsideScope('useUserProfile', 'UserProfileState'));
}
return context;
};

View File

@ -4,6 +4,7 @@ import { createContext, useCallback, useContext, useEffect, useState } from 'rea
import { getActiveUsers } from '@/app/backendAPI';
import { IUserInfo } from '@/models/user';
import { contextOutsideScope } from '@/utils/labels';
interface IUsersContext {
users: IUserInfo[];
@ -15,7 +16,7 @@ const UsersContext = createContext<IUsersContext | null>(null);
export const useUsers = (): IUsersContext => {
const context = useContext(UsersContext);
if (context === null) {
throw new Error('useUsers has to be used within <UsersState.Provider>');
throw new Error(contextOutsideScope('useUsers', 'UsersState'));
}
return context;
};

View File

@ -20,6 +20,7 @@ import { useConceptNavigation } from '@/context/NavigationContext';
import { AccessPolicy, ILibraryItem, LocationHead } from '@/models/library';
import { cloneTitle, combineLocation, validateLocation } from '@/models/libraryAPI';
import { ConstituentaID, IRSFormCloneData } from '@/models/rsform';
import { information } from '@/utils/labels';
interface DlgCloneLibraryItemProps extends Pick<ModalProps, 'hideWindow'> {
base: ILibraryItem;
@ -62,7 +63,7 @@ function DlgCloneLibraryItem({ hideWindow, base, initialLocation, selected, tota
data.items = selected;
}
cloneItem(base.id, data, newSchema => {
toast.success(`Копия создана: ${newSchema.alias}`);
toast.success(information.cloneComplete(newSchema.alias));
router.push(urls.schema(newSchema.id));
});
}
@ -100,7 +101,6 @@ function DlgCloneLibraryItem({ hideWindow, base, initialLocation, selected, tota
/>
<MiniButton
className='disabled:cursor-auto'
title={visible ? 'Библиотека: отображать' : 'Библиотека: скрывать'}
icon={<VisibilityIcon value={visible} />}
onClick={() => setVisible(prev => !prev)}

View File

@ -9,6 +9,7 @@ import RSInput from '@/components/RSInput';
import PickConstituenta from '@/components/select/PickConstituenta';
import DataTable, { IConditionalStyle } from '@/components/ui/DataTable';
import MiniButton from '@/components/ui/MiniButton';
import NoData from '@/components/ui/NoData';
import AnimateFade from '@/components/wrap/AnimateFade';
import { useConceptOptions } from '@/context/OptionsContext';
import { IConstituenta, IRSForm } from '@/models/rsform';
@ -160,7 +161,7 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
data={state.arguments}
columns={columns}
conditionalRowStyles={conditionalRowStyles}
noDataComponent={<p className={clsx('min-h-[3.6rem]', 'p-2', 'text-center')}>Аргументы отсутствуют</p>}
noDataComponent={<NoData className='min-h-[3.6rem]'>Аргументы отсутствуют</NoData>}
onRowClicked={handleSelectArgument}
/>

View File

@ -14,6 +14,7 @@ import { HelpTopic } from '@/models/miscellaneous';
import { CstType, ICstCreateData, IRSForm } from '@/models/rsform';
import { generateAlias, validateNewAlias } from '@/models/rsformAPI';
import { inferTemplatedType, substituteTemplateArgs } from '@/models/rslangAPI';
import { PARAMETER } from '@/utils/constants';
import FormCreateCst from '../DlgCreateCst/FormCreateCst';
import ArgumentsTab, { IArgumentsState } from './ArgumentsTab';
@ -144,7 +145,11 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
onSubmit={handleSubmit}
>
<Overlay position='top-0 right-[6rem]'>
<BadgeHelp topic={HelpTopic.RSL_TEMPLATES} className='max-w-[40rem]' offset={12} />
<BadgeHelp
topic={HelpTopic.RSL_TEMPLATES}
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}
offset={12}
/>
</Overlay>
<Tabs
selectedTabClassName='clr-selected'
@ -155,7 +160,7 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
<TabList className={clsx('mb-3 self-center', 'flex', 'border divide-x rounded-none')}>
<TabLabel label='Шаблон' title='Выбор шаблона выражения' className='w-[8rem]' />
<TabLabel label='Аргументы' title='Подстановка аргументов шаблона' className='w-[8rem]' />
<TabLabel label='Конституента' title='Редактирование атрибутов конституенты' className='w-[8rem]' />
<TabLabel label='Конституента' title='Редактирование конституенты' className='w-[8rem]' />
</TabList>
{templatePanel}

View File

@ -24,7 +24,7 @@ interface TemplateTabProps {
function TemplateTab({ state, partialUpdate }: TemplateTabProps) {
const { templates, retrieveTemplate } = useLibrary();
const [category, setCategory] = useState<IRSForm | undefined>(undefined);
const [templateSchema, setTemplateSchema] = useState<IRSForm | undefined>(undefined);
const [filteredData, setFilteredData] = useState<IConstituenta[]>([]);
@ -48,16 +48,16 @@ function TemplateTab({ state, partialUpdate }: TemplateTabProps) {
);
const categorySelector = useMemo((): { value: number; label: string }[] => {
if (!category) {
if (!templateSchema) {
return [];
}
return category.items
return templateSchema.items
.filter(cst => cst.cst_type === CATEGORY_CST_TYPE)
.map(cst => ({
value: cst.id,
label: cst.term_raw
}));
}, [category]);
}, [templateSchema]);
useEffect(() => {
if (templates.length > 0 && !state.templateID) {
@ -67,22 +67,22 @@ function TemplateTab({ state, partialUpdate }: TemplateTabProps) {
useEffect(() => {
if (!state.templateID) {
setCategory(undefined);
setTemplateSchema(undefined);
} else {
retrieveTemplate(state.templateID, setCategory);
retrieveTemplate(state.templateID, setTemplateSchema);
}
}, [state.templateID, retrieveTemplate]);
useEffect(() => {
if (!category) {
if (!templateSchema) {
return;
}
let data = category.items;
let data = templateSchema.items;
if (state.filterCategory) {
data = applyFilterCategory(state.filterCategory, category);
data = applyFilterCategory(state.filterCategory, templateSchema);
}
setFilteredData(data);
}, [state.filterCategory, category]);
}, [state.filterCategory, templateSchema]);
return (
<AnimateFade>
@ -93,14 +93,16 @@ function TemplateTab({ state, partialUpdate }: TemplateTabProps) {
className='flex-grow border-none'
options={categorySelector}
value={
state.filterCategory && category
state.filterCategory && templateSchema
? {
value: state.filterCategory.id,
label: state.filterCategory.term_raw
}
: null
}
onChange={data => partialUpdate({ filterCategory: data ? category?.cstByID.get(data?.value) : undefined })}
onChange={data =>
partialUpdate({ filterCategory: data ? templateSchema?.cstByID.get(data?.value) : undefined })
}
isClearable
/>
<SelectSingle

View File

@ -1,5 +1,6 @@
'use client';
import clsx from 'clsx';
import { AnimatePresence } from 'framer-motion';
import { useLayoutEffect, useMemo, useState } from 'react';
@ -12,6 +13,7 @@ import AnimateFade from '@/components/wrap/AnimateFade';
import { HelpTopic } from '@/models/miscellaneous';
import { CstType, ICstCreateData, IRSForm } from '@/models/rsform';
import { generateAlias, isBaseSet, isBasicConcept, isFunctional, validateNewAlias } from '@/models/rsformAPI';
import { PARAMETER } from '@/utils/constants';
import { labelCstType } from '@/utils/labels';
import { SelectorCstType } from '@/utils/selectors';
@ -52,7 +54,11 @@ function FormCreateCst({ schema, state, partialUpdate, setValidated }: FormCreat
value={{ value: state.cst_type, label: labelCstType(state.cst_type) }}
onChange={data => partialUpdate({ cst_type: data?.value ?? CstType.BASE })}
/>
<BadgeHelp topic={HelpTopic.CC_CONSTITUENTA} offset={16} className='max-w-[40rem] max-h-[calc(100svh-2rem)]' />
<BadgeHelp
topic={HelpTopic.CC_CONSTITUENTA}
offset={16}
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}
/>
<TextInput
id='dlg_cst_alias'
dense
@ -89,6 +95,7 @@ function FormCreateCst({ schema, state, partialUpdate, setValidated }: FormCreat
}
value={state.definition_formal}
onChange={value => partialUpdate({ definition_formal: value })}
schema={schema}
/>
</AnimateFade>
<AnimateFade key='dlg_cst_definition' hideContent={!state.definition_raw && isElementary}>

View File

@ -48,7 +48,12 @@ function DlgDeleteCst({ hideWindow, selected, schema, onDelete }: DlgDeleteCstPr
schema={schema}
prefix={prefixes.cst_dependant_list}
/>
<Checkbox label='Удалить зависимые конституенты' value={expandOut} setValue={value => setExpandOut(value)} />
<Checkbox
label='Удалить зависимые конституенты'
className='my-2'
value={expandOut}
setValue={value => setExpandOut(value)}
/>
</Modal>
);
}

View File

@ -11,6 +11,7 @@ import TabLabel from '@/components/ui/TabLabel';
import { ReferenceType } from '@/models/language';
import { HelpTopic } from '@/models/miscellaneous';
import { IRSForm } from '@/models/rsform';
import { PARAMETER } from '@/utils/constants';
import { labelReferenceType } from '@/utils/labels';
import EntityTab from './EntityTab';
@ -69,10 +70,14 @@ function DlgEditReference({ hideWindow, schema, initial, onSave }: DlgEditRefere
hideWindow={hideWindow}
canSubmit={isValid}
onSubmit={handleSubmit}
className='w-[40rem] px-6 min-h-[34rem]'
className='w-[40rem] px-6 min-h-[35rem]'
>
<Overlay position='top-0 right-[4rem]'>
<BadgeHelp topic={HelpTopic.TERM_CONTROL} className='max-w-[35rem]' offset={14} />
<Overlay position='top-0 right-0'>
<BadgeHelp
topic={HelpTopic.TERM_CONTROL}
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}
offset={14}
/>
</Overlay>
<Tabs
@ -82,15 +87,10 @@ function DlgEditReference({ hideWindow, schema, initial, onSave }: DlgEditRefere
onSelect={setActiveTab}
>
<TabList className={clsx('mb-3 self-center', 'flex', 'border divide-x rounded-none')}>
<TabLabel
title='Отсылка на термин в заданной словоформе'
label={labelReferenceType(ReferenceType.ENTITY)}
className='w-[12rem]'
/>
<TabLabel title='Отсылка на термин в заданной словоформе' label={labelReferenceType(ReferenceType.ENTITY)} />
<TabLabel
title='Установление синтаксической связи с отсылкой на термин'
label={labelReferenceType(ReferenceType.SYNTACTIC)}
className='w-[12rem]'
/>
</TabList>

View File

@ -22,7 +22,7 @@ function SelectWordForm({ selected, setSelected }: SelectWordFormProps) {
);
return (
<div className='text-sm'>
<div className='text-xs sm:text-sm'>
{DefaultWordForms.slice(0, 12).map((data, index) => (
<WordformButton
key={`${prefixes.wordform_list}${index}`}

View File

@ -17,7 +17,7 @@ function WordformButton({ text, example, grams, onSelectGrams, isSelected, ...re
tabIndex={-1}
onClick={() => onSelectGrams(grams)}
className={clsx(
'min-w-[6.15rem]',
'min-w-[4.15rem] sm:min-w-[6.15rem]',
'p-1',
'border rounded-none',
'cursor-pointer',

View File

@ -16,6 +16,8 @@ import { Grammeme, ITextRequest, IWordForm, IWordFormPlain } from '@/models/lang
import { parseGrammemes, wordFormEquals } from '@/models/languageAPI';
import { HelpTopic } from '@/models/miscellaneous';
import { IConstituenta, TermForm } from '@/models/rsform';
import { PARAMETER } from '@/utils/constants';
import { prompts } from '@/utils/labels';
import { IGrammemeOption, SelectorGrammemes, SelectorGrammemesList } from '@/utils/selectors';
import WordFormsTable from './WordFormsTable';
@ -92,7 +94,7 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
function handleGenerateLexeme() {
if (forms.length > 0) {
if (!window.confirm('Данное действие приведет к перезаписи словоформ при совпадении граммем. Продолжить?')) {
if (!window.confirm(prompts.generateWordforms)) {
return;
}
}
@ -130,7 +132,11 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
className='flex flex-col w-[40rem] px-6'
>
<Overlay position='top-[-0.2rem] left-[8rem]'>
<BadgeHelp topic={HelpTopic.TERM_CONTROL} className='max-w-[38rem]' offset={3} />
<BadgeHelp
topic={HelpTopic.TERM_CONTROL}
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}
offset={3}
/>
</Overlay>
<TextArea
@ -178,33 +184,36 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
/>
</div>
<Overlay position='top-2 left-0'>
<MiniButton
noHover
title='Внести словоформу'
icon={<IconAccept size='1.5rem' className='icon-green' />}
disabled={textProcessor.loading || !inputText || inputGrams.length == 0}
onClick={handleAddForm}
/>
<MiniButton
noHover
title='Генерировать стандартные словоформы'
icon={<IconMoveDown size='1.5rem' className='icon-primary' />}
disabled={textProcessor.loading || !inputText}
onClick={handleGenerateLexeme}
/>
</Overlay>
<div className={clsx('mt-3 mb-2', 'flex self-center items-center', 'text-sm text-center font-semibold')}>
<span>Заданные вручную словоформы [{forms.length}]</span>
<MiniButton
noHover
title='Сбросить все словоформы'
className='py-0'
icon={<IconRemove size='1.5rem' className='icon-red' />}
disabled={textProcessor.loading || forms.length === 0}
onClick={handleResetAll}
/>
<div className='flex justify-between'>
<div className='cc-icons'>
<MiniButton
noHover
title='Внести словоформу'
icon={<IconAccept size='1.5rem' className='icon-green' />}
disabled={textProcessor.loading || !inputText || inputGrams.length == 0}
onClick={handleAddForm}
/>
<MiniButton
noHover
title='Генерировать стандартные словоформы'
icon={<IconMoveDown size='1.5rem' className='icon-primary' />}
disabled={textProcessor.loading || !inputText}
onClick={handleGenerateLexeme}
/>
</div>
<div
className={clsx('mt-3 mb-2', 'w-full flex justify-center items-center', 'text-sm text-center font-semibold')}
>
<div>Заданные вручную словоформы [{forms.length}]</div>
<MiniButton
noHover
title='Сбросить все словоформы'
className='py-0'
icon={<IconRemove size='1.5rem' className='icon-red' />}
disabled={textProcessor.loading || forms.length === 0}
onClick={handleResetAll}
/>
</div>
</div>
<WordFormsTable forms={forms} setForms={setForms} onFormSelect={handleSelectForm} />

View File

@ -7,6 +7,7 @@ import { IconRemove } from '@/components/Icons';
import BadgeWordForm from '@/components/info/BadgeWordForm';
import DataTable, { createColumnHelper } from '@/components/ui/DataTable';
import MiniButton from '@/components/ui/MiniButton';
import NoData from '@/components/ui/NoData';
import { IWordForm } from '@/models/language';
interface WordFormsTableProps {
@ -78,10 +79,10 @@ function WordFormsTable({ forms, setForms, onFormSelect }: WordFormsTableProps)
columns={columns}
headPosition='0'
noDataComponent={
<span className='p-2 text-center min-h-[2rem]'>
<NoData className='min-h-[2rem]'>
<p>Список пуст</p>
<p>Добавьте словоформу</p>
</span>
</NoData>
}
onRowClicked={onFormSelect}
/>

View File

@ -27,7 +27,7 @@ function DlgGraphParams({ hideWindow, initial, onConfirm }: DlgGraphParamsProps)
header='Настройки графа термов'
onSubmit={handleSubmit}
submitText='Применить'
className='flex gap-6 px-6 py-2 w-[35rem]'
className='flex gap-6 justify-between px-6 pb-3 w-[30rem]'
>
<div className='flex flex-col gap-1'>
<h1 className='mb-2'>Преобразования</h1>
@ -45,13 +45,13 @@ function DlgGraphParams({ hideWindow, initial, onConfirm }: DlgGraphParamsProps)
/>
<Checkbox
label='Скрыть шаблоны'
title='Терм-функции и предикат-функции с параметризованными аргументами'
titleHtml='Терм-функции и предикат-функции <br/>с параметризованными аргументами'
value={params.noTemplates}
setValue={value => updateParams({ noTemplates: value })}
/>
<Checkbox
label='Транзитивная редукция'
title='Удалить связи, образующие транзитивные пути в графе'
titleHtml='Удалить связи, образующие <br/>транзитивные пути в графе'
value={params.noTransitive}
setValue={value => updateParams({ noTransitive: value })}
/>

View File

@ -12,6 +12,7 @@ import usePartialUpdate from '@/hooks/usePartialUpdate';
import { HelpTopic } from '@/models/miscellaneous';
import { CstType, ICstRenameData } from '@/models/rsform';
import { generateAlias, validateNewAlias } from '@/models/rsformAPI';
import { PARAMETER } from '@/utils/constants';
import { labelCstType } from '@/utils/labels';
import { SelectorCstType } from '@/utils/selectors';
@ -47,7 +48,7 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
hideWindow={hideWindow}
canSubmit={validated}
onSubmit={handleSubmit}
className={clsx('w-[30rem]', 'py-6 px-6 flex gap-3 justify-center items-center')}
className={clsx('w-[30rem]', 'py-6 pr-3 pl-6 flex justify-center items-center')}
>
<SelectSingle
id='dlg_cst_type'
@ -60,15 +61,20 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
}}
onChange={data => updateData({ cst_type: data?.value ?? CstType.BASE })}
/>
<BadgeHelp topic={HelpTopic.CC_CONSTITUENTA} offset={16} className='max-w-[40rem] max-h-[calc(100svh-2rem)]' />
<TextInput
id='dlg_cst_alias'
dense
label='Имя'
className='w-[7rem]'
className='w-[7rem] ml-3'
value={cstData.alias}
onChange={event => updateData({ alias: event.target.value })}
/>
<BadgeHelp
topic={HelpTopic.CC_CONSTITUENTA}
offset={16}
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}
/>
</Modal>
);
}

View File

@ -11,7 +11,7 @@ import { HelpTopic } from '@/models/miscellaneous';
import { SyntaxTree } from '@/models/rslang';
import { graphDarkT, graphLightT } from '@/styling/color';
import { colorBgSyntaxTree } from '@/styling/color';
import { resources } from '@/utils/constants';
import { PARAMETER, resources } from '@/utils/constants';
import { labelSyntaxTree } from '@/utils/labels';
interface DlgShowASTProps extends Pick<ModalProps, 'hideWindow'> {
@ -55,7 +55,7 @@ function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) {
return (
<Modal readonly hideWindow={hideWindow} className='px-6'>
<Overlay position='left-[-1rem] top-[0.25rem]'>
<BadgeHelp topic={HelpTopic.UI_FORMULA_TREE} className='max-w-[32rem]' />
<BadgeHelp topic={HelpTopic.UI_FORMULA_TREE} className={PARAMETER.TOOLTIP_WIDTH} />
</Overlay>
<div className='my-2 text-lg text-center'>
{!hoverNode ? expression : null}

View File

@ -0,0 +1,47 @@
import { FolderTree } from './FolderTree';
// TODO: test FolderNode and FolderTree exhaustively
describe('Testing Tree construction', () => {
test('empty Tree should be empty', () => {
const tree = new FolderTree();
expect(tree.roots.size).toBe(0);
});
test('constructing from paths', () => {
const tree = new FolderTree(['/S', '/S/project1/123', '/U']);
expect(tree.roots.size).toBe(2);
expect(tree.roots.get('S')?.children.size).toBe(1);
});
});
describe('Testing Tree editing', () => {
test('add invalid path', () => {
const tree = new FolderTree();
expect(() => tree.addPath('invalid')).toThrow(Error);
});
test('add valid path', () => {
const tree = new FolderTree();
const node = tree.addPath('/S/test');
expect(node.getPath()).toBe('/S/test');
expect(node.filesInside).toBe(1);
expect(node.filesTotal).toBe(1);
expect(node.parent?.getPath()).toBe('/S');
expect(node.parent?.filesInside).toBe(0);
expect(node.parent?.filesTotal).toBe(1);
});
test('incrementing counter', () => {
const tree = new FolderTree();
const node1 = tree.addPath('/S/test', 0);
expect(node1.filesInside).toBe(0);
expect(node1.filesTotal).toBe(0);
const node2 = tree.addPath('/S/test', 2);
expect(node1).toBe(node2);
expect(node2.filesInside).toBe(2);
expect(node2.filesTotal).toBe(2);
});
});

View File

@ -0,0 +1,159 @@
/**
* Module: Folder tree data structure. Does not support deletions.
*/
/**
* Represents single node of a {@link FolderTree}.
*/
export class FolderNode {
rank: number = 0;
text: string;
children: Map<string, FolderNode>;
parent: FolderNode | undefined;
filesInside: number = 0;
filesTotal: number = 0;
constructor(text: string, parent?: FolderNode) {
this.text = text;
this.parent = parent;
this.children = new Map();
if (parent) {
this.rank = parent.rank + 1;
}
}
addChild(text: string): FolderNode {
const node = new FolderNode(text, this);
this.children.set(text, node);
return node;
}
hasPredecessor(target: FolderNode): boolean {
if (this.parent === target) {
return true;
} else if (!this.parent) {
return false;
}
let node = this.parent;
while (node.parent) {
if (node.parent === target) {
return true;
}
node = node.parent;
}
return false;
}
incrementFiles(count: number = 1): void {
this.filesInside = this.filesInside + count;
this.incrementTotal(count);
}
incrementTotal(count: number = 1): void {
this.filesTotal = this.filesTotal + count;
if (this.parent) {
this.parent.incrementTotal(count);
}
}
getPath(): string {
const suffix = this.text ? `/${this.text}` : '';
if (!this.parent) {
return suffix;
} else {
return this.parent.getPath() + suffix;
}
}
}
/**
* Represents a FolderTree.
*
*/
export class FolderTree {
roots: Map<string, FolderNode> = new Map();
constructor(arr?: string[]) {
arr?.forEach(path => this.addPath(path));
}
at(path: string): FolderNode | undefined {
let parse = ChopPathHead(path);
if (!this.roots.has(parse.head)) {
return undefined;
}
let node = this.roots.get(parse.head)!;
while (parse.tail !== '') {
parse = ChopPathHead(parse.tail);
if (!node.children.has(parse.head)) {
return undefined;
}
node = node.children.get(parse.head)!;
}
return node;
}
getTree(): FolderNode[] {
const result: FolderNode[] = [];
this.roots.forEach(root => this.visitNode(root, result));
return result;
}
private visitNode(target: FolderNode, result: FolderNode[]) {
result.push(target);
[...target.children.keys()]
.sort((a, b) => a.localeCompare(b))
.forEach(key => this.visitNode(target.children.get(key)!, result));
}
addPath(path: string, filesCount: number = 1): FolderNode {
let parse = ChopPathHead(path);
if (!parse.head) {
throw Error(`Invalid path ${path}`);
}
let node = this.roots.has(parse.head) ? this.roots.get(parse.head)! : this.addNode(parse.head);
while (parse.tail !== '') {
parse = ChopPathHead(parse.tail);
if (node.children.has(parse.head)) {
node = node.children.get(parse.head)!;
} else {
node = this.addNode(parse.head, node);
}
}
node.incrementFiles(filesCount);
return node;
}
private addNode(text: string, parent?: FolderNode): FolderNode {
if (parent === undefined) {
const newNode = new FolderNode(text);
this.roots.set(text, newNode);
return newNode;
} else {
return parent.addChild(text);
}
}
}
// ========= Internals =======
function ChopPathHead(path: string) {
if (!path || path.at(0) !== '/') {
return {
head: '',
tail: ''
};
}
const slash = path.indexOf('/', 1);
if (slash === -1) {
return {
head: path.substring(1),
tail: ''
};
} else {
return {
head: path.substring(1, slash),
tail: path.substring(slash)
};
}
}

View File

@ -26,9 +26,9 @@ export enum AccessPolicy {
*/
export enum LocationHead {
USER = '/U',
LIBRARY = '/L',
COMMON = '/S',
PROJECTS = '/P'
PROJECTS = '/P',
LIBRARY = '/L'
}
/**

View File

@ -2,7 +2,7 @@
* Module: Miscellaneous frontend model types. Future targets for refactoring aimed at extracting modules.
*/
import { LocationHead } from './library';
import { LibraryItemType, LocationHead } from './library';
/**
* Represents graph dependency mode.
@ -37,11 +37,6 @@ export type FontStyle = 'controls' | 'main' | 'math' | 'math2';
export enum HelpTopic {
MAIN = 'main',
DOCS = 'documentation',
RULES = 'rules',
PRIVACY = 'privacy',
API = 'api',
INTERFACE = 'user-interface',
UI_LIBRARY = 'ui-library',
UI_RS_MENU = 'ui-rsform-menu',
@ -52,12 +47,14 @@ export enum HelpTopic {
UI_FORMULA_TREE = 'ui-formula-tree',
UI_CST_STATUS = 'ui-rsform-cst-status',
UI_CST_CLASS = 'ui-rsform-cst-class',
UI_OSS_GRAPH = 'ui-oss-graph',
CONCEPTUAL = 'concept',
CC_SYSTEM = 'rslang-rsform',
CC_CONSTITUENTA = 'rslang-cst',
CC_RELATIONS = 'rslang-relations',
CC_SYNTHESIS = 'rslang-synthesis',
CC_SYSTEM = 'concept-rsform',
CC_CONSTITUENTA = 'concept-constituenta',
CC_RELATIONS = 'concept-relations',
CC_SYNTHESIS = 'concept-synthesis',
CC_OSS = 'concept-operations-schema',
RSLANG = 'rslang',
RSL_TYPES = 'rslang-types',
@ -69,6 +66,13 @@ export enum HelpTopic {
TERM_CONTROL = 'terminology-control',
ACCESS = 'access',
VERSIONS = 'versions',
INFO = 'documentation',
INFO_RULES = 'rules',
INFO_CONTRIB = 'contributors',
INFO_PRIVACY = 'privacy',
INFO_API = 'api',
EXTEOR = 'exteor'
}
@ -78,11 +82,6 @@ export enum HelpTopic {
export const topicParent: Map<HelpTopic, HelpTopic> = new Map([
[HelpTopic.MAIN, HelpTopic.MAIN],
[HelpTopic.DOCS, HelpTopic.DOCS],
[HelpTopic.RULES, HelpTopic.DOCS],
[HelpTopic.API, HelpTopic.DOCS],
[HelpTopic.PRIVACY, HelpTopic.DOCS],
[HelpTopic.INTERFACE, HelpTopic.INTERFACE],
[HelpTopic.UI_LIBRARY, HelpTopic.INTERFACE],
[HelpTopic.UI_RS_MENU, HelpTopic.INTERFACE],
@ -93,12 +92,14 @@ export const topicParent: Map<HelpTopic, HelpTopic> = new Map([
[HelpTopic.UI_FORMULA_TREE, HelpTopic.INTERFACE],
[HelpTopic.UI_CST_STATUS, HelpTopic.INTERFACE],
[HelpTopic.UI_CST_CLASS, HelpTopic.INTERFACE],
[HelpTopic.UI_OSS_GRAPH, HelpTopic.INTERFACE],
[HelpTopic.CONCEPTUAL, HelpTopic.CONCEPTUAL],
[HelpTopic.CC_SYSTEM, HelpTopic.CONCEPTUAL],
[HelpTopic.CC_CONSTITUENTA, HelpTopic.CONCEPTUAL],
[HelpTopic.CC_RELATIONS, HelpTopic.CONCEPTUAL],
[HelpTopic.CC_SYNTHESIS, HelpTopic.CONCEPTUAL],
[HelpTopic.CC_OSS, HelpTopic.CONCEPTUAL],
[HelpTopic.RSLANG, HelpTopic.RSLANG],
[HelpTopic.RSL_TYPES, HelpTopic.RSLANG],
@ -110,13 +111,20 @@ export const topicParent: Map<HelpTopic, HelpTopic> = new Map([
[HelpTopic.TERM_CONTROL, HelpTopic.TERM_CONTROL],
[HelpTopic.ACCESS, HelpTopic.ACCESS],
[HelpTopic.VERSIONS, HelpTopic.VERSIONS],
[HelpTopic.INFO, HelpTopic.INFO],
[HelpTopic.INFO_RULES, HelpTopic.INFO],
[HelpTopic.INFO_CONTRIB, HelpTopic.INFO],
[HelpTopic.INFO_PRIVACY, HelpTopic.INFO],
[HelpTopic.INFO_API, HelpTopic.INFO],
[HelpTopic.EXTEOR, HelpTopic.EXTEOR]
]);
/**
* Topics that can be folded.
*/
export const foldableTopics = [HelpTopic.INTERFACE, HelpTopic.RSLANG, HelpTopic.CONCEPTUAL, HelpTopic.DOCS];
export const foldableTopics = [HelpTopic.INTERFACE, HelpTopic.RSLANG, HelpTopic.CONCEPTUAL, HelpTopic.INFO];
/**
* Represents {@link IConstituenta} matching mode.
@ -133,10 +141,15 @@ export enum CstMatchMode {
* Represents Library filter parameters.
*/
export interface ILibraryFilter {
type?: LibraryItemType;
query?: string;
path?: string;
head?: LocationHead;
folderMode?: boolean;
folder?: string;
isVisible?: boolean;
isOwned?: boolean;
isSubscribed?: boolean;

View File

@ -65,6 +65,3 @@ export interface IRunSynthesisResponse {
export interface IOperationSchema extends IOperationSchemaData {
subscribers: UserID[];
editors: UserID[];
//producedData: number[]; // TODO: modify this to store calculated state on load
}

View File

@ -1,7 +1,7 @@
'use client';
import clsx from 'clsx';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { toast } from 'react-toastify';
import { urls } from '@/app/urls';
@ -10,6 +10,7 @@ import { IconDownload } from '@/components/Icons';
import InfoError from '@/components/info/InfoError';
import SelectAccessPolicy from '@/components/select/SelectAccessPolicy';
import SelectItemType from '@/components/select/SelectItemType';
import SelectLocation from '@/components/select/SelectLocation';
import SelectLocationHead from '@/components/select/SelectLocationHead';
import Button from '@/components/ui/Button';
import Label from '@/components/ui/Label';
@ -25,11 +26,12 @@ import { AccessPolicy, LibraryItemType, LocationHead } from '@/models/library';
import { ILibraryCreateData } from '@/models/library';
import { combineLocation, validateLocation } from '@/models/libraryAPI';
import { EXTEOR_TRS_FILE, limits, patterns } from '@/utils/constants';
import { information } from '@/utils/labels';
function FormCreateItem() {
const router = useConceptNavigation();
const { user } = useAuth();
const { createItem, error, setError, processing } = useLibrary();
const { createItem, processingError, setProcessingError, processing, folders } = useLibrary();
const [itemType, setItemType] = useState(LibraryItemType.RSFORM);
const [title, setTitle] = useState('');
@ -49,8 +51,8 @@ function FormCreateItem() {
const inputRef = useRef<HTMLInputElement | null>(null);
useEffect(() => {
setError(undefined);
}, [title, alias, setError]);
setProcessingError(undefined);
}, [title, alias, setProcessingError]);
function handleCancel() {
if (router.canBack()) {
@ -78,7 +80,7 @@ function FormCreateItem() {
fileName: file?.name
};
createItem(data, newItem => {
toast.success('Схема успешно создана');
toast.success(information.newLibraryItem);
if (itemType == LibraryItemType.RSFORM) {
router.push(urls.schema(newItem.id));
} else {
@ -97,6 +99,11 @@ function FormCreateItem() {
}
}
const handleSelectLocation = useCallback((newValue: string) => {
setHead(newValue.substring(0, 2) as LocationHead);
setBody(newValue.length > 3 ? newValue.substring(3) : '');
}, []);
return (
<form className={clsx('cc-column', 'min-w-[30rem] max-w-[30rem] mx-auto', 'px-6 py-3')} onSubmit={handleSubmit}>
<h1>
@ -153,7 +160,6 @@ function FormCreateItem() {
<div className='ml-auto cc-icons'>
<SelectAccessPolicy value={policy} onChange={setPolicy} />
<MiniButton
className='disabled:cursor-auto'
title={visible ? 'Библиотека: отображать' : 'Библиотека: скрывать'}
icon={<VisibilityIcon value={visible} />}
onClick={() => setVisible(prev => !prev)}
@ -179,6 +185,11 @@ function FormCreateItem() {
excluded={!user?.is_staff ? [LocationHead.LIBRARY] : []}
/>
</div>
{user?.is_staff ? (
<div className='self-start mt-[-0.25rem] ml-[-1.5rem]'>
<SelectLocation folderTree={folders} value={location} onChange={handleSelectLocation} />
</div>
) : null}
<TextArea
id='dlg_cst_body'
label='Путь'
@ -193,7 +204,7 @@ function FormCreateItem() {
<SubmitButton text='Создать схему' loading={processing} className='min-w-[10rem]' disabled={!isValid} />
<Button text='Отмена' className='min-w-[10rem]' onClick={() => handleCancel()} />
</div>
{error ? <InfoError error={error} /> : null}
{processingError ? <InfoError error={processingError} /> : null}
</form>
);
}

View File

@ -1,27 +1,30 @@
import { useLayoutEffect } from 'react';
import { urls } from '@/app/urls';
import Loader from '@/components/ui/Loader';
import { useAuth } from '@/context/AuthContext';
import { useConceptNavigation } from '@/context/NavigationContext';
import { PARAMETER } from '@/utils/constants';
function HomePage() {
const router = useConceptNavigation();
const { user } = useAuth();
const { user, loading } = useAuth();
useLayoutEffect(() => {
if (!user) {
setTimeout(() => {
router.push(urls.manuals);
}, PARAMETER.refreshTimeout);
} else {
setTimeout(() => {
router.push(urls.library);
}, PARAMETER.refreshTimeout);
if (!loading) {
if (!user) {
setTimeout(() => {
router.push(urls.manuals);
}, PARAMETER.refreshTimeout);
} else {
setTimeout(() => {
router.push(urls.library);
}, PARAMETER.refreshTimeout);
}
}
}, [router, user]);
}, [router, user, loading]);
return <div />;
return <Loader />;
}
export default HomePage;

View File

@ -0,0 +1,156 @@
'use client';
import clsx from 'clsx';
import { motion } from 'framer-motion';
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { toast } from 'react-toastify';
import { IconFolder, IconFolderClosed, IconFolderEmpty, IconFolderOpened, IconFolderTree } from '@/components/Icons';
import BadgeHelp from '@/components/info/BadgeHelp';
import { CProps } from '@/components/props';
import MiniButton from '@/components/ui/MiniButton';
import { FolderNode, FolderTree } from '@/models/FolderTree';
import { HelpTopic } from '@/models/miscellaneous';
import { animateSideView } from '@/styling/animations';
import { PARAMETER, prefixes } from '@/utils/constants';
import { information, labelFolderNode } from '@/utils/labels';
interface LibraryTableProps {
folders: FolderTree;
currentFolder: string;
setFolder: React.Dispatch<React.SetStateAction<string>>;
toggleFolderMode: () => void;
}
function LibraryFolders({ folders, currentFolder, setFolder, toggleFolderMode }: LibraryTableProps) {
const activeNode = useMemo(() => folders.at(currentFolder), [folders, currentFolder]);
const items = useMemo(() => folders.getTree(), [folders]);
const [folded, setFolded] = useState<FolderNode[]>(items);
useLayoutEffect(() => {
setFolded(items.filter(item => item !== activeNode && (!activeNode || !activeNode.hasPredecessor(item))));
}, [items, activeNode]);
const onFoldItem = useCallback(
(target: FolderNode, showChildren: boolean) => {
setFolded(prev =>
items.filter(item => {
if (item === target) {
return !showChildren;
}
if (!showChildren && item.hasPredecessor(target)) {
return true;
} else {
return prev.includes(item);
}
})
);
},
[items]
);
const handleClickFolder = useCallback(
(event: CProps.EventMouse, target: FolderNode) => {
event.preventDefault();
event.stopPropagation();
if (event.ctrlKey || event.metaKey) {
navigator.clipboard
.writeText(target.getPath())
.then(() => toast.success(information.pathReady))
.catch(console.error);
} else {
setFolder(target.getPath());
}
},
[setFolder]
);
const handleClickFold = useCallback(
(event: CProps.EventMouse, target: FolderNode, showChildren: boolean) => {
event.preventDefault();
event.stopPropagation();
onFoldItem(target, showChildren);
},
[onFoldItem]
);
return (
<motion.div
className='flex flex-col select-none text:xs sm:text-sm'
initial={{ ...animateSideView.initial }}
animate={{ ...animateSideView.animate }}
exit={{ ...animateSideView.exit }}
>
<div className='h-[2.08rem] flex justify-between items-center pr-1'>
<BadgeHelp
topic={HelpTopic.UI_LIBRARY}
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'text-sm')}
offset={5}
place='right-start'
/>
<MiniButton
icon={<IconFolderTree size='1.25rem' className='icon-green' />}
title='Переключение в режим Поиск'
onClick={toggleFolderMode}
/>
</div>
<div
className={clsx(
'max-w-[10rem] sm:max-w-[15rem] min-w-[10rem] sm:min-w-[15rem]',
'flex flex-col',
'cc-scroll-y'
)}
>
{items.map((item, index) =>
!item.parent || !folded.includes(item.parent) ? (
<div
tabIndex={-1}
key={`${prefixes.folders_list}${index}`}
className={clsx(
'min-h-[2.0825rem] sm:min-h-[2.3125rem]',
'pr-3 flex items-center gap-2',
'cc-scroll-row',
'clr-hover',
'cursor-pointer',
activeNode === item && 'clr-selected'
)}
style={{ paddingLeft: `${(item.rank > 5 ? 5 : item.rank) * 0.5 + 0.5}rem` }}
onClick={event => handleClickFolder(event, item)}
>
{item.children.size > 0 ? (
<MiniButton
noPadding
noHover
icon={
folded.includes(item) ? (
item.filesInside ? (
<IconFolderClosed size='1rem' className='icon-primary' />
) : (
<IconFolderEmpty size='1rem' className='icon-primary' />
)
) : (
<IconFolderOpened size='1rem' className='icon-green' />
)
}
onClick={event => handleClickFold(event, item, folded.includes(item))}
/>
) : (
<div>
{item.filesInside ? (
<IconFolder size='1rem' className='clr-text-default' />
) : (
<IconFolderEmpty size='1rem' className='clr-text-controls' />
)}
</div>
)}
<div className='self-center'>{labelFolderNode(item)}</div>
</div>
) : null
)}
</div>
</motion.div>
);
}
export default LibraryFolders;

View File

@ -1,8 +1,10 @@
'use client';
import { AnimatePresence } from 'framer-motion';
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import DataLoader from '@/components/wrap/DataLoader';
import { useAuth } from '@/context/AuthContext';
import { useLibrary } from '@/context/LibraryContext';
import useLocalStorage from '@/hooks/useLocalStorage';
import { ILibraryItem, LocationHead } from '@/models/library';
@ -10,17 +12,21 @@ import { ILibraryFilter } from '@/models/miscellaneous';
import { storage } from '@/utils/constants';
import { toggleTristateFlag } from '@/utils/utils';
import LibraryFolders from './LibraryFolders';
import LibraryTable from './LibraryTable';
import SearchPanel from './SearchPanel';
function LibraryPage() {
const library = useLibrary();
const { user } = useAuth();
const [items, setItems] = useState<ILibraryItem[]>([]);
const [query, setQuery] = useState('');
const [path, setPath] = useState('');
const [head, setHead] = useLocalStorage<LocationHead | undefined>(storage.librarySearchHead, undefined);
const [folderMode, setFolderMode] = useLocalStorage<boolean>(storage.librarySearchFolderMode, true);
const [folder, setFolder] = useLocalStorage<string>(storage.librarySearchFolder, '');
const [isVisible, setIsVisible] = useLocalStorage<boolean | undefined>(storage.librarySearchVisible, true);
const [isSubscribed, setIsSubscribed] = useLocalStorage<boolean | undefined>(
storage.librarySearchSubscribed,
@ -34,12 +40,27 @@ function LibraryPage() {
head: head,
path: path,
query: query,
isEditor: isEditor,
isOwned: isOwned,
isSubscribed: isSubscribed,
isVisible: isVisible
isEditor: user ? isEditor : undefined,
isOwned: user ? isOwned : undefined,
isSubscribed: user ? isSubscribed : undefined,
isVisible: user ? isVisible : true,
folderMode: folderMode,
folder: folder
}),
[head, path, query, isEditor, isOwned, isSubscribed, isVisible]
[head, path, query, isEditor, isOwned, isSubscribed, isVisible, user, folderMode, folder]
);
const hasCustomFilter = useMemo(
() =>
!!filter.path ||
!!filter.query ||
filter.head !== undefined ||
filter.isEditor !== undefined ||
filter.isOwned !== undefined ||
filter.isSubscribed !== undefined ||
filter.isVisible !== true ||
!!filter.folder,
[filter]
);
useLayoutEffect(() => {
@ -50,6 +71,7 @@ function LibraryPage() {
const toggleOwned = useCallback(() => setIsOwned(prev => toggleTristateFlag(prev)), [setIsOwned]);
const toggleSubscribed = useCallback(() => setIsSubscribed(prev => toggleTristateFlag(prev)), [setIsSubscribed]);
const toggleEditor = useCallback(() => setIsEditor(prev => toggleTristateFlag(prev)), [setIsEditor]);
const toggleFolderMode = useCallback(() => setFolderMode(prev => !prev), [setFolderMode]);
const resetFilter = useCallback(() => {
setQuery('');
@ -59,34 +81,38 @@ function LibraryPage() {
setIsSubscribed(undefined);
setIsOwned(undefined);
setIsEditor(undefined);
}, [setHead, setIsVisible, setIsSubscribed, setIsOwned, setIsEditor]);
setFolder('');
}, [setHead, setIsVisible, setIsSubscribed, setIsOwned, setIsEditor, setFolder]);
const view = useMemo(
() => (
<LibraryTable
resetQuery={resetFilter} // prettier: split lines
items={items}
folderMode={folderMode}
toggleFolderMode={toggleFolderMode}
/>
),
[resetFilter, items]
[resetFilter, items, folderMode, toggleFolderMode]
);
return (
<DataLoader
id='library-page' // prettier: split lines
isLoading={library.loading}
error={library.error}
error={library.loadingError}
hasNoData={library.items.length === 0}
>
<SearchPanel
total={library.items.length ?? 0}
filtered={items.length}
hasCustomFilter={hasCustomFilter}
query={query}
setQuery={setQuery}
path={path}
setPath={setPath}
head={head}
setHead={setHead}
total={library.items.length ?? 0}
filtered={items.length}
isVisible={isVisible}
isOwned={isOwned}
toggleOwned={toggleOwned}
@ -95,8 +121,24 @@ function LibraryPage() {
toggleSubscribed={toggleSubscribed}
isEditor={isEditor}
toggleEditor={toggleEditor}
resetFilter={resetFilter}
folderMode={folderMode}
toggleFolderMode={toggleFolderMode}
/>
{view}
<div className='flex'>
<AnimatePresence initial={false}>
{folderMode ? (
<LibraryFolders
currentFolder={folder} // prettier: split-lines
setFolder={setFolder}
folders={library.folders}
toggleFolderMode={toggleFolderMode}
/>
) : null}
</AnimatePresence>
{view}
</div>
</DataLoader>
);
}

View File

@ -1,14 +1,16 @@
'use client';
import { useLayoutEffect, useMemo, useState } from 'react';
import clsx from 'clsx';
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { useIntl } from 'react-intl';
import { urls } from '@/app/urls';
import { IconFolder } from '@/components/Icons';
import { IconFolderTree } from '@/components/Icons';
import BadgeLocation from '@/components/info/BadgeLocation';
import { CProps } from '@/components/props';
import DataTable, { createColumnHelper, IConditionalStyle, VisibilityState } from '@/components/ui/DataTable';
import FlexColumn from '@/components/ui/FlexColumn';
import MiniButton from '@/components/ui/MiniButton';
import TextURL from '@/components/ui/TextURL';
import { useConceptNavigation } from '@/context/NavigationContext';
import { useConceptOptions } from '@/context/OptionsContext';
@ -21,11 +23,13 @@ import { storage } from '@/utils/constants';
interface LibraryTableProps {
items: ILibraryItem[];
resetQuery: () => void;
folderMode: boolean;
toggleFolderMode: () => void;
}
const columnHelper = createColumnHelper<ILibraryItem>();
function LibraryTable({ items, resetQuery }: LibraryTableProps) {
function LibraryTable({ items, resetQuery, folderMode, toggleFolderMode }: LibraryTableProps) {
const router = useConceptNavigation();
const intl = useIntl();
const { getUserLabel } = useUsers();
@ -50,22 +54,40 @@ function LibraryTable({ items, resetQuery }: LibraryTableProps) {
});
}, [windowSize]);
const handleToggleFolder = useCallback(
(event: CProps.EventMouse) => {
event.preventDefault();
event.stopPropagation();
toggleFolderMode();
},
[toggleFolderMode]
);
const columns = useMemo(
() => [
columnHelper.accessor('location', {
id: 'location',
header: () => (
<div className='pl-2 max-h-[1rem] translate-y-[-0.125rem]'>
<IconFolder size='1.25rem' className='clr-text-controls' />
</div>
),
size: 50,
minSize: 50,
maxSize: 50,
enableSorting: true,
cell: props => <BadgeLocation location={props.getValue()} />,
sortingFn: 'text'
}),
...(folderMode
? []
: [
columnHelper.accessor('location', {
id: 'location',
header: () => (
<MiniButton
noPadding
noHover
className='pl-2 max-h-[1rem] translate-y-[-0.125rem]'
onClick={handleToggleFolder}
titleHtml='Переключение в режим Проводник'
icon={<IconFolderTree size='1.25rem' className='clr-text-controls' />}
/>
),
size: 50,
minSize: 50,
maxSize: 50,
enableSorting: true,
cell: props => <BadgeLocation location={props.getValue()} />,
sortingFn: 'text'
})
]),
columnHelper.accessor('alias', {
id: 'alias',
header: 'Шифр',
@ -116,7 +138,7 @@ function LibraryTable({ items, resetQuery }: LibraryTableProps) {
sortDescFirst: true
})
],
[intl, getUserLabel, windowSize]
[intl, getUserLabel, windowSize, handleToggleFolder, folderMode]
);
const tableHeight = useMemo(() => calculateHeight('2.2rem'), [calculateHeight]);
@ -139,10 +161,10 @@ function LibraryTable({ items, resetQuery }: LibraryTableProps) {
columns={columns}
data={items}
headPosition='0'
className='text-xs sm:text-sm cc-scroll-y'
className={clsx('text-xs sm:text-sm cc-scroll-y', { 'border-l border-b': folderMode })}
style={{ maxHeight: tableHeight }}
noDataComponent={
<FlexColumn className='p-3 items-center min-h-[6rem]'>
<FlexColumn className='dense p-3 items-center min-h-[6rem]'>
<p>Список схем пуст</p>
<p className='flex gap-6'>
<TextURL text='Создать схему' href='/library/create' />

View File

@ -4,16 +4,16 @@ import clsx from 'clsx';
import { useCallback } from 'react';
import { LocationIcon, SubscribeIcon, VisibilityIcon } from '@/components/DomainIcons';
import { IconEditor, IconFolder, IconOwner } from '@/components/Icons';
import BadgeHelp from '@/components/info/BadgeHelp';
import { IconEditor, IconFilterReset, IconFolder, IconFolderTree, IconOwner } from '@/components/Icons';
import { CProps } from '@/components/props';
import Dropdown from '@/components/ui/Dropdown';
import DropdownButton from '@/components/ui/DropdownButton';
import MiniButton from '@/components/ui/MiniButton';
import SearchBar from '@/components/ui/SearchBar';
import SelectorButton from '@/components/ui/SelectorButton';
import { useAuth } from '@/context/AuthContext';
import useDropdown from '@/hooks/useDropdown';
import { LocationHead } from '@/models/library';
import { HelpTopic } from '@/models/miscellaneous';
import { prefixes } from '@/utils/constants';
import { describeLocationHead, labelLocationHead } from '@/utils/labels';
import { tripleToggleColor } from '@/utils/utils';
@ -21,6 +21,7 @@ import { tripleToggleColor } from '@/utils/utils';
interface SearchPanelProps {
total: number;
filtered: number;
hasCustomFilter: boolean;
query: string;
setQuery: React.Dispatch<React.SetStateAction<string>>;
@ -29,6 +30,9 @@ interface SearchPanelProps {
head: LocationHead | undefined;
setHead: React.Dispatch<React.SetStateAction<LocationHead | undefined>>;
folderMode: boolean;
toggleFolderMode: () => void;
isVisible: boolean | undefined;
toggleVisible: () => void;
isOwned: boolean | undefined;
@ -37,11 +41,14 @@ interface SearchPanelProps {
toggleSubscribed: () => void;
isEditor: boolean | undefined;
toggleEditor: () => void;
resetFilter: () => void;
}
function SearchPanel({
total,
filtered,
hasCustomFilter,
query,
setQuery,
path,
@ -49,6 +56,9 @@ function SearchPanel({
head,
setHead,
folderMode,
toggleFolderMode,
isVisible,
toggleVisible,
isOwned,
@ -56,8 +66,10 @@ function SearchPanel({
isSubscribed,
toggleSubscribed,
isEditor,
toggleEditor
toggleEditor,
resetFilter
}: SearchPanelProps) {
const { user } = useAuth();
const headMenu = useDropdown();
const handleChange = useCallback(
@ -68,6 +80,22 @@ function SearchPanel({
[headMenu, setHead]
);
const handleToggleFolder = useCallback(() => {
headMenu.hide();
toggleFolderMode();
}, [headMenu, toggleFolderMode]);
const handleFolderClick = useCallback(
(event: CProps.EventMouse) => {
if (event.ctrlKey || event.metaKey) {
toggleFolderMode();
} else {
headMenu.toggle();
}
},
[headMenu, toggleFolderMode]
);
return (
<div
className={clsx(
@ -79,33 +107,43 @@ function SearchPanel({
'clr-input'
)}
>
<div className={clsx('px-3 self-center', 'min-w-[5.5rem]', 'select-none', 'whitespace-nowrap')}>
<div className={clsx('px-3 pt-1 self-center', 'min-w-[5.5rem]', 'select-none', 'whitespace-nowrap')}>
{filtered} из {total}
</div>
<div className='cc-icons'>
<MiniButton
title='Видимость'
icon={<VisibilityIcon value={true} className={tripleToggleColor(isVisible)} />}
onClick={toggleVisible}
/>
<MiniButton
title='Я - Подписчик'
icon={<SubscribeIcon value={true} className={tripleToggleColor(isSubscribed)} />}
onClick={toggleSubscribed}
/>
<MiniButton
title='Я - Владелец'
icon={<IconOwner size='1.25rem' className={tripleToggleColor(isOwned)} />}
onClick={toggleOwned}
/>
{user ? (
<div className='cc-icons'>
<MiniButton
title='Видимость'
icon={<VisibilityIcon value={true} className={tripleToggleColor(isVisible)} />}
onClick={toggleVisible}
/>
<MiniButton
title='Я - Подписчик'
icon={<SubscribeIcon value={true} className={tripleToggleColor(isSubscribed)} />}
onClick={toggleSubscribed}
/>
<MiniButton
title='Я - Редактор'
icon={<IconEditor size='1.25rem' className={tripleToggleColor(isEditor)} />}
onClick={toggleEditor}
/>
</div>
<MiniButton
title='Я - Владелец'
icon={<IconOwner size='1.25rem' className={tripleToggleColor(isOwned)} />}
onClick={toggleOwned}
/>
<MiniButton
title='Я - Редактор'
icon={<IconEditor size='1.25rem' className={tripleToggleColor(isEditor)} />}
onClick={toggleEditor}
/>
<MiniButton
title='Сбросить фильтры'
icon={<IconFilterReset size='1.25rem' className='icon-primary' />}
onClick={resetFilter}
disabled={!hasCustomFilter}
/>
</div>
) : null}
<div className='flex items-center h-full mx-auto'>
<SearchBar
@ -116,61 +154,67 @@ function SearchPanel({
value={query}
onChange={setQuery}
/>
{!folderMode ? (
<div ref={headMenu.ref} className='flex items-center h-full py-1 select-none'>
<SelectorButton
transparent
className='h-full rounded-lg'
titleHtml={(head ? describeLocationHead(head) : 'Выберите каталог') + '<br/>Ctrl + клик - Проводник'}
hideTitle={headMenu.isOpen}
icon={
head ? (
<LocationIcon value={head} size='1.25rem' />
) : (
<IconFolder size='1.25rem' className='clr-text-controls' />
)
}
onClick={handleFolderClick}
text={head ?? '//'}
/>
<div ref={headMenu.ref} className='flex items-center h-full py-1 select-none'>
<SelectorButton
transparent
className='h-full rounded-lg'
title={head ? describeLocationHead(head) : 'Выберите каталог'}
hideTitle={headMenu.isOpen}
icon={
head ? (
<LocationIcon value={head} size='1.25rem' />
) : (
<IconFolder size='1.25rem' className='clr-text-controls' />
)
}
onClick={headMenu.toggle}
text={head ?? '//'}
<Dropdown isOpen={headMenu.isOpen} stretchLeft className='z-modalTooltip'>
<DropdownButton className='w-[10rem]' title='Переключение в режим Проводник' onClick={handleToggleFolder}>
<div className='inline-flex items-center gap-3'>
<IconFolderTree size='1rem' className='clr-text-controls' />
<span>проводник...</span>
</div>
</DropdownButton>
<DropdownButton className='w-[10rem]' onClick={() => handleChange(undefined)}>
<div className='inline-flex items-center gap-3'>
<IconFolder size='1rem' className='clr-text-controls' />
<span>отображать все</span>
</div>
</DropdownButton>
{Object.values(LocationHead).map((head, index) => {
return (
<DropdownButton
className='w-[10rem]'
key={`${prefixes.location_head_list}${index}`}
onClick={() => handleChange(head)}
title={describeLocationHead(head)}
>
<div className='inline-flex items-center gap-3'>
<LocationIcon value={head} size='1rem' />
{labelLocationHead(head)}
</div>
</DropdownButton>
);
})}
</Dropdown>
</div>
) : null}
{!folderMode ? (
<SearchBar
id='path_search'
placeholder='Путь'
noIcon
noBorder
className='min-w-[4.5rem] sm:min-w-[5rem]'
value={path}
onChange={setPath}
/>
<Dropdown isOpen={headMenu.isOpen} stretchLeft className='z-modalTooltip'>
<DropdownButton className='w-[10rem]' onClick={() => handleChange(undefined)}>
<div className='inline-flex items-center gap-3'>
<IconFolder size='1rem' className='clr-text-controls' />
<span>отображать все</span>
</div>
</DropdownButton>
{Object.values(LocationHead).map((head, index) => {
return (
<DropdownButton
className='w-[10rem]'
key={`${prefixes.location_head_list}${index}`}
onClick={() => handleChange(head)}
title={describeLocationHead(head)}
>
<div className='inline-flex items-center gap-3'>
<LocationIcon value={head} size='1rem' />
{labelLocationHead(head)}
</div>
</DropdownButton>
);
})}
</Dropdown>
</div>
<SearchBar
id='path_search'
placeholder='Путь'
noIcon
noBorder
className='min-w-[5rem]'
value={path}
onChange={setPath}
/>
) : null}
</div>
<BadgeHelp topic={HelpTopic.UI_LIBRARY} className='max-w-[28rem] text-sm' offset={5} place='right-start' />
</div>
);
}

View File

@ -1,5 +1,5 @@
import { HelpTopic } from '@/models/miscellaneous';
import { describeHelpTopic, labelHelpTopic } from '@/utils/labels';
import { describeHelpTopic, labelHelpTopic, removeTags } from '@/utils/labels';
import LinkTopic from '../../components/ui/LinkTopic';
@ -10,7 +10,7 @@ interface TopicItemProps {
function TopicItem({ topic }: TopicItemProps) {
return (
<li>
<LinkTopic text={labelHelpTopic(topic)} topic={topic} /> {describeHelpTopic(topic)}
<LinkTopic text={labelHelpTopic(topic)} topic={topic} /> {removeTags(describeHelpTopic(topic))}
</li>
);
}

View File

@ -1,20 +1,24 @@
import useWindowSize from '@/hooks/useWindowSize';
import { HelpTopic } from '@/models/miscellaneous';
import HelpAccess from './items/HelpAccess';
import HelpAPI from './items/HelpAPI';
import HelpConcept from './items/HelpConcept';
import HelpConceptOSS from './items/HelpConceptOSS';
import HelpConceptRelations from './items/HelpConceptRelations';
import HelpConceptSynthesis from './items/HelpConceptSynthesis';
import HelpConceptSystem from './items/HelpConceptSystem';
import HelpContributors from './items/HelpContributors';
import HelpCstAttributes from './items/HelpCstAttributes';
import HelpCstClass from './items/HelpCstClass';
import HelpCstEditor from './items/HelpCstEditor';
import HelpCstStatus from './items/HelpCstStatus';
import HelpDocs from './items/HelpDocs';
import HelpExteor from './items/HelpExteor';
import HelpFormulaTree from './items/HelpFormulaTree';
import HelpInfo from './items/HelpInfo';
import HelpInterface from './items/HelpInterface';
import HelpLibrary from './items/HelpLibrary';
import HelpOssGraph from './items/HelpOssGraph';
import HelpPortal from './items/HelpPortal';
import HelpPrivacy from './items/HelpPrivacy';
import HelpRSFormCard from './items/HelpRSFormCard';
@ -31,17 +35,21 @@ import HelpTermGraph from './items/HelpTermGraph';
import HelpTerminologyControl from './items/HelpTerminologyControl';
import HelpVersions from './items/HelpVersions';
// PDF Viewer setup
const OFFSET_X_SMALL = 32;
const OFFSET_X_LARGE = 280;
const MIN_SIZE_SMALL = 300;
const MIN_SIZE_LARGE = 600;
interface TopicPageProps {
topic: HelpTopic;
}
function TopicPage({ topic }: TopicPageProps) {
if (topic === HelpTopic.MAIN) return <HelpPortal />;
const size = useWindowSize();
if (topic === HelpTopic.DOCS) return <HelpDocs />;
if (topic === HelpTopic.RULES) return <HelpRules />;
if (topic === HelpTopic.PRIVACY) return <HelpPrivacy />;
if (topic === HelpTopic.API) return <HelpAPI />;
if (topic === HelpTopic.MAIN) return <HelpPortal />;
if (topic === HelpTopic.INTERFACE) return <HelpInterface />;
if (topic === HelpTopic.UI_LIBRARY) return <HelpLibrary />;
@ -53,12 +61,14 @@ function TopicPage({ topic }: TopicPageProps) {
if (topic === HelpTopic.UI_FORMULA_TREE) return <HelpFormulaTree />;
if (topic === HelpTopic.UI_CST_STATUS) return <HelpCstStatus />;
if (topic === HelpTopic.UI_CST_CLASS) return <HelpCstClass />;
if (topic === HelpTopic.UI_OSS_GRAPH) return <HelpOssGraph />;
if (topic === HelpTopic.CONCEPTUAL) return <HelpConcept />;
if (topic === HelpTopic.CC_SYSTEM) return <HelpConceptSystem />;
if (topic === HelpTopic.CC_CONSTITUENTA) return <HelpCstAttributes />;
if (topic === HelpTopic.CC_RELATIONS) return <HelpConceptRelations />;
if (topic === HelpTopic.CC_SYNTHESIS) return <HelpConceptSynthesis />;
if (topic === HelpTopic.CC_OSS) return <HelpConceptOSS />;
if (topic === HelpTopic.RSLANG) return <HelpRSLang />;
if (topic === HelpTopic.RSL_TYPES) return <HelpRSLangTypes />;
@ -70,6 +80,19 @@ function TopicPage({ topic }: TopicPageProps) {
if (topic === HelpTopic.TERM_CONTROL) return <HelpTerminologyControl />;
if (topic === HelpTopic.ACCESS) return <HelpAccess />;
if (topic === HelpTopic.VERSIONS) return <HelpVersions />;
if (topic === HelpTopic.INFO) return <HelpInfo />;
if (topic === HelpTopic.INFO_RULES) return <HelpRules />;
if (topic === HelpTopic.INFO_CONTRIB) return <HelpContributors />;
if (topic === HelpTopic.INFO_PRIVACY)
return (
<HelpPrivacy
offsetXpx={size.isSmall ? OFFSET_X_SMALL : OFFSET_X_LARGE}
minWidth={size.isSmall ? MIN_SIZE_SMALL : MIN_SIZE_LARGE}
/>
);
if (topic === HelpTopic.INFO_API) return <HelpAPI />;
if (topic === HelpTopic.EXTEOR) return <HelpExteor />;
return null;
}

View File

@ -8,7 +8,7 @@ interface ViewTopicProps {
function ViewTopic({ topic }: ViewTopicProps) {
return (
<AnimateFade key={topic} className='py-2 pl-6 pr-3 mx-auto'>
<AnimateFade key={topic} className='px-3 py-2 mx-auto'>
<TopicPage topic={topic} />
</AnimateFade>
);

View File

@ -11,7 +11,7 @@ function HelpAPI() {
С описанием интерфейса можно ознакомиться <TextURL text='по ссылке' href={external_urls.restAPI} />.
</p>
<p>
<TextURL text='Принять участие в разработке' href={external_urls.git_repo} />
<TextURL text='Принять участие в разработке' href={external_urls.git_portal} />
</p>
</div>
);

View File

@ -1,8 +1,31 @@
import { IconHide, IconImmutable, IconPrivate, IconProtected, IconPublic } from '@/components/Icons';
function HelpAccess() {
return (
<div>
<h1>Организация доступов</h1>
<p>TBD.</p>
<p>Редактирование контента осуществляется Редакторами и Владельцом.</p>
<p>Редактирование прав и доступов осуществляется Владельцом.</p>
<p>
Доступ к контенту на Портале может быть ограничен владельцем каждой схемы в рамках <b>политики доступа</b>.
</p>
<li>
<IconPublic className='inline-icon icon-green' /> публичная политика не ограничивает чтение схемы
</li>
<li>
<IconProtected className='inline-icon icon-blue' /> защитная политика запрещает доступ для всех кроме редакторов
и владельца схемы
</li>
<li>
<IconPrivate className='inline-icon icon-red' /> личная политика оставляет доступ к схеме только владельцу
</li>
<li>
<IconHide className='inline-icon' /> режим скрытия схемы из списка в Библиотеке не ограничивает доступ к схеме
по прямой ссылке
</li>
<li>
<IconImmutable className='inline-icon' /> режим защиты от редактирования предохраняет от случайных изменений
</li>
</div>
);
}

View File

@ -0,0 +1,27 @@
import LinkTopic from '@/components/ui/LinkTopic';
import { HelpTopic } from '@/models/miscellaneous';
function HelpConceptOSS() {
return (
<div>
<h1>Операционная схема синтеза</h1>
<p>
Работа со сложными предметными областями требует многократного{' '}
<LinkTopic text='синтеза' topic={HelpTopic.CC_SYNTHESIS} /> для построения целевых понятий. Последовательность
синтезов концептуальных схем задается с помощью <b>Операционной схемы синтеза (ОСС)</b> в форме Графа синтеза.
</p>
<p>
Отдельные операции в рамках ОСС задаются <b>таблицами отождествлений</b> понятий из синтезируемых схем. Таким
образом <LinkTopic text='конституенты' topic={HelpTopic.CC_CONSTITUENTA} /> в каждой КС разделяются на
наследованные, отождествленные и дописанные.
</p>
<p>
Портал поддерживает <b>сквозные изменения</b> в рамках ОСС. Изменения, внесенные в исходные концептуальные схемы
автоматически проносятся через граф синтеза (путем обновления наследованных конституент). Формальные определения
наследованных конституент можно редактировать только путем изменения исходных конституент.
</p>
</div>
);
}
export default HelpConceptOSS;

View File

@ -0,0 +1,285 @@
import LinkTopic from '@/components/ui/LinkTopic';
import TextURL from '@/components/ui/TextURL';
import { HelpTopic } from '@/models/miscellaneous';
import { external_urls } from '@/utils/constants';
function HelpInfo() {
return (
<div>
<h1>Благодарности разработчикам и исследователям</h1>
<p>
История инструментов работы с концептуальными схемами начинается с 1970-х годов и продолжается в настоящее
время. Здесь представлена скромная попытка перечислить вклад различных людей в развитие инструментов и
математического аппарата, лежащего в основе экспликации концептуальных схем.
</p>
<p>
В списке указан год окончания работ над соответствующим результатом или год публикации соответствующей статьи.
Курсивом выделены комментарии к значимости указанного результата.
</p>
<p>Любые добавления и поправки приветствуются.</p>
<div className='flex flex-col gap-3'>
<li>1973 Никаноров С.П., Персиц Д.Б. Формальное проектирование целостных СОУ.</li>
<li>
19751981 Никаноров С.П., Персиц Д.Б., Айзенштат А.В., Закс Б.А. Экспериментальная система пакетов прикладных
программ автоматизированного проектирования систем организационного управления (АСП СОУ).
</li>
<li>1976 Поспелов Д.А., Чернышев С.Б. Метод построения формально-логической модели большой размерности.</li>
<li>
1977 Персиц Д.Б., Савелов Е.В., Тищенко А.В. Теоретические основы АСП СОУ,{' '}
<i>
сформировавшие базу для развития формализации предметных областей с помощью экспликации концептуальных схем.
</i>
</li>
<li>
1980 Никаноров С.П., Персиц Д.Б., Егоров Б.Б., Никитина Н.К., Ашихмин В.С., Астрина И.В., Тищенко А.В. Блок
документирования в АСП СОУ.
</li>
<li>
1986 Никаноров С.П., Кучкаров З.А., Никитина Н.К., Крюков И.А., Комаров В.Г. Система автоматизированного
проектирования концептуального уровня баз данных (МАКС),{' '}
<i>
заложивший основу для хранения концептуальных схем в базах данных и ставший первым редактором
родоструктурной экспликации в группе Никитиной.
</i>
</li>
<li>
1987 Иванов А.Ю., Кучкаров З.А. Разработка концептуальных и математических средств описания процессов принятия
решений.
</li>
<li>
1989 Кучкаров З.А., Остапов А.В. Методические вопросы концептуализации предметных областей,{' '}
<i>
как пример одной из работ Остапова, значительно расширившего технику экспликации и практику применения
"бескванторных" выражений.
</i>
</li>
<li>
1990 Никитина Н.К., Постников В.В. Синтаксический анализатор текста рода структуры для МАКС,{' '}
<i>являющийся первой попыткой реализовать автоматизированную проверку синтаксиса родов структур.</i>
</li>
<li>
1993 Костюк А.В., Никитина Н.К., Юдкин Ю.Ю. Программа визуализации М-графов, представляющих родовую структуру.
</li>
<li>1993 Никитина Н.К., Чувашов Е.В. Система проектирования баз данных по их концептуальной модели.</li>
<li>
1993 Никаноров С.П., Кучкаров З.А., Остапов А.В., Шульпекин А.Н., Коваль А.Г., Костюк А.В. Программа
операционализации текстов концептуальных моделей, эксплицированных в аппарате родов структур Экстеор 1,{' '}
<i>ставший первым редактором родоструктурной экспликации в группе Кучкарова.</i>
</li>
<li>
1993 Кучкаров З.А., Лавров В., Крайнев А., Шульпекин А.Н., Симонов М. Автоматический генератор
PROLOG-программ, формирующих предметные интерпретации родоструктурных экспликаций Инттеор.
</li>
<li>
1994 Кучкаров З.А., Ким В.Л. Разработка родоструктурных конструктов для библиотеки моделей и исследование
возможностей их развития.
</li>
<li>
1996 Коваль А.Г., Кучкаров З.А., Костюк А.В., Кононенко А.А., Син Ю.Е., Маклаков Ю.И. Программа
родоструктурного синтеза операционализированных терминальных концептуальных моделей Экстеор 2,{' '}
<i>ставшая первой версией реализации родоструктурного аппарата на C++ под Windows.</i>
</li>
<li>
1996 Никаноров С.П., Никитина Н.К., Климишин В.В. Автоматизированная система "Библиотека концептуальных схем",{' '}
<i>впервые определившая паспорт концептуальной схемы.</i>
</li>
<li>
1997 Никитина Н.К., Юрьев О.И. Система поддержки процессов концептуального анализа и проектирования ПРОКСИМА
1.
</li>
<li>
1998 Никитина Н.К., Гараева Ю.Р. Синтаксический анализатор выражений на языке родоструктурной экспликации для
ПРОКСИМА 1.
</li>
<li>
1998 Син Ю.Е. Разработка и исследование класса теоретико-модельных операций для технологической линии
концептуального проектирования.
</li>
<li>
1999 Кучкаров З.А., Кононенко А.А. Программа преобразования родоструктурного синтеза операционализированных
терминальных концептуальных моделей Экстеор 3,{' '}
<i>впервые включившая операционную схему синтеза (дерево синтеза).</i>
</li>
<li>
1999 Никитина Н.К., Ландин Н.А. Разработка автоматизированной подсистемы, реализующей операции отслоения и
рассечения над концептуальными схемами.
</li>
<li>
1999 Юрьев О.И., Зверев В.Ю. Разработка и создание экспериментальной версии автоматизированной системы
"Библиотека проектов систем организационного управления".
</li>
<li>
2000 Кучкаров З.А., Кононенко А.А., Син Ю.Е. Программа генерации концептуально определенной предметно
интерпретированной сети организационных процедур Оргтеор,{' '}
<i>
позволившая строить процессные схемы по графу термов концептуальной схемы и впервые включившая модуль
терминологических преобразований текстовых описаний.
</i>
</li>
<li>
2000 Кононенко А.А., Майоров В.А. Программа автоматизированной генерации структуры данных и их визуализации по
концептуальной модели БДтеор,{' '}
<i>
определившая проблемы интерфейса наполнения концептуальной модели в сложных ступенях и предложившая
библиотеку Kernel для удержания интерпретации с помощью М-графа.
</i>
</li>
<li>2000 Тищенко А.В. Шкалы множеств и родов структур.</li>
<li>
2000 Тищенко А.В., Акименков А.М., Ключников А.В. Система операций над концептуальными схемами,
представленными в родоструктурной форме.
</li>
<li>2000 Ключников А.В. Эквивалентность теорий родов структур.</li>
<li>
2001 Кучкаров З.А., Никитин А.В. Исследование и построение типологии изменений теоретико-множественных
интерпретаций класса декартового произведения.
</li>
<li>
2001 Кононенко А.А., Майоров В.А. Программа преобразования сети процедур из формата Оргтеор в формат BPWin
(IDEF0).
</li>
<li>
2001 Майоров В.А. Программа построения формального выражения путем выбора альтернатив Grammar,{' '}
<i>предложившая альтернативной подход к построению корректных формальных выражений.</i>
</li>
<li>
2004 Гараева Ю.Р., Пономарев И.Н. Семантико-синтаксический анализатор текстов родов структур Бурбакизатор,{' '}
<i>
впервые включивший полный грамматический анализ родов структур и проверку биективной переносимости
выражения.
</i>
</li>
<li>
2003 Юдкин Ю.Ю., Кудюкин Д.А. Разработка и испытание компьютерной программы, формирующей
теоретико-множественную интерпретацию терма частной родоструктурной теории.
</li>
<li>
2004 Кононенко А.А. Генерация кода на языке программирования C++ по тексту концептуальной схемы,
эксплицированной в родах структур.
</li>
<li>2006 Кучкаров З.А., Никаноров С.П. Библиотека моделей.</li>
<li>2006 Кучкаров З.А., Лавров В.А. Полные системы простых теоретико-множественных операций.</li>
<li>
2006 Солнцев С.В., Присакарь С.В. Введение количественных отношений в методологию концептуального анализа и
проектирования, в том числе в язык родоструктурной экспликации.
</li>
<li>
2007 Пономарев И.Н. Учебное пособие: Введение в математическую логику и роды структур,{' '}
<i>
являющееся наиболее полным описанием теории родов структур, используемой в родоструктурных экспликациях.
</i>
</li>
<li>
2008 Пономарев И.Н. Об эквивалентной представимости рода структуры с помощью заданной типовой характеристики.
</li>
<li>
2008 Кононенко А.А., Кучкаров З.А., Никаноров С.П., Никитина Н.К. Технология концептуального проектирования,
&mdash; <i>монография, собравшая исторический обзор и перспективы развития технологического направления.</i>
</li>
<li>
2010 Кононенко А.А., Грязнов А.Д. Исследование и построение транслятора концептуальной схемы в концептуальную
модель.
</li>
<li>2010 Никаноров С.П. Введение в аппарат ступеней.</li>
<li>
2012 Кононенко А.А., Елисов Д.Н. Использование механизма XSD-схем для хранения и операционализации
концептуальных схем и концептуальных моделей с помощью XML.
</li>
<li>
2013 Кононенко А.А., Борисов И.Р. Исследование, разработка и экспериментальная программная реализация операций
над концептуальными моделями,{' '}
<i>
впервые реализовавшая модуль прямого вычисления интерпретации формального выражения, встроенный в Экстеор
3.5.
</i>
</li>
<li>2013 Пономарев И.Н., Липатов А.А. Операции над родами структур и пример автоматизации их выполнения.</li>
<li>
2014 Борисов И.Р., Баширов Р.М. Исследования и программная реализации оптимальной структуры данных для
вычисления интерпретации концептуальных схем.
</li>
<li>
2014 Борисов И.Р. Программный комплекс Экстеор 4,{' '}
<i>
включавший доработанный модуль операционного синтеза и синтаксический анализатор на базе грамматики,
предложенной Пономаревым И.Н.
</i>
</li>
<li>
2014 Борисов И.Р. Концептуальные конструкции в задаче синтеза систем на примере Экологического кодекса,
&mdash;{' '}
<i>
статья, вводящая понятие концептуальных конструкций как промежуточных форм для операционализации
концептуальных схем.
</i>
</li>
<li>2015 Иванов А.Ю. Аппарат ступеней С.П. Никанорова и возможное развитие идей по его использованию.</li>
<li>
2016 Борисов И.Р., Баширов Р.М. Исследование области компьютерной лингвистики и разработка модулей
терминологического контроля в Экстеор 4 и Microsoft Office Word,{' '}
<i>
являющееся основой библиотеки <TextURL text='cctext' href={external_urls.git_cctext} />.
</i>
</li>
<li>
2016 Борисов И.Р. Программный комплекс Экстеор 4.5,{' '}
<i>
включавший текстовый модуль и полностью переработанное ядро, выделенное в отдельную библиотеку (впоследствии
&mdash; <TextURL text='ConceptCore' href={external_urls.git_core} />
).
</i>
</li>
<li>
2017 Иванов А.Ю. Введение в технологию концептуализации предметных областей социологии: основы полагания ядра
теории (на примере родственных отношений).
</li>
<li>
2017 Борисов И.Р., Мурадов А.К. Организация операций над системами понятий посредством графических
интерфейсов, <i>заложивший основу для технологии Концепт.Блоки и блока графического синтеза.</i>
</li>
<li>
2018 Борисов И.Р., Князев А.В. Изучение методов концептуальной расчистки, разметки текстов и разработка
программных средств их автоматизации,{' '}
<i> &mdash; диплом, сформировавший основу для технологий Концепт.Разметка и Концепт.Майнинг.</i>
</li>
<li>
2018 Никитин А.В., Болотин П.В. Исследование типологии изменения теоретико-множественной интерпретации класса
множества подмножеств.
</li>
<li>
2019 Борисов И.Р., Широкова Л.Р. Исследование возможностей применения методов машинного обучения для решения
задач расчистки текстов. Разработка прототипа программного модуля, &mdash;{' '}
<i>первая попытка внедрения технологий ИИ в текстовый модуль.</i>
</li>
<li>
2020 Борисов И.Р., Пакулина Т.А. Исследование применения методов машинного обучения для выделения именованных
сущностей в текстах интервью. Экспериментальная разработка программного модуля расчистки текстов,{' '}
<i>ставшего расширением технологии Концепт.Расчистка.</i>
</li>
<li>
2020 Программный комплекс Экстеор 4.7, включающий значительное расширение выразительных средств языка родов
структур (рекурсивные и императивные выражения, фильтры, ASCII синтаксис).
</li>
<li>
2021 Борисов И.Р., Демешко А.Б. Исследование и разработка программного модуля формирования текстов функций на
основе концепта функциональная структура,{' '}
<i>дополнившего текстовый модуль возможностью работы с глагольными формами.</i>
</li>
<li>
2023 Борисов И.Р., Тулисов А.В. Разработка инструмента экспликации концептуальных схем в родоструктурной форме
через веб-интерфейс, &mdash; <i>разработка прототипа интерфейса КонцептПортал.</i>
</li>
<li>
2024 Борисов И.Р. Программный комплекс <LinkTopic text='Экстеор 4.9' topic={HelpTopic.EXTEOR} />,
поддерживающий работу со схемами, выгруженными из КонцептПортал.{' '}
<i>
Функционал ConceptCore (С++) стал доступен в Python через обертку{' '}
<TextURL text='pyconcept' href={external_urls.git_core} />.
</i>
</li>
</div>
</div>
);
}
export default HelpInfo;

View File

@ -1,16 +0,0 @@
import { HelpTopic } from '@/models/miscellaneous';
import Subtopics from '../Subtopics';
function HelpDocs() {
return (
<div>
<h1>Документы</h1>
<p>TBD.</p>
<Subtopics headTopic={HelpTopic.DOCS} />
</div>
);
}
export default HelpDocs;

View File

@ -1,19 +1,26 @@
import TextURL from '@/components/ui/TextURL';
import { external_urls } from '@/utils/constants';
import { external_urls, PARAMETER } from '@/utils/constants';
function HelpExteor() {
return (
<div>
<h1>Экстеор</h1>
<p>Экстеор 4.9 редактор текстов систем понятий эксплицированных в родах структур</p>
<p>Экстеор 4.9 редактор текстов систем понятий эксплицированных в родах структур.</p>
<p>
Портал превосходит Экстеор в части редактирования экспликаций, но вычисление интерпретации доступно только в
Экстеоре. Также следует использовать Экстеор для выгрузки экспликаций в Word для последующей печати
Экстеор. Также следует использовать Экстеор для выгрузки экспликаций в Word для последующей печати.
</p>
<p>Экстеор доступен на операционной системы Windows 10+</p>
<p>
Скачать установщик: <TextURL href={external_urls.exteor64} text='64bit' /> |{' '}
<TextURL href={external_urls.exteor32} text='32bit' />
<TextURL href={external_urls.exteor32} text='32bit' /> (Windows 10 и выше)
</p>
<p>
Текущая версия: <b>{PARAMETER.exteorVersion}</b>
</p>
<p>
Экстеор не поддерживает автоматическое обновление. Если в выгруженной схеме присутствуют неожиданные диагностики
или ошибки, то попробуйте скачать новую версию по ссылкам выше.
</p>
<h2>Основные функции</h2>

View File

@ -5,10 +5,8 @@ function HelpFormulaTree() {
return (
<div>
<h1>Дерево разбора выражения</h1>
<p>
Дерево разбора получено путем семантических преобразований дерева синтаксического разбора. Оно отражает
структуру грамматически корректного выражения языка родов структур.
</p>
<p>Дерево получено путем семантических преобразований дерева синтаксического разбора.</p>
<p>Оно отражает структуру грамматически корректного выражения языка родов структур.</p>
<li>Порядок узлов в рамках одного уровня может отличаться от их порядка в выражении</li>
<li>При наведении курсора на узел в тексте выделяется соответствующий ему фрагмент</li>
<li>Текст в узле дерева соответствует элементу языка</li>

View File

@ -0,0 +1,19 @@
import { HelpTopic } from '@/models/miscellaneous';
import Subtopics from '../Subtopics';
function HelpInfo() {
return (
<div>
<h1>Справочная информация и документы</h1>
<p>
Раздел содержит различные документы, задающие правовой статус Портала,
<br />а также документацию для разработчиков.
</p>
<Subtopics headTopic={HelpTopic.INFO} />
</div>
);
}
export default HelpInfo;

View File

@ -1,4 +1,15 @@
import { IconFolder, IconSearch, IconShow, IconSortAsc, IconSortDesc } from '@/components/Icons';
import {
IconFilterReset,
IconFolder,
IconFolderClosed,
IconFolderEmpty,
IconFolderOpened,
IconFolderTree,
IconSearch,
IconShow,
IconSortAsc,
IconSortDesc
} from '@/components/Icons';
function HelpLibrary() {
return (
@ -6,8 +17,9 @@ function HelpLibrary() {
<h1>Библиотека схем</h1>
<p>В библиотеке собраны концептуальные схемы, эксплицированные в родоструктурном аппарате</p>
<h2>Интерфейс</h2>
<li>клик по строке - переход к редактированию схемы</li>
<li>Ctrl + клик по строке откроет схему в новой вкладке</li>
<li>Фильтры атрибутов три позиции: да/нет/не применять</li>
<li>
<IconShow size='1rem' className='inline-icon' /> фильтры атрибутов применяются по клику
</li>
@ -21,6 +33,32 @@ function HelpLibrary() {
<li>
<IconFolder size='1rem' className='inline-icon' /> фильтр по расположению
</li>
<li>
<IconFilterReset size='1rem' className='inline-icon' /> сбросить фильтры
</li>
<li>
<IconFolderTree size='1rem' className='inline-icon' /> переключение между Проводник и Поиск
</li>
<h2>Режим: Проводник</h2>
<li>клик по папке отображает справа файлы в ней</li>
<li>Ctrl + клик по папке копирует путь в буфер обмена</li>
<li>клик по иконке сворачивает/разворачивает вложенные</li>
<li>
<IconFolderEmpty size='1rem' className='inline-icon clr-text-default' /> папка без файлов
</li>
<li>
<IconFolderEmpty size='1rem' className='inline-icon' /> папка с вложенными без файлов
</li>
<li>
<IconFolder size='1rem' className='inline-icon' /> папка без вложенных
</li>
<li>
<IconFolderClosed size='1rem' className='inline-icon' /> папка с вложенными и файлами
</li>
<li>
<IconFolderOpened size='1rem' className='inline-icon icon-green' /> развернутая папка
</li>
</div>
);
}

View File

@ -0,0 +1,10 @@
function HelpOssGraph() {
return (
<div>
<h1>Граф синтеза</h1>
<p>TBD.</p>
</div>
);
}
export default HelpOssGraph;

View File

@ -39,7 +39,7 @@ function HelpPortal() {
<h2>Разделы Справки</h2>
{[
HelpTopic.DOCS,
HelpTopic.INFO,
HelpTopic.INTERFACE,
HelpTopic.CONCEPTUAL,
HelpTopic.RSLANG,
@ -55,11 +55,11 @@ function HelpPortal() {
<h2>Лицензирование и раскрытие информации</h2>
<li>Пользователи Портала сохраняют авторские права на создаваемый ими контент</li>
<li>
Политика обработки данных доступна по <LinkTopic text='ссылке' topic={HelpTopic.PRIVACY} />
Политика обработки данных доступна по <LinkTopic text='ссылке' topic={HelpTopic.INFO_PRIVACY} />
</li>
<li>
Портал является проектом с открытым исходным кодом, доступным на{' '}
<TextURL text='Github' href={external_urls.git_repo} />
<TextURL text='Github' href={external_urls.git_portal} />
</li>
<li>
Данный сайт использует доменное имя и серверные мощности{' '}

View File

@ -1,10 +1,15 @@
import PDFViewer from '@/components/ui/PDFViewer';
import { resources } from '@/utils/constants';
function HelpPrivacy() {
interface HelpPrivacyProps {
offsetXpx: number;
minWidth: number;
}
function HelpPrivacy({ offsetXpx, minWidth }: HelpPrivacyProps) {
return (
<div>
<PDFViewer file={resources.privacy_policy} />
<PDFViewer file={resources.privacy_policy} offsetXpx={offsetXpx} minWidth={minWidth} />
</div>
);
}

View File

@ -1,8 +1,54 @@
import { urls } from '@/app/urls';
import TextURL from '@/components/ui/TextURL';
import { external_urls } from '@/utils/constants';
function HelpRules() {
return (
<div>
<h1>Правила Портала</h1>
<p>TBD.</p>
<h1>Правила поведения участников Портала</h1>
<p>
Мы стремимся предоставить возможность участия в работе Портала как можно большему количеству людей. Мы считаем,
что сообщества участников у нас должны быть как можно более разнообразными и открытыми для новых участников. Мы
хотим, чтобы эти сообщества представляли собой позитивное, безопасное и здоровое окружение для всех, кто
присоединяется или хочет присоединиться к ним. Мы стремимся сохранить такое окружение, в том числе путем
принятия этих Правил поведения. Кроме того, мы хотим защитить наши проекты от тех, кто портит или искажает их
содержание, поэтому к участникам, нарушающим Правила будут применяться санкции, определяемые Администрацией
Портала.
</p>
<p>
Поведение в рамках Портала основано на уважении, вежливости, коллегиальности, солидарности и социальной
ответственности. Это относится ко всем читателям, участникам и администраторам в их взаимодействии без
исключений, основанных на возрасте, умственных или физических возможностях, внешнем виде, национальном,
религиозном, этническом и культурном происхождении, касте, социальном классе, уровне владения языком,
сексуальной ориентации, гендерной идентичности, поле или сфере работы. Мы также не будем делать исключения на
основании статуса, навыков или достижений.
</p>
<h2>Ожидаемое поведение</h2>
<li>взаимное уважением, поддержка в отношениях с участниками Портала.</li>
<li>
пожелания по доработке, найденные ошибки и иные предложения следует направлять по адресу email:{' '}
<TextURL href={external_urls.mail_portal} text='portal@acconcept.ru' />.
</li>
<h2>Неприемлемое поведение</h2>
<li>оскорбления, угрозы, сексуальное домогательство, троллинг и преследование других участников.</li>
<li>
раскрытие персональных данных (доксинг) участников Портала. Не распространяется на персональные данные,
раскрытые участниками для отображения на Портале. Эти данные можно изменить в{' '}
<TextURL text='профиле' href={urls.profile} />.
</li>
<li>
злоупотребление властью, привилегиями или влиянием, включая использование статусов и доступов, предоставленных
Порталом в личных целях, не связанных с разработкой контента, развитием и продвижением Портала.
</li>
<li>
вандализм, намеренное добавление неуместного контента, или препятствование, затруднение или другого рода
осложнение создания (и/или поддержания) контента, созданного другими участниками.
</li>
<li>нарушение работоспособности Портала, в том числе путем использования уязвимостей и ошибок в коде.</li>
</div>
);
}

View File

@ -26,8 +26,8 @@ function HelpTermGraph() {
const { colors } = useConceptOptions();
return (
<div className='flex flex-col'>
<div className='flex'>
<div className='dense w-[14rem]'>
<div className='flex flex-col sm:flex-row'>
<div className='w-full sm:w-[14rem]'>
<h1>Настройка графа</h1>
<li>Цвет покраска узлов</li>
<li>Граф расположение</li>
@ -43,9 +43,9 @@ function HelpTermGraph() {
</li>
</div>
<Divider vertical margins='mx-3 mt-3' />
<Divider vertical margins='mx-3 mt-3' className='hidden sm:block' />
<div className='dense w-[21rem]'>
<div className='w-full sm:w-[21rem]'>
<h1>Изменение узлов</h1>
<li>Клик на конституенту выделение</li>
<li>
@ -66,10 +66,10 @@ function HelpTermGraph() {
</div>
</div>
<Divider margins='my-3' />
<Divider margins='my-3' className='hidden sm:block' />
<div className='flex mb-3'>
<div className='dense w-[14rem]'>
<div className='flex mb-3 flex-col-reverse sm:flex-row'>
<div className='w-full sm:w-[14rem]'>
<h1>Общие</h1>
<li>
<IconFilter className='inline-icon' /> Открыть настройки
@ -82,7 +82,7 @@ function HelpTermGraph() {
</li>
</div>
<Divider vertical margins='mx-3' />
<Divider vertical margins='mx-3' className='hidden sm:block' />
<div className='dense w-[21rem]'>
<h1>Выделение</h1>

View File

@ -12,6 +12,7 @@ import { useOSS } from '@/context/OssContext';
import { ILibraryUpdateData, LibraryItemType } from '@/models/library';
import AccessToolbar from '@/pages/RSFormPage/EditorRSFormCard/AccessToolbar';
import { limits, patterns } from '@/utils/constants';
import { information } from '@/utils/labels';
import { useOssEdit } from '../OssEditContext';
@ -81,7 +82,7 @@ function FormOSS({ id, isModified, setIsModified }: FormOSSProps) {
visible: visible,
read_only: readOnly
};
update(data, () => toast.success('Изменения сохранены'));
update(data, () => toast.success(information.changesSaved));
};
return (

View File

@ -10,6 +10,7 @@ import Overlay from '@/components/ui/Overlay';
import { useAccessMode } from '@/context/AccessModeContext';
import { HelpTopic } from '@/models/miscellaneous';
import { UserLevel } from '@/models/user';
import { PARAMETER } from '@/utils/constants';
import { prepareTooltip } from '@/utils/labels';
import { useOssEdit } from '../OssEditContext';
@ -57,7 +58,7 @@ function RSFormToolbar({ modified, anonymous, subscribed, onSubmit, onDestroy }:
onClick={onDestroy}
/>
) : null}
<BadgeHelp topic={HelpTopic.UI_RS_CARD} offset={4} className='max-w-[30rem]' />
<BadgeHelp topic={HelpTopic.UI_RS_CARD} offset={4} className={PARAMETER.TOOLTIP_WIDTH} />
</Overlay>
);
}

Some files were not shown because too many files have changed in this diff Show More