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
- postcss
- autoprefixer
- eslint-plugin-react-compiler
- eslint-plugin-simple-import-sort
- eslint-plugin-react-hooks
- eslint-plugin-tsdoc
- babel-plugin-react-compiler
- vite
- 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
- overflow behavior: overflow-scroll overscroll-contain
- border: borer-2 outline-none shadow-md
- colors: clr-controls
- text: text-start text-sm font-semibold whitespace-nowrap
- text: text-start text-sm font-semibold whitespace-nowrap bg-prim-200 fg-app-100
- behavior modifiers: select-none disabled:cursor-auto
- transitions:
</pre>

View File

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

View File

@ -41,8 +41,10 @@
"@typescript-eslint/parser": "^8.0.1",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"babel-plugin-react-compiler": "^19.0.0-beta-37ed2a7-20241206",
"eslint": "^9.16.0",
"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-simple-import-sort": "^12.1.1",
"globals": "^15.13.0",
@ -163,6 +165,19 @@
"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": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz",
@ -190,6 +205,52 @@
"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": {
"version": "7.25.9",
"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"
}
},
"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": {
"version": "7.25.9",
"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_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": {
"version": "7.25.9",
"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_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": {
"version": "7.8.4",
"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"
}
},
"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": {
"version": "1.1.0",
"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"
}
},
"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": {
"version": "5.1.0",
"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_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": {
"version": "3.3.2",
"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"
}
},
"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": {
"version": "4.5.5",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.5.tgz",

View File

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

View File

@ -13,7 +13,7 @@ function ApplicationLayout() {
const { viewportHeight, mainHeight, showScroll } = useConceptOptions();
return (
<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
className='mt-[4rem] text-[14px]' // prettier: split lines
autoClose={3000}

View File

@ -5,7 +5,7 @@ import Button from '../components/ui/Button';
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
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>
<Button onClick={resetErrorBoundary} text='Попробовать еще раз' />
<InfoError error={error as Error} />

View File

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

View File

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

View File

@ -31,7 +31,7 @@ function NavigationButton({
className={clsx(
'mr-1 h-full', // prettier: split lines
'flex items-center gap-1',
'clr-btn-nav',
'clr-btn-nav cc-animate-color',
'rounded-xl',
'transition duration-500',
'font-controls whitespace-nowrap',

View File

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

View File

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

View File

@ -49,9 +49,9 @@ export interface DomIconProps<RequestData> extends IconProps {
export function ItemTypeIcon({ value, size = '1.25rem', className }: DomIconProps<LibraryItemType>) {
switch (value) {
case LibraryItemType.RSFORM:
return <IconRSForm size={size} className={className ?? 'clr-text-primary'} />;
return <IconRSForm size={size} className={className ?? 'text-sec-600'} />;
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>) {
switch (value) {
case AccessPolicy.PRIVATE:
return <IconPrivate size={size} className={className ?? 'clr-text-red'} />;
return <IconPrivate size={size} className={className ?? 'text-warn-600'} />;
case AccessPolicy.PROTECTED:
return <IconProtected size={size} className={className ?? 'clr-text-primary'} />;
return <IconProtected size={size} className={className ?? 'text-sec-600'} />;
case AccessPolicy.PUBLIC:
return <IconPublic size={size} className={className ?? 'clr-text-green'} />;
return <IconPublic size={size} className={className ?? 'text-ok-600'} />;
}
}
/** Icon for visibility. */
export function VisibilityIcon({ value, size = '1.25rem', className }: DomIconProps<boolean>) {
if (value) {
return <IconShow size={size} className={className ?? 'clr-text-green'} />;
return <IconShow size={size} className={className ?? 'text-ok-600'} />;
} else {
return <IconHide size={size} className={className ?? 'clr-text-red'} />;
return <IconHide size={size} className={className ?? 'text-warn-600'} />;
}
}
/** Icon for subfolders. */
export function SubfoldersIcon({ value, size = '1.25rem', className }: DomIconProps<boolean>) {
if (value) {
return <IconSubfolders size={size} className={className ?? 'clr-text-green'} />;
return <IconSubfolders size={size} className={className ?? 'text-ok-600'} />;
} 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>) {
switch (value.substring(0, 2) as LocationHead) {
case LocationHead.COMMON:
return <IconPublic size={size} className={className ?? 'clr-text-primary'} />;
return <IconPublic size={size} className={className ?? 'text-sec-600'} />;
case LocationHead.LIBRARY:
return <IconTemplates size={size} className={className ?? 'clr-text-red'} />;
return <IconTemplates size={size} className={className ?? 'text-warn-600'} />;
case LocationHead.PROJECTS:
return <IconBusiness size={size} className={className ?? 'clr-text-primary'} />;
return <IconBusiness size={size} className={className ?? 'text-sec-600'} />;
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:
return <IconSettings size={size} className={className} />;
case DependencyMode.OUTPUTS:
return <IconGraphOutputs size={size} className={className ?? 'clr-text-primary'} />;
return <IconGraphOutputs size={size} className={className ?? 'text-sec-600'} />;
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:
return <IconGraphExpand size={size} className={className ?? 'clr-text-primary'} />;
return <IconGraphExpand size={size} className={className ?? 'text-sec-600'} />;
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:
return <IconFilter size={size} className={className} />;
case CstMatchMode.TEXT:
return <IconText size={size} className={className ?? 'clr-text-primary'} />;
return <IconText size={size} className={className ?? 'text-sec-600'} />;
case CstMatchMode.EXPR:
return <IconFormula size={size} className={className ?? 'clr-text-primary'} />;
return <IconFormula size={size} className={className ?? 'text-sec-600'} />;
case CstMatchMode.TERM:
return <IconTerm size={size} className={className ?? 'clr-text-primary'} />;
return <IconTerm size={size} className={className ?? 'text-sec-600'} />;
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>) {
switch (value) {
case CstType.BASE:
return <IconCstBaseSet size={size} className={className ?? 'clr-text-green'} />;
return <IconCstBaseSet size={size} className={className ?? 'text-ok-600'} />;
case CstType.CONSTANT:
return <IconCstConstSet size={size} className={className ?? 'clr-text-green'} />;
return <IconCstConstSet size={size} className={className ?? 'text-ok-600'} />;
case CstType.STRUCTURED:
return <IconCstStructured size={size} className={className ?? 'clr-text-green'} />;
return <IconCstStructured size={size} className={className ?? 'text-ok-600'} />;
case CstType.TERM:
return <IconCstTerm size={size} className={className ?? 'clr-text-primary'} />;
return <IconCstTerm size={size} className={className ?? 'text-sec-600'} />;
case CstType.AXIOM:
return <IconCstAxiom size={size} className={className ?? 'clr-text-red'} />;
return <IconCstAxiom size={size} className={className ?? 'text-warn-600'} />;
case CstType.FUNCTION:
return <IconCstFunction size={size} className={className ?? 'clr-text-primary'} />;
return <IconCstFunction size={size} className={className ?? 'text-sec-600'} />;
case CstType.PREDICATE:
return <IconCstPredicate size={size} className={className ?? 'clr-text-red'} />;
return <IconCstPredicate size={size} className={className ?? 'text-warn-600'} />;
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. */
export function RelocateUpIcon({ value, size = '1.25rem', className }: DomIconProps<boolean>) {
if (value) {
return <IconMoveUp size={size} className={className ?? 'clr-text-primary'} />;
return <IconMoveUp size={size} className={className ?? 'text-sec-600'} />;
} 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 clsx from 'clsx';
import { EditorView } from 'codemirror';
import { forwardRef, useCallback, useMemo, useRef } from 'react';
import { forwardRef, useRef } from 'react';
import Label from '@/components/ui/Label';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { ConstituentaID, IRSForm } from '@/models/rsform';
import { generateAlias, getCstTypePrefix, guessCstType } from '@/models/rsformAPI';
import { extractGlobals } from '@/models/rslangAPI';
import { APP_COLORS } from '@/styling/color';
import { ccBracketMatching } from './bracketMatching';
import { rsNavigation } from './clickNavigation';
@ -63,50 +64,42 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
},
ref
) => {
const { darkMode, colors } = useConceptOptions();
const { darkMode } = useConceptOptions();
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 customTheme: Extension = useMemo(
() =>
createTheme({
const cursor = !disabled ? 'cursor-text' : 'cursor-default';
const customTheme: Extension = createTheme({
theme: darkMode ? 'dark' : 'light',
settings: {
fontFamily: 'inherit',
background: !disabled ? colors.bgInput : colors.bgDefault,
foreground: colors.fgDefault,
selection: colors.bgHover,
caret: colors.fgDefault
background: !disabled ? APP_COLORS.bgInput : APP_COLORS.bgDefault,
foreground: APP_COLORS.fgDefault,
selection: APP_COLORS.bgHover,
caret: APP_COLORS.fgDefault
},
styles: [
{ tag: tags.name, color: colors.fgPurple, cursor: schema ? 'default' : cursor }, // GlobalID
{ tag: tags.variableName, color: colors.fgGreen }, // LocalID
{ tag: tags.propertyName, color: colors.fgTeal }, // Radical
{ tag: tags.keyword, color: colors.fgBlue }, // keywords
{ tag: tags.literal, color: colors.fgBlue }, // literals
{ tag: tags.name, color: APP_COLORS.fgPurple, cursor: schema ? 'default' : cursor }, // GlobalID
{ tag: tags.variableName, color: APP_COLORS.fgGreen }, // LocalID
{ tag: tags.propertyName, color: APP_COLORS.fgTeal }, // Radical
{ tag: tags.keyword, color: APP_COLORS.fgBlue }, // keywords
{ tag: tags.literal, color: APP_COLORS.fgBlue }, // literals
{ tag: tags.controlKeyword, fontWeight: '400' }, // R | I | D
{ 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,
RSLanguage,
ccBracketMatching(darkMode),
ccBracketMatching(),
...(!schema || !onOpenEdit ? [] : [rsNavigation(schema, onOpenEdit)]),
...(noTooltip || !schema ? [] : [rsHoverTooltip(schema, onOpenEdit !== undefined)])
],
[darkMode, schema, noTooltip, onOpenEdit]
);
];
const handleInput = useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
function handleInput(event: React.KeyboardEvent<HTMLDivElement>) {
if (!thisRef.current) {
return;
}
@ -159,9 +152,7 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
event.preventDefault();
event.stopPropagation();
}
},
[thisRef, onAnalyze, schema]
);
}
return (
<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 { 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 nonMatchingMark = Decoration.mark({ class: 'cc-nonmatchingBracket' });
@ -16,16 +16,14 @@ function bracketRender(match: MatchResult) {
return decorations;
}
const darkTheme = EditorView.baseTheme(bracketsDarkT);
const theme = EditorView.baseTheme(BRACKETS_THEME);
const lightTheme = EditorView.baseTheme(bracketsLightT);
export function ccBracketMatching(darkMode: boolean) {
export function ccBracketMatching() {
return [
bracketMatching({
renderMatch: bracketRender,
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 clsx from 'clsx';
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 { useConceptOptions } from '@/context/ConceptOptionsContext';
import DlgEditReference from '@/dialogs/DlgEditReference';
import { ReferenceType } from '@/models/language';
import { ConstituentaID, IRSForm } from '@/models/rsform';
import { APP_COLORS } from '@/styling/color';
import { CodeMirrorWrapper } from '@/utils/codemirror';
import { PARAMETER } from '@/utils/constants';
@ -91,7 +92,7 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
},
ref
) => {
const { darkMode, colors } = useConceptOptions();
const { darkMode } = useConceptOptions();
const [isFocused, setIsFocused] = useState(false);
@ -103,39 +104,32 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
const [mainRefs, setMainRefs] = useState<string[]>([]);
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 customTheme: Extension = useMemo(
() =>
createTheme({
const cursor = !disabled ? 'cursor-text' : 'cursor-default';
const customTheme: Extension = createTheme({
theme: darkMode ? 'dark' : 'light',
settings: {
fontFamily: 'inherit',
background: !disabled ? colors.bgInput : colors.bgDefault,
foreground: colors.fgDefault,
selection: colors.bgHover,
caret: colors.fgDefault
background: !disabled ? APP_COLORS.bgInput : APP_COLORS.bgDefault,
foreground: APP_COLORS.fgDefault,
selection: APP_COLORS.bgHover,
caret: APP_COLORS.fgDefault
},
styles: [
{ tag: tags.name, color: colors.fgPurple, cursor: 'default' }, // EntityReference
{ tag: tags.literal, color: colors.fgTeal, cursor: 'default' }, // SyntacticReference
{ tag: tags.comment, color: colors.fgRed } // Error
{ tag: tags.name, color: APP_COLORS.fgPurple, cursor: 'default' }, // EntityReference
{ tag: tags.literal, color: APP_COLORS.fgTeal, cursor: 'default' }, // SyntacticReference
{ tag: tags.comment, color: APP_COLORS.fgRed } // Error
]
}),
[disabled, colors, darkMode]
);
});
const editorExtensions = useMemo(
() => [
const editorExtensions = [
EditorView.lineWrapping,
EditorView.contentAttributes.of({ spellcheck: 'true' }),
NaturalLanguage,
...(!schema || !onOpenEdit ? [] : [refsNavigation(schema, onOpenEdit)]),
...(schema ? [refsHoverTooltip(schema, colors, onOpenEdit !== undefined)] : [])
],
[schema, colors, onOpenEdit]
);
...(schema ? [refsHoverTooltip(schema, onOpenEdit !== undefined)] : [])
];
function handleChange(newValue: string) {
if (onChange) onChange(newValue);
@ -151,8 +145,7 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
if (onBlur) onBlur(event);
}
const handleInput = useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
function handleInput(event: React.KeyboardEvent<HTMLDivElement>) {
if (!thisRef.current?.view) {
event.preventDefault();
event.stopPropagation();
@ -183,26 +176,21 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
setShowEditor(true);
}
},
[thisRef]
);
}
const handleInputReference = useCallback(
(referenceText: string) => {
function handleInputReference(referenceText: string) {
if (!thisRef.current?.view) {
return;
}
thisRef.current.view.focus();
const wrap = new CodeMirrorWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
wrap.replaceWith(referenceText);
},
[thisRef]
);
}
const hideEditReference = useCallback(() => {
function hideEditReference() {
setShowEditor(false);
setTimeout(() => thisRef.current?.view?.focus(), PARAMETER.refreshTimeout);
}, [thisRef]);
}
return (
<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 { IRSForm } from '@/models/rsform';
import { IColorTheme } from '@/styling/color';
import {
domTooltipEntityReference,
domTooltipSyntacticReference,
@ -14,7 +13,7 @@ import {
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) => {
const parse = findReferenceAt(pos, view.state);
if (!parse) {
@ -27,7 +26,7 @@ export const tooltipProducer = (schema: IRSForm, colors: IColorTheme, canClick?:
pos: parse.start,
end: parse.end,
above: false,
create: () => domTooltipEntityReference(parse.ref as IEntityReference, cst, colors, canClick)
create: () => domTooltipEntityReference(parse.ref as IEntityReference, cst, canClick)
};
} else {
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 {
return [tooltipProducer(schema, colors, canClick)];
export function refsHoverTooltip(schema: IRSForm, canClick?: boolean): Extension {
return [tooltipProducer(schema, canClick)];
}

View File

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

View File

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

View File

@ -1,6 +1,5 @@
import clsx from 'clsx';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { CstClass } from '@/models/rsform';
import { colorBgCstClass } from '@/styling/color';
import { prefixes } from '@/utils/constants';
@ -11,8 +10,6 @@ interface InfoCstClassProps {
}
function InfoCstClass({ header }: InfoCstClassProps) {
const { colors } = useConceptOptions();
return (
<div className='flex flex-col gap-1 mb-2 dense'>
{header ? <h1>{header}</h1> : null}
@ -21,7 +18,7 @@ function InfoCstClass({ header }: InfoCstClassProps) {
<p key={`${prefixes.cst_status_list}${index}`}>
<span
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)}
</span>

View File

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

View File

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

View File

@ -1,7 +1,6 @@
'use client';
import { createColumnHelper } from '@tanstack/react-table';
import { useMemo } from 'react';
import Tooltip from '@/components/ui/Tooltip';
import { OssNodeInternal } from '@/models/miscellaneous';
@ -19,8 +18,7 @@ interface TooltipOperationProps {
const columnHelper = createColumnHelper<ICstSubstituteEx>();
function TooltipOperation({ node, anchor }: TooltipOperationProps) {
const columns = useMemo(
() => [
const columns = [
columnHelper.accessor('substitution_term', {
id: 'substitution_term',
size: 200
@ -43,23 +41,7 @@ function TooltipOperation({ node, anchor }: TooltipOperationProps) {
id: 'original_term',
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 (
<Tooltip layer='z-modalTooltip' anchorSelect={anchor} className='max-w-[35rem] max-h-[40rem] dense'>
@ -90,7 +72,14 @@ function TooltipOperation({ node, anchor }: TooltipOperationProps) {
</p>
) : null}
{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 ? (
<p>
<b>Отождествления:</b> Отсутствуют

View File

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

View File

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

View File

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

View File

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

View File

@ -1,17 +1,17 @@
'use client';
import clsx from 'clsx';
import { useCallback, useMemo, useState } from 'react';
import { useState } from 'react';
import { toast } from 'react-toastify';
import BadgeConstituenta from '@/components/info/BadgeConstituenta';
import SelectConstituenta from '@/components/select/SelectConstituenta';
import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/ui/DataTable';
import MiniButton from '@/components/ui/MiniButton';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { ILibraryItem } from '@/models/library';
import { ICstSubstitute, IMultiSubstitution } from '@/models/oss';
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
import { APP_COLORS } from '@/styling/color';
import { errors } from '@/utils/labels';
import { IconAccept, IconPageLeft, IconPageRight, IconRemove, IconReplace } from '../Icons';
@ -46,8 +46,6 @@ function PickSubstitutions({
className,
...restProps
}: PickSubstitutionsProps) {
const { colors } = useConceptOptions();
const [leftArgument, setLeftArgument] = useState<ILibraryItem | undefined>(
schemas.length === 1 ? schemas[0] : undefined
);
@ -62,42 +60,12 @@ function PickSubstitutions({
const toggleDelete = () => setDeleteRight(prev => !prev);
const [ignores, setIgnores] = useState<ICstSubstitute[]>([]);
const filteredSuggestions = useMemo(
() =>
const filteredSuggestions =
suggestions?.filter(
item => !ignores.find(ignore => ignore.original === item.original && ignore.substitution === item.substitution)
) ?? [],
[ignores, suggestions]
);
) ?? [];
const getSchemaByCst = useCallback(
(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(
() => [
const substitutionData: IMultiSubstitution[] = [
...substitutions.map(item => ({
original_source: getSchemaByCst(item.original)!,
original: getConstituenta(item.original)!,
@ -112,9 +80,27 @@ function PickSubstitutions({
substitution_source: getSchemaByCst(item.substitution)!,
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() {
if (!leftCst || !rightCst) {
@ -145,22 +131,15 @@ function PickSubstitutions({
setRightCst(undefined);
}
const handleDeclineSuggestion = useCallback(
(item: IMultiSubstitution) => {
function handleDeclineSuggestion(item: IMultiSubstitution) {
setIgnores(prev => [...prev, { original: item.original.id, substitution: item.substitution.id }]);
},
[setIgnores]
);
}
const handleAcceptSuggestion = useCallback(
(item: IMultiSubstitution) => {
function handleAcceptSuggestion(item: IMultiSubstitution) {
setSubstitutions(prev => [...prev, { original: item.original.id, substitution: item.substitution.id }]);
},
[setSubstitutions]
);
}
const handleDeleteSubstitution = useCallback(
(target: IMultiSubstitution) => {
function handleDeleteSubstitution(target: IMultiSubstitution) {
handleDeclineSuggestion(target);
setSubstitutions(prev => {
const newItems: ICstSubstitute[] = [];
@ -171,12 +150,9 @@ function PickSubstitutions({
});
return newItems;
});
},
[setSubstitutions, handleDeclineSuggestion]
);
}
const columns = useMemo(
() => [
const columns = [
columnHelper.accessor(item => item.substitution_source.alias, {
id: 'left_schema',
size: 100,
@ -186,11 +162,7 @@ function PickSubstitutions({
id: 'left_alias',
size: 65,
cell: props => (
<BadgeConstituenta
theme={colors}
value={props.row.original.substitution}
prefixID={`${prefixID}_${props.row.index}_1_`}
/>
<BadgeConstituenta value={props.row.original.substitution} prefixID={`${prefixID}_${props.row.index}_1_`} />
)
}),
columnHelper.display({
@ -202,11 +174,7 @@ function PickSubstitutions({
id: 'right_alias',
size: 65,
cell: props => (
<BadgeConstituenta
theme={colors}
value={props.row.original.original}
prefixID={`${prefixID}_${props.row.index}_2_`}
/>
<BadgeConstituenta value={props.row.original.original} prefixID={`${prefixID}_${props.row.index}_2_`} />
)
}),
columnHelper.accessor(item => item.original_source.alias, {
@ -244,21 +212,14 @@ function PickSubstitutions({
</div>
)
})
],
[handleDeleteSubstitution, handleDeclineSuggestion, handleAcceptSuggestion, colors, prefixID]
);
];
const conditionalRowStyles = useMemo(
(): IConditionalStyle<IMultiSubstitution>[] => [
const conditionalRowStyles: IConditionalStyle<IMultiSubstitution>[] = [
{
when: (item: IMultiSubstitution) => item.is_suggestion,
style: {
backgroundColor: colors.bgOrange50
style: { backgroundColor: APP_COLORS.bgOrange50 }
}
}
],
[colors]
);
];
return (
<div className={clsx('flex flex-col', className)} {...restProps}>
@ -286,9 +247,9 @@ function PickSubstitutions({
onClick={toggleDelete}
icon={
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';
import clsx from 'clsx';
import { useCallback, useMemo } from 'react';
import { CstMatchMode } from '@/models/miscellaneous';
import { ConstituentaID, IConstituenta } from '@/models/rsform';
@ -28,22 +27,16 @@ function SelectConstituenta({
placeholder = 'Выберите конституенту',
...restProps
}: SelectConstituentaProps) {
const options = useMemo(() => {
return (
const options =
items?.map(cst => ({
value: cst.id,
label: `${cst.alias}${cst.is_inherited ? '*' : ''}: ${describeConstituenta(cst)}`
})) ?? []
);
}, [items]);
})) ?? [];
const filter = useCallback(
(option: { value: ConstituentaID | undefined; label: string }, inputValue: string) => {
function filter(option: { value: ConstituentaID | undefined; label: string }, inputValue: string) {
const cst = items?.find(item => item.id === option.value);
return !cst ? false : matchConstituenta(cst, inputValue, CstMatchMode.ALL);
},
[items]
);
}
return (
<SelectSingle

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
'use client';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { APP_COLORS } from '@/styling/color';
interface LoaderProps {
/** Scale of the loader from 1 to 10. */
@ -55,11 +55,10 @@ const animatePulse = (startBig: boolean, duration: string) => {
* Displays animated loader.
*/
function Loader({ scale = 5, circular }: LoaderProps) {
const { colors } = useConceptOptions();
if (circular) {
return (
<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'>
{animateRotation('2.25s')}
</path>
@ -75,7 +74,7 @@ function Loader({ scale = 5, circular }: LoaderProps) {
} else {
return (
<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'>
{animatePulse(true, '0.8s')}
</circle>

View File

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

View File

@ -93,16 +93,16 @@ function Modal({
return (
<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
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}
/>
<div
className={clsx(
'cc-animate-modal',
'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'>

View File

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

View File

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

View File

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

View File

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

View File

@ -41,6 +41,7 @@ function SelectorButton({
'text-sm font-controls select-none',
'text-btn clr-text-controls',
'disabled:cursor-auto cursor-pointer',
'cc-animate-color',
{
'clr-hover': 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',
'border',
'font-medium',
'clr-btn-primary',
'clr-btn-primary cc-animate-color',
'select-none disabled:cursor-auto',
loading && 'cursor-progress',
className

View File

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

View File

@ -20,7 +20,7 @@ interface TextURLProps {
/**
* 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}`;
if (href) {
return (

View File

@ -1,5 +1,4 @@
import clsx from 'clsx';
import { useMemo } from 'react';
import { globals } from '@/utils/constants';
@ -50,7 +49,8 @@ function ValueIcon({
onClick,
...restProps
}: 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 (
<div
className={clsx(

View File

@ -22,7 +22,7 @@ function ExpectedAnonymous() {
<span> | </span>
<TextURL text='Справка' href='/manuals' />
<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>
</div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
'use client';
import clsx from 'clsx';
import { useCallback, useMemo, useState } from 'react';
import { useState } from 'react';
import { toast } from 'react-toastify';
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 [body, setBody] = useState(initialLocation.substring(3));
const location = useMemo(() => combineLocation(head, body), [head, body]);
const location = combineLocation(head, body);
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);
setBody(newValue.length > 3 ? newValue.substring(3) : '');
}, []);
}
function handleSubmit() {
const data: IRSFormCloneData = {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
'use client';
import { useLayoutEffect, useMemo, useState } from 'react';
import { useEffect, useState } from 'react';
import { IconReset, IconSave } from '@/components/Icons';
import MiniButton from '@/components/ui/MiniButton';
@ -26,19 +26,8 @@ function DlgEditVersions({ hideWindow, versions, onDelete, onUpdate }: DlgEditVe
const [version, setVersion] = useState('');
const [description, setDescription] = useState('');
const isValid = useMemo(() => {
if (!selected) {
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]);
const isValid = selected && versions.every(ver => ver.id === selected.id || ver.version != version);
const isModified = selected && (selected.version != version || selected.description != description);
function handleUpdate() {
if (!isModified || !selected || processing || !isValid) {
@ -59,24 +48,11 @@ function DlgEditVersions({ hideWindow, versions, onDelete, onUpdate }: DlgEditVe
setDescription(selected?.description ?? '');
}
useLayoutEffect(() => {
useEffect(() => {
setVersion(selected?.version ?? '');
setDescription(selected?.description ?? '');
}, [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 (
<Modal
readonly
@ -84,7 +60,14 @@ function DlgEditVersions({ hideWindow, versions, onDelete, onUpdate }: DlgEditVe
hideWindow={hideWindow}
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'>
<TextInput
id='dlg_version'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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