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

View File

@ -2,25 +2,24 @@
For more specific TODOs see comments in code For more specific TODOs see comments in code
[Functionality - PROGRESS] [Functionality - PROGRESS]
- Landing page
- Home page (user specific)
- Operational synthesis schema as LibraryItem ? - Operational synthesis schema as LibraryItem ?
- Draggable rows in constituents table
- Clickable IDs in RSEditor tooltips
- Library organization, search and exploration. Consider new user experience - Library organization, search and exploration. Consider new user experience
- Private projects and permissions. Consider cooperative editing - 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] [Functionality - PENDING]
- Search functionality for manuals
- User notifications on edit - consider spam prevention and change aggregation - User notifications on edit - consider spam prevention and change aggregation
- Static analyzer for RSForm - Static analyzer for RSForm
- Content based search in Library - Content based search in Library
- User profile: Settings + settings persistency - User profile: Settings + settings persistency
- Landing page
- Home page (user specific)
- Draggable rows in constituents table
- Export PDF (Items list, Graph) - Export PDF (Items list, Graph)
- ARIA (accessibility considerations) - for now machine reading not supported - ARIA (accessibility considerations) - for now machine reading not supported
- Internationalization - at least english version. Consider react.intl - Internationalization - at least english version. Consider react.intl
@ -42,6 +41,8 @@ For more specific TODOs see comments in code
[Security] [Security]
- password-reset leaks info of email being used - password-reset leaks info of email being used
- improve nginx config. Consider DDOS and other types of attacks on infrastructure - 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] [Research]

View File

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

View File

