Compare commits

...

15 Commits

Author SHA1 Message Date
Ivan
939652cef0 B: Fix error with loading RSForm
Some checks failed
Frontend CI / build (22.x) (push) Has been cancelled
2024-12-17 21:24:58 +03:00
Ivan
160383b394 Update package.json 2024-12-17 21:23:07 +03:00
Ivan
02c93dfddc M: Add animation for tooltip 2024-12-17 13:43:32 +03:00
Ivan
7e63103d6b R: Improve styling semantics pt2 2024-12-17 11:37:42 +03:00
Ivan
d250a9fda0 R: Improve styling semantics pt1 2024-12-17 10:52:36 +03:00
Ivan
e70f7e45b9 F: Rework colors using tailwind configs 2024-12-17 00:05:13 +03:00
Ivan
c7f155bba1 F: Add selection animation for graphs 2024-12-16 11:54:01 +03:00
Ivan
6f922df227 R: Remove unused useMemo and useCallback 2024-12-13 21:30:49 +03:00
Ivan
dc7a0025c9 F: Add react-compiler to build toolchain 2024-12-13 13:55:11 +03:00
Ivan
bf44b14945 M: Small UI improvement 2024-12-13 13:22:13 +03:00
Ivan
9da54d78ab M: Fix redirect to replace history 2024-12-13 13:17:54 +03:00
Ivan
32b41620d8 R: Fix Rules of React violations 2024-12-13 13:10:03 +03:00
Ivan
150e6498af R: Fix updating procedure for graph nodes 2024-12-13 10:59:47 +03:00
Ivan
07919cd912 R: Remove .Provider from Contexts 2024-12-12 21:58:07 +03:00
Ivan
aa4a8b3f94 R: Prepare to migrate to react-compiler pt1 2024-12-12 21:45:46 +03:00
179 changed files with 2942 additions and 3619 deletions

View File

@ -56,9 +56,11 @@ This readme file is used mostly to document project dependencies and conventions
- tailwindcss - tailwindcss
- postcss - postcss
- autoprefixer - autoprefixer
- eslint-plugin-react-compiler
- eslint-plugin-simple-import-sort - eslint-plugin-simple-import-sort
- eslint-plugin-react-hooks - eslint-plugin-react-hooks
- eslint-plugin-tsdoc - eslint-plugin-tsdoc
- babel-plugin-react-compiler
- vite - vite
- jest - jest
- ts-jest - ts-jest

View File

@ -14,8 +14,7 @@ Styling conventions
- inner layout: px-3 py-2 flex flex-col gap-3 justify-between items-center - inner layout: px-3 py-2 flex flex-col gap-3 justify-between items-center
- overflow behavior: overflow-scroll overscroll-contain - overflow behavior: overflow-scroll overscroll-contain
- border: borer-2 outline-none shadow-md - border: borer-2 outline-none shadow-md
- colors: clr-controls - text: text-start text-sm font-semibold whitespace-nowrap bg-prim-200 fg-app-100
- text: text-start text-sm font-semibold whitespace-nowrap
- behavior modifiers: select-none disabled:cursor-auto - behavior modifiers: select-none disabled:cursor-auto
- transitions: - transitions:
</pre> </pre>

View File

@ -2,6 +2,7 @@ import globals from 'globals';
import typescriptPlugin from 'typescript-eslint'; import typescriptPlugin from 'typescript-eslint';
import typescriptParser from '@typescript-eslint/parser'; import typescriptParser from '@typescript-eslint/parser';
import reactPlugin from 'eslint-plugin-react'; import reactPlugin from 'eslint-plugin-react';
import reactCompilerPlugin from 'eslint-plugin-react-compiler';
import reactHooksPlugin from 'eslint-plugin-react-hooks'; import reactHooksPlugin from 'eslint-plugin-react-hooks';
import simpleImportSort from 'eslint-plugin-simple-import-sort'; import simpleImportSort from 'eslint-plugin-simple-import-sort';
@ -34,11 +35,13 @@ export default [
{ {
plugins: { plugins: {
'react': reactPlugin, 'react': reactPlugin,
'react-compiler': reactCompilerPlugin,
'react-hooks': reactHooksPlugin, 'react-hooks': reactHooksPlugin,
'simple-import-sort': simpleImportSort 'simple-import-sort': simpleImportSort
}, },
settings: { react: { version: 'detect' } }, settings: { react: { version: 'detect' } },
rules: { rules: {
'react-compiler/react-compiler': 'error',
'@typescript-eslint/no-empty-object-type': ['error', { allowInterfaces: 'with-single-extends' }], '@typescript-eslint/no-empty-object-type': ['error', { allowInterfaces: 'with-single-extends' }],
'@typescript-eslint/prefer-nullish-coalescing': 'off', '@typescript-eslint/prefer-nullish-coalescing': 'off',
'@typescript-eslint/no-inferrable-types': 'off', '@typescript-eslint/no-inferrable-types': 'off',

View File

@ -41,8 +41,10 @@
"@typescript-eslint/parser": "^8.0.1", "@typescript-eslint/parser": "^8.0.1",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"babel-plugin-react-compiler": "^19.0.0-beta-37ed2a7-20241206",
"eslint": "^9.16.0", "eslint": "^9.16.0",
"eslint-plugin-react": "^7.37.2", "eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-compiler": "^19.0.0-beta-37ed2a7-20241206",
"eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-simple-import-sort": "^12.1.1",
"globals": "^15.13.0", "globals": "^15.13.0",
@ -163,6 +165,19 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/helper-annotate-as-pure": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz",
"integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.25.9"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-compilation-targets": { "node_modules/@babel/helper-compilation-targets": {
"version": "7.25.9", "version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz",
@ -190,6 +205,52 @@
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
}, },
"node_modules/@babel/helper-create-class-features-plugin": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz",
"integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.25.9",
"@babel/helper-member-expression-to-functions": "^7.25.9",
"@babel/helper-optimise-call-expression": "^7.25.9",
"@babel/helper-replace-supers": "^7.25.9",
"@babel/helper-skip-transparent-expression-wrappers": "^7.25.9",
"@babel/traverse": "^7.25.9",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@babel/helper-member-expression-to-functions": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz",
"integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.25.9",
"@babel/types": "^7.25.9"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-imports": { "node_modules/@babel/helper-module-imports": {
"version": "7.25.9", "version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
@ -221,6 +282,19 @@
"@babel/core": "^7.0.0" "@babel/core": "^7.0.0"
} }
}, },
"node_modules/@babel/helper-optimise-call-expression": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz",
"integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.25.9"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-plugin-utils": { "node_modules/@babel/helper-plugin-utils": {
"version": "7.25.9", "version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz",
@ -231,6 +305,38 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/helper-replace-supers": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz",
"integrity": "sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-member-expression-to-functions": "^7.25.9",
"@babel/helper-optimise-call-expression": "^7.25.9",
"@babel/traverse": "^7.25.9"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-skip-transparent-expression-wrappers": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz",
"integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.25.9",
"@babel/types": "^7.25.9"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": { "node_modules/@babel/helper-string-parser": {
"version": "7.25.9", "version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
@ -288,6 +394,24 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/@babel/plugin-proposal-private-methods": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz",
"integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==",
"deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.18.6",
"@babel/helper-plugin-utils": "^7.18.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-async-generators": { "node_modules/@babel/plugin-syntax-async-generators": {
"version": "7.8.4", "version": "7.8.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
@ -3885,6 +4009,16 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/babel-plugin-react-compiler": {
"version": "19.0.0-beta-37ed2a7-20241206",
"resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-19.0.0-beta-37ed2a7-20241206.tgz",
"integrity": "sha512-nnkrHpeDKM8A5laq9tmFvvGbbDQ7laGfQLp50cvCkCXmWrPcZdCtaQpNh8UJS/yLREJnv2R4JDL5ADfxyAn+yQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.19.0"
}
},
"node_modules/babel-preset-current-node-syntax": { "node_modules/babel-preset-current-node-syntax": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz",
@ -5153,6 +5287,27 @@
"eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7"
} }
}, },
"node_modules/eslint-plugin-react-compiler": {
"version": "19.0.0-beta-37ed2a7-20241206",
"resolved": "https://registry.npmjs.org/eslint-plugin-react-compiler/-/eslint-plugin-react-compiler-19.0.0-beta-37ed2a7-20241206.tgz",
"integrity": "sha512-5Pex1fUCJwLwwqEJe6NkgTn45kUjjj9TZP6IrW4IcpWM/YaEe+QvcOeF60huDjBq0kz1svGeW2nw8WdY+qszAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/core": "^7.24.4",
"@babel/parser": "^7.24.4",
"@babel/plugin-proposal-private-methods": "^7.18.6",
"hermes-parser": "^0.25.1",
"zod": "^3.22.4",
"zod-validation-error": "^3.0.3"
},
"engines": {
"node": "^14.17.0 || ^16.0.0 || >= 18.0.0"
},
"peerDependencies": {
"eslint": ">=7"
}
},
"node_modules/eslint-plugin-react-hooks": { "node_modules/eslint-plugin-react-hooks": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0.tgz",
@ -6022,6 +6177,23 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/hermes-estree": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
"integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
"dev": true,
"license": "MIT"
},
"node_modules/hermes-parser": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
"integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
"dev": true,
"license": "MIT",
"dependencies": {
"hermes-estree": "0.25.1"
}
},
"node_modules/hoist-non-react-statics": { "node_modules/hoist-non-react-statics": {
"version": "3.3.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@ -10333,6 +10505,29 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/zod": {
"version": "3.24.1",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
"integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zod-validation-error": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.4.0.tgz",
"integrity": "sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"zod": "^3.18.0"
}
},
"node_modules/zustand": { "node_modules/zustand": {
"version": "4.5.5", "version": "4.5.5",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.5.tgz", "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.5.tgz",

View File

@ -9,7 +9,7 @@
"dev": "vite --host", "dev": "vite --host",
"build": "tsc && vite build", "build": "tsc && vite build",
"lint": "eslint . --report-unused-disable-directives --max-warnings 0", "lint": "eslint . --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview" "preview": "vite preview --port 3000"
}, },
"dependencies": { "dependencies": {
"@dagrejs/dagre": "^1.1.4", "@dagrejs/dagre": "^1.1.4",
@ -45,8 +45,10 @@
"@typescript-eslint/parser": "^8.0.1", "@typescript-eslint/parser": "^8.0.1",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"babel-plugin-react-compiler": "^19.0.0-beta-37ed2a7-20241206",
"eslint": "^9.16.0", "eslint": "^9.16.0",
"eslint-plugin-react": "^7.37.2", "eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-compiler": "^19.0.0-beta-37ed2a7-20241206",
"eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-simple-import-sort": "^12.1.1",
"globals": "^15.13.0", "globals": "^15.13.0",

View File

@ -13,7 +13,7 @@ function ApplicationLayout() {
const { viewportHeight, mainHeight, showScroll } = useConceptOptions(); const { viewportHeight, mainHeight, showScroll } = useConceptOptions();
return ( return (
<NavigationState> <NavigationState>
<div className='min-w-[20rem] clr-app antialiased h-full max-w-[120rem] mx-auto'> <div className='min-w-[20rem] antialiased h-full max-w-[120rem] mx-auto'>
<ConceptToaster <ConceptToaster
className='mt-[4rem] text-[14px]' // prettier: split lines className='mt-[4rem] text-[14px]' // prettier: split lines
autoClose={3000} autoClose={3000}

View File

@ -5,7 +5,7 @@ import Button from '../components/ui/Button';
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
return ( return (
<div className='flex flex-col gap-3 items-center antialiased clr-app' role='alert'> <div className='flex flex-col gap-3 items-center antialiased' role='alert'>
<h1>Что-то пошло не так!</h1> <h1>Что-то пошло не так!</h1>
<Button onClick={resetErrorBoundary} text='Попробовать еще раз' /> <Button onClick={resetErrorBoundary} text='Попробовать еще раз' />
<InfoError error={error as Error} /> <InfoError error={error as Error} />

View File

@ -16,17 +16,17 @@ function Footer() {
'z-navigation', 'z-navigation',
'mx-auto', 'mx-auto',
'px-3 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 text-prim-600 bg-prim-100'
)} )}
> >
<div className='flex gap-3'> <div className='flex gap-3'>
<TextURL text='Библиотека' href='/library' color='clr-footer' /> <TextURL text='Библиотека' href='/library' color='' />
<TextURL text='Справка' href='/manuals' color='clr-footer' /> <TextURL text='Справка' href='/manuals' color='' />
<TextURL text='Центр Концепт' href={external_urls.concept} color='clr-footer' /> <TextURL text='Центр Концепт' href={external_urls.concept} color='' />
<TextURL text='Экстеор' href='/manuals?topic=exteor' color='clr-footer' /> <TextURL text='Экстеор' href='/manuals?topic=exteor' color='' />
</div> </div>
<div> <div>
<p className='clr-footer'>© 2024 ЦИВТ КОНЦЕПТ</p> <p>© 2024 ЦИВТ КОНЦЕПТ</p>
</div> </div>
</footer> </footer>
); );

View File

@ -29,7 +29,6 @@ function Navigation() {
className={clsx( className={clsx(
'z-navigation', // prettier: split lines 'z-navigation', // prettier: split lines
'sticky top-0 left-0 right-0', 'sticky top-0 left-0 right-0',
'clr-app',
'select-none' 'select-none'
)} )}
> >

View File

@ -31,7 +31,7 @@ function NavigationButton({
className={clsx( className={clsx(
'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 cc-animate-color',
'rounded-xl', 'rounded-xl',
'transition duration-500', 'transition duration-500',
'font-controls whitespace-nowrap', 'font-controls whitespace-nowrap',

View File

@ -21,7 +21,7 @@ function ToggleNavigation() {
data-tooltip-id={globals.tooltip} data-tooltip-id={globals.tooltip}
data-tooltip-content={noNavigationAnimation ? 'Показать навигацию' : 'Скрыть навигацию'} data-tooltip-content={noNavigationAnimation ? 'Показать навигацию' : 'Скрыть навигацию'}
style={{ style={{
transitionProperty: 'height, width', transitionProperty: 'height, width, background-color',
transitionDuration: `${PARAMETER.moveDuration}ms`, transitionDuration: `${PARAMETER.moveDuration}ms`,
height: noNavigationAnimation ? '1.2rem' : '3rem', height: noNavigationAnimation ? '1.2rem' : '3rem',
width: noNavigationAnimation ? '3rem' : '1.2rem' width: noNavigationAnimation ? '3rem' : '1.2rem'

View File

@ -62,8 +62,8 @@ function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
} }
function handleToggleDarkMode() { function handleToggleDarkMode() {
hideDropdown();
toggleDarkMode(); toggleDarkMode();
hideDropdown();
} }
return ( return (

View File

@ -49,9 +49,9 @@ export interface DomIconProps<RequestData> extends IconProps {
export function ItemTypeIcon({ value, size = '1.25rem', className }: DomIconProps<LibraryItemType>) { export function ItemTypeIcon({ value, size = '1.25rem', className }: DomIconProps<LibraryItemType>) {
switch (value) { switch (value) {
case LibraryItemType.RSFORM: case LibraryItemType.RSFORM:
return <IconRSForm size={size} className={className ?? 'clr-text-primary'} />; return <IconRSForm size={size} className={className ?? 'text-sec-600'} />;
case LibraryItemType.OSS: case LibraryItemType.OSS:
return <IconOSS size={size} className={className ?? 'clr-text-green'} />; return <IconOSS size={size} className={className ?? 'text-ok-600'} />;
} }
} }
@ -59,29 +59,29 @@ export function ItemTypeIcon({ value, size = '1.25rem', className }: DomIconProp
export function PolicyIcon({ value, size = '1.25rem', className }: DomIconProps<AccessPolicy>) { export function PolicyIcon({ value, size = '1.25rem', className }: DomIconProps<AccessPolicy>) {
switch (value) { switch (value) {
case AccessPolicy.PRIVATE: case AccessPolicy.PRIVATE:
return <IconPrivate size={size} className={className ?? 'clr-text-red'} />; return <IconPrivate size={size} className={className ?? 'text-warn-600'} />;
case AccessPolicy.PROTECTED: case AccessPolicy.PROTECTED:
return <IconProtected size={size} className={className ?? 'clr-text-primary'} />; return <IconProtected size={size} className={className ?? 'text-sec-600'} />;
case AccessPolicy.PUBLIC: case AccessPolicy.PUBLIC:
return <IconPublic size={size} className={className ?? 'clr-text-green'} />; return <IconPublic size={size} className={className ?? 'text-ok-600'} />;
} }
} }
/** Icon for visibility. */ /** Icon for visibility. */
export function VisibilityIcon({ value, size = '1.25rem', className }: DomIconProps<boolean>) { export function VisibilityIcon({ value, size = '1.25rem', className }: DomIconProps<boolean>) {
if (value) { if (value) {
return <IconShow size={size} className={className ?? 'clr-text-green'} />; return <IconShow size={size} className={className ?? 'text-ok-600'} />;
} else { } else {
return <IconHide size={size} className={className ?? 'clr-text-red'} />; return <IconHide size={size} className={className ?? 'text-warn-600'} />;
} }
} }
/** Icon for subfolders. */ /** Icon for subfolders. */
export function SubfoldersIcon({ value, size = '1.25rem', className }: DomIconProps<boolean>) { export function SubfoldersIcon({ value, size = '1.25rem', className }: DomIconProps<boolean>) {
if (value) { if (value) {
return <IconSubfolders size={size} className={className ?? 'clr-text-green'} />; return <IconSubfolders size={size} className={className ?? 'text-ok-600'} />;
} else { } else {
return <IconSubfolders size={size} className={className ?? 'clr-text-primary'} />; return <IconSubfolders size={size} className={className ?? 'text-sec-600'} />;
} }
} }
@ -89,13 +89,13 @@ export function SubfoldersIcon({ value, size = '1.25rem', className }: DomIconPr
export function LocationIcon({ value, size = '1.25rem', className }: DomIconProps<string>) { export function LocationIcon({ value, size = '1.25rem', className }: DomIconProps<string>) {
switch (value.substring(0, 2) as LocationHead) { switch (value.substring(0, 2) as LocationHead) {
case LocationHead.COMMON: case LocationHead.COMMON:
return <IconPublic size={size} className={className ?? 'clr-text-primary'} />; return <IconPublic size={size} className={className ?? 'text-sec-600'} />;
case LocationHead.LIBRARY: case LocationHead.LIBRARY:
return <IconTemplates size={size} className={className ?? 'clr-text-red'} />; return <IconTemplates size={size} className={className ?? 'text-warn-600'} />;
case LocationHead.PROJECTS: case LocationHead.PROJECTS:
return <IconBusiness size={size} className={className ?? 'clr-text-primary'} />; return <IconBusiness size={size} className={className ?? 'text-sec-600'} />;
case LocationHead.USER: case LocationHead.USER:
return <IconUser size={size} className={className ?? 'clr-text-green'} />; return <IconUser size={size} className={className ?? 'text-ok-600'} />;
} }
} }
@ -105,13 +105,13 @@ export function DependencyIcon({ value, size = '1.25rem', className }: DomIconPr
case DependencyMode.ALL: case DependencyMode.ALL:
return <IconSettings size={size} className={className} />; return <IconSettings size={size} className={className} />;
case DependencyMode.OUTPUTS: case DependencyMode.OUTPUTS:
return <IconGraphOutputs size={size} className={className ?? 'clr-text-primary'} />; return <IconGraphOutputs size={size} className={className ?? 'text-sec-600'} />;
case DependencyMode.INPUTS: case DependencyMode.INPUTS:
return <IconGraphInputs size={size} className={className ?? 'clr-text-primary'} />; return <IconGraphInputs size={size} className={className ?? 'text-sec-600'} />;
case DependencyMode.EXPAND_OUTPUTS: case DependencyMode.EXPAND_OUTPUTS:
return <IconGraphExpand size={size} className={className ?? 'clr-text-primary'} />; return <IconGraphExpand size={size} className={className ?? 'text-sec-600'} />;
case DependencyMode.EXPAND_INPUTS: case DependencyMode.EXPAND_INPUTS:
return <IconGraphCollapse size={size} className={className ?? 'clr-text-primary'} />; return <IconGraphCollapse size={size} className={className ?? 'text-sec-600'} />;
} }
} }
@ -121,13 +121,13 @@ export function MatchModeIcon({ value, size = '1.25rem', className }: DomIconPro
case CstMatchMode.ALL: case CstMatchMode.ALL:
return <IconFilter size={size} className={className} />; return <IconFilter size={size} className={className} />;
case CstMatchMode.TEXT: case CstMatchMode.TEXT:
return <IconText size={size} className={className ?? 'clr-text-primary'} />; return <IconText size={size} className={className ?? 'text-sec-600'} />;
case CstMatchMode.EXPR: case CstMatchMode.EXPR:
return <IconFormula size={size} className={className ?? 'clr-text-primary'} />; return <IconFormula size={size} className={className ?? 'text-sec-600'} />;
case CstMatchMode.TERM: case CstMatchMode.TERM:
return <IconTerm size={size} className={className ?? 'clr-text-primary'} />; return <IconTerm size={size} className={className ?? 'text-sec-600'} />;
case CstMatchMode.NAME: case CstMatchMode.NAME:
return <IconAlias size={size} className={className ?? 'clr-text-primary'} />; return <IconAlias size={size} className={className ?? 'text-sec-600'} />;
} }
} }
@ -153,29 +153,29 @@ export function StatusIcon({ value, size = '1.25rem', className }: DomIconProps<
export function CstTypeIcon({ value, size = '1.25rem', className }: DomIconProps<CstType>) { export function CstTypeIcon({ value, size = '1.25rem', className }: DomIconProps<CstType>) {
switch (value) { switch (value) {
case CstType.BASE: case CstType.BASE:
return <IconCstBaseSet size={size} className={className ?? 'clr-text-green'} />; return <IconCstBaseSet size={size} className={className ?? 'text-ok-600'} />;
case CstType.CONSTANT: case CstType.CONSTANT:
return <IconCstConstSet size={size} className={className ?? 'clr-text-green'} />; return <IconCstConstSet size={size} className={className ?? 'text-ok-600'} />;
case CstType.STRUCTURED: case CstType.STRUCTURED:
return <IconCstStructured size={size} className={className ?? 'clr-text-green'} />; return <IconCstStructured size={size} className={className ?? 'text-ok-600'} />;
case CstType.TERM: case CstType.TERM:
return <IconCstTerm size={size} className={className ?? 'clr-text-primary'} />; return <IconCstTerm size={size} className={className ?? 'text-sec-600'} />;
case CstType.AXIOM: case CstType.AXIOM:
return <IconCstAxiom size={size} className={className ?? 'clr-text-red'} />; return <IconCstAxiom size={size} className={className ?? 'text-warn-600'} />;
case CstType.FUNCTION: case CstType.FUNCTION:
return <IconCstFunction size={size} className={className ?? 'clr-text-primary'} />; return <IconCstFunction size={size} className={className ?? 'text-sec-600'} />;
case CstType.PREDICATE: case CstType.PREDICATE:
return <IconCstPredicate size={size} className={className ?? 'clr-text-red'} />; return <IconCstPredicate size={size} className={className ?? 'text-warn-600'} />;
case CstType.THEOREM: case CstType.THEOREM:
return <IconCstTheorem size={size} className={className ?? 'clr-text-red'} />; return <IconCstTheorem size={size} className={className ?? 'text-warn-600'} />;
} }
} }
/** Icon for relocation direction. */ /** Icon for relocation direction. */
export function RelocateUpIcon({ value, size = '1.25rem', className }: DomIconProps<boolean>) { export function RelocateUpIcon({ value, size = '1.25rem', className }: DomIconProps<boolean>) {
if (value) { if (value) {
return <IconMoveUp size={size} className={className ?? 'clr-text-primary'} />; return <IconMoveUp size={size} className={className ?? 'text-sec-600'} />;
} else { } else {
return <IconMoveDown size={size} className={className ?? 'clr-text-primary'} />; return <IconMoveDown size={size} className={className ?? 'text-sec-600'} />;
} }
} }

View File

@ -6,13 +6,14 @@ import { createTheme } from '@uiw/codemirror-themes';
import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror'; import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror';
import clsx from 'clsx'; import clsx from 'clsx';
import { EditorView } from 'codemirror'; import { EditorView } from 'codemirror';
import { forwardRef, useCallback, useMemo, useRef } from 'react'; import { forwardRef, useRef } from 'react';
import Label from '@/components/ui/Label'; import Label from '@/components/ui/Label';
import { useConceptOptions } from '@/context/ConceptOptionsContext'; import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { ConstituentaID, IRSForm } from '@/models/rsform'; 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 { APP_COLORS } from '@/styling/color';
import { ccBracketMatching } from './bracketMatching'; import { ccBracketMatching } from './bracketMatching';
import { rsNavigation } from './clickNavigation'; import { rsNavigation } from './clickNavigation';
@ -63,50 +64,42 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
}, },
ref ref
) => { ) => {
const { darkMode, colors } = useConceptOptions(); const { darkMode } = useConceptOptions();
const internalRef = useRef<ReactCodeMirrorRef>(null); const internalRef = useRef<ReactCodeMirrorRef>(null);
const thisRef = useMemo(() => (!ref || typeof ref === 'function' ? internalRef : ref), [internalRef, ref]); const thisRef = !ref || typeof ref === 'function' ? internalRef : ref;
const cursor = useMemo(() => (!disabled ? 'cursor-text' : 'cursor-default'), [disabled]); const cursor = !disabled ? 'cursor-text' : 'cursor-default';
const customTheme: Extension = useMemo( const customTheme: Extension = createTheme({
() =>
createTheme({
theme: darkMode ? 'dark' : 'light', theme: darkMode ? 'dark' : 'light',
settings: { settings: {
fontFamily: 'inherit', fontFamily: 'inherit',
background: !disabled ? colors.bgInput : colors.bgDefault, background: !disabled ? APP_COLORS.bgInput : APP_COLORS.bgDefault,
foreground: colors.fgDefault, foreground: APP_COLORS.fgDefault,
selection: colors.bgHover, selection: APP_COLORS.bgHover,
caret: colors.fgDefault caret: APP_COLORS.fgDefault
}, },
styles: [ styles: [
{ tag: tags.name, color: colors.fgPurple, cursor: schema ? 'default' : cursor }, // GlobalID { tag: tags.name, color: APP_COLORS.fgPurple, cursor: schema ? 'default' : cursor }, // GlobalID
{ tag: tags.variableName, color: colors.fgGreen }, // LocalID { tag: tags.variableName, color: APP_COLORS.fgGreen }, // LocalID
{ tag: tags.propertyName, color: colors.fgTeal }, // Radical { tag: tags.propertyName, color: APP_COLORS.fgTeal }, // Radical
{ tag: tags.keyword, color: colors.fgBlue }, // keywords { tag: tags.keyword, color: APP_COLORS.fgBlue }, // keywords
{ tag: tags.literal, color: colors.fgBlue }, // literals { tag: tags.literal, color: APP_COLORS.fgBlue }, // literals
{ tag: tags.controlKeyword, fontWeight: '400' }, // R | I | D { tag: tags.controlKeyword, fontWeight: '400' }, // R | I | D
{ tag: tags.unit, fontSize: '0.75rem' }, // indices { tag: tags.unit, fontSize: '0.75rem' }, // indices
{ tag: tags.brace, color: colors.fgPurple, fontWeight: '600' } // braces (curly brackets) { tag: tags.brace, color: APP_COLORS.fgPurple, fontWeight: '600' } // braces (curly brackets)
] ]
}), });
[disabled, colors, darkMode, schema, cursor]
);
const editorExtensions = useMemo( const editorExtensions = [
() => [
EditorView.lineWrapping, EditorView.lineWrapping,
RSLanguage, RSLanguage,
ccBracketMatching(darkMode), ccBracketMatching(),
...(!schema || !onOpenEdit ? [] : [rsNavigation(schema, onOpenEdit)]), ...(!schema || !onOpenEdit ? [] : [rsNavigation(schema, onOpenEdit)]),
...(noTooltip || !schema ? [] : [rsHoverTooltip(schema, onOpenEdit !== undefined)]) ...(noTooltip || !schema ? [] : [rsHoverTooltip(schema, onOpenEdit !== undefined)])
], ];
[darkMode, schema, noTooltip, onOpenEdit]
);
const handleInput = useCallback( function handleInput(event: React.KeyboardEvent<HTMLDivElement>) {
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (!thisRef.current) { if (!thisRef.current) {
return; return;
} }
@ -159,9 +152,7 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
} }
}, }
[thisRef, onAnalyze, schema]
);
return ( return (
<div className={clsx('flex flex-col gap-2', className, cursor)} style={style}> <div className={clsx('flex flex-col gap-2', className, cursor)} style={style}>

View File

@ -1,7 +1,7 @@
import { bracketMatching, MatchResult } from '@codemirror/language'; import { bracketMatching, MatchResult } from '@codemirror/language';
import { Decoration, EditorView } from '@codemirror/view'; import { Decoration, EditorView } from '@codemirror/view';
import { bracketsDarkT, bracketsLightT } from '@/styling/color'; import { BRACKETS_THEME } from '@/styling/color';
const matchingMark = Decoration.mark({ class: 'cc-matchingBracket' }); const matchingMark = Decoration.mark({ class: 'cc-matchingBracket' });
const nonMatchingMark = Decoration.mark({ class: 'cc-nonmatchingBracket' }); const nonMatchingMark = Decoration.mark({ class: 'cc-nonmatchingBracket' });
@ -16,16 +16,14 @@ function bracketRender(match: MatchResult) {
return decorations; return decorations;
} }
const darkTheme = EditorView.baseTheme(bracketsDarkT); const theme = EditorView.baseTheme(BRACKETS_THEME);
const lightTheme = EditorView.baseTheme(bracketsLightT); export function ccBracketMatching() {
export function ccBracketMatching(darkMode: boolean) {
return [ return [
bracketMatching({ bracketMatching({
renderMatch: bracketRender, renderMatch: bracketRender,
brackets: '{}[]()' brackets: '{}[]()'
}), }),
darkMode ? darkTheme : lightTheme theme
]; ];
} }

View File

@ -6,13 +6,14 @@ import { createTheme } from '@uiw/codemirror-themes';
import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror'; import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror';
import clsx from 'clsx'; import clsx from 'clsx';
import { EditorView } from 'codemirror'; import { EditorView } from 'codemirror';
import { forwardRef, useCallback, useMemo, useRef, useState } from 'react'; import { forwardRef, useRef, useState } from 'react';
import Label from '@/components/ui/Label'; import Label from '@/components/ui/Label';
import { useConceptOptions } from '@/context/ConceptOptionsContext'; import { useConceptOptions } from '@/context/ConceptOptionsContext';
import DlgEditReference from '@/dialogs/DlgEditReference'; import DlgEditReference from '@/dialogs/DlgEditReference';
import { ReferenceType } from '@/models/language'; import { ReferenceType } from '@/models/language';
import { ConstituentaID, IRSForm } from '@/models/rsform'; import { ConstituentaID, IRSForm } from '@/models/rsform';
import { APP_COLORS } from '@/styling/color';
import { CodeMirrorWrapper } from '@/utils/codemirror'; import { CodeMirrorWrapper } from '@/utils/codemirror';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
@ -91,7 +92,7 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
}, },
ref ref
) => { ) => {
const { darkMode, colors } = useConceptOptions(); const { darkMode } = useConceptOptions();
const [isFocused, setIsFocused] = useState(false); const [isFocused, setIsFocused] = useState(false);
@ -103,39 +104,32 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
const [mainRefs, setMainRefs] = useState<string[]>([]); const [mainRefs, setMainRefs] = useState<string[]>([]);
const internalRef = useRef<ReactCodeMirrorRef>(null); const internalRef = useRef<ReactCodeMirrorRef>(null);
const thisRef = useMemo(() => (!ref || typeof ref === 'function' ? internalRef : ref), [internalRef, ref]); const thisRef = !ref || typeof ref === 'function' ? internalRef : ref;
const cursor = useMemo(() => (!disabled ? 'cursor-text' : 'cursor-default'), [disabled]); const cursor = !disabled ? 'cursor-text' : 'cursor-default';
const customTheme: Extension = useMemo( const customTheme: Extension = createTheme({
() =>
createTheme({
theme: darkMode ? 'dark' : 'light', theme: darkMode ? 'dark' : 'light',
settings: { settings: {
fontFamily: 'inherit', fontFamily: 'inherit',
background: !disabled ? colors.bgInput : colors.bgDefault, background: !disabled ? APP_COLORS.bgInput : APP_COLORS.bgDefault,
foreground: colors.fgDefault, foreground: APP_COLORS.fgDefault,
selection: colors.bgHover, selection: APP_COLORS.bgHover,
caret: colors.fgDefault caret: APP_COLORS.fgDefault
}, },
styles: [ styles: [
{ tag: tags.name, color: colors.fgPurple, cursor: 'default' }, // EntityReference { tag: tags.name, color: APP_COLORS.fgPurple, cursor: 'default' }, // EntityReference
{ tag: tags.literal, color: colors.fgTeal, cursor: 'default' }, // SyntacticReference { tag: tags.literal, color: APP_COLORS.fgTeal, cursor: 'default' }, // SyntacticReference
{ tag: tags.comment, color: colors.fgRed } // Error { tag: tags.comment, color: APP_COLORS.fgRed } // Error
] ]
}), });
[disabled, colors, darkMode]
);
const editorExtensions = useMemo( const editorExtensions = [
() => [
EditorView.lineWrapping, EditorView.lineWrapping,
EditorView.contentAttributes.of({ spellcheck: 'true' }), EditorView.contentAttributes.of({ spellcheck: 'true' }),
NaturalLanguage, NaturalLanguage,
...(!schema || !onOpenEdit ? [] : [refsNavigation(schema, onOpenEdit)]), ...(!schema || !onOpenEdit ? [] : [refsNavigation(schema, onOpenEdit)]),
...(schema ? [refsHoverTooltip(schema, colors, onOpenEdit !== undefined)] : []) ...(schema ? [refsHoverTooltip(schema, onOpenEdit !== undefined)] : [])
], ];
[schema, colors, onOpenEdit]
);
function handleChange(newValue: string) { function handleChange(newValue: string) {
if (onChange) onChange(newValue); if (onChange) onChange(newValue);
@ -151,8 +145,7 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
if (onBlur) onBlur(event); if (onBlur) onBlur(event);
} }
const handleInput = useCallback( function handleInput(event: React.KeyboardEvent<HTMLDivElement>) {
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (!thisRef.current?.view) { if (!thisRef.current?.view) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
@ -183,26 +176,21 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
setShowEditor(true); setShowEditor(true);
} }
}, }
[thisRef]
);
const handleInputReference = useCallback( function handleInputReference(referenceText: string) {
(referenceText: string) => {
if (!thisRef.current?.view) { if (!thisRef.current?.view) {
return; return;
} }
thisRef.current.view.focus(); thisRef.current.view.focus();
const wrap = new CodeMirrorWrapper(thisRef.current as Required<ReactCodeMirrorRef>); const wrap = new CodeMirrorWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
wrap.replaceWith(referenceText); wrap.replaceWith(referenceText);
}, }
[thisRef]
);
const hideEditReference = useCallback(() => { function hideEditReference() {
setShowEditor(false); setShowEditor(false);
setTimeout(() => thisRef.current?.view?.focus(), PARAMETER.refreshTimeout); setTimeout(() => thisRef.current?.view?.focus(), PARAMETER.refreshTimeout);
}, [thisRef]); }
return ( return (
<div className={clsx('flex flex-col gap-2', cursor)}> <div className={clsx('flex flex-col gap-2', cursor)}>

View File

@ -4,7 +4,6 @@ import { hoverTooltip } from '@codemirror/view';
import { IEntityReference, ISyntacticReference } from '@/models/language'; import { IEntityReference, ISyntacticReference } from '@/models/language';
import { IRSForm } from '@/models/rsform'; import { IRSForm } from '@/models/rsform';
import { IColorTheme } from '@/styling/color';
import { import {
domTooltipEntityReference, domTooltipEntityReference,
domTooltipSyntacticReference, domTooltipSyntacticReference,
@ -14,7 +13,7 @@ import {
import { RefEntity } from './parse/parser.terms'; import { RefEntity } from './parse/parser.terms';
export const tooltipProducer = (schema: IRSForm, colors: IColorTheme, canClick?: boolean) => { export const tooltipProducer = (schema: IRSForm, canClick?: boolean) => {
return hoverTooltip((view, pos) => { return hoverTooltip((view, pos) => {
const parse = findReferenceAt(pos, view.state); const parse = findReferenceAt(pos, view.state);
if (!parse) { if (!parse) {
@ -27,7 +26,7 @@ export const tooltipProducer = (schema: IRSForm, colors: IColorTheme, canClick?:
pos: parse.start, pos: parse.start,
end: parse.end, end: parse.end,
above: false, above: false,
create: () => domTooltipEntityReference(parse.ref as IEntityReference, cst, colors, canClick) create: () => domTooltipEntityReference(parse.ref as IEntityReference, cst, canClick)
}; };
} else { } else {
let masterText: string | undefined = undefined; let masterText: string | undefined = undefined;
@ -54,6 +53,6 @@ export const tooltipProducer = (schema: IRSForm, colors: IColorTheme, canClick?:
}); });
}; };
export function refsHoverTooltip(schema: IRSForm, colors: IColorTheme, canClick?: boolean): Extension { export function refsHoverTooltip(schema: IRSForm, canClick?: boolean): Extension {
return [tooltipProducer(schema, colors, canClick)]; return [tooltipProducer(schema, canClick)];
} }

View File

@ -1,7 +1,7 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { CstClass, IConstituenta } from '@/models/rsform'; import { CstClass, IConstituenta } from '@/models/rsform';
import { colorFgCstStatus, IColorTheme } from '@/styling/color'; import { APP_COLORS, colorFgCstStatus } from '@/styling/color';
import { CProps } from '../props'; import { CProps } from '../props';
import TooltipConstituenta from './TooltipConstituenta'; import TooltipConstituenta from './TooltipConstituenta';
@ -12,15 +12,12 @@ interface BadgeConstituentaProps extends CProps.Styling {
/** Constituenta to display. */ /** Constituenta to display. */
value: IConstituenta; value: IConstituenta;
/** Color theme to use. */
theme: IColorTheme;
} }
/** /**
* Displays a badge with a constituenta alias and information tooltip. * Displays a badge with a constituenta alias and information tooltip.
*/ */
function BadgeConstituenta({ value, prefixID, className, style, theme }: BadgeConstituentaProps) { function BadgeConstituenta({ value, prefixID, className, style }: BadgeConstituentaProps) {
return ( return (
<div <div
id={`${prefixID}${value.id}`} id={`${prefixID}${value.id}`}
@ -33,9 +30,9 @@ function BadgeConstituenta({ value, prefixID, className, style, theme }: BadgeCo
className className
)} )}
style={{ style={{
borderColor: colorFgCstStatus(value.status, theme), borderColor: colorFgCstStatus(value.status),
color: colorFgCstStatus(value.status, theme), color: colorFgCstStatus(value.status),
backgroundColor: value.cst_class === CstClass.BASIC ? theme.bgGreen25 : theme.bgInput, backgroundColor: value.cst_class === CstClass.BASIC ? APP_COLORS.bgGreen25 : APP_COLORS.bgInput,
...style ...style
}} }}
> >

View File

@ -1,8 +1,7 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { GramData } from '@/models/language'; import { GramData } from '@/models/language';
import { colorFgGrammeme } from '@/styling/color'; import { APP_COLORS, colorFgGrammeme } from '@/styling/color';
import { labelGrammeme } from '@/utils/labels'; import { labelGrammeme } from '@/utils/labels';
interface BadgeGrammemeProps { interface BadgeGrammemeProps {
@ -14,7 +13,6 @@ interface BadgeGrammemeProps {
* Displays a badge with a grammeme tag. * Displays a badge with a grammeme tag.
*/ */
function BadgeGrammeme({ grammeme }: BadgeGrammemeProps) { function BadgeGrammeme({ grammeme }: BadgeGrammemeProps) {
const { colors } = useConceptOptions();
return ( return (
<div <div
className={clsx( className={clsx(
@ -24,9 +22,9 @@ function BadgeGrammeme({ grammeme }: BadgeGrammemeProps) {
'text-sm font-medium text-center whitespace-nowrap' 'text-sm font-medium text-center whitespace-nowrap'
)} )}
style={{ style={{
borderColor: colorFgGrammeme(grammeme, colors), borderColor: colorFgGrammeme(grammeme),
color: colorFgGrammeme(grammeme, colors), color: colorFgGrammeme(grammeme),
backgroundColor: colors.bgInput backgroundColor: APP_COLORS.bgInput
}} }}
> >
{labelGrammeme(grammeme)} {labelGrammeme(grammeme)}

View File

@ -1,6 +1,5 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { CstClass } from '@/models/rsform'; import { CstClass } from '@/models/rsform';
import { colorBgCstClass } from '@/styling/color'; import { colorBgCstClass } from '@/styling/color';
import { prefixes } from '@/utils/constants'; import { prefixes } from '@/utils/constants';
@ -11,8 +10,6 @@ interface InfoCstClassProps {
} }
function InfoCstClass({ header }: InfoCstClassProps) { function InfoCstClass({ header }: InfoCstClassProps) {
const { colors } = useConceptOptions();
return ( return (
<div className='flex flex-col gap-1 mb-2 dense'> <div className='flex flex-col gap-1 mb-2 dense'>
{header ? <h1>{header}</h1> : null} {header ? <h1>{header}</h1> : null}
@ -21,7 +18,7 @@ function InfoCstClass({ header }: InfoCstClassProps) {
<p key={`${prefixes.cst_status_list}${index}`}> <p key={`${prefixes.cst_status_list}${index}`}>
<span <span
className={clsx('inline-block', 'min-w-[7rem]', 'px-1', 'border', 'text-center text-sm font-controls')} className={clsx('inline-block', 'min-w-[7rem]', 'px-1', 'border', 'text-center text-sm font-controls')}
style={{ backgroundColor: colorBgCstClass(cstClass, colors) }} style={{ backgroundColor: colorBgCstClass(cstClass) }}
> >
{labelCstClass(cstClass)} {labelCstClass(cstClass)}
</span> </span>

View File

@ -1,6 +1,5 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { ExpressionStatus } from '@/models/rsform'; import { ExpressionStatus } from '@/models/rsform';
import { colorBgCstStatus } from '@/styling/color'; import { colorBgCstStatus } from '@/styling/color';
import { prefixes } from '@/utils/constants'; import { prefixes } from '@/utils/constants';
@ -11,8 +10,6 @@ interface InfoCstStatusProps {
} }
function InfoCstStatus({ title }: InfoCstStatusProps) { function InfoCstStatus({ title }: InfoCstStatusProps) {
const { colors } = useConceptOptions();
return ( return (
<div className='flex flex-col gap-1 mb-2 dense'> <div className='flex flex-col gap-1 mb-2 dense'>
{title ? <h1>{title}</h1> : null} {title ? <h1>{title}</h1> : null}
@ -28,7 +25,7 @@ function InfoCstStatus({ title }: InfoCstStatusProps) {
'border', 'border',
'text-center text-sm font-controls' 'text-center text-sm font-controls'
)} )}
style={{ backgroundColor: colorBgCstStatus(status, colors) }} style={{ backgroundColor: colorBgCstStatus(status) }}
> >
{labelExpressionStatus(status)} {labelExpressionStatus(status)}
</span> </span>

View File

@ -63,8 +63,7 @@ function InfoError({ error }: InfoErrorProps) {
'cc-fade-in', 'cc-fade-in',
'min-w-[25rem]', 'min-w-[25rem]',
'px-3 py-2 flex flex-col', 'px-3 py-2 flex flex-col',
'clr-text-red', 'text-warn-600 text-sm font-semibold',
'text-sm font-semibold',
'select-text' 'select-text'
)} )}
> >

View File

@ -1,7 +1,6 @@
'use client'; 'use client';
import { createColumnHelper } from '@tanstack/react-table'; import { createColumnHelper } from '@tanstack/react-table';
import { useMemo } from 'react';
import Tooltip from '@/components/ui/Tooltip'; import Tooltip from '@/components/ui/Tooltip';
import { OssNodeInternal } from '@/models/miscellaneous'; import { OssNodeInternal } from '@/models/miscellaneous';
@ -19,8 +18,7 @@ interface TooltipOperationProps {
const columnHelper = createColumnHelper<ICstSubstituteEx>(); const columnHelper = createColumnHelper<ICstSubstituteEx>();
function TooltipOperation({ node, anchor }: TooltipOperationProps) { function TooltipOperation({ node, anchor }: TooltipOperationProps) {
const columns = useMemo( const columns = [
() => [
columnHelper.accessor('substitution_term', { columnHelper.accessor('substitution_term', {
id: 'substitution_term', id: 'substitution_term',
size: 200 size: 200
@ -43,23 +41,7 @@ function TooltipOperation({ node, anchor }: TooltipOperationProps) {
id: 'original_term', id: 'original_term',
size: 200 size: 200
}) })
], ];
[]
);
const table = useMemo(
() => (
<DataTable
dense
noHeader
noFooter
className='text-sm border select-none mb-2'
data={node.data.operation.substitutions}
columns={columns}
/>
),
[columns, node]
);
return ( return (
<Tooltip layer='z-modalTooltip' anchorSelect={anchor} className='max-w-[35rem] max-h-[40rem] dense'> <Tooltip layer='z-modalTooltip' anchorSelect={anchor} className='max-w-[35rem] max-h-[40rem] dense'>
@ -90,7 +72,14 @@ function TooltipOperation({ node, anchor }: TooltipOperationProps) {
</p> </p>
) : null} ) : null}
{node.data.operation.substitutions.length > 0 ? ( {node.data.operation.substitutions.length > 0 ? (
table <DataTable
dense
noHeader
noFooter
className='text-sm border select-none mb-2'
data={node.data.operation.substitutions}
columns={columns}
/>
) : node.data.operation.operation_type !== OperationType.INPUT ? ( ) : node.data.operation.operation_type !== OperationType.INPUT ? (
<p> <p>
<b>Отождествления:</b> Отсутствуют <b>Отождествления:</b> Отсутствуют

View File

@ -1,14 +1,14 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useState } from 'react';
import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/ui/DataTable'; import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/ui/DataTable';
import SearchBar from '@/components/ui/SearchBar'; import SearchBar from '@/components/ui/SearchBar';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { CstMatchMode } from '@/models/miscellaneous'; import { CstMatchMode } from '@/models/miscellaneous';
import { IConstituenta } from '@/models/rsform'; import { IConstituenta } from '@/models/rsform';
import { matchConstituenta } from '@/models/rsformAPI'; import { matchConstituenta } from '@/models/rsformAPI';
import { APP_COLORS } from '@/styling/color';
import { prefixes } from '@/utils/constants'; import { prefixes } from '@/utils/constants';
import { describeConstituenta } from '@/utils/labels'; import { describeConstituenta } from '@/utils/labels';
@ -47,7 +47,6 @@ function PickConstituenta({
className, className,
...restProps ...restProps
}: PickConstituentaProps) { }: PickConstituentaProps) {
const { colors } = useConceptOptions();
const [filteredData, setFilteredData] = useState<IConstituenta[]>([]); const [filteredData, setFilteredData] = useState<IConstituenta[]>([]);
const [filterText, setFilterText] = useState(initialFilter); const [filterText, setFilterText] = useState(initialFilter);
@ -64,33 +63,27 @@ function PickConstituenta({
} }
}, [data, filterText, matchFunc, onBeginFilter]); }, [data, filterText, matchFunc, onBeginFilter]);
const columns = useMemo( const columns = [
() => [
columnHelper.accessor('alias', { columnHelper.accessor('alias', {
id: 'alias', id: 'alias',
size: 65, size: 65,
minSize: 65, minSize: 65,
maxSize: 65, maxSize: 65,
cell: props => <BadgeConstituenta theme={colors} value={props.row.original} prefixID={prefixID} /> cell: props => <BadgeConstituenta value={props.row.original} prefixID={prefixID} />
}), }),
columnHelper.accessor(cst => describeFunc(cst), { columnHelper.accessor(cst => describeFunc(cst), {
id: 'description', id: 'description',
size: 1000, size: 1000,
minSize: 1000 minSize: 1000
}) })
], ];
[colors, prefixID, describeFunc]
);
const conditionalRowStyles = useMemo( const conditionalRowStyles: IConditionalStyle<IConstituenta>[] = [
(): IConditionalStyle<IConstituenta>[] => [
{ {
when: (cst: IConstituenta) => cst.id === value?.id, when: (cst: IConstituenta) => cst.id === value?.id,
style: { backgroundColor: colors.bgSelected } style: { backgroundColor: APP_COLORS.bgSelected }
} }
], ];
[value, colors]
);
return ( return (
<div className={clsx('border divide-y', className)} {...restProps}> <div className={clsx('border divide-y', className)} {...restProps}>

View File

@ -1,10 +1,9 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useLayoutEffect, useMemo, useState } from 'react'; import { useEffect, useState } from 'react';
import DataTable, { createColumnHelper, RowSelectionState } from '@/components/ui/DataTable'; import DataTable, { createColumnHelper, RowSelectionState } from '@/components/ui/DataTable';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { Graph } from '@/models/Graph'; import { Graph } from '@/models/Graph';
import { CstMatchMode } from '@/models/miscellaneous'; import { CstMatchMode } from '@/models/miscellaneous';
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform'; import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
@ -44,12 +43,12 @@ function PickMultiConstituenta({
className, className,
...restProps ...restProps
}: PickMultiConstituentaProps) { }: PickMultiConstituentaProps) {
const { colors } = useConceptOptions();
const [rowSelection, setRowSelection] = useState<RowSelectionState>({}); const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [filtered, setFiltered] = useState<IConstituenta[]>(data); const [filtered, setFiltered] = useState<IConstituenta[]>(data);
const [filterText, setFilterText] = useState(''); const [filterText, setFilterText] = useState('');
const foldedGraph = useMemo(() => { // TODO: extract graph fold logic to separate function
const foldedGraph = (() => {
if (data.length === schema.items.length) { if (data.length === schema.items.length) {
return schema.graph; return schema.graph;
} }
@ -66,9 +65,9 @@ function PickMultiConstituenta({
newGraph.foldNode(item.id); newGraph.foldNode(item.id);
}); });
return newGraph; return newGraph;
}, [data, schema.graph, schema.items]); })();
useLayoutEffect(() => { useEffect(() => {
if (filtered.length === 0) { if (filtered.length === 0) {
setRowSelection({}); setRowSelection({});
return; return;
@ -80,7 +79,7 @@ function PickMultiConstituenta({
setRowSelection(newRowSelection); setRowSelection(newRowSelection);
}, [filtered, setRowSelection, selected]); }, [filtered, setRowSelection, selected]);
useLayoutEffect(() => { useEffect(() => {
if (data.length === 0) { if (data.length === 0) {
setFiltered([]); setFiltered([]);
} else if (filterText) { } else if (filterText) {
@ -105,22 +104,19 @@ function PickMultiConstituenta({
} }
} }
const columns = useMemo( const columns = [
() => [
columnHelper.accessor('alias', { columnHelper.accessor('alias', {
id: 'alias', id: 'alias',
header: () => <span className='pl-3'>Имя</span>, header: () => <span className='pl-3'>Имя</span>,
size: 65, size: 65,
cell: props => <BadgeConstituenta theme={colors} value={props.row.original} prefixID={prefixID} /> cell: props => <BadgeConstituenta value={props.row.original} prefixID={prefixID} />
}), }),
columnHelper.accessor(cst => describeConstituenta(cst), { columnHelper.accessor(cst => describeConstituenta(cst), {
id: 'description', id: 'description',
size: 1000, size: 1000,
header: 'Описание' header: 'Описание'
}) })
], ];
[colors, prefixID]
);
return ( return (
<div className={clsx(noBorder ? '' : 'border', className)} {...restProps}> <div className={clsx(noBorder ? '' : 'border', className)} {...restProps}>

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useMemo, useState } from 'react'; import { useState } from 'react';
import { IconMoveDown, IconMoveUp, IconRemove } from '@/components/Icons'; import { IconMoveDown, IconMoveUp, IconRemove } from '@/components/Icons';
import SelectOperation from '@/components/select/SelectOperation'; import SelectOperation from '@/components/select/SelectOperation';
@ -23,31 +23,23 @@ interface PickMultiOperationProps extends CProps.Styling {
const columnHelper = createColumnHelper<IOperation>(); const columnHelper = createColumnHelper<IOperation>();
function PickMultiOperation({ rows, items, selected, setSelected, className, ...restProps }: PickMultiOperationProps) { function PickMultiOperation({ rows, items, selected, setSelected, className, ...restProps }: PickMultiOperationProps) {
const selectedItems = useMemo( const selectedItems = selected.map(itemID => items.find(item => item.id === itemID)!);
() => selected.map(itemID => items.find(item => item.id === itemID)!), const nonSelectedItems = items.filter(item => !selected.includes(item.id));
[items, selected]
);
const nonSelectedItems = useMemo(() => items.filter(item => !selected.includes(item.id)), [items, selected]);
const [lastSelected, setLastSelected] = useState<IOperation | undefined>(undefined); const [lastSelected, setLastSelected] = useState<IOperation | undefined>(undefined);
const handleDelete = useCallback( function handleDelete(operation: OperationID) {
(operation: OperationID) => setSelected(prev => prev.filter(item => item !== operation)), setSelected(prev => prev.filter(item => item !== operation));
[setSelected] }
);
const handleSelect = useCallback( function handleSelect(operation?: IOperation) {
(operation?: IOperation) => {
if (operation) { if (operation) {
setLastSelected(operation); setLastSelected(operation);
setSelected(prev => [...prev, operation.id]); setSelected(prev => [...prev, operation.id]);
setTimeout(() => setLastSelected(undefined), 1000); setTimeout(() => setLastSelected(undefined), 1000);
} }
}, }
[setSelected]
);
const handleMoveUp = useCallback( function handleMoveUp(operation: OperationID) {
(operation: OperationID) => {
const index = selected.indexOf(operation); const index = selected.indexOf(operation);
if (index > 0) { if (index > 0) {
setSelected(prev => { setSelected(prev => {
@ -57,12 +49,9 @@ function PickMultiOperation({ rows, items, selected, setSelected, className, ...
return newSelected; return newSelected;
}); });
} }
}, }
[setSelected, selected]
);
const handleMoveDown = useCallback( function handleMoveDown(operation: OperationID) {
(operation: OperationID) => {
const index = selected.indexOf(operation); const index = selected.indexOf(operation);
if (index < selected.length - 1) { if (index < selected.length - 1) {
setSelected(prev => { setSelected(prev => {
@ -72,12 +61,9 @@ function PickMultiOperation({ rows, items, selected, setSelected, className, ...
return newSelected; return newSelected;
}); });
} }
}, }
[setSelected, selected]
);
const columns = useMemo( const columns = [
() => [
columnHelper.accessor('alias', { columnHelper.accessor('alias', {
id: 'alias', id: 'alias',
header: 'Шифр', header: 'Шифр',
@ -122,9 +108,7 @@ function PickMultiOperation({ rows, items, selected, setSelected, className, ...
</div> </div>
) )
}) })
], ];
[handleDelete, handleMoveUp, handleMoveDown]
);
return ( return (
<div <div

View File

@ -1,14 +1,14 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; import { useEffect, useState } from 'react';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/ui/DataTable'; import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/ui/DataTable';
import SearchBar from '@/components/ui/SearchBar'; import SearchBar from '@/components/ui/SearchBar';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { useLibrary } from '@/context/LibraryContext'; import { useLibrary } from '@/context/LibraryContext';
import useDropdown from '@/hooks/useDropdown'; import useDropdown from '@/hooks/useDropdown';
import { ILibraryItem, LibraryItemID, LibraryItemType } from '@/models/library'; import { ILibraryItem, LibraryItemID, LibraryItemType } from '@/models/library';
import { matchLibraryItem } from '@/models/libraryAPI'; import { matchLibraryItem } from '@/models/libraryAPI';
import { APP_COLORS } from '@/styling/color';
import { prefixes } from '@/utils/constants'; import { prefixes } from '@/utils/constants';
import { IconClose, IconFolderTree } from '../Icons'; import { IconClose, IconFolderTree } from '../Icons';
@ -45,20 +45,16 @@ function PickSchema({
...restProps ...restProps
}: PickSchemaProps) { }: PickSchemaProps) {
const intl = useIntl(); const intl = useIntl();
const { colors } = useConceptOptions();
const { folders } = useLibrary(); const { folders } = useLibrary();
const [filterText, setFilterText] = useState(initialFilter); const [filterText, setFilterText] = useState(initialFilter);
const [filterLocation, setFilterLocation] = useState(''); const [filterLocation, setFilterLocation] = useState('');
const [filtered, setFiltered] = useState<ILibraryItem[]>([]); const [filtered, setFiltered] = useState<ILibraryItem[]>([]);
const baseFiltered = useMemo( const baseFiltered = items.filter(item => item.item_type === itemType && (!baseFilter || baseFilter(item)));
() => items.filter(item => item.item_type === itemType && (!baseFilter || baseFilter(item))),
[items, itemType, baseFilter]
);
const locationMenu = useDropdown(); const locationMenu = useDropdown();
useLayoutEffect(() => { useEffect(() => {
let newFiltered = baseFiltered.filter(item => matchLibraryItem(item, filterText)); let newFiltered = baseFiltered.filter(item => matchLibraryItem(item, filterText));
if (filterLocation.length > 0) { if (filterLocation.length > 0) {
newFiltered = newFiltered.filter( newFiltered = newFiltered.filter(
@ -68,8 +64,7 @@ function PickSchema({
setFiltered(newFiltered); setFiltered(newFiltered);
}, [filterText, filterLocation, baseFiltered]); }, [filterText, filterLocation, baseFiltered]);
const columns = useMemo( const columns = [
() => [
columnHelper.accessor('alias', { columnHelper.accessor('alias', {
id: 'alias', id: 'alias',
header: 'Шифр', header: 'Шифр',
@ -98,29 +93,21 @@ function PickSchema({
</div> </div>
) )
}) })
], ];
[intl]
);
const conditionalRowStyles = useMemo( const conditionalRowStyles: IConditionalStyle<ILibraryItem>[] = [
(): IConditionalStyle<ILibraryItem>[] => [
{ {
when: (item: ILibraryItem) => item.id === value, when: (item: ILibraryItem) => item.id === value,
style: { backgroundColor: colors.bgSelected } style: { backgroundColor: APP_COLORS.bgSelected }
} }
], ];
[value, colors]
);
const handleLocationClick = useCallback( function handleLocationClick(event: CProps.EventMouse, newValue: string) {
(event: CProps.EventMouse, newValue: string) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
locationMenu.hide(); locationMenu.hide();
setFilterLocation(newValue); setFilterLocation(newValue);
}, }
[locationMenu]
);
return ( return (
<div className={clsx('border divide-y', className)} {...restProps}> <div className={clsx('border divide-y', className)} {...restProps}>

View File

@ -1,17 +1,17 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useMemo, useState } from 'react'; import { useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import BadgeConstituenta from '@/components/info/BadgeConstituenta'; import BadgeConstituenta from '@/components/info/BadgeConstituenta';
import SelectConstituenta from '@/components/select/SelectConstituenta'; import SelectConstituenta from '@/components/select/SelectConstituenta';
import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/ui/DataTable'; import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/ui/DataTable';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { ILibraryItem } from '@/models/library'; import { ILibraryItem } from '@/models/library';
import { ICstSubstitute, IMultiSubstitution } from '@/models/oss'; import { ICstSubstitute, IMultiSubstitution } from '@/models/oss';
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform'; import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
import { APP_COLORS } from '@/styling/color';
import { errors } from '@/utils/labels'; import { errors } from '@/utils/labels';
import { IconAccept, IconPageLeft, IconPageRight, IconRemove, IconReplace } from '../Icons'; import { IconAccept, IconPageLeft, IconPageRight, IconRemove, IconReplace } from '../Icons';
@ -46,8 +46,6 @@ function PickSubstitutions({
className, className,
...restProps ...restProps
}: PickSubstitutionsProps) { }: PickSubstitutionsProps) {
const { colors } = useConceptOptions();
const [leftArgument, setLeftArgument] = useState<ILibraryItem | undefined>( const [leftArgument, setLeftArgument] = useState<ILibraryItem | undefined>(
schemas.length === 1 ? schemas[0] : undefined schemas.length === 1 ? schemas[0] : undefined
); );
@ -62,42 +60,12 @@ function PickSubstitutions({
const toggleDelete = () => setDeleteRight(prev => !prev); const toggleDelete = () => setDeleteRight(prev => !prev);
const [ignores, setIgnores] = useState<ICstSubstitute[]>([]); const [ignores, setIgnores] = useState<ICstSubstitute[]>([]);
const filteredSuggestions = useMemo( const filteredSuggestions =
() =>
suggestions?.filter( suggestions?.filter(
item => !ignores.find(ignore => ignore.original === item.original && ignore.substitution === item.substitution) item => !ignores.find(ignore => ignore.original === item.original && ignore.substitution === item.substitution)
) ?? [], ) ?? [];
[ignores, suggestions]
);
const getSchemaByCst = useCallback( const substitutionData: IMultiSubstitution[] = [
(id: ConstituentaID): IRSForm | undefined => {
for (const schema of schemas) {
const cst = schema.cstByID.get(id);
if (cst) {
return schema;
}
}
return undefined;
},
[schemas]
);
const getConstituenta = useCallback(
(id: ConstituentaID): IConstituenta | undefined => {
for (const schema of schemas) {
const cst = schema.cstByID.get(id);
if (cst) {
return cst;
}
}
return undefined;
},
[schemas]
);
const substitutionData: IMultiSubstitution[] = useMemo(
() => [
...substitutions.map(item => ({ ...substitutions.map(item => ({
original_source: getSchemaByCst(item.original)!, original_source: getSchemaByCst(item.original)!,
original: getConstituenta(item.original)!, original: getConstituenta(item.original)!,
@ -112,9 +80,27 @@ function PickSubstitutions({
substitution_source: getSchemaByCst(item.substitution)!, substitution_source: getSchemaByCst(item.substitution)!,
is_suggestion: true is_suggestion: true
})) }))
], ];
[getConstituenta, getSchemaByCst, substitutions, filteredSuggestions]
); function getSchemaByCst(id: ConstituentaID): IRSForm | undefined {
for (const schema of schemas) {
const cst = schema.cstByID.get(id);
if (cst) {
return schema;
}
}
return undefined;
}
function getConstituenta(id: ConstituentaID): IConstituenta | undefined {
for (const schema of schemas) {
const cst = schema.cstByID.get(id);
if (cst) {
return cst;
}
}
return undefined;
}
function addSubstitution() { function addSubstitution() {
if (!leftCst || !rightCst) { if (!leftCst || !rightCst) {
@ -145,22 +131,15 @@ function PickSubstitutions({
setRightCst(undefined); setRightCst(undefined);
} }
const handleDeclineSuggestion = useCallback( function handleDeclineSuggestion(item: IMultiSubstitution) {
(item: IMultiSubstitution) => {
setIgnores(prev => [...prev, { original: item.original.id, substitution: item.substitution.id }]); setIgnores(prev => [...prev, { original: item.original.id, substitution: item.substitution.id }]);
}, }
[setIgnores]
);
const handleAcceptSuggestion = useCallback( function handleAcceptSuggestion(item: IMultiSubstitution) {
(item: IMultiSubstitution) => {
setSubstitutions(prev => [...prev, { original: item.original.id, substitution: item.substitution.id }]); setSubstitutions(prev => [...prev, { original: item.original.id, substitution: item.substitution.id }]);
}, }
[setSubstitutions]
);
const handleDeleteSubstitution = useCallback( function handleDeleteSubstitution(target: IMultiSubstitution) {
(target: IMultiSubstitution) => {
handleDeclineSuggestion(target); handleDeclineSuggestion(target);
setSubstitutions(prev => { setSubstitutions(prev => {
const newItems: ICstSubstitute[] = []; const newItems: ICstSubstitute[] = [];
@ -171,12 +150,9 @@ function PickSubstitutions({
}); });
return newItems; return newItems;
}); });
}, }
[setSubstitutions, handleDeclineSuggestion]
);
const columns = useMemo( const columns = [
() => [
columnHelper.accessor(item => item.substitution_source.alias, { columnHelper.accessor(item => item.substitution_source.alias, {
id: 'left_schema', id: 'left_schema',
size: 100, size: 100,
@ -186,11 +162,7 @@ function PickSubstitutions({
id: 'left_alias', id: 'left_alias',
size: 65, size: 65,
cell: props => ( cell: props => (
<BadgeConstituenta <BadgeConstituenta value={props.row.original.substitution} prefixID={`${prefixID}_${props.row.index}_1_`} />
theme={colors}
value={props.row.original.substitution}
prefixID={`${prefixID}_${props.row.index}_1_`}
/>
) )
}), }),
columnHelper.display({ columnHelper.display({
@ -202,11 +174,7 @@ function PickSubstitutions({
id: 'right_alias', id: 'right_alias',
size: 65, size: 65,
cell: props => ( cell: props => (
<BadgeConstituenta <BadgeConstituenta value={props.row.original.original} prefixID={`${prefixID}_${props.row.index}_2_`} />
theme={colors}
value={props.row.original.original}
prefixID={`${prefixID}_${props.row.index}_2_`}
/>
) )
}), }),
columnHelper.accessor(item => item.original_source.alias, { columnHelper.accessor(item => item.original_source.alias, {
@ -244,21 +212,14 @@ function PickSubstitutions({
</div> </div>
) )
}) })
], ];
[handleDeleteSubstitution, handleDeclineSuggestion, handleAcceptSuggestion, colors, prefixID]
);
const conditionalRowStyles = useMemo( const conditionalRowStyles: IConditionalStyle<IMultiSubstitution>[] = [
(): IConditionalStyle<IMultiSubstitution>[] => [
{ {
when: (item: IMultiSubstitution) => item.is_suggestion, when: (item: IMultiSubstitution) => item.is_suggestion,
style: { style: { backgroundColor: APP_COLORS.bgOrange50 }
backgroundColor: colors.bgOrange50
} }
} ];
],
[colors]
);
return ( return (
<div className={clsx('flex flex-col', className)} {...restProps}> <div className={clsx('flex flex-col', className)} {...restProps}>
@ -286,9 +247,9 @@ function PickSubstitutions({
onClick={toggleDelete} onClick={toggleDelete}
icon={ icon={
deleteRight ? ( deleteRight ? (
<IconPageRight size='1.5rem' className='clr-text-primary' /> <IconPageRight size='1.5rem' className='text-sec-600' />
) : ( ) : (
<IconPageLeft size='1.5rem' className='clr-text-primary' /> <IconPageLeft size='1.5rem' className='text-sec-600' />
) )
} }
/> />

View File

@ -1,7 +1,6 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useMemo } from 'react';
import { CstMatchMode } from '@/models/miscellaneous'; import { CstMatchMode } from '@/models/miscellaneous';
import { ConstituentaID, IConstituenta } from '@/models/rsform'; import { ConstituentaID, IConstituenta } from '@/models/rsform';
@ -28,22 +27,16 @@ function SelectConstituenta({
placeholder = 'Выберите конституенту', placeholder = 'Выберите конституенту',
...restProps ...restProps
}: SelectConstituentaProps) { }: SelectConstituentaProps) {
const options = useMemo(() => { const options =
return (
items?.map(cst => ({ items?.map(cst => ({
value: cst.id, value: cst.id,
label: `${cst.alias}${cst.is_inherited ? '*' : ''}: ${describeConstituenta(cst)}` label: `${cst.alias}${cst.is_inherited ? '*' : ''}: ${describeConstituenta(cst)}`
})) ?? [] })) ?? [];
);
}, [items]);
const filter = useCallback( function filter(option: { value: ConstituentaID | undefined; label: string }, inputValue: string) {
(option: { value: ConstituentaID | undefined; label: string }, inputValue: string) => {
const cst = items?.find(item => item.id === option.value); const cst = items?.find(item => item.id === option.value);
return !cst ? false : matchConstituenta(cst, inputValue, CstMatchMode.ALL); return !cst ? false : matchConstituenta(cst, inputValue, CstMatchMode.ALL);
}, }
[items]
);
return ( return (
<SelectSingle <SelectSingle

View File

@ -1,7 +1,6 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useMemo } from 'react';
import { ILibraryItem, LibraryItemID } from '@/models/library'; import { ILibraryItem, LibraryItemID } from '@/models/library';
import { matchLibraryItem } from '@/models/libraryAPI'; import { matchLibraryItem } from '@/models/libraryAPI';
@ -26,22 +25,16 @@ function SelectLibraryItem({
placeholder = 'Выберите схему', placeholder = 'Выберите схему',
...restProps ...restProps
}: SelectLibraryItemProps) { }: SelectLibraryItemProps) {
const options = useMemo(() => { const options =
return (
items?.map(cst => ({ items?.map(cst => ({
value: cst.id, value: cst.id,
label: `${cst.alias}: ${cst.title}` label: `${cst.alias}: ${cst.title}`
})) ?? [] })) ?? [];
);
}, [items]);
const filter = useCallback( function filter(option: { value: LibraryItemID | undefined; label: string }, inputValue: string) {
(option: { value: LibraryItemID | undefined; label: string }, inputValue: string) => {
const item = items?.find(item => item.id === option.value); const item = items?.find(item => item.id === option.value);
return !item ? false : matchLibraryItem(item, inputValue); return !item ? false : matchLibraryItem(item, inputValue);
}, }
[items]
);
return ( return (
<SelectSingle <SelectSingle

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; import { useEffect, useState } from 'react';
import { FolderNode, FolderTree } from '@/models/FolderTree'; import { FolderNode, FolderTree } from '@/models/FolderTree';
import { labelFolderNode } from '@/utils/labels'; import { labelFolderNode } from '@/utils/labels';
@ -19,17 +19,15 @@ interface SelectLocationProps extends CProps.Styling {
} }
function SelectLocation({ value, folderTree, dense, prefix, onClick, className, style }: SelectLocationProps) { function SelectLocation({ value, folderTree, dense, prefix, onClick, className, style }: SelectLocationProps) {
const activeNode = useMemo(() => folderTree.at(value), [folderTree, value]); const activeNode = folderTree.at(value);
const items = folderTree.getTree();
const items = useMemo(() => folderTree.getTree(), [folderTree]);
const [folded, setFolded] = useState<FolderNode[]>(items); const [folded, setFolded] = useState<FolderNode[]>(items);
useLayoutEffect(() => { useEffect(() => {
setFolded(items.filter(item => item !== activeNode && !activeNode?.hasPredecessor(item))); setFolded(items.filter(item => item !== activeNode && !activeNode?.hasPredecessor(item)));
}, [items, activeNode]); }, [items, activeNode]);
const onFoldItem = useCallback( function onFoldItem(target: FolderNode, showChildren: boolean) {
(target: FolderNode, showChildren: boolean) => {
setFolded(prev => setFolded(prev =>
items.filter(item => { items.filter(item => {
if (item === target) { if (item === target) {
@ -42,18 +40,13 @@ function SelectLocation({ value, folderTree, dense, prefix, onClick, className,
} }
}) })
); );
}, }
[items]
);
const handleClickFold = useCallback( function handleClickFold(event: CProps.EventMouse, target: FolderNode, showChildren: boolean) {
(event: CProps.EventMouse, target: FolderNode, showChildren: boolean) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
onFoldItem(target, showChildren); onFoldItem(target, showChildren);
}, }
[onFoldItem]
);
return ( return (
<div className={clsx('flex flex-col', 'cc-scroll-y', className)} style={style}> <div className={clsx('flex flex-col', 'cc-scroll-y', className)} style={style}>
@ -66,7 +59,7 @@ function SelectLocation({ value, folderTree, dense, prefix, onClick, className,
!dense && 'min-h-[2.0825rem] sm:min-h-[2.3125rem]', !dense && 'min-h-[2.0825rem] sm:min-h-[2.3125rem]',
'pr-3 py-1 flex items-center gap-2', 'pr-3 py-1 flex items-center gap-2',
'cc-scroll-row', 'cc-scroll-row',
'clr-hover', 'clr-hover cc-animate-color',
'cursor-pointer', 'cursor-pointer',
'leading-3 sm:leading-4', 'leading-3 sm:leading-4',
activeNode === item && 'clr-selected' activeNode === item && 'clr-selected'

View File

@ -1,7 +1,6 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useMemo } from 'react';
import { IOperation, OperationID } from '@/models/oss'; import { IOperation, OperationID } from '@/models/oss';
import { matchOperation } from '@/models/ossAPI'; import { matchOperation } from '@/models/ossAPI';
@ -26,22 +25,16 @@ function SelectOperation({
placeholder = 'Выберите операцию', placeholder = 'Выберите операцию',
...restProps ...restProps
}: SelectOperationProps) { }: SelectOperationProps) {
const options = useMemo(() => { const options =
return (
items?.map(cst => ({ items?.map(cst => ({
value: cst.id, value: cst.id,
label: `${cst.alias}: ${cst.title}` label: `${cst.alias}: ${cst.title}`
})) ?? [] })) ?? [];
);
}, [items]);
const filter = useCallback( function filter(option: { value: OperationID | undefined; label: string }, inputValue: string) {
(option: { value: OperationID | undefined; label: string }, inputValue: string) => {
const operation = items?.find(item => item.id === option.value); const operation = items?.find(item => item.id === option.value);
return !operation ? false : matchOperation(operation, inputValue); return !operation ? false : matchOperation(operation, inputValue);
}, }
[items]
);
return ( return (
<SelectSingle <SelectSingle

View File

@ -1,7 +1,6 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useMemo } from 'react';
import { useUsers } from '@/context/UsersContext'; import { useUsers } from '@/context/UsersContext';
import { IUserInfo, UserID } from '@/models/user'; import { IUserInfo, UserID } from '@/models/user';
@ -28,22 +27,16 @@ function SelectUser({
...restProps ...restProps
}: SelectUserProps) { }: SelectUserProps) {
const { getUserLabel } = useUsers(); const { getUserLabel } = useUsers();
const options = useMemo(() => { const options =
return (
items?.map(user => ({ items?.map(user => ({
value: user.id, value: user.id,
label: getUserLabel(user.id) label: getUserLabel(user.id)
})) ?? [] })) ?? [];
);
}, [items, getUserLabel]);
const filter = useCallback( function filter(option: { value: UserID | undefined; label: string }, inputValue: string) {
(option: { value: UserID | undefined; label: string }, inputValue: string) => {
const user = items?.find(item => item.id === option.value); const user = items?.find(item => item.id === option.value);
return !user ? false : matchUser(user, inputValue); return !user ? false : matchUser(user, inputValue);
}, }
[items]
);
return ( return (
<SelectSingle <SelectSingle

View File

@ -1,7 +1,6 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useMemo } from 'react';
import { IVersionInfo, VersionID } from '@/models/library'; import { IVersionInfo, VersionID } from '@/models/library';
import { labelVersion } from '@/utils/labels'; import { labelVersion } from '@/utils/labels';
@ -20,8 +19,7 @@ interface SelectVersionProps extends CProps.Styling {
} }
function SelectVersion({ id, className, items, value, onSelectValue, ...restProps }: SelectVersionProps) { function SelectVersion({ id, className, items, value, onSelectValue, ...restProps }: SelectVersionProps) {
const options = useMemo(() => { const options = [
return [
{ {
value: undefined, value: undefined,
label: labelVersion(undefined) label: labelVersion(undefined)
@ -31,11 +29,11 @@ function SelectVersion({ id, className, items, value, onSelectValue, ...restProp
label: version.version label: version.version
})) ?? []) })) ?? [])
]; ];
}, [items]);
const valueLabel = useMemo(() => { const valueLabel = (() => {
const version = items?.find(ver => ver.id === value); const version = items?.find(ver => ver.id === value);
return version ? version.version : labelVersion(undefined); return version ? version.version : labelVersion(undefined);
}, [items, value]); })();
return ( return (
<SelectSingle <SelectSingle

View File

@ -43,6 +43,7 @@ function Button({
className={clsx( className={clsx(
'inline-flex gap-2 items-center justify-center', 'inline-flex gap-2 items-center justify-center',
'select-none disabled:cursor-auto', 'select-none disabled:cursor-auto',
'cc-animate-color',
{ {
'border rounded': !noBorder, 'border rounded': !noBorder,
'px-1': dense, 'px-1': dense,

View File

@ -1,5 +1,4 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { useMemo } from 'react';
import { globals } from '@/utils/constants'; import { globals } from '@/utils/constants';
@ -34,15 +33,7 @@ function Checkbox({
setValue, setValue,
...restProps ...restProps
}: CheckboxProps) { }: CheckboxProps) {
const cursor = useMemo(() => { const cursor = disabled ? 'cursor-arrow' : setValue ? 'cursor-pointer' : '';
if (disabled) {
return 'cursor-arrow';
} else if (setValue) {
return 'cursor-pointer';
} else {
return '';
}
}, [disabled, setValue]);
function handleClick(event: CProps.EventMouse): void { function handleClick(event: CProps.EventMouse): void {
event.preventDefault(); event.preventDefault();
@ -75,9 +66,10 @@ function Checkbox({
className={clsx( className={clsx(
'max-w-[1rem] min-w-[1rem] h-4', // prettier: split lines 'max-w-[1rem] min-w-[1rem] h-4', // prettier: split lines
'border rounded-sm ', 'border rounded-sm ',
'cc-animate-color',
{ {
'clr-primary': value !== false, 'bg-sec-600 text-sec-0': value !== false,
'clr-app': value === false 'bg-prim-100': value === false
} }
)} )}
> >

View File

@ -1,5 +1,4 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { useMemo } from 'react';
import { globals } from '@/utils/constants'; import { globals } from '@/utils/constants';
@ -29,15 +28,7 @@ function CheckboxTristate({
setValue, setValue,
...restProps ...restProps
}: CheckboxTristateProps) { }: CheckboxTristateProps) {
const cursor = useMemo(() => { const cursor = disabled ? 'cursor-arrow' : setValue ? 'cursor-pointer' : '';
if (disabled) {
return 'cursor-arrow';
} else if (setValue) {
return 'cursor-pointer';
} else {
return '';
}
}, [disabled, setValue]);
function handleClick(event: CProps.EventMouse): void { function handleClick(event: CProps.EventMouse): void {
event.preventDefault(); event.preventDefault();
@ -76,9 +67,10 @@ function CheckboxTristate({
className={clsx( className={clsx(
'w-4 h-4', // prettier: split lines 'w-4 h-4', // prettier: split lines
'border rounded-sm', 'border rounded-sm',
'cc-animate-color',
{ {
'clr-primary': value !== false, 'bg-sec-600 text-sec-0': value !== false,
'clr-app': value === false 'bg-prim-100': value === false
} }
)} )}
> >

View File

@ -1,4 +1,5 @@
'use client'; 'use client';
'use no memo';
import { import {
ColumnSort, ColumnSort,

View File

@ -1,4 +1,5 @@
'use client'; 'use client';
'use no memo';
import { Table } from '@tanstack/react-table'; import { Table } from '@tanstack/react-table';
import clsx from 'clsx'; import clsx from 'clsx';
@ -46,7 +47,7 @@ function PaginationTools<TData>({
<div className='flex'> <div className='flex'>
<button <button
type='button' type='button'
className='clr-hover clr-text-controls' className='clr-hover clr-text-controls cc-animate-color'
onClick={() => table.setPageIndex(0)} onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()} disabled={!table.getCanPreviousPage()}
> >
@ -54,7 +55,7 @@ function PaginationTools<TData>({
</button> </button>
<button <button
type='button' type='button'
className='clr-hover clr-text-controls' className='clr-hover clr-text-controls cc-animate-color'
onClick={() => table.previousPage()} onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()} disabled={!table.getCanPreviousPage()}
> >
@ -63,7 +64,7 @@ function PaginationTools<TData>({
<input <input
id={id ? `${id}__page` : undefined} id={id ? `${id}__page` : undefined}
title='Номер страницы. Выделите для ручного ввода' title='Номер страницы. Выделите для ручного ввода'
className='w-6 text-center clr-app' className='w-6 text-center bg-prim-100'
value={table.getState().pagination.pageIndex + 1} value={table.getState().pagination.pageIndex + 1}
onChange={event => { onChange={event => {
const page = event.target.value ? Number(event.target.value) - 1 : 0; const page = event.target.value ? Number(event.target.value) - 1 : 0;
@ -74,7 +75,7 @@ function PaginationTools<TData>({
/> />
<button <button
type='button' type='button'
className='clr-hover clr-text-controls' className='clr-hover clr-text-controls cc-animate-color'
onClick={() => table.nextPage()} onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()} disabled={!table.getCanNextPage()}
> >
@ -82,7 +83,7 @@ function PaginationTools<TData>({
</button> </button>
<button <button
type='button' type='button'
className='clr-hover clr-text-controls' className='clr-hover clr-text-controls cc-animate-color'
onClick={() => table.setPageIndex(table.getPageCount() - 1)} onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()} disabled={!table.getCanNextPage()}
> >
@ -93,7 +94,7 @@ function PaginationTools<TData>({
id={id ? `${id}__per_page` : undefined} id={id ? `${id}__per_page` : undefined}
value={table.getState().pagination.pageSize} value={table.getState().pagination.pageSize}
onChange={handlePaginationOptionsChange} onChange={handlePaginationOptionsChange}
className='mx-2 cursor-pointer clr-app' className='mx-2 cursor-pointer bg-prim-100'
> >
{paginationOptions.map(pageSize => ( {paginationOptions.map(pageSize => (
<option key={`${prefixes.page_size}${pageSize}`} value={pageSize}> <option key={`${prefixes.page_size}${pageSize}`} value={pageSize}>

View File

@ -1,3 +1,5 @@
'use no memo';
import { Table } from '@tanstack/react-table'; import { Table } from '@tanstack/react-table';
import CheckboxTristate from '@/components/ui/CheckboxTristate'; import CheckboxTristate from '@/components/ui/CheckboxTristate';

View File

@ -1,3 +1,5 @@
'use no memo';
import { Row } from '@tanstack/react-table'; import { Row } from '@tanstack/react-table';
import Checkbox from '@/components/ui/Checkbox'; import Checkbox from '@/components/ui/Checkbox';

View File

@ -1,3 +1,5 @@
'use no memo';
import { Column } from '@tanstack/react-table'; import { Column } from '@tanstack/react-table';
import { IconSortAsc, IconSortDesc } from '@/components/Icons'; import { IconSortAsc, IconSortDesc } from '@/components/Icons';

View File

@ -1,3 +1,5 @@
'use no memo';
import { Cell, flexRender, Row, Table } from '@tanstack/react-table'; import { Cell, flexRender, Row, Table } from '@tanstack/react-table';
import clsx from 'clsx'; import clsx from 'clsx';
@ -70,12 +72,9 @@ function TableBody<TData>({
key={row.id} key={row.id}
className={clsx( className={clsx(
'cc-scroll-row', 'cc-scroll-row',
'clr-hover cc-animate-color',
!noHeader && 'scroll-mt-[calc(2px+2rem)]', !noHeader && 'scroll-mt-[calc(2px+2rem)]',
row.getIsSelected() row.getIsSelected() ? 'clr-selected' : index % 2 === 0 ? 'bg-prim-200' : 'bg-prim-100'
? 'clr-selected clr-hover'
: index % 2 === 0
? 'clr-controls clr-hover'
: 'clr-app clr-hover'
)} )}
style={{ ...(conditionalRowStyles ? getRowStyles(row) : []) }} style={{ ...(conditionalRowStyles ? getRowStyles(row) : []) }}
> >

View File

@ -1,3 +1,5 @@
'use no memo';
import { flexRender, Header, HeaderGroup, Table } from '@tanstack/react-table'; import { flexRender, Header, HeaderGroup, Table } from '@tanstack/react-table';
interface TableFooterProps<TData> { interface TableFooterProps<TData> {

View File

@ -1,3 +1,5 @@
'use no memo';
import { flexRender, Header, HeaderGroup, Table } from '@tanstack/react-table'; import { flexRender, Header, HeaderGroup, Table } from '@tanstack/react-table';
import SelectAll from './SelectAll'; import SelectAll from './SelectAll';
@ -20,7 +22,7 @@ function TableHeader<TData>({
}: TableHeaderProps<TData>) { }: TableHeaderProps<TData>) {
return ( return (
<thead <thead
className='clr-app cc-shadow-border' className='bg-prim-100 cc-shadow-border'
style={{ style={{
top: headPosition, top: headPosition,
position: 'sticky' position: 'sticky'

View File

@ -39,6 +39,7 @@ function DropdownButton({
'px-3 py-1 inline-flex items-center gap-2', 'px-3 py-1 inline-flex items-center gap-2',
'text-left text-sm overflow-ellipsis whitespace-nowrap', 'text-left text-sm overflow-ellipsis whitespace-nowrap',
'disabled:clr-text-controls', 'disabled:clr-text-controls',
'cc-animate-color',
{ {
'clr-hover': onClick, 'clr-hover': onClick,
'cursor-pointer disabled:cursor-auto': onClick, 'cursor-pointer disabled:cursor-auto': onClick,

View File

@ -9,7 +9,7 @@ function DropdownCheckbox({ setValue, disabled, ...restProps }: CheckboxProps) {
className={clsx( className={clsx(
'px-3 py-1', 'px-3 py-1',
'text-left overflow-ellipsis whitespace-nowrap', 'text-left overflow-ellipsis whitespace-nowrap',
'disabled:clr-text-controls', 'disabled:clr-text-controls cc-animate-color',
!!setValue && !disabled && 'clr-hover' !!setValue && !disabled && 'clr-hover'
)} )}
> >

View File

@ -19,7 +19,7 @@ function Indicator({ icon, title, titleHtml, hideTitle, noPadding, className, ..
return ( return (
<div <div
className={clsx( className={clsx(
'clr-btn-clear', 'clr-text-controls',
'outline-none', 'outline-none',
{ {
'px-1 py-1': !noPadding 'px-1 py-1': !noPadding

View File

@ -1,6 +1,6 @@
'use client'; 'use client';
import { useConceptOptions } from '@/context/ConceptOptionsContext'; import { APP_COLORS } from '@/styling/color';
interface LoaderProps { interface LoaderProps {
/** Scale of the loader from 1 to 10. */ /** Scale of the loader from 1 to 10. */
@ -55,11 +55,10 @@ const animatePulse = (startBig: boolean, duration: string) => {
* Displays animated loader. * Displays animated loader.
*/ */
function Loader({ scale = 5, circular }: LoaderProps) { function Loader({ scale = 5, circular }: LoaderProps) {
const { colors } = useConceptOptions();
if (circular) { if (circular) {
return ( return (
<div className='flex justify-center' aria-label='three-circles-loading' aria-busy='true' role='progressbar'> <div className='flex justify-center' aria-label='three-circles-loading' aria-busy='true' role='progressbar'>
<svg height={`${scale * 20}`} width={`${scale * 20}`} viewBox='0 0 100 100' fill={colors.bgPrimary}> <svg height={`${scale * 20}`} width={`${scale * 20}`} viewBox='0 0 100 100' fill={APP_COLORS.bgPrimary}>
<path d='M31.6,3.5C5.9,13.6-6.6,42.7,3.5,68.4c10.1,25.7,39.2,38.3,64.9,28.1l-3.1-7.9c-21.3,8.4-45.4-2-53.8-23.3 c-8.4-21.3,2-45.4,23.3-53.8L31.6,3.5z'> <path d='M31.6,3.5C5.9,13.6-6.6,42.7,3.5,68.4c10.1,25.7,39.2,38.3,64.9,28.1l-3.1-7.9c-21.3,8.4-45.4-2-53.8-23.3 c-8.4-21.3,2-45.4,23.3-53.8L31.6,3.5z'>
{animateRotation('2.25s')} {animateRotation('2.25s')}
</path> </path>
@ -75,7 +74,7 @@ function Loader({ scale = 5, circular }: LoaderProps) {
} else { } else {
return ( return (
<div className='flex justify-center' aria-busy='true' role='progressbar'> <div className='flex justify-center' aria-busy='true' role='progressbar'>
<svg height={`${scale * 20}`} width={`${scale * 20}`} viewBox='0 0 120 30' fill={colors.bgPrimary}> <svg height={`${scale * 20}`} width={`${scale * 20}`} viewBox='0 0 120 30' fill={APP_COLORS.bgPrimary}>
<circle cx='15' cy='15' r='16'> <circle cx='15' cy='15' r='16'>
{animatePulse(true, '0.8s')} {animatePulse(true, '0.8s')}
</circle> </circle>

View File

@ -35,7 +35,7 @@ function MiniButton({
tabIndex={tabIndex ?? -1} tabIndex={tabIndex ?? -1}
className={clsx( className={clsx(
'rounded-lg', 'rounded-lg',
'clr-btn-clear', 'clr-text-controls cc-animate-color',
'cursor-pointer disabled:cursor-auto', 'cursor-pointer disabled:cursor-auto',
{ {
'px-1 py-1': !noPadding, 'px-1 py-1': !noPadding,

View File

@ -93,16 +93,16 @@ function Modal({
return ( return (
<div className='fixed top-0 left-0 w-full h-full z-modal cursor-default'> <div className='fixed top-0 left-0 w-full h-full z-modal cursor-default'>
<div className={clsx('z-navigation', 'fixed top-0 left-0', 'w-full h-full', 'cc-modal-blur')} /> <div className={clsx('z-navigation', 'fixed top-0 left-0', 'w-full h-full', 'backdrop-blur-[3px] opacity-50')} />
<div <div
className={clsx('z-navigation', 'fixed top-0 left-0', 'w-full h-full', 'cc-modal-backdrop')} className={clsx('z-navigation', 'fixed top-0 left-0', 'w-full h-full', 'bg-prim-0 opacity-25')}
onClick={hideWindow} onClick={hideWindow}
/> />
<div <div
className={clsx( className={clsx(
'cc-animate-modal', 'cc-animate-modal',
'z-modal absolute bottom-1/2 left-1/2 -translate-x-1/2 translate-y-1/2', 'z-modal absolute bottom-1/2 left-1/2 -translate-x-1/2 translate-y-1/2',
'border rounded-xl clr-app' 'border rounded-xl bg-prim-100'
)} )}
> >
<Overlay position='right-2 top-2'> <Overlay position='right-2 top-2'>

View File

@ -1,7 +1,5 @@
'use client'; 'use client';
import { useMemo } from 'react';
import { useConceptOptions } from '@/context/ConceptOptionsContext'; import { useConceptOptions } from '@/context/ConceptOptionsContext';
import useWindowSize from '@/hooks/useWindowSize'; import useWindowSize from '@/hooks/useWindowSize';
@ -29,10 +27,8 @@ function PDFViewer({ file, offsetXpx, minWidth = MINIMUM_WIDTH }: PDFViewerProps
const windowSize = useWindowSize(); const windowSize = useWindowSize();
const { calculateHeight } = useConceptOptions(); const { calculateHeight } = useConceptOptions();
const pageWidth = useMemo(() => { const pageWidth = Math.max(minWidth, Math.min((windowSize?.width ?? 0) - (offsetXpx ?? 0) - 10, MAXIMUM_WIDTH));
return Math.max(minWidth, Math.min((windowSize?.width ?? 0) - (offsetXpx ?? 0) - 10, MAXIMUM_WIDTH)); const pageHeight = calculateHeight('1rem');
}, [windowSize, offsetXpx, minWidth]);
const pageHeight = useMemo(() => calculateHeight('1rem'), [calculateHeight]);
return <embed src={`${file}#toolbar=0`} className='p-3' style={{ width: pageWidth, height: pageHeight }} />; return <embed src={`${file}#toolbar=0`} className='p-3' style={{ width: pageWidth, height: pageHeight }} />;
} }

View File

@ -1,6 +1,5 @@
'use client'; 'use client';
import { useMemo } from 'react';
import Select, { import Select, {
ClearIndicatorProps, ClearIndicatorProps,
components, components,
@ -10,9 +9,8 @@ import Select, {
StylesConfig StylesConfig
} from 'react-select'; } from 'react-select';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import useWindowSize from '@/hooks/useWindowSize'; import useWindowSize from '@/hooks/useWindowSize';
import { selectDarkT, selectLightT } from '@/styling/color'; import { APP_COLORS, SELECT_THEME } from '@/styling/color';
import { IconClose, IconDropArrow, IconDropArrowUp } from '../Icons'; import { IconClose, IconDropArrow, IconDropArrowUp } from '../Icons';
@ -52,12 +50,9 @@ function SelectMulti<Option, Group extends GroupBase<Option> = GroupBase<Option>
noPortal, noPortal,
...restProps ...restProps
}: SelectMultiProps<Option, Group>) { }: SelectMultiProps<Option, Group>) {
const { darkMode, colors } = useConceptOptions();
const size = useWindowSize(); const size = useWindowSize();
const themeColors = useMemo(() => (!darkMode ? selectLightT : selectDarkT), [darkMode]);
const adjustedStyles: StylesConfig<Option, true, Group> = useMemo( const adjustedStyles: StylesConfig<Option, true, Group> = {
() => ({
container: defaultStyles => ({ container: defaultStyles => ({
...defaultStyles, ...defaultStyles,
borderRadius: '0.25rem' borderRadius: '0.25rem'
@ -73,10 +68,10 @@ function SelectMulti<Option, Group extends GroupBase<Option> = GroupBase<Option>
padding: '0.25rem 0.75rem', padding: '0.25rem 0.75rem',
fontSize: '0.875rem', fontSize: '0.875rem',
lineHeight: '1.25rem', lineHeight: '1.25rem',
backgroundColor: isSelected ? colors.bgSelected : styles.backgroundColor, backgroundColor: isSelected ? APP_COLORS.bgSelected : styles.backgroundColor,
color: isSelected ? colors.fgSelected : styles.color, color: isSelected ? APP_COLORS.fgSelected : styles.color,
borderWidth: '1px', borderWidth: '1px',
borderColor: colors.border borderColor: APP_COLORS.border
}), }),
menuPortal: styles => ({ menuPortal: styles => ({
...styles, ...styles,
@ -91,7 +86,7 @@ function SelectMulti<Option, Group extends GroupBase<Option> = GroupBase<Option>
multiValue: styles => ({ multiValue: styles => ({
...styles, ...styles,
borderRadius: '0.5rem', borderRadius: '0.5rem',
backgroundColor: colors.bgSelected backgroundColor: APP_COLORS.bgSelected
}), }),
dropdownIndicator: base => ({ dropdownIndicator: base => ({
...base, ...base,
@ -103,9 +98,7 @@ function SelectMulti<Option, Group extends GroupBase<Option> = GroupBase<Option>
paddingTop: 0, paddingTop: 0,
paddingBottom: 0 paddingBottom: 0
}) })
}), };
[colors]
);
return ( return (
<Select <Select
@ -123,7 +116,7 @@ function SelectMulti<Option, Group extends GroupBase<Option> = GroupBase<Option>
}, },
colors: { colors: {
...theme.colors, ...theme.colors,
...themeColors ...SELECT_THEME
} }
})} })}
menuPortalTarget={!noPortal ? document.body : null} menuPortalTarget={!noPortal ? document.body : null}

View File

@ -1,6 +1,5 @@
'use client'; 'use client';
import { useMemo } from 'react';
import Select, { import Select, {
ClearIndicatorProps, ClearIndicatorProps,
components, components,
@ -10,9 +9,8 @@ import Select, {
StylesConfig StylesConfig
} from 'react-select'; } from 'react-select';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import useWindowSize from '@/hooks/useWindowSize'; import useWindowSize from '@/hooks/useWindowSize';
import { selectDarkT, selectLightT } from '@/styling/color'; import { APP_COLORS, SELECT_THEME } from '@/styling/color';
import { IconClose, IconDropArrow, IconDropArrowUp } from '../Icons'; import { IconClose, IconDropArrow, IconDropArrowUp } from '../Icons';
@ -54,12 +52,9 @@ function SelectSingle<Option, Group extends GroupBase<Option> = GroupBase<Option
noBorder, noBorder,
...restProps ...restProps
}: SelectSingleProps<Option, Group>) { }: SelectSingleProps<Option, Group>) {
const { darkMode, colors } = useConceptOptions();
const size = useWindowSize(); const size = useWindowSize();
const themeColors = useMemo(() => (!darkMode ? selectLightT : selectDarkT), [darkMode]);
const adjustedStyles: StylesConfig<Option, false, Group> = useMemo( const adjustedStyles: StylesConfig<Option, false, Group> = {
() => ({
container: defaultStyles => ({ container: defaultStyles => ({
...defaultStyles, ...defaultStyles,
borderRadius: '0.25rem' borderRadius: '0.25rem'
@ -84,10 +79,10 @@ function SelectSingle<Option, Group extends GroupBase<Option> = GroupBase<Option
padding: '0.25rem 0.75rem', padding: '0.25rem 0.75rem',
fontSize: '0.875rem', fontSize: '0.875rem',
lineHeight: '1.25rem', lineHeight: '1.25rem',
backgroundColor: isSelected ? colors.bgSelected : defaultStyles.backgroundColor, backgroundColor: isSelected ? APP_COLORS.bgSelected : defaultStyles.backgroundColor,
color: isSelected ? colors.fgSelected : defaultStyles.color, color: isSelected ? APP_COLORS.fgSelected : defaultStyles.color,
borderWidth: '1px', borderWidth: '1px',
borderColor: colors.border borderColor: APP_COLORS.border
}), }),
input: defaultStyles => ({ ...defaultStyles }), input: defaultStyles => ({ ...defaultStyles }),
placeholder: defaultStyles => ({ ...defaultStyles }), placeholder: defaultStyles => ({ ...defaultStyles }),
@ -102,9 +97,7 @@ function SelectSingle<Option, Group extends GroupBase<Option> = GroupBase<Option
paddingTop: 0, paddingTop: 0,
paddingBottom: 0 paddingBottom: 0
}) })
}), };
[colors, noBorder]
);
return ( return (
<Select <Select
@ -121,7 +114,7 @@ function SelectSingle<Option, Group extends GroupBase<Option> = GroupBase<Option
}, },
colors: { colors: {
...theme.colors, ...theme.colors,
...themeColors ...SELECT_THEME
} }
})} })}
menuPortalTarget={!noPortal ? document.body : null} menuPortalTarget={!noPortal ? document.body : null}

View File

@ -1,5 +1,5 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; import { useEffect, useState } from 'react';
import { globals, PARAMETER } from '@/utils/constants'; import { globals, PARAMETER } from '@/utils/constants';
@ -44,18 +44,14 @@ function SelectTree<ItemType>({
prefix, prefix,
...restProps ...restProps
}: SelectTreeProps<ItemType>) { }: SelectTreeProps<ItemType>) {
const foldable = useMemo( const foldable = new Set(items.filter(item => getParent(item) !== item).map(item => getParent(item)));
() => new Set(items.filter(item => getParent(item) !== item).map(item => getParent(item))),
[items, getParent]
);
const [folded, setFolded] = useState<ItemType[]>(items); const [folded, setFolded] = useState<ItemType[]>(items);
useLayoutEffect(() => { useEffect(() => {
setFolded(items.filter(item => getParent(value) !== item && getParent(getParent(value)) !== item)); setFolded(items.filter(item => getParent(value) !== item && getParent(getParent(value)) !== item));
}, [value, getParent, items]); }, [value, getParent, items]);
const onFoldItem = useCallback( function onFoldItem(target: ItemType, showChildren: boolean) {
(target: ItemType, showChildren: boolean) => {
setFolded(prev => setFolded(prev =>
items.filter(item => { items.filter(item => {
if (item === target) { if (item === target) {
@ -68,27 +64,19 @@ function SelectTree<ItemType>({
} }
}) })
); );
}, }
[items, getParent]
);
const handleClickFold = useCallback( function handleClickFold(event: CProps.EventMouse, target: ItemType, showChildren: boolean) {
(event: CProps.EventMouse, target: ItemType, showChildren: boolean) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
onFoldItem(target, showChildren); onFoldItem(target, showChildren);
}, }
[onFoldItem]
);
const handleSetValue = useCallback( function handleSetValue(event: CProps.EventMouse, target: ItemType) {
(event: CProps.EventMouse, target: ItemType) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
onChangeValue(target); onChangeValue(target);
}, }
[onChangeValue]
);
return ( return (
<div {...restProps}> <div {...restProps}>
@ -101,7 +89,7 @@ function SelectTree<ItemType>({
className={clsx( className={clsx(
'pr-3 pl-6 border-b', 'pr-3 pl-6 border-b',
'cc-scroll-row', 'cc-scroll-row',
'clr-controls clr-hover', 'bg-prim-200 clr-hover cc-animate-color',
'cursor-pointer', 'cursor-pointer',
value === item && 'clr-selected' value === item && 'clr-selected'
)} )}

View File

@ -41,6 +41,7 @@ function SelectorButton({
'text-sm font-controls select-none', 'text-sm font-controls select-none',
'text-btn clr-text-controls', 'text-btn clr-text-controls',
'disabled:cursor-auto cursor-pointer', 'disabled:cursor-auto cursor-pointer',
'cc-animate-color',
{ {
'clr-hover': transparent, 'clr-hover': transparent,
'border': !transparent 'border': !transparent

View File

@ -24,7 +24,7 @@ function SubmitButton({ text = 'ОК', icon, disabled, loading, className, ...re
'px-3 py-1 flex gap-2 items-center justify-center', 'px-3 py-1 flex gap-2 items-center justify-center',
'border', 'border',
'font-medium', 'font-medium',
'clr-btn-primary', 'clr-btn-primary cc-animate-color',
'select-none disabled:cursor-auto', 'select-none disabled:cursor-auto',
loading && 'cursor-progress', loading && 'cursor-progress',
className className

View File

@ -20,7 +20,7 @@ function TabLabel({ label, title, titleHtml, hideTitle, className, ...otherProps
className={clsx( className={clsx(
'min-w-[5.5rem] h-full', 'min-w-[5.5rem] h-full',
'px-2 py-1 flex justify-center', 'px-2 py-1 flex justify-center',
'clr-tab', 'clr-hover bg-prim-200 cc-animate-color',
'text-sm whitespace-nowrap font-controls', 'text-sm whitespace-nowrap font-controls',
'select-none hover:cursor-pointer', 'select-none hover:cursor-pointer',
'outline-none', 'outline-none',

View File

@ -20,7 +20,7 @@ interface TextURLProps {
/** /**
* Displays a text with a clickable link. * Displays a text with a clickable link.
*/ */
function TextURL({ text, href, title, color = 'clr-text-url', onClick }: TextURLProps) { function TextURL({ text, href, title, color = 'text-sec-600', onClick }: TextURLProps) {
const design = `cursor-pointer hover:underline ${color}`; const design = `cursor-pointer hover:underline ${color}`;
if (href) { if (href) {
return ( return (

View File

@ -1,5 +1,4 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { useMemo } from 'react';
import { globals } from '@/utils/constants'; import { globals } from '@/utils/constants';
@ -50,7 +49,8 @@ function ValueIcon({
onClick, onClick,
...restProps ...restProps
}: ValueIconProps) { }: ValueIconProps) {
const isSmall = useMemo(() => !smallThreshold || String(value).length < smallThreshold, [value, smallThreshold]); // TODO: use CSS instead of threshold
const isSmall = !smallThreshold || String(value).length < smallThreshold;
return ( return (
<div <div
className={clsx( className={clsx(

View File

@ -22,7 +22,7 @@ function ExpectedAnonymous() {
<span> | </span> <span> | </span>
<TextURL text='Справка' href='/manuals' /> <TextURL text='Справка' href='/manuals' />
<span> | </span> <span> | </span>
<span className='cursor-pointer hover:underline clr-text-url' onClick={logoutAndRedirect}> <span className='cursor-pointer hover:underline text-sec-600' onClick={logoutAndRedirect}>
Выйти Выйти
</span> </span>
</div> </div>

View File

@ -21,5 +21,5 @@ export const useAccessMode = () => {
export const AccessModeState = ({ children }: React.PropsWithChildren) => { export const AccessModeState = ({ children }: React.PropsWithChildren) => {
const [accessLevel, setAccessLevel] = useState<UserLevel>(UserLevel.READER); const [accessLevel, setAccessLevel] = useState<UserLevel>(UserLevel.READER);
return <AccessContext.Provider value={{ accessLevel, setAccessLevel }}>{children}</AccessContext.Provider>; return <AccessContext value={{ accessLevel, setAccessLevel }}>{children}</AccessContext>;
}; };

View File

@ -1,6 +1,6 @@
'use client'; 'use client';
import { createContext, useCallback, useContext, useLayoutEffect, useState } from 'react'; import { createContext, useCallback, useContext, useEffect, useState } from 'react';
import { DataCallback } from '@/backend/apiTransport'; import { DataCallback } from '@/backend/apiTransport';
import { import {
@ -124,10 +124,7 @@ export const AuthState = ({ children }: React.PropsWithChildren) => {
showError: true, showError: true,
setLoading: setLoading, setLoading: setLoading,
onError: setError, onError: setError,
onSuccess: () => onSuccess: () => reload(callback)
reload(() => {
callback?.();
})
}); });
}, },
[reload] [reload]
@ -184,12 +181,12 @@ export const AuthState = ({ children }: React.PropsWithChildren) => {
[reload] [reload]
); );
useLayoutEffect(() => { useEffect(() => {
reload(); reload();
}, [reload]); }, [reload]);
return ( return (
<AuthContext.Provider <AuthContext
value={{ value={{
user, user,
login, login,
@ -205,6 +202,6 @@ export const AuthState = ({ children }: React.PropsWithChildren) => {
}} }}
> >
{children} {children}
</AuthContext.Provider> </AuthContext>
); );
}; };

View File

@ -1,10 +1,9 @@
'use client'; 'use client';
import { createContext, useCallback, useContext, useLayoutEffect, useMemo, useState } from 'react'; import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import Tooltip from '@/components/ui/Tooltip'; import Tooltip from '@/components/ui/Tooltip';
import useLocalStorage from '@/hooks/useLocalStorage'; import useLocalStorage from '@/hooks/useLocalStorage';
import { darkT, IColorTheme, lightT } from '@/styling/color';
import { globals, PARAMETER, storage } from '@/utils/constants'; import { globals, PARAMETER, storage } from '@/utils/constants';
import { contextOutsideScope } from '@/utils/labels'; import { contextOutsideScope } from '@/utils/labels';
@ -12,8 +11,6 @@ interface IOptionsContext {
viewportHeight: string; viewportHeight: string;
mainHeight: string; mainHeight: string;
colors: IColorTheme;
darkMode: boolean; darkMode: boolean;
toggleDarkMode: () => void; toggleDarkMode: () => void;
@ -60,8 +57,6 @@ export const OptionsState = ({ children }: React.PropsWithChildren) => {
const [folderMode, setFolderMode] = useLocalStorage<boolean>(storage.librarySearchFolderMode, true); const [folderMode, setFolderMode] = useLocalStorage<boolean>(storage.librarySearchFolderMode, true);
const [location, setLocation] = useLocalStorage<string>(storage.librarySearchLocation, ''); const [location, setLocation] = useLocalStorage<string>(storage.librarySearchLocation, '');
const [colors, setColors] = useState<IColorTheme>(lightT);
const [noNavigationAnimation, setNoNavigationAnimation] = useState(false); const [noNavigationAnimation, setNoNavigationAnimation] = useState(false);
const [noFooter, setNoFooter] = useState(false); const [noFooter, setNoFooter] = useState(false);
const [showScroll, setShowScroll] = useState(false); const [showScroll, setShowScroll] = useState(false);
@ -76,14 +71,10 @@ export const OptionsState = ({ children }: React.PropsWithChildren) => {
root.setAttribute('data-color-scheme', !isDark ? 'light' : 'dark'); root.setAttribute('data-color-scheme', !isDark ? 'light' : 'dark');
} }
useLayoutEffect(() => { useEffect(() => {
setDarkClass(darkMode); setDarkClass(darkMode);
}, [darkMode]); }, [darkMode]);
useLayoutEffect(() => {
setColors(darkMode ? darkT : lightT);
}, [darkMode, setColors]);
const toggleNoNavigation = useCallback(() => { const toggleNoNavigation = useCallback(() => {
if (noNavigation) { if (noNavigation) {
setNoNavigationAnimation(false); setNoNavigationAnimation(false);
@ -109,7 +100,6 @@ export const OptionsState = ({ children }: React.PropsWithChildren) => {
const toggleDarkMode = useCallback(() => { const toggleDarkMode = useCallback(() => {
setDarkMode(prev => !prev); setDarkMode(prev => !prev);
window.location.reload();
}, [setDarkMode]); }, [setDarkMode]);
const mainHeight = useMemo(() => { const mainHeight = useMemo(() => {
@ -127,11 +117,10 @@ export const OptionsState = ({ children }: React.PropsWithChildren) => {
}, [noNavigation]); }, [noNavigation]);
return ( return (
<OptionsContext.Provider <OptionsContext
value={{ value={{
darkMode, darkMode,
adminMode, adminMode,
colors,
noNavigationAnimation, noNavigationAnimation,
noNavigation, noNavigation,
noFooter, noFooter,
@ -169,6 +158,6 @@ export const OptionsState = ({ children }: React.PropsWithChildren) => {
{children} {children}
</> </>
</OptionsContext.Provider> </OptionsContext>
); );
}; };

View File

@ -8,8 +8,6 @@ import { LibraryItemID } from '@/models/library';
import { IOperationSchema, IOperationSchemaData } from '@/models/oss'; import { IOperationSchema, IOperationSchemaData } from '@/models/oss';
import { contextOutsideScope } from '@/utils/labels'; import { contextOutsideScope } from '@/utils/labels';
import { useLibrary } from './LibraryContext';
interface IGlobalOssContext { interface IGlobalOssContext {
schema: IOperationSchema | undefined; schema: IOperationSchema | undefined;
setID: (id: string | undefined) => void; setID: (id: string | undefined) => void;
@ -20,6 +18,7 @@ interface IGlobalOssContext {
invalidate: () => void; invalidate: () => void;
invalidateItem: (target: LibraryItemID) => void; invalidateItem: (target: LibraryItemID) => void;
partialUpdate: (data: Partial<IOperationSchema>) => void;
reload: (callback?: () => void) => void; reload: (callback?: () => void) => void;
} }
@ -33,16 +32,16 @@ export const useGlobalOss = (): IGlobalOssContext => {
}; };
export const GlobalOssState = ({ children }: React.PropsWithChildren) => { export const GlobalOssState = ({ children }: React.PropsWithChildren) => {
const library = useLibrary(); const [isValid, setIsValid] = useState(true);
const [isValid, setIsValid] = useState(false);
const [ossID, setIdInternal] = useState<string | undefined>(undefined); const [ossID, setIdInternal] = useState<string | undefined>(undefined);
const { const {
schema: schema, // prettier: split lines schema: schema, // prettier: split lines
error: loadingError, error: loadingError,
setSchema: setDataInternal, setSchema: setDataInternal,
loading: loading, loading: loading,
reload: reloadInternal reload: reloadInternal,
} = useOssDetails({ target: ossID, items: library.items }); partialUpdate
} = useOssDetails({ target: ossID });
const reload = useCallback( const reload = useCallback(
(callback?: () => void) => { (callback?: () => void) => {
@ -88,7 +87,7 @@ export const GlobalOssState = ({ children }: React.PropsWithChildren) => {
); );
return ( return (
<GlobalOssContext.Provider <GlobalOssContext
value={{ value={{
schema, schema,
setID, setID,
@ -96,12 +95,13 @@ export const GlobalOssState = ({ children }: React.PropsWithChildren) => {
loading, loading,
loadingError, loadingError,
reload, reload,
partialUpdate,
isValid, isValid,
invalidateItem, invalidateItem,
invalidate invalidate
}} }}
> >
{children} {children}
</GlobalOssContext.Provider> </GlobalOssContext>
); );
}; };

View File

@ -48,7 +48,7 @@ interface ILibraryContext {
destroyItem: (target: LibraryItemID, callback?: () => void) => void; destroyItem: (target: LibraryItemID, callback?: () => void) => void;
renameLocation: (data: IRenameLocationData, callback?: () => void) => void; renameLocation: (data: IRenameLocationData, callback?: () => void) => void;
localUpdateItem: (data: ILibraryItem) => void; localUpdateItem: (data: Partial<ILibraryItem>) => void;
localUpdateTimestamp: (target: LibraryItemID) => void; localUpdateTimestamp: (target: LibraryItemID) => void;
} }
@ -196,21 +196,17 @@ export const LibraryState = ({ children }: React.PropsWithChildren) => {
}, [reloadTemplates]); }, [reloadTemplates]);
const localUpdateItem = useCallback( const localUpdateItem = useCallback(
(data: ILibraryItem) => { (data: Partial<ILibraryItem>) => {
const libraryItem = items.find(item => item.id === data.id); setItems(prev => prev.map(item => (item.id === data.id ? { ...item, ...data } : item)));
if (libraryItem) Object.assign(libraryItem, data);
}, },
[items] [setItems]
); );
const localUpdateTimestamp = useCallback( const localUpdateTimestamp = useCallback(
(target: LibraryItemID) => { (target: LibraryItemID) => {
const libraryItem = items.find(item => item.id === target); setItems(prev => prev.map(item => (item.id === target ? { ...item, time_update: Date() } : item)));
if (libraryItem) {
libraryItem.time_update = Date();
}
}, },
[items] [setItems]
); );
const createItem = useCallback( const createItem = useCallback(
@ -295,7 +291,7 @@ export const LibraryState = ({ children }: React.PropsWithChildren) => {
); );
return ( return (
<LibraryContext.Provider <LibraryContext
value={{ value={{
items, items,
folders, folders,
@ -322,6 +318,6 @@ export const LibraryState = ({ children }: React.PropsWithChildren) => {
}} }}
> >
{children} {children}
</LibraryContext.Provider> </LibraryContext>
); );
}; };

View File

@ -93,7 +93,7 @@ export const NavigationState = ({ children }: React.PropsWithChildren) => {
}, [pathname, scrollTop]); }, [pathname, scrollTop]);
return ( return (
<NavigationContext.Provider <NavigationContext
value={{ value={{
push, push,
replace, replace,
@ -105,7 +105,7 @@ export const NavigationState = ({ children }: React.PropsWithChildren) => {
}} }}
> >
{children} {children}
</NavigationContext.Provider> </NavigationContext>
); );
}; };

View File

@ -85,23 +85,22 @@ interface OssStateProps {
export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateProps>) => { export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateProps>) => {
const library = useLibrary(); const library = useLibrary();
const oss = useGlobalOss(); const ossData = useGlobalOss();
const model = oss.schema;
const { user } = useAuth(); const { user } = useAuth();
const [processing, setProcessing] = useState(false); const [processing, setProcessing] = useState(false);
const [processingError, setProcessingError] = useState<ErrorData>(undefined); const [processingError, setProcessingError] = useState<ErrorData>(undefined);
const isOwned = useMemo(() => { const isOwned = useMemo(() => {
return user?.id === model?.owner || false; return user?.id === ossData.schema?.owner || false;
}, [user, model?.owner]); }, [user, ossData.schema?.owner]);
useEffect(() => { useEffect(() => {
oss.setID(itemID); ossData.setID(itemID);
}, [itemID, oss]); }, [itemID, ossData]);
const update = useCallback( const update = useCallback(
(data: ILibraryUpdateData, callback?: DataCallback<ILibraryItem>) => { (data: ILibraryUpdateData, callback?: DataCallback<ILibraryItem>) => {
if (!model) { if (!ossData.schema) {
return; return;
} }
setProcessingError(undefined); setProcessingError(undefined);
@ -111,43 +110,39 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: newData => { onSuccess: newData => {
const fullData: IOperationSchemaData = Object.assign(model, newData); const fullData: IOperationSchemaData = Object.assign(ossData.schema!, newData);
oss.setData(fullData); ossData.setData(fullData);
library.localUpdateItem(newData); library.localUpdateItem(newData);
callback?.(newData); callback?.(newData);
} }
}); });
}, },
[itemID, model, library, oss] [itemID, library, ossData]
); );
const setOwner = useCallback( const setOwner = useCallback(
(newOwner: UserID, callback?: () => void) => { (newOwner: UserID, callback?: () => void) => {
if (!model) { if (!ossData.schema) {
return; return;
} }
setProcessingError(undefined); setProcessingError(undefined);
patchSetOwner(itemID, { patchSetOwner(itemID, {
data: { data: { user: newOwner },
user: newOwner
},
showError: true, showError: true,
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: () => { onSuccess: () => {
model.owner = newOwner; ossData.partialUpdate({ owner: newOwner });
library.reloadItems(() => { library.reloadItems(callback);
callback?.();
});
} }
}); });
}, },
[itemID, model, library] [itemID, ossData, library]
); );
const setAccessPolicy = useCallback( const setAccessPolicy = useCallback(
(newPolicy: AccessPolicy, callback?: () => void) => { (newPolicy: AccessPolicy, callback?: () => void) => {
if (!model) { if (!ossData.schema) {
return; return;
} }
setProcessingError(undefined); setProcessingError(undefined);
@ -159,19 +154,17 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: () => { onSuccess: () => {
model.access_policy = newPolicy; ossData.partialUpdate({ access_policy: newPolicy });
library.reloadItems(() => { library.reloadItems(callback);
callback?.();
});
} }
}); });
}, },
[itemID, model, library] [itemID, ossData, library]
); );
const setLocation = useCallback( const setLocation = useCallback(
(newLocation: string, callback?: () => void) => { (newLocation: string, callback?: () => void) => {
if (!model) { if (!ossData.schema) {
return; return;
} }
setProcessingError(undefined); setProcessingError(undefined);
@ -183,19 +176,17 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: () => { onSuccess: () => {
model.location = newLocation; ossData.partialUpdate({ location: newLocation });
library.reloadItems(() => { library.reloadItems(callback);
callback?.();
});
} }
}); });
}, },
[itemID, model, library] [itemID, ossData, library]
); );
const setEditors = useCallback( const setEditors = useCallback(
(newEditors: UserID[], callback?: () => void) => { (newEditors: UserID[], callback?: () => void) => {
if (!model) { if (!ossData.schema) {
return; return;
} }
setProcessingError(undefined); setProcessingError(undefined);
@ -207,14 +198,12 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: () => { onSuccess: () => {
model.editors = newEditors; ossData.partialUpdate({ editors: newEditors });
library.reloadItems(() => { library.reloadItems(callback);
callback?.();
});
} }
}); });
}, },
[itemID, model, library] [itemID, ossData, library]
); );
const savePositions = useCallback( const savePositions = useCallback(
@ -243,13 +232,13 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: newData => { onSuccess: newData => {
oss.setData(newData.oss); ossData.setData(newData.oss);
library.localUpdateTimestamp(newData.oss.id); library.localUpdateTimestamp(newData.oss.id);
callback?.(newData.new_operation); callback?.(newData.new_operation);
} }
}); });
}, },
[itemID, library, oss] [itemID, library, ossData]
); );
const deleteOperation = useCallback( const deleteOperation = useCallback(
@ -261,14 +250,12 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: newData => { onSuccess: newData => {
oss.setData(newData); ossData.setData(newData);
library.reloadItems(() => { library.reloadItems(callback);
callback?.();
});
} }
}); });
}, },
[itemID, library, oss] [itemID, library, ossData]
); );
const createInput = useCallback( const createInput = useCallback(
@ -280,19 +267,19 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: newData => { onSuccess: newData => {
oss.setData(newData.oss); ossData.setData(newData.oss);
library.reloadItems(() => { library.reloadItems(() => {
callback?.(newData.new_schema); callback?.(newData.new_schema);
}); });
} }
}); });
}, },
[itemID, library, oss] [itemID, library, ossData]
); );
const setInput = useCallback( const setInput = useCallback(
(data: IOperationSetInputData, callback?: () => void) => { (data: IOperationSetInputData, callback?: () => void) => {
if (!model) { if (!ossData.schema) {
return; return;
} }
setProcessingError(undefined); setProcessingError(undefined);
@ -302,19 +289,17 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: newData => { onSuccess: newData => {
oss.setData(newData); ossData.setData(newData);
library.reloadItems(() => { library.reloadItems(callback);
callback?.();
});
} }
}); });
}, },
[itemID, model, library, oss] [itemID, ossData, library]
); );
const updateOperation = useCallback( const updateOperation = useCallback(
(data: IOperationUpdateData, callback?: () => void) => { (data: IOperationUpdateData, callback?: () => void) => {
if (!model) { if (!ossData.schema) {
return; return;
} }
setProcessingError(undefined); setProcessingError(undefined);
@ -324,19 +309,17 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: newData => { onSuccess: newData => {
oss.setData(newData); ossData.setData(newData);
library.reloadItems(() => { library.reloadItems(callback);
callback?.();
});
} }
}); });
}, },
[itemID, model, library, oss] [itemID, library, ossData]
); );
const executeOperation = useCallback( const executeOperation = useCallback(
(data: ITargetOperation, callback?: () => void) => { (data: ITargetOperation, callback?: () => void) => {
if (!model) { if (!ossData.schema) {
return; return;
} }
setProcessingError(undefined); setProcessingError(undefined);
@ -346,19 +329,17 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: newData => { onSuccess: newData => {
oss.setData(newData); ossData.setData(newData);
library.reloadItems(() => { library.reloadItems(callback);
callback?.();
});
} }
}); });
}, },
[itemID, model, library, oss] [itemID, library, ossData]
); );
const relocateConstituents = useCallback( const relocateConstituents = useCallback(
(data: ICstRelocateData, callback?: () => void) => { (data: ICstRelocateData, callback?: () => void) => {
if (!model) { if (!ossData.schema) {
return; return;
} }
setProcessingError(undefined); setProcessingError(undefined);
@ -368,23 +349,21 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: () => { onSuccess: () => {
oss.reload(); ossData.reload();
library.reloadItems(() => { library.reloadItems(callback);
callback?.();
});
} }
}); });
}, },
[model, library, oss] [library, ossData]
); );
return ( return (
<OssContext.Provider <OssContext
value={{ value={{
schema: model, schema: ossData.schema,
itemID, itemID,
loading: oss.loading, loading: ossData.loading,
loadingError: oss.loadingError, loadingError: ossData.loadingError,
processing, processing,
processingError, processingError,
isOwned, isOwned,
@ -406,6 +385,6 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
}} }}
> >
{children} {children}
</OssContext.Provider> </OssContext>
); );
}; };

View File

@ -113,28 +113,19 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
const library = useLibrary(); const library = useLibrary();
const oss = useGlobalOss(); const oss = useGlobalOss();
const { user } = useAuth(); const { user } = useAuth();
const { const rsData = useRSFormDetails({ target: itemID, version: versionID });
schema, // prettier: split lines
reload,
error: errorLoading,
setSchema,
loading
} = useRSFormDetails({
target: itemID,
version: versionID
});
const [processing, setProcessing] = useState(false); const [processing, setProcessing] = useState(false);
const [processingError, setProcessingError] = useState<ErrorData>(undefined); const [processingError, setProcessingError] = useState<ErrorData>(undefined);
const isOwned = useMemo(() => { const isOwned = useMemo(() => {
return user?.id === schema?.owner || false; return user?.id === rsData.schema?.owner || false;
}, [user, schema?.owner]); }, [user, rsData.schema?.owner]);
const isArchive = useMemo(() => !!versionID, [versionID]); const isArchive = useMemo(() => !!versionID, [versionID]);
const update = useCallback( const update = useCallback(
(data: ILibraryUpdateData, callback?: DataCallback<ILibraryItem>) => { (data: ILibraryUpdateData, callback?: DataCallback<ILibraryItem>) => {
if (!schema) { if (!rsData.schema) {
return; return;
} }
setProcessingError(undefined); setProcessingError(undefined);
@ -144,19 +135,19 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: newData => { onSuccess: newData => {
setSchema(Object.assign(schema, newData)); rsData.setSchema(Object.assign(rsData.schema!, newData));
library.localUpdateItem(newData); library.localUpdateItem(newData);
oss.invalidateItem(newData.id); oss.invalidateItem(newData.id);
callback?.(newData); callback?.(newData);
} }
}); });
}, },
[itemID, setSchema, schema, library, oss] [itemID, rsData, library, oss]
); );
const upload = useCallback( const upload = useCallback(
(data: IRSFormUploadData, callback?: () => void) => { (data: IRSFormUploadData, callback?: () => void) => {
if (!schema) { if (!rsData.schema) {
return; return;
} }
setProcessingError(undefined); setProcessingError(undefined);
@ -166,20 +157,17 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: newData => { onSuccess: newData => {
setSchema(newData); rsData.setSchema(newData);
library.localUpdateItem(newData); library.localUpdateItem(newData);
callback?.(); callback?.();
} }
}); });
}, },
[itemID, setSchema, schema, library] [itemID, rsData, library]
); );
const setOwner = useCallback( const setOwner = useCallback(
(newOwner: UserID, callback?: () => void) => { (newOwner: UserID, callback?: () => void) => {
if (!schema) {
return;
}
setProcessingError(undefined); setProcessingError(undefined);
patchSetOwner(itemID, { patchSetOwner(itemID, {
data: { data: {
@ -189,18 +177,18 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: () => { onSuccess: () => {
schema.owner = newOwner; rsData.partialUpdate({ owner: newOwner });
library.localUpdateItem(schema); library.localUpdateItem({ id: Number(itemID), owner: newOwner });
callback?.(); callback?.();
} }
}); });
}, },
[itemID, schema, library] [itemID, rsData, library]
); );
const setAccessPolicy = useCallback( const setAccessPolicy = useCallback(
(newPolicy: AccessPolicy, callback?: () => void) => { (newPolicy: AccessPolicy, callback?: () => void) => {
if (!schema) { if (!rsData.schema) {
return; return;
} }
setProcessingError(undefined); setProcessingError(undefined);
@ -212,18 +200,18 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: () => { onSuccess: () => {
schema.access_policy = newPolicy; rsData.partialUpdate({ access_policy: newPolicy });
library.localUpdateItem(schema); library.localUpdateItem({ id: Number(itemID), access_policy: newPolicy });
callback?.(); callback?.();
} }
}); });
}, },
[itemID, schema, library] [itemID, rsData, library]
); );
const setLocation = useCallback( const setLocation = useCallback(
(newLocation: string, callback?: () => void) => { (newLocation: string, callback?: () => void) => {
if (!schema) { if (!rsData.schema) {
return; return;
} }
setProcessingError(undefined); setProcessingError(undefined);
@ -235,17 +223,18 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: () => { onSuccess: () => {
schema.location = newLocation; rsData.partialUpdate({ location: newLocation });
library.reloadItems(callback); library.localUpdateItem({ id: Number(itemID), location: newLocation });
callback?.();
} }
}); });
}, },
[itemID, schema, library] [itemID, rsData, library]
); );
const setEditors = useCallback( const setEditors = useCallback(
(newEditors: UserID[], callback?: () => void) => { (newEditors: UserID[], callback?: () => void) => {
if (!schema) { if (!rsData.schema) {
return; return;
} }
setProcessingError(undefined); setProcessingError(undefined);
@ -257,17 +246,17 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: () => { onSuccess: () => {
schema.editors = newEditors; rsData.partialUpdate({ editors: newEditors });
callback?.(); callback?.();
} }
}); });
}, },
[itemID, schema] [itemID, rsData]
); );
const resetAliases = useCallback( const resetAliases = useCallback(
(callback?: () => void) => { (callback?: () => void) => {
if (!schema || !user) { if (!rsData.schema || !user) {
return; return;
} }
setProcessingError(undefined); setProcessingError(undefined);
@ -276,19 +265,19 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: newData => { onSuccess: newData => {
setSchema(newData); rsData.setSchema(newData);
library.localUpdateTimestamp(newData.id); library.localUpdateTimestamp(newData.id);
oss.invalidateItem(newData.id); oss.invalidateItem(newData.id);
callback?.(); callback?.();
} }
}); });
}, },
[itemID, schema, user, setSchema, library, oss] [itemID, rsData, user, library, oss]
); );
const restoreOrder = useCallback( const restoreOrder = useCallback(
(callback?: () => void) => { (callback?: () => void) => {
if (!schema || !user) { if (!rsData.schema || !user) {
return; return;
} }
setProcessingError(undefined); setProcessingError(undefined);
@ -297,13 +286,13 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: newData => { onSuccess: newData => {
setSchema(newData); rsData.setSchema(newData);
library.localUpdateTimestamp(newData.id); library.localUpdateTimestamp(newData.id);
callback?.(); callback?.();
} }
}); });
}, },
[itemID, schema, user, setSchema, library] [itemID, rsData, user, library]
); );
const produceStructure = useCallback( const produceStructure = useCallback(
@ -315,27 +304,27 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: newData => { onSuccess: newData => {
setSchema(newData.schema); rsData.setSchema(newData.schema);
library.localUpdateTimestamp(newData.schema.id); library.localUpdateTimestamp(newData.schema.id);
oss.invalidateItem(newData.schema.id); oss.invalidateItem(newData.schema.id);
callback?.(newData.cst_list); callback?.(newData.cst_list);
} }
}); });
}, },
[setSchema, itemID, library, oss] [rsData, itemID, library, oss]
); );
const download = useCallback( const download = useCallback(
(callback: DataCallback<Blob>) => { (callback: DataCallback<Blob>) => {
setProcessingError(undefined); setProcessingError(undefined);
getTRSFile(itemID, String(schema?.version ?? ''), { getTRSFile(itemID, String(rsData.schema?.version ?? ''), {
showError: true, showError: true,
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: callback onSuccess: callback
}); });
}, },
[itemID, schema] [itemID, rsData]
); );
const cstCreate = useCallback( const cstCreate = useCallback(
@ -347,14 +336,14 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: newData => { onSuccess: newData => {
setSchema(newData.schema); rsData.setSchema(newData.schema);
library.localUpdateTimestamp(newData.schema.id); library.localUpdateTimestamp(newData.schema.id);
oss.invalidateItem(newData.schema.id); oss.invalidateItem(newData.schema.id);
callback?.(newData.new_cst); callback?.(newData.new_cst);
} }
}); });
}, },
[itemID, setSchema, library, oss] [itemID, rsData, library, oss]
); );
const cstDelete = useCallback( const cstDelete = useCallback(
@ -366,14 +355,14 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: newData => { onSuccess: newData => {
setSchema(newData); rsData.setSchema(newData);
library.localUpdateTimestamp(newData.id); library.localUpdateTimestamp(newData.id);
oss.invalidateItem(newData.id); oss.invalidateItem(newData.id);
callback?.(); callback?.();
} }
}); });
}, },
[itemID, setSchema, library, oss] [itemID, rsData, library, oss]
); );
const cstUpdate = useCallback( const cstUpdate = useCallback(
@ -385,14 +374,14 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: newData => onSuccess: newData =>
reload(setProcessing, () => { rsData.reload(setProcessing, () => {
library.localUpdateTimestamp(Number(itemID)); library.localUpdateTimestamp(Number(itemID));
oss.invalidateItem(Number(itemID)); oss.invalidateItem(Number(itemID));
callback?.(newData); callback?.(newData);
}) })
}); });
}, },
[itemID, reload, library, oss] [itemID, rsData, library, oss]
); );
const cstRename = useCallback( const cstRename = useCallback(
@ -404,14 +393,14 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: newData => { onSuccess: newData => {
setSchema(newData.schema); rsData.setSchema(newData.schema);
library.localUpdateTimestamp(newData.schema.id); library.localUpdateTimestamp(newData.schema.id);
oss.invalidateItem(newData.schema.id); oss.invalidateItem(newData.schema.id);
callback?.(newData.new_cst); callback?.(newData.new_cst);
} }
}); });
}, },
[setSchema, itemID, library, oss] [rsData, itemID, library, oss]
); );
const cstSubstitute = useCallback( const cstSubstitute = useCallback(
@ -423,14 +412,14 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: newData => { onSuccess: newData => {
setSchema(newData); rsData.setSchema(newData);
library.localUpdateTimestamp(newData.id); library.localUpdateTimestamp(newData.id);
oss.invalidateItem(newData.id); oss.invalidateItem(newData.id);
callback?.(); callback?.();
} }
}); });
}, },
[setSchema, itemID, library, oss] [rsData, itemID, library, oss]
); );
const cstMoveTo = useCallback( const cstMoveTo = useCallback(
@ -442,13 +431,13 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: newData => { onSuccess: newData => {
setSchema(newData); rsData.setSchema(newData);
library.localUpdateTimestamp(Number(itemID)); library.localUpdateTimestamp(Number(itemID));
callback?.(); callback?.();
} }
}); });
}, },
[itemID, setSchema, library] [itemID, rsData, library]
); );
const versionCreate = useCallback( const versionCreate = useCallback(
@ -460,13 +449,13 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: newData => { onSuccess: newData => {
setSchema(newData.schema); rsData.setSchema(newData.schema);
library.localUpdateTimestamp(Number(itemID)); library.localUpdateTimestamp(Number(itemID));
callback?.(newData.version); callback?.(newData.version);
} }
}); });
}, },
[itemID, setSchema, library] [itemID, rsData, library]
); );
const findPredecessor = useCallback((data: ITargetCst, callback: (reference: IConstituentaReference) => void) => { const findPredecessor = useCallback((data: ITargetCst, callback: (reference: IConstituentaReference) => void) => {
@ -489,7 +478,7 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: () => { onSuccess: () => {
schema!.versions = schema!.versions.map(prev => { const newVersions = rsData.schema!.versions.map(prev => {
if (prev.id === target) { if (prev.id === target) {
prev.description = data.description; prev.description = data.description;
prev.version = data.version; prev.version = data.version;
@ -498,12 +487,12 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
return prev; return prev;
} }
}); });
setSchema(schema); rsData.partialUpdate({ versions: newVersions });
callback?.(); callback?.();
} }
}); });
}, },
[schema, setSchema] [rsData]
); );
const versionDelete = useCallback( const versionDelete = useCallback(
@ -514,13 +503,13 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: () => { onSuccess: () => {
schema!.versions = schema!.versions.filter(prev => prev.id !== target); const newVersions = rsData.schema!.versions.filter(prev => prev.id !== target);
setSchema(schema); rsData.partialUpdate({ versions: newVersions });
callback?.(); callback?.();
} }
}); });
}, },
[schema, setSchema] [rsData]
); );
const versionRestore = useCallback( const versionRestore = useCallback(
@ -531,13 +520,13 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: newData => { onSuccess: newData => {
setSchema(newData); rsData.setSchema(newData);
library.localUpdateItem(newData); library.localUpdateItem(newData);
callback?.(); callback?.();
} }
}); });
}, },
[setSchema, library] [rsData, library]
); );
const inlineSynthesis = useCallback( const inlineSynthesis = useCallback(
@ -549,24 +538,24 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
setLoading: setProcessing, setLoading: setProcessing,
onError: setProcessingError, onError: setProcessingError,
onSuccess: newData => { onSuccess: newData => {
setSchema(newData); rsData.setSchema(newData);
library.localUpdateTimestamp(newData.id); library.localUpdateTimestamp(newData.id);
oss.invalidateItem(newData.id); oss.invalidateItem(newData.id);
callback?.(newData); callback?.(newData);
} }
}); });
}, },
[setSchema, library, oss] [rsData, library, oss]
); );
return ( return (
<RSFormContext.Provider <RSFormContext
value={{ value={{
schema, schema: rsData.schema,
itemID, itemID,
versionID, versionID,
loading, loading: rsData.loading,
errorLoading, errorLoading: rsData.error,
processing, processing,
processingError, processingError,
isOwned, isOwned,
@ -598,6 +587,6 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
}} }}
> >
{children} {children}
</RSFormContext.Provider> </RSFormContext>
); );
}; };

View File

@ -76,8 +76,8 @@ export const UserProfileState = ({ children }: React.PropsWithChildren) => {
}, [reload]); }, [reload]);
return ( return (
<ProfileContext.Provider value={{ user, updateUser, error, loading, setError, processing, errorProcessing }}> <ProfileContext value={{ user, updateUser, error, loading, setError, processing, errorProcessing }}>
{children} {children}
</ProfileContext.Provider> </ProfileContext>
); );
}; };

View File

@ -81,7 +81,7 @@ export const UsersState = ({ children }: React.PropsWithChildren) => {
}, [reload]); }, [reload]);
return ( return (
<UsersContext.Provider <UsersContext
value={{ value={{
users, users,
reload, reload,
@ -89,6 +89,6 @@ export const UsersState = ({ children }: React.PropsWithChildren) => {
}} }}
> >
{children} {children}
</UsersContext.Provider> </UsersContext>
); );
}; };

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useMemo, useState } from 'react'; import { useState } from 'react';
import { IconReset } from '@/components/Icons'; import { IconReset } from '@/components/Icons';
import PickSchema from '@/components/select/PickSchema'; import PickSchema from '@/components/select/PickSchema';
@ -22,18 +22,16 @@ interface DlgChangeInputSchemaProps extends Pick<ModalProps, 'hideWindow'> {
function DlgChangeInputSchema({ oss, hideWindow, target, onSubmit }: DlgChangeInputSchemaProps) { function DlgChangeInputSchema({ oss, hideWindow, target, onSubmit }: DlgChangeInputSchemaProps) {
const [selected, setSelected] = useState<LibraryItemID | undefined>(target.result ?? undefined); const [selected, setSelected] = useState<LibraryItemID | undefined>(target.result ?? undefined);
const library = useLibrary(); const library = useLibrary();
const sortedItems = useMemo(() => sortItemsForOSS(oss, library.items), [oss, library.items]); const sortedItems = sortItemsForOSS(oss, library.items);
const isValid = target.result !== selected;
const baseFilter = useCallback( function baseFilter(item: ILibraryItem) {
(item: ILibraryItem) => !oss.schemas.includes(item.id) || item.id === selected || item.id === target.result, return !oss.schemas.includes(item.id) || item.id === selected || item.id === target.result;
[oss, selected, target] }
);
const isValid = useMemo(() => target.result !== selected, [target, selected]); function handleSelectLocation(newValue: LibraryItemID) {
const handleSelectLocation = useCallback((newValue: LibraryItemID) => {
setSelected(newValue); setSelected(newValue);
}, []); }
return ( return (
<Modal <Modal

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useMemo, useState } from 'react'; import { useState } from 'react';
import SelectLocationContext from '@/components/select/SelectLocationContext'; import SelectLocationContext from '@/components/select/SelectLocationContext';
import SelectLocationHead from '@/components/select/SelectLocationHead'; import SelectLocationHead from '@/components/select/SelectLocationHead';
@ -26,13 +26,13 @@ function DlgChangeLocation({ hideWindow, initial, onChangeLocation }: DlgChangeL
const { folders } = useLibrary(); const { folders } = useLibrary();
const location = useMemo(() => combineLocation(head, body), [head, body]); const location = combineLocation(head, body);
const isValid = useMemo(() => initial !== location && validateLocation(location), [initial, location]); const isValid = initial !== location && validateLocation(location);
const handleSelectLocation = useCallback((newValue: string) => { function handleSelectLocation(newValue: string) {
setHead(newValue.substring(0, 2) as LocationHead); setHead(newValue.substring(0, 2) as LocationHead);
setBody(newValue.length > 3 ? newValue.substring(3) : ''); setBody(newValue.length > 3 ? newValue.substring(3) : '');
}, []); }
return ( return (
<Modal <Modal

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useMemo, useState } from 'react'; import { useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
@ -43,16 +43,16 @@ function DlgCloneLibraryItem({ hideWindow, base, initialLocation, selected, tota
const [head, setHead] = useState(initialLocation.substring(0, 2) as LocationHead); const [head, setHead] = useState(initialLocation.substring(0, 2) as LocationHead);
const [body, setBody] = useState(initialLocation.substring(3)); const [body, setBody] = useState(initialLocation.substring(3));
const location = useMemo(() => combineLocation(head, body), [head, body]); const location = combineLocation(head, body);
const { cloneItem, folders } = useLibrary(); const { cloneItem, folders } = useLibrary();
const canSubmit = useMemo(() => title !== '' && alias !== '' && validateLocation(location), [title, alias, location]); const canSubmit = title !== '' && alias !== '' && validateLocation(location);
const handleSelectLocation = useCallback((newValue: string) => { function handleSelectLocation(newValue: string) {
setHead(newValue.substring(0, 2) as LocationHead); setHead(newValue.substring(0, 2) as LocationHead);
setBody(newValue.length > 3 ? newValue.substring(3) : ''); setBody(newValue.length > 3 ? newValue.substring(3) : '');
}, []); }
function handleSubmit() { function handleSubmit() {
const data: IRSFormCloneData = { const data: IRSFormCloneData = {

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useLayoutEffect, useMemo, useState } from 'react'; import { useEffect, useState } from 'react';
import { TabList, TabPanel, Tabs } from 'react-tabs'; import { TabList, TabPanel, Tabs } from 'react-tabs';
import Modal, { ModalProps } from '@/components/ui/Modal'; import Modal, { ModalProps } from '@/components/ui/Modal';
@ -65,7 +65,7 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
return true; return true;
} }
useLayoutEffect(() => { useEffect(() => {
if (!template.templateID) { if (!template.templateID) {
setTemplateSchema(undefined); setTemplateSchema(undefined);
} else { } else {
@ -73,7 +73,7 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
} }
}, [template.templateID, retrieveTemplate]); }, [template.templateID, retrieveTemplate]);
useLayoutEffect(() => { useEffect(() => {
if (!template.prototype) { if (!template.prototype) {
updateConstituenta({ updateConstituenta({
definition_raw: '', definition_raw: '',
@ -103,7 +103,7 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
} }
}, [template.prototype, updateConstituenta, updateSubstitutes, schema]); }, [template.prototype, updateConstituenta, updateSubstitutes, schema]);
useLayoutEffect(() => { useEffect(() => {
if (substitutes.arguments.length === 0 || !template.prototype) { if (substitutes.arguments.length === 0 || !template.prototype) {
return; return;
} }
@ -119,39 +119,10 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
}); });
}, [substitutes.arguments, template.prototype, updateConstituenta, updateSubstitutes, schema]); }, [substitutes.arguments, template.prototype, updateConstituenta, updateSubstitutes, schema]);
useLayoutEffect(() => { useEffect(() => {
setValidated(!!template.prototype && validateNewAlias(constituenta.alias, constituenta.cst_type, schema)); setValidated(!!template.prototype && validateNewAlias(constituenta.alias, constituenta.cst_type, schema));
}, [constituenta.alias, constituenta.cst_type, schema, template.prototype]); }, [constituenta.alias, constituenta.cst_type, schema, template.prototype]);
const templatePanel = useMemo(
() => (
<TabPanel>
<TabTemplate state={template} partialUpdate={updateTemplate} templateSchema={templateSchema} />
</TabPanel>
),
[template, templateSchema, updateTemplate]
);
const argumentsPanel = useMemo(
() => (
<TabPanel>
<TabArguments schema={schema} state={substitutes} partialUpdate={updateSubstitutes} />
</TabPanel>
),
[schema, substitutes, updateSubstitutes]
);
const editorPanel = useMemo(
() => (
<TabPanel>
<div className='cc-fade-in cc-column'>
<FormCreateCst state={constituenta} partialUpdate={updateConstituenta} schema={schema} />
</div>
</TabPanel>
),
[constituenta, updateConstituenta, schema]
);
return ( return (
<Modal <Modal
header='Создание конституенты из шаблона' header='Создание конституенты из шаблона'
@ -175,9 +146,19 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
<TabLabel label='Конституента' title='Редактирование конституенты' className='w-[8rem]' /> <TabLabel label='Конституента' title='Редактирование конституенты' className='w-[8rem]' />
</TabList> </TabList>
{templatePanel} <TabPanel>
{argumentsPanel} <TabTemplate state={template} partialUpdate={updateTemplate} templateSchema={templateSchema} />
{editorPanel} </TabPanel>
<TabPanel>
<TabArguments schema={schema} state={substitutes} partialUpdate={updateSubstitutes} />
</TabPanel>
<TabPanel>
<div className='cc-fade-in cc-column'>
<FormCreateCst state={constituenta} partialUpdate={updateConstituenta} schema={schema} />
</div>
</TabPanel>
</Tabs> </Tabs>
</Modal> </Modal>
); );

View File

@ -2,7 +2,7 @@
import { createColumnHelper } from '@tanstack/react-table'; import { createColumnHelper } from '@tanstack/react-table';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useEffect, useState } from 'react';
import { IconAccept, IconRemove, IconReset } from '@/components/Icons'; import { IconAccept, IconRemove, IconReset } from '@/components/Icons';
import RSInput from '@/components/RSInput'; import RSInput from '@/components/RSInput';
@ -10,9 +10,9 @@ 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 NoData from '@/components/ui/NoData';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { IConstituenta, IRSForm } from '@/models/rsform'; import { IConstituenta, IRSForm } from '@/models/rsform';
import { IArgumentValue } from '@/models/rslang'; import { IArgumentValue } from '@/models/rslang';
import { APP_COLORS } from '@/styling/color';
import { prefixes } from '@/utils/constants'; import { prefixes } from '@/utils/constants';
interface TabArgumentsProps { interface TabArgumentsProps {
@ -29,17 +29,10 @@ export interface IArgumentsState {
const argumentsHelper = createColumnHelper<IArgumentValue>(); const argumentsHelper = createColumnHelper<IArgumentValue>();
function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) { function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
const { colors } = useConceptOptions();
const [selectedCst, setSelectedCst] = useState<IConstituenta | undefined>(undefined); const [selectedCst, setSelectedCst] = useState<IConstituenta | undefined>(undefined);
const [selectedArgument, setSelectedArgument] = useState<IArgumentValue | undefined>(undefined); const [selectedArgument, setSelectedArgument] = useState<IArgumentValue | undefined>(undefined);
const [argumentValue, setArgumentValue] = useState(''); const [argumentValue, setArgumentValue] = useState('');
const isModified = selectedArgument && argumentValue !== selectedArgument.value;
const isModified = useMemo(
() => selectedArgument && argumentValue !== selectedArgument.value,
[selectedArgument, argumentValue]
);
useEffect(() => { useEffect(() => {
if (!selectedArgument && state.arguments.length > 0) { if (!selectedArgument && state.arguments.length > 0) {
@ -47,46 +40,39 @@ function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
} }
}, [state.arguments, selectedArgument]); }, [state.arguments, selectedArgument]);
const handleSelectArgument = useCallback((arg: IArgumentValue) => { function handleSelectArgument(arg: IArgumentValue) {
setSelectedArgument(arg); setSelectedArgument(arg);
if (arg.value) { if (arg.value) {
setArgumentValue(arg.value); setArgumentValue(arg.value);
} }
}, []); }
const handleSelectConstituenta = useCallback((cst: IConstituenta) => { function handleSelectConstituenta(cst: IConstituenta) {
setSelectedCst(cst); setSelectedCst(cst);
setArgumentValue(cst.alias); setArgumentValue(cst.alias);
}, []); }
const handleClearArgument = useCallback( function handleClearArgument(target: IArgumentValue) {
(target: IArgumentValue) => {
const newArg = { ...target, value: '' }; const newArg = { ...target, value: '' };
partialUpdate({ partialUpdate({
arguments: state.arguments.map(arg => (arg.alias !== target.alias ? arg : newArg)) arguments: state.arguments.map(arg => (arg.alias !== target.alias ? arg : newArg))
}); });
setSelectedArgument(newArg); setSelectedArgument(newArg);
}, }
[partialUpdate, state.arguments]
);
const handleReset = useCallback(() => { function handleReset() {
setArgumentValue(selectedArgument?.value ?? ''); setArgumentValue(selectedArgument?.value ?? '');
}, [selectedArgument]); }
const handleAssignArgument = useCallback( function handleAssignArgument(target: IArgumentValue, value: string) {
(target: IArgumentValue, value: string) => {
const newArg = { ...target, value: value }; const newArg = { ...target, value: value };
partialUpdate({ partialUpdate({
arguments: state.arguments.map(arg => (arg.alias !== target.alias ? arg : newArg)) arguments: state.arguments.map(arg => (arg.alias !== target.alias ? arg : newArg))
}); });
setSelectedArgument(newArg); setSelectedArgument(newArg);
}, }
[partialUpdate, state.arguments]
);
const columns = useMemo( const columns = [
() => [
argumentsHelper.accessor('alias', { argumentsHelper.accessor('alias', {
id: 'alias', id: 'alias',
size: 40, size: 40,
@ -131,19 +117,14 @@ function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
</div> </div>
) )
}) })
], ];
[handleClearArgument]
);
const conditionalRowStyles = useMemo( const conditionalRowStyles: IConditionalStyle<IArgumentValue>[] = [
(): IConditionalStyle<IArgumentValue>[] => [
{ {
when: (arg: IArgumentValue) => arg.alias === selectedArgument?.alias, when: (arg: IArgumentValue) => arg.alias === selectedArgument?.alias,
style: { backgroundColor: colors.bgSelected } style: { backgroundColor: APP_COLORS.bgSelected }
} }
], ];
[selectedArgument, colors]
);
return ( return (
<div className='cc-fade-in'> <div className='cc-fade-in'>

View File

@ -1,6 +1,6 @@
'use client'; 'use client';
import { Dispatch, useEffect, useMemo, useState } from 'react'; import { Dispatch, useEffect, useState } from 'react';
import RSInput from '@/components/RSInput'; import RSInput from '@/components/RSInput';
import PickConstituenta from '@/components/select/PickConstituenta'; import PickConstituenta from '@/components/select/PickConstituenta';
@ -28,36 +28,23 @@ function TabTemplate({ state, partialUpdate, templateSchema }: TabTemplateProps)
const [filteredData, setFilteredData] = useState<IConstituenta[]>([]); const [filteredData, setFilteredData] = useState<IConstituenta[]>([]);
const prototypeInfo = useMemo(() => { const prototypeInfo = !state.prototype
if (!state.prototype) { ? ''
return ''; : `${state.prototype?.term_raw}${state.prototype?.definition_raw ? `${state.prototype?.definition_raw}` : ''}`;
} else {
return `${state.prototype?.term_raw}${
state.prototype?.definition_raw ? `${state.prototype?.definition_raw}` : ''
}`;
}
}, [state.prototype]);
const templateSelector = useMemo( const templateSelector = templates.map(template => ({
() =>
templates.map(template => ({
value: template.id, value: template.id,
label: template.title label: template.title
})), }));
[templates]
);
const categorySelector = useMemo((): { value: number; label: string }[] => { const categorySelector: { value: number; label: string }[] = !templateSchema
if (!templateSchema) { ? []
return []; : templateSchema.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
})); }));
}, [templateSchema]);
useEffect(() => { useEffect(() => {
if (templates.length > 0 && !state.templateID) { if (templates.length > 0 && !state.templateID) {

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; import { useEffect, useState } from 'react';
import BadgeHelp from '@/components/info/BadgeHelp'; import BadgeHelp from '@/components/info/BadgeHelp';
import RSInput from '@/components/RSInput'; import RSInput from '@/components/RSInput';
@ -26,24 +26,23 @@ interface FormCreateCstProps {
function FormCreateCst({ schema, state, partialUpdate, setValidated }: FormCreateCstProps) { function FormCreateCst({ schema, state, partialUpdate, setValidated }: FormCreateCstProps) {
const [forceComment, setForceComment] = useState(false); const [forceComment, setForceComment] = useState(false);
const isBasic = useMemo(() => isBasicConcept(state.cst_type), [state]); const isBasic = isBasicConcept(state.cst_type);
const isElementary = useMemo(() => isBaseSet(state.cst_type), [state]); const isElementary = isBaseSet(state.cst_type);
const showConvention = useMemo(() => !!state.convention || forceComment || isBasic, [state, forceComment, isBasic]); const showConvention = !!state.convention || forceComment || isBasic;
useLayoutEffect(() => { useEffect(() => {
setForceComment(false); setForceComment(false);
}, [state.cst_type, partialUpdate, schema]); }, [state.cst_type, partialUpdate, schema]);
useLayoutEffect(() => { useEffect(() => {
if (setValidated) { if (setValidated) {
setValidated(validateNewAlias(state.alias, state.cst_type, schema)); setValidated(validateNewAlias(state.alias, state.cst_type, schema));
} }
}, [state.alias, state.cst_type, schema, setValidated]); }, [state.alias, state.cst_type, schema, setValidated]);
const handleTypeChange = useCallback( function handleTypeChange(target: CstType) {
(target: CstType) => partialUpdate({ cst_type: target, alias: generateAlias(target, schema) }), return partialUpdate({ cst_type: target, alias: generateAlias(target, schema) });
[partialUpdate, schema] }
);
return ( return (
<> <>
@ -120,7 +119,7 @@ function FormCreateCst({ schema, state, partialUpdate, setValidated }: FormCreat
id='dlg_cst_show_comment' id='dlg_cst_show_comment'
tabIndex={-1} tabIndex={-1}
type='button' type='button'
className='self-start cc-label clr-text-url hover:underline' className='self-start cc-label text-sec-600 hover:underline'
onClick={() => setForceComment(true)} onClick={() => setForceComment(true)}
> >
Добавить комментарий Добавить комментарий

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; import { useEffect, useState } from 'react';
import { TabList, TabPanel, Tabs } from 'react-tabs'; import { TabList, TabPanel, Tabs } from 'react-tabs';
import Modal from '@/components/ui/Modal'; import Modal from '@/components/ui/Modal';
@ -38,7 +38,7 @@ function DlgCreateOperation({ hideWindow, oss, onCreate, initialInputs }: DlgCre
const [attachedID, setAttachedID] = useState<LibraryItemID | undefined>(undefined); const [attachedID, setAttachedID] = useState<LibraryItemID | undefined>(undefined);
const [createSchema, setCreateSchema] = useState(false); const [createSchema, setCreateSchema] = useState(false);
const isValid = useMemo(() => { const isValid = (() => {
if (alias === '') { if (alias === '') {
return false; return false;
} }
@ -51,9 +51,9 @@ function DlgCreateOperation({ hideWindow, oss, onCreate, initialInputs }: DlgCre
} }
} }
return true; return true;
}, [alias, activeTab, inputs, attachedID, oss.items]); })();
useLayoutEffect(() => { useEffect(() => {
if (attachedID) { if (attachedID) {
const schema = library.items.find(value => value.id === attachedID); const schema = library.items.find(value => value.id === attachedID);
if (schema) { if (schema) {
@ -82,8 +82,7 @@ function DlgCreateOperation({ hideWindow, oss, onCreate, initialInputs }: DlgCre
onCreate(data); onCreate(data);
}; };
const handleSelectTab = useCallback( function handleSelectTab(newTab: TabID, last: TabID) {
(newTab: TabID, last: TabID) => {
if (last === newTab) { if (last === newTab) {
return; return;
} }
@ -93,49 +92,7 @@ function DlgCreateOperation({ hideWindow, oss, onCreate, initialInputs }: DlgCre
setInputs(initialInputs); setInputs(initialInputs);
} }
setActiveTab(newTab); setActiveTab(newTab);
}, }
[setActiveTab, initialInputs]
);
const inputPanel = useMemo(
() => (
<TabPanel>
<TabInputOperation
oss={oss}
alias={alias}
onChangeAlias={setAlias}
comment={comment}
onChangeComment={setComment}
title={title}
onChangeTitle={setTitle}
attachedID={attachedID}
onChangeAttachedID={setAttachedID}
createSchema={createSchema}
onChangeCreateSchema={setCreateSchema}
/>
</TabPanel>
),
[alias, comment, title, attachedID, oss, createSchema, setAlias]
);
const synthesisPanel = useMemo(
() => (
<TabPanel>
<TabSynthesisOperation
oss={oss}
alias={alias}
onChangeAlias={setAlias}
comment={comment}
onChangeComment={setComment}
title={title}
onChangeTitle={setTitle}
inputs={inputs}
setInputs={setInputs}
/>
</TabPanel>
),
[oss, alias, comment, title, inputs, setAlias]
);
return ( return (
<Modal <Modal
@ -164,8 +121,35 @@ function DlgCreateOperation({ hideWindow, oss, onCreate, initialInputs }: DlgCre
/> />
</TabList> </TabList>
{inputPanel} <TabPanel>
{synthesisPanel} <TabInputOperation
oss={oss}
alias={alias}
onChangeAlias={setAlias}
comment={comment}
onChangeComment={setComment}
title={title}
onChangeTitle={setTitle}
attachedID={attachedID}
onChangeAttachedID={setAttachedID}
createSchema={createSchema}
onChangeCreateSchema={setCreateSchema}
/>
</TabPanel>
<TabPanel>
<TabSynthesisOperation
oss={oss}
alias={alias}
onChangeAlias={setAlias}
comment={comment}
onChangeComment={setComment}
title={title}
onChangeTitle={setTitle}
inputs={inputs}
setInputs={setInputs}
/>
</TabPanel>
</Tabs> </Tabs>
</Modal> </Modal>
); );

View File

@ -1,6 +1,6 @@
'use client'; 'use client';
import { useCallback, useEffect, useMemo } from 'react'; import { useEffect } from 'react';
import { IconReset } from '@/components/Icons'; import { IconReset } from '@/components/Icons';
import PickSchema from '@/components/select/PickSchema'; import PickSchema from '@/components/select/PickSchema';
@ -41,9 +41,12 @@ function TabInputOperation({
createSchema, createSchema,
onChangeCreateSchema onChangeCreateSchema
}: TabInputOperationProps) { }: TabInputOperationProps) {
const baseFilter = useCallback((item: ILibraryItem) => !oss.schemas.includes(item.id), [oss]);
const library = useLibrary(); const library = useLibrary();
const sortedItems = useMemo(() => sortItemsForOSS(oss, library.items), [oss, library.items]); const sortedItems = sortItemsForOSS(oss, library.items);
function baseFilter(item: ILibraryItem) {
return !oss.schemas.includes(item.id);
}
useEffect(() => { useEffect(() => {
if (createSchema) { if (createSchema) {

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useMemo, useState } from 'react'; import { useState } from 'react';
import Checkbox from '@/components/ui/Checkbox'; import Checkbox from '@/components/ui/Checkbox';
import Modal, { ModalProps } from '@/components/ui/Modal'; import Modal, { ModalProps } from '@/components/ui/Modal';
@ -23,9 +23,7 @@ function DlgCreateVersion({ hideWindow, versions, selected, totalCount, onCreate
const [description, setDescription] = useState(''); const [description, setDescription] = useState('');
const [onlySelected, setOnlySelected] = useState(false); const [onlySelected, setOnlySelected] = useState(false);
const canSubmit = useMemo(() => { const canSubmit = !versions.find(ver => ver.version === version);
return !versions.find(ver => ver.version === version);
}, [versions, version]);
function handleSubmit() { function handleSubmit() {
const data: IVersionCreateData = { const data: IVersionCreateData = {

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useMemo, useState } from 'react'; import { useState } from 'react';
import Checkbox from '@/components/ui/Checkbox'; import Checkbox from '@/components/ui/Checkbox';
import Modal, { ModalProps } from '@/components/ui/Modal'; import Modal, { ModalProps } from '@/components/ui/Modal';
@ -18,12 +18,9 @@ interface DlgDeleteCstProps extends Pick<ModalProps, 'hideWindow'> {
function DlgDeleteCst({ hideWindow, selected, schema, onDelete }: DlgDeleteCstProps) { function DlgDeleteCst({ hideWindow, selected, schema, onDelete }: DlgDeleteCstProps) {
const [expandOut, setExpandOut] = useState(false); const [expandOut, setExpandOut] = useState(false);
const expansion: ConstituentaID[] = useMemo( const expansion: ConstituentaID[] = schema.graph.expandAllOutputs(selected);
() => schema.graph.expandAllOutputs(selected), // prettier: split-lines const hasInherited = selected.some(
[selected, schema.graph] id => schema.inheritance.find(item => item.parent === id),
);
const hasInherited = useMemo(
() => selected.some(id => schema.inheritance.find(item => item.parent === id), [selected, schema.inheritance]),
[selected, schema.inheritance] [selected, schema.inheritance]
); );

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useMemo, useState } from 'react'; import { useState } from 'react';
import { IconRemove } from '@/components/Icons'; import { IconRemove } from '@/components/Icons';
import SelectUser from '@/components/select/SelectUser'; import SelectUser from '@/components/select/SelectUser';
@ -22,20 +22,19 @@ interface DlgEditEditorsProps {
function DlgEditEditors({ hideWindow, editors, setEditors }: DlgEditEditorsProps) { function DlgEditEditors({ hideWindow, editors, setEditors }: DlgEditEditorsProps) {
const [selected, setSelected] = useState<UserID[]>(editors); const [selected, setSelected] = useState<UserID[]>(editors);
const { users } = useUsers(); const { users } = useUsers();
const filtered = useMemo(() => users.filter(user => !selected.includes(user.id)), [users, selected]); const filtered = users.filter(user => !selected.includes(user.id));
function handleSubmit() { function handleSubmit() {
setEditors(selected); setEditors(selected);
} }
const onDeleteEditor = useCallback((target: UserID) => setSelected(prev => prev.filter(id => id !== target)), []); function onDeleteEditor(target: UserID) {
setSelected(prev => prev.filter(id => id !== target));
}
const onAddEditor = useCallback((target: UserID) => setSelected(prev => [...prev, target]), []); function onAddEditor(target: UserID) {
setSelected(prev => [...prev, target]);
const usersTable = useMemo( }
() => <TableUsers items={users.filter(user => selected.includes(user.id))} onDelete={onDeleteEditor} />,
[users, selected, onDeleteEditor]
);
return ( return (
<Modal <Modal
@ -58,7 +57,7 @@ function DlgEditEditors({ hideWindow, editors, setEditors }: DlgEditEditorsProps
/> />
</div> </div>
{usersTable} <TableUsers items={users.filter(user => selected.includes(user.id))} onDelete={onDeleteEditor} />
<div className='flex items-center gap-3'> <div className='flex items-center gap-3'>
<Label text='Добавить' /> <Label text='Добавить' />

View File

@ -1,7 +1,5 @@
'use client'; 'use client';
import { useMemo } from 'react';
import { IconRemove } from '@/components/Icons'; import { IconRemove } from '@/components/Icons';
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';
@ -15,8 +13,7 @@ interface TableUsersProps {
const columnHelper = createColumnHelper<IUserInfo>(); const columnHelper = createColumnHelper<IUserInfo>();
function TableUsers({ items, onDelete }: TableUsersProps) { function TableUsers({ items, onDelete }: TableUsersProps) {
const columns = useMemo( const columns = [
() => [
columnHelper.accessor('last_name', { columnHelper.accessor('last_name', {
id: 'last_name', id: 'last_name',
size: 400, size: 400,
@ -42,9 +39,7 @@ function TableUsers({ items, onDelete }: TableUsersProps) {
</div> </div>
) )
}) })
], ];
[onDelete]
);
return ( return (
<DataTable <DataTable

View File

@ -1,12 +1,13 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { TabList, TabPanel, Tabs } from 'react-tabs'; import { TabList, TabPanel, Tabs } from 'react-tabs';
import Modal from '@/components/ui/Modal'; import Modal from '@/components/ui/Modal';
import TabLabel from '@/components/ui/TabLabel'; import TabLabel from '@/components/ui/TabLabel';
import useRSFormCache from '@/hooks/useRSFormCache'; import useRSFormCache from '@/hooks/useRSFormCache';
import { LibraryItemID } from '@/models/library';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { import {
ICstSubstitute, ICstSubstitute,
@ -17,6 +18,7 @@ import {
OperationType OperationType
} from '@/models/oss'; } from '@/models/oss';
import { SubstitutionValidator } from '@/models/ossAPI'; import { SubstitutionValidator } from '@/models/ossAPI';
import { ConstituentaID } from '@/models/rsform';
import TabArguments from './TabArguments'; import TabArguments from './TabArguments';
import TabOperation from './TabOperation'; import TabOperation from './TabOperation';
@ -45,73 +47,73 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
const [isCorrect, setIsCorrect] = useState(true); const [isCorrect, setIsCorrect] = useState(true);
const [validationText, setValidationText] = useState(''); const [validationText, setValidationText] = useState('');
const initialInputs = useMemo(() => oss.graph.expandInputs([target.id]), [oss.graph, target.id]); const initialInputs = oss.graph.expandInputs([target.id]);
const [inputs, setInputs] = useState<OperationID[]>(initialInputs); const [inputs, setInputs] = useState<OperationID[]>(initialInputs);
const inputOperations = useMemo(() => inputs.map(id => oss.operationByID.get(id)!), [inputs, oss.operationByID]); const inputOperations = inputs.map(id => oss.operationByID.get(id)!);
const schemasIDs = useMemo(
() => inputOperations.map(operation => operation.result).filter(id => id !== null), const [needPreload, setNeedPreload] = useState(false);
[inputOperations] const [schemasIDs, setSchemaIDs] = useState<LibraryItemID[]>([]);
);
const [substitutions, setSubstitutions] = useState<ICstSubstitute[]>(target.substitutions); const [substitutions, setSubstitutions] = useState<ICstSubstitute[]>(target.substitutions);
const [suggestions, setSuggestions] = useState<ICstSubstitute[]>([]); const [suggestions, setSuggestions] = useState<ICstSubstitute[]>([]);
const cache = useRSFormCache(); const cache = useRSFormCache();
const schemas = useMemo( const schemas = schemasIDs.map(id => cache.data.find(item => item.id === id)).filter(item => item !== undefined);
() => schemasIDs.map(id => cache.getSchema(id)).filter(item => item !== undefined),
// eslint-disable-next-line react-hooks/exhaustive-deps
[schemasIDs, cache.getSchema]
);
const isModified = useMemo( const isModified =
() =>
alias !== target.alias || alias !== target.alias ||
title !== target.title || title !== target.title ||
comment !== target.comment || comment !== target.comment ||
JSON.stringify(initialInputs) !== JSON.stringify(inputs) || JSON.stringify(initialInputs) !== JSON.stringify(inputs) ||
JSON.stringify(substitutions) !== JSON.stringify(target.substitutions), JSON.stringify(substitutions) !== JSON.stringify(target.substitutions);
[
alias, const canSubmit = isModified && alias !== '';
title,
comment, const getSchemaByCst = useCallback(
target.alias, (id: ConstituentaID) => {
target.title, for (const schema of cache.data) {
target.comment, const cst = schema.items.find(cst => cst.id === id);
initialInputs, if (cst) {
inputs, return schema;
substitutions, }
target.substitutions }
] return undefined;
},
[cache.data]
); );
const canSubmit = useMemo(() => isModified && alias !== '', [isModified, alias]); useEffect(() => {
setNeedPreload(true);
setSchemaIDs(inputOperations.map(operation => operation.result).filter(id => id !== null));
}, [inputOperations]);
useLayoutEffect(() => { useEffect(() => {
if (needPreload) {
setNeedPreload(false);
cache.preload(schemasIDs); cache.preload(schemasIDs);
// eslint-disable-next-line react-hooks/exhaustive-deps }
}, [schemasIDs]); }, [schemasIDs, needPreload, cache]);
useLayoutEffect(() => { useEffect(() => {
if (cache.loading || schemas.length !== schemasIDs.length) { if (cache.loading || schemas.length !== schemasIDs.length) {
return; return;
} }
setSubstitutions(prev => setSubstitutions(prev =>
prev.filter(sub => { prev.filter(sub => {
const original = cache.getSchemaByCst(sub.original); const original = getSchemaByCst(sub.original);
if (!original || !schemasIDs.includes(original.id)) { if (!original || !schemasIDs.includes(original.id)) {
return false; return false;
} }
const substitution = cache.getSchemaByCst(sub.substitution); const substitution = getSchemaByCst(sub.substitution);
if (!substitution || !schemasIDs.includes(substitution.id)) { if (!substitution || !schemasIDs.includes(substitution.id)) {
return false; return false;
} }
return true; return true;
}) })
); );
// eslint-disable-next-line react-hooks/exhaustive-deps }, [schemasIDs, schemas, cache.loading, getSchemaByCst]);
}, [schemasIDs, schemas, cache.loading]);
useLayoutEffect(() => { useEffect(() => {
if (cache.loading || schemas.length !== schemasIDs.length) { if (cache.loading || schemas.length !== schemasIDs.length) {
return; return;
} }
@ -121,7 +123,7 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
setSuggestions(validator.suggestions); setSuggestions(validator.suggestions);
}, [substitutions, cache.loading, schemas, schemasIDs.length]); }, [substitutions, cache.loading, schemas, schemasIDs.length]);
const handleSubmit = useCallback(() => { function handleSubmit() {
const data: IOperationUpdateData = { const data: IOperationUpdateData = {
target: target.id, target: target.id,
item_data: { item_data: {
@ -134,55 +136,7 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
substitutions: target.operation_type !== OperationType.SYNTHESIS ? undefined : substitutions substitutions: target.operation_type !== OperationType.SYNTHESIS ? undefined : substitutions
}; };
onSubmit(data); onSubmit(data);
}, [alias, comment, title, inputs, substitutions, target, onSubmit]); }
const cardPanel = useMemo(
() => (
<TabPanel>
<TabOperation
alias={alias}
onChangeAlias={setAlias}
comment={comment}
onChangeComment={setComment}
title={title}
onChangeTitle={setTitle}
/>
</TabPanel>
),
[alias, comment, title, setAlias]
);
const argumentsPanel = useMemo(
() => (
<TabPanel>
<TabArguments
target={target.id} // prettier: split-lines
oss={oss}
inputs={inputs}
setInputs={setInputs}
/>
</TabPanel>
),
[oss, target, inputs, setInputs]
);
const synthesisPanel = useMemo(
() => (
<TabPanel>
<TabSynthesis
schemas={schemas}
loading={cache.loading}
error={cache.error}
validationText={validationText}
isCorrect={isCorrect}
substitutions={substitutions}
setSubstitutions={setSubstitutions}
suggestions={suggestions}
/>
</TabPanel>
),
[cache.loading, cache.error, substitutions, suggestions, schemas, validationText, isCorrect]
);
return ( return (
<Modal <Modal
@ -215,9 +169,41 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
) : null} ) : null}
</TabList> </TabList>
{cardPanel} <TabPanel>
{target.operation_type === OperationType.SYNTHESIS ? argumentsPanel : null} <TabOperation
{target.operation_type === OperationType.SYNTHESIS ? synthesisPanel : null} alias={alias}
onChangeAlias={setAlias}
comment={comment}
onChangeComment={setComment}
title={title}
onChangeTitle={setTitle}
/>
</TabPanel>
{target.operation_type === OperationType.SYNTHESIS ? (
<TabPanel>
<TabArguments
target={target.id} // prettier: split-lines
oss={oss}
inputs={inputs}
setInputs={setInputs}
/>
</TabPanel>
) : null}
{target.operation_type === OperationType.SYNTHESIS ? (
<TabPanel>
<TabSynthesis
schemas={schemas}
loading={cache.loading}
error={cache.error}
validationText={validationText}
isCorrect={isCorrect}
substitutions={substitutions}
setSubstitutions={setSubstitutions}
suggestions={suggestions}
/>
</TabPanel>
) : null}
</Tabs> </Tabs>
</Modal> </Modal>
); );

View File

@ -1,7 +1,5 @@
'use client'; 'use client';
import { useMemo } from 'react';
import PickMultiOperation from '@/components/select/PickMultiOperation'; import PickMultiOperation from '@/components/select/PickMultiOperation';
import FlexColumn from '@/components/ui/FlexColumn'; import FlexColumn from '@/components/ui/FlexColumn';
import Label from '@/components/ui/Label'; import Label from '@/components/ui/Label';
@ -15,11 +13,8 @@ interface TabArgumentsProps {
} }
function TabArguments({ oss, inputs, target, setInputs }: TabArgumentsProps) { function TabArguments({ oss, inputs, target, setInputs }: TabArgumentsProps) {
const potentialCycle = useMemo(() => [target, ...oss.graph.expandAllOutputs([target])], [target, oss.graph]); const potentialCycle = [target, ...oss.graph.expandAllOutputs([target])];
const filtered = useMemo( const filtered = oss.items.filter(item => !potentialCycle.includes(item.id));
() => oss.items.filter(item => !potentialCycle.includes(item.id)),
[oss.items, potentialCycle]
);
return ( return (
<div className='cc-fade-in cc-column'> <div className='cc-fade-in cc-column'>
<FlexColumn> <FlexColumn>

View File

@ -2,9 +2,9 @@ import { ErrorData } from '@/components/info/InfoError';
import PickSubstitutions from '@/components/select/PickSubstitutions'; import PickSubstitutions from '@/components/select/PickSubstitutions';
import TextArea from '@/components/ui/TextArea'; import TextArea from '@/components/ui/TextArea';
import DataLoader from '@/components/wrap/DataLoader'; import DataLoader from '@/components/wrap/DataLoader';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { ICstSubstitute } from '@/models/oss'; import { ICstSubstitute } from '@/models/oss';
import { IRSForm } from '@/models/rsform'; import { IRSForm } from '@/models/rsform';
import { APP_COLORS } from '@/styling/color';
import { prefixes } from '@/utils/constants'; import { prefixes } from '@/utils/constants';
interface TabSynthesisProps { interface TabSynthesisProps {
@ -29,7 +29,6 @@ function TabSynthesis({
setSubstitutions, setSubstitutions,
suggestions suggestions
}: TabSynthesisProps) { }: TabSynthesisProps) {
const { colors } = useConceptOptions();
return ( return (
<DataLoader isLoading={loading} error={error}> <DataLoader isLoading={loading} error={error}>
<div className='cc-fade-in cc-column mt-3'> <div className='cc-fade-in cc-column mt-3'>
@ -45,7 +44,7 @@ function TabSynthesis({
disabled disabled
value={validationText} value={validationText}
rows={4} rows={4}
style={{ borderColor: isCorrect ? undefined : colors.fgRed }} style={{ borderColor: isCorrect ? undefined : APP_COLORS.fgRed }}
/> />
</div> </div>
</DataLoader> </DataLoader>

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useMemo, useState } from 'react'; import { useState } from 'react';
import { TabList, TabPanel, Tabs } from 'react-tabs'; import { TabList, TabPanel, Tabs } from 'react-tabs';
import Modal from '@/components/ui/Modal'; import Modal from '@/components/ui/Modal';
@ -36,42 +36,16 @@ export enum TabID {
function DlgEditReference({ hideWindow, schema, initial, onSave }: DlgEditReferenceProps) { function DlgEditReference({ hideWindow, schema, initial, onSave }: DlgEditReferenceProps) {
const [activeTab, setActiveTab] = useState(initial.type === ReferenceType.ENTITY ? TabID.ENTITY : TabID.SYNTACTIC); const [activeTab, setActiveTab] = useState(initial.type === ReferenceType.ENTITY ? TabID.ENTITY : TabID.SYNTACTIC);
const [reference, setReference] = useState(''); const [reference, setReference] = useState('');
const [isValid, setIsValid] = useState(false); const [isValid, setIsValid] = useState(false);
const handleSubmit = () => onSave(reference);
const entityPanel = useMemo(
() => (
<TabPanel>
<TabEntityReference
initial={initial}
schema={schema}
onChangeReference={setReference}
onChangeValid={setIsValid}
/>
</TabPanel>
),
[initial, schema]
);
const syntacticPanel = useMemo(
() => (
<TabPanel>
<TabSyntacticReference initial={initial} onChangeReference={setReference} onChangeValid={setIsValid} />
</TabPanel>
),
[initial]
);
return ( return (
<Modal <Modal
header='Редактирование ссылки' header='Редактирование ссылки'
submitText='Сохранить ссылку' submitText='Сохранить ссылку'
hideWindow={hideWindow} hideWindow={hideWindow}
canSubmit={isValid} canSubmit={isValid}
onSubmit={handleSubmit} onSubmit={() => onSave(reference)}
className='w-[40rem] px-6 h-[32rem]' className='w-[40rem] px-6 h-[32rem]'
helpTopic={HelpTopic.TERM_CONTROL} helpTopic={HelpTopic.TERM_CONTROL}
> >
@ -89,8 +63,18 @@ function DlgEditReference({ hideWindow, schema, initial, onSave }: DlgEditRefere
/> />
</TabList> </TabList>
{entityPanel} <TabPanel>
{syntacticPanel} <TabEntityReference
initial={initial}
schema={schema}
onChangeReference={setReference}
onChangeValid={setIsValid}
/>
</TabPanel>
<TabPanel>
<TabSyntacticReference initial={initial} onChangeReference={setReference} onChangeValid={setIsValid} />
</TabPanel>
</Tabs> </Tabs>
</Modal> </Modal>
); );

View File

@ -1,6 +1,6 @@
'use client'; 'use client';
import { useEffect, useLayoutEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import PickConstituenta from '@/components/select/PickConstituenta'; import PickConstituenta from '@/components/select/PickConstituenta';
import SelectMultiGrammeme from '@/components/select/SelectMultiGrammeme'; import SelectMultiGrammeme from '@/components/select/SelectMultiGrammeme';
@ -31,7 +31,7 @@ function TabEntityReference({ initial, schema, onChangeValid, onChangeReference
const [selectedGrams, setSelectedGrams] = useState<IGrammemeOption[]>([]); const [selectedGrams, setSelectedGrams] = useState<IGrammemeOption[]>([]);
// Initialization // Initialization
useLayoutEffect(() => { useEffect(() => {
if (!!initial.refRaw && initial.type === ReferenceType.ENTITY) { if (!!initial.refRaw && initial.type === ReferenceType.ENTITY) {
const ref = parseEntityReference(initial.refRaw); const ref = parseEntityReference(initial.refRaw);
setAlias(ref.entity); setAlias(ref.entity);

View File

@ -1,6 +1,6 @@
'use client'; 'use client';
import { useEffect, useLayoutEffect, useMemo, useState } from 'react'; import { useEffect, useState } from 'react';
import TextInput from '@/components/ui/TextInput'; import TextInput from '@/components/ui/TextInput';
import { ReferenceType } from '@/models/language'; import { ReferenceType } from '@/models/language';
@ -18,16 +18,16 @@ function TabSyntacticReference({ initial, onChangeValid, onChangeReference }: Ta
const [nominal, setNominal] = useState(''); const [nominal, setNominal] = useState('');
const [offset, setOffset] = useState(1); const [offset, setOffset] = useState(1);
const mainLink = useMemo(() => { const mainLink = (() => {
const position = offset > 0 ? initial.basePosition + (offset - 1) : initial.basePosition + offset; const position = offset > 0 ? initial.basePosition + (offset - 1) : initial.basePosition + offset;
if (offset === 0 || position < 0 || position >= initial.mainRefs.length) { if (offset === 0 || position < 0 || position >= initial.mainRefs.length) {
return 'Некорректное значение смещения'; return 'Некорректное значение смещения';
} else { } else {
return initial.mainRefs[position]; return initial.mainRefs[position];
} }
}, [initial, offset]); })();
useLayoutEffect(() => { useEffect(() => {
if (initial.refRaw && initial.type === ReferenceType.SYNTACTIC) { if (initial.refRaw && initial.type === ReferenceType.SYNTACTIC) {
const ref = parseSyntacticReference(initial.refRaw); const ref = parseSyntacticReference(initial.refRaw);
setOffset(ref.offset); setOffset(ref.offset);

View File

@ -21,7 +21,7 @@ function WordformButton({ text, example, grams, onSelectGrams, isSelected, ...re
'p-1', 'p-1',
'border rounded-none', 'border rounded-none',
'cursor-pointer', 'cursor-pointer',
'clr-btn-clear clr-hover', 'clr-text-controls clr-hover cc-animate-color',
isSelected && 'clr-selected' isSelected && 'clr-selected'
)} )}
{...restProps} {...restProps}

View File

@ -1,6 +1,6 @@
'use client'; 'use client';
import { useLayoutEffect, useMemo, useState } from 'react'; import { useEffect, useState } from 'react';
import { IconReset, IconSave } from '@/components/Icons'; import { IconReset, IconSave } from '@/components/Icons';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
@ -26,19 +26,8 @@ function DlgEditVersions({ hideWindow, versions, onDelete, onUpdate }: DlgEditVe
const [version, setVersion] = useState(''); const [version, setVersion] = useState('');
const [description, setDescription] = useState(''); const [description, setDescription] = useState('');
const isValid = useMemo(() => { const isValid = selected && versions.every(ver => ver.id === selected.id || ver.version != version);
if (!selected) { const isModified = selected && (selected.version != version || selected.description != description);
return false;
}
return versions.every(ver => ver.id === selected.id || ver.version != version);
}, [selected, version, versions]);
const isModified = useMemo(() => {
if (!selected) {
return false;
}
return selected.version != version || selected.description != description;
}, [version, description, selected]);
function handleUpdate() { function handleUpdate() {
if (!isModified || !selected || processing || !isValid) { if (!isModified || !selected || processing || !isValid) {
@ -59,24 +48,11 @@ function DlgEditVersions({ hideWindow, versions, onDelete, onUpdate }: DlgEditVe
setDescription(selected?.description ?? ''); setDescription(selected?.description ?? '');
} }
useLayoutEffect(() => { useEffect(() => {
setVersion(selected?.version ?? ''); setVersion(selected?.version ?? '');
setDescription(selected?.description ?? ''); setDescription(selected?.description ?? '');
}, [selected]); }, [selected]);
const versionsTable = useMemo(
() => (
<TableVersions
processing={processing}
items={versions}
onDelete={onDelete}
onSelect={versionID => setSelected(versions.find(ver => ver.id === versionID))}
selected={selected?.id}
/>
),
[processing, versions, onDelete, selected?.id]
);
return ( return (
<Modal <Modal
readonly readonly
@ -84,7 +60,14 @@ function DlgEditVersions({ hideWindow, versions, onDelete, onUpdate }: DlgEditVe
hideWindow={hideWindow} hideWindow={hideWindow}
className='flex flex-col w-[40rem] px-6 gap-3 pb-6' className='flex flex-col w-[40rem] px-6 gap-3 pb-6'
> >
{versionsTable} <TableVersions
processing={processing}
items={versions}
onDelete={onDelete}
onSelect={versionID => setSelected(versions.find(ver => ver.id === versionID))}
selected={selected?.id}
/>
<div className='flex'> <div className='flex'>
<TextInput <TextInput
id='dlg_version' id='dlg_version'

View File

@ -1,14 +1,13 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useMemo } from 'react';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { IconRemove } from '@/components/Icons'; import { IconRemove } from '@/components/Icons';
import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/ui/DataTable'; import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/ui/DataTable';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { IVersionInfo, VersionID } from '@/models/library'; import { IVersionInfo, VersionID } from '@/models/library';
import { APP_COLORS } from '@/styling/color';
interface TableVersionsProps { interface TableVersionsProps {
processing: boolean; processing: boolean;
@ -22,10 +21,7 @@ const columnHelper = createColumnHelper<IVersionInfo>();
function TableVersions({ processing, items, onDelete, selected, onSelect }: TableVersionsProps) { function TableVersions({ processing, items, onDelete, selected, onSelect }: TableVersionsProps) {
const intl = useIntl(); const intl = useIntl();
const { colors } = useConceptOptions(); const columns = [
const columns = useMemo(
() => [
columnHelper.accessor('version', { columnHelper.accessor('version', {
id: 'version', id: 'version',
header: 'Версия', header: 'Версия',
@ -70,21 +66,16 @@ function TableVersions({ processing, items, onDelete, selected, onSelect }: Tabl
</div> </div>
) )
}) })
], ];
[onDelete, intl, processing]
);
const conditionalRowStyles = useMemo( const conditionalRowStyles: IConditionalStyle<IVersionInfo>[] = [
(): IConditionalStyle<IVersionInfo>[] => [
{ {
when: (version: IVersionInfo) => version.id === selected, when: (version: IVersionInfo) => version.id === selected,
style: { style: {
backgroundColor: colors.bgSelected backgroundColor: APP_COLORS.bgSelected
} }
} }
], ];
[selected, colors]
);
return ( return (
<DataTable <DataTable

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useLayoutEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { IconAccept, IconMoveDown, IconMoveLeft, IconMoveRight, IconRemove } from '@/components/Icons'; import { IconAccept, IconMoveDown, IconMoveLeft, IconMoveRight, IconRemove } from '@/components/Icons';
import SelectMultiGrammeme from '@/components/select/SelectMultiGrammeme'; import SelectMultiGrammeme from '@/components/select/SelectMultiGrammeme';
@ -33,7 +33,7 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
const [inputGrams, setInputGrams] = useState<IGrammemeOption[]>([]); const [inputGrams, setInputGrams] = useState<IGrammemeOption[]>([]);
const [forms, setForms] = useState<IWordForm[]>([]); const [forms, setForms] = useState<IWordForm[]>([]);
useLayoutEffect(() => { useEffect(() => {
const initForms: IWordForm[] = []; const initForms: IWordForm[] = [];
target.term_forms.forEach(term => target.term_forms.forEach(term =>
initForms.push({ initForms.push({

View File

@ -1,7 +1,6 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useMemo } from 'react';
import { IconRemove } from '@/components/Icons'; import { IconRemove } from '@/components/Icons';
import BadgeWordForm from '@/components/info/BadgeWordForm'; import BadgeWordForm from '@/components/info/BadgeWordForm';
@ -19,8 +18,7 @@ interface TableWordFormsProps {
const columnHelper = createColumnHelper<IWordForm>(); const columnHelper = createColumnHelper<IWordForm>();
function TableWordForms({ forms, setForms, onFormSelect }: TableWordFormsProps) { function TableWordForms({ forms, setForms, onFormSelect }: TableWordFormsProps) {
const handleDeleteRow = useCallback( function handleDeleteRow(row: number) {
(row: number) => {
setForms(prev => { setForms(prev => {
const newForms: IWordForm[] = []; const newForms: IWordForm[] = [];
prev.forEach((form, index) => { prev.forEach((form, index) => {
@ -30,12 +28,9 @@ function TableWordForms({ forms, setForms, onFormSelect }: TableWordFormsProps)
}); });
return newForms; return newForms;
}); });
}, }
[setForms]
);
const columns = useMemo( const columns = [
() => [
columnHelper.accessor('text', { columnHelper.accessor('text', {
id: 'text', id: 'text',
size: 350, size: 350,
@ -63,9 +58,7 @@ function TableWordForms({ forms, setForms, onFormSelect }: TableWordFormsProps)
</div> </div>
) )
}) })
], ];
[handleDeleteRow]
);
return ( return (
<DataTable <DataTable

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useState } from 'react';
import { TabList, TabPanel, Tabs } from 'react-tabs'; import { TabList, TabPanel, Tabs } from 'react-tabs';
import Modal, { ModalProps } from '@/components/ui/Modal'; import Modal, { ModalProps } from '@/components/ui/Modal';
@ -35,7 +35,7 @@ function DlgInlineSynthesis({ hideWindow, receiver, onInlineSynthesis }: DlgInli
const source = useRSFormDetails({ target: donorID ? String(donorID) : undefined }); const source = useRSFormDetails({ target: donorID ? String(donorID) : undefined });
const validated = useMemo(() => !!source.schema && selected.length > 0, [source.schema, selected]); const validated = !!source.schema && selected.length > 0;
function handleSubmit() { function handleSubmit() {
if (!source.schema) { if (!source.schema) {
@ -55,43 +55,6 @@ function DlgInlineSynthesis({ hideWindow, receiver, onInlineSynthesis }: DlgInli
setSubstitutions([]); setSubstitutions([]);
}, [source.schema]); }, [source.schema]);
const schemaPanel = useMemo(
() => (
<TabPanel>
<TabSchema selected={donorID} setSelected={setDonorID} receiver={receiver} />
</TabPanel>
),
[donorID, receiver]
);
const itemsPanel = useMemo(
() => (
<TabPanel>
<TabConstituents
schema={source.schema}
loading={source.loading}
selected={selected}
setSelected={setSelected}
/>
</TabPanel>
),
[source.schema, source.loading, selected]
);
const substitutesPanel = useMemo(
() => (
<TabPanel>
<TabSubstitutions
receiver={receiver}
source={source.schema}
selected={selected}
loading={source.loading}
substitutions={substitutions}
setSubstitutions={setSubstitutions}
/>
</TabPanel>
),
[source.schema, source.loading, receiver, selected, substitutions]
);
return ( return (
<Modal <Modal
header='Импорт концептуальной схем' header='Импорт концептуальной схем'
@ -113,9 +76,29 @@ function DlgInlineSynthesis({ hideWindow, receiver, onInlineSynthesis }: DlgInli
<TabLabel label='Отождествления' title='Таблица отождествлений' className='w-[8rem]' /> <TabLabel label='Отождествления' title='Таблица отождествлений' className='w-[8rem]' />
</TabList> </TabList>
{schemaPanel} <TabPanel>
{itemsPanel} <TabSchema selected={donorID} setSelected={setDonorID} receiver={receiver} />
{substitutesPanel} </TabPanel>
<TabPanel>
<TabConstituents
schema={source.schema}
loading={source.loading}
selected={selected}
setSelected={setSelected}
/>
</TabPanel>
<TabPanel>
<TabSubstitutions
receiver={receiver}
source={source.schema}
selected={selected}
loading={source.loading}
substitutions={substitutions}
setSubstitutions={setSubstitutions}
/>
</TabPanel>
</Tabs> </Tabs>
</Modal> </Modal>
); );

View File

@ -1,7 +1,5 @@
'use client'; 'use client';
import { useMemo } from 'react';
import PickSchema from '@/components/select/PickSchema'; import PickSchema from '@/components/select/PickSchema';
import TextInput from '@/components/ui/TextInput'; import TextInput from '@/components/ui/TextInput';
import { useLibrary } from '@/context/LibraryContext'; import { useLibrary } from '@/context/LibraryContext';
@ -17,8 +15,8 @@ interface TabSchemaProps {
function TabSchema({ selected, receiver, setSelected }: TabSchemaProps) { function TabSchema({ selected, receiver, setSelected }: TabSchemaProps) {
const library = useLibrary(); const library = useLibrary();
const selectedInfo = useMemo(() => library.items.find(item => item.id === selected), [selected, library.items]); const selectedInfo = library.items.find(item => item.id === selected);
const sortedItems = useMemo(() => sortItemsForInlineSynthesis(receiver, library.items), [receiver, library.items]); const sortedItems = sortItemsForInlineSynthesis(receiver, library.items);
return ( return (
<div className='cc-fade-in flex flex-col'> <div className='cc-fade-in flex flex-col'>

View File

@ -1,12 +1,10 @@
'use client'; 'use client';
import { useCallback, useMemo } from 'react';
import { ErrorData } from '@/components/info/InfoError'; import { ErrorData } from '@/components/info/InfoError';
import PickSubstitutions from '@/components/select/PickSubstitutions'; import PickSubstitutions from '@/components/select/PickSubstitutions';
import DataLoader from '@/components/wrap/DataLoader'; import DataLoader from '@/components/wrap/DataLoader';
import { ICstSubstitute } from '@/models/oss'; import { ICstSubstitute } from '@/models/oss';
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform'; import { ConstituentaID, IRSForm } from '@/models/rsform';
import { prefixes } from '@/utils/constants'; import { prefixes } from '@/utils/constants';
interface TabSubstitutionsProps { interface TabSubstitutionsProps {
@ -32,12 +30,7 @@ function TabSubstitutions({
substitutions, substitutions,
setSubstitutions setSubstitutions
}: TabSubstitutionsProps) { }: TabSubstitutionsProps) {
const filter = useCallback( const schemas = [...(source ? [source] : []), ...(receiver ? [receiver] : [])];
(cst: IConstituenta) => cst.id !== source?.id || selected.includes(cst.id),
[selected, source]
);
const schemas = useMemo(() => [...(source ? [source] : []), ...(receiver ? [receiver] : [])], [source, receiver]);
return ( return (
<DataLoader isLoading={loading} error={error} hasNoData={!source}> <DataLoader isLoading={loading} error={error} hasNoData={!source}>
@ -47,7 +40,7 @@ function TabSubstitutions({
rows={10} rows={10}
prefixID={prefixes.cst_inline_synth_substitutes} prefixID={prefixes.cst_inline_synth_substitutes}
schemas={schemas} schemas={schemas}
filter={filter} filter={cst => cst.id !== source?.id || selected.includes(cst.id)}
/> />
</DataLoader> </DataLoader>
); );

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useMemo, useState } from 'react'; import { useState } from 'react';
import { RelocateUpIcon } from '@/components/DomainIcons'; import { RelocateUpIcon } from '@/components/DomainIcons';
import PickMultiConstituenta from '@/components/select/PickMultiConstituenta'; import PickMultiConstituenta from '@/components/select/PickMultiConstituenta';
@ -33,10 +33,11 @@ function DlgRelocateConstituents({ oss, hideWindow, initialTarget, onSubmit }: D
const [source, setSource] = useState<ILibraryItem | undefined>( const [source, setSource] = useState<ILibraryItem | undefined>(
library.items.find(item => item.id === initialTarget?.result) library.items.find(item => item.id === initialTarget?.result)
); );
const isValid = !!destination && selected.length > 0;
const operation = useMemo(() => oss.items.find(item => item.result === source?.id), [oss, source]); const operation = oss.items.find(item => item.result === source?.id);
const sourceSchemas = useMemo(() => library.items.filter(item => oss.schemas.includes(item.id)), [library, oss]); const sourceSchemas = library.items.filter(item => oss.schemas.includes(item.id));
const destinationSchemas = useMemo(() => { const destinationSchemas = (() => {
if (!operation) { if (!operation) {
return []; return [];
} }
@ -45,35 +46,34 @@ function DlgRelocateConstituents({ oss, hideWindow, initialTarget, onSubmit }: D
? node.inputs.map(id => oss.operationByID.get(id)!.result).filter(id => id !== null) ? node.inputs.map(id => oss.operationByID.get(id)!.result).filter(id => id !== null)
: node.outputs.map(id => oss.operationByID.get(id)!.result).filter(id => id !== null); : node.outputs.map(id => oss.operationByID.get(id)!.result).filter(id => id !== null);
return ids.map(id => library.items.find(item => item.id === id)).filter(item => item !== undefined); return ids.map(id => library.items.find(item => item.id === id)).filter(item => item !== undefined);
}, [oss, library.items, operation, directionUp]); })();
const sourceData = useRSFormDetails({ target: source ? String(source.id) : undefined }); const sourceData = useRSFormDetails({ target: source ? String(source.id) : undefined });
const filteredConstituents = useMemo(() => { const filteredConstituents = (() => {
if (!sourceData.schema || !destination || !operation) { if (!sourceData.schema || !destination || !operation) {
return []; return [];
} }
const destinationOperation = oss.items.find(item => item.result === destination.id); const destinationOperation = oss.items.find(item => item.result === destination.id);
return getRelocateCandidates(operation.id, destinationOperation!.id, sourceData.schema, oss); return getRelocateCandidates(operation.id, destinationOperation!.id, sourceData.schema, oss);
}, [destination, operation, sourceData.schema, oss]); })();
const isValid = useMemo(() => !!destination && selected.length > 0, [destination, selected]); function toggleDirection() {
const toggleDirection = useCallback(() => {
setDirectionUp(prev => !prev); setDirectionUp(prev => !prev);
setDestination(undefined); setDestination(undefined);
}, []); }
const handleSelectSource = useCallback((newValue: ILibraryItem | undefined) => { function handleSelectSource(newValue: ILibraryItem | undefined) {
setSource(newValue); setSource(newValue);
setDestination(undefined); setDestination(undefined);
setSelected([]); setSelected([]);
}, []); }
const handleSelectDestination = useCallback((newValue: ILibraryItem | undefined) => { function handleSelectDestination(newValue: ILibraryItem | undefined) {
setDestination(newValue); setDestination(newValue);
setSelected([]); setSelected([]);
}, []); }
const handleSubmit = useCallback(() => { function handleSubmit() {
if (!destination) { if (!destination) {
return; return;
} }
@ -82,7 +82,7 @@ function DlgRelocateConstituents({ oss, hideWindow, initialTarget, onSubmit }: D
items: selected items: selected
}; };
onSubmit(data); onSubmit(data);
}, [destination, onSubmit, selected]); }
return ( return (
<Modal <Modal

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useLayoutEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import Modal, { ModalProps } from '@/components/ui/Modal'; import Modal, { ModalProps } from '@/components/ui/Modal';
import SelectSingle from '@/components/ui/SelectSingle'; import SelectSingle from '@/components/ui/SelectSingle';
@ -25,13 +25,13 @@ function DlgRenameCst({ hideWindow, initial, allowChangeType, onRename }: DlgRen
const [validated, setValidated] = useState(false); const [validated, setValidated] = useState(false);
const [cstData, updateData] = usePartialUpdate(initial); const [cstData, updateData] = usePartialUpdate(initial);
useLayoutEffect(() => { useEffect(() => {
if (schema && initial && cstData.cst_type !== initial.cst_type) { if (schema && initial && cstData.cst_type !== initial.cst_type) {
updateData({ alias: generateAlias(cstData.cst_type, schema) }); updateData({ alias: generateAlias(cstData.cst_type, schema) });
} }
}, [initial, cstData.cst_type, updateData, schema]); }, [initial, cstData.cst_type, updateData, schema]);
useLayoutEffect(() => { useEffect(() => {
setValidated( setValidated(
!!schema && cstData.alias !== initial.alias && validateNewAlias(cstData.alias, cstData.cst_type, schema) !!schema && cstData.alias !== initial.alias && validateNewAlias(cstData.alias, cstData.cst_type, schema)
); );

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