@ -16,7 +16,7 @@ Styling conventions
- border: borer-2 outline-none shadow-md - border: borer-2 outline-none shadow-md
- colors: clr-controls - colors: clr-controls
- text: text-start text-sm font-semibold whitespace-nowrap - 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: - transitions:
</pre> </pre>
</details> </details>

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,7 @@ function Footer() {
className={clsx( className={clsx(
'z-navigation', 'z-navigation',
'mx-auto', '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' 'text-xs sm:text-sm select-none whitespace-nowrap'
)} )}
> >

View File

@ -11,7 +11,7 @@ import { UsersState } from '@/context/UsersContext';
import ErrorFallback from './ErrorFallback'; 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 = () => { const resetState = () => {
console.log('Resetting state after error fallback'); 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}> <div tabIndex={-1} className='flex items-center mr-auto cursor-pointer' onClick={navigateHome}>
<Logo /> <Logo />
</div> </div>
<div className='flex'> <div className='flex gap-1 py-[0.3rem]'>
<NavigationButton <NavigationButton
text='Новая схема' text='Новая схема'
title='Создать новую схему' title='Создать новую схему'

View File

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

View File

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

View File

@ -1,5 +1,6 @@
import { AccessPolicy, LibraryItemType, LocationHead } from '@/models/library'; import { AccessPolicy, LibraryItemType, LocationHead } from '@/models/library';
import { CstMatchMode, DependencyMode } from '@/models/miscellaneous'; import { CstMatchMode, DependencyMode } from '@/models/miscellaneous';
import { ExpressionStatus } from '@/models/rsform';
import { import {
IconAlias, IconAlias,
@ -21,6 +22,10 @@ import {
IconRSForm, IconRSForm,
IconSettings, IconSettings,
IconShow, IconShow,
IconStatusError,
IconStatusIncalculable,
IconStatusOK,
IconStatusUnknown,
IconTemplates, IconTemplates,
IconTerm, IconTerm,
IconText, IconText,
@ -80,34 +85,51 @@ export function LocationIcon({ value, size = '1.25rem', className }: DomIconProp
} }
} }
export function DependencyIcon(mode: DependencyMode, size: string, color?: string) { export function DependencyIcon({ value, size = '1.25rem', className }: DomIconProps<DependencyMode>) {
switch (mode) { switch (value) {
case DependencyMode.ALL: case DependencyMode.ALL:
return <IconSettings size={size} className={color} />; return <IconSettings size={size} className={className} />;
case DependencyMode.EXPRESSION: case DependencyMode.EXPRESSION:
return <IconText size={size} className={color} />; return <IconText size={size} className={className ?? 'clr-text-primary'} />;
case DependencyMode.OUTPUTS: case DependencyMode.OUTPUTS:
return <IconGraphOutputs size={size} className={color} />; return <IconGraphOutputs size={size} className={className ?? 'clr-text-primary'} />;
case DependencyMode.INPUTS: case DependencyMode.INPUTS:
return <IconGraphInputs size={size} className={color} />; return <IconGraphInputs size={size} className={className ?? 'clr-text-primary'} />;
case DependencyMode.EXPAND_OUTPUTS: case DependencyMode.EXPAND_OUTPUTS:
return <IconGraphExpand size={size} className={color} />; return <IconGraphExpand size={size} className={className ?? 'clr-text-primary'} />;
case DependencyMode.EXPAND_INPUTS: 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) { export function MatchModeIcon({ value, size = '1.25rem', className }: DomIconProps<CstMatchMode>) {
switch (mode) { switch (value) {
case CstMatchMode.ALL: case CstMatchMode.ALL:
return <IconFilter size={size} className={color} />; return <IconFilter size={size} className={className} />;
case CstMatchMode.TEXT: case CstMatchMode.TEXT:
return <IconText size={size} className={color} />; return <IconText size={size} className={className ?? 'clr-text-primary'} />;
case CstMatchMode.EXPR: case CstMatchMode.EXPR:
return <IconFormula size={size} className={color} />; return <IconFormula size={size} className={className ?? 'clr-text-primary'} />;
case CstMatchMode.TERM: case CstMatchMode.TERM:
return <IconTerm size={size} className={color} />; return <IconTerm size={size} className={className ?? 'clr-text-primary'} />;
case CstMatchMode.NAME: 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 { TbEye as IconShow } from 'react-icons/tb';
export { TbEyeX as IconHide } from 'react-icons/tb'; export { TbEyeX as IconHide } from 'react-icons/tb';
export { BiShareAlt as IconShare } from 'react-icons/bi'; 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 {BiDownArrowCircle as IconOpenList } from 'react-icons/bi';
export { LuAlertTriangle as IconAlert } from 'react-icons/lu'; 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 { RiMenuUnfoldFill as IconMenuUnfold } from 'react-icons/ri';
export { LuMoon as IconDarkTheme } from 'react-icons/lu'; export { LuMoon as IconDarkTheme } from 'react-icons/lu';
export { LuSun as IconLightTheme } 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 { LuLightbulb as IconHelp } from 'react-icons/lu';
export { LuLightbulbOff as IconHelpOff } from 'react-icons/lu'; export { LuLightbulbOff as IconHelpOff } from 'react-icons/lu';
export { RiPushpinFill as IconPin } from 'react-icons/ri'; 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 Label from '@/components/ui/Label';
import { useConceptOptions } from '@/context/OptionsContext'; import { useConceptOptions } from '@/context/OptionsContext';
import { useRSForm } from '@/context/RSFormContext';
import { getFontClassName } from '@/models/miscellaneousAPI'; import { getFontClassName } from '@/models/miscellaneousAPI';
import { ConstituentaID, IRSForm } from '@/models/rsform';
import { generateAlias, getCstTypePrefix, guessCstType } from '@/models/rsformAPI'; import { generateAlias, getCstTypePrefix, guessCstType } from '@/models/rsformAPI';
import { extractGlobals } from '@/models/rslangAPI'; import { extractGlobals } from '@/models/rslangAPI';
import { ccBracketMatching } from './bracketMatching'; import { ccBracketMatching } from './bracketMatching';
import { rsNavigation } from './clickNavigation';
import { RSLanguage } from './rslang'; import { RSLanguage } from './rslang';
import { getSymbolSubstitute, RSTextWrapper } from './textEditing'; import { getSymbolSubstitute, RSTextWrapper } from './textEditing';
import { rsHoverTooltip } from './tooltip'; import { rsHoverTooltip } from './tooltip';
@ -39,6 +40,8 @@ interface RSInputProps
noTooltip?: boolean; noTooltip?: boolean;
onChange?: (newValue: string) => void; onChange?: (newValue: string) => void;
onAnalyze?: () => void; onAnalyze?: () => void;
schema?: IRSForm;
onOpenEdit?: (cstID: ConstituentaID) => void;
} }
const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>( const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
@ -49,6 +52,9 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
disabled, disabled,
noTooltip, noTooltip,
schema,
onOpenEdit,
className, className,
style, style,
@ -59,7 +65,6 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
ref ref
) => { ) => {
const { darkMode, colors, mathFont } = useConceptOptions(); const { darkMode, colors, mathFont } = useConceptOptions();
const { schema } = useRSForm();
const internalRef = useRef<ReactCodeMirrorRef>(null); const internalRef = useRef<ReactCodeMirrorRef>(null);
const thisRef = useMemo(() => (!ref || typeof ref === 'function' ? internalRef : ref), [internalRef, ref]); const thisRef = useMemo(() => (!ref || typeof ref === 'function' ? internalRef : ref), [internalRef, ref]);
@ -77,7 +82,7 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
caret: colors.fgDefault caret: colors.fgDefault
}, },
styles: [ 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.variableName, color: colors.fgGreen }, // LocalID
{ tag: tags.propertyName, color: colors.fgTeal }, // Radical { tag: tags.propertyName, color: colors.fgTeal }, // Radical
{ tag: tags.keyword, color: colors.fgBlue }, // keywords { 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) { tag: tags.brace, color: colors.fgPurple, fontWeight: '600' } // braces (curly brackets)
] ]
}), }),
[disabled, colors, darkMode] [disabled, colors, darkMode, schema, cursor]
); );
const editorExtensions = useMemo( const editorExtensions = useMemo(
@ -95,9 +100,10 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
EditorView.lineWrapping, EditorView.lineWrapping,
RSLanguage, RSLanguage,
ccBracketMatching(darkMode), 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( 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 { Extension } from '@codemirror/state';
import { hoverTooltip } from '@codemirror/view'; import { hoverTooltip } from '@codemirror/view';
import { EditorState } from '@uiw/react-codemirror';
import { IRSForm } from '@/models/rsform'; import { IRSForm } from '@/models/rsform';
import { findEnvelopingNodes } from '@/utils/codemirror'; import { findAliasAt } from '@/utils/codemirror';
import { domTooltipConstituenta } from '@/utils/codemirror'; import { domTooltipConstituenta } from '@/utils/codemirror';
import { GlobalTokens } from './rslang'; const tooltipProducer = (schema: IRSForm, canClick?: boolean) => {
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) => {
return hoverTooltip((view, pos) => { return hoverTooltip((view, pos) => {
const { alias, start, end } = findAliasAt(pos, view.state); const { alias, start, end } = findAliasAt(pos, view.state);
if (!alias) { if (!alias) {
@ -36,11 +16,11 @@ const globalsHoverTooltip = (schema: IRSForm) => {
pos: start, pos: start,
end: end, end: end,
above: false, above: false,
create: () => domTooltipConstituenta(cst) create: () => domTooltipConstituenta(cst, canClick)
}; };
}); });
}; };
export function rsHoverTooltip(schema: IRSForm): Extension { export function rsHoverTooltip(schema: IRSForm, canClick?: boolean): Extension {
return [globalsHoverTooltip(schema)]; return [tooltipProducer(schema, canClick)];
} }

View File

@ -13,10 +13,11 @@ import Label from '@/components/ui/Label';
import { useConceptOptions } from '@/context/OptionsContext'; import { useConceptOptions } from '@/context/OptionsContext';
import DlgEditReference from '@/dialogs/DlgEditReference'; import DlgEditReference from '@/dialogs/DlgEditReference';
import { ReferenceType } from '@/models/language'; import { ReferenceType } from '@/models/language';
import { IRSForm } from '@/models/rsform'; import { ConstituentaID, IRSForm } from '@/models/rsform';
import { CodeMirrorWrapper } from '@/utils/codemirror'; import { CodeMirrorWrapper } from '@/utils/codemirror';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import { refsNavigation } from './clickNavigation';
import { NaturalLanguage, ReferenceTokens } from './parse'; import { NaturalLanguage, ReferenceTokens } from './parse';
import { RefEntity } from './parse/parser.terms'; import { RefEntity } from './parse/parser.terms';
import { refsHoverTooltip } from './tooltip'; import { refsHoverTooltip } from './tooltip';
@ -65,6 +66,7 @@ interface RefsInputInputProps
label?: string; label?: string;
onChange?: (newValue: string) => void; onChange?: (newValue: string) => void;
schema?: IRSForm; schema?: IRSForm;
onOpenEdit?: (cstID: ConstituentaID) => void;
disabled?: boolean; disabled?: boolean;
initialValue?: string; initialValue?: string;
@ -73,7 +75,23 @@ interface RefsInputInputProps
} }
const RefsInput = forwardRef<ReactCodeMirrorRef, 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 { darkMode, colors } = useConceptOptions();
const [isFocused, setIsFocused] = useState(false); const [isFocused, setIsFocused] = useState(false);
@ -114,9 +132,10 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
EditorView.lineWrapping, EditorView.lineWrapping,
EditorView.contentAttributes.of({ spellcheck: 'true' }), EditorView.contentAttributes.of({ spellcheck: 'true' }),
NaturalLanguage, 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) { 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 { Extension } from '@codemirror/state';
import { hoverTooltip } from '@codemirror/view'; import { hoverTooltip } from '@codemirror/view';
import { parseEntityReference, parseSyntacticReference } from '@/models/languageAPI'; import { IEntityReference, ISyntacticReference } from '@/models/language';
import { IRSForm } from '@/models/rsform'; import { IRSForm } from '@/models/rsform';
import { IColorTheme } from '@/styling/color'; import { IColorTheme } from '@/styling/color';
import { import {
domTooltipEntityReference, domTooltipEntityReference,
domTooltipSyntacticReference, domTooltipSyntacticReference,
findContainedNodes, findContainedNodes,
findEnvelopingNodes findReferenceAt
} from '@/utils/codemirror'; } from '@/utils/codemirror';
import { ReferenceTokens } from './parse'; import { RefEntity } from './parse/parser.terms';
import { RefEntity, RefSyntactic } from './parse/parser.terms';
export const globalsHoverTooltip = (schema: IRSForm, colors: IColorTheme) => { export const tooltipProducer = (schema: IRSForm, colors: IColorTheme, canClick?: boolean) => {
return hoverTooltip((view, pos) => { return hoverTooltip((view, pos) => {
const nodes = findEnvelopingNodes(pos, pos, syntaxTree(view.state), ReferenceTokens); const parse = findReferenceAt(pos, view.state);
if (nodes.length !== 1) { if (!parse) {
return null; return null;
} }
const start = nodes[0].from;
const end = nodes[0].to; if ('entity' in parse.ref) {
const text = view.state.doc.sliceString(start, end); const cst = schema.cstByAlias.get(parse.ref.entity);
if (nodes[0].type.id === RefEntity) {
const ref = parseEntityReference(text);
const cst = schema.cstByAlias.get(ref.entity);
return { return {
pos: start, pos: parse.start,
end: end, end: parse.end,
above: false, above: false,
create: () => domTooltipEntityReference(ref, cst, colors) create: () => domTooltipEntityReference(parse.ref as IEntityReference, cst, colors, canClick)
}; };
} else if (nodes[0].type.id === RefSyntactic) { } else {
const ref = parseSyntacticReference(text);
let masterText: string | undefined = undefined; let masterText: string | undefined = undefined;
if (ref.offset > 0) { if (parse.ref.offset > 0) {
const entities = findContainedNodes(end, view.state.doc.length, syntaxTree(view.state), [RefEntity]); const entities = findContainedNodes(parse.end, view.state.doc.length, syntaxTree(view.state), [RefEntity]);
if (ref.offset <= entities.length) { if (parse.ref.offset <= entities.length) {
const master = entities[ref.offset - 1]; const master = entities[parse.ref.offset - 1];
masterText = view.state.doc.sliceString(master.from, master.to); masterText = view.state.doc.sliceString(master.from, master.to);
} }
} else { } else {
const entities = findContainedNodes(0, start, syntaxTree(view.state), [RefEntity]); const entities = findContainedNodes(0, parse.start, syntaxTree(view.state), [RefEntity]);
if (-ref.offset <= entities.length) { if (-parse.ref.offset <= entities.length) {
const master = entities[-ref.offset - 1]; const master = entities[-parse.ref.offset - 1];
masterText = view.state.doc.sliceString(master.from, master.to); masterText = view.state.doc.sliceString(master.from, master.to);
} }
} }
return { return {
pos: start, pos: parse.start,
end: end, end: parse.end,
above: false, 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 { export function refsHoverTooltip(schema: IRSForm, colors: IColorTheme, canClick?: boolean): Extension {
return [globalsHoverTooltip(schema, colors)]; return [tooltipProducer(schema, colors, canClick)];
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -38,7 +38,7 @@ function SelectItemType({ value, disabled, stretchLeft, onChange }: SelectItemTy
transparent transparent
title={describeLibraryItemType(value)} title={describeLibraryItemType(value)}
hideTitle={menu.isOpen} 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' />} icon={<ItemTypeIcon value={value} size='1.25rem' />}
text={labelLibraryItemType(value)} text={labelLibraryItemType(value)}
onClick={menu.toggle} 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 { interface SelectLocationHeadProps {
value: LocationHead; value: LocationHead;
onChange: (value: LocationHead) => void; onChange: (newValue: LocationHead) => void;
excluded?: LocationHead[]; excluded?: LocationHead[];
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,7 +28,7 @@ function MiniButton({
className={clsx( className={clsx(
'rounded-lg', 'rounded-lg',
'clr-btn-clear', 'clr-btn-clear',
'cursor-pointer disabled:cursor-not-allowed', 'cursor-pointer disabled:cursor-auto',
{ {
'px-1 py-1': !noPadding, 'px-1 py-1': !noPadding,
'outline-none': noHover, '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'; import PageControls from './PageControls';
const MAXIMUM_WIDTH = 1000; const MAXIMUM_WIDTH = 1000;
const MINIMUM_WIDTH = 320; const MINIMUM_WIDTH = 300;
interface PDFViewerProps { interface PDFViewerProps {
file?: string | ArrayBuffer | Blob; file?: string | ArrayBuffer | Blob;
offsetXpx?: number;
minWidth?: number;
} }
function PDFViewer({ file }: PDFViewerProps) { function PDFViewer({ file, offsetXpx, minWidth = MINIMUM_WIDTH }: PDFViewerProps) {
const windowSize = useWindowSize(); const windowSize = useWindowSize();
const [pageCount, setPageCount] = useState(0); const [pageCount, setPageCount] = useState(0);
const [pageNumber, setPageNumber] = useState(1); const [pageNumber, setPageNumber] = useState(1);
const pageWidth = useMemo(() => { const pageWidth = useMemo(() => {
return Math.max(MINIMUM_WIDTH, Math.min((windowSize?.width ?? 0) - 10, MAXIMUM_WIDTH)); return Math.max(minWidth, Math.min((windowSize?.width ?? 0) - (offsetXpx ?? 0) - 10, MAXIMUM_WIDTH));
}, [windowSize]); }, [windowSize, offsetXpx, minWidth]);
function onDocumentLoadSuccess({ numPages }: PDFDocumentProxy) { function onDocumentLoadSuccess({ numPages }: PDFDocumentProxy) {
setPageCount(numPages); setPageCount(numPages);
@ -42,14 +44,14 @@ function PDFViewer({ file }: PDFViewerProps) {
<PageControls pageCount={pageCount} pageNumber={pageNumber} setPageNumber={setPageNumber} /> <PageControls pageCount={pageCount} pageNumber={pageNumber} setPageNumber={setPageNumber} />
</Overlay> </Overlay>
<Page <Page
className='pointer-events-none select-none sm:translate-x-0' className='overflow-hidden pointer-events-none select-none'
renderTextLayer={false} renderTextLayer={false}
renderAnnotationLayer={false} renderAnnotationLayer={false}
pageNumber={pageNumber} pageNumber={pageNumber}
width={pageWidth} width={pageWidth}
canvasBackground={graphLightT.canvas.background} 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} /> <PageControls pageCount={pageCount} pageNumber={pageNumber} setPageNumber={setPageNumber} />
</Overlay> </Overlay>
</Document> </Document>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -33,7 +33,8 @@ function Tooltip({
delayHide={100} delayHide={100}
opacity={0.97} opacity={0.97}
className={clsx( 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', 'border shadow-md',
'text-balance', 'text-balance',
layer, layer,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,6 +9,7 @@ import RSInput from '@/components/RSInput';
import PickConstituenta from '@/components/select/PickConstituenta'; import PickConstituenta from '@/components/select/PickConstituenta';
import DataTable, { IConditionalStyle } from '@/components/ui/DataTable'; import DataTable, { IConditionalStyle } from '@/components/ui/DataTable';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import NoData from '@/components/ui/NoData';
import AnimateFade from '@/components/wrap/AnimateFade'; import AnimateFade from '@/components/wrap/AnimateFade';
import { useConceptOptions } from '@/context/OptionsContext'; import { useConceptOptions } from '@/context/OptionsContext';
import { IConstituenta, IRSForm } from '@/models/rsform'; import { IConstituenta, IRSForm } from '@/models/rsform';
@ -160,7 +161,7 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
data={state.arguments} data={state.arguments}
columns={columns} columns={columns}
conditionalRowStyles={conditionalRowStyles} 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} onRowClicked={handleSelectArgument}
/> />

View File

@ -14,6 +14,7 @@ import { HelpTopic } from '@/models/miscellaneous';
import { CstType, ICstCreateData, IRSForm } from '@/models/rsform'; import { CstType, ICstCreateData, IRSForm } from '@/models/rsform';
import { generateAlias, validateNewAlias } from '@/models/rsformAPI'; import { generateAlias, validateNewAlias } from '@/models/rsformAPI';
import { inferTemplatedType, substituteTemplateArgs } from '@/models/rslangAPI'; import { inferTemplatedType, substituteTemplateArgs } from '@/models/rslangAPI';
import { PARAMETER } from '@/utils/constants';
import FormCreateCst from '../DlgCreateCst/FormCreateCst'; import FormCreateCst from '../DlgCreateCst/FormCreateCst';
import ArgumentsTab, { IArgumentsState } from './ArgumentsTab'; import ArgumentsTab, { IArgumentsState } from './ArgumentsTab';
@ -144,7 +145,11 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
onSubmit={handleSubmit} onSubmit={handleSubmit}
> >
<Overlay position='top-0 right-[6rem]'> <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> </Overlay>
<Tabs <Tabs
selectedTabClassName='clr-selected' 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')}> <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]' />
<TabLabel label='Конституента' title='Редактирование атрибутов конституенты' className='w-[8rem]' /> <TabLabel label='Конституента' title='Редактирование конституенты' className='w-[8rem]' />
</TabList> </TabList>
{templatePanel} {templatePanel}

View File

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

View File

@ -1,5 +1,6 @@
'use client'; 'use client';
import clsx from 'clsx';
import { AnimatePresence } from 'framer-motion'; import { AnimatePresence } from 'framer-motion';
import { useLayoutEffect, useMemo, useState } from 'react'; import { useLayoutEffect, useMemo, useState } from 'react';
@ -12,6 +13,7 @@ import AnimateFade from '@/components/wrap/AnimateFade';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { CstType, ICstCreateData, IRSForm } from '@/models/rsform'; import { CstType, ICstCreateData, IRSForm } from '@/models/rsform';
import { generateAlias, isBaseSet, isBasicConcept, isFunctional, validateNewAlias } from '@/models/rsformAPI'; import { generateAlias, isBaseSet, isBasicConcept, isFunctional, validateNewAlias } from '@/models/rsformAPI';
import { PARAMETER } from '@/utils/constants';
import { labelCstType } from '@/utils/labels'; import { labelCstType } from '@/utils/labels';
import { SelectorCstType } from '@/utils/selectors'; 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) }} value={{ value: state.cst_type, label: labelCstType(state.cst_type) }}
onChange={data => partialUpdate({ cst_type: data?.value ?? CstType.BASE })} 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 <TextInput
id='dlg_cst_alias' id='dlg_cst_alias'
dense dense
@ -89,6 +95,7 @@ function FormCreateCst({ schema, state, partialUpdate, setValidated }: FormCreat
} }
value={state.definition_formal} value={state.definition_formal}
onChange={value => partialUpdate({ definition_formal: value })} onChange={value => partialUpdate({ definition_formal: value })}
schema={schema}
/> />
</AnimateFade> </AnimateFade>
<AnimateFade key='dlg_cst_definition' hideContent={!state.definition_raw && isElementary}> <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} schema={schema}
prefix={prefixes.cst_dependant_list} 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> </Modal>
); );
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@ import { HelpTopic } from '@/models/miscellaneous';
import { SyntaxTree } from '@/models/rslang'; import { SyntaxTree } from '@/models/rslang';
import { graphDarkT, graphLightT } from '@/styling/color'; import { graphDarkT, graphLightT } from '@/styling/color';
import { colorBgSyntaxTree } from '@/styling/color'; import { colorBgSyntaxTree } from '@/styling/color';
import { resources } from '@/utils/constants'; import { PARAMETER, resources } from '@/utils/constants';
import { labelSyntaxTree } from '@/utils/labels'; import { labelSyntaxTree } from '@/utils/labels';
interface DlgShowASTProps extends Pick<ModalProps, 'hideWindow'> { interface DlgShowASTProps extends Pick<ModalProps, 'hideWindow'> {
@ -55,7 +55,7 @@ function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) {
return ( return (
<Modal readonly hideWindow={hideWindow} className='px-6'> <Modal readonly hideWindow={hideWindow} className='px-6'>
<Overlay position='left-[-1rem] top-[0.25rem]'> <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> </Overlay>
<div className='my-2 text-lg text-center'> <div className='my-2 text-lg text-center'>
{!hoverNode ? expression : null} {!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 { export enum LocationHead {
USER = '/U', USER = '/U',
LIBRARY = '/L',
COMMON = '/S', 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. * 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. * Represents graph dependency mode.
@ -37,11 +37,6 @@ export type FontStyle = 'controls' | 'main' | 'math' | 'math2';
export enum HelpTopic { export enum HelpTopic {
MAIN = 'main', MAIN = 'main',
DOCS = 'documentation',
RULES = 'rules',
PRIVACY = 'privacy',
API = 'api',
INTERFACE = 'user-interface', INTERFACE = 'user-interface',
UI_LIBRARY = 'ui-library', UI_LIBRARY = 'ui-library',
UI_RS_MENU = 'ui-rsform-menu', UI_RS_MENU = 'ui-rsform-menu',
@ -52,12 +47,14 @@ export enum HelpTopic {
UI_FORMULA_TREE = 'ui-formula-tree', UI_FORMULA_TREE = 'ui-formula-tree',
UI_CST_STATUS = 'ui-rsform-cst-status', UI_CST_STATUS = 'ui-rsform-cst-status',
UI_CST_CLASS = 'ui-rsform-cst-class', UI_CST_CLASS = 'ui-rsform-cst-class',
UI_OSS_GRAPH = 'ui-oss-graph',
CONCEPTUAL = 'concept', CONCEPTUAL = 'concept',
CC_SYSTEM = 'rslang-rsform', CC_SYSTEM = 'concept-rsform',
CC_CONSTITUENTA = 'rslang-cst', CC_CONSTITUENTA = 'concept-constituenta',
CC_RELATIONS = 'rslang-relations', CC_RELATIONS = 'concept-relations',
CC_SYNTHESIS = 'rslang-synthesis', CC_SYNTHESIS = 'concept-synthesis',
CC_OSS = 'concept-operations-schema',
RSLANG = 'rslang', RSLANG = 'rslang',
RSL_TYPES = 'rslang-types', RSL_TYPES = 'rslang-types',
@ -69,6 +66,13 @@ export enum HelpTopic {
TERM_CONTROL = 'terminology-control', TERM_CONTROL = 'terminology-control',
ACCESS = 'access', ACCESS = 'access',
VERSIONS = 'versions', VERSIONS = 'versions',
INFO = 'documentation',
INFO_RULES = 'rules',
INFO_CONTRIB = 'contributors',
INFO_PRIVACY = 'privacy',
INFO_API = 'api',
EXTEOR = 'exteor' EXTEOR = 'exteor'
} }
@ -78,11 +82,6 @@ export enum HelpTopic {
export const topicParent: Map<HelpTopic, HelpTopic> = new Map([ export const topicParent: Map<HelpTopic, HelpTopic> = new Map([
[HelpTopic.MAIN, HelpTopic.MAIN], [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.INTERFACE, HelpTopic.INTERFACE],
[HelpTopic.UI_LIBRARY, HelpTopic.INTERFACE], [HelpTopic.UI_LIBRARY, HelpTopic.INTERFACE],
[HelpTopic.UI_RS_MENU, 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_FORMULA_TREE, HelpTopic.INTERFACE],
[HelpTopic.UI_CST_STATUS, HelpTopic.INTERFACE], [HelpTopic.UI_CST_STATUS, HelpTopic.INTERFACE],
[HelpTopic.UI_CST_CLASS, HelpTopic.INTERFACE], [HelpTopic.UI_CST_CLASS, HelpTopic.INTERFACE],
[HelpTopic.UI_OSS_GRAPH, HelpTopic.INTERFACE],
[HelpTopic.CONCEPTUAL, HelpTopic.CONCEPTUAL], [HelpTopic.CONCEPTUAL, HelpTopic.CONCEPTUAL],
[HelpTopic.CC_SYSTEM, HelpTopic.CONCEPTUAL], [HelpTopic.CC_SYSTEM, HelpTopic.CONCEPTUAL],
[HelpTopic.CC_CONSTITUENTA, HelpTopic.CONCEPTUAL], [HelpTopic.CC_CONSTITUENTA, HelpTopic.CONCEPTUAL],
[HelpTopic.CC_RELATIONS, HelpTopic.CONCEPTUAL], [HelpTopic.CC_RELATIONS, HelpTopic.CONCEPTUAL],
[HelpTopic.CC_SYNTHESIS, HelpTopic.CONCEPTUAL], [HelpTopic.CC_SYNTHESIS, HelpTopic.CONCEPTUAL],
[HelpTopic.CC_OSS, HelpTopic.CONCEPTUAL],
[HelpTopic.RSLANG, HelpTopic.RSLANG], [HelpTopic.RSLANG, HelpTopic.RSLANG],
[HelpTopic.RSL_TYPES, 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.TERM_CONTROL, HelpTopic.TERM_CONTROL],
[HelpTopic.ACCESS, HelpTopic.ACCESS], [HelpTopic.ACCESS, HelpTopic.ACCESS],
[HelpTopic.VERSIONS, HelpTopic.VERSIONS], [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] [HelpTopic.EXTEOR, HelpTopic.EXTEOR]
]); ]);
/** /**
* Topics that can be folded. * 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. * Represents {@link IConstituenta} matching mode.
@ -133,10 +141,15 @@ export enum CstMatchMode {
* Represents Library filter parameters. * Represents Library filter parameters.
*/ */
export interface ILibraryFilter { export interface ILibraryFilter {
type?: LibraryItemType;
query?: string; query?: string;
path?: string; path?: string;
head?: LocationHead; head?: LocationHead;
folderMode?: boolean;
folder?: string;
isVisible?: boolean; isVisible?: boolean;
isOwned?: boolean; isOwned?: boolean;
isSubscribed?: boolean; isSubscribed?: boolean;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,20 +1,24 @@
import useWindowSize from '@/hooks/useWindowSize';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import HelpAccess from './items/HelpAccess'; import HelpAccess from './items/HelpAccess';
import HelpAPI from './items/HelpAPI'; import HelpAPI from './items/HelpAPI';
import HelpConcept from './items/HelpConcept'; import HelpConcept from './items/HelpConcept';
import HelpConceptOSS from './items/HelpConceptOSS';
import HelpConceptRelations from './items/HelpConceptRelations'; import HelpConceptRelations from './items/HelpConceptRelations';
import HelpConceptSynthesis from './items/HelpConceptSynthesis'; import HelpConceptSynthesis from './items/HelpConceptSynthesis';
import HelpConceptSystem from './items/HelpConceptSystem'; import HelpConceptSystem from './items/HelpConceptSystem';
import HelpContributors from './items/HelpContributors';
import HelpCstAttributes from './items/HelpCstAttributes'; import HelpCstAttributes from './items/HelpCstAttributes';
import HelpCstClass from './items/HelpCstClass'; import HelpCstClass from './items/HelpCstClass';
import HelpCstEditor from './items/HelpCstEditor'; import HelpCstEditor from './items/HelpCstEditor';
import HelpCstStatus from './items/HelpCstStatus'; import HelpCstStatus from './items/HelpCstStatus';
import HelpDocs from './items/HelpDocs';
import HelpExteor from './items/HelpExteor'; import HelpExteor from './items/HelpExteor';
import HelpFormulaTree from './items/HelpFormulaTree'; import HelpFormulaTree from './items/HelpFormulaTree';
import HelpInfo from './items/HelpInfo';
import HelpInterface from './items/HelpInterface'; import HelpInterface from './items/HelpInterface';
import HelpLibrary from './items/HelpLibrary'; import HelpLibrary from './items/HelpLibrary';
import HelpOssGraph from './items/HelpOssGraph';
import HelpPortal from './items/HelpPortal'; import HelpPortal from './items/HelpPortal';
import HelpPrivacy from './items/HelpPrivacy'; import HelpPrivacy from './items/HelpPrivacy';
import HelpRSFormCard from './items/HelpRSFormCard'; import HelpRSFormCard from './items/HelpRSFormCard';
@ -31,17 +35,21 @@ import HelpTermGraph from './items/HelpTermGraph';
import HelpTerminologyControl from './items/HelpTerminologyControl'; import HelpTerminologyControl from './items/HelpTerminologyControl';
import HelpVersions from './items/HelpVersions'; 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 { interface TopicPageProps {
topic: HelpTopic; topic: HelpTopic;
} }
function TopicPage({ topic }: TopicPageProps) { function TopicPage({ topic }: TopicPageProps) {
if (topic === HelpTopic.MAIN) return <HelpPortal />; const size = useWindowSize();
if (topic === HelpTopic.DOCS) return <HelpDocs />; if (topic === HelpTopic.MAIN) return <HelpPortal />;
if (topic === HelpTopic.RULES) return <HelpRules />;
if (topic === HelpTopic.PRIVACY) return <HelpPrivacy />;
if (topic === HelpTopic.API) return <HelpAPI />;
if (topic === HelpTopic.INTERFACE) return <HelpInterface />; if (topic === HelpTopic.INTERFACE) return <HelpInterface />;
if (topic === HelpTopic.UI_LIBRARY) return <HelpLibrary />; 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_FORMULA_TREE) return <HelpFormulaTree />;
if (topic === HelpTopic.UI_CST_STATUS) return <HelpCstStatus />; if (topic === HelpTopic.UI_CST_STATUS) return <HelpCstStatus />;
if (topic === HelpTopic.UI_CST_CLASS) return <HelpCstClass />; 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.CONCEPTUAL) return <HelpConcept />;
if (topic === HelpTopic.CC_SYSTEM) return <HelpConceptSystem />; if (topic === HelpTopic.CC_SYSTEM) return <HelpConceptSystem />;
if (topic === HelpTopic.CC_CONSTITUENTA) return <HelpCstAttributes />; if (topic === HelpTopic.CC_CONSTITUENTA) return <HelpCstAttributes />;
if (topic === HelpTopic.CC_RELATIONS) return <HelpConceptRelations />; if (topic === HelpTopic.CC_RELATIONS) return <HelpConceptRelations />;
if (topic === HelpTopic.CC_SYNTHESIS) return <HelpConceptSynthesis />; if (topic === HelpTopic.CC_SYNTHESIS) return <HelpConceptSynthesis />;
if (topic === HelpTopic.CC_OSS) return <HelpConceptOSS />;
if (topic === HelpTopic.RSLANG) return <HelpRSLang />; if (topic === HelpTopic.RSLANG) return <HelpRSLang />;
if (topic === HelpTopic.RSL_TYPES) return <HelpRSLangTypes />; 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.TERM_CONTROL) return <HelpTerminologyControl />;
if (topic === HelpTopic.ACCESS) return <HelpAccess />; if (topic === HelpTopic.ACCESS) return <HelpAccess />;
if (topic === HelpTopic.VERSIONS) return <HelpVersions />; 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 />; if (topic === HelpTopic.EXTEOR) return <HelpExteor />;
return null; return null;
} }

View File

@ -8,7 +8,7 @@ interface ViewTopicProps {
function ViewTopic({ topic }: ViewTopicProps) { function ViewTopic({ topic }: ViewTopicProps) {
return ( 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} /> <TopicPage topic={topic} />
</AnimateFade> </AnimateFade>
); );

View File

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

View File

@ -1,8 +1,31 @@
import { IconHide, IconImmutable, IconPrivate, IconProtected, IconPublic } from '@/components/Icons';
function HelpAccess() { function HelpAccess() {
return ( return (
<div> <div>
<h1>Организация доступов</h1> <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> </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 TextURL from '@/components/ui/TextURL';
import { external_urls } from '@/utils/constants'; import { external_urls, PARAMETER } from '@/utils/constants';
function HelpExteor() { function HelpExteor() {
return ( return (
<div> <div>
<h1>Экстеор</h1> <h1>Экстеор</h1>
<p>Экстеор 4.9 редактор текстов систем понятий эксплицированных в родах структур</p> <p>Экстеор 4.9 редактор текстов систем понятий эксплицированных в родах структур.</p>
<p> <p>
Портал превосходит Экстеор в части редактирования экспликаций, но вычисление интерпретации доступно только в Портал превосходит Экстеор в части редактирования экспликаций, но вычисление интерпретации доступно только в
Экстеоре. Также следует использовать Экстеор для выгрузки экспликаций в Word для последующей печати Экстеор. Также следует использовать Экстеор для выгрузки экспликаций в Word для последующей печати.
</p> </p>
<p>Экстеор доступен на операционной системы Windows 10+</p>
<p> <p>
Скачать установщик: <TextURL href={external_urls.exteor64} text='64bit' /> |{' '} Скачать установщик: <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> </p>
<h2>Основные функции</h2> <h2>Основные функции</h2>

View File

@ -5,10 +5,8 @@ function HelpFormulaTree() {
return ( return (
<div> <div>
<h1>Дерево разбора выражения</h1> <h1>Дерево разбора выражения</h1>
<p> <p>Дерево получено путем семантических преобразований дерева синтаксического разбора.</p>
Дерево разбора получено путем семантических преобразований дерева синтаксического разбора. Оно отражает <p>Оно отражает структуру грамматически корректного выражения языка родов структур.</p>
структуру грамматически корректного выражения языка родов структур.
</p>
<li>Порядок узлов в рамках одного уровня может отличаться от их порядка в выражении</li> <li>Порядок узлов в рамках одного уровня может отличаться от их порядка в выражении</li>
<li>При наведении курсора на узел в тексте выделяется соответствующий ему фрагмент</li> <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() { function HelpLibrary() {
return ( return (
@ -6,8 +17,9 @@ function HelpLibrary() {
<h1>Библиотека схем</h1> <h1>Библиотека схем</h1>
<p>В библиотеке собраны концептуальные схемы, эксплицированные в родоструктурном аппарате</p> <p>В библиотеке собраны концептуальные схемы, эксплицированные в родоструктурном аппарате</p>
<h2>Интерфейс</h2> <li>клик по строке - переход к редактированию схемы</li>
<li>Ctrl + клик по строке откроет схему в новой вкладке</li> <li>Ctrl + клик по строке откроет схему в новой вкладке</li>
<li>Фильтры атрибутов три позиции: да/нет/не применять</li>
<li> <li>
<IconShow size='1rem' className='inline-icon' /> фильтры атрибутов применяются по клику <IconShow size='1rem' className='inline-icon' /> фильтры атрибутов применяются по клику
</li> </li>
@ -21,6 +33,32 @@ function HelpLibrary() {
<li> <li>
<IconFolder size='1rem' className='inline-icon' /> фильтр по расположению <IconFolder size='1rem' className='inline-icon' /> фильтр по расположению
</li> </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> </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> <h2>Разделы Справки</h2>
{[ {[
HelpTopic.DOCS, HelpTopic.INFO,
HelpTopic.INTERFACE, HelpTopic.INTERFACE,
HelpTopic.CONCEPTUAL, HelpTopic.CONCEPTUAL,
HelpTopic.RSLANG, HelpTopic.RSLANG,
@ -55,11 +55,11 @@ function HelpPortal() {
<h2>Лицензирование и раскрытие информации</h2> <h2>Лицензирование и раскрытие информации</h2>
<li>Пользователи Портала сохраняют авторские права на создаваемый ими контент</li> <li>Пользователи Портала сохраняют авторские права на создаваемый ими контент</li>
<li> <li>
Политика обработки данных доступна по <LinkTopic text='ссылке' topic={HelpTopic.PRIVACY} /> Политика обработки данных доступна по <LinkTopic text='ссылке' topic={HelpTopic.INFO_PRIVACY} />
</li> </li>
<li> <li>
Портал является проектом с открытым исходным кодом, доступным на{' '} Портал является проектом с открытым исходным кодом, доступным на{' '}
<TextURL text='Github' href={external_urls.git_repo} /> <TextURL text='Github' href={external_urls.git_portal} />
</li> </li>
<li> <li>
Данный сайт использует доменное имя и серверные мощности{' '} Данный сайт использует доменное имя и серверные мощности{' '}

View File

@ -1,10 +1,15 @@
import PDFViewer from '@/components/ui/PDFViewer'; import PDFViewer from '@/components/ui/PDFViewer';
import { resources } from '@/utils/constants'; import { resources } from '@/utils/constants';
function HelpPrivacy() { interface HelpPrivacyProps {
offsetXpx: number;
minWidth: number;
}
function HelpPrivacy({ offsetXpx, minWidth }: HelpPrivacyProps) {
return ( return (
<div> <div>
<PDFViewer file={resources.privacy_policy} /> <PDFViewer file={resources.privacy_policy} offsetXpx={offsetXpx} minWidth={minWidth} />
</div> </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() { function HelpRules() {
return ( return (
<div> <div>
<h1>Правила Портала</h1> <h1>Правила поведения участников Портала</h1>
<p>TBD.</p>
<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> </div>
); );
} }

View File

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

View File

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

View File

@ -10,6 +10,7 @@ import Overlay from '@/components/ui/Overlay';
import { useAccessMode } from '@/context/AccessModeContext'; import { useAccessMode } from '@/context/AccessModeContext';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { UserLevel } from '@/models/user'; import { UserLevel } from '@/models/user';
import { PARAMETER } from '@/utils/constants';
import { prepareTooltip } from '@/utils/labels'; import { prepareTooltip } from '@/utils/labels';
import { useOssEdit } from '../OssEditContext'; import { useOssEdit } from '../OssEditContext';
@ -57,7 +58,7 @@ function RSFormToolbar({ modified, anonymous, subscribed, onSubmit, onDestroy }:
onClick={onDestroy} onClick={onDestroy}
/> />
) : null} ) : 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> </Overlay>
); );
} }

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