mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
Setup ESLint and fix issues
This commit is contained in:
parent
747ce126ae
commit
e737628cea
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
"python.linting.flake8Enabled": true,
|
"python.linting.flake8Enabled": true,
|
||||||
"python.linting.enabled": true
|
"python.linting.enabled": true,
|
||||||
|
"eslint.workingDirectories": [{ "mode": "auto" }]
|
||||||
}
|
}
|
28
rsconcept/frontend/.eslintrc.json
Normal file
28
rsconcept/frontend/.eslintrc.json
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es2021": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"standard-with-typescript",
|
||||||
|
"plugin:react/recommended",
|
||||||
|
"plugin:react/jsx-runtime"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": "latest",
|
||||||
|
"sourceType": "module",
|
||||||
|
"project": ["tsconfig.json"]
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"react", "simple-import-sort"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"simple-import-sort/imports": "error",
|
||||||
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
|
"@typescript-eslint/semi": "off",
|
||||||
|
"@typescript-eslint/strict-boolean-expressions": "off",
|
||||||
|
"@typescript-eslint/space-before-function-paren": "off",
|
||||||
|
"@typescript-eslint/indent": "off",
|
||||||
|
"object-shorthand": "off"
|
||||||
|
}
|
||||||
|
}
|
229
rsconcept/frontend/package-lock.json
generated
229
rsconcept/frontend/package-lock.json
generated
|
@ -29,12 +29,20 @@
|
||||||
"react-tabs": "^6.0.1",
|
"react-tabs": "^6.0.1",
|
||||||
"react-toastify": "^9.1.3",
|
"react-toastify": "^9.1.3",
|
||||||
"styled-components": "^6.0.4",
|
"styled-components": "^6.0.4",
|
||||||
"typescript": "^4.9.5",
|
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.0",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.0",
|
||||||
"tailwindcss": "^3.3.2"
|
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||||
|
"eslint": "^8.45.0",
|
||||||
|
"eslint-config-standard-with-typescript": "^37.0.0",
|
||||||
|
"eslint-plugin-import": "^2.27.5",
|
||||||
|
"eslint-plugin-n": "^16.0.1",
|
||||||
|
"eslint-plugin-promise": "^6.1.1",
|
||||||
|
"eslint-plugin-react": "^7.33.0",
|
||||||
|
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||||
|
"tailwindcss": "^3.3.2",
|
||||||
|
"typescript": "^5.1.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@aashutoshrathi/word-wrap": {
|
"node_modules/@aashutoshrathi/word-wrap": {
|
||||||
|
@ -2530,9 +2538,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint-community/regexpp": {
|
"node_modules/@eslint-community/regexpp": {
|
||||||
"version": "4.5.1",
|
"version": "4.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.1.tgz",
|
||||||
"integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==",
|
"integrity": "sha512-O7x6dMstWLn2ktjcoiNLDkAGG2EjveHL+Vvc+n0fXumkJYAcSqcVYKtwDU+hDZ0uDUsnUagSYaZrOLAYE8un1A==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
|
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
|
||||||
}
|
}
|
||||||
|
@ -5956,6 +5964,48 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/builtins": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"semver": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/builtins/node_modules/lru-cache": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"yallist": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/builtins/node_modules/semver": {
|
||||||
|
"version": "7.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||||
|
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"lru-cache": "^6.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/builtins/node_modules/yallist": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/bytes": {
|
"node_modules/bytes": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
||||||
|
@ -7515,9 +7565,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint": {
|
"node_modules/eslint": {
|
||||||
"version": "8.44.0",
|
"version": "8.45.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.45.0.tgz",
|
||||||
"integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==",
|
"integrity": "sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.4.0",
|
"@eslint-community/regexpp": "^4.4.0",
|
||||||
|
@ -7544,7 +7594,6 @@
|
||||||
"globals": "^13.19.0",
|
"globals": "^13.19.0",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
"ignore": "^5.2.0",
|
"ignore": "^5.2.0",
|
||||||
"import-fresh": "^3.0.0",
|
|
||||||
"imurmurhash": "^0.1.4",
|
"imurmurhash": "^0.1.4",
|
||||||
"is-glob": "^4.0.0",
|
"is-glob": "^4.0.0",
|
||||||
"is-path-inside": "^3.0.3",
|
"is-path-inside": "^3.0.3",
|
||||||
|
@ -7556,7 +7605,6 @@
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
"optionator": "^0.9.3",
|
"optionator": "^0.9.3",
|
||||||
"strip-ansi": "^6.0.1",
|
"strip-ansi": "^6.0.1",
|
||||||
"strip-json-comments": "^3.1.0",
|
|
||||||
"text-table": "^0.2.0"
|
"text-table": "^0.2.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -7596,6 +7644,53 @@
|
||||||
"eslint": "^8.0.0"
|
"eslint": "^8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eslint-config-standard": {
|
||||||
|
"version": "17.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz",
|
||||||
|
"integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": "^8.0.1",
|
||||||
|
"eslint-plugin-import": "^2.25.2",
|
||||||
|
"eslint-plugin-n": "^15.0.0 || ^16.0.0 ",
|
||||||
|
"eslint-plugin-promise": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eslint-config-standard-with-typescript": {
|
||||||
|
"version": "37.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-37.0.0.tgz",
|
||||||
|
"integrity": "sha512-V8I/Q1eFf9tiOuFHkbksUdWO3p1crFmewecfBtRxXdnvb71BCJx+1xAknlIRZMwZioMX3/bPtMVCZsf1+AjjOw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@typescript-eslint/parser": "^5.52.0",
|
||||||
|
"eslint-config-standard": "17.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.52.0",
|
||||||
|
"eslint": "^8.0.1",
|
||||||
|
"eslint-plugin-import": "^2.25.2",
|
||||||
|
"eslint-plugin-n": "^15.0.0 || ^16.0.0 ",
|
||||||
|
"eslint-plugin-promise": "^6.0.0",
|
||||||
|
"typescript": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eslint-import-resolver-node": {
|
"node_modules/eslint-import-resolver-node": {
|
||||||
"version": "0.3.7",
|
"version": "0.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz",
|
||||||
|
@ -7638,6 +7733,25 @@
|
||||||
"ms": "^2.1.1"
|
"ms": "^2.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eslint-plugin-es-x": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-9dvv5CcvNjSJPqnS5uZkqb3xmbeqRLnvXKK7iI5+oK/yTusyc46zbBZKENGsOfojm/mKfszyZb+wNqNPAPeGXA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@eslint-community/eslint-utils": "^4.1.2",
|
||||||
|
"@eslint-community/regexpp": "^4.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.18.0 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ota-meshi"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eslint-plugin-flowtype": {
|
"node_modules/eslint-plugin-flowtype": {
|
||||||
"version": "8.0.3",
|
"version": "8.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz",
|
||||||
|
@ -7754,10 +7868,80 @@
|
||||||
"eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
|
"eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eslint-plugin-n": {
|
||||||
|
"version": "16.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.0.1.tgz",
|
||||||
|
"integrity": "sha512-CDmHegJN0OF3L5cz5tATH84RPQm9kG+Yx39wIqIwPR2C0uhBGMWfbbOtetR83PQjjidA5aXMu+LEFw1jaSwvTA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@eslint-community/eslint-utils": "^4.4.0",
|
||||||
|
"builtins": "^5.0.1",
|
||||||
|
"eslint-plugin-es-x": "^7.1.0",
|
||||||
|
"ignore": "^5.2.4",
|
||||||
|
"is-core-module": "^2.12.1",
|
||||||
|
"minimatch": "^3.1.2",
|
||||||
|
"resolve": "^1.22.2",
|
||||||
|
"semver": "^7.5.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/mysticatea"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eslint-plugin-n/node_modules/lru-cache": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"yallist": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eslint-plugin-n/node_modules/semver": {
|
||||||
|
"version": "7.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||||
|
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"lru-cache": "^6.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eslint-plugin-n/node_modules/yallist": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/eslint-plugin-promise": {
|
||||||
|
"version": "6.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz",
|
||||||
|
"integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": "^7.0.0 || ^8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eslint-plugin-react": {
|
"node_modules/eslint-plugin-react": {
|
||||||
"version": "7.32.2",
|
"version": "7.33.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.0.tgz",
|
||||||
"integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==",
|
"integrity": "sha512-qewL/8P34WkY8jAqdQxsiL82pDUeT7nhs8IsuXgfgnsEloKCT4miAV9N9kGtx7/KM9NH/NCGUE7Edt9iGxLXFw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"array-includes": "^3.1.6",
|
"array-includes": "^3.1.6",
|
||||||
"array.prototype.flatmap": "^1.3.1",
|
"array.prototype.flatmap": "^1.3.1",
|
||||||
|
@ -7772,7 +7956,7 @@
|
||||||
"object.values": "^1.1.6",
|
"object.values": "^1.1.6",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"resolve": "^2.0.0-next.4",
|
"resolve": "^2.0.0-next.4",
|
||||||
"semver": "^6.3.0",
|
"semver": "^6.3.1",
|
||||||
"string.prototype.matchall": "^4.0.8"
|
"string.prototype.matchall": "^4.0.8"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@ -7820,6 +8004,15 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eslint-plugin-simple-import-sort": {
|
||||||
|
"version": "10.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-10.0.0.tgz",
|
||||||
|
"integrity": "sha512-AeTvO9UCMSNzIHRkg8S6c3RPy5YEwKWSQPx3DYghLedo2ZQxowPFLGDN1AZ2evfg6r6mjBSZSLxLFsWSu3acsw==",
|
||||||
|
"dev": true,
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": ">=5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eslint-plugin-testing-library": {
|
"node_modules/eslint-plugin-testing-library": {
|
||||||
"version": "5.11.0",
|
"version": "5.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.11.0.tgz",
|
||||||
|
@ -16973,15 +17166,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "4.9.5",
|
"version": "5.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
|
||||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
"integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.2.0"
|
"node": ">=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/unbox-primitive": {
|
"node_modules/unbox-primitive": {
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
"react-tabs": "^6.0.1",
|
"react-tabs": "^6.0.1",
|
||||||
"react-toastify": "^9.1.3",
|
"react-toastify": "^9.1.3",
|
||||||
"styled-components": "^6.0.4",
|
"styled-components": "^6.0.4",
|
||||||
"typescript": "^4.9.5",
|
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -33,12 +32,6 @@
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject"
|
"eject": "react-scripts eject"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
|
||||||
"extends": [
|
|
||||||
"react-app",
|
|
||||||
"react-app/jest"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
">0.2%",
|
">0.2%",
|
||||||
|
@ -53,6 +46,15 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.0",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.0",
|
||||||
"tailwindcss": "^3.3.2"
|
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||||
|
"eslint": "^8.45.0",
|
||||||
|
"eslint-config-standard-with-typescript": "^37.0.0",
|
||||||
|
"eslint-plugin-import": "^2.27.5",
|
||||||
|
"eslint-plugin-n": "^16.0.1",
|
||||||
|
"eslint-plugin-promise": "^6.1.1",
|
||||||
|
"eslint-plugin-react": "^7.33.0",
|
||||||
|
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||||
|
"tailwindcss": "^3.3.2",
|
||||||
|
"typescript": "^5.1.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import { Route, Routes } from 'react-router-dom';
|
import { Route, Routes } from 'react-router-dom';
|
||||||
|
|
||||||
|
import Footer from './components/Footer';
|
||||||
import Navigation from './components/Navigation/Navigation';
|
import Navigation from './components/Navigation/Navigation';
|
||||||
import RSFormsPage from './pages/RSFormsPage';
|
import ToasterThemed from './components/ToasterThemed';
|
||||||
import RSFormPage from './pages/RSFormPage';
|
|
||||||
import NotFoundPage from './pages/NotFoundPage';
|
|
||||||
import HomePage from './pages/HomePage';
|
import HomePage from './pages/HomePage';
|
||||||
import LoginPage from './pages/LoginPage';
|
import LoginPage from './pages/LoginPage';
|
||||||
import RestorePasswordPage from './pages/RestorePasswordPage';
|
|
||||||
import UserProfilePage from './pages/UserProfilePage';
|
|
||||||
import RegisterPage from './pages/RegisterPage';
|
|
||||||
import ManualsPage from './pages/ManualsPage';
|
import ManualsPage from './pages/ManualsPage';
|
||||||
import Footer from './components/Footer';
|
import NotFoundPage from './pages/NotFoundPage';
|
||||||
|
import RegisterPage from './pages/RegisterPage';
|
||||||
|
import RestorePasswordPage from './pages/RestorePasswordPage';
|
||||||
import RSFormCreatePage from './pages/RSFormCreatePage';
|
import RSFormCreatePage from './pages/RSFormCreatePage';
|
||||||
import ToasterThemed from './components/ToasterThemed';
|
import RSFormPage from './pages/RSFormPage';
|
||||||
|
import RSFormsPage from './pages/RSFormsPage';
|
||||||
|
import UserProfilePage from './pages/UserProfilePage';
|
||||||
|
|
||||||
function App() {
|
function App () {
|
||||||
return (
|
return (
|
||||||
<div className='antialiased clr-app'>
|
<div className='antialiased clr-app'>
|
||||||
<Navigation />
|
<Navigation />
|
||||||
|
@ -32,9 +32,9 @@ function App() {
|
||||||
<Route path='signup' element={<RegisterPage/>} />
|
<Route path='signup' element={<RegisterPage/>} />
|
||||||
<Route path='restore-password' element={ <RestorePasswordPage/>} />
|
<Route path='restore-password' element={ <RestorePasswordPage/>} />
|
||||||
<Route path='profile' element={<UserProfilePage/>} />
|
<Route path='profile' element={<UserProfilePage/>} />
|
||||||
|
|
||||||
<Route path='manuals' element={<ManualsPage/>} />
|
<Route path='manuals' element={<ManualsPage/>} />
|
||||||
|
|
||||||
<Route path='rsforms' element={<RSFormsPage/>} />
|
<Route path='rsforms' element={<RSFormsPage/>} />
|
||||||
<Route path='rsforms/:id' element={ <RSFormPage/>} />
|
<Route path='rsforms/:id' element={ <RSFormPage/>} />
|
||||||
<Route path='rsform-create' element={ <RSFormCreatePage/>} />
|
<Route path='rsform-create' element={ <RSFormCreatePage/>} />
|
||||||
|
@ -46,4 +46,4 @@ function App() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import axios, { AxiosError } from 'axios';
|
import axios, { type AxiosError } from 'axios';
|
||||||
|
|
||||||
import PrettyJson from './Common/PrettyJSON';
|
import PrettyJson from './Common/PrettyJSON';
|
||||||
|
|
||||||
export type ErrorInfo = string | Error | AxiosError | undefined;
|
export type ErrorInfo = string | Error | AxiosError | undefined;
|
||||||
|
@ -22,12 +23,12 @@ function DescribeError(error: ErrorInfo) {
|
||||||
if (error.response.status === 404) {
|
if (error.response.status === 404) {
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col justify-start'>
|
<div className='flex flex-col justify-start'>
|
||||||
<p>{`Обращение к несуществующему API`}</p>
|
<p>{'Обращение к несуществующему API'}</p>
|
||||||
<PrettyJson data={error} />
|
<PrettyJson data={error} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isHtml = error.response.headers['content-type'].includes('text/html');
|
const isHtml = error.response.headers['content-type'].includes('text/html');
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col justify-start'>
|
<div className='flex flex-col justify-start'>
|
||||||
|
@ -42,7 +43,7 @@ function DescribeError(error: ErrorInfo) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BackendError({error}: BackendErrorProps) {
|
function BackendError({ error }: BackendErrorProps) {
|
||||||
return (
|
return (
|
||||||
<div className='py-2 text-sm font-semibold text-red-600 dark:text-red-400'>
|
<div className='py-2 text-sm font-semibold text-red-600 dark:text-red-400'>
|
||||||
{DescribeError(error)}
|
{DescribeError(error)}
|
||||||
|
@ -50,4 +51,4 @@ function BackendError({error}: BackendErrorProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BackendError;
|
export default BackendError;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { MouseEventHandler } from 'react'
|
import { type MouseEventHandler } from 'react';
|
||||||
|
|
||||||
interface ButtonProps {
|
interface ButtonProps {
|
||||||
id?: string
|
id?: string
|
||||||
|
@ -14,14 +14,15 @@ interface ButtonProps {
|
||||||
onClick?: MouseEventHandler<HTMLButtonElement> | undefined
|
onClick?: MouseEventHandler<HTMLButtonElement> | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
function Button({id, text, icon, tooltip,
|
function Button({
|
||||||
|
id, text, icon, tooltip,
|
||||||
dense, disabled,
|
dense, disabled,
|
||||||
borderClass='border rounded', colorClass='clr-btn-default', widthClass='w-fit h-fit',
|
borderClass = 'border rounded', colorClass = 'clr-btn-default', widthClass = 'w-fit h-fit',
|
||||||
loading, onClick,
|
loading, onClick,
|
||||||
...props
|
...props
|
||||||
}: ButtonProps) {
|
}: ButtonProps) {
|
||||||
const padding = dense ? 'px-1' : 'px-3 py-2'
|
const padding = dense ? 'px-1' : 'px-3 py-2';
|
||||||
const cursor = 'disabled:cursor-not-allowed ' + (loading ? 'cursor-progress ': 'cursor-pointer ')
|
const cursor = 'disabled:cursor-not-allowed ' + (loading ? 'cursor-progress ' : 'cursor-pointer ');
|
||||||
return (
|
return (
|
||||||
<button id={id}
|
<button id={id}
|
||||||
type='button'
|
type='button'
|
||||||
|
@ -34,7 +35,7 @@ function Button({id, text, icon, tooltip,
|
||||||
{icon && <span>{icon}</span>}
|
{icon && <span>{icon}</span>}
|
||||||
{text && <span className={'font-semibold'}>{text}</span>}
|
{text && <span className={'font-semibold'}>{text}</span>}
|
||||||
</button>
|
</button>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Button;
|
export default Button;
|
||||||
|
|
|
@ -4,7 +4,7 @@ interface CardProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
function Card({title, widthClass='min-w-fit', children}: CardProps) {
|
function Card({ title, widthClass = 'min-w-fit', children }: CardProps) {
|
||||||
return (
|
return (
|
||||||
<div className={`border shadow-md py-2 clr-card px-6 ${widthClass}`}>
|
<div className={`border shadow-md py-2 clr-card px-6 ${widthClass}`}>
|
||||||
{ title && <h1 className='mb-2 text-xl font-bold whitespace-nowrap'>{title}</h1> }
|
{ title && <h1 className='mb-2 text-xl font-bold whitespace-nowrap'>{title}</h1> }
|
||||||
|
@ -13,4 +13,4 @@ function Card({title, widthClass='min-w-fit', children}: CardProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Card;
|
export default Card;
|
||||||
|
|
|
@ -10,7 +10,7 @@ export interface CheckboxProps {
|
||||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function Checkbox({id, required, disabled, label, widthClass='w-full', value, onChange}: CheckboxProps) {
|
function Checkbox({ id, required, disabled, label, widthClass = 'w-full', value, onChange }: CheckboxProps) {
|
||||||
return (
|
return (
|
||||||
<div className={'flex gap-2 [&:not(:first-child)]:mt-3 ' + widthClass}>
|
<div className={'flex gap-2 [&:not(:first-child)]:mt-3 ' + widthClass}>
|
||||||
<input id={id} type='checkbox'
|
<input id={id} type='checkbox'
|
||||||
|
@ -20,7 +20,7 @@ function Checkbox({id, required, disabled, label, widthClass='w-full', value, on
|
||||||
checked={value}
|
checked={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
{ label && <Label
|
{ label && <Label
|
||||||
text={label}
|
text={label}
|
||||||
required={required}
|
required={required}
|
||||||
htmlFor={id}
|
htmlFor={id}
|
||||||
|
@ -36,4 +36,4 @@ function Checkbox({id, required, disabled, label, widthClass='w-full', value, on
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Checkbox;
|
export default Checkbox;
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { Tab} from 'react-tabs';
|
|
||||||
import type { TabProps } from 'react-tabs';
|
import type { TabProps } from 'react-tabs';
|
||||||
|
import { Tab } from 'react-tabs';
|
||||||
|
|
||||||
function ConceptTab({children, className, ...otherProps} : TabProps) {
|
function ConceptTab({ children, className, ...otherProps }: TabProps) {
|
||||||
return (
|
return (
|
||||||
<Tab
|
<Tab
|
||||||
className={`px-2 py-1 text-sm hover:cursor-pointer clr-tab ${className} whitespace-nowrap`}
|
className={`px-2 py-1 text-sm hover:cursor-pointer clr-tab ${className?.toString() ?? ''} whitespace-nowrap`}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@ -14,4 +14,4 @@ function ConceptTab({children, className, ...otherProps} : TabProps) {
|
||||||
|
|
||||||
ConceptTab.tabsRole = 'Tab';
|
ConceptTab.tabsRole = 'Tab';
|
||||||
|
|
||||||
export default ConceptTab;
|
export default ConceptTab;
|
||||||
|
|
|
@ -1,48 +1,49 @@
|
||||||
import DataTable, { createTheme, TableProps } from 'react-data-table-component';
|
import DataTable, { createTheme, type TableProps } from 'react-data-table-component';
|
||||||
|
|
||||||
import { useConceptTheme } from '../../context/ThemeContext';
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
|
|
||||||
export interface SelectionInfo<T> {
|
export interface SelectionInfo<T> {
|
||||||
allSelected: boolean;
|
allSelected: boolean
|
||||||
selectedCount: number;
|
selectedCount: number
|
||||||
selectedRows: T[];
|
selectedRows: T[]
|
||||||
}
|
}
|
||||||
|
|
||||||
createTheme('customDark', {
|
createTheme('customDark', {
|
||||||
text: {
|
text: {
|
||||||
primary: 'rgba(228, 228, 231, 1)',
|
primary: 'rgba(228, 228, 231, 1)',
|
||||||
secondary: 'rgba(228, 228, 231, 0.87)',
|
secondary: 'rgba(228, 228, 231, 0.87)',
|
||||||
disabled: 'rgba(228, 228, 231, 0.54)',
|
disabled: 'rgba(228, 228, 231, 0.54)'
|
||||||
},
|
},
|
||||||
background: {
|
background: {
|
||||||
default: '#002b36',
|
default: '#002b36'
|
||||||
},
|
},
|
||||||
context: {
|
context: {
|
||||||
background: '#3e014d',
|
background: '#3e014d',
|
||||||
text: 'rgba(228, 228, 231, 0.87)',
|
text: 'rgba(228, 228, 231, 0.87)'
|
||||||
},
|
},
|
||||||
highlightOnHover: {
|
highlightOnHover: {
|
||||||
default: '#3e014d',
|
default: '#3e014d',
|
||||||
text: 'rgba(228, 228, 231, 1)',
|
text: 'rgba(228, 228, 231, 1)'
|
||||||
},
|
},
|
||||||
divider: {
|
divider: {
|
||||||
default: '#6b6b6b',
|
default: '#6b6b6b'
|
||||||
},
|
},
|
||||||
striped: {
|
striped: {
|
||||||
default: '#004859',
|
default: '#004859',
|
||||||
text: 'rgba(228, 228, 231, 1)',
|
text: 'rgba(228, 228, 231, 1)'
|
||||||
},
|
},
|
||||||
selected: {
|
selected: {
|
||||||
default: '#4b015c',
|
default: '#4b015c',
|
||||||
text: 'rgba(228, 228, 231, 1)',
|
text: 'rgba(228, 228, 231, 1)'
|
||||||
},
|
}
|
||||||
}, 'dark');
|
}, 'dark');
|
||||||
|
|
||||||
function DataTableThemed<T>({theme, ...props}: TableProps<T>) {
|
function DataTableThemed<T>({ theme, ...props }: TableProps<T>) {
|
||||||
const { darkMode } = useConceptTheme();
|
const { darkMode } = useConceptTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable<T>
|
<DataTable<T>
|
||||||
theme={ theme ? theme : darkMode ? 'customDark' : ''}
|
theme={ theme ?? (darkMode ? 'customDark' : '')}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,7 +3,7 @@ interface DividerProps {
|
||||||
margins?: string
|
margins?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function Divider({vertical, margins='2'}: DividerProps) {
|
function Divider({ vertical, margins = '2' }: DividerProps) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{vertical && <div className={`mx-${margins} border-x-2`} />}
|
{vertical && <div className={`mx-${margins} border-x-2`} />}
|
||||||
|
@ -12,4 +12,4 @@ function Divider({vertical, margins='2'}: DividerProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Divider;
|
export default Divider;
|
||||||
|
|
|
@ -4,14 +4,14 @@ interface DropdownProps {
|
||||||
widthClass?: string
|
widthClass?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function Dropdown({children, widthClass='w-fit', stretchLeft}: DropdownProps) {
|
function Dropdown({ children, widthClass = 'w-fit', stretchLeft }: DropdownProps) {
|
||||||
return (
|
return (
|
||||||
<div className='relative'>
|
<div className='relative'>
|
||||||
<div className={`absolute ${stretchLeft ? 'right-0': 'left-0'} py-2 z-10 flex flex-col items-stretch justify-start px-2 mt-2 text-sm origin-top-right bg-white border border-gray-100 divide-y rounded-md shadow-lg dark:border-gray-500 dark:bg-gray-900 ${widthClass}`}>
|
<div className={`absolute ${stretchLeft ? 'right-0' : 'left-0'} py-2 z-10 flex flex-col items-stretch justify-start px-2 mt-2 text-sm origin-top-right bg-white border border-gray-100 divide-y rounded-md shadow-lg dark:border-gray-500 dark:bg-gray-900 ${widthClass}`}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Dropdown;
|
export default Dropdown;
|
||||||
|
|
|
@ -5,10 +5,10 @@ interface NavigationTextItemProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownButton({description='', onClick, disabled, children}: NavigationTextItemProps) {
|
function DropdownButton({ description = '', onClick, disabled, children }: NavigationTextItemProps) {
|
||||||
const behavior = (onClick ? 'cursor-pointer clr-hover': 'cursor-default') + ' disabled:cursor-not-allowed';
|
const behavior = (onClick ? 'cursor-pointer clr-hover' : 'cursor-default') + ' disabled:cursor-not-allowed';
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
title={description}
|
title={description}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
@ -19,4 +19,4 @@ function DropdownButton({description='', onClick, disabled, children}: Navigatio
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DropdownButton;
|
export default DropdownButton;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
|
|
||||||
import { UploadIcon } from '../Icons';
|
import { UploadIcon } from '../Icons';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
import Label from './Label';
|
import Label from './Label';
|
||||||
|
@ -12,16 +13,16 @@ interface FileInputProps {
|
||||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function FileInput({id, required, label, acceptType, widthClass='w-full', onChange}: FileInputProps) {
|
function FileInput({ id, required, label, acceptType, widthClass = 'w-full', onChange }: FileInputProps) {
|
||||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||||
const [labelText, setLabelText] = useState('Файл не выбран');
|
const [labelText, setLabelText] = useState('Файл не выбран');
|
||||||
|
|
||||||
const handleUploadClick = () => {
|
const handleUploadClick = () => {
|
||||||
inputRef.current?.click();
|
inputRef.current?.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if(event.target.files && event.target.files.length > 0) {
|
if (event.target.files && event.target.files.length > 0) {
|
||||||
setLabelText(event.target.files[0].name)
|
setLabelText(event.target.files[0].name)
|
||||||
} else {
|
} else {
|
||||||
setLabelText('Файл не выбран')
|
setLabelText('Файл не выбран')
|
||||||
|
@ -30,9 +31,9 @@ function FileInput({id, required, label, acceptType, widthClass='w-full', onChan
|
||||||
onChange(event);
|
onChange(event);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'flex gap-2 py-2 mt-3 items-center '+ widthClass}>
|
<div className={'flex gap-2 py-2 mt-3 items-center ' + widthClass}>
|
||||||
<input id={id} type='file'
|
<input id={id} type='file'
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
required={required}
|
required={required}
|
||||||
|
@ -40,16 +41,16 @@ function FileInput({id, required, label, acceptType, widthClass='w-full', onChan
|
||||||
accept={acceptType}
|
accept={acceptType}
|
||||||
onChange={handleFileChange}
|
onChange={handleFileChange}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
text={label}
|
text={label}
|
||||||
icon={<UploadIcon/>}
|
icon={<UploadIcon/>}
|
||||||
onClick={handleUploadClick}
|
onClick={handleUploadClick}
|
||||||
/>
|
/>
|
||||||
<Label
|
<Label
|
||||||
text={labelText}
|
text={labelText}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FileInput;
|
export default FileInput;
|
||||||
|
|
|
@ -7,7 +7,7 @@ interface FormProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
function Form({title, onSubmit, widthClass='max-w-xs', children}: FormProps) {
|
function Form({ title, onSubmit, widthClass = 'max-w-xs', children }: FormProps) {
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col items-center w-full'>
|
<div className='flex flex-col items-center w-full'>
|
||||||
<Card title={title} widthClass={widthClass}>
|
<Card title={title} widthClass={widthClass}>
|
||||||
|
@ -19,4 +19,4 @@ function Form({title, onSubmit, widthClass='max-w-xs', children}: FormProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Form;
|
export default Form;
|
||||||
|
|
|
@ -5,9 +5,9 @@ interface LabelProps {
|
||||||
title?: string
|
title?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function Label({text, htmlFor, required=false, title}: LabelProps) {
|
function Label({ text, htmlFor, required = false, title }: LabelProps) {
|
||||||
return (
|
return (
|
||||||
<label
|
<label
|
||||||
className='text-sm font-semibold'
|
className='text-sm font-semibold'
|
||||||
htmlFor={htmlFor}
|
htmlFor={htmlFor}
|
||||||
title={ (required && !title) ? 'обязательное поле' : title }
|
title={ (required && !title) ? 'обязательное поле' : title }
|
||||||
|
@ -17,4 +17,4 @@ function Label({text, htmlFor, required=false, title}: LabelProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Label;
|
export default Label;
|
||||||
|
|
|
@ -5,17 +5,17 @@ interface LabeledTextProps {
|
||||||
tooltip?: string
|
tooltip?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function LabeledText({id, label, text, tooltip}: LabeledTextProps) {
|
function LabeledText({ id, label, text, tooltip }: LabeledTextProps) {
|
||||||
return (
|
return (
|
||||||
<div className='flex justify-between gap-4'>
|
<div className='flex justify-between gap-4'>
|
||||||
<label
|
<label
|
||||||
className='font-semibold'
|
className='font-semibold'
|
||||||
title={tooltip}
|
title={tooltip}
|
||||||
htmlFor={id}
|
htmlFor={id}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
<span
|
<span
|
||||||
id={id}
|
id={id}
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
|
@ -24,4 +24,4 @@ function LabeledText({id, label, text, tooltip}: LabeledTextProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LabeledText;
|
export default LabeledText;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ThreeDots } from 'react-loader-spinner'
|
import { ThreeDots } from 'react-loader-spinner';
|
||||||
|
|
||||||
export function Loader() {
|
export function Loader() {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { useRef } from 'react'
|
import { useRef } from 'react';
|
||||||
import Button from './Button'
|
|
||||||
import useClickedOutside from '../../hooks/useClickedOutside'
|
import useClickedOutside from '../../hooks/useClickedOutside';
|
||||||
|
import Button from './Button';
|
||||||
|
|
||||||
interface ModalProps {
|
interface ModalProps {
|
||||||
title?: string
|
title?: string
|
||||||
|
@ -13,9 +14,9 @@ interface ModalProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
function Modal({title, show, toggle, onSubmit, onCancel, canSubmit, children, submitText='Продолжить'}: ModalProps) {
|
function Modal({ title, show, toggle, onSubmit, onCancel, canSubmit, children, submitText = 'Продолжить' }: ModalProps) {
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
useClickedOutside({ref: ref, callback: toggle})
|
useClickedOutside({ ref, callback: toggle })
|
||||||
|
|
||||||
if (!show) {
|
if (!show) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -23,7 +24,7 @@ function Modal({title, show, toggle, onSubmit, onCancel, canSubmit, children, su
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
toggle();
|
toggle();
|
||||||
if(onCancel) onCancel();
|
if (onCancel) onCancel();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
|
@ -33,14 +34,14 @@ function Modal({title, show, toggle, onSubmit, onCancel, canSubmit, children, su
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='fixed top-0 left-0 w-full h-full clr-modal opacity-50 z-50'>
|
<div className='fixed top-0 left-0 z-50 w-full h-full opacity-50 clr-modal'>
|
||||||
</div>
|
</div>
|
||||||
<div ref={ref} className='fixed bottom-1/2 left-1/2 -translate-y-1/2 -translate-x-1/2 px-6 py-4 flex flex-col w-fit z-[60] clr-card border shadow-md'>
|
<div ref={ref} className='fixed bottom-1/2 left-1/2 -translate-y-1/2 -translate-x-1/2 px-6 py-4 flex flex-col w-fit z-[60] clr-card border shadow-md'>
|
||||||
{ title && <h1 className='mb-4 text-xl font-bold'>{title}</h1> }
|
{ title && <h1 className='mb-4 text-xl font-bold'>{title}</h1> }
|
||||||
<div className='py-2'>
|
<div className='py-2'>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
<div className='pt-4 mt-2 border-t-4 flex justify-between w-full'>
|
<div className='flex justify-between w-full pt-4 mt-2 border-t-4'>
|
||||||
<Button
|
<Button
|
||||||
text={submitText}
|
text={submitText}
|
||||||
widthClass='min-w-[6rem] w-fit h-fit'
|
widthClass='min-w-[6rem] w-fit h-fit'
|
||||||
|
@ -48,7 +49,7 @@ function Modal({title, show, toggle, onSubmit, onCancel, canSubmit, children, su
|
||||||
disabled={!canSubmit}
|
disabled={!canSubmit}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
text='Отмена'
|
text='Отмена'
|
||||||
onClick={handleCancel}
|
onClick={handleCancel}
|
||||||
/>
|
/>
|
||||||
|
@ -58,4 +59,4 @@ function Modal({title, show, toggle, onSubmit, onCancel, canSubmit, children, su
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Modal;
|
export default Modal;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
interface PrettyJsonProps {
|
interface PrettyJsonProps {
|
||||||
data: Object
|
data: any
|
||||||
}
|
}
|
||||||
|
|
||||||
function PrettyJson({data}: PrettyJsonProps) {
|
function PrettyJson({ data }: PrettyJsonProps) {
|
||||||
return (<pre>{JSON.stringify(data, null, 2)}</pre>);
|
return (<pre>{JSON.stringify(data, null, 2)}</pre>);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PrettyJson;
|
export default PrettyJson;
|
||||||
|
|
|
@ -5,10 +5,10 @@ interface SubmitButtonProps {
|
||||||
icon?: React.ReactNode
|
icon?: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
function SubmitButton({text='ОК', icon, disabled, loading=false}: SubmitButtonProps) {
|
function SubmitButton({ text = 'ОК', icon, disabled, loading = false }: SubmitButtonProps) {
|
||||||
return (
|
return (
|
||||||
<button type='submit'
|
<button type='submit'
|
||||||
className={`px-4 py-2 inline-flex items-center gap-2 align-middle justify-center font-bold disabled:cursor-not-allowed rounded clr-btn-primary ${loading ? ' cursor-progress': ''}`}
|
className={`px-4 py-2 inline-flex items-center gap-2 align-middle justify-center font-bold disabled:cursor-not-allowed rounded clr-btn-primary ${loading ? ' cursor-progress' : ''}`}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
{icon && <span>{icon}</span>}
|
{icon && <span>{icon}</span>}
|
||||||
|
@ -17,4 +17,4 @@ function SubmitButton({text='ОК', icon, disabled, loading=false}: SubmitButton
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SubmitButton;
|
export default SubmitButton;
|
||||||
|
|
|
@ -16,19 +16,19 @@ interface TextAreaProps {
|
||||||
|
|
||||||
function TextArea({
|
function TextArea({
|
||||||
id, label, placeholder,
|
id, label, placeholder,
|
||||||
required, spellCheck, disabled,
|
required, spellCheck, disabled,
|
||||||
widthClass='w-full', rows=4, value,
|
widthClass = 'w-full', rows = 4, value,
|
||||||
onChange, onFocus
|
onChange, onFocus
|
||||||
}: TextAreaProps) {
|
}: TextAreaProps) {
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3'>
|
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3'>
|
||||||
<Label
|
<Label
|
||||||
text={label}
|
text={label}
|
||||||
required={required}
|
required={required}
|
||||||
htmlFor={id}
|
htmlFor={id}
|
||||||
/>
|
/>
|
||||||
<textarea id={id}
|
<textarea id={id}
|
||||||
className={'px-3 py-2 mt-2 leading-tight border shadow dark:bg-gray-800 '+ widthClass}
|
className={'px-3 py-2 mt-2 leading-tight border shadow dark:bg-gray-800 ' + widthClass}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
required={required}
|
required={required}
|
||||||
|
@ -42,4 +42,4 @@ function TextArea({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TextArea;
|
export default TextArea;
|
||||||
|
|
|
@ -1,26 +1,27 @@
|
||||||
import { InputHTMLAttributes } from 'react';
|
import { type InputHTMLAttributes } from 'react';
|
||||||
|
|
||||||
import Label from './Label';
|
import Label from './Label';
|
||||||
|
|
||||||
interface TextInputProps
|
interface TextInputProps
|
||||||
extends Omit<InputHTMLAttributes<HTMLInputElement>, 'className'> {
|
extends Omit<InputHTMLAttributes<HTMLInputElement>, 'className'> {
|
||||||
id: string
|
id: string
|
||||||
label: string
|
label: string
|
||||||
widthClass?: string
|
widthClass?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function TextInput({
|
function TextInput({
|
||||||
id, required, label, widthClass='w-full',
|
id, required, label, widthClass = 'w-full',
|
||||||
...props
|
...props
|
||||||
}: TextInputProps) {
|
}: TextInputProps) {
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3'>
|
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3'>
|
||||||
<Label
|
<Label
|
||||||
text={label}
|
text={label}
|
||||||
required={required}
|
required={required}
|
||||||
htmlFor={id}
|
htmlFor={id}
|
||||||
/>
|
/>
|
||||||
<input id={id}
|
<input id={id}
|
||||||
className={'px-3 py-2 mt-2 leading-tight border shadow dark:bg-gray-800 truncate hover:text-clip '+ widthClass}
|
className={'px-3 py-2 mt-2 leading-tight border shadow dark:bg-gray-800 truncate hover:text-clip ' + widthClass}
|
||||||
required={required}
|
required={required}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
@ -28,4 +29,4 @@ function TextInput({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TextInput;
|
export default TextInput;
|
||||||
|
|
|
@ -5,7 +5,7 @@ interface TextURLProps {
|
||||||
href: string
|
href: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function TextURL({text, href}: TextURLProps) {
|
function TextURL({ text, href }: TextURLProps) {
|
||||||
return (
|
return (
|
||||||
<Link className='text-sm font-bold text-blue-400 dark:text-orange-600 dark:hover:text-orange-400 hover:underline hover:text-blue-600' to={href}>
|
<Link className='text-sm font-bold text-blue-400 dark:text-orange-600 dark:hover:text-orange-400 hover:underline hover:text-blue-600' to={href}>
|
||||||
{text}
|
{text}
|
||||||
|
@ -13,4 +13,4 @@ function TextURL({text, href}: TextURLProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TextURL;
|
export default TextURL;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { FallbackProps } from 'react-error-boundary';
|
import { type FallbackProps } from 'react-error-boundary';
|
||||||
|
|
||||||
import Button from './Common/Button';
|
import Button from './Common/Button';
|
||||||
|
|
||||||
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
||||||
|
@ -11,4 +12,4 @@ function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ErrorFallback;
|
export default ErrorFallback;
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { urls } from '../utils/constants';
|
import { urls } from '../utils/constants';
|
||||||
|
|
||||||
function Footer() {
|
function Footer() {
|
||||||
return (
|
return (
|
||||||
<footer className='z-50 px-4 pt-2 pb-4 t border-t-2 clr-footer'>
|
<footer className='z-50 px-4 pt-2 pb-4 border-t-2 t clr-footer'>
|
||||||
<div className='flex items-stretch justify-center w-full mx-auto'>
|
<div className='flex items-stretch justify-center w-full mx-auto'>
|
||||||
<div className='px-4 underline'>
|
<div className='px-4 underline'>
|
||||||
<Link to='manuals' tabIndex={-1}>Справка</Link> <br/>
|
<Link to='manuals' tabIndex={-1}>Справка</Link> <br/>
|
||||||
|
|
|
@ -13,13 +13,13 @@ export interface IconProps {
|
||||||
color?: string
|
color?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function IconSVG({viewbox, size=6, color, props, children} : IconSVGProps) {
|
function IconSVG({ viewbox, size = 6, color, props, children }: IconSVGProps) {
|
||||||
const width = `${size*1/4}rem`
|
const width = `${size * 1 / 4}rem`
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
width={width}
|
width={width}
|
||||||
height={width}
|
height={width}
|
||||||
className={`w-[${width}] h-[${width}] ${color}`}
|
className={`w-[${width}] h-[${width}] ${color ?? ''}`}
|
||||||
fill='currentColor'
|
fill='currentColor'
|
||||||
viewBox={viewbox}
|
viewBox={viewbox}
|
||||||
{...props}
|
{...props}
|
||||||
|
@ -29,17 +29,17 @@ function IconSVG({viewbox, size=6, color, props, children} : IconSVGProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MagnifyingGlassIcon({size, ...props}: IconProps) {
|
export function MagnifyingGlassIcon({ size, ...props }: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 20 20' size={size || 5} {...props} >
|
<IconSVG viewbox='0 0 20 20' size={size ?? 5} {...props} >
|
||||||
<path d='M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z'/>
|
<path d='M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z'/>
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BellIcon(props: IconProps) {
|
export function BellIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 20 20' {...props}>
|
<IconSVG viewbox='0 0 20 20' {...props}>
|
||||||
<path d='M10 2a6 6 0 00-6 6v3.586l-.707.707A1 1 0 004 14h12a1 1 0 00.707-1.707L16 11.586V8a6 6 0 00-6-6zM10 18a3 3 0 01-3-3h6a3 3 0 01-3 3z' />
|
<path d='M10 2a6 6 0 00-6 6v3.586l-.707.707A1 1 0 004 14h12a1 1 0 00.707-1.707L16 11.586V8a6 6 0 00-6-6zM10 18a3 3 0 01-3-3h6a3 3 0 01-3 3z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -47,7 +47,7 @@ export function BellIcon(props: IconProps) {
|
||||||
|
|
||||||
export function EyeIcon(props: IconProps) {
|
export function EyeIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 1024 1024' {...props}>
|
<IconSVG viewbox='0 0 1024 1024' {...props}>
|
||||||
<path d='M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z' />
|
<path d='M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -64,7 +64,7 @@ export function EyeOffIcon(props: IconProps) {
|
||||||
|
|
||||||
export function PenIcon(props: IconProps) {
|
export function PenIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='-3 -3 21 21' {...props}>
|
<IconSVG viewbox='-3 -3 21 21' {...props}>
|
||||||
<path d='M15.502 1.94a.5.5 0 010 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 01.707 0l1.293 1.293zm-1.75 2.456l-2-2L4.939 9.21a.5.5 0 00-.121.196l-.805 2.414a.25.25 0 00.316.316l2.414-.805a.5.5 0 00.196-.12l6.813-6.814z' />
|
<path d='M15.502 1.94a.5.5 0 010 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 01.707 0l1.293 1.293zm-1.75 2.456l-2-2L4.939 9.21a.5.5 0 00-.121.196l-.805 2.414a.25.25 0 00.316.316l2.414-.805a.5.5 0 00.196-.12l6.813-6.814z' />
|
||||||
<path d='M1 13.5A1.5 1.5 0 002.5 15h11a1.5 1.5 0 001.5-1.5v-6a.5.5 0 00-1 0v6a.5.5 0 01-.5.5h-11a.5.5 0 01-.5-.5v-11a.5.5 0 01.5-.5H9a.5.5 0 000-1H2.5A1.5 1.5 0 001 2.5v11z' />
|
<path d='M1 13.5A1.5 1.5 0 002.5 15h11a1.5 1.5 0 001.5-1.5v-6a.5.5 0 00-1 0v6a.5.5 0 01-.5.5h-11a.5.5 0 01-.5-.5v-11a.5.5 0 01.5-.5H9a.5.5 0 000-1H2.5A1.5 1.5 0 001 2.5v11z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
|
@ -73,7 +73,7 @@ export function PenIcon(props: IconProps) {
|
||||||
|
|
||||||
export function SquaresIcon(props: IconProps) {
|
export function SquaresIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 20 20' {...props}>
|
<IconSVG viewbox='0 0 20 20' {...props}>
|
||||||
<path d='M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5zM11 13a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z' />
|
<path d='M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5zM11 13a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -81,7 +81,7 @@ export function SquaresIcon(props: IconProps) {
|
||||||
|
|
||||||
export function GroupIcon(props: IconProps) {
|
export function GroupIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 20 20' {...props}>
|
<IconSVG viewbox='0 0 20 20' {...props}>
|
||||||
<path d='M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3zM6 8a2 2 0 11-4 0 2 2 0 014 0zM16 18v-3a5.972 5.972 0 00-.75-2.906A3.005 3.005 0 0119 15v3h-3zM4.75 12.094A5.973 5.973 0 004 15v3H1v-3a3 3 0 013.75-2.906z' />
|
<path d='M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3zM6 8a2 2 0 11-4 0 2 2 0 014 0zM16 18v-3a5.972 5.972 0 00-.75-2.906A3.005 3.005 0 0119 15v3h-3zM4.75 12.094A5.973 5.973 0 004 15v3H1v-3a3 3 0 013.75-2.906z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -89,7 +89,7 @@ export function GroupIcon(props: IconProps) {
|
||||||
|
|
||||||
export function FrameIcon(props: IconProps) {
|
export function FrameIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 20 20' {...props}>
|
<IconSVG viewbox='0 0 20 20' {...props}>
|
||||||
<path d='M5 3a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2V5a2 2 0 00-2-2H5zm0 2h10v7h-2l-1 2H8l-1-2H5V5z' />
|
<path d='M5 3a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2V5a2 2 0 00-2-2H5zm0 2h10v7h-2l-1 2H8l-1-2H5V5z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -97,7 +97,7 @@ export function FrameIcon(props: IconProps) {
|
||||||
|
|
||||||
export function AsteriskIcon(props: IconProps) {
|
export function AsteriskIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 20 20' {...props}>
|
<IconSVG viewbox='0 0 20 20' {...props}>
|
||||||
<path d='M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z' />
|
<path d='M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -105,7 +105,7 @@ export function AsteriskIcon(props: IconProps) {
|
||||||
|
|
||||||
export function MenuIcon(props: IconProps) {
|
export function MenuIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||||
<path d='M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z' />
|
<path d='M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -113,7 +113,7 @@ export function MenuIcon(props: IconProps) {
|
||||||
|
|
||||||
export function ShareIcon(props: IconProps) {
|
export function ShareIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||||
<path d='M5.5 15a3.51 3.51 0 002.36-.93l6.26 3.58a3.06 3.06 0 00-.12.85 3.53 3.53 0 101.14-2.57l-6.26-3.58a2.74 2.74 0 00.12-.76l6.15-3.52A3.49 3.49 0 1014 5.5a3.35 3.35 0 00.12.85L8.43 9.6A3.5 3.5 0 105.5 15zm12 2a1.5 1.5 0 11-1.5 1.5 1.5 1.5 0 011.5-1.5zm0-13A1.5 1.5 0 1116 5.5 1.5 1.5 0 0117.5 4zm-12 6A1.5 1.5 0 114 11.5 1.5 1.5 0 015.5 10z' />
|
<path d='M5.5 15a3.51 3.51 0 002.36-.93l6.26 3.58a3.06 3.06 0 00-.12.85 3.53 3.53 0 101.14-2.57l-6.26-3.58a2.74 2.74 0 00.12-.76l6.15-3.52A3.49 3.49 0 1014 5.5a3.35 3.35 0 00.12.85L8.43 9.6A3.5 3.5 0 105.5 15zm12 2a1.5 1.5 0 11-1.5 1.5 1.5 1.5 0 011.5-1.5zm0-13A1.5 1.5 0 1116 5.5 1.5 1.5 0 0117.5 4zm-12 6A1.5 1.5 0 114 11.5 1.5 1.5 0 015.5 10z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -121,7 +121,7 @@ export function ShareIcon(props: IconProps) {
|
||||||
|
|
||||||
export function FilterCogIcon(props: IconProps) {
|
export function FilterCogIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||||
<path d='M22.77 19.32l-1.07-.82c.02-.17.04-.33.04-.5s-.01-.33-.04-.5l1.06-.82a.26.26 0 00.06-.32l-1-1.73c-.06-.13-.19-.13-.32-.13l-1.23.5c-.27-.18-.54-.35-.85-.47l-.19-1.32A.236.236 0 0019 13h-2a.26.26 0 00-.26.21l-.19 1.32c-.3.13-.59.29-.85.47l-1.24-.5c-.11 0-.24 0-.31.13l-1 1.73c-.06.11-.04.24.06.32l1.06.82a4.193 4.193 0 000 1l-1.06.82a.26.26 0 00-.06.32l1 1.73c.06.13.19.13.31.13l1.24-.5c.26.18.54.35.85.47l.19 1.32c.02.12.12.21.26.21h2c.11 0 .22-.09.24-.21l.19-1.32c.3-.13.57-.29.84-.47l1.23.5c.13 0 .26 0 .33-.13l1-1.73a.26.26 0 00-.06-.32M18 19.5c-.84 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5M3 3c-.22 0-.43.08-.62.22a1 1 0 00-.17 1.4L7.97 12H8v5.87c-.04.29.06.6.29.83l2.01 2.01c.35.35.89.37 1.28.09-.38-.89-.58-1.84-.58-2.8 0-1.27.35-2.5 1-3.6V12h.03l5.76-7.38a1 1 0 00-.17-1.4c-.19-.14-.4-.22-.62-.22H3z' />
|
<path d='M22.77 19.32l-1.07-.82c.02-.17.04-.33.04-.5s-.01-.33-.04-.5l1.06-.82a.26.26 0 00.06-.32l-1-1.73c-.06-.13-.19-.13-.32-.13l-1.23.5c-.27-.18-.54-.35-.85-.47l-.19-1.32A.236.236 0 0019 13h-2a.26.26 0 00-.26.21l-.19 1.32c-.3.13-.59.29-.85.47l-1.24-.5c-.11 0-.24 0-.31.13l-1 1.73c-.06.11-.04.24.06.32l1.06.82a4.193 4.193 0 000 1l-1.06.82a.26.26 0 00-.06.32l1 1.73c.06.13.19.13.31.13l1.24-.5c.26.18.54.35.85.47l.19 1.32c.02.12.12.21.26.21h2c.11 0 .22-.09.24-.21l.19-1.32c.3-.13.57-.29.84-.47l1.23.5c.13 0 .26 0 .33-.13l1-1.73a.26.26 0 00-.06-.32M18 19.5c-.84 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5M3 3c-.22 0-.43.08-.62.22a1 1 0 00-.17 1.4L7.97 12H8v5.87c-.04.29.06.6.29.83l2.01 2.01c.35.35.89.37 1.28.09-.38-.89-.58-1.84-.58-2.8 0-1.27.35-2.5 1-3.6V12h.03l5.76-7.38a1 1 0 00-.17-1.4c-.19-.14-.4-.22-.62-.22H3z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -129,7 +129,7 @@ export function FilterCogIcon(props: IconProps) {
|
||||||
|
|
||||||
export function FilterIcon(props: IconProps) {
|
export function FilterIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||||
<path d='M22 3H2l8 9.46V19l4 2v-8.54L22 3z' />
|
<path d='M22 3H2l8 9.46V19l4 2v-8.54L22 3z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -137,7 +137,7 @@ export function FilterIcon(props: IconProps) {
|
||||||
|
|
||||||
export function SortIcon(props: IconProps) {
|
export function SortIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||||
<path d='M8 16H4l6 6V2H8zm6-11v17h2V8h4l-6-6z' />
|
<path d='M8 16H4l6 6V2H8zm6-11v17h2V8h4l-6-6z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -145,7 +145,7 @@ export function SortIcon(props: IconProps) {
|
||||||
|
|
||||||
export function BookmarkIcon(props: IconProps) {
|
export function BookmarkIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 20 20' {...props}>
|
<IconSVG viewbox='0 0 20 20' {...props}>
|
||||||
<path d='M5 2a2 2 0 00-2 2v14l3.5-2 3.5 2 3.5-2 3.5 2V4a2 2 0 00-2-2H5zm2.5 3a1.5 1.5 0 100 3 1.5 1.5 0 000-3zm6.207.293a1 1 0 00-1.414 0l-6 6a1 1 0 101.414 1.414l6-6a1 1 0 000-1.414zM12.5 10a1.5 1.5 0 100 3 1.5 1.5 0 000-3z' />
|
<path d='M5 2a2 2 0 00-2 2v14l3.5-2 3.5 2 3.5-2 3.5 2V4a2 2 0 00-2-2H5zm2.5 3a1.5 1.5 0 100 3 1.5 1.5 0 000-3zm6.207.293a1 1 0 00-1.414 0l-6 6a1 1 0 101.414 1.414l6-6a1 1 0 000-1.414zM12.5 10a1.5 1.5 0 100 3 1.5 1.5 0 000-3z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -153,7 +153,7 @@ export function BookmarkIcon(props: IconProps) {
|
||||||
|
|
||||||
export function UserIcon(props: IconProps) {
|
export function UserIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 512 512' {...props}>
|
<IconSVG viewbox='0 0 512 512' {...props}>
|
||||||
<path d='M399 384.2c-22.1-38.4-63.6-64.2-111-64.2h-64c-47.4 0-88.9 25.8-111 64.2 35.2 39.2 86.2 63.8 143 63.8s107.8-24.7 143-63.8zM512 256c0 141.4-114.6 256-256 256S0 397.4 0 256 114.6 0 256 0s256 114.6 256 256zm-256 16c39.8 0 72-32.2 72-72s-32.2-72-72-72-72 32.2-72 72 32.2 72 72 72z' />
|
<path d='M399 384.2c-22.1-38.4-63.6-64.2-111-64.2h-64c-47.4 0-88.9 25.8-111 64.2 35.2 39.2 86.2 63.8 143 63.8s107.8-24.7 143-63.8zM512 256c0 141.4-114.6 256-256 256S0 397.4 0 256 114.6 0 256 0s256 114.6 256 256zm-256 16c39.8 0 72-32.2 72-72s-32.2-72-72-72-72 32.2-72 72 32.2 72 72 72z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -161,7 +161,7 @@ export function UserIcon(props: IconProps) {
|
||||||
|
|
||||||
export function EducationIcon(props: IconProps) {
|
export function EducationIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 20 20' {...props}>
|
<IconSVG viewbox='0 0 20 20' {...props}>
|
||||||
<path d='M3.33 8L10 12l10-6-10-6L0 6h10v2H3.33zM0 8v8l2-2.22V9.2L0 8zm10 12l-5-3-2-1.2v-6l7 4.2 7-4.2v6L10 20z' />
|
<path d='M3.33 8L10 12l10-6-10-6L0 6h10v2H3.33zM0 8v8l2-2.22V9.2L0 8zm10 12l-5-3-2-1.2v-6l7 4.2 7-4.2v6L10 20z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -169,7 +169,7 @@ export function EducationIcon(props: IconProps) {
|
||||||
|
|
||||||
export function DarkThemeIcon(props: IconProps) {
|
export function DarkThemeIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 20 20' {...props}>
|
<IconSVG viewbox='0 0 20 20' {...props}>
|
||||||
<path d='M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z' />
|
<path d='M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -177,7 +177,7 @@ export function DarkThemeIcon(props: IconProps) {
|
||||||
|
|
||||||
export function LightThemeIcon(props: IconProps) {
|
export function LightThemeIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 20 20' {...props}>
|
<IconSVG viewbox='0 0 20 20' {...props}>
|
||||||
<path d='M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z' />
|
<path d='M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -185,7 +185,7 @@ export function LightThemeIcon(props: IconProps) {
|
||||||
|
|
||||||
export function LibraryIcon(props: IconProps) {
|
export function LibraryIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 512 512' {...props}>
|
<IconSVG viewbox='0 0 512 512' {...props}>
|
||||||
<path d='M64 480H48a32 32 0 01-32-32V112a32 32 0 0132-32h16a32 32 0 0132 32v336a32 32 0 01-32 32zM240 176a32 32 0 00-32-32h-64a32 32 0 00-32 32v28a4 4 0 004 4h120a4 4 0 004-4zM112 448a32 32 0 0032 32h64a32 32 0 0032-32v-30a2 2 0 00-2-2H114a2 2 0 00-2 2z' />
|
<path d='M64 480H48a32 32 0 01-32-32V112a32 32 0 0132-32h16a32 32 0 0132 32v336a32 32 0 01-32 32zM240 176a32 32 0 00-32-32h-64a32 32 0 00-32 32v28a4 4 0 004 4h120a4 4 0 004-4zM112 448a32 32 0 0032 32h64a32 32 0 0032-32v-30a2 2 0 00-2-2H114a2 2 0 00-2 2z' />
|
||||||
<path d='M114 240 H238 A2 2 0 0 1 240 242 V382 A2 2 0 0 1 238 384 H114 A2 2 0 0 1 112 382 V242 A2 2 0 0 1 114 240 z' />
|
<path d='M114 240 H238 A2 2 0 0 1 240 242 V382 A2 2 0 0 1 238 384 H114 A2 2 0 0 1 112 382 V242 A2 2 0 0 1 114 240 z' />
|
||||||
<path d='M320 480h-32a32 32 0 01-32-32V64a32 32 0 0132-32h32a32 32 0 0132 32v384a32 32 0 01-32 32zM495.89 445.45l-32.23-340c-1.48-15.65-16.94-27-34.53-25.31l-31.85 3c-17.59 1.67-30.65 15.71-29.17 31.36l32.23 340c1.48 15.65 16.94 27 34.53 25.31l31.85-3c17.59-1.67 30.65-15.71 29.17-31.36z' />
|
<path d='M320 480h-32a32 32 0 01-32-32V64a32 32 0 0132-32h32a32 32 0 0132 32v384a32 32 0 01-32 32zM495.89 445.45l-32.23-340c-1.48-15.65-16.94-27-34.53-25.31l-31.85 3c-17.59 1.67-30.65 15.71-29.17 31.36l32.23 340c1.48 15.65 16.94 27 34.53 25.31l31.85-3c17.59-1.67 30.65-15.71 29.17-31.36z' />
|
||||||
|
@ -195,7 +195,7 @@ export function LibraryIcon(props: IconProps) {
|
||||||
|
|
||||||
export function PlusIcon(props: IconProps) {
|
export function PlusIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 1024 1024' {...props}>
|
<IconSVG viewbox='0 0 1024 1024' {...props}>
|
||||||
<path d='M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM704 536c0 4.4-3.6 8-8 8H544v152c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V544H328c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h152V328c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v152h152c4.4 0 8 3.6 8 8v48z' />
|
<path d='M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM704 536c0 4.4-3.6 8-8 8H544v152c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V544H328c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h152V328c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v152h152c4.4 0 8 3.6 8 8v48z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -203,7 +203,7 @@ export function PlusIcon(props: IconProps) {
|
||||||
|
|
||||||
export function SmallPlusIcon(props: IconProps) {
|
export function SmallPlusIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||||
<path d='M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12zm10-8a8 8 0 100 16 8 8 0 000-16z'/>
|
<path d='M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12zm10-8a8 8 0 100 16 8 8 0 000-16z'/>
|
||||||
<path d='M13 7a1 1 0 10-2 0v4H7a1 1 0 100 2h4v4a1 1 0 102 0v-4h4a1 1 0 100-2h-4V7z'/>
|
<path d='M13 7a1 1 0 10-2 0v4H7a1 1 0 100 2h4v4a1 1 0 102 0v-4h4a1 1 0 100-2h-4V7z'/>
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
|
@ -212,7 +212,7 @@ export function SmallPlusIcon(props: IconProps) {
|
||||||
|
|
||||||
export function UploadIcon(props: IconProps) {
|
export function UploadIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||||
<path d='M11 15h2V9h3l-4-5-4 5h3z'/>
|
<path d='M11 15h2V9h3l-4-5-4 5h3z'/>
|
||||||
<path d='M20 18H4v-7H2v7c0 1.103.897 2 2 2h16c1.103 0 2-.897 2-2v-7h-2v7z'/>
|
<path d='M20 18H4v-7H2v7c0 1.103.897 2 2 2h16c1.103 0 2-.897 2-2v-7h-2v7z'/>
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
|
@ -221,7 +221,7 @@ export function UploadIcon(props: IconProps) {
|
||||||
|
|
||||||
export function DownloadIcon(props: IconProps) {
|
export function DownloadIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||||
<path d='M12 16l4-5h-3V4h-2v7H8z'/>
|
<path d='M12 16l4-5h-3V4h-2v7H8z'/>
|
||||||
<path d='M20 18H4v-7H2v7c0 1.103.897 2 2 2h16c1.103 0 2-.897 2-2v-7h-2v7z'/>
|
<path d='M20 18H4v-7H2v7c0 1.103.897 2 2 2h16c1.103 0 2-.897 2-2v-7h-2v7z'/>
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
|
@ -230,7 +230,7 @@ export function DownloadIcon(props: IconProps) {
|
||||||
|
|
||||||
export function CrownIcon(props: IconProps) {
|
export function CrownIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||||
<path d='M5 16L3 5l5.5 5L12 4l3.5 6L21 5l-2 11H5m14 3c0 .6-.4 1-1 1H6c-.6 0-1-.4-1-1v-1h14v1z' />
|
<path d='M5 16L3 5l5.5 5L12 4l3.5 6L21 5l-2 11H5m14 3c0 .6-.4 1-1 1H6c-.6 0-1-.4-1-1v-1h14v1z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -238,7 +238,7 @@ export function CrownIcon(props: IconProps) {
|
||||||
|
|
||||||
export function ArrowUpIcon(props: IconProps) {
|
export function ArrowUpIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 16 16' {...props}>
|
<IconSVG viewbox='0 0 16 16' {...props}>
|
||||||
<path d='M8 12a.5.5 0 00.5-.5V5.707l2.146 2.147a.5.5 0 00.708-.708l-3-3a.5.5 0 00-.708 0l-3 3a.5.5 0 10.708.708L7.5 5.707V11.5a.5.5 0 00.5.5z' />
|
<path d='M8 12a.5.5 0 00.5-.5V5.707l2.146 2.147a.5.5 0 00.708-.708l-3-3a.5.5 0 00-.708 0l-3 3a.5.5 0 10.708.708L7.5 5.707V11.5a.5.5 0 00.5.5z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -246,7 +246,7 @@ export function ArrowUpIcon(props: IconProps) {
|
||||||
|
|
||||||
export function ArrowDownIcon(props: IconProps) {
|
export function ArrowDownIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 16 16' {...props}>
|
<IconSVG viewbox='0 0 16 16' {...props}>
|
||||||
<path d='M8 4a.5.5 0 01.5.5v5.793l2.146-2.147a.5.5 0 01.708.708l-3 3a.5.5 0 01-.708 0l-3-3a.5.5 0 11.708-.708L7.5 10.293V4.5A.5.5 0 018 4z' />
|
<path d='M8 4a.5.5 0 01.5.5v5.793l2.146-2.147a.5.5 0 01.708.708l-3 3a.5.5 0 01-.708 0l-3-3a.5.5 0 11.708-.708L7.5 10.293V4.5A.5.5 0 018 4z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -254,7 +254,7 @@ export function ArrowDownIcon(props: IconProps) {
|
||||||
|
|
||||||
export function CloneIcon(props: IconProps) {
|
export function CloneIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 512 512' {...props}>
|
<IconSVG viewbox='0 0 512 512' {...props}>
|
||||||
<path d='M64 464h224c8.8 0 16-7.2 16-16v-64h48v64c0 35.3-28.7 64-64 64H64c-35.35 0-64-28.7-64-64V224c0-35.3 28.65-64 64-64h64v48H64c-8.84 0-16 7.2-16 16v224c0 8.8 7.16 16 16 16zm96-400c0-35.35 28.7-64 64-64h224c35.3 0 64 28.65 64 64v224c0 35.3-28.7 64-64 64H224c-35.3 0-64-28.7-64-64V64zm64 240h224c8.8 0 16-7.2 16-16V64c0-8.84-7.2-16-16-16H224c-8.8 0-16 7.16-16 16v224c0 8.8 7.2 16 16 16z' />
|
<path d='M64 464h224c8.8 0 16-7.2 16-16v-64h48v64c0 35.3-28.7 64-64 64H64c-35.35 0-64-28.7-64-64V224c0-35.3 28.65-64 64-64h64v48H64c-8.84 0-16 7.2-16 16v224c0 8.8 7.16 16 16 16zm96-400c0-35.35 28.7-64 64-64h224c35.3 0 64 28.65 64 64v224c0 35.3-28.7 64-64 64H224c-35.3 0-64-28.7-64-64V64zm64 240h224c8.8 0 16-7.2 16-16V64c0-8.84-7.2-16-16-16H224c-8.8 0-16 7.16-16 16v224c0 8.8 7.2 16 16 16z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -262,7 +262,7 @@ export function CloneIcon(props: IconProps) {
|
||||||
|
|
||||||
export function DumpBinIcon(props: IconProps) {
|
export function DumpBinIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||||
<path d='M19 4h-3.5l-1-1h-5l-1 1H5v2h14M6 19a2 2 0 002 2h8a2 2 0 002-2V7H6v12z' />
|
<path d='M19 4h-3.5l-1-1h-5l-1 1H5v2h14M6 19a2 2 0 002 2h8a2 2 0 002-2V7H6v12z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -270,7 +270,7 @@ export function DumpBinIcon(props: IconProps) {
|
||||||
|
|
||||||
export function ArrowsRotateIcon(props: IconProps) {
|
export function ArrowsRotateIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||||
<path d='M12 6v3l4-4-4-4v3a8 8 0 00-8 8c0 1.57.46 3.03 1.24 4.26L6.7 14.8A5.9 5.9 0 016 12a6 6 0 016-6m6.76 1.74L17.3 9.2c.44.84.7 1.8.7 2.8a6 6 0 01-6 6v-3l-4 4 4 4v-3a8 8 0 008-8c0-1.57-.46-3.03-1.24-4.26z' />
|
<path d='M12 6v3l4-4-4-4v3a8 8 0 00-8 8c0 1.57.46 3.03 1.24 4.26L6.7 14.8A5.9 5.9 0 016 12a6 6 0 016-6m6.76 1.74L17.3 9.2c.44.84.7 1.8.7 2.8a6 6 0 01-6 6v-3l-4 4 4 4v-3a8 8 0 008-8c0-1.57-.46-3.03-1.24-4.26z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
|
@ -278,8 +278,8 @@ export function ArrowsRotateIcon(props: IconProps) {
|
||||||
|
|
||||||
export function SaveIcon(props: IconProps) {
|
export function SaveIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||||
<path d='M19.35 10.04A7.49 7.49 0 0012 4C9.11 4 6.6 5.64 5.35 8.04A5.994 5.994 0 000 14a6 6 0 006 6h13a5 5 0 005-5c0-2.64-2.05-4.78-4.65-4.96M19 18H6a4 4 0 01-4-4c0-2.05 1.53-3.76 3.56-3.97l1.07-.11.5-.95A5.469 5.469 0 0112 6c2.62 0 4.88 1.86 5.39 4.43l.3 1.5 1.53.11A2.98 2.98 0 0122 15a3 3 0 01-3 3M8 13h2.55v3h2.9v-3H16l-4-4-4 4z' />
|
<path d='M19.35 10.04A7.49 7.49 0 0012 4C9.11 4 6.6 5.64 5.35 8.04A5.994 5.994 0 000 14a6 6 0 006 6h13a5 5 0 005-5c0-2.64-2.05-4.78-4.65-4.96M19 18H6a4 4 0 01-4-4c0-2.05 1.53-3.76 3.56-3.97l1.07-.11.5-.95A5.469 5.469 0 0112 6c2.62 0 4.88 1.86 5.39 4.43l.3 1.5 1.53.11A2.98 2.98 0 0122 15a3 3 0 01-3 3M8 13h2.55v3h2.9v-3H16l-4-4-4 4z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
interface InfoMessageProps {
|
interface InfoMessageProps {
|
||||||
message: string
|
message: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InfoMessage({message}: InfoMessageProps) {
|
export function InfoMessage({ message }: InfoMessageProps) {
|
||||||
return (
|
return (
|
||||||
<p className='font-bold'>{ message }</p>
|
<p className='font-bold'>{ message }</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default InfoMessage;
|
export default InfoMessage;
|
||||||
|
|
|
@ -4,7 +4,7 @@ interface LogoProps {
|
||||||
title: string
|
title: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function Logo({title}: LogoProps) {
|
function Logo({ title }: LogoProps) {
|
||||||
return (
|
return (
|
||||||
<Link to='/' className='flex items-center mr-4' tabIndex={-1}>
|
<Link to='/' className='flex items-center mr-4' tabIndex={-1}>
|
||||||
<img src='/favicon.svg' className='min-h-[2.5rem] mr-2 min-w-[2.5rem]' alt=''/>
|
<img src='/favicon.svg' className='min-h-[2.5rem] mr-2 min-w-[2.5rem]' alt=''/>
|
||||||
|
@ -12,4 +12,4 @@ function Logo({title}: LogoProps) {
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default Logo;
|
export default Logo;
|
||||||
|
|
|
@ -1,24 +1,25 @@
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import TopSearch from './TopSearch';
|
|
||||||
import { EducationIcon, LibraryIcon } from '../Icons';
|
|
||||||
import NavigationButton from './NavigationButton';
|
|
||||||
import UserMenu from './UserMenu';
|
|
||||||
import { useAuth } from '../../context/AuthContext';
|
|
||||||
import UserTools from './UserTools';
|
|
||||||
import Logo from './Logo';
|
|
||||||
import { useConceptTheme } from '../../context/ThemeContext';
|
|
||||||
|
|
||||||
function Navigation() {
|
import { useAuth } from '../../context/AuthContext';
|
||||||
const {user} = useAuth();
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
|
import { EducationIcon, LibraryIcon } from '../Icons';
|
||||||
|
import Logo from './Logo'
|
||||||
|
import NavigationButton from './NavigationButton';
|
||||||
|
import TopSearch from './TopSearch';
|
||||||
|
import UserMenu from './UserMenu';
|
||||||
|
import UserTools from './UserTools';
|
||||||
|
|
||||||
|
function Navigation () {
|
||||||
|
const { user } = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { noNavigation, toggleNoNavigation } = useConceptTheme();
|
const { noNavigation, toggleNoNavigation } = useConceptTheme();
|
||||||
|
|
||||||
const navigateCommon = () => navigate('/rsforms?filter=common');
|
const navigateCommon = () => { navigate('/rsforms?filter=common') };
|
||||||
const navigateHelp = () => navigate('/manuals');
|
const navigateHelp = () => { navigate('/manuals') };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className='sticky top-0 left-0 right-0 z-50'>
|
<nav className='sticky top-0 left-0 right-0 z-50'>
|
||||||
{!noNavigation &&
|
{!noNavigation &&
|
||||||
<button
|
<button
|
||||||
title='Скрыть навигацию'
|
title='Скрыть навигацию'
|
||||||
className='absolute top-0 right-0 z-[60] w-[1.2rem] h-[4rem] border-b-2 border-l-2 clr-nav rounded-none'
|
className='absolute top-0 right-0 z-[60] w-[1.2rem] h-[4rem] border-b-2 border-l-2 clr-nav rounded-none'
|
||||||
|
@ -26,7 +27,7 @@ function Navigation() {
|
||||||
>
|
>
|
||||||
<p>{'>'}</p><p>{'>'}</p>
|
<p>{'>'}</p><p>{'>'}</p>
|
||||||
</button>}
|
</button>}
|
||||||
{noNavigation &&
|
{noNavigation &&
|
||||||
<button
|
<button
|
||||||
title='Показать навигацию'
|
title='Показать навигацию'
|
||||||
className='absolute top-0 right-0 z-[60] w-[4rem] h-[1.6rem] border-b-2 border-l-2 clr-nav rounded-none'
|
className='absolute top-0 right-0 z-[60] w-[4rem] h-[1.6rem] border-b-2 border-l-2 clr-nav rounded-none'
|
||||||
|
@ -34,7 +35,7 @@ function Navigation() {
|
||||||
>
|
>
|
||||||
{'∨∨∨'}
|
{'∨∨∨'}
|
||||||
</button>}
|
</button>}
|
||||||
{!noNavigation &&
|
{!noNavigation &&
|
||||||
<div className='pr-6 pl-2 py-2.5 h-[4rem] flex items-center justify-between border-b-2 clr-nav rounded-none'>
|
<div className='pr-6 pl-2 py-2.5 h-[4rem] flex items-center justify-between border-b-2 clr-nav rounded-none'>
|
||||||
<div className='flex items-start justify-start '>
|
<div className='flex items-start justify-start '>
|
||||||
<Logo title='КонцептПортал' />
|
<Logo title='КонцептПортал' />
|
||||||
|
|
|
@ -7,7 +7,7 @@ interface NavigationButtonProps {
|
||||||
|
|
||||||
const defaultColors = 'text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white'
|
const defaultColors = 'text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white'
|
||||||
|
|
||||||
function NavigationButton({icon, description, colorClass=defaultColors, onClick}: NavigationButtonProps) {
|
function NavigationButton({ icon, description, colorClass = defaultColors, onClick }: NavigationButtonProps) {
|
||||||
return (
|
return (
|
||||||
<button title={description}
|
<button title={description}
|
||||||
type='button'
|
type='button'
|
||||||
|
@ -19,4 +19,4 @@ function NavigationButton({icon, description, colorClass=defaultColors, onClick}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NavigationButton;
|
export default NavigationButton;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { DarkThemeIcon, LightThemeIcon } from '../Icons';
|
||||||
import NavigationButton from './NavigationButton';
|
import NavigationButton from './NavigationButton';
|
||||||
|
|
||||||
function ThemeSwitcher() {
|
function ThemeSwitcher() {
|
||||||
const {darkMode, toggleDarkMode} = useConceptTheme();
|
const { darkMode, toggleDarkMode } = useConceptTheme();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{darkMode && <NavigationButton icon={<LightThemeIcon />} description='Светлая тема' onClick={toggleDarkMode} />}
|
{darkMode && <NavigationButton icon={<LightThemeIcon />} description='Светлая тема' onClick={toggleDarkMode} />}
|
||||||
|
@ -12,4 +12,4 @@ function ThemeSwitcher() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ThemeSwitcher;
|
export default ThemeSwitcher;
|
||||||
|
|
|
@ -4,7 +4,7 @@ interface TopSearchProps {
|
||||||
placeholder: string
|
placeholder: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function TopSearch({placeholder}: TopSearchProps) {
|
function TopSearch({ placeholder }: TopSearchProps) {
|
||||||
return (
|
return (
|
||||||
<form action='#' method='GET' className='hidden md:block md:pl-2'>
|
<form action='#' method='GET' className='hidden md:block md:pl-2'>
|
||||||
<div className='relative md:w-96'>
|
<div className='relative md:w-96'>
|
||||||
|
@ -24,4 +24,4 @@ function TopSearch({placeholder}: TopSearchProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TopSearch;
|
export default TopSearch;
|
||||||
|
|
|
@ -1,30 +1,32 @@
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
import DropdownButton from '../Common/DropdownButton';
|
|
||||||
import { useConceptTheme } from '../../context/ThemeContext';
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
import Dropdown from '../Common/Dropdown';
|
import Dropdown from '../Common/Dropdown';
|
||||||
|
import DropdownButton from '../Common/DropdownButton';
|
||||||
|
|
||||||
interface UserDropdownProps {
|
interface UserDropdownProps {
|
||||||
hideDropdown: Function
|
hideDropdown: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function UserDropdown({hideDropdown}: UserDropdownProps) {
|
function UserDropdown({ hideDropdown }: UserDropdownProps) {
|
||||||
const {darkMode, toggleDarkMode} = useConceptTheme();
|
const { darkMode, toggleDarkMode } = useConceptTheme();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const {user, logout} = useAuth();
|
const { user, logout } = useAuth();
|
||||||
|
|
||||||
const navigateProfile = () => {
|
const navigateProfile = () => {
|
||||||
hideDropdown()
|
hideDropdown()
|
||||||
navigate('/profile');
|
navigate('/profile');
|
||||||
};
|
};
|
||||||
|
|
||||||
const logoutAndRedirect = () => {
|
const logoutAndRedirect =
|
||||||
hideDropdown()
|
() => {
|
||||||
logout(() => {navigate('/login/');})
|
hideDropdown();
|
||||||
|
logout(() => { navigate('/login/'); })
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigateMyWork = () => {
|
const navigateMyWork = () => {
|
||||||
hideDropdown()
|
hideDropdown();
|
||||||
navigate('/rsforms?filter=personal');
|
navigate('/rsforms?filter=personal');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -34,7 +36,7 @@ function UserDropdown({hideDropdown}: UserDropdownProps) {
|
||||||
{user?.username}
|
{user?.username}
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
<DropdownButton description='Переключение темы оформления' onClick={toggleDarkMode}>
|
<DropdownButton description='Переключение темы оформления' onClick={toggleDarkMode}>
|
||||||
{darkMode ? 'Светлая тема' : 'Темная тема'}
|
{darkMode ? 'Светлая тема' : 'Темная тема'}
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
<DropdownButton onClick={navigateMyWork}>
|
<DropdownButton onClick={navigateMyWork}>
|
||||||
Мои схемы
|
Мои схемы
|
||||||
|
@ -46,4 +48,4 @@ function UserDropdown({hideDropdown}: UserDropdownProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default UserDropdown;
|
export default UserDropdown;
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { UserIcon } from '../Icons';
|
|
||||||
import { useAuth } from '../../context/AuthContext';
|
|
||||||
import UserDropdown from './UserDropdown';
|
|
||||||
import NavigationButton from './NavigationButton';
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { useAuth } from '../../context/AuthContext';
|
||||||
import useDropdown from '../../hooks/useDropdown';
|
import useDropdown from '../../hooks/useDropdown';
|
||||||
|
import { UserIcon } from '../Icons';
|
||||||
|
import NavigationButton from './NavigationButton';
|
||||||
|
import UserDropdown from './UserDropdown';
|
||||||
|
|
||||||
function LoginRef() {
|
function LoginRef() {
|
||||||
return (
|
return (
|
||||||
|
@ -14,23 +15,23 @@ function LoginRef() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function UserMenu() {
|
function UserMenu() {
|
||||||
const {user} = useAuth();
|
const { user } = useAuth();
|
||||||
const menu = useDropdown();
|
const menu = useDropdown();
|
||||||
return (
|
return (
|
||||||
<div ref={menu.ref}>
|
<div ref={menu.ref}>
|
||||||
{ !user && <LoginRef />}
|
{ !user && <LoginRef />}
|
||||||
{ user &&
|
{ user &&
|
||||||
<NavigationButton
|
<NavigationButton
|
||||||
icon={<UserIcon />}
|
icon={<UserIcon />}
|
||||||
description={`Пользователь ${user?.username}`}
|
description={`Пользователь ${user?.username}`}
|
||||||
onClick={menu.toggle}
|
onClick={menu.toggle}
|
||||||
/>}
|
/>}
|
||||||
{ user && menu.isActive &&
|
{ user && menu.isActive &&
|
||||||
<UserDropdown
|
<UserDropdown
|
||||||
hideDropdown={() => menu.hide()}
|
hideDropdown={() => { menu.hide(); }}
|
||||||
/>}
|
/>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default UserMenu;
|
export default UserMenu;
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import NavigationButton from './NavigationButton';
|
|
||||||
import { BellIcon, PlusIcon, SquaresIcon } from '../Icons';
|
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import { BellIcon, PlusIcon, SquaresIcon } from '../Icons';
|
||||||
|
import NavigationButton from './NavigationButton';
|
||||||
|
|
||||||
function UserTools() {
|
function UserTools() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const navigateCreateRSForm = () => navigate('/rsform-create');
|
const navigateCreateRSForm = () => { navigate('/rsform-create'); };
|
||||||
const navigateMyWork = () => navigate('/rsforms?filter=personal');
|
const navigateMyWork = () => { navigate('/rsforms?filter=personal'); };
|
||||||
|
|
||||||
const handleNotifications = () => {
|
const handleNotifications = () => {
|
||||||
toast.info('Уведомления в разработке');
|
toast.info('Уведомления в разработке');
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex items-center px-2 border-r-2 border-gray-400 dark:border-gray-300'>
|
<div className='flex items-center px-2 border-r-2 border-gray-400 dark:border-gray-300'>
|
||||||
<span>
|
<span>
|
||||||
|
@ -28,4 +29,4 @@ function UserTools() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default UserTools;
|
export default UserTools;
|
||||||
|
|
|
@ -6,22 +6,22 @@ interface RequireAuthProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
function RequireAuth({children}: RequireAuthProps) {
|
function RequireAuth({ children }: RequireAuthProps) {
|
||||||
const { user } = useAuth()
|
const { user } = useAuth()
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{user && children}
|
{user && children}
|
||||||
{!user &&
|
{!user &&
|
||||||
<div className='flex flex-col items-center'>
|
<div className='flex flex-col items-center'>
|
||||||
<InfoMessage message={'Данная функция доступна только зарегистрированным пользователям. Пожалуйста войдите в систему'} />
|
<InfoMessage message={'Данная функция доступна только зарегистрированным пользователям. Пожалуйста войдите в систему'} />
|
||||||
<div className='flex flex-col items-start'>
|
<div className='flex flex-col items-start'>
|
||||||
<TextURL text='Войти в систему...' href='login' />
|
<TextURL text='Войти в систему...' href='login' />
|
||||||
<TextURL text='Зарегистрироваться...' href='signup' />
|
<TextURL text='Зарегистрироваться...' href='signup' />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RequireAuth;
|
export default RequireAuth;
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import { ToastContainer, ToastContainerProps } from 'react-toastify';
|
import { ToastContainer, type ToastContainerProps } from 'react-toastify';
|
||||||
|
|
||||||
import { useConceptTheme } from '../context/ThemeContext';
|
import { useConceptTheme } from '../context/ThemeContext';
|
||||||
|
|
||||||
function ToasterThemed({theme, ...props}: ToastContainerProps) {
|
function ToasterThemed({ theme, ...props }: ToastContainerProps) {
|
||||||
const { darkMode } = useConceptTheme();
|
const { darkMode } = useConceptTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToastContainer
|
<ToastContainer
|
||||||
theme={ darkMode ? 'dark' : 'light'}
|
theme={ darkMode ? 'dark' : 'light'}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default ToasterThemed;
|
export default ToasterThemed;
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useState } from 'react';
|
import { createContext, useCallback, useContext, useLayoutEffect, useState } from 'react';
|
||||||
import { ICurrentUser, IUserSignupData } from '../utils/models';
|
|
||||||
import { ErrorInfo } from '../components/BackendError';
|
|
||||||
import useLocalStorage from '../hooks/useLocalStorage';
|
|
||||||
import { BackendCallback, getAuth, postLogin, postLogout, postSignup } from '../utils/backendAPI';
|
|
||||||
|
|
||||||
|
import { type ErrorInfo } from '../components/BackendError';
|
||||||
|
import useLocalStorage from '../hooks/useLocalStorage';
|
||||||
|
import { type BackendCallback, getAuth, postLogin, postLogout, postSignup } from '../utils/backendAPI';
|
||||||
|
import { type ICurrentUser, type IUserSignupData } from '../utils/models';
|
||||||
|
|
||||||
interface IAuthContext {
|
interface IAuthContext {
|
||||||
user: ICurrentUser | undefined
|
user: ICurrentUser | undefined
|
||||||
login: (username: string, password: string, callback?: BackendCallback) => Promise<void>
|
login: (username: string, password: string, callback?: BackendCallback) => void
|
||||||
logout: (callback?: BackendCallback) => Promise<void>
|
logout: (callback?: BackendCallback) => void
|
||||||
signup: (data: IUserSignupData, callback?: BackendCallback) => Promise<void>
|
signup: (data: IUserSignupData, callback?: BackendCallback) => void
|
||||||
loading: boolean
|
loading: boolean
|
||||||
error: ErrorInfo
|
error: ErrorInfo
|
||||||
setError: (error: ErrorInfo) => void
|
setError: (error: ErrorInfo) => void
|
||||||
|
@ -37,8 +37,8 @@ export const AuthState = ({ children }: AuthStateProps) => {
|
||||||
|
|
||||||
const loadCurrentUser = useCallback(
|
const loadCurrentUser = useCallback(
|
||||||
async () => {
|
async () => {
|
||||||
getAuth({
|
await getAuth({
|
||||||
onError: error => setUser(undefined),
|
onError: () => { setUser(undefined); },
|
||||||
onSucccess: response => {
|
onSucccess: response => {
|
||||||
if (response.data.id) {
|
if (response.data.id) {
|
||||||
setUser(response.data);
|
setUser(response.data);
|
||||||
|
@ -49,51 +49,54 @@ export const AuthState = ({ children }: AuthStateProps) => {
|
||||||
});
|
});
|
||||||
}, [setUser]
|
}, [setUser]
|
||||||
);
|
);
|
||||||
|
|
||||||
async function login(uname: string, pw: string, callback?: BackendCallback) {
|
function login(uname: string, pw: string, callback?: BackendCallback) {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
postLogin({
|
postLogin({
|
||||||
data: {username: uname, password: pw},
|
data: { username: uname, password: pw },
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setLoading,
|
setLoading,
|
||||||
onError: error => setError(error),
|
onError: error => { setError(error); },
|
||||||
onSucccess:
|
onSucccess:
|
||||||
async (response) => {
|
(response) => {
|
||||||
await loadCurrentUser();
|
loadCurrentUser()
|
||||||
if(callback) callback(response);
|
.then(() => { if (callback) callback(response); })
|
||||||
|
.catch(console.error);
|
||||||
}
|
}
|
||||||
});
|
}).catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function logout(callback?: BackendCallback) {
|
function logout(callback?: BackendCallback) {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
postLogout({
|
postLogout({
|
||||||
showError: true,
|
showError: true,
|
||||||
onSucccess:
|
onSucccess:
|
||||||
async (response) => {
|
(response) => {
|
||||||
await loadCurrentUser();
|
loadCurrentUser()
|
||||||
if (callback) callback(response);
|
.then(() => { if (callback) callback(response); })
|
||||||
|
.catch(console.error);
|
||||||
}
|
}
|
||||||
});
|
}).catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function signup(data: IUserSignupData, callback?: BackendCallback) {
|
function signup(data: IUserSignupData, callback?: BackendCallback) {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
postSignup({
|
postSignup({
|
||||||
data: data,
|
data,
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setLoading,
|
setLoading,
|
||||||
onError: error => setError(error),
|
onError: error => { setError(error); },
|
||||||
onSucccess:
|
onSucccess:
|
||||||
async (response) => {
|
(response) => {
|
||||||
await loadCurrentUser();
|
loadCurrentUser()
|
||||||
if (callback) callback(response);
|
.then(() => { if (callback) callback(response); })
|
||||||
|
.catch(console.error);
|
||||||
}
|
}
|
||||||
});
|
}).catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
loadCurrentUser();
|
loadCurrentUser().catch(console.error);
|
||||||
}, [loadCurrentUser])
|
}, [loadCurrentUser])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -103,4 +106,4 @@ export const AuthState = ({ children }: AuthStateProps) => {
|
||||||
{children}
|
{children}
|
||||||
</AuthContext.Provider>
|
</AuthContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import { createContext, useState, useContext, useMemo, useCallback } from 'react';
|
import { createContext, useCallback, useContext, useMemo, useState } from 'react'
|
||||||
import { IConstituenta, IRSForm } from '../utils/models';
|
import { toast } from 'react-toastify'
|
||||||
import { useRSFormDetails } from '../hooks/useRSFormDetails';
|
|
||||||
import { ErrorInfo } from '../components/BackendError';
|
import { type ErrorInfo } from '../components/BackendError'
|
||||||
import { useAuth } from './AuthContext';
|
import { useRSFormDetails } from '../hooks/useRSFormDetails'
|
||||||
import {
|
import {
|
||||||
BackendCallback, deleteRSForm, getTRSFile,
|
type BackendCallback, deleteRSForm, getTRSFile,
|
||||||
patchConstituenta, patchMoveConstituenta, patchRSForm,
|
patchConstituenta, patchDeleteConstituenta, patchMoveConstituenta, patchRSForm,
|
||||||
postClaimRSForm, patchDeleteConstituenta, postNewConstituenta
|
postClaimRSForm, postNewConstituenta
|
||||||
} from '../utils/backendAPI';
|
} from '../utils/backendAPI'
|
||||||
import { toast } from 'react-toastify';
|
import { type IConstituenta, type IRSForm } from '../utils/models'
|
||||||
|
import { useAuth } from './AuthContext'
|
||||||
|
|
||||||
interface IRSFormContext {
|
interface IRSFormContext {
|
||||||
schema?: IRSForm
|
schema?: IRSForm
|
||||||
|
@ -25,32 +26,32 @@ interface IRSFormContext {
|
||||||
isReadonly: boolean
|
isReadonly: boolean
|
||||||
isTracking: boolean
|
isTracking: boolean
|
||||||
isForceAdmin: boolean
|
isForceAdmin: boolean
|
||||||
|
|
||||||
setActiveID: React.Dispatch<React.SetStateAction<number | undefined>>
|
setActiveID: React.Dispatch<React.SetStateAction<number | undefined>>
|
||||||
toggleForceAdmin: () => void
|
toggleForceAdmin: () => void
|
||||||
toggleReadonly: () => void
|
toggleReadonly: () => void
|
||||||
toggleTracking: () => void
|
toggleTracking: () => void
|
||||||
|
|
||||||
update: (data: any, callback?: BackendCallback) => Promise<void>
|
update: (data: any, callback?: BackendCallback) => void
|
||||||
destroy: (callback?: BackendCallback) => Promise<void>
|
destroy: (callback?: BackendCallback) => void
|
||||||
claim: (callback?: BackendCallback) => Promise<void>
|
claim: (callback?: BackendCallback) => void
|
||||||
download: (callback: BackendCallback) => Promise<void>
|
download: (callback: BackendCallback) => void
|
||||||
|
|
||||||
cstUpdate: (data: any, callback?: BackendCallback) => Promise<void>
|
cstUpdate: (data: any, callback?: BackendCallback) => void
|
||||||
cstCreate: (data: any, callback?: BackendCallback) => Promise<void>
|
cstCreate: (data: any, callback?: BackendCallback) => void
|
||||||
cstDelete: (data: any, callback?: BackendCallback) => Promise<void>
|
cstDelete: (data: any, callback?: BackendCallback) => void
|
||||||
cstMoveTo: (data: any, callback?: BackendCallback) => Promise<void>
|
cstMoveTo: (data: any, callback?: BackendCallback) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const RSFormContext = createContext<IRSFormContext | null>(null);
|
const RSFormContext = createContext<IRSFormContext | null>(null)
|
||||||
export const useRSForm = () => {
|
export const useRSForm = () => {
|
||||||
const context = useContext(RSFormContext);
|
const context = useContext(RSFormContext)
|
||||||
if (!context) {
|
if (context == null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'useRSForm has to be used within <RSFormState.Provider>'
|
'useRSForm has to be used within <RSFormState.Provider>'
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
return context;
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RSFormStateProps {
|
interface RSFormStateProps {
|
||||||
|
@ -59,163 +60,181 @@ interface RSFormStateProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth()
|
||||||
const { schema, reload, error, setError, setSchema, loading } = useRSFormDetails({target: schemaID});
|
const { schema, reload, error, setError, setSchema, loading } = useRSFormDetails({ target: schemaID })
|
||||||
const [processing, setProcessing] = useState(false)
|
const [processing, setProcessing] = useState(false)
|
||||||
const [activeID, setActiveID] = useState<number | undefined>(undefined);
|
const [activeID, setActiveID] = useState<number | undefined>(undefined)
|
||||||
|
|
||||||
const [isForceAdmin, setIsForceAdmin] = useState(false);
|
const [isForceAdmin, setIsForceAdmin] = useState(false)
|
||||||
const [isReadonly, setIsReadonly] = useState(false);
|
const [isReadonly, setIsReadonly] = useState(false)
|
||||||
|
|
||||||
const isOwned = useMemo(() => user?.id === schema?.owner || false, [user, schema?.owner]);
|
const isOwned = useMemo(() => user?.id === schema?.owner || false, [user, schema?.owner])
|
||||||
const isClaimable = useMemo(() => user?.id !== schema?.owner || false, [user, schema?.owner]);
|
const isClaimable = useMemo(() => user?.id !== schema?.owner || false, [user, schema?.owner])
|
||||||
const isEditable = useMemo(
|
const isEditable = useMemo(
|
||||||
() => {
|
() => {
|
||||||
return (
|
return (
|
||||||
!loading && !isReadonly &&
|
!loading && !isReadonly &&
|
||||||
(isOwned || (isForceAdmin && user?.is_staff) || false)
|
((isOwned || (isForceAdmin && user?.is_staff)) ?? false)
|
||||||
)
|
)
|
||||||
}, [user, isReadonly, isForceAdmin, isOwned, loading]);
|
}, [user, isReadonly, isForceAdmin, isOwned, loading])
|
||||||
|
|
||||||
const activeCst = useMemo(
|
const activeCst = useMemo(
|
||||||
() => {
|
() => {
|
||||||
return schema?.items && schema?.items.find((cst) => cst.id === activeID);
|
return schema?.items?.find((cst) => cst.id === activeID)
|
||||||
}, [schema?.items, activeID]);
|
}, [schema?.items, activeID])
|
||||||
|
|
||||||
const isTracking = useMemo(
|
const isTracking = useMemo(
|
||||||
() => {
|
() => {
|
||||||
return true;
|
return true
|
||||||
}, []);
|
}, [])
|
||||||
|
|
||||||
const toggleTracking = useCallback(
|
const toggleTracking = useCallback(
|
||||||
() => {
|
() => {
|
||||||
toast('not implemented yet');
|
toast('not implemented yet')
|
||||||
}, []);
|
}, [])
|
||||||
|
|
||||||
const update = useCallback(
|
const update = useCallback(
|
||||||
async (data: any, callback?: BackendCallback) => {
|
(data: any, callback?: BackendCallback) => {
|
||||||
setError(undefined);
|
setError(undefined)
|
||||||
patchRSForm(schemaID, {
|
patchRSForm(schemaID, {
|
||||||
data: data,
|
data,
|
||||||
showError: true,
|
|
||||||
setLoading: setProcessing,
|
|
||||||
onError: error => setError(error),
|
|
||||||
onSucccess: async (response) => {
|
|
||||||
await reload();
|
|
||||||
if (callback) callback(response);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [schemaID, setError, reload]);
|
|
||||||
|
|
||||||
const destroy = useCallback(
|
|
||||||
async (callback?: BackendCallback) => {
|
|
||||||
setError(undefined);
|
|
||||||
deleteRSForm(schemaID, {
|
|
||||||
showError: true,
|
|
||||||
setLoading: setProcessing,
|
|
||||||
onError: error => setError(error),
|
|
||||||
onSucccess: callback
|
|
||||||
});
|
|
||||||
}, [schemaID, setError]);
|
|
||||||
|
|
||||||
const claim = useCallback(
|
|
||||||
async (callback?: BackendCallback) => {
|
|
||||||
setError(undefined);
|
|
||||||
postClaimRSForm(schemaID, {
|
|
||||||
showError: true,
|
|
||||||
setLoading: setProcessing,
|
|
||||||
onError: error => setError(error),
|
|
||||||
onSucccess: async (response) => {
|
|
||||||
schema!.owner = user!.id
|
|
||||||
schema!.time_update = response.data['time_update']
|
|
||||||
setSchema(schema)
|
|
||||||
if (callback) callback(response);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [schemaID, setError, schema, user, setSchema]);
|
|
||||||
|
|
||||||
const download = useCallback(
|
|
||||||
async (callback: BackendCallback) => {
|
|
||||||
setError(undefined);
|
|
||||||
getTRSFile(schemaID, {
|
|
||||||
showError: true,
|
|
||||||
setLoading: setProcessing,
|
|
||||||
onError: error => setError(error),
|
|
||||||
onSucccess: callback
|
|
||||||
});
|
|
||||||
}, [schemaID, setError]);
|
|
||||||
|
|
||||||
const cstUpdate = useCallback(
|
|
||||||
async (data: any, callback?: BackendCallback) => {
|
|
||||||
setError(undefined);
|
|
||||||
patchConstituenta(String(activeID), {
|
|
||||||
data: data,
|
|
||||||
showError: true,
|
|
||||||
setLoading: setProcessing,
|
|
||||||
onError: error => setError(error),
|
|
||||||
onSucccess: callback
|
|
||||||
});
|
|
||||||
}, [activeID, setError]);
|
|
||||||
|
|
||||||
const cstCreate = useCallback(
|
|
||||||
async (data: any, callback?: BackendCallback) => {
|
|
||||||
setError(undefined);
|
|
||||||
postNewConstituenta(schemaID, {
|
|
||||||
data: data,
|
|
||||||
showError: true,
|
|
||||||
setLoading: setProcessing,
|
|
||||||
onError: error => setError(error),
|
|
||||||
onSucccess: async (response) => {
|
|
||||||
setSchema(response.data['schema']);
|
|
||||||
if (callback) callback(response);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [schemaID, setError, setSchema]);
|
|
||||||
|
|
||||||
const cstDelete = useCallback(
|
|
||||||
async (data: any, callback?: BackendCallback) => {
|
|
||||||
setError(undefined);
|
|
||||||
patchDeleteConstituenta(schemaID, {
|
|
||||||
data: data,
|
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setProcessing,
|
setLoading: setProcessing,
|
||||||
onError: error => setError(error),
|
onError: error => { setError(error) },
|
||||||
onSucccess: async (response) => {
|
onSucccess: (response) => {
|
||||||
setSchema(response.data);
|
reload()
|
||||||
if (callback) callback(response);
|
.then(() => { if (callback != null) callback(response); })
|
||||||
|
.catch(console.error);
|
||||||
}
|
}
|
||||||
});
|
}).catch(console.error);
|
||||||
}, [schemaID, setError, setSchema]);
|
}, [schemaID, setError, reload])
|
||||||
|
|
||||||
const cstMoveTo = useCallback(
|
const destroy = useCallback(
|
||||||
async (data: any, callback?: BackendCallback) => {
|
(callback?: BackendCallback) => {
|
||||||
setError(undefined);
|
setError(undefined)
|
||||||
patchMoveConstituenta(schemaID, {
|
deleteRSForm(schemaID, {
|
||||||
data: data,
|
showError: true,
|
||||||
showError: true,
|
setLoading: setProcessing,
|
||||||
setLoading: setProcessing,
|
onError: error => { setError(error) },
|
||||||
onError: error => setError(error),
|
onSucccess: callback
|
||||||
onSucccess: (response) => {
|
}).catch(console.error);
|
||||||
setSchema(response.data);
|
}, [schemaID, setError])
|
||||||
if (callback) callback(response);
|
|
||||||
}
|
const claim = useCallback(
|
||||||
});
|
(callback?: BackendCallback) => {
|
||||||
}, [schemaID, setError, setSchema]);
|
if (!schema || !user) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setError(undefined)
|
||||||
|
postClaimRSForm(schemaID, {
|
||||||
|
showError: true,
|
||||||
|
setLoading: setProcessing,
|
||||||
|
onError: error => { setError(error) },
|
||||||
|
onSucccess: (response) => {
|
||||||
|
schema.owner = user.id
|
||||||
|
schema.time_update = response.data.time_update
|
||||||
|
setSchema(schema)
|
||||||
|
if (callback != null) callback(response)
|
||||||
|
}
|
||||||
|
}).catch(console.error);
|
||||||
|
}, [schemaID, setError, schema, user, setSchema])
|
||||||
|
|
||||||
|
const download = useCallback(
|
||||||
|
(callback: BackendCallback) => {
|
||||||
|
setError(undefined)
|
||||||
|
getTRSFile(schemaID, {
|
||||||
|
showError: true,
|
||||||
|
setLoading: setProcessing,
|
||||||
|
onError: error => { setError(error) },
|
||||||
|
onSucccess: callback
|
||||||
|
}).catch(console.error);
|
||||||
|
}, [schemaID, setError])
|
||||||
|
|
||||||
|
const cstUpdate = useCallback(
|
||||||
|
(data: any, callback?: BackendCallback) => {
|
||||||
|
setError(undefined)
|
||||||
|
patchConstituenta(String(activeID), {
|
||||||
|
data,
|
||||||
|
showError: true,
|
||||||
|
setLoading: setProcessing,
|
||||||
|
onError: error => { setError(error) },
|
||||||
|
onSucccess: callback
|
||||||
|
}).catch(console.error);
|
||||||
|
}, [activeID, setError])
|
||||||
|
|
||||||
|
const cstCreate = useCallback(
|
||||||
|
(data: any, callback?: BackendCallback) => {
|
||||||
|
setError(undefined)
|
||||||
|
postNewConstituenta(schemaID, {
|
||||||
|
data,
|
||||||
|
showError: true,
|
||||||
|
setLoading: setProcessing,
|
||||||
|
onError: error => { setError(error) },
|
||||||
|
onSucccess: (response) => {
|
||||||
|
setSchema(response.data.schema)
|
||||||
|
if (callback != null) callback(response)
|
||||||
|
}
|
||||||
|
}).catch(console.error);
|
||||||
|
}, [schemaID, setError, setSchema])
|
||||||
|
|
||||||
|
const cstDelete = useCallback(
|
||||||
|
(data: any, callback?: BackendCallback) => {
|
||||||
|
setError(undefined)
|
||||||
|
patchDeleteConstituenta(schemaID, {
|
||||||
|
data,
|
||||||
|
showError: true,
|
||||||
|
setLoading: setProcessing,
|
||||||
|
onError: error => { setError(error) },
|
||||||
|
onSucccess: (response) => {
|
||||||
|
setSchema(response.data)
|
||||||
|
if (callback != null) callback(response)
|
||||||
|
}
|
||||||
|
}).catch(console.error);
|
||||||
|
}, [schemaID, setError, setSchema])
|
||||||
|
|
||||||
|
const cstMoveTo = useCallback(
|
||||||
|
(data: any, callback?: BackendCallback) => {
|
||||||
|
setError(undefined)
|
||||||
|
patchMoveConstituenta(schemaID, {
|
||||||
|
data,
|
||||||
|
showError: true,
|
||||||
|
setLoading: setProcessing,
|
||||||
|
onError: error => { setError(error) },
|
||||||
|
onSucccess: (response) => {
|
||||||
|
setSchema(response.data)
|
||||||
|
if (callback != null) callback(response)
|
||||||
|
}
|
||||||
|
}).catch(console.error);
|
||||||
|
}, [schemaID, setError, setSchema])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RSFormContext.Provider value={{
|
<RSFormContext.Provider value={{
|
||||||
schema, error, loading, processing,
|
schema,
|
||||||
activeID, activeCst,
|
error,
|
||||||
|
loading,
|
||||||
|
processing,
|
||||||
|
activeID,
|
||||||
|
activeCst,
|
||||||
setActiveID,
|
setActiveID,
|
||||||
isForceAdmin, isReadonly,
|
isForceAdmin,
|
||||||
toggleForceAdmin: () => setIsForceAdmin(prev => !prev),
|
isReadonly,
|
||||||
toggleReadonly: () => setIsReadonly(prev => !prev),
|
toggleForceAdmin: () => { setIsForceAdmin(prev => !prev) },
|
||||||
isOwned, isEditable, isClaimable,
|
toggleReadonly: () => { setIsReadonly(prev => !prev) },
|
||||||
isTracking, toggleTracking,
|
isOwned,
|
||||||
update, download, destroy, claim,
|
isEditable,
|
||||||
cstUpdate, cstCreate, cstDelete, cstMoveTo,
|
isClaimable,
|
||||||
|
isTracking,
|
||||||
|
toggleTracking,
|
||||||
|
update,
|
||||||
|
download,
|
||||||
|
destroy,
|
||||||
|
claim,
|
||||||
|
cstUpdate,
|
||||||
|
cstCreate,
|
||||||
|
cstDelete,
|
||||||
|
cstMoveTo
|
||||||
}}>
|
}}>
|
||||||
{ children }
|
{ children }
|
||||||
</RSFormContext.Provider>
|
</RSFormContext.Provider>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { createContext, useContext, useEffect, useState } from 'react';
|
import { createContext, useContext, useEffect, useState } from 'react';
|
||||||
import useLocalStorage from '../hooks/useLocalStorage';
|
|
||||||
|
|
||||||
|
import useLocalStorage from '../hooks/useLocalStorage';
|
||||||
|
|
||||||
interface IThemeContext {
|
interface IThemeContext {
|
||||||
darkMode: boolean
|
darkMode: boolean
|
||||||
|
@ -44,10 +44,12 @@ export const ThemeState = ({ children }: ThemeStateProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeContext.Provider value={{
|
<ThemeContext.Provider value={{
|
||||||
darkMode, toggleDarkMode: () => setDarkMode(prev => !prev),
|
darkMode,
|
||||||
noNavigation, toggleNoNavigation: () => setNoNavigation(prev => !prev),
|
toggleDarkMode: () => { setDarkMode(prev => !prev); },
|
||||||
|
noNavigation,
|
||||||
|
toggleNoNavigation: () => { setNoNavigation(prev => !prev); }
|
||||||
}}>
|
}}>
|
||||||
{children}
|
{children}
|
||||||
</ThemeContext.Provider>
|
</ThemeContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { createContext, useCallback, useContext, useEffect, useState } from 'react'
|
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
||||||
import { IUserInfo } from '../utils/models'
|
|
||||||
import { getActiveUsers } from '../utils/backendAPI'
|
|
||||||
|
|
||||||
|
import { getActiveUsers } from '../utils/backendAPI';
|
||||||
|
import { type IUserInfo } from '../utils/models';
|
||||||
|
|
||||||
interface IUsersContext {
|
interface IUsersContext {
|
||||||
users: IUserInfo[]
|
users: IUserInfo[]
|
||||||
|
@ -9,10 +9,10 @@ interface IUsersContext {
|
||||||
getUserLabel: (userID?: number) => string
|
getUserLabel: (userID?: number) => string
|
||||||
}
|
}
|
||||||
|
|
||||||
const UsersContext = createContext<IUsersContext | null>(null);
|
const UsersContext = createContext<IUsersContext | null>(null)
|
||||||
export const useUsers = () => {
|
export const useUsers = (): IUsersContext => {
|
||||||
const context = useContext(UsersContext);
|
const context = useContext(UsersContext);
|
||||||
if (!context) {
|
if (context == null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'useUsers has to be used within <UsersState.Provider>'
|
'useUsers has to be used within <UsersState.Provider>'
|
||||||
);
|
);
|
||||||
|
@ -25,47 +25,48 @@ interface UsersStateProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UsersState = ({ children }: UsersStateProps) => {
|
export const UsersState = ({ children }: UsersStateProps) => {
|
||||||
const [users, setUsers] = useState<IUserInfo[]>([]);
|
const [users, setUsers] = useState<IUserInfo[]>([])
|
||||||
|
|
||||||
const getUserLabel = (userID?: number) => {
|
const getUserLabel = (userID?: number) => {
|
||||||
const user = users.find(({id}) => id === userID);
|
const user = users.find(({ id }) => id === userID)
|
||||||
if (!user) {
|
if (user == null) {
|
||||||
return (userID ? userID.toString() : 'Отсутствует');
|
return (userID !== undefined ? userID.toString() : 'Отсутствует');
|
||||||
}
|
}
|
||||||
if (user.first_name || user.last_name) {
|
const hasFirstName = user.first_name != null && user.first_name !== '';
|
||||||
if (!user.last_name) {
|
const hasLastName = user.last_name != null && user.last_name !== '';
|
||||||
|
if (hasFirstName || hasLastName) {
|
||||||
|
if (!hasLastName) {
|
||||||
return user.first_name;
|
return user.first_name;
|
||||||
}
|
}
|
||||||
if (!user.first_name) {
|
if (!hasFirstName) {
|
||||||
return user.last_name;
|
return user.last_name;
|
||||||
}
|
}
|
||||||
return user.first_name + ' ' + user.last_name
|
return user.first_name + ' ' + user.last_name;
|
||||||
}
|
}
|
||||||
return user.username;
|
return user.username;
|
||||||
}
|
}
|
||||||
|
|
||||||
const reload = useCallback(
|
const reload = useCallback(
|
||||||
async () => {
|
async () => {
|
||||||
getActiveUsers({
|
await getActiveUsers({
|
||||||
showError: true,
|
showError: true,
|
||||||
onError: error => setUsers([]),
|
onError: () => { setUsers([]); },
|
||||||
onSucccess: response => {
|
onSucccess: response => { setUsers(response.data); }
|
||||||
setUsers(response ? response.data : []);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}, [setUsers]
|
}, [setUsers]
|
||||||
);
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reload();
|
reload().catch(console.error);
|
||||||
}, [reload]);
|
}, [reload])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UsersContext.Provider value={{
|
<UsersContext.Provider value={{
|
||||||
users,
|
users,
|
||||||
reload, getUserLabel
|
reload,
|
||||||
|
getUserLabel
|
||||||
}}>
|
}}>
|
||||||
{ children }
|
{ children }
|
||||||
</UsersContext.Provider>
|
</UsersContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,25 @@
|
||||||
|
import { type AxiosResponse } from 'axios';
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { ErrorInfo } from '../components/BackendError';
|
|
||||||
import { postCheckExpression } from '../utils/backendAPI';
|
|
||||||
import { IRSForm } from '../utils/models';
|
|
||||||
import { AxiosResponse } from 'axios';
|
|
||||||
|
|
||||||
function useCheckExpression({schema}: {schema?: IRSForm}) {
|
import { type ErrorInfo } from '../components/BackendError';
|
||||||
|
import { postCheckExpression } from '../utils/backendAPI';
|
||||||
|
import { type IRSForm } from '../utils/models';
|
||||||
|
|
||||||
|
function useCheckExpression({ schema }: { schema?: IRSForm }) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||||
const [parseData, setParseData] = useState<any | undefined>(undefined);
|
const [parseData, setParseData] = useState<any | undefined>(undefined);
|
||||||
|
|
||||||
const resetParse = useCallback(() => setParseData(undefined), []);
|
const resetParse = useCallback(() => { setParseData(undefined); }, []);
|
||||||
|
|
||||||
async function checkExpression(expression: string, onSuccess?: (response: AxiosResponse) => void) {
|
async function checkExpression(expression: string, onSuccess?: (response: AxiosResponse) => void) {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
setParseData(undefined);
|
setParseData(undefined);
|
||||||
postCheckExpression(String(schema!.id), {
|
await postCheckExpression(String(schema?.id), {
|
||||||
data: {'expression': expression},
|
data: { expression },
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setLoading,
|
setLoading,
|
||||||
onError: error => setError(error),
|
onError: error => { setError(error); },
|
||||||
onSucccess: (response) => {
|
onSucccess: (response) => {
|
||||||
setParseData(response.data);
|
setParseData(response.data);
|
||||||
if (onSuccess) onSuccess(response);
|
if (onSuccess) onSuccess(response);
|
||||||
|
@ -29,4 +30,4 @@ function useCheckExpression({schema}: {schema?: IRSForm}) {
|
||||||
return { parseData, checkExpression, resetParse, error, setError, loading };
|
return { parseData, checkExpression, resetParse, error, setError, loading };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useCheckExpression;
|
export default useCheckExpression;
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { assertIsNode } from '../utils/utils';
|
import { assertIsNode } from '../utils/utils';
|
||||||
|
|
||||||
function useClickedOutside({ref, callback}: {ref: React.RefObject<HTMLElement>, callback: Function}) {
|
function useClickedOutside({ ref, callback }: { ref: React.RefObject<HTMLElement>, callback?: () => void }) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleClickOutside(event: MouseEvent) {
|
function handleClickOutside(event: MouseEvent) {
|
||||||
assertIsNode(event.target);
|
assertIsNode(event.target);
|
||||||
if (ref.current && !ref.current.contains(event.target)) {
|
if (ref.current && !ref.current.contains(event.target)) {
|
||||||
callback()
|
if (callback) callback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
document.addEventListener('mouseup', handleClickOutside);
|
document.addEventListener('mouseup', handleClickOutside);
|
||||||
|
@ -16,4 +17,4 @@ function useClickedOutside({ref, callback}: {ref: React.RefObject<HTMLElement>,
|
||||||
}, [ref, callback]);
|
}, [ref, callback]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useClickedOutside;
|
export default useClickedOutside;
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
|
|
||||||
import useClickedOutside from './useClickedOutside';
|
import useClickedOutside from './useClickedOutside';
|
||||||
|
|
||||||
function useDropdown() {
|
function useDropdown() {
|
||||||
const [isActive, setIsActive] = useState(false);
|
const [isActive, setIsActive] = useState(false);
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
|
|
||||||
useClickedOutside({ref: ref, callback: () => setIsActive(false)})
|
useClickedOutside({ ref, callback: () => { setIsActive(false); } })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ref: ref,
|
ref,
|
||||||
isActive: isActive,
|
isActive,
|
||||||
setIsActive: setIsActive,
|
setIsActive,
|
||||||
toggle: () => setIsActive(!isActive),
|
toggle: () => { setIsActive(!isActive); },
|
||||||
hide: () => setIsActive(false)
|
hide: () => { setIsActive(false); }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useDropdown;
|
export default useDropdown;
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import { useState, useEffect } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
function getStorageValue<ValueType>(key: string, defaultValue: ValueType) {
|
function getStorageValue<ValueType>(key: string, defaultValue: ValueType) {
|
||||||
const saved = localStorage.getItem(key);
|
const saved = localStorage.getItem(key);
|
||||||
const initial = saved ? JSON.parse(saved!) : undefined;
|
const initial = saved ? JSON.parse(saved) : undefined;
|
||||||
return initial || defaultValue;
|
return initial || defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useLocalStorage =
|
const useLocalStorage =
|
||||||
<ValueType>(key: string, defaultValue: ValueType):
|
<ValueType>(key: string, defaultValue: ValueType):
|
||||||
[ValueType, React.Dispatch<React.SetStateAction<ValueType>>] => {
|
[ValueType, React.Dispatch<React.SetStateAction<ValueType>>] => {
|
||||||
const [value, setValue] = useState<ValueType>(() => {
|
const [value, setValue] = useState<ValueType>(() => {
|
||||||
return getStorageValue(key, defaultValue);
|
return getStorageValue(key, defaultValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(value === undefined) {
|
if (value === undefined) {
|
||||||
localStorage.removeItem(key)
|
localStorage.removeItem(key)
|
||||||
} else {
|
} else {
|
||||||
localStorage.setItem(key, JSON.stringify(value));
|
localStorage.setItem(key, JSON.stringify(value));
|
||||||
|
@ -24,4 +24,4 @@ const useLocalStorage =
|
||||||
return [value, setValue];
|
return [value, setValue];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useLocalStorage;
|
export default useLocalStorage;
|
||||||
|
|
|
@ -1,30 +1,32 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { ErrorInfo } from '../components/BackendError';
|
|
||||||
|
import { type ErrorInfo } from '../components/BackendError';
|
||||||
import { postNewRSForm } from '../utils/backendAPI';
|
import { postNewRSForm } from '../utils/backendAPI';
|
||||||
|
|
||||||
function useNewRSForm() {
|
function useNewRSForm() {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||||
|
|
||||||
async function createSchema({data, file, onSuccess}: {
|
async function createSchema({ data, file, onSuccess }: {
|
||||||
data: any, file?: File,
|
data: any
|
||||||
|
file?: File
|
||||||
onSuccess: (newID: string) => void
|
onSuccess: (newID: string) => void
|
||||||
}) {
|
}) {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
if (file) {
|
if (file) {
|
||||||
data['file'] = file;
|
data.file = file;
|
||||||
data['fileName'] = file.name;
|
data.fileName = file.name;
|
||||||
}
|
}
|
||||||
postNewRSForm({
|
await postNewRSForm({
|
||||||
data: data,
|
data,
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setLoading,
|
setLoading,
|
||||||
onError: error => setError(error),
|
onError: error => { setError(error); },
|
||||||
onSucccess: response => onSuccess(response.data.id)
|
onSucccess: response => { onSuccess(response.data.id); }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { createSchema, error, setError, loading };
|
return { createSchema, error, setError, loading };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useNewRSForm;
|
export default useNewRSForm;
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { CalculateStats, IRSForm } from '../utils/models'
|
|
||||||
import { ErrorInfo } from '../components/BackendError';
|
|
||||||
import { getRSFormDetails } from '../utils/backendAPI';
|
|
||||||
|
|
||||||
export function useRSFormDetails({target}: {target?: string}) {
|
import { type ErrorInfo } from '../components/BackendError';
|
||||||
|
import { getRSFormDetails } from '../utils/backendAPI';
|
||||||
|
import { CalculateStats, type IRSForm } from '../utils/models'
|
||||||
|
|
||||||
|
export function useRSFormDetails({ target }: { target?: string }) {
|
||||||
const [schema, setInnerSchema] = useState<IRSForm | undefined>(undefined);
|
const [schema, setInnerSchema] = useState<IRSForm | undefined>(undefined);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||||
|
@ -15,27 +16,27 @@ export function useRSFormDetails({target}: {target?: string}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchData = useCallback(
|
const fetchData = useCallback(
|
||||||
async () => {
|
async () => {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
setInnerSchema(undefined);
|
setInnerSchema(undefined);
|
||||||
if (!target) {
|
if (!target) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
getRSFormDetails(target, {
|
await getRSFormDetails(target, {
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setLoading,
|
setLoading,
|
||||||
onError: error => setError(error),
|
onError: error => { setError(error); },
|
||||||
onSucccess: (response) => setSchema(response.data)
|
onSucccess: (response) => { setSchema(response.data); }
|
||||||
});
|
});
|
||||||
}, [target]);
|
}, [target]);
|
||||||
|
|
||||||
async function reload() {
|
async function reload() {
|
||||||
fetchData();
|
await fetchData();
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData();
|
fetchData().catch((error) => { setError(error); });
|
||||||
}, [fetchData])
|
}, [fetchData])
|
||||||
|
|
||||||
return { schema, setSchema, reload, error, setError, loading };
|
return { schema, setSchema, reload, error, setError, loading };
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { IRSForm } from '../utils/models'
|
|
||||||
import { ErrorInfo } from '../components/BackendError';
|
import { type ErrorInfo } from '../components/BackendError';
|
||||||
import { getRSForms } from '../utils/backendAPI';
|
import { getRSForms } from '../utils/backendAPI';
|
||||||
|
import { type IRSForm } from '../utils/models'
|
||||||
|
|
||||||
export enum FilterType {
|
export enum FilterType {
|
||||||
PERSONAL = 'personal',
|
PERSONAL = 'personal',
|
||||||
|
@ -19,13 +20,13 @@ export function useRSForms() {
|
||||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||||
|
|
||||||
const loadList = useCallback(async (filter: RSFormsFilter) => {
|
const loadList = useCallback(async (filter: RSFormsFilter) => {
|
||||||
getRSForms(filter, {
|
await getRSForms(filter, {
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setLoading,
|
setLoading,
|
||||||
onError: error => setError(error),
|
onError: error => { setError(error); },
|
||||||
onSucccess: response => setRSForms(response.data)
|
onSucccess: response => { setRSForms(response.data); }
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return { rsforms, error, loading, loadList };
|
return { rsforms, error, loading, loadList };
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,30 @@
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { IUserProfile } from '../utils/models'
|
|
||||||
import { ErrorInfo } from '../components/BackendError'
|
import { type ErrorInfo } from '../components/BackendError'
|
||||||
import { getProfile } from '../utils/backendAPI'
|
import { getProfile } from '../utils/backendAPI'
|
||||||
|
import { type IUserProfile } from '../utils/models'
|
||||||
|
|
||||||
export function useUserProfile() {
|
export function useUserProfile() {
|
||||||
const [user, setUser] = useState<IUserProfile | undefined>(undefined);
|
const [user, setUser] = useState<IUserProfile | undefined>(undefined);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||||
|
|
||||||
const fetchUser = useCallback(
|
const fetchUser = useCallback(
|
||||||
async () => {
|
async () => {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
setUser(undefined);
|
setUser(undefined);
|
||||||
getProfile({
|
await getProfile({
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setLoading,
|
setLoading,
|
||||||
onError: error => setError(error),
|
onError: error => { setError(error); },
|
||||||
onSucccess: response => setUser(response.data)
|
onSucccess: response => { setUser(response.data); }
|
||||||
});
|
});
|
||||||
}, [setUser]
|
}, [setUser]
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchUser();
|
fetchUser().catch((error) => { setError(error); });
|
||||||
}, [fetchUser])
|
}, [fetchUser])
|
||||||
|
|
||||||
return { user, fetchUser, error, loading };
|
return { user, fetchUser, error, loading };
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import axios from 'axios';
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import 'react-toastify/dist/ReactToastify.css';
|
import 'react-toastify/dist/ReactToastify.css';
|
||||||
|
|
||||||
|
import axios from 'axios';
|
||||||
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
import ErrorFallback from './components/ErrorFallback';
|
||||||
import { AuthState } from './context/AuthContext';
|
import { AuthState } from './context/AuthContext';
|
||||||
import { ThemeState } from './context/ThemeContext';
|
import { ThemeState } from './context/ThemeContext';
|
||||||
import { IntlProvider } from 'react-intl';
|
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
|
||||||
import ErrorFallback from './components/ErrorFallback';
|
|
||||||
import { UsersState } from './context/UsersContext';
|
import { UsersState } from './context/UsersContext';
|
||||||
|
|
||||||
axios.defaults.withCredentials = true
|
axios.defaults.withCredentials = true
|
||||||
|
@ -28,13 +30,13 @@ const resetState = () => {
|
||||||
|
|
||||||
const logError = (error: Error, info: { componentStack: string }) => {
|
const logError = (error: Error, info: { componentStack: string }) => {
|
||||||
console.log('Error fallback: ' + error.message)
|
console.log('Error fallback: ' + error.message)
|
||||||
console.log('Component stack: ' + info)
|
console.log('Component stack: ' + info.componentStack)
|
||||||
};
|
};
|
||||||
|
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
FallbackComponent={ErrorFallback}
|
FallbackComponent={ErrorFallback}
|
||||||
onReset={resetState}
|
onReset={resetState}
|
||||||
onError={logError}
|
onError={logError}
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { useAuth } from '../context/AuthContext';
|
import { useAuth } from '../context/AuthContext';
|
||||||
|
|
||||||
function HomePage() {
|
function HomePage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const {user} = useAuth();
|
const { user } = useAuth();
|
||||||
if (user) {
|
if (user) {
|
||||||
navigate('/rsforms?filter=personal');
|
navigate('/rsforms?filter=personal');
|
||||||
} else {
|
} else {
|
||||||
navigate('/rsforms?filter=common');
|
navigate('/rsforms?filter=common');
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col items-center justify-center w-full py-2'>
|
<div className='flex flex-col items-center justify-center w-full py-2'>
|
||||||
<p>Home page</p>
|
<p>Home page</p>
|
||||||
|
@ -17,4 +18,4 @@ function HomePage() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default HomePage;
|
export default HomePage;
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import TextInput from '../components/Common/TextInput';
|
|
||||||
import Form from '../components/Common/Form';
|
|
||||||
import { useAuth } from '../context/AuthContext';
|
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import SubmitButton from '../components/Common/SubmitButton';
|
|
||||||
import BackendError from '../components/BackendError';
|
import BackendError from '../components/BackendError';
|
||||||
import InfoMessage from '../components/InfoMessage';
|
import Form from '../components/Common/Form';
|
||||||
|
import SubmitButton from '../components/Common/SubmitButton';
|
||||||
|
import TextInput from '../components/Common/TextInput';
|
||||||
import TextURL from '../components/Common/TextURL';
|
import TextURL from '../components/Common/TextURL';
|
||||||
|
import InfoMessage from '../components/InfoMessage';
|
||||||
|
import { useAuth } from '../context/AuthContext';
|
||||||
|
|
||||||
function LoginPage() {
|
function LoginPage() {
|
||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const { user, login, loading, error, setError } = useAuth()
|
const { user, login, loading, error, setError } = useAuth()
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const search = useLocation().search;
|
const search = useLocation().search;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const name = new URLSearchParams(search).get('username');
|
const name = new URLSearchParams(search).get('username');
|
||||||
setUsername(name || '');
|
setUsername(name ?? '');
|
||||||
setPassword('');
|
setPassword('');
|
||||||
}, [search]);
|
}, [search]);
|
||||||
|
|
||||||
|
@ -30,29 +30,28 @@ function LoginPage() {
|
||||||
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!loading) {
|
if (!loading) {
|
||||||
login(username, password, () => navigate('/rsforms?filter=personal'));
|
login(username, password, () => { navigate('/rsforms?filter=personal'); });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full py-2'> { user ?
|
<div className='w-full py-2'> { user
|
||||||
<InfoMessage message={`Вы вошли в систему как ${user.username}`} />
|
? <InfoMessage message={`Вы вошли в систему как ${user.username}`} />
|
||||||
:
|
: <Form title='Ввод данных пользователя' onSubmit={handleSubmit} widthClass='w-[20rem]'>
|
||||||
<Form title='Ввод данных пользователя' onSubmit={handleSubmit} widthClass='w-[20rem]'>
|
|
||||||
<TextInput id='username'
|
<TextInput id='username'
|
||||||
label='Имя пользователя'
|
label='Имя пользователя'
|
||||||
required
|
required
|
||||||
type='text'
|
type='text'
|
||||||
value={username}
|
value={username}
|
||||||
autoFocus
|
autoFocus
|
||||||
onChange={event => setUsername(event.target.value)}
|
onChange={event => { setUsername(event.target.value); }}
|
||||||
/>
|
/>
|
||||||
<TextInput id='password'
|
<TextInput id='password'
|
||||||
label='Пароль'
|
label='Пароль'
|
||||||
required
|
required
|
||||||
type='password'
|
type='password'
|
||||||
value={password}
|
value={password}
|
||||||
onChange={event => setPassword(event.target.value)}
|
onChange={event => { setPassword(event.target.value); }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='flex items-center justify-between mt-4'>
|
<div className='flex items-center justify-between mt-4'>
|
||||||
|
@ -68,4 +67,4 @@ function LoginPage() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LoginPage;
|
export default LoginPage;
|
||||||
|
|
|
@ -20,4 +20,4 @@ function ManualsPage() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ManualsPage;
|
export default ManualsPage;
|
||||||
|
|
|
@ -7,4 +7,4 @@ export function NotFoundPage() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NotFoundPage;
|
export default NotFoundPage;
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import TextInput from '../components/Common/TextInput';
|
|
||||||
import Form from '../components/Common/Form';
|
|
||||||
import SubmitButton from '../components/Common/SubmitButton';
|
|
||||||
import BackendError from '../components/BackendError';
|
|
||||||
import { IRSFormCreateData } from '../utils/models';
|
|
||||||
import RequireAuth from '../components/RequireAuth';
|
|
||||||
import useNewRSForm from '../hooks/useNewRSForm';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import TextArea from '../components/Common/TextArea';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import BackendError from '../components/BackendError';
|
||||||
import Checkbox from '../components/Common/Checkbox';
|
import Checkbox from '../components/Common/Checkbox';
|
||||||
import FileInput from '../components/Common/FileInput';
|
import FileInput from '../components/Common/FileInput';
|
||||||
import { toast } from 'react-toastify';
|
import Form from '../components/Common/Form';
|
||||||
|
import SubmitButton from '../components/Common/SubmitButton';
|
||||||
|
import TextArea from '../components/Common/TextArea';
|
||||||
|
import TextInput from '../components/Common/TextInput';
|
||||||
|
import RequireAuth from '../components/RequireAuth';
|
||||||
|
import useNewRSForm from '../hooks/useNewRSForm';
|
||||||
|
import { type IRSFormCreateData } from '../utils/models';
|
||||||
|
|
||||||
function RSFormCreatePage() {
|
function RSFormCreatePage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
@ -29,7 +29,7 @@ function RSFormCreatePage() {
|
||||||
setFile(undefined)
|
setFile(undefined)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSuccess = (newID: string) => {
|
const onSuccess = (newID: string) => {
|
||||||
toast.success('Схема успешно создана');
|
toast.success('Схема успешно создана');
|
||||||
navigate(`/rsforms/${newID}`);
|
navigate(`/rsforms/${newID}`);
|
||||||
|
@ -39,54 +39,55 @@ function RSFormCreatePage() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setError(undefined)
|
setError(undefined)
|
||||||
}, [title, alias, setError]);
|
}, [title, alias, setError]);
|
||||||
|
|
||||||
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!loading) {
|
if (loading) {
|
||||||
const data: IRSFormCreateData = {
|
return;
|
||||||
'title': title,
|
|
||||||
'alias': alias,
|
|
||||||
'comment': comment,
|
|
||||||
'is_common': common,
|
|
||||||
};
|
|
||||||
createSchema({
|
|
||||||
data: data,
|
|
||||||
file: file,
|
|
||||||
onSuccess: onSuccess
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
const data: IRSFormCreateData = {
|
||||||
|
title,
|
||||||
|
alias,
|
||||||
|
comment,
|
||||||
|
is_common: common
|
||||||
|
};
|
||||||
|
void createSchema({
|
||||||
|
data,
|
||||||
|
file,
|
||||||
|
onSuccess
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RequireAuth>
|
<RequireAuth>
|
||||||
<Form title='Создание концептуальной схемы' onSubmit={handleSubmit} widthClass='max-w-lg mt-4'>
|
<Form title='Создание концептуальной схемы' onSubmit={handleSubmit} widthClass='max-w-lg mt-4'>
|
||||||
<TextInput id='title' label='Полное название' type='text'
|
<TextInput id='title' label='Полное название' type='text'
|
||||||
required={!file}
|
required={!file}
|
||||||
placeholder={file && 'Загрузить из файла'}
|
placeholder={file && 'Загрузить из файла'}
|
||||||
value={title}
|
value={title}
|
||||||
onChange={event => setTitle(event.target.value)}
|
onChange={event => { setTitle(event.target.value); }}
|
||||||
/>
|
/>
|
||||||
<TextInput id='alias' label='Сокращение' type='text'
|
<TextInput id='alias' label='Сокращение' type='text'
|
||||||
required={!file}
|
required={!file}
|
||||||
value={alias}
|
value={alias}
|
||||||
placeholder={file && 'Загрузить из файла'}
|
placeholder={file && 'Загрузить из файла'}
|
||||||
widthClass='max-w-sm'
|
widthClass='max-w-sm'
|
||||||
onChange={event => setAlias(event.target.value)}
|
onChange={event => { setAlias(event.target.value); }}
|
||||||
/>
|
/>
|
||||||
<TextArea id='comment' label='Комментарий'
|
<TextArea id='comment' label='Комментарий'
|
||||||
value={comment}
|
value={comment}
|
||||||
placeholder={file && 'Загрузить из файла'}
|
placeholder={file && 'Загрузить из файла'}
|
||||||
onChange={event => setComment(event.target.value)}
|
onChange={event => { setComment(event.target.value); }}
|
||||||
/>
|
/>
|
||||||
<Checkbox id='common' label='Общедоступная схема'
|
<Checkbox id='common' label='Общедоступная схема'
|
||||||
value={common}
|
value={common}
|
||||||
onChange={event => setCommon(event.target.checked)}
|
onChange={event => { setCommon(event.target.checked); }}
|
||||||
/>
|
/>
|
||||||
<FileInput id='trs' label='Загрузить *.trs'
|
<FileInput id='trs' label='Загрузить *.trs'
|
||||||
acceptType='.trs'
|
acceptType='.trs'
|
||||||
onChange={handleFile}
|
onChange={handleFile}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='flex items-center justify-center py-2 mt-4'>
|
<div className='flex items-center justify-center py-2 mt-4'>
|
||||||
<SubmitButton text='Создать схему' loading={loading} />
|
<SubmitButton text='Создать схему' loading={loading} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -96,4 +97,4 @@ function RSFormCreatePage() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RSFormCreatePage;
|
export default RSFormCreatePage;
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
|
import { type AxiosResponse } from 'axios';
|
||||||
import { useCallback, useLayoutEffect, useState } from 'react';
|
import { useCallback, useLayoutEffect, useState } from 'react';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
|
||||||
import { CstType, EditMode, INewCstData } from '../../utils/models';
|
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import TextArea from '../../components/Common/TextArea';
|
|
||||||
import ExpressionEditor from './ExpressionEditor';
|
|
||||||
import SubmitButton from '../../components/Common/SubmitButton';
|
import SubmitButton from '../../components/Common/SubmitButton';
|
||||||
|
import TextArea from '../../components/Common/TextArea';
|
||||||
|
import { DumpBinIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
|
||||||
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
|
import { type CstType, EditMode, type INewCstData } from '../../utils/models';
|
||||||
import { createAliasFor, getCstTypeLabel } from '../../utils/staticUI';
|
import { createAliasFor, getCstTypeLabel } from '../../utils/staticUI';
|
||||||
import ConstituentsSideList from './ConstituentsSideList';
|
import ConstituentsSideList from './ConstituentsSideList';
|
||||||
import { DumpBinIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
|
|
||||||
import CreateCstModal from './CreateCstModal';
|
import CreateCstModal from './CreateCstModal';
|
||||||
import { AxiosResponse } from 'axios';
|
import ExpressionEditor from './ExpressionEditor';
|
||||||
|
|
||||||
function ConstituentEditor() {
|
function ConstituentEditor() {
|
||||||
const {
|
const {
|
||||||
activeCst, activeID, schema, setActiveID, processing, isEditable,
|
activeCst, activeID, schema, setActiveID, processing, isEditable,
|
||||||
cstDelete, cstUpdate, cstCreate
|
cstDelete, cstUpdate, cstCreate
|
||||||
} = useRSForm();
|
} = useRSForm();
|
||||||
|
@ -30,7 +31,9 @@ function ConstituentEditor() {
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (schema?.items && schema?.items.length > 0) {
|
if (schema?.items && schema?.items.length > 0) {
|
||||||
setActiveID((prev) => (prev || schema?.items![0].id));
|
// TODO: figure out why schema.items could be undef?
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
setActiveID((prev) => (prev ?? schema?.items![0].id ?? undefined));
|
||||||
}
|
}
|
||||||
}, [schema, setActiveID])
|
}, [schema, setActiveID])
|
||||||
|
|
||||||
|
@ -38,43 +41,43 @@ function ConstituentEditor() {
|
||||||
if (activeCst) {
|
if (activeCst) {
|
||||||
setAlias(activeCst.alias);
|
setAlias(activeCst.alias);
|
||||||
setType(getCstTypeLabel(activeCst.cstType));
|
setType(getCstTypeLabel(activeCst.cstType));
|
||||||
setConvention(activeCst.convention || '');
|
setConvention(activeCst.convention ?? '');
|
||||||
setTerm(activeCst.term?.raw || '');
|
setTerm(activeCst.term?.raw ?? '');
|
||||||
setTextDefinition(activeCst.definition?.text?.raw || '');
|
setTextDefinition(activeCst.definition?.text?.raw ?? '');
|
||||||
setExpression(activeCst.definition?.formal || '');
|
setExpression(activeCst.definition?.formal ?? '');
|
||||||
setTypification(activeCst?.parse?.typification || 'N/A');
|
setTypification(activeCst?.parse?.typification ?? 'N/A');
|
||||||
}
|
}
|
||||||
}, [activeCst]);
|
}, [activeCst]);
|
||||||
|
|
||||||
const handleSubmit =
|
const handleSubmit =
|
||||||
async (event: React.FormEvent<HTMLFormElement>) => {
|
(event: React.FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!processing) {
|
if (!processing) {
|
||||||
const data = {
|
const data = {
|
||||||
'alias': alias,
|
alias: alias,
|
||||||
'convention': convention,
|
convention: convention,
|
||||||
'definition_formal': expression,
|
definition_formal: expression,
|
||||||
'definition_text': {
|
definition_text: {
|
||||||
'raw': textDefinition,
|
raw: textDefinition,
|
||||||
'resolved': '',
|
resolved: ''
|
||||||
},
|
},
|
||||||
'term': {
|
term: {
|
||||||
'raw': term,
|
raw: term,
|
||||||
'resolved': '',
|
resolved: '',
|
||||||
'forms': activeCst?.term?.forms || [],
|
forms: activeCst?.term?.forms ?? []
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cstUpdate(data, () => toast.success('Изменения сохранены'));
|
cstUpdate(data, () => toast.success('Изменения сохранены'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = useCallback(
|
const handleDelete = useCallback(
|
||||||
async () => {
|
() => {
|
||||||
if (!activeID || !schema?.items || !window.confirm('Вы уверены, что хотите удалить конституенту?')) {
|
if (!activeID || !schema?.items || !window.confirm('Вы уверены, что хотите удалить конституенту?')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = {
|
const data = {
|
||||||
'items': [{'id': activeID}]
|
items: [{ id: activeID }]
|
||||||
}
|
}
|
||||||
const index = schema.items.findIndex((cst) => cst.id === activeID);
|
const index = schema.items.findIndex((cst) => cst.id === activeID);
|
||||||
if (index !== -1 && index + 1 < schema.items.length) {
|
if (index !== -1 && index + 1 < schema.items.length) {
|
||||||
|
@ -84,24 +87,23 @@ function ConstituentEditor() {
|
||||||
}, [activeID, schema, setActiveID, cstDelete]);
|
}, [activeID, schema, setActiveID, cstDelete]);
|
||||||
|
|
||||||
const handleAddNew = useCallback(
|
const handleAddNew = useCallback(
|
||||||
async (csttype?: CstType) => {
|
(csttype?: CstType) => {
|
||||||
if (!activeID || !schema?.items) {
|
if (!activeID || !schema?.items) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!csttype) {
|
if (!csttype) {
|
||||||
setShowCstModal(true);
|
setShowCstModal(true);
|
||||||
} else {
|
} else {
|
||||||
const data: INewCstData = {
|
const data: INewCstData = {
|
||||||
'csttype': csttype,
|
csttype: csttype,
|
||||||
'alias': createAliasFor(csttype, schema!),
|
alias: createAliasFor(csttype, schema),
|
||||||
'insert_after': activeID
|
insert_after: activeID
|
||||||
}
|
}
|
||||||
cstCreate(data,
|
cstCreate(data,
|
||||||
async (response: AxiosResponse) => {
|
(response: AxiosResponse) => {
|
||||||
// navigate(`/rsforms/${schema.id}?tab=${RSFormTabsList.CST_EDIT}&active=${response.data['new_cst']['id']}`);
|
setActiveID(response.data.new_cst.id);
|
||||||
setActiveID(response.data['new_cst']['id']);
|
toast.success(`Конституента добавлена: ${response.data.new_cst.alias as string}`);
|
||||||
toast.success(`Конституента добавлена: ${response.data['new_cst']['alias']}`);
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [activeID, schema, cstCreate, setActiveID]);
|
}, [activeID, schema, cstCreate, setActiveID]);
|
||||||
|
|
||||||
|
@ -113,12 +115,11 @@ function ConstituentEditor() {
|
||||||
toast.info('Изменение типа в разработке');
|
toast.info('Изменение типа в разработке');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex items-start w-full gap-2'>
|
<div className='flex items-start w-full gap-2'>
|
||||||
<CreateCstModal
|
<CreateCstModal
|
||||||
show={showCstModal}
|
show={showCstModal}
|
||||||
toggle={() => setShowCstModal(!showCstModal)}
|
toggle={() => { setShowCstModal(!showCstModal); }}
|
||||||
onCreate={handleAddNew}
|
onCreate={handleAddNew}
|
||||||
defaultType={activeCst?.cstType as CstType}
|
defaultType={activeCst?.cstType as CstType}
|
||||||
/>
|
/>
|
||||||
|
@ -133,8 +134,8 @@ function ConstituentEditor() {
|
||||||
</button>
|
</button>
|
||||||
<div className='flex items-start justify-center w-full gap-4'>
|
<div className='flex items-start justify-center w-full gap-4'>
|
||||||
<span className='mr-12'>
|
<span className='mr-12'>
|
||||||
<label
|
<label
|
||||||
title='Переименовать конституенту'
|
title='Переименовать конституенту'
|
||||||
className='font-semibold underline cursor-pointer'
|
className='font-semibold underline cursor-pointer'
|
||||||
onClick={handleRename}
|
onClick={handleRename}
|
||||||
>
|
>
|
||||||
|
@ -143,7 +144,7 @@ function ConstituentEditor() {
|
||||||
<b className='ml-2'>{alias}</b>
|
<b className='ml-2'>{alias}</b>
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<label
|
<label
|
||||||
title='Изменить тип конституенты'
|
title='Изменить тип конституенты'
|
||||||
className='font-semibold underline cursor-pointer'
|
className='font-semibold underline cursor-pointer'
|
||||||
onClick={handleChangeType}
|
onClick={handleChangeType}
|
||||||
|
@ -158,9 +159,9 @@ function ConstituentEditor() {
|
||||||
title='Создать конституенты после данной'
|
title='Создать конституенты после данной'
|
||||||
className='px-1 py-1 font-bold rounded-full whitespace-nowrap disabled:cursor-not-allowed clr-btn-clear'
|
className='px-1 py-1 font-bold rounded-full whitespace-nowrap disabled:cursor-not-allowed clr-btn-clear'
|
||||||
disabled={!isEditable}
|
disabled={!isEditable}
|
||||||
onClick={() => handleAddNew()}
|
onClick={() => { handleAddNew(); }}
|
||||||
>
|
>
|
||||||
<SmallPlusIcon size={5} color={isEditable ? 'text-green': ''} />
|
<SmallPlusIcon size={5} color={isEditable ? 'text-green' : ''} />
|
||||||
</button>
|
</button>
|
||||||
<button type='button'
|
<button type='button'
|
||||||
title='Удалить редактируемую конституенту'
|
title='Удалить редактируемую конституенту'
|
||||||
|
@ -168,7 +169,7 @@ function ConstituentEditor() {
|
||||||
disabled={!isEditable}
|
disabled={!isEditable}
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
>
|
>
|
||||||
<DumpBinIcon size={5} color={isEditable ? 'text-red': ''} />
|
<DumpBinIcon size={5} color={isEditable ? 'text-red' : ''} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -178,8 +179,8 @@ function ConstituentEditor() {
|
||||||
value={term}
|
value={term}
|
||||||
disabled={!isEditable}
|
disabled={!isEditable}
|
||||||
spellCheck
|
spellCheck
|
||||||
onChange={event => setTerm(event.target.value)}
|
onChange={event => { setTerm(event.target.value); }}
|
||||||
onFocus={() => setEditMode(EditMode.TEXT)}
|
onFocus={() => { setEditMode(EditMode.TEXT); }}
|
||||||
/>
|
/>
|
||||||
<TextArea id='typification' label='Типизация'
|
<TextArea id='typification' label='Типизация'
|
||||||
rows={1}
|
rows={1}
|
||||||
|
@ -190,9 +191,9 @@ function ConstituentEditor() {
|
||||||
placeholder='Родоструктурное выражение, задающее формальное определение'
|
placeholder='Родоструктурное выражение, задающее формальное определение'
|
||||||
value={expression}
|
value={expression}
|
||||||
disabled={!isEditable}
|
disabled={!isEditable}
|
||||||
isActive={editMode==='rslang'}
|
isActive={editMode === 'rslang'}
|
||||||
toggleEditMode={() => setEditMode(EditMode.RSLANG)}
|
toggleEditMode={() => { setEditMode(EditMode.RSLANG); }}
|
||||||
onChange={event => setExpression(event.target.value)}
|
onChange={event => { setExpression(event.target.value); }}
|
||||||
setValue={setExpression}
|
setValue={setExpression}
|
||||||
setTypification={setTypification}
|
setTypification={setTypification}
|
||||||
/>
|
/>
|
||||||
|
@ -202,8 +203,8 @@ function ConstituentEditor() {
|
||||||
value={textDefinition}
|
value={textDefinition}
|
||||||
disabled={!isEditable}
|
disabled={!isEditable}
|
||||||
spellCheck
|
spellCheck
|
||||||
onChange={event => setTextDefinition(event.target.value)}
|
onChange={event => { setTextDefinition(event.target.value); }}
|
||||||
onFocus={() => setEditMode(EditMode.TEXT)}
|
onFocus={() => { setEditMode(EditMode.TEXT); }}
|
||||||
/>
|
/>
|
||||||
<TextArea id='convention' label='Конвенция / Комментарий'
|
<TextArea id='convention' label='Конвенция / Комментарий'
|
||||||
placeholder='Договоренность об интерпретации неопределяемого понятия
Комментарий к производному понятию'
|
placeholder='Договоренность об интерпретации неопределяемого понятия
Комментарий к производному понятию'
|
||||||
|
@ -211,8 +212,8 @@ function ConstituentEditor() {
|
||||||
value={convention}
|
value={convention}
|
||||||
disabled={!isEditable}
|
disabled={!isEditable}
|
||||||
spellCheck
|
spellCheck
|
||||||
onChange={event => setConvention(event.target.value)}
|
onChange={event => { setConvention(event.target.value); }}
|
||||||
onFocus={() => setEditMode(EditMode.TEXT)}
|
onFocus={() => { setEditMode(EditMode.TEXT); }}
|
||||||
/>
|
/>
|
||||||
<div className='flex justify-center w-full mt-2'>
|
<div className='flex justify-center w-full mt-2'>
|
||||||
<SubmitButton
|
<SubmitButton
|
||||||
|
@ -227,4 +228,4 @@ function ConstituentEditor() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ConstituentEditor;
|
export default ConstituentEditor;
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
import { useCallback, useState, useMemo, useEffect } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
|
||||||
import { CstType, IConstituenta, matchConstituenta } from '../../utils/models';
|
|
||||||
import Checkbox from '../../components/Common/Checkbox';
|
import Checkbox from '../../components/Common/Checkbox';
|
||||||
import DataTableThemed from '../../components/Common/DataTableThemed';
|
import DataTableThemed from '../../components/Common/DataTableThemed';
|
||||||
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import useLocalStorage from '../../hooks/useLocalStorage';
|
import useLocalStorage from '../../hooks/useLocalStorage';
|
||||||
|
import { CstType, type IConstituenta, matchConstituenta } from '../../utils/models';
|
||||||
import { extractGlobals } from '../../utils/staticUI';
|
import { extractGlobals } from '../../utils/staticUI';
|
||||||
|
|
||||||
interface ConstituentsSideListProps {
|
interface ConstituentsSideListProps {
|
||||||
expression: string
|
expression: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function ConstituentsSideList({expression}: ConstituentsSideListProps) {
|
function ConstituentsSideList({ expression }: ConstituentsSideListProps) {
|
||||||
const { schema, setActiveID } = useRSForm();
|
const { schema, setActiveID } = useRSForm();
|
||||||
const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items || []);
|
const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items ?? []);
|
||||||
const [filterText, setFilterText] = useLocalStorage('side-filter-text', '')
|
const [filterText, setFilterText] = useLocalStorage('side-filter-text', '')
|
||||||
const [onlyExpression, setOnlyExpression] = useLocalStorage('side-filter-flag', false);
|
const [onlyExpression, setOnlyExpression] = useLocalStorage('side-filter-flag', false);
|
||||||
|
|
||||||
|
@ -21,9 +22,9 @@ function ConstituentsSideList({expression}: ConstituentsSideListProps) {
|
||||||
setFilteredData([]);
|
setFilteredData([]);
|
||||||
} else if (onlyExpression) {
|
} else if (onlyExpression) {
|
||||||
const aliases = extractGlobals(expression);
|
const aliases = extractGlobals(expression);
|
||||||
let filtered = schema?.items.filter((cst) => aliases.has(cst.alias));
|
const filtered = schema?.items.filter((cst) => aliases.has(cst.alias));
|
||||||
const names = filtered.map(cst => cst.alias)
|
const names = filtered.map(cst => cst.alias)
|
||||||
const diff = Array.from(aliases).filter(name => names.indexOf(name) < 0);
|
const diff = Array.from(aliases).filter(name => !names.includes(name));
|
||||||
if (diff.length > 0) {
|
if (diff.length > 0) {
|
||||||
diff.forEach(
|
diff.forEach(
|
||||||
(alias, i) => filtered.push({
|
(alias, i) => filtered.push({
|
||||||
|
@ -42,23 +43,23 @@ function ConstituentsSideList({expression}: ConstituentsSideListProps) {
|
||||||
}, [filterText, setFilteredData, onlyExpression, expression, schema]);
|
}, [filterText, setFilteredData, onlyExpression, expression, schema]);
|
||||||
|
|
||||||
const handleRowClicked = useCallback(
|
const handleRowClicked = useCallback(
|
||||||
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
||||||
if (event.altKey && cst.id > 0) {
|
if (event.altKey && cst.id > 0) {
|
||||||
setActiveID(cst.id);
|
setActiveID(cst.id);
|
||||||
}
|
}
|
||||||
}, [setActiveID]);
|
}, [setActiveID]);
|
||||||
|
|
||||||
const handleDoubleClick = useCallback(
|
const handleDoubleClick = useCallback(
|
||||||
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
||||||
if (cst.id > 0) setActiveID(cst.id);
|
if (cst.id > 0) setActiveID(cst.id);
|
||||||
}, [setActiveID]);
|
}, [setActiveID]);
|
||||||
|
|
||||||
const columns = useMemo(() =>
|
const columns = useMemo(() =>
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
id: 'id',
|
id: 'id',
|
||||||
selector: (cst: IConstituenta) => cst.id,
|
selector: (cst: IConstituenta) => cst.id,
|
||||||
omit: true,
|
omit: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ID',
|
name: 'ID',
|
||||||
|
@ -70,26 +71,27 @@ function ConstituentsSideList({expression}: ConstituentsSideListProps) {
|
||||||
{
|
{
|
||||||
when: (cst: IConstituenta) => cst.id <= 0,
|
when: (cst: IConstituenta) => cst.id <= 0,
|
||||||
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
|
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Описание',
|
name: 'Описание',
|
||||||
id: 'description',
|
id: 'description',
|
||||||
selector: (cst: IConstituenta) => cst.term?.resolved || cst.definition?.text.resolved || cst.definition?.formal || cst.convention || '',
|
selector: (cst: IConstituenta) =>
|
||||||
|
cst.term?.resolved ?? cst.definition?.text.resolved ?? cst.definition?.formal ?? cst.convention ?? '',
|
||||||
minWidth: '350px',
|
minWidth: '350px',
|
||||||
wrap: true,
|
wrap: true,
|
||||||
conditionalCellStyles: [
|
conditionalCellStyles: [
|
||||||
{
|
{
|
||||||
when: (cst: IConstituenta) => cst.id <= 0,
|
when: (cst: IConstituenta) => cst.id <= 0,
|
||||||
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
|
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Выражение',
|
name: 'Выражение',
|
||||||
id: 'expression',
|
id: 'expression',
|
||||||
selector: (cst: IConstituenta) => cst.definition?.formal || '',
|
selector: (cst: IConstituenta) => cst.definition?.formal ?? '',
|
||||||
minWidth: '200px',
|
minWidth: '200px',
|
||||||
hide: 1600,
|
hide: 1600,
|
||||||
grow: 2,
|
grow: 2,
|
||||||
|
@ -98,8 +100,8 @@ function ConstituentsSideList({expression}: ConstituentsSideListProps) {
|
||||||
{
|
{
|
||||||
when: (cst: IConstituenta) => cst.id <= 0,
|
when: (cst: IConstituenta) => cst.id <= 0,
|
||||||
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
|
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
}
|
}
|
||||||
], []
|
], []
|
||||||
);
|
);
|
||||||
|
@ -112,7 +114,7 @@ function ConstituentsSideList({expression}: ConstituentsSideListProps) {
|
||||||
className='w-full px-2 outline-none dark:bg-gray-700 hover:text-clip'
|
className='w-full px-2 outline-none dark:bg-gray-700 hover:text-clip'
|
||||||
placeholder='текст для фильтрации списка'
|
placeholder='текст для фильтрации списка'
|
||||||
value={filterText}
|
value={filterText}
|
||||||
onChange={event => setFilterText(event.target.value)}
|
onChange={event => { setFilterText(event.target.value); }}
|
||||||
disabled={onlyExpression}
|
disabled={onlyExpression}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -120,7 +122,7 @@ function ConstituentsSideList({expression}: ConstituentsSideListProps) {
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label='из выражения'
|
label='из выражения'
|
||||||
value={onlyExpression}
|
value={onlyExpression}
|
||||||
onChange={event => setOnlyExpression(event.target.checked)}
|
onChange={event => { setOnlyExpression(event.target.checked); }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,22 +1,23 @@
|
||||||
import { CstType, IConstituenta, INewCstData, ParsingStatus, ValueClass, inferStatus } from '../../utils/models'
|
import { type AxiosResponse } from 'axios';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import DataTableThemed from '../../components/Common/DataTableThemed';
|
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
|
||||||
import Button from '../../components/Common/Button';
|
|
||||||
import { ArrowDownIcon, ArrowUpIcon, ArrowsRotateIcon, DumpBinIcon, SmallPlusIcon } from '../../components/Icons';
|
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import Button from '../../components/Common/Button';
|
||||||
|
import DataTableThemed from '../../components/Common/DataTableThemed';
|
||||||
import Divider from '../../components/Common/Divider';
|
import Divider from '../../components/Common/Divider';
|
||||||
|
import { ArrowDownIcon, ArrowsRotateIcon, ArrowUpIcon, DumpBinIcon, SmallPlusIcon } from '../../components/Icons';
|
||||||
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
|
import { CstType, type IConstituenta, type INewCstData, inferStatus, ParsingStatus, ValueClass } from '../../utils/models'
|
||||||
import { createAliasFor, getCstTypeLabel, getCstTypePrefix, getStatusInfo, getTypeLabel } from '../../utils/staticUI';
|
import { createAliasFor, getCstTypeLabel, getCstTypePrefix, getStatusInfo, getTypeLabel } from '../../utils/staticUI';
|
||||||
import CreateCstModal from './CreateCstModal';
|
import CreateCstModal from './CreateCstModal';
|
||||||
import { AxiosResponse } from 'axios';
|
|
||||||
import { useConceptTheme } from '../../context/ThemeContext';
|
|
||||||
|
|
||||||
interface ConstituentsTableProps {
|
interface ConstituentsTableProps {
|
||||||
onOpenEdit: (cst: IConstituenta) => void
|
onOpenEdit: (cst: IConstituenta) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
function ConstituentsTable({ onOpenEdit }: ConstituentsTableProps) {
|
||||||
const {
|
const {
|
||||||
schema, isEditable,
|
schema, isEditable,
|
||||||
cstCreate, cstDelete, cstMoveTo
|
cstCreate, cstDelete, cstMoveTo
|
||||||
} = useRSForm();
|
} = useRSForm();
|
||||||
|
@ -27,31 +28,31 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
||||||
const [showCstModal, setShowCstModal] = useState(false);
|
const [showCstModal, setShowCstModal] = useState(false);
|
||||||
|
|
||||||
const handleRowClicked = useCallback(
|
const handleRowClicked = useCallback(
|
||||||
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
||||||
if (event.altKey) {
|
if (event.altKey) {
|
||||||
onOpenEdit(cst);
|
onOpenEdit(cst);
|
||||||
}
|
}
|
||||||
}, [onOpenEdit]);
|
}, [onOpenEdit]);
|
||||||
|
|
||||||
const handleSelectionChange = useCallback(
|
const handleSelectionChange = useCallback(
|
||||||
({selectedRows}: {
|
({ selectedRows }: {
|
||||||
allSelected: boolean;
|
allSelected: boolean
|
||||||
selectedCount: number;
|
selectedCount: number
|
||||||
selectedRows: IConstituenta[];
|
selectedRows: IConstituenta[]
|
||||||
}) => {
|
}) => {
|
||||||
setSelected(selectedRows.map((cst) => cst.id));
|
setSelected(selectedRows.map((cst) => cst.id));
|
||||||
}, [setSelected]);
|
}, [setSelected]);
|
||||||
|
|
||||||
// Delete selected constituents
|
// Delete selected constituents
|
||||||
const handleDelete = useCallback(() => {
|
const handleDelete = useCallback(() => {
|
||||||
if (!schema?.items || !window.confirm('Вы уверены, что хотите удалить выбранные конституенты?')) {
|
if (!schema?.items || !window.confirm('Вы уверены, что хотите удалить выбранные конституенты?')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = {
|
const data = {
|
||||||
'items': selected.map(id => { return {'id': id }; }),
|
items: selected.map(id => { return { id }; })
|
||||||
}
|
}
|
||||||
const deletedNamed = selected.map(id => schema.items?.find((cst) => cst.id === id)?.alias);
|
const deletedNames = selected.map(id => schema.items?.find((cst) => cst.id === id)?.alias);
|
||||||
cstDelete(data, () => toast.success(`Конституенты удалены: ${deletedNamed}`));
|
cstDelete(data, () => toast.success(`Конституенты удалены: ${deletedNames.toString()}`));
|
||||||
}, [selected, schema?.items, cstDelete]);
|
}, [selected, schema?.items, cstDelete]);
|
||||||
|
|
||||||
// Move selected cst up
|
// Move selected cst up
|
||||||
|
@ -61,7 +62,7 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const currentIndex = schema.items.reduce((prev, cst, index) => {
|
const currentIndex = schema.items.reduce((prev, cst, index) => {
|
||||||
if (selected.indexOf(cst.id) < 0) {
|
if (!selected.includes(cst.id)) {
|
||||||
return prev;
|
return prev;
|
||||||
} else if (prev === -1) {
|
} else if (prev === -1) {
|
||||||
return index;
|
return index;
|
||||||
|
@ -69,23 +70,22 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
||||||
return Math.min(prev, index);
|
return Math.min(prev, index);
|
||||||
}, -1);
|
}, -1);
|
||||||
const insertIndex = Math.max(0, currentIndex - 1) + 1
|
const insertIndex = Math.max(0, currentIndex - 1) + 1
|
||||||
const data = {
|
const data = {
|
||||||
'items': selected.map(id => { return {'id': id }; }),
|
items: selected.map(id => { return { id }; }),
|
||||||
'move_to': insertIndex
|
move_to: insertIndex
|
||||||
}
|
}
|
||||||
cstMoveTo(data);
|
cstMoveTo(data);
|
||||||
}, [selected, schema?.items, cstMoveTo]);
|
}, [selected, schema?.items, cstMoveTo]);
|
||||||
|
|
||||||
|
|
||||||
// Move selected cst down
|
// Move selected cst down
|
||||||
const handleMoveDown = useCallback(
|
const handleMoveDown = useCallback(
|
||||||
async () => {
|
() => {
|
||||||
if (!schema?.items || selected.length === 0) {
|
if (!schema?.items || selected.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let count = 0;
|
let count = 0;
|
||||||
const currentIndex = schema.items.reduce((prev, cst, index) => {
|
const currentIndex = schema.items.reduce((prev, cst, index) => {
|
||||||
if (selected.indexOf(cst.id) < 0) {
|
if (!selected.includes(cst.id)) {
|
||||||
return prev;
|
return prev;
|
||||||
} else {
|
} else {
|
||||||
count += 1;
|
count += 1;
|
||||||
|
@ -96,32 +96,35 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
||||||
}
|
}
|
||||||
}, -1);
|
}, -1);
|
||||||
const insertIndex = Math.min(schema.items.length - 1, currentIndex - count + 2) + 1
|
const insertIndex = Math.min(schema.items.length - 1, currentIndex - count + 2) + 1
|
||||||
const data = {
|
const data = {
|
||||||
'items': selected.map(id => { return {'id': id }; }),
|
items: selected.map(id => { return { id }; }),
|
||||||
'move_to': insertIndex
|
move_to: insertIndex
|
||||||
}
|
}
|
||||||
cstMoveTo(data);
|
cstMoveTo(data);
|
||||||
}, [selected, schema?.items, cstMoveTo]);
|
}, [selected, schema?.items, cstMoveTo]);
|
||||||
|
|
||||||
// Generate new names for all constituents
|
// Generate new names for all constituents
|
||||||
const handleReindex = useCallback(() => {
|
const handleReindex = useCallback(() => {
|
||||||
toast.info('Переиндексация');
|
toast.info('Переиндексация');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Add new constituent
|
// Add new constituent
|
||||||
const handleAddNew = useCallback((csttype?: CstType) => {
|
const handleAddNew = useCallback((csttype?: CstType) => {
|
||||||
|
if (!schema) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!csttype) {
|
if (!csttype) {
|
||||||
setShowCstModal(true);
|
setShowCstModal(true);
|
||||||
} else {
|
} else {
|
||||||
let data: INewCstData = {
|
const data: INewCstData = {
|
||||||
'csttype': csttype,
|
csttype,
|
||||||
'alias': createAliasFor(csttype, schema!)
|
alias: createAliasFor(csttype, schema)
|
||||||
}
|
}
|
||||||
if (selected.length > 0) {
|
if (selected.length > 0) {
|
||||||
data['insert_after'] = selected[selected.length - 1]
|
data.insert_after = selected[selected.length - 1]
|
||||||
}
|
}
|
||||||
cstCreate(data, (response: AxiosResponse) =>
|
cstCreate(data, (response: AxiosResponse) =>
|
||||||
toast.success(`Добавлена конституента ${response.data['new_cst']['alias']}`));
|
toast.success(`Добавлена конституента ${response.data.new_cst.alias as string}`));
|
||||||
}
|
}
|
||||||
}, [schema, selected, cstCreate]);
|
}, [schema, selected, cstCreate]);
|
||||||
|
|
||||||
|
@ -133,25 +136,25 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
||||||
if (!isEditable || selected.length === 0) {
|
if (!isEditable || selected.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch(event.key) {
|
switch (event.key) {
|
||||||
case 'ArrowUp': handleMoveUp(); return;
|
case 'ArrowUp': handleMoveUp(); return;
|
||||||
case 'ArrowDown': handleMoveDown(); return;
|
case 'ArrowDown': handleMoveDown();
|
||||||
}
|
}
|
||||||
}, [isEditable, selected, handleMoveUp, handleMoveDown]);
|
}, [isEditable, selected, handleMoveUp, handleMoveDown]);
|
||||||
|
|
||||||
const columns = useMemo(() =>
|
const columns = useMemo(() =>
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: 'ID',
|
name: 'ID',
|
||||||
id: 'id',
|
id: 'id',
|
||||||
selector: (cst: IConstituenta) => cst.id,
|
selector: (cst: IConstituenta) => cst.id,
|
||||||
omit: true,
|
omit: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Статус',
|
name: 'Статус',
|
||||||
id: 'status',
|
id: 'status',
|
||||||
cell: (cst: IConstituenta) =>
|
cell: (cst: IConstituenta) =>
|
||||||
<div style={{fontSize: 12}}>
|
<div style={{ fontSize: 12 }}>
|
||||||
{getStatusInfo(inferStatus(cst.parse?.status, cst.parse?.valueClass)).text}
|
{getStatusInfo(inferStatus(cst.parse?.status, cst.parse?.valueClass)).text}
|
||||||
</div>,
|
</div>,
|
||||||
width: '80px',
|
width: '80px',
|
||||||
|
@ -170,8 +173,8 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
||||||
{
|
{
|
||||||
when: (cst: IConstituenta) => cst.parse?.status === ParsingStatus.VERIFIED && cst.parse?.valueClass === ValueClass.PROPERTY,
|
when: (cst: IConstituenta) => cst.parse?.status === ParsingStatus.VERIFIED && cst.parse?.valueClass === ValueClass.PROPERTY,
|
||||||
classNames: ['bg-[#a5e9fa]', 'dark:bg-[#36899e]']
|
classNames: ['bg-[#a5e9fa]', 'dark:bg-[#36899e]']
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Имя',
|
name: 'Имя',
|
||||||
|
@ -192,77 +195,82 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
||||||
{
|
{
|
||||||
when: (cst: IConstituenta) => cst.parse?.status === ParsingStatus.VERIFIED && cst.parse?.valueClass === ValueClass.PROPERTY,
|
when: (cst: IConstituenta) => cst.parse?.status === ParsingStatus.VERIFIED && cst.parse?.valueClass === ValueClass.PROPERTY,
|
||||||
classNames: ['bg-[#a5e9fa]', 'dark:bg-[#36899e]']
|
classNames: ['bg-[#a5e9fa]', 'dark:bg-[#36899e]']
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Тип',
|
name: 'Тип',
|
||||||
id: 'type',
|
id: 'type',
|
||||||
cell: (cst: IConstituenta) => <div style={{fontSize: 12}}>{getTypeLabel(cst)}</div>,
|
cell: (cst: IConstituenta) => <div style={{ fontSize: 12 }}>{getTypeLabel(cst)}</div>,
|
||||||
width: '140px',
|
width: '140px',
|
||||||
minWidth: '100px',
|
minWidth: '100px',
|
||||||
maxWidth: '140px',
|
maxWidth: '140px',
|
||||||
wrap: true,
|
wrap: true,
|
||||||
reorder: true,
|
reorder: true,
|
||||||
hide: 1600,
|
hide: 1600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Термин',
|
name: 'Термин',
|
||||||
id: 'term',
|
id: 'term',
|
||||||
selector: (cst: IConstituenta) => cst.term?.resolved || cst.term?.raw || '',
|
selector: (cst: IConstituenta) => cst.term?.resolved ?? cst.term?.raw ?? '',
|
||||||
width: '350px',
|
width: '350px',
|
||||||
minWidth: '150px',
|
minWidth: '150px',
|
||||||
maxWidth: '350px',
|
maxWidth: '350px',
|
||||||
wrap: true,
|
wrap: true,
|
||||||
reorder: true,
|
reorder: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Формальное определение',
|
name: 'Формальное определение',
|
||||||
id: 'expression',
|
id: 'expression',
|
||||||
selector: (cst: IConstituenta) => cst.definition?.formal || '',
|
selector: (cst: IConstituenta) => cst.definition?.formal ?? '',
|
||||||
minWidth: '300px',
|
minWidth: '300px',
|
||||||
maxWidth: '500px',
|
maxWidth: '500px',
|
||||||
grow: 2,
|
grow: 2,
|
||||||
wrap: true,
|
wrap: true,
|
||||||
reorder: true,
|
reorder: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Текстовое определение',
|
name: 'Текстовое определение',
|
||||||
id: 'definition',
|
id: 'definition',
|
||||||
cell: (cst: IConstituenta) => (
|
cell: (cst: IConstituenta) => (
|
||||||
<div style={{fontSize: 12}}>
|
<div style={{ fontSize: 12 }}>
|
||||||
{cst.definition?.text.resolved || cst.definition?.text.raw || ''}
|
{cst.definition?.text.resolved ?? cst.definition?.text.raw ?? ''}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
minWidth: '200px',
|
minWidth: '200px',
|
||||||
grow: 2,
|
grow: 2,
|
||||||
wrap: true,
|
wrap: true,
|
||||||
reorder: true,
|
reorder: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Конвенция / Комментарий',
|
name: 'Конвенция / Комментарий',
|
||||||
id: 'convention',
|
id: 'convention',
|
||||||
cell: (cst: IConstituenta) => <div style={{fontSize: 12}}>{cst.convention || ''}</div>,
|
cell: (cst: IConstituenta) => <div style={{ fontSize: 12 }}>{cst.convention ?? ''}</div>,
|
||||||
minWidth: '100px',
|
minWidth: '100px',
|
||||||
wrap: true,
|
wrap: true,
|
||||||
reorder: true,
|
reorder: true,
|
||||||
hide: 1800,
|
hide: 1800
|
||||||
},
|
}
|
||||||
], []
|
], []
|
||||||
);
|
);
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<CreateCstModal
|
<CreateCstModal
|
||||||
show={showCstModal}
|
show={showCstModal}
|
||||||
toggle={() => setShowCstModal(!showCstModal)}
|
toggle={() => { setShowCstModal(!showCstModal); }}
|
||||||
onCreate={handleAddNew}
|
onCreate={handleAddNew}
|
||||||
/>
|
/>
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
<div
|
<div
|
||||||
className={'flex justify-start w-full gap-1 px-2 py-1 border-y items-center h-[2.2rem] clr-app'
|
className={'flex justify-start w-full gap-1 px-2 py-1 border-y items-center h-[2.2rem] clr-app' +
|
||||||
+ (!noNavigation ? ' sticky z-10 top-[4rem]' : ' sticky z-10 top-[0rem]')}
|
(!noNavigation ? ' sticky z-10 top-[4rem]' : ' sticky z-10 top-[0rem]')}
|
||||||
>
|
>
|
||||||
<div className='mr-3 whitespace-nowrap'>Выбраны <span className='ml-2'><b>{selected.length}</b> из {schema?.stats?.count_all || 0}</span></div>
|
<div className='mr-3 whitespace-nowrap'>
|
||||||
|
Выбраны
|
||||||
|
<span className='ml-2'>
|
||||||
|
<b>{selected.length}</b> из {schema?.stats?.count_all ?? 0}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
{isEditable && <div className='flex justify-start w-full gap-1'>
|
{isEditable && <div className='flex justify-start w-full gap-1'>
|
||||||
<Button
|
<Button
|
||||||
tooltip='Переместить вверх'
|
tooltip='Переместить вверх'
|
||||||
|
@ -280,7 +288,7 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
tooltip='Удалить выбранные'
|
tooltip='Удалить выбранные'
|
||||||
icon={<DumpBinIcon color={!nothingSelected ? 'text-red': ''} size={6}/>}
|
icon={<DumpBinIcon color={!nothingSelected ? 'text-red' : ''} size={6}/>}
|
||||||
disabled={nothingSelected}
|
disabled={nothingSelected}
|
||||||
dense
|
dense
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
|
@ -296,23 +304,23 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
||||||
tooltip='Новая конституента'
|
tooltip='Новая конституента'
|
||||||
icon={<SmallPlusIcon color='text-green' size={6}/>}
|
icon={<SmallPlusIcon color='text-green' size={6}/>}
|
||||||
dense
|
dense
|
||||||
onClick={() => handleAddNew()}
|
onClick={() => { handleAddNew(); }}
|
||||||
/>
|
/>
|
||||||
{(Object.values(CstType)).map(
|
{(Object.values(CstType)).map(
|
||||||
(typeStr) => {
|
(typeStr) => {
|
||||||
const type = typeStr as CstType;
|
const type = typeStr as CstType;
|
||||||
return <Button
|
return <Button key={type}
|
||||||
text={`${getCstTypePrefix(type)}`}
|
text={`${getCstTypePrefix(type)}`}
|
||||||
tooltip={getCstTypeLabel(type)}
|
tooltip={getCstTypeLabel(type)}
|
||||||
dense
|
dense
|
||||||
onClick={() =>handleAddNew(type)}
|
onClick={() => { handleAddNew(type); }}
|
||||||
/>;
|
/>;
|
||||||
})}
|
})}
|
||||||
</div>}
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
<div className='w-full h-full' onKeyDown={handleTableKey} tabIndex={0}>
|
<div className='w-full h-full' onKeyDown={handleTableKey} tabIndex={0}>
|
||||||
<DataTableThemed
|
<DataTableThemed
|
||||||
data={schema!.items!}
|
data={schema?.items ?? []}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
keyField='id'
|
keyField='id'
|
||||||
noDataComponent={
|
noDataComponent={
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import Modal from '../../components/Common/Modal';
|
|
||||||
import { CstType } from '../../utils/models';
|
|
||||||
import Select from 'react-select';
|
|
||||||
import { CstTypeSelector, getCstTypeLabel } from '../../utils/staticUI';
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import Select from 'react-select';
|
||||||
|
|
||||||
|
import Modal from '../../components/Common/Modal';
|
||||||
|
import { type CstType } from '../../utils/models';
|
||||||
|
import { CstTypeSelector, getCstTypeLabel } from '../../utils/staticUI';
|
||||||
|
|
||||||
interface CreateCstModalProps {
|
interface CreateCstModalProps {
|
||||||
show: boolean
|
show: boolean
|
||||||
|
@ -11,9 +12,9 @@ interface CreateCstModalProps {
|
||||||
onCreate: (type: CstType) => void
|
onCreate: (type: CstType) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function CreateCstModal({show, toggle, defaultType, onCreate}: CreateCstModalProps) {
|
function CreateCstModal({ show, toggle, defaultType, onCreate }: CreateCstModalProps) {
|
||||||
const [validated, setValidated] = useState(false);
|
const [validated, setValidated] = useState(false);
|
||||||
const [selectedType, setSelectedType] = useState<CstType|undefined>(undefined);
|
const [selectedType, setSelectedType] = useState<CstType | undefined>(undefined);
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
if (selectedType) onCreate(selectedType);
|
if (selectedType) onCreate(selectedType);
|
||||||
|
@ -29,22 +30,22 @@ function CreateCstModal({show, toggle, defaultType, onCreate}: CreateCstModalPro
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title='Создание конституенты'
|
title='Создание конституенты'
|
||||||
show={show}
|
show={show}
|
||||||
toggle={toggle}
|
toggle={toggle}
|
||||||
canSubmit={validated}
|
canSubmit={validated}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
options={CstTypeSelector}
|
options={CstTypeSelector}
|
||||||
placeholder='Выберите тип'
|
placeholder='Выберите тип'
|
||||||
filterOption={null}
|
filterOption={null}
|
||||||
value={selectedType && {value: selectedType, label: getCstTypeLabel(selectedType)}}
|
value={selectedType && { value: selectedType, label: getCstTypeLabel(selectedType) }}
|
||||||
onChange={(data) => setSelectedType(data?.value)}
|
onChange={(data) => { setSelectedType(data?.value); }}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CreateCstModal;
|
export default CreateCstModal;
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
|
import { type AxiosResponse } from 'axios';
|
||||||
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import Button from '../../components/Common/Button';
|
import Button from '../../components/Common/Button';
|
||||||
import Label from '../../components/Common/Label';
|
import Label from '../../components/Common/Label';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import RSTokenButton from './RSTokenButton';
|
|
||||||
import { CstType, TokenID } from '../../utils/models';
|
|
||||||
import useCheckExpression from '../../hooks/useCheckExpression';
|
|
||||||
import ParsingResult from './ParsingResult';
|
|
||||||
import { Loader } from '../../components/Common/Loader';
|
import { Loader } from '../../components/Common/Loader';
|
||||||
import StatusBar from './StatusBar';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import { AxiosResponse } from 'axios';
|
import useCheckExpression from '../../hooks/useCheckExpression';
|
||||||
import { TextWrapper, getSymbolSubstitute } from './textEditing';
|
import { CstType, TokenID } from '../../utils/models';
|
||||||
|
import ParsingResult from './ParsingResult';
|
||||||
import RSLocalButton from './RSLocalButton';
|
import RSLocalButton from './RSLocalButton';
|
||||||
|
import RSTokenButton from './RSTokenButton';
|
||||||
|
import StatusBar from './StatusBar';
|
||||||
|
import { getSymbolSubstitute, TextWrapper } from './textEditing';
|
||||||
|
|
||||||
interface ExpressionEditorProps {
|
interface ExpressionEditorProps {
|
||||||
id: string
|
id: string
|
||||||
|
@ -19,7 +20,7 @@ interface ExpressionEditorProps {
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
value: any
|
value: string
|
||||||
onChange: (event: React.ChangeEvent<HTMLTextAreaElement>) => void
|
onChange: (event: React.ChangeEvent<HTMLTextAreaElement>) => void
|
||||||
toggleEditMode: () => void
|
toggleEditMode: () => void
|
||||||
setTypification: (typificaiton: string) => void
|
setTypification: (typificaiton: string) => void
|
||||||
|
@ -32,7 +33,7 @@ function ExpressionEditor({
|
||||||
}: ExpressionEditorProps) {
|
}: ExpressionEditorProps) {
|
||||||
const { schema, activeCst } = useRSForm();
|
const { schema, activeCst } = useRSForm();
|
||||||
const [isModified, setIsModified] = useState(false);
|
const [isModified, setIsModified] = useState(false);
|
||||||
const { parseData, checkExpression, resetParse, loading } = useCheckExpression({schema: schema});
|
const { parseData, checkExpression, resetParse, loading } = useCheckExpression({ schema });
|
||||||
const expressionCtrl = useRef<HTMLTextAreaElement>(null);
|
const expressionCtrl = useRef<HTMLTextAreaElement>(null);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
|
@ -41,14 +42,17 @@ function ExpressionEditor({
|
||||||
}, [activeCst, resetParse]);
|
}, [activeCst, resetParse]);
|
||||||
|
|
||||||
const handleCheckExpression = useCallback(() => {
|
const handleCheckExpression = useCallback(() => {
|
||||||
|
if (!activeCst) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const prefix = activeCst?.alias + (activeCst?.cstType === CstType.STRUCTURED ? '::=' : ':==');
|
const prefix = activeCst?.alias + (activeCst?.cstType === CstType.STRUCTURED ? '::=' : ':==');
|
||||||
const expression = prefix + value;
|
const expression = prefix + value;
|
||||||
checkExpression(expression, (response: AxiosResponse) => {
|
checkExpression(expression, (response: AxiosResponse) => {
|
||||||
// TODO: update cursor position
|
// TODO: update cursor position
|
||||||
setIsModified(false);
|
setIsModified(false);
|
||||||
setTypification(response.data['typification']);
|
setTypification(response.data.typification);
|
||||||
toast.success('проверка завершена');
|
toast.success('проверка завершена');
|
||||||
});
|
}).catch(console.error);
|
||||||
}, [value, checkExpression, activeCst, setTypification]);
|
}, [value, checkExpression, activeCst, setTypification]);
|
||||||
|
|
||||||
const handleEdit = useCallback((id: TokenID, key?: string) => {
|
const handleEdit = useCallback((id: TokenID, key?: string) => {
|
||||||
|
@ -56,9 +60,9 @@ function ExpressionEditor({
|
||||||
toast.error('Нет доступа к полю редактирования формального выражения');
|
toast.error('Нет доступа к полю редактирования формального выражения');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let text = new TextWrapper(expressionCtrl.current);
|
const text = new TextWrapper(expressionCtrl.current);
|
||||||
if (id === TokenID.ID_LOCAL) {
|
if (id === TokenID.ID_LOCAL) {
|
||||||
text.insertChar(key!);
|
text.insertChar(key ?? 'unknown_local');
|
||||||
} else {
|
} else {
|
||||||
text.insertToken(id);
|
text.insertToken(id);
|
||||||
}
|
}
|
||||||
|
@ -74,8 +78,11 @@ function ExpressionEditor({
|
||||||
}, [setIsModified, onChange]);
|
}, [setIsModified, onChange]);
|
||||||
|
|
||||||
const handleInput = useCallback((event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
const handleInput = useCallback((event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
|
if (!expressionCtrl.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (event.altKey) {
|
if (event.altKey) {
|
||||||
let text = new TextWrapper(expressionCtrl.current!);
|
const text = new TextWrapper(expressionCtrl.current);
|
||||||
if (text.processAltKey(event.key)) {
|
if (text.processAltKey(event.key)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
text.finalize();
|
text.finalize();
|
||||||
|
@ -86,7 +93,7 @@ function ExpressionEditor({
|
||||||
const newSymbol = getSymbolSubstitute(event.key);
|
const newSymbol = getSymbolSubstitute(event.key);
|
||||||
if (newSymbol) {
|
if (newSymbol) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let text = new TextWrapper(expressionCtrl.current!);
|
const text = new TextWrapper(expressionCtrl.current);
|
||||||
text.replaceWith(newSymbol);
|
text.replaceWith(newSymbol);
|
||||||
text.finalize();
|
text.finalize();
|
||||||
setValue(text.value);
|
setValue(text.value);
|
||||||
|
@ -99,7 +106,7 @@ function ExpressionEditor({
|
||||||
toggleEditMode()
|
toggleEditMode()
|
||||||
}, [toggleEditMode]);
|
}, [toggleEditMode]);
|
||||||
|
|
||||||
const EditButtons = useMemo( () => {
|
const EditButtons = useMemo(() => {
|
||||||
return (<div className='flex items-center justify-between w-full'>
|
return (<div className='flex items-center justify-between w-full'>
|
||||||
<div className='text-sm w-fit'>
|
<div className='text-sm w-fit'>
|
||||||
<div className='flex justify-start'>
|
<div className='flex justify-start'>
|
||||||
|
@ -112,7 +119,7 @@ function ExpressionEditor({
|
||||||
<RSTokenButton id={TokenID.REDUCE} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.REDUCE} onInsert={handleEdit}/>
|
||||||
<RSTokenButton id={TokenID.CARD} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.CARD} onInsert={handleEdit}/>
|
||||||
<RSTokenButton id={TokenID.BOOL} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.BOOL} onInsert={handleEdit}/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-start'>
|
<div className='flex justify-start'>
|
||||||
<RSTokenButton id={TokenID.BOOLEAN} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.BOOLEAN} onInsert={handleEdit}/>
|
||||||
|
@ -126,7 +133,7 @@ function ExpressionEditor({
|
||||||
<RSTokenButton id={TokenID.AND} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.AND} onInsert={handleEdit}/>
|
||||||
<RSTokenButton id={TokenID.IMPLICATION} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.IMPLICATION} onInsert={handleEdit}/>
|
||||||
<RSTokenButton id={TokenID.SET_MINUS} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.SET_MINUS} onInsert={handleEdit}/>
|
||||||
<RSTokenButton id={TokenID.PUNC_ITERATE} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.PUNC_ITERATE} onInsert={handleEdit}/>
|
||||||
<RSTokenButton id={TokenID.SUBSET} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.SUBSET} onInsert={handleEdit}/>
|
||||||
<RSTokenButton id={TokenID.DEBOOL} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.DEBOOL} onInsert={handleEdit}/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -134,7 +141,7 @@ function ExpressionEditor({
|
||||||
<RSTokenButton id={TokenID.DECART} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.DECART} onInsert={handleEdit}/>
|
||||||
<RSTokenButton id={TokenID.PUNC_SL} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.PUNC_SL} onInsert={handleEdit}/>
|
||||||
<RSTokenButton id={TokenID.UNION} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.UNION} onInsert={handleEdit}/>
|
||||||
<RSTokenButton id={TokenID.LIT_INTSET} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.LIT_INTSET} onInsert={handleEdit}/>
|
||||||
<RSTokenButton id={TokenID.EXISTS} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.EXISTS} onInsert={handleEdit}/>
|
||||||
<RSTokenButton id={TokenID.NOTEQUAL} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.NOTEQUAL} onInsert={handleEdit}/>
|
||||||
<RSTokenButton id={TokenID.NOTIN} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.NOTIN} onInsert={handleEdit}/>
|
||||||
|
@ -176,10 +183,10 @@ function ExpressionEditor({
|
||||||
</div>
|
</div>
|
||||||
</div>);
|
</div>);
|
||||||
}, [handleEdit])
|
}, [handleEdit])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3 w-full'>
|
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3 w-full'>
|
||||||
<Label
|
<Label
|
||||||
text={label}
|
text={label}
|
||||||
required={false}
|
required={false}
|
||||||
htmlFor={id}
|
htmlFor={id}
|
||||||
|
@ -220,4 +227,4 @@ function ExpressionEditor({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ExpressionEditor;
|
export default ExpressionEditor;
|
||||||
|
|
|
@ -4,7 +4,7 @@ interface ParsingResultProps {
|
||||||
data?: any
|
data?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
function ParsingResult({data}: ParsingResultProps) {
|
function ParsingResult({ data }: ParsingResultProps) {
|
||||||
return (
|
return (
|
||||||
<div className='w-full px-3 py-2 mt-2 border'>
|
<div className='w-full px-3 py-2 mt-2 border'>
|
||||||
<PrettyJson data={data} />
|
<PrettyJson data={data} />
|
||||||
|
@ -18,4 +18,4 @@ function ParsingResult({data}: ParsingResultProps) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ParsingResult;
|
export default ParsingResult;
|
||||||
|
|
|
@ -1,25 +1,26 @@
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import Button from '../../components/Common/Button';
|
||||||
import Checkbox from '../../components/Common/Checkbox';
|
import Checkbox from '../../components/Common/Checkbox';
|
||||||
import SubmitButton from '../../components/Common/SubmitButton';
|
import SubmitButton from '../../components/Common/SubmitButton';
|
||||||
import TextArea from '../../components/Common/TextArea';
|
import TextArea from '../../components/Common/TextArea';
|
||||||
import TextInput from '../../components/Common/TextInput';
|
import TextInput from '../../components/Common/TextInput';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
|
||||||
import Button from '../../components/Common/Button';
|
|
||||||
import { CrownIcon, DownloadIcon, DumpBinIcon, SaveIcon, ShareIcon } from '../../components/Icons';
|
import { CrownIcon, DownloadIcon, DumpBinIcon, SaveIcon, ShareIcon } from '../../components/Icons';
|
||||||
import { useUsers } from '../../context/UsersContext';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
|
import { useUsers } from '../../context/UsersContext';
|
||||||
import { claimOwnershipProc, deleteRSFormProc, downloadRSFormProc, shareCurrentURLProc } from '../../utils/procedures';
|
import { claimOwnershipProc, deleteRSFormProc, downloadRSFormProc, shareCurrentURLProc } from '../../utils/procedures';
|
||||||
|
|
||||||
function RSFormCard() {
|
function RSFormCard() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { getUserLabel } = useUsers();
|
const { getUserLabel } = useUsers();
|
||||||
const {
|
const {
|
||||||
schema, update, download,
|
schema, update, download,
|
||||||
isEditable, isOwned, isClaimable, processing, destroy, claim
|
isEditable, isOwned, isClaimable, processing, destroy, claim
|
||||||
} = useRSForm();
|
} = useRSForm();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
|
||||||
|
@ -29,28 +30,30 @@ function RSFormCard() {
|
||||||
const [common, setCommon] = useState(false);
|
const [common, setCommon] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTitle(schema!.title)
|
setTitle(schema?.title ?? '');
|
||||||
setAlias(schema!.alias)
|
setAlias(schema?.alias ?? '');
|
||||||
setComment(schema!.comment)
|
setComment(schema?.comment ?? '');
|
||||||
setCommon(schema!.is_common)
|
setCommon(schema?.is_common ?? false);
|
||||||
}, [schema]);
|
}, [schema]);
|
||||||
|
|
||||||
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const data = {
|
const data = {
|
||||||
'title': title,
|
title,
|
||||||
'alias': alias,
|
alias,
|
||||||
'comment': comment,
|
comment,
|
||||||
'is_common': common,
|
is_common: common
|
||||||
};
|
};
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
update(data, () => toast.success('Изменения сохранены'));
|
update(data, () => toast.success('Изменения сохранены'));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete =
|
const handleDelete =
|
||||||
useCallback(() => deleteRSFormProc(destroy, navigate), [destroy, navigate]);
|
useCallback(() => { deleteRSFormProc(destroy, navigate); }, [destroy, navigate]);
|
||||||
|
|
||||||
const handleDownload = useCallback(() => {
|
const handleDownload = useCallback(() => {
|
||||||
const fileName = (schema?.alias || 'Schema') + '.trs';
|
const fileName = (schema?.alias ?? 'Schema') + '.trs';
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
downloadRSFormProc(download, fileName);
|
downloadRSFormProc(download, fileName);
|
||||||
}, [download, schema?.alias]);
|
}, [download, schema?.alias]);
|
||||||
|
|
||||||
|
@ -60,62 +63,62 @@ function RSFormCard() {
|
||||||
required
|
required
|
||||||
value={title}
|
value={title}
|
||||||
disabled={!isEditable}
|
disabled={!isEditable}
|
||||||
onChange={event => setTitle(event.target.value)}
|
onChange={event => { setTitle(event.target.value); }}
|
||||||
/>
|
/>
|
||||||
<TextInput id='alias' label='Сокращение' type='text'
|
<TextInput id='alias' label='Сокращение' type='text'
|
||||||
required
|
required
|
||||||
value={alias}
|
value={alias}
|
||||||
disabled={!isEditable}
|
disabled={!isEditable}
|
||||||
widthClass='max-w-sm'
|
widthClass='max-w-sm'
|
||||||
onChange={event => setAlias(event.target.value)}
|
onChange={event => { setAlias(event.target.value); }}
|
||||||
/>
|
/>
|
||||||
<TextArea id='comment' label='Комментарий'
|
<TextArea id='comment' label='Комментарий'
|
||||||
value={comment}
|
value={comment}
|
||||||
disabled={!isEditable}
|
disabled={!isEditable}
|
||||||
onChange={event => setComment(event.target.value)}
|
onChange={event => { setComment(event.target.value); }}
|
||||||
/>
|
/>
|
||||||
<Checkbox id='common' label='Общедоступная схема'
|
<Checkbox id='common' label='Общедоступная схема'
|
||||||
value={common}
|
value={common}
|
||||||
disabled={!isEditable}
|
disabled={!isEditable}
|
||||||
onChange={event => setCommon(event.target.checked)}
|
onChange={event => { setCommon(event.target.checked); }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='flex items-center justify-between gap-1 py-2 mt-2'>
|
<div className='flex items-center justify-between gap-1 py-2 mt-2'>
|
||||||
<SubmitButton
|
<SubmitButton
|
||||||
text='Сохранить изменения'
|
text='Сохранить изменения'
|
||||||
loading={processing}
|
loading={processing}
|
||||||
disabled={!isEditable || processing}
|
disabled={!isEditable || processing}
|
||||||
icon={<SaveIcon size={6} />}
|
icon={<SaveIcon size={6} />}
|
||||||
/>
|
/>
|
||||||
<div className='flex justify-end gap-1'>
|
<div className='flex justify-end gap-1'>
|
||||||
<Button
|
<Button
|
||||||
tooltip='Поделиться схемой'
|
tooltip='Поделиться схемой'
|
||||||
icon={<ShareIcon color='text-primary'/>}
|
icon={<ShareIcon color='text-primary'/>}
|
||||||
onClick={shareCurrentURLProc}
|
onClick={shareCurrentURLProc}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
disabled={processing}
|
disabled={processing}
|
||||||
tooltip='Скачать TRS файл'
|
tooltip='Скачать TRS файл'
|
||||||
icon={<DownloadIcon color='text-primary'/>}
|
icon={<DownloadIcon color='text-primary'/>}
|
||||||
loading={processing}
|
loading={processing}
|
||||||
onClick={handleDownload}
|
onClick={handleDownload}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
tooltip={isClaimable ? 'Стать владельцем' : 'Вы уже являетесь владельцем' }
|
tooltip={isClaimable ? 'Стать владельцем' : 'Вы уже являетесь владельцем' }
|
||||||
disabled={!isClaimable || processing || !user}
|
disabled={!isClaimable || processing || !user}
|
||||||
icon={<CrownIcon color={isOwned ? '' : 'text-green'}/>}
|
icon={<CrownIcon color={isOwned ? '' : 'text-green'}/>}
|
||||||
onClick={() => claimOwnershipProc(claim)}
|
onClick={() => { claimOwnershipProc(claim); }}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
tooltip={ isEditable ? 'Удалить схему' : 'Вы не можете редактировать данную схему'}
|
tooltip={ isEditable ? 'Удалить схему' : 'Вы не можете редактировать данную схему'}
|
||||||
disabled={!isEditable || processing}
|
disabled={!isEditable || processing}
|
||||||
icon={<DumpBinIcon color={isEditable ? 'text-red': ''} />}
|
icon={<DumpBinIcon color={isEditable ? 'text-red' : ''} />}
|
||||||
loading={processing}
|
loading={processing}
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex justify-start mt-2'>
|
<div className='flex justify-start mt-2'>
|
||||||
<label className='font-semibold'>Владелец:</label>
|
<label className='font-semibold'>Владелец:</label>
|
||||||
<span className='min-w-[200px] ml-2 overflow-ellipsis overflow-hidden whitespace-nowrap'>
|
<span className='min-w-[200px] ml-2 overflow-ellipsis overflow-hidden whitespace-nowrap'>
|
||||||
|
@ -124,14 +127,14 @@ function RSFormCard() {
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-start mt-2'>
|
<div className='flex justify-start mt-2'>
|
||||||
<label className='font-semibold'>Дата обновления:</label>
|
<label className='font-semibold'>Дата обновления:</label>
|
||||||
<span className='ml-2'>{new Date(schema!.time_update).toLocaleString(intl.locale)}</span>
|
<span className='ml-2'>{schema && new Date(schema?.time_update).toLocaleString(intl.locale)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-start mt-2'>
|
<div className='flex justify-start mt-2'>
|
||||||
<label className='font-semibold'>Дата создания:</label>
|
<label className='font-semibold'>Дата создания:</label>
|
||||||
<span className='ml-8'>{new Date(schema!.time_create).toLocaleString(intl.locale)}</span>
|
<span className='ml-8'>{schema && new Date(schema?.time_create).toLocaleString(intl.locale)}</span>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RSFormCard;
|
export default RSFormCard;
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import Card from '../../components/Common/Card';
|
import Card from '../../components/Common/Card';
|
||||||
import Divider from '../../components/Common/Divider';
|
import Divider from '../../components/Common/Divider';
|
||||||
import LabeledText from '../../components/Common/LabeledText';
|
import LabeledText from '../../components/Common/LabeledText';
|
||||||
import { IRSFormStats } from '../../utils/models';
|
import { type IRSFormStats } from '../../utils/models';
|
||||||
|
|
||||||
interface RSFormStatsProps {
|
interface RSFormStatsProps {
|
||||||
stats: IRSFormStats
|
stats: IRSFormStats
|
||||||
}
|
}
|
||||||
|
|
||||||
function RSFormStats({stats}: RSFormStatsProps) {
|
function RSFormStats({ stats }: RSFormStatsProps) {
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<LabeledText id='count_all'
|
<LabeledText id='count_all'
|
||||||
|
@ -78,4 +78,4 @@ function RSFormStats({stats}: RSFormStatsProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RSFormStats;
|
export default RSFormStats;
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import { Tabs, TabList, TabPanel } from 'react-tabs';
|
|
||||||
import ConstituentsTable from './ConstituentsTable';
|
|
||||||
import { IConstituenta } from '../../utils/models';
|
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
|
||||||
import { useEffect, useLayoutEffect, useState } from 'react';
|
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||||
import ConceptTab from '../../components/Common/ConceptTab';
|
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||||
import RSFormCard from './RSFormCard';
|
|
||||||
import { Loader } from '../../components/Common/Loader';
|
|
||||||
import BackendError from '../../components/BackendError';
|
import BackendError from '../../components/BackendError';
|
||||||
import ConstituentEditor from './ConstituentEditor';
|
import ConceptTab from '../../components/Common/ConceptTab';
|
||||||
import RSFormStats from './RSFormStats';
|
import { Loader } from '../../components/Common/Loader';
|
||||||
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import useLocalStorage from '../../hooks/useLocalStorage';
|
import useLocalStorage from '../../hooks/useLocalStorage';
|
||||||
|
import { type IConstituenta } from '../../utils/models';
|
||||||
|
import ConstituentEditor from './ConstituentEditor';
|
||||||
|
import ConstituentsTable from './ConstituentsTable';
|
||||||
|
import RSFormCard from './RSFormCard';
|
||||||
|
import RSFormStats from './RSFormStats';
|
||||||
import TablistTools from './TablistTools';
|
import TablistTools from './TablistTools';
|
||||||
|
|
||||||
export enum RSFormTabsList {
|
export enum RSFormTabsList {
|
||||||
|
@ -19,7 +20,7 @@ export enum RSFormTabsList {
|
||||||
}
|
}
|
||||||
|
|
||||||
function RSFormTabs() {
|
function RSFormTabs() {
|
||||||
const { setActiveID, activeCst, activeID, error, schema, loading } = useRSForm();
|
const { setActiveID, activeID, error, schema, loading } = useRSForm();
|
||||||
const [tabIndex, setTabIndex] = useLocalStorage('rsform_edit_tab', RSFormTabsList.CARD);
|
const [tabIndex, setTabIndex] = useLocalStorage('rsform_edit_tab', RSFormTabsList.CARD);
|
||||||
const [init, setInit] = useState(false);
|
const [init, setInit] = useState(false);
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ function RSFormTabs() {
|
||||||
if (schema) {
|
if (schema) {
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
const activeQuery = url.searchParams.get('active');
|
const activeQuery = url.searchParams.get('active');
|
||||||
const activeCst = schema?.items?.find((cst) => cst.id === Number(activeQuery)) || undefined;
|
const activeCst = schema?.items?.find((cst) => cst.id === Number(activeQuery));
|
||||||
setActiveID(activeCst?.id);
|
setActiveID(activeCst?.id);
|
||||||
setInit(true);
|
setInit(true);
|
||||||
}
|
}
|
||||||
|
@ -52,7 +53,7 @@ function RSFormTabs() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (init) {
|
if (init) {
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
let currentActive = url.searchParams.get('active');
|
const currentActive = url.searchParams.get('active');
|
||||||
const currentTab = url.searchParams.get('tab');
|
const currentTab = url.searchParams.get('tab');
|
||||||
const saveHistory = tabIndex === RSFormTabsList.CST_EDIT && currentActive !== String(activeID);
|
const saveHistory = tabIndex === RSFormTabsList.CST_EDIT && currentActive !== String(activeID);
|
||||||
if (currentTab !== String(tabIndex)) {
|
if (currentTab !== String(tabIndex)) {
|
||||||
|
@ -78,7 +79,7 @@ function RSFormTabs() {
|
||||||
{ loading && <Loader /> }
|
{ loading && <Loader /> }
|
||||||
{ error && <BackendError error={error} />}
|
{ error && <BackendError error={error} />}
|
||||||
{ schema && !loading &&
|
{ schema && !loading &&
|
||||||
<Tabs
|
<Tabs
|
||||||
selectedIndex={tabIndex}
|
selectedIndex={tabIndex}
|
||||||
onSelect={onSelectTab}
|
onSelect={onSelectTab}
|
||||||
defaultFocus={true}
|
defaultFocus={true}
|
||||||
|
@ -89,7 +90,7 @@ function RSFormTabs() {
|
||||||
<ConceptTab>Паспорт схемы</ConceptTab>
|
<ConceptTab>Паспорт схемы</ConceptTab>
|
||||||
<ConceptTab className='border-x-2 clr-border min-w-[10rem] flex justify-between gap-2'>
|
<ConceptTab className='border-x-2 clr-border min-w-[10rem] flex justify-between gap-2'>
|
||||||
<span>Конституенты</span>
|
<span>Конституенты</span>
|
||||||
<span>{`${schema.stats?.count_errors} | ${schema.stats?.count_all}`}</span>
|
<span>{`${schema.stats?.count_errors ?? 0} | ${schema.stats?.count_all ?? 0}`}</span>
|
||||||
</ConceptTab>
|
</ConceptTab>
|
||||||
<ConceptTab>Редактор</ConceptTab>
|
<ConceptTab>Редактор</ConceptTab>
|
||||||
</TabList>
|
</TabList>
|
||||||
|
@ -111,4 +112,4 @@ function RSFormTabs() {
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RSFormTabs;
|
export default RSFormTabs;
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import { TokenID } from '../../utils/models'
|
import { TokenID } from '../../utils/models'
|
||||||
|
|
||||||
interface RSLocalButtonProps {
|
interface RSLocalButtonProps {
|
||||||
text: string
|
text: string
|
||||||
tooltip: string
|
tooltip: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
onInsert: (token: TokenID, key?: string) => void
|
onInsert: (token: TokenID, key?: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function RSLocalButton({text, tooltip, disabled, onInsert}: RSLocalButtonProps) {
|
function RSLocalButton({ text, tooltip, disabled, onInsert }: RSLocalButtonProps) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={() => onInsert(TokenID.ID_LOCAL, text)}
|
onClick={() => { onInsert(TokenID.ID_LOCAL, text); }}
|
||||||
title={tooltip}
|
title={tooltip}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
className='w-[1.5rem] h-7 cursor-pointer border rounded-none clr-btn-clear'
|
className='w-[1.5rem] h-7 cursor-pointer border rounded-none clr-btn-clear'
|
||||||
|
@ -22,4 +22,4 @@ function RSLocalButton({text, tooltip, disabled, onInsert}: RSLocalButtonProps)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RSLocalButton;
|
export default RSLocalButton;
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import { TokenID } from '../../utils/models'
|
import { type TokenID } from '../../utils/models'
|
||||||
import { getRSButtonData } from '../../utils/staticUI'
|
import { getRSButtonData } from '../../utils/staticUI'
|
||||||
|
|
||||||
interface RSTokenButtonProps {
|
interface RSTokenButtonProps {
|
||||||
id: TokenID
|
id: TokenID
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
onInsert: (token: TokenID, key?: string) => void
|
onInsert: (token: TokenID, key?: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function RSTokenButton({id, disabled, onInsert}: RSTokenButtonProps) {
|
function RSTokenButton({ id, disabled, onInsert }: RSTokenButtonProps) {
|
||||||
const data = getRSButtonData(id);
|
const data = getRSButtonData(id);
|
||||||
const width = data.text.length > 3 ? 'w-[4rem]' : 'w-[2rem]';
|
const width = data.text.length > 3 ? 'w-[4rem]' : 'w-[2rem]';
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={() => onInsert(id)}
|
onClick={() => { onInsert(id); }}
|
||||||
title={data.tooltip}
|
title={data.tooltip}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
className={`px-1 cursor-pointer border rounded-none h-7 ${width} clr-btn-clear`}
|
className={`px-1 cursor-pointer border rounded-none h-7 ${width} clr-btn-clear`}
|
||||||
|
@ -24,4 +24,4 @@ function RSTokenButton({id, disabled, onInsert}: RSTokenButtonProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RSTokenButton;
|
export default RSTokenButton;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { ExpressionStatus, IConstituenta, ParsingStatus, inferStatus } from '../../utils/models';
|
|
||||||
|
import { ExpressionStatus, type IConstituenta, inferStatus, ParsingStatus } from '../../utils/models';
|
||||||
import { getStatusInfo } from '../../utils/staticUI';
|
import { getStatusInfo } from '../../utils/staticUI';
|
||||||
|
|
||||||
interface StatusBarProps {
|
interface StatusBarProps {
|
||||||
|
@ -8,14 +9,14 @@ interface StatusBarProps {
|
||||||
constituenta?: IConstituenta
|
constituenta?: IConstituenta
|
||||||
}
|
}
|
||||||
|
|
||||||
function StatusBar({isModified, constituenta, parseData}: StatusBarProps) {
|
function StatusBar({ isModified, constituenta, parseData }: StatusBarProps) {
|
||||||
const status = useMemo(() => {
|
const status = useMemo(() => {
|
||||||
if (isModified) {
|
if (isModified) {
|
||||||
return ExpressionStatus.UNKNOWN;
|
return ExpressionStatus.UNKNOWN;
|
||||||
}
|
}
|
||||||
if (parseData) {
|
if (parseData) {
|
||||||
const parse = parseData['parseResult'] ? ParsingStatus.VERIFIED : ParsingStatus.INCORRECT;
|
const parse = parseData.parseResult ? ParsingStatus.VERIFIED : ParsingStatus.INCORRECT;
|
||||||
return inferStatus(parse, parseData['valueClass']);
|
return inferStatus(parse, parseData.valueClass);
|
||||||
}
|
}
|
||||||
return inferStatus(constituenta?.parse?.status, constituenta?.parse?.valueClass);
|
return inferStatus(constituenta?.parse?.status, constituenta?.parse?.valueClass);
|
||||||
}, [isModified, constituenta, parseData]);
|
}, [isModified, constituenta, parseData]);
|
||||||
|
@ -29,4 +30,4 @@ function StatusBar({isModified, constituenta, parseData}: StatusBarProps) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default StatusBar;
|
export default StatusBar;
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import Button from '../../components/Common/Button';
|
|
||||||
import Dropdown from '../../components/Common/Dropdown';
|
|
||||||
import { CloneIcon, CrownIcon, DownloadIcon, DumpBinIcon, EyeIcon, EyeOffIcon, MenuIcon, PenIcon, ShareIcon, UploadIcon } from '../../components/Icons';
|
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
|
||||||
import useDropdown from '../../hooks/useDropdown';
|
|
||||||
import DropdownButton from '../../components/Common/DropdownButton';
|
|
||||||
import Checkbox from '../../components/Common/Checkbox';
|
|
||||||
import { useAuth } from '../../context/AuthContext';
|
|
||||||
import { claimOwnershipProc, deleteRSFormProc, downloadRSFormProc, shareCurrentURLProc } from '../../utils/procedures';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import Button from '../../components/Common/Button';
|
||||||
|
import Checkbox from '../../components/Common/Checkbox';
|
||||||
|
import Dropdown from '../../components/Common/Dropdown';
|
||||||
|
import DropdownButton from '../../components/Common/DropdownButton';
|
||||||
|
import { CloneIcon, CrownIcon, DownloadIcon, DumpBinIcon, EyeIcon, EyeOffIcon, MenuIcon, PenIcon, ShareIcon, UploadIcon } from '../../components/Icons';
|
||||||
|
import { useAuth } from '../../context/AuthContext';
|
||||||
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
|
import useDropdown from '../../hooks/useDropdown';
|
||||||
|
import { claimOwnershipProc, deleteRSFormProc, downloadRSFormProc, shareCurrentURLProc } from '../../utils/procedures';
|
||||||
|
|
||||||
function TablistTools() {
|
function TablistTools() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const {user} = useAuth();
|
const { user } = useAuth();
|
||||||
const { schema,
|
const {
|
||||||
|
schema,
|
||||||
isOwned, isEditable, isTracking, isReadonly: readonly, isForceAdmin: forceAdmin,
|
isOwned, isEditable, isTracking, isReadonly: readonly, isForceAdmin: forceAdmin,
|
||||||
toggleTracking, toggleForceAdmin, toggleReadonly,
|
toggleTracking, toggleForceAdmin, toggleReadonly,
|
||||||
claim, destroy, download
|
claim, destroy, download
|
||||||
|
@ -24,7 +26,7 @@ function TablistTools() {
|
||||||
|
|
||||||
const handleClaimOwner = useCallback(() => {
|
const handleClaimOwner = useCallback(() => {
|
||||||
editMenu.hide();
|
editMenu.hide();
|
||||||
claimOwnershipProc(claim);
|
claimOwnershipProc(claim)
|
||||||
}, [claim, editMenu]);
|
}, [claim, editMenu]);
|
||||||
|
|
||||||
const handleDelete = useCallback(() => {
|
const handleDelete = useCallback(() => {
|
||||||
|
@ -34,7 +36,7 @@ function TablistTools() {
|
||||||
|
|
||||||
const handleDownload = useCallback(() => {
|
const handleDownload = useCallback(() => {
|
||||||
schemaMenu.hide();
|
schemaMenu.hide();
|
||||||
const fileName = (schema?.alias || 'Schema') + '.trs';
|
const fileName = (schema?.alias ?? 'Schema') + '.trs';
|
||||||
downloadRSFormProc(download, fileName);
|
downloadRSFormProc(download, fileName);
|
||||||
}, [schemaMenu, download, schema?.alias]);
|
}, [schemaMenu, download, schema?.alias]);
|
||||||
|
|
||||||
|
@ -44,7 +46,6 @@ function TablistTools() {
|
||||||
toast.info('Замена содержимого на файл Экстеора');
|
toast.info('Замена содержимого на файл Экстеора');
|
||||||
}, [schemaMenu]);
|
}, [schemaMenu]);
|
||||||
|
|
||||||
|
|
||||||
const handleClone = useCallback(() => {
|
const handleClone = useCallback(() => {
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
schemaMenu.hide();
|
schemaMenu.hide();
|
||||||
|
@ -55,7 +56,7 @@ function TablistTools() {
|
||||||
schemaMenu.hide();
|
schemaMenu.hide();
|
||||||
shareCurrentURLProc();
|
shareCurrentURLProc();
|
||||||
}, [schemaMenu]);
|
}, [schemaMenu]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex items-center w-fit'>
|
<div className='flex items-center w-fit'>
|
||||||
<div ref={schemaMenu.ref}>
|
<div ref={schemaMenu.ref}>
|
||||||
|
@ -64,23 +65,23 @@ function TablistTools() {
|
||||||
icon={<MenuIcon size={5}/>}
|
icon={<MenuIcon size={5}/>}
|
||||||
borderClass=''
|
borderClass=''
|
||||||
dense
|
dense
|
||||||
onClick={schemaMenu.toggle}
|
onClick={schemaMenu.toggle}
|
||||||
/>
|
/>
|
||||||
{ schemaMenu.isActive &&
|
{ schemaMenu.isActive &&
|
||||||
<Dropdown>
|
<Dropdown>
|
||||||
<DropdownButton onClick={handleShare}>
|
<DropdownButton onClick={handleShare}>
|
||||||
<div className='inline-flex items-center justify-start gap-2'>
|
<div className='inline-flex items-center justify-start gap-2'>
|
||||||
<ShareIcon color='text-primary' size={4}/>
|
<ShareIcon color='text-primary' size={4}/>
|
||||||
<p>Поделиться</p>
|
<p>Поделиться</p>
|
||||||
</div>
|
</div>
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
<DropdownButton onClick={handleClone}>
|
<DropdownButton onClick={handleClone}>
|
||||||
<div className='inline-flex items-center justify-start gap-2'>
|
<div className='inline-flex items-center justify-start gap-2'>
|
||||||
<CloneIcon color='text-primary' size={4}/>
|
<CloneIcon color='text-primary' size={4}/>
|
||||||
<p>Клонировать</p>
|
<p>Клонировать</p>
|
||||||
</div>
|
</div>
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
<DropdownButton onClick={handleDownload}>
|
<DropdownButton onClick={handleDownload}>
|
||||||
<div className='inline-flex items-center justify-start gap-2'>
|
<div className='inline-flex items-center justify-start gap-2'>
|
||||||
<DownloadIcon color='text-primary' size={4}/>
|
<DownloadIcon color='text-primary' size={4}/>
|
||||||
<p>Выгрузить файл Экстеор</p>
|
<p>Выгрузить файл Экстеор</p>
|
||||||
|
@ -102,11 +103,11 @@ function TablistTools() {
|
||||||
</div>
|
</div>
|
||||||
<div ref={editMenu.ref}>
|
<div ref={editMenu.ref}>
|
||||||
<Button
|
<Button
|
||||||
tooltip={'измнение: ' + (isEditable ? '[доступно]': '[запрещено]')}
|
tooltip={'измнение: ' + (isEditable ? '[доступно]' : '[запрещено]')}
|
||||||
borderClass=''
|
borderClass=''
|
||||||
icon={<PenIcon size={5} color={isEditable ? 'text-green': 'text-red'}/>}
|
icon={<PenIcon size={5} color={isEditable ? 'text-green' : 'text-red'}/>}
|
||||||
dense
|
dense
|
||||||
onClick={editMenu.toggle}
|
onClick={editMenu.toggle}
|
||||||
/>
|
/>
|
||||||
{ editMenu.isActive &&
|
{ editMenu.isActive &&
|
||||||
<Dropdown>
|
<Dropdown>
|
||||||
|
@ -130,18 +131,18 @@ function TablistTools() {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
tooltip={'отслеживание: ' + (isTracking ? '[включено]': '[выключено]')}
|
tooltip={'отслеживание: ' + (isTracking ? '[включено]' : '[выключено]')}
|
||||||
icon={isTracking ?
|
icon={isTracking
|
||||||
<EyeIcon color='text-primary' size={5}/>
|
? <EyeIcon color='text-primary' size={5}/>
|
||||||
: <EyeOffIcon size={5}/>
|
: <EyeOffIcon size={5}/>
|
||||||
}
|
}
|
||||||
borderClass=''
|
borderClass=''
|
||||||
dense
|
dense
|
||||||
onClick={toggleTracking}
|
onClick={toggleTracking}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TablistTools
|
export default TablistTools
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { RSFormState } from '../../context/RSFormContext';
|
import { RSFormState } from '../../context/RSFormContext';
|
||||||
import RSFormTabs from './RSFormTabs';
|
import RSFormTabs from './RSFormTabs';
|
||||||
|
|
||||||
function RSFormPage() {
|
function RSFormPage() {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
return (
|
return (
|
||||||
<RSFormState schemaID={id || ''}>
|
<RSFormState schemaID={id ?? ''}>
|
||||||
<RSFormTabs />
|
<RSFormTabs />
|
||||||
</RSFormState>
|
</RSFormState>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RSFormPage;
|
export default RSFormPage;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import { TokenID } from '../../utils/models'
|
import { TokenID } from '../../utils/models'
|
||||||
|
|
||||||
export function getSymbolSubstitute(input: string): string | undefined {
|
export function getSymbolSubstitute(input: string): string | undefined {
|
||||||
switch(input) {
|
switch (input) {
|
||||||
case '`': return '∀';
|
case '`': return '∀';
|
||||||
case '~': return '∃';
|
case '~': return '∃';
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ export class TextWrapper implements IManagedText {
|
||||||
this.object.selectionStart = this.selStart;
|
this.object.selectionStart = this.selStart;
|
||||||
this.object.selectionEnd = this.selEnd;
|
this.object.selectionEnd = this.selEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceWith(data: string) {
|
replaceWith(data: string) {
|
||||||
this.value = this.value.substring(0, this.selStart) + data + this.value.substring(this.selEnd, this.value.length);
|
this.value = this.value.substring(0, this.selStart) + data + this.value.substring(this.selEnd, this.value.length);
|
||||||
this.selEnd += data.length - this.selEnd + this.selStart;
|
this.selEnd += data.length - this.selEnd + this.selStart;
|
||||||
|
@ -79,8 +79,8 @@ export class TextWrapper implements IManagedText {
|
||||||
}
|
}
|
||||||
|
|
||||||
envelopeWith(left: string, right: string) {
|
envelopeWith(left: string, right: string) {
|
||||||
this.value = this.value.substring(0, this.selStart) + left +
|
this.value = this.value.substring(0, this.selStart) + left +
|
||||||
this.value.substring(this.selStart, this.selEnd) + right +
|
this.value.substring(this.selStart, this.selEnd) + right +
|
||||||
this.value.substring(this.selEnd, this.value.length);
|
this.value.substring(this.selEnd, this.value.length);
|
||||||
this.selEnd += left.length + right.length;
|
this.selEnd += left.length + right.length;
|
||||||
}
|
}
|
||||||
|
@ -100,18 +100,18 @@ export class TextWrapper implements IManagedText {
|
||||||
}
|
}
|
||||||
|
|
||||||
insertToken(tokenID: TokenID): boolean {
|
insertToken(tokenID: TokenID): boolean {
|
||||||
switch(tokenID) {
|
switch (tokenID) {
|
||||||
case TokenID.NT_DECLARATIVE_EXPR: this.envelopeWith('D{ξ∈X1 | ', '}'); return true;
|
case TokenID.NT_DECLARATIVE_EXPR: this.envelopeWith('D{ξ∈X1 | ', '}'); return true;
|
||||||
case TokenID.NT_IMPERATIVE_EXPR: this.envelopeWith('I{(σ, γ) | σ:∈X1; γ:=F1[σ]; ', '}'); return true;
|
case TokenID.NT_IMPERATIVE_EXPR: this.envelopeWith('I{(σ, γ) | σ:∈X1; γ:=F1[σ]; ', '}'); return true;
|
||||||
case TokenID.NT_RECURSIVE_FULL: this.envelopeWith('R{ ξ:=D1 | 1=1 | ', '}'); return true;
|
case TokenID.NT_RECURSIVE_FULL: this.envelopeWith('R{ ξ:=D1 | 1=1 | ', '}'); return true;
|
||||||
case TokenID.BIGPR: this.envelopeWith('Pr1(', ')'); return true;
|
case TokenID.BIGPR: this.envelopeWith('Pr1(', ')'); return true;
|
||||||
case TokenID.SMALLPR: this.envelopeWith('pr1(', ')'); return true;
|
case TokenID.SMALLPR: this.envelopeWith('pr1(', ')'); return true;
|
||||||
case TokenID.FILTER: this.envelopeWith('Fi1[α](', ')'); return true;
|
case TokenID.FILTER: this.envelopeWith('Fi1[α](', ')'); return true;
|
||||||
case TokenID.REDUCE: this.envelopeWith('red(', ')'); return true;
|
case TokenID.REDUCE: this.envelopeWith('red(', ')'); return true;
|
||||||
case TokenID.CARD: this.envelopeWith('card(', ')'); return true;
|
case TokenID.CARD: this.envelopeWith('card(', ')'); return true;
|
||||||
case TokenID.BOOL: this.envelopeWith('bool(', ')'); return true;
|
case TokenID.BOOL: this.envelopeWith('bool(', ')'); return true;
|
||||||
case TokenID.DEBOOL: this.envelopeWith('debool(', ')'); return true;
|
case TokenID.DEBOOL: this.envelopeWith('debool(', ')'); return true;
|
||||||
|
|
||||||
case TokenID.PUNC_PL: {
|
case TokenID.PUNC_PL: {
|
||||||
this.envelopeWith('(', ')');
|
this.envelopeWith('(', ')');
|
||||||
this.selEnd = this.selStart + 1;
|
this.selEnd = this.selStart + 1;
|
||||||
|
@ -130,41 +130,41 @@ export class TextWrapper implements IManagedText {
|
||||||
} else {
|
} else {
|
||||||
this.envelopeWith('ℬ(', ')');
|
this.envelopeWith('ℬ(', ')');
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
case TokenID.DECART: this.replaceWith('×'); return true;
|
case TokenID.DECART: this.replaceWith('×'); return true;
|
||||||
case TokenID.FORALL: this.replaceWith('∀'); return true;
|
case TokenID.FORALL: this.replaceWith('∀'); return true;
|
||||||
case TokenID.EXISTS: this.replaceWith('∃'); return true;
|
case TokenID.EXISTS: this.replaceWith('∃'); return true;
|
||||||
case TokenID.IN: this.replaceWith('∈'); return true;
|
case TokenID.IN: this.replaceWith('∈'); return true;
|
||||||
case TokenID.NOTIN: this.replaceWith('∉'); return true;
|
case TokenID.NOTIN: this.replaceWith('∉'); return true;
|
||||||
case TokenID.OR: this.replaceWith('∨'); return true;
|
case TokenID.OR: this.replaceWith('∨'); return true;
|
||||||
case TokenID.AND: this.replaceWith('&'); return true;
|
case TokenID.AND: this.replaceWith('&'); return true;
|
||||||
case TokenID.SUBSET_OR_EQ: this.replaceWith('⊆'); return true;
|
case TokenID.SUBSET_OR_EQ: this.replaceWith('⊆'); return true;
|
||||||
case TokenID.IMPLICATION: this.replaceWith('⇒'); return true;
|
case TokenID.IMPLICATION: this.replaceWith('⇒'); return true;
|
||||||
case TokenID.INTERSECTION: this.replaceWith('∩'); return true;
|
case TokenID.INTERSECTION: this.replaceWith('∩'); return true;
|
||||||
case TokenID.UNION: this.replaceWith('∪'); return true;
|
case TokenID.UNION: this.replaceWith('∪'); return true;
|
||||||
case TokenID.SET_MINUS: this.replaceWith('\\'); return true;
|
case TokenID.SET_MINUS: this.replaceWith('\\'); return true;
|
||||||
case TokenID.SYMMINUS: this.replaceWith('∆'); return true;
|
case TokenID.SYMMINUS: this.replaceWith('∆'); return true;
|
||||||
case TokenID.LIT_EMPTYSET: this.replaceWith('∅'); return true;
|
case TokenID.LIT_EMPTYSET: this.replaceWith('∅'); return true;
|
||||||
case TokenID.LIT_INTSET: this.replaceWith('Z'); return true;
|
case TokenID.LIT_INTSET: this.replaceWith('Z'); return true;
|
||||||
case TokenID.SUBSET: this.replaceWith('⊂'); return true;
|
case TokenID.SUBSET: this.replaceWith('⊂'); return true;
|
||||||
case TokenID.NOTSUBSET: this.replaceWith('⊄'); return true;
|
case TokenID.NOTSUBSET: this.replaceWith('⊄'); return true;
|
||||||
case TokenID.EQUAL: this.replaceWith('='); return true;
|
case TokenID.EQUAL: this.replaceWith('='); return true;
|
||||||
case TokenID.NOTEQUAL: this.replaceWith('≠'); return true;
|
case TokenID.NOTEQUAL: this.replaceWith('≠'); return true;
|
||||||
case TokenID.NOT: this.replaceWith('¬'); return true;
|
case TokenID.NOT: this.replaceWith('¬'); return true;
|
||||||
case TokenID.EQUIVALENT: this.replaceWith('⇔'); return true;
|
case TokenID.EQUIVALENT: this.replaceWith('⇔'); return true;
|
||||||
case TokenID.GREATER_OR_EQ: this.replaceWith('≥'); return true;
|
case TokenID.GREATER_OR_EQ: this.replaceWith('≥'); return true;
|
||||||
case TokenID.LESSER_OR_EQ: this.replaceWith('≤'); return true;
|
case TokenID.LESSER_OR_EQ: this.replaceWith('≤'); return true;
|
||||||
case TokenID.PUNC_ASSIGN: this.replaceWith(':='); return true;
|
case TokenID.PUNC_ASSIGN: this.replaceWith(':='); return true;
|
||||||
case TokenID.PUNC_ITERATE: this.replaceWith(':∈'); return true;
|
case TokenID.PUNC_ITERATE: this.replaceWith(':∈'); return true;
|
||||||
case TokenID.MULTIPLY: this.replaceWith('*'); return true;
|
case TokenID.MULTIPLY: this.replaceWith('*'); return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
processAltKey(key: string): boolean {
|
processAltKey(key: string): boolean {
|
||||||
switch(key) {
|
switch (key) {
|
||||||
// qwert
|
// qwert
|
||||||
// asdfg
|
// asdfg
|
||||||
// zxcvb
|
// zxcvb
|
||||||
|
@ -183,7 +183,7 @@ export class TextWrapper implements IManagedText {
|
||||||
case 'c': return this.insertToken(TokenID.CARD);
|
case 'c': return this.insertToken(TokenID.CARD);
|
||||||
case 'v': return this.insertToken(TokenID.DEBOOL);
|
case 'v': return this.insertToken(TokenID.DEBOOL);
|
||||||
case 'b': return this.insertToken(TokenID.BOOL);
|
case 'b': return this.insertToken(TokenID.BOOL);
|
||||||
|
|
||||||
// `123456
|
// `123456
|
||||||
// ~!@#$%^
|
// ~!@#$%^
|
||||||
case '`': return this.insertToken(TokenID.NOT);
|
case '`': return this.insertToken(TokenID.NOT);
|
||||||
|
@ -209,4 +209,4 @@ export class TextWrapper implements IManagedText {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import BackendError from '../../components/BackendError'
|
import BackendError from '../../components/BackendError'
|
||||||
import { Loader } from '../../components/Common/Loader'
|
import { Loader } from '../../components/Common/Loader'
|
||||||
import { FilterType, RSFormsFilter, useRSForms } from '../../hooks/useRSForms'
|
|
||||||
import RSFormsTable from './RSFormsTable';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
|
import { FilterType, type RSFormsFilter, useRSForms } from '../../hooks/useRSForms'
|
||||||
|
import RSFormsTable from './RSFormsTable';
|
||||||
|
|
||||||
function RSFormsPage() {
|
function RSFormsPage() {
|
||||||
const search = useLocation().search;
|
const search = useLocation().search;
|
||||||
|
@ -14,13 +15,13 @@ function RSFormsPage() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const filterQuery = new URLSearchParams(search).get('filter');
|
const filterQuery = new URLSearchParams(search).get('filter');
|
||||||
const type = (!user || !filterQuery ? FilterType.COMMON : filterQuery as FilterType);
|
const type = (!user || !filterQuery ? FilterType.COMMON : filterQuery as FilterType);
|
||||||
let filter: RSFormsFilter = {type: type};
|
const filter: RSFormsFilter = { type };
|
||||||
if (type === FilterType.PERSONAL) {
|
if (type === FilterType.PERSONAL) {
|
||||||
filter.data = user?.id;
|
filter.data = user?.id;
|
||||||
}
|
}
|
||||||
loadList(filter);
|
loadList(filter).catch(console.error);
|
||||||
}, [search, user, loadList]);
|
}, [search, user, loadList]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
{ loading && <Loader /> }
|
{ loading && <Loader /> }
|
||||||
|
@ -30,4 +31,4 @@ function RSFormsPage() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RSFormsPage;
|
export default RSFormsPage;
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import TextInput from '../components/Common/TextInput';
|
|
||||||
import Form from '../components/Common/Form';
|
|
||||||
import { useAuth } from '../context/AuthContext';
|
|
||||||
import SubmitButton from '../components/Common/SubmitButton';
|
|
||||||
import BackendError from '../components/BackendError';
|
import BackendError from '../components/BackendError';
|
||||||
import { IUserSignupData } from '../utils/models';
|
import Form from '../components/Common/Form';
|
||||||
import InfoMessage from '../components/InfoMessage';
|
import SubmitButton from '../components/Common/SubmitButton';
|
||||||
|
import TextInput from '../components/Common/TextInput';
|
||||||
import TextURL from '../components/Common/TextURL';
|
import TextURL from '../components/Common/TextURL';
|
||||||
|
import InfoMessage from '../components/InfoMessage';
|
||||||
|
import { useAuth } from '../context/AuthContext';
|
||||||
|
import { type IUserSignupData } from '../utils/models';
|
||||||
|
|
||||||
function RegisterPage() {
|
function RegisterPage() {
|
||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
|
@ -16,7 +16,7 @@ function RegisterPage() {
|
||||||
const [password2, setPassword2] = useState('');
|
const [password2, setPassword2] = useState('');
|
||||||
const [firstName, setFirstName] = useState('');
|
const [firstName, setFirstName] = useState('');
|
||||||
const [lastName, setLastName] = useState('');
|
const [lastName, setLastName] = useState('');
|
||||||
|
|
||||||
const [success, setSuccess] = useState(false);
|
const [success, setSuccess] = useState(false);
|
||||||
const { user, signup, loading, error, setError } = useAuth()
|
const { user, signup, loading, error, setError } = useAuth()
|
||||||
|
|
||||||
|
@ -27,43 +27,43 @@ function RegisterPage() {
|
||||||
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!loading) {
|
if (!loading) {
|
||||||
const data: IUserSignupData = {
|
const data: IUserSignupData = {
|
||||||
'username': username,
|
username,
|
||||||
'email': email,
|
email,
|
||||||
'password': password,
|
password,
|
||||||
'password2': password2,
|
password2,
|
||||||
'first_name': firstName,
|
first_name: firstName,
|
||||||
'last_name': lastName,
|
last_name: lastName
|
||||||
};
|
};
|
||||||
signup(data, () => setSuccess(true));
|
signup(data, () => { setSuccess(true); });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full py-2'>
|
<div className='w-full py-2'>
|
||||||
{ success &&
|
{ success &&
|
||||||
<div className='flex flex-col items-center'>
|
<div className='flex flex-col items-center'>
|
||||||
<InfoMessage message={`Вы успешно зарегистрировали пользователя ${username}`}/>
|
<InfoMessage message={`Вы успешно зарегистрировали пользователя ${username}`}/>
|
||||||
<TextURL text='Войти в аккаунт' href={`/login?username=${username}`}/>
|
<TextURL text='Войти в аккаунт' href={`/login?username=${username}`}/>
|
||||||
</div>}
|
</div>}
|
||||||
{ !success && user &&
|
{ !success && user &&
|
||||||
<InfoMessage message={`Вы вошли в систему как ${user.username}. Если хотите зарегистрировать нового пользователя, выйдите из системы (меню в правом верхнем углу экрана)`} /> }
|
<InfoMessage message={`Вы вошли в систему как ${user.username}. Если хотите зарегистрировать нового пользователя, выйдите из системы (меню в правом верхнем углу экрана)`} /> }
|
||||||
{ !success && !user &&
|
{ !success && !user &&
|
||||||
<Form title='Регистрация пользователя' onSubmit={handleSubmit}>
|
<Form title='Регистрация пользователя' onSubmit={handleSubmit}>
|
||||||
<TextInput id='username' label='Имя пользователя' type='text'
|
<TextInput id='username' label='Имя пользователя' type='text'
|
||||||
required
|
required
|
||||||
value={username}
|
value={username}
|
||||||
onChange={event => setUsername(event.target.value)}
|
onChange={event => { setUsername(event.target.value); }}
|
||||||
/>
|
/>
|
||||||
<TextInput id='password' label='Пароль' type='password'
|
<TextInput id='password' label='Пароль' type='password'
|
||||||
required
|
required
|
||||||
value={password}
|
value={password}
|
||||||
onChange={event => setPassword(event.target.value)}
|
onChange={event => { setPassword(event.target.value); }}
|
||||||
/>
|
/>
|
||||||
<TextInput id='password2' label='Повторите пароль' type='password'
|
<TextInput id='password2' label='Повторите пароль' type='password'
|
||||||
required
|
required
|
||||||
value={password2}
|
value={password2}
|
||||||
onChange={event => setPassword2(event.target.value)}
|
onChange={event => { setPassword2(event.target.value); }}
|
||||||
/>
|
/>
|
||||||
<div className='text-sm'>
|
<div className='text-sm'>
|
||||||
<p>- минимум 8 символов</p>
|
<p>- минимум 8 символов</p>
|
||||||
|
@ -73,15 +73,15 @@ function RegisterPage() {
|
||||||
<TextInput id='email' label='email' type='text'
|
<TextInput id='email' label='email' type='text'
|
||||||
required
|
required
|
||||||
value={email}
|
value={email}
|
||||||
onChange={event => setEmail(event.target.value)}
|
onChange={event => { setEmail(event.target.value); }}
|
||||||
/>
|
/>
|
||||||
<TextInput id='first_name' label='Имя' type='text'
|
<TextInput id='first_name' label='Имя' type='text'
|
||||||
value={firstName}
|
value={firstName}
|
||||||
onChange={event => setFirstName(event.target.value)}
|
onChange={event => { setFirstName(event.target.value); }}
|
||||||
/>
|
/>
|
||||||
<TextInput id='last_name' label='Фамилия' type='text'
|
<TextInput id='last_name' label='Фамилия' type='text'
|
||||||
value={lastName}
|
value={lastName}
|
||||||
onChange={event => setLastName(event.target.value)}
|
onChange={event => { setLastName(event.target.value); }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='flex items-center justify-between my-4'>
|
<div className='flex items-center justify-between my-4'>
|
||||||
|
@ -94,4 +94,4 @@ function RegisterPage() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RegisterPage;
|
export default RegisterPage;
|
||||||
|
|
|
@ -6,4 +6,4 @@ function RestorePasswordPage() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RestorePasswordPage;
|
export default RestorePasswordPage;
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { IUserProfile } from '../../utils/models';
|
import { type IUserProfile } from '../../utils/models';
|
||||||
|
|
||||||
interface UserProfileProps {
|
interface UserProfileProps {
|
||||||
profile: IUserProfile
|
profile: IUserProfile
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UserProfile({profile}: UserProfileProps) {
|
export function UserProfile({ profile }: UserProfileProps) {
|
||||||
return (
|
return (
|
||||||
<div className='flex justify-center'>
|
<div className='flex justify-center'>
|
||||||
<p>username: {profile.username}</p>
|
<p>username: {profile.username}</p>
|
||||||
<p>email: {profile.email}</p>
|
<p>email: {profile.email}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,4 +15,4 @@ function UserProfilePage() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default UserProfilePage;
|
export default UserProfilePage;
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import axios, { AxiosResponse } from 'axios'
|
import axios, { type AxiosResponse } from 'axios'
|
||||||
import { config } from './constants'
|
|
||||||
import { ErrorInfo } from '../components/BackendError'
|
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { ICurrentUser, IRSForm, IUserInfo, IUserProfile } from './models'
|
|
||||||
import { FilterType, RSFormsFilter } from '../hooks/useRSForms'
|
import { type ErrorInfo } from '../components/BackendError'
|
||||||
|
import { FilterType, type RSFormsFilter } from '../hooks/useRSForms'
|
||||||
|
import { config } from './constants'
|
||||||
|
import { type ICurrentUser, type IRSForm, type IUserInfo, type IUserProfile } from './models'
|
||||||
|
|
||||||
export type BackendCallback = (response: AxiosResponse) => void;
|
export type BackendCallback = (response: AxiosResponse) => void;
|
||||||
|
|
||||||
|
@ -23,231 +24,230 @@ interface IAxiosRequest {
|
||||||
|
|
||||||
// ================= Export API ==============
|
// ================= Export API ==============
|
||||||
export async function postLogin(request?: IFrontRequest) {
|
export async function postLogin(request?: IFrontRequest) {
|
||||||
AxiosPost({
|
await AxiosPost({
|
||||||
title: 'Login',
|
title: 'Login',
|
||||||
endpoint: `${config.url.AUTH}login`,
|
endpoint: `${config.url.AUTH}login`,
|
||||||
request: request
|
request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAuth(request?: IFrontRequest) {
|
export async function getAuth(request?: IFrontRequest) {
|
||||||
AxiosGet<ICurrentUser>({
|
await AxiosGet<ICurrentUser>({
|
||||||
title: 'Current user',
|
title: 'Current user',
|
||||||
endpoint: `${config.url.AUTH}auth`,
|
endpoint: `${config.url.AUTH}auth`,
|
||||||
request: request
|
request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getProfile(request?: IFrontRequest) {
|
export async function getProfile(request?: IFrontRequest) {
|
||||||
AxiosGet<IUserProfile>({
|
await AxiosGet<IUserProfile>({
|
||||||
title: 'Current user profile',
|
title: 'Current user profile',
|
||||||
endpoint: `${config.url.AUTH}profile`,
|
endpoint: `${config.url.AUTH}profile`,
|
||||||
request: request
|
request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postLogout(request?: IFrontRequest) {
|
export async function postLogout(request?: IFrontRequest) {
|
||||||
AxiosPost({
|
await AxiosPost({
|
||||||
title: 'Logout',
|
title: 'Logout',
|
||||||
endpoint: `${config.url.AUTH}logout`,
|
endpoint: `${config.url.AUTH}logout`,
|
||||||
request: request
|
request
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function postSignup(request?: IFrontRequest) {
|
export async function postSignup(request?: IFrontRequest) {
|
||||||
AxiosPost({
|
await AxiosPost({
|
||||||
title: 'Register user',
|
title: 'Register user',
|
||||||
endpoint: `${config.url.AUTH}signup`,
|
endpoint: `${config.url.AUTH}signup`,
|
||||||
request: request
|
request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getActiveUsers(request?: IFrontRequest) {
|
export async function getActiveUsers(request?: IFrontRequest) {
|
||||||
AxiosGet<IUserInfo>({
|
await AxiosGet<IUserInfo>({
|
||||||
title: 'Active users list',
|
title: 'Active users list',
|
||||||
endpoint: `${config.url.AUTH}active-users`,
|
endpoint: `${config.url.AUTH}active-users`,
|
||||||
request: request
|
request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRSForms(filter: RSFormsFilter, request?: IFrontRequest) {
|
export async function getRSForms(filter: RSFormsFilter, request?: IFrontRequest) {
|
||||||
let endpoint: string = ''
|
let endpoint: string = ''
|
||||||
if (filter.type === FilterType.PERSONAL) {
|
if (filter.type === FilterType.PERSONAL) {
|
||||||
endpoint = `${config.url.BASE}rsforms?owner=${filter.data!}`
|
endpoint = `${config.url.BASE}rsforms?owner=${filter.data as number}`
|
||||||
} else {
|
} else {
|
||||||
endpoint = `${config.url.BASE}rsforms?is_common=true`
|
endpoint = `${config.url.BASE}rsforms?is_common=true`
|
||||||
}
|
}
|
||||||
|
|
||||||
AxiosGet<IRSForm[]>({
|
await AxiosGet<IRSForm[]>({
|
||||||
title: `RSForms list`,
|
title: 'RSForms list',
|
||||||
endpoint: endpoint,
|
endpoint,
|
||||||
request: request
|
request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postNewRSForm(request?: IFrontRequest) {
|
export async function postNewRSForm(request?: IFrontRequest) {
|
||||||
AxiosPost({
|
await AxiosPost({
|
||||||
title: `New RSForm`,
|
title: 'New RSForm',
|
||||||
endpoint: `${config.url.BASE}rsforms/create-detailed/`,
|
endpoint: `${config.url.BASE}rsforms/create-detailed/`,
|
||||||
request: request
|
request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRSFormDetails(target: string, request?: IFrontRequest) {
|
export async function getRSFormDetails(target: string, request?: IFrontRequest) {
|
||||||
AxiosGet<IRSForm>({
|
await AxiosGet<IRSForm>({
|
||||||
title: `RSForm details for id=${target}`,
|
title: `RSForm details for id=${target}`,
|
||||||
endpoint: `${config.url.BASE}rsforms/${target}/details/`,
|
endpoint: `${config.url.BASE}rsforms/${target}/details/`,
|
||||||
request: request
|
request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function patchRSForm(target: string, request?: IFrontRequest) {
|
export async function patchRSForm(target: string, request?: IFrontRequest) {
|
||||||
AxiosPatch({
|
await AxiosPatch({
|
||||||
title: `RSForm id=${target}`,
|
title: `RSForm id=${target}`,
|
||||||
endpoint: `${config.url.BASE}rsforms/${target}/`,
|
endpoint: `${config.url.BASE}rsforms/${target}/`,
|
||||||
request: request
|
request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function patchConstituenta(target: string, request?: IFrontRequest) {
|
export async function patchConstituenta(target: string, request?: IFrontRequest) {
|
||||||
AxiosPatch({
|
await AxiosPatch({
|
||||||
title: `Constituenta id=${target}`,
|
title: `Constituenta id=${target}`,
|
||||||
endpoint: `${config.url.BASE}constituents/${target}/`,
|
endpoint: `${config.url.BASE}constituents/${target}/`,
|
||||||
request: request
|
request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function deleteRSForm(target: string, request?: IFrontRequest) {
|
export async function deleteRSForm(target: string, request?: IFrontRequest) {
|
||||||
AxiosDelete({
|
await AxiosDelete({
|
||||||
title: `RSForm id=${target}`,
|
title: `RSForm id=${target}`,
|
||||||
endpoint: `${config.url.BASE}rsforms/${target}/`,
|
endpoint: `${config.url.BASE}rsforms/${target}/`,
|
||||||
request: request
|
request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getTRSFile(target: string, request?: IFrontRequest) {
|
export async function getTRSFile(target: string, request?: IFrontRequest) {
|
||||||
AxiosGetBlob({
|
await AxiosGetBlob({
|
||||||
title: `RSForm TRS file for id=${target}`,
|
title: `RSForm TRS file for id=${target}`,
|
||||||
endpoint: `${config.url.BASE}rsforms/${target}/export-trs/`,
|
endpoint: `${config.url.BASE}rsforms/${target}/export-trs/`,
|
||||||
request: request
|
request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postClaimRSForm(target: string, request?: IFrontRequest) {
|
export async function postClaimRSForm(target: string, request?: IFrontRequest) {
|
||||||
AxiosPost({
|
await AxiosPost({
|
||||||
title: `Claim on RSForm id=${target}`,
|
title: `Claim on RSForm id=${target}`,
|
||||||
endpoint: `${config.url.BASE}rsforms/${target}/claim/`,
|
endpoint: `${config.url.BASE}rsforms/${target}/claim/`,
|
||||||
request: request
|
request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postCheckExpression(schema: string, request?: IFrontRequest) {
|
export async function postCheckExpression(schema: string, request?: IFrontRequest) {
|
||||||
AxiosPost({
|
await AxiosPost({
|
||||||
title: `Check expression for RSForm id=${schema}: ${request?.data['expression']}`,
|
title: `Check expression for RSForm id=${schema}: ${request?.data.expression as string}`,
|
||||||
endpoint: `${config.url.BASE}rsforms/${schema}/check/`,
|
endpoint: `${config.url.BASE}rsforms/${schema}/check/`,
|
||||||
request: request
|
request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postNewConstituenta(schema: string, request?: IFrontRequest) {
|
export async function postNewConstituenta(schema: string, request?: IFrontRequest) {
|
||||||
AxiosPost({
|
await AxiosPost({
|
||||||
title: `New Constituenta for RSForm id=${schema}: ${request?.data['alias']}`,
|
title: `New Constituenta for RSForm id=${schema}: ${request?.data.alias as string}`,
|
||||||
endpoint: `${config.url.BASE}rsforms/${schema}/cst-create/`,
|
endpoint: `${config.url.BASE}rsforms/${schema}/cst-create/`,
|
||||||
request: request
|
request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function patchDeleteConstituenta(schema: string, request?: IFrontRequest) {
|
export async function patchDeleteConstituenta(schema: string, request?: IFrontRequest) {
|
||||||
AxiosPatch<IRSForm>({
|
await AxiosPatch<IRSForm>({
|
||||||
title: `Delete Constituents for RSForm id=${schema}: ${request?.data['items'].toString()}`,
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
|
title: `Delete Constituents for RSForm id=${schema}: ${request?.data.items.toString()}`,
|
||||||
endpoint: `${config.url.BASE}rsforms/${schema}/cst-multidelete/`,
|
endpoint: `${config.url.BASE}rsforms/${schema}/cst-multidelete/`,
|
||||||
request: request
|
request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function patchMoveConstituenta(schema: string, request?: IFrontRequest) {
|
export async function patchMoveConstituenta(schema: string, request?: IFrontRequest) {
|
||||||
AxiosPatch<IRSForm>({
|
await AxiosPatch<IRSForm>({
|
||||||
title: `Moving Constituents for RSForm id=${schema}: ${JSON.stringify(request?.data['items'])} to ${request?.data['move_to']}`,
|
title: `Moving Constituents for RSForm id=${schema}: ${JSON.stringify(request?.data.items)} to ${request?.data.move_to as number}`,
|
||||||
endpoint: `${config.url.BASE}rsforms/${schema}/cst-moveto/`,
|
endpoint: `${config.url.BASE}rsforms/${schema}/cst-moveto/`,
|
||||||
request: request
|
request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ====== Helper functions ===========
|
// ====== Helper functions ===========
|
||||||
async function AxiosGet<ReturnType>({endpoint, request, title}: IAxiosRequest) {
|
async function AxiosGet<ReturnType>({ endpoint, request, title }: IAxiosRequest) {
|
||||||
if (title) console.log(`[[${title}]] requested`);
|
if (title) console.log(`[[${title}]] requested`);
|
||||||
if (request?.setLoading) request?.setLoading(true);
|
if (request?.setLoading) request?.setLoading(true);
|
||||||
axios.get<ReturnType>(endpoint)
|
axios.get<ReturnType>(endpoint)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (request?.setLoading) request?.setLoading(false);
|
if (request?.setLoading) request?.setLoading(false);
|
||||||
if (request?.onSucccess) request.onSucccess(response);
|
if (request?.onSucccess) request.onSucccess(response);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (request?.setLoading) request?.setLoading(false);
|
if (request?.setLoading) request?.setLoading(false);
|
||||||
if (request?.showError) toast.error(error.message);
|
if (request?.showError) toast.error(error.message);
|
||||||
if (request?.onError) request.onError(error);
|
if (request?.onError) request.onError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function AxiosGetBlob({endpoint, request, title}: IAxiosRequest) {
|
async function AxiosGetBlob({ endpoint, request, title }: IAxiosRequest) {
|
||||||
if (title) console.log(`[[${title}]] requested`);
|
if (title) console.log(`[[${title}]] requested`);
|
||||||
if (request?.setLoading) request?.setLoading(true);
|
if (request?.setLoading) request?.setLoading(true);
|
||||||
axios.get(endpoint, {responseType: 'blob'})
|
axios.get(endpoint, { responseType: 'blob' })
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (request?.setLoading) request?.setLoading(false);
|
if (request?.setLoading) request?.setLoading(false);
|
||||||
if (request?.onSucccess) request.onSucccess(response);
|
if (request?.onSucccess) request.onSucccess(response);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (request?.setLoading) request?.setLoading(false);
|
if (request?.setLoading) request?.setLoading(false);
|
||||||
if (request?.showError) toast.error(error.message);
|
if (request?.showError) toast.error(error.message);
|
||||||
if (request?.onError) request.onError(error);
|
if (request?.onError) request.onError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function AxiosPost({endpoint, request, title}: IAxiosRequest) {
|
async function AxiosPost({ endpoint, request, title }: IAxiosRequest) {
|
||||||
if (title) console.log(`[[${title}]] posted`);
|
if (title) console.log(`[[${title}]] posted`);
|
||||||
if (request?.setLoading) request?.setLoading(true);
|
if (request?.setLoading) request?.setLoading(true);
|
||||||
axios.post(endpoint, request?.data)
|
axios.post(endpoint, request?.data)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (request?.setLoading) request?.setLoading(false);
|
if (request?.setLoading) request?.setLoading(false);
|
||||||
if (request?.onSucccess) request.onSucccess(response);
|
if (request?.onSucccess) request.onSucccess(response);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (request?.setLoading) request?.setLoading(false);
|
if (request?.setLoading) request?.setLoading(false);
|
||||||
if (request?.showError) toast.error(error.message);
|
if (request?.showError) toast.error(error.message);
|
||||||
if (request?.onError) request.onError(error);
|
if (request?.onError) request.onError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function AxiosDelete({endpoint, request, title}: IAxiosRequest) {
|
async function AxiosDelete({ endpoint, request, title }: IAxiosRequest) {
|
||||||
if (title) console.log(`[[${title}]] is being deleted`);
|
if (title) console.log(`[[${title}]] is being deleted`);
|
||||||
if (request?.setLoading) request?.setLoading(true);
|
if (request?.setLoading) request?.setLoading(true);
|
||||||
axios.delete(endpoint)
|
axios.delete(endpoint)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (request?.setLoading) request?.setLoading(false);
|
if (request?.setLoading) request?.setLoading(false);
|
||||||
if (request?.onSucccess) request.onSucccess(response);
|
if (request?.onSucccess) request.onSucccess(response);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (request?.setLoading) request?.setLoading(false);
|
if (request?.setLoading) request?.setLoading(false);
|
||||||
if (request?.showError) toast.error(error.message);
|
if (request?.showError) toast.error(error.message);
|
||||||
if (request?.onError) request.onError(error);
|
if (request?.onError) request.onError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function AxiosPatch<ReturnType>({endpoint, request, title}: IAxiosRequest) {
|
async function AxiosPatch<ReturnType>({ endpoint, request, title }: IAxiosRequest) {
|
||||||
if (title) console.log(`[[${title}]] is being patrially updated`);
|
if (title) console.log(`[[${title}]] is being patrially updated`);
|
||||||
if (request?.setLoading) request?.setLoading(true);
|
if (request?.setLoading) request?.setLoading(true);
|
||||||
axios.patch<ReturnType>(endpoint, request?.data)
|
axios.patch<ReturnType>(endpoint, request?.data)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (request?.setLoading) request?.setLoading(false);
|
if (request?.setLoading) request?.setLoading(false);
|
||||||
if (request?.onSucccess) request.onSucccess(response);
|
if (request?.onSucccess) request.onSucccess(response);
|
||||||
return response.data;
|
return response.data;
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (request?.setLoading) request?.setLoading(false);
|
if (request?.setLoading) request?.setLoading(false);
|
||||||
if (request?.showError) toast.error(error.message);
|
if (request?.showError) toast.error(error.message);
|
||||||
if (request?.onError) request.onError(error);
|
if (request?.onError) request.onError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
// Constants
|
// Constants
|
||||||
const prod = {
|
const prod = {
|
||||||
url: {
|
url: {
|
||||||
BASE: 'http://rs.acconcept.ru:8000/api/',
|
BASE: 'http://rs.acconcept.ru:8000/api/',
|
||||||
AUTH: 'http://rs.acconcept.ru:8000/users/api/',
|
AUTH: 'http://rs.acconcept.ru:8000/users/api/'
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const dev = {
|
const dev = {
|
||||||
url: {
|
url: {
|
||||||
BASE: 'http://localhost:8000/api/',
|
BASE: 'http://localhost:8000/api/',
|
||||||
AUTH: 'http://localhost:8000/users/api/',
|
AUTH: 'http://localhost:8000/users/api/'
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const urls = {
|
export const urls = {
|
||||||
concept: 'https://www.acconcept.ru/',
|
concept: 'https://www.acconcept.ru/',
|
||||||
exteor32: 'https://drive.google.com/open?id=1IHlMMwaYlAUBRSxU1RU_hXM5mFU9-oyK&usp=drive_fs',
|
exteor32: 'https://drive.google.com/open?id=1IHlMMwaYlAUBRSxU1RU_hXM5mFU9-oyK&usp=drive_fs',
|
||||||
exteor64: 'https://drive.google.com/open?id=1IJt25ZRQ-ZMA6t7hOqmo5cv05WJCQKMv&usp=drive_fs',
|
exteor64: 'https://drive.google.com/open?id=1IJt25ZRQ-ZMA6t7hOqmo5cv05WJCQKMv&usp=drive_fs',
|
||||||
ponomarev: 'https://inponomarev.ru/textbook',
|
ponomarev: 'https://inponomarev.ru/textbook',
|
||||||
intro_video: 'https://www.youtube.com/watch?v=0Ty9mu9sOJo',
|
intro_video: 'https://www.youtube.com/watch?v=0Ty9mu9sOJo',
|
||||||
full_course: 'https://www.youtube.com/playlist?list=PLGe_JiAwpqu1C70ruQmCm_OWTWU3KJwDo',
|
full_course: 'https://www.youtube.com/playlist?list=PLGe_JiAwpqu1C70ruQmCm_OWTWU3KJwDo'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const config = process.env.NODE_ENV === 'production' ? prod : dev;
|
export const config = process.env.NODE_ENV === 'production' ? prod : dev;
|
||||||
|
|
|
@ -1,388 +1,352 @@
|
||||||
// Current user info
|
// Current user info
|
||||||
export interface ICurrentUser {
|
export interface ICurrentUser {
|
||||||
id: number
|
id: number
|
||||||
username: string
|
username: string
|
||||||
is_staff: boolean
|
is_staff: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// User profile data
|
// User profile data
|
||||||
export interface IUserProfile {
|
export interface IUserProfile {
|
||||||
id: number
|
id: number
|
||||||
username: string
|
username: string
|
||||||
email: string
|
email: string
|
||||||
first_name: string
|
first_name: string
|
||||||
last_name: string
|
last_name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// User base info
|
// User base info
|
||||||
export interface IUserInfo {
|
export interface IUserInfo {
|
||||||
id: number
|
id: number
|
||||||
username: string
|
username: string
|
||||||
first_name: string
|
first_name: string
|
||||||
last_name: string
|
last_name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// User data for signup
|
// User data for signup
|
||||||
export interface IUserSignupData {
|
export interface IUserSignupData {
|
||||||
username: string
|
username: string
|
||||||
email: string
|
email: string
|
||||||
first_name: string
|
first_name: string
|
||||||
last_name: string
|
last_name: string
|
||||||
password: string
|
password: string
|
||||||
password2: string
|
password2: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// User data for signup
|
// User data for signup
|
||||||
export interface INewCstData {
|
export interface INewCstData {
|
||||||
alias: string
|
alias: string
|
||||||
csttype: CstType
|
csttype: CstType
|
||||||
insert_after?: number
|
insert_after?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constituenta type
|
// Constituenta type
|
||||||
export enum CstType {
|
export enum CstType {
|
||||||
BASE = 'basic',
|
BASE = 'basic',
|
||||||
CONSTANT = 'constant',
|
CONSTANT = 'constant',
|
||||||
STRUCTURED = 'structure',
|
STRUCTURED = 'structure',
|
||||||
AXIOM = 'axiom',
|
AXIOM = 'axiom',
|
||||||
TERM = 'term',
|
TERM = 'term',
|
||||||
FUNCTION = 'function',
|
FUNCTION = 'function',
|
||||||
PREDICATE = 'predicate',
|
PREDICATE = 'predicate',
|
||||||
THEOREM = 'theorem'
|
THEOREM = 'theorem'
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValueClass
|
// ValueClass
|
||||||
export enum ValueClass {
|
export enum ValueClass {
|
||||||
INVALID = 'invalid',
|
INVALID = 'invalid',
|
||||||
VALUE = 'value',
|
VALUE = 'value',
|
||||||
PROPERTY = 'property'
|
PROPERTY = 'property'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Syntax
|
// Syntax
|
||||||
export enum Syntax {
|
export enum Syntax {
|
||||||
UNDEF = 'undefined',
|
UNDEF = 'undefined',
|
||||||
ASCII = 'ascii',
|
ASCII = 'ascii',
|
||||||
MATH = 'math'
|
MATH = 'math'
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParsingStatus
|
// ParsingStatus
|
||||||
export enum ParsingStatus {
|
export enum ParsingStatus {
|
||||||
UNDEF = 'undefined',
|
UNDEF = 'undefined',
|
||||||
VERIFIED = 'verified',
|
VERIFIED = 'verified',
|
||||||
INCORRECT = 'incorrect'
|
INCORRECT = 'incorrect'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constituenta data
|
// Constituenta data
|
||||||
export interface IConstituenta {
|
export interface IConstituenta {
|
||||||
id: number
|
id: number
|
||||||
alias: string
|
alias: string
|
||||||
cstType: CstType
|
cstType: CstType
|
||||||
convention?: string
|
convention?: string
|
||||||
term?: {
|
term?: {
|
||||||
raw: string
|
raw: string
|
||||||
resolved?: string
|
resolved?: string
|
||||||
forms?: string[]
|
forms?: string[]
|
||||||
}
|
}
|
||||||
definition?: {
|
definition?: {
|
||||||
formal: string
|
formal: string
|
||||||
text: {
|
text: {
|
||||||
raw: string
|
raw: string
|
||||||
resolved?: string
|
resolved?: string
|
||||||
}
|
|
||||||
}
|
|
||||||
parse?: {
|
|
||||||
status: ParsingStatus
|
|
||||||
valueClass: ValueClass
|
|
||||||
typification: string
|
|
||||||
syntaxTree: string
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
parse?: {
|
||||||
|
status: ParsingStatus
|
||||||
|
valueClass: ValueClass
|
||||||
|
typification: string
|
||||||
|
syntaxTree: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RSForm stats
|
// RSForm stats
|
||||||
export interface IRSFormStats {
|
export interface IRSFormStats {
|
||||||
count_all: number
|
count_all: number
|
||||||
count_errors: number
|
count_errors: number
|
||||||
count_property: number
|
count_property: number
|
||||||
count_incalc: number
|
count_incalc: number
|
||||||
|
|
||||||
count_termin: number
|
count_termin: number
|
||||||
|
|
||||||
count_base: number
|
count_base: number
|
||||||
count_constant: number
|
count_constant: number
|
||||||
count_structured: number
|
count_structured: number
|
||||||
count_axiom: number
|
count_axiom: number
|
||||||
count_term: number
|
count_term: number
|
||||||
count_function: number
|
count_function: number
|
||||||
count_predicate: number
|
count_predicate: number
|
||||||
count_theorem: number
|
count_theorem: number
|
||||||
}
|
}
|
||||||
|
|
||||||
// RSForm data
|
// RSForm data
|
||||||
export interface IRSForm {
|
export interface IRSForm {
|
||||||
id: number
|
id: number
|
||||||
title: string
|
title: string
|
||||||
alias: string
|
alias: string
|
||||||
comment: string
|
comment: string
|
||||||
is_common: boolean
|
is_common: boolean
|
||||||
time_create: string
|
time_create: string
|
||||||
time_update: string
|
time_update: string
|
||||||
owner?: number
|
owner?: number
|
||||||
items?: IConstituenta[]
|
items?: IConstituenta[]
|
||||||
stats?: IRSFormStats
|
stats?: IRSFormStats
|
||||||
}
|
}
|
||||||
|
|
||||||
// RSForm user input
|
// RSForm user input
|
||||||
export interface IRSFormCreateData {
|
export interface IRSFormCreateData {
|
||||||
title: string
|
title: string
|
||||||
alias: string
|
alias: string
|
||||||
comment: string
|
comment: string
|
||||||
is_common: boolean
|
is_common: boolean
|
||||||
file?: File
|
file?: File
|
||||||
}
|
}
|
||||||
|
|
||||||
//! RS language token types enumeration
|
//! RS language token types enumeration
|
||||||
export enum TokenID {
|
export enum TokenID {
|
||||||
// Global, local IDs and literals
|
// Global, local IDs and literals
|
||||||
ID_LOCAL = 258,
|
ID_LOCAL = 258,
|
||||||
ID_GLOBAL,
|
ID_GLOBAL,
|
||||||
ID_FUNCTION,
|
ID_FUNCTION,
|
||||||
ID_PREDICATE,
|
ID_PREDICATE,
|
||||||
ID_RADICAL,
|
ID_RADICAL,
|
||||||
LIT_INTEGER,
|
LIT_INTEGER,
|
||||||
LIT_INTSET,
|
LIT_INTSET,
|
||||||
LIT_EMPTYSET,
|
LIT_EMPTYSET,
|
||||||
|
|
||||||
// Aithmetic
|
// Aithmetic
|
||||||
PLUS,
|
PLUS,
|
||||||
MINUS,
|
MINUS,
|
||||||
MULTIPLY,
|
MULTIPLY,
|
||||||
|
|
||||||
// Integer predicate symbols
|
// Integer predicate symbols
|
||||||
GREATER,
|
GREATER,
|
||||||
LESSER,
|
LESSER,
|
||||||
GREATER_OR_EQ,
|
GREATER_OR_EQ,
|
||||||
LESSER_OR_EQ,
|
LESSER_OR_EQ,
|
||||||
|
|
||||||
// Equality comparison
|
// Equality comparison
|
||||||
EQUAL,
|
EQUAL,
|
||||||
NOTEQUAL,
|
NOTEQUAL,
|
||||||
|
|
||||||
// Logic predicate symbols
|
// Logic predicate symbols
|
||||||
FORALL,
|
FORALL,
|
||||||
EXISTS,
|
EXISTS,
|
||||||
NOT,
|
NOT,
|
||||||
EQUIVALENT,
|
EQUIVALENT,
|
||||||
IMPLICATION,
|
IMPLICATION,
|
||||||
OR,
|
OR,
|
||||||
AND,
|
AND,
|
||||||
|
|
||||||
// Set theory predicate symbols
|
// Set theory predicate symbols
|
||||||
IN,
|
IN,
|
||||||
NOTIN,
|
NOTIN,
|
||||||
SUBSET,
|
SUBSET,
|
||||||
SUBSET_OR_EQ,
|
SUBSET_OR_EQ,
|
||||||
NOTSUBSET,
|
NOTSUBSET,
|
||||||
|
|
||||||
// Set theory operators
|
// Set theory operators
|
||||||
DECART,
|
DECART,
|
||||||
UNION,
|
UNION,
|
||||||
INTERSECTION,
|
INTERSECTION,
|
||||||
SET_MINUS,
|
SET_MINUS,
|
||||||
SYMMINUS,
|
SYMMINUS,
|
||||||
BOOLEAN,
|
BOOLEAN,
|
||||||
|
|
||||||
// Structure operations
|
// Structure operations
|
||||||
BIGPR,
|
BIGPR,
|
||||||
SMALLPR,
|
SMALLPR,
|
||||||
FILTER,
|
FILTER,
|
||||||
CARD,
|
CARD,
|
||||||
BOOL,
|
BOOL,
|
||||||
DEBOOL,
|
DEBOOL,
|
||||||
REDUCE,
|
REDUCE,
|
||||||
|
|
||||||
// Term constructions prefixes
|
// Term constructions prefixes
|
||||||
DECLARATIVE,
|
DECLARATIVE,
|
||||||
RECURSIVE,
|
RECURSIVE,
|
||||||
IMPERATIVE,
|
IMPERATIVE,
|
||||||
|
|
||||||
// Punctuation
|
// Punctuation
|
||||||
PUNC_DEFINE,
|
PUNC_DEFINE,
|
||||||
PUNC_STRUCT,
|
PUNC_STRUCT,
|
||||||
PUNC_ASSIGN,
|
PUNC_ASSIGN,
|
||||||
PUNC_ITERATE,
|
PUNC_ITERATE,
|
||||||
PUNC_PL,
|
PUNC_PL,
|
||||||
PUNC_PR,
|
PUNC_PR,
|
||||||
PUNC_CL,
|
PUNC_CL,
|
||||||
PUNC_CR,
|
PUNC_CR,
|
||||||
PUNC_SL,
|
PUNC_SL,
|
||||||
PUNC_SR,
|
PUNC_SR,
|
||||||
PUNC_BAR,
|
PUNC_BAR,
|
||||||
PUNC_COMMA,
|
PUNC_COMMA,
|
||||||
PUNC_SEMICOLON,
|
PUNC_SEMICOLON,
|
||||||
|
|
||||||
// ======= Non-terminal tokens =========
|
// ======= Non-terminal tokens =========
|
||||||
NT_ENUM_DECL, // Перечисление переменных в кванторной декларации
|
NT_ENUM_DECL, // Перечисление переменных в кванторной декларации
|
||||||
NT_TUPLE, // Кортеж (a,b,c), типизация B(T(a)xT(b)xT(c))
|
NT_TUPLE, // Кортеж (a,b,c), типизация B(T(a)xT(b)xT(c))
|
||||||
NT_ENUMERATION, // Задание множества перечислением
|
NT_ENUMERATION, // Задание множества перечислением
|
||||||
NT_TUPLE_DECL, // Декларация переменных с помощью кортежа
|
NT_TUPLE_DECL, // Декларация переменных с помощью кортежа
|
||||||
NT_ARG_DECL, // Объявление аргумента
|
NT_ARG_DECL, // Объявление аргумента
|
||||||
|
|
||||||
NT_FUNC_DEFINITION, // Определение функции
|
NT_FUNC_DEFINITION, // Определение функции
|
||||||
NT_ARGUMENTS, // Задание аргументов функции
|
NT_ARGUMENTS, // Задание аргументов функции
|
||||||
NT_FUNC_CALL, // Вызов функции
|
NT_FUNC_CALL, // Вызов функции
|
||||||
|
|
||||||
NT_DECLARATIVE_EXPR, // Задание множества с помощью выражения D{x из H | A(x) }
|
NT_DECLARATIVE_EXPR, // Задание множества с помощью выражения D{x из H | A(x) }
|
||||||
NT_IMPERATIVE_EXPR, // Императивное определение
|
NT_IMPERATIVE_EXPR, // Императивное определение
|
||||||
NT_RECURSIVE_FULL, // Полная рекурсия
|
NT_RECURSIVE_FULL, // Полная рекурсия
|
||||||
NT_RECURSIVE_SHORT, // Сокращенная рекурсия
|
NT_RECURSIVE_SHORT, // Сокращенная рекурсия
|
||||||
|
|
||||||
NT_IMP_DECLARE, // Блок декларации
|
NT_IMP_DECLARE, // Блок декларации
|
||||||
NT_IMP_ASSIGN, // Блок присвоения
|
NT_IMP_ASSIGN, // Блок присвоения
|
||||||
NT_IMP_LOGIC, // Блок проверки
|
NT_IMP_LOGIC, // Блок проверки
|
||||||
|
|
||||||
// ======= Helper tokens ========
|
// ======= Helper tokens ========
|
||||||
INTERRUPT,
|
INTERRUPT,
|
||||||
END,
|
END,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Constituenta edit mode
|
// Constituenta edit mode
|
||||||
export enum EditMode {
|
export enum EditMode {
|
||||||
TEXT = 'text',
|
TEXT = 'text',
|
||||||
RSLANG = 'rslang'
|
RSLANG = 'rslang'
|
||||||
}
|
}
|
||||||
|
|
||||||
// RSExpression status
|
// RSExpression status
|
||||||
export enum ExpressionStatus {
|
export enum ExpressionStatus {
|
||||||
UNDEFINED = 0,
|
UNDEFINED = 0,
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
INCORRECT,
|
INCORRECT,
|
||||||
INCALCULABLE,
|
INCALCULABLE,
|
||||||
PROPERTY,
|
PROPERTY,
|
||||||
VERIFIED
|
VERIFIED
|
||||||
}
|
}
|
||||||
|
|
||||||
export function inferStatus(parse?: ParsingStatus, value?: ValueClass): ExpressionStatus {
|
export function inferStatus(parse?: ParsingStatus, value?: ValueClass): ExpressionStatus {
|
||||||
if (!parse || !value) {
|
if (!parse || !value) {
|
||||||
return ExpressionStatus.UNDEFINED;
|
return ExpressionStatus.UNDEFINED;
|
||||||
}
|
}
|
||||||
if (parse === ParsingStatus.UNDEF) {
|
if (parse === ParsingStatus.UNDEF) {
|
||||||
return ExpressionStatus.UNKNOWN;
|
return ExpressionStatus.UNKNOWN;
|
||||||
}
|
}
|
||||||
if (parse === ParsingStatus.INCORRECT) {
|
if (parse === ParsingStatus.INCORRECT) {
|
||||||
return ExpressionStatus.INCORRECT;
|
return ExpressionStatus.INCORRECT;
|
||||||
}
|
}
|
||||||
if (value === ValueClass.INVALID) {
|
if (value === ValueClass.INVALID) {
|
||||||
return ExpressionStatus.INCALCULABLE;
|
return ExpressionStatus.INCALCULABLE;
|
||||||
}
|
}
|
||||||
if (value === ValueClass.PROPERTY) {
|
if (value === ValueClass.PROPERTY) {
|
||||||
return ExpressionStatus.PROPERTY;
|
return ExpressionStatus.PROPERTY;
|
||||||
}
|
}
|
||||||
return ExpressionStatus.VERIFIED
|
return ExpressionStatus.VERIFIED
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CalculateStats(schema: IRSForm) {
|
export function CalculateStats(schema: IRSForm) {
|
||||||
if (!schema.items) {
|
if (!schema.items) {
|
||||||
schema.stats = {
|
|
||||||
count_all: 0,
|
|
||||||
count_errors: 0,
|
|
||||||
count_property: 0,
|
|
||||||
count_incalc: 0,
|
|
||||||
|
|
||||||
count_termin: 0,
|
|
||||||
|
|
||||||
count_base: 0,
|
|
||||||
count_constant: 0,
|
|
||||||
count_structured: 0,
|
|
||||||
count_axiom: 0,
|
|
||||||
count_term: 0,
|
|
||||||
count_function: 0,
|
|
||||||
count_predicate: 0,
|
|
||||||
count_theorem: 0,
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
schema.stats = {
|
schema.stats = {
|
||||||
count_all: schema.items?.length || 0,
|
count_all: 0,
|
||||||
count_errors: schema.items?.reduce(
|
count_errors: 0,
|
||||||
(sum, cst) => sum +
|
count_property: 0,
|
||||||
(cst.parse?.status === ParsingStatus.INCORRECT ? 1 : 0) || 0,
|
count_incalc: 0,
|
||||||
0
|
|
||||||
),
|
|
||||||
count_property: schema.items?.reduce(
|
|
||||||
(sum, cst) => sum +
|
|
||||||
(cst.parse?.valueClass === ValueClass.PROPERTY ? 1 : 0) || 0,
|
|
||||||
0
|
|
||||||
),
|
|
||||||
count_incalc: schema.items?.reduce(
|
|
||||||
(sum, cst) => sum +
|
|
||||||
((cst.parse?.status === ParsingStatus.VERIFIED &&
|
|
||||||
cst.parse?.valueClass === ValueClass.INVALID) ? 1 : 0) || 0,
|
|
||||||
0
|
|
||||||
),
|
|
||||||
|
|
||||||
count_termin: schema.items?.reduce(
|
count_termin: 0,
|
||||||
(sum, cst) => (sum +
|
|
||||||
(cst.term?.raw ? 1 : 0) || 0),
|
|
||||||
0
|
|
||||||
),
|
|
||||||
|
|
||||||
count_base: schema.items?.reduce(
|
count_base: 0,
|
||||||
(sum, cst) => sum +
|
count_constant: 0,
|
||||||
(cst.cstType === CstType.BASE ? 1 : 0),
|
count_structured: 0,
|
||||||
0
|
count_axiom: 0,
|
||||||
),
|
count_term: 0,
|
||||||
count_constant: schema.items?.reduce(
|
count_function: 0,
|
||||||
(sum, cst) => sum +
|
count_predicate: 0,
|
||||||
(cst.cstType === CstType.CONSTANT ? 1 : 0),
|
count_theorem: 0
|
||||||
0
|
|
||||||
),
|
|
||||||
count_structured: schema.items?.reduce(
|
|
||||||
(sum, cst) => sum +
|
|
||||||
(cst.cstType === CstType.STRUCTURED ? 1 : 0),
|
|
||||||
0
|
|
||||||
),
|
|
||||||
count_axiom: schema.items?.reduce(
|
|
||||||
(sum, cst) => sum +
|
|
||||||
(cst.cstType === CstType.AXIOM ? 1 : 0),
|
|
||||||
0
|
|
||||||
),
|
|
||||||
count_term: schema.items?.reduce(
|
|
||||||
(sum, cst) => sum +
|
|
||||||
(cst.cstType === CstType.TERM ? 1 : 0),
|
|
||||||
0
|
|
||||||
),
|
|
||||||
count_function: schema.items?.reduce(
|
|
||||||
(sum, cst) => sum +
|
|
||||||
(cst.cstType === CstType.FUNCTION ? 1 : 0),
|
|
||||||
0
|
|
||||||
),
|
|
||||||
count_predicate: schema.items?.reduce(
|
|
||||||
(sum, cst) => sum +
|
|
||||||
(cst.cstType === CstType.PREDICATE ? 1 : 0),
|
|
||||||
0
|
|
||||||
),
|
|
||||||
count_theorem: schema.items?.reduce(
|
|
||||||
(sum, cst) => sum +
|
|
||||||
(cst.cstType === CstType.THEOREM ? 1 : 0),
|
|
||||||
0
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
schema.stats = {
|
||||||
|
count_all: schema.items?.length || 0,
|
||||||
|
count_errors: schema.items?.reduce(
|
||||||
|
(sum, cst) => sum + (cst.parse?.status === ParsingStatus.INCORRECT ? 1 : 0) || 0, 0),
|
||||||
|
count_property: schema.items?.reduce(
|
||||||
|
(sum, cst) => sum + (cst.parse?.valueClass === ValueClass.PROPERTY ? 1 : 0) || 0, 0),
|
||||||
|
count_incalc: schema.items?.reduce(
|
||||||
|
(sum, cst) => sum +
|
||||||
|
((cst.parse?.status === ParsingStatus.VERIFIED && cst.parse?.valueClass === ValueClass.INVALID) ? 1 : 0) || 0, 0),
|
||||||
|
|
||||||
|
count_termin: schema.items?.reduce(
|
||||||
|
(sum, cst) => (sum + (cst.term?.raw ? 1 : 0) || 0), 0),
|
||||||
|
|
||||||
|
count_base: schema.items?.reduce(
|
||||||
|
(sum, cst) => sum + (cst.cstType === CstType.BASE ? 1 : 0), 0),
|
||||||
|
count_constant: schema.items?.reduce(
|
||||||
|
(sum, cst) => sum + (cst.cstType === CstType.CONSTANT ? 1 : 0), 0),
|
||||||
|
count_structured: schema.items?.reduce(
|
||||||
|
(sum, cst) => sum + (cst.cstType === CstType.STRUCTURED ? 1 : 0), 0),
|
||||||
|
count_axiom: schema.items?.reduce(
|
||||||
|
(sum, cst) => sum + (cst.cstType === CstType.AXIOM ? 1 : 0), 0),
|
||||||
|
count_term: schema.items?.reduce(
|
||||||
|
(sum, cst) => sum + (cst.cstType === CstType.TERM ? 1 : 0), 0),
|
||||||
|
count_function: schema.items?.reduce(
|
||||||
|
(sum, cst) => sum + (cst.cstType === CstType.FUNCTION ? 1 : 0), 0),
|
||||||
|
count_predicate: schema.items?.reduce(
|
||||||
|
(sum, cst) => sum + (cst.cstType === CstType.PREDICATE ? 1 : 0), 0),
|
||||||
|
count_theorem: schema.items?.reduce(
|
||||||
|
(sum, cst) => sum + (cst.cstType === CstType.THEOREM ? 1 : 0), 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function matchConstituenta(query: string, target?: IConstituenta) {
|
export function matchConstituenta(query: string, target?: IConstituenta) {
|
||||||
if (!target) {
|
if (!target) {
|
||||||
return false;
|
return false;
|
||||||
} else if (target.alias.match(query)) {
|
} else if (target.alias.match(query)) {
|
||||||
return true;
|
return true;
|
||||||
} else if (target.term?.resolved?.match(query)) {
|
} else if (target.term?.resolved?.match(query)) {
|
||||||
return true;
|
return true;
|
||||||
} else if (target.definition?.formal.match(query)) {
|
} else if (target.definition?.formal.match(query)) {
|
||||||
return true;
|
return true;
|
||||||
} else if (target.definition?.text.resolved?.match(query)) {
|
} else if (target.definition?.text.resolved?.match(query)) {
|
||||||
return true;
|
return true;
|
||||||
} else if (target.convention?.match(query)) {
|
} else if (target.convention?.match(query)) {
|
||||||
return true;
|
return true;
|
||||||
}else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import { BackendCallback } from './backendAPI';
|
|
||||||
import fileDownload from 'js-file-download';
|
import fileDownload from 'js-file-download';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import { type BackendCallback } from './backendAPI';
|
||||||
|
|
||||||
export function shareCurrentURLProc() {
|
export function shareCurrentURLProc() {
|
||||||
const url = window.location.href + '&share';
|
const url = window.location.href + '&share';
|
||||||
navigator.clipboard.writeText(url);
|
navigator.clipboard.writeText(url)
|
||||||
toast.success(`Ссылка скопирована: ${url}`);
|
.then(() => toast.success(`Ссылка скопирована: ${url}`))
|
||||||
|
.catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function claimOwnershipProc(
|
export function claimOwnershipProc(
|
||||||
claim: (callback: BackendCallback) => Promise<void>,
|
claim: (callback: BackendCallback) => void
|
||||||
) {
|
) {
|
||||||
if (!window.confirm('Вы уверены, что хотите стать владельцем данной схемы?')) {
|
if (!window.confirm('Вы уверены, что хотите стать владельцем данной схемы?')) {
|
||||||
return;
|
return;
|
||||||
|
@ -17,9 +19,9 @@ export async function claimOwnershipProc(
|
||||||
claim(() => toast.success('Вы стали владельцем схемы'));
|
claim(() => toast.success('Вы стали владельцем схемы'));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteRSFormProc(
|
export function deleteRSFormProc(
|
||||||
destroy: (callback: BackendCallback) => Promise<void>,
|
destroy: (callback: BackendCallback) => void,
|
||||||
navigate: Function
|
navigate: (path: string) => void
|
||||||
) {
|
) {
|
||||||
if (!window.confirm('Вы уверены, что хотите удалить данную схему?')) {
|
if (!window.confirm('Вы уверены, что хотите удалить данную схему?')) {
|
||||||
return;
|
return;
|
||||||
|
@ -30,8 +32,8 @@ export async function deleteRSFormProc(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadRSFormProc(
|
export function downloadRSFormProc(
|
||||||
download: (callback: BackendCallback) => Promise<void>,
|
download: (callback: BackendCallback) => void,
|
||||||
fileName: string
|
fileName: string
|
||||||
) {
|
) {
|
||||||
download((response) => {
|
download((response) => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { CstType, ExpressionStatus, IConstituenta, IRSForm, ParsingStatus, TokenID } from './models';
|
import { CstType, ExpressionStatus, type IConstituenta, type IRSForm, ParsingStatus, TokenID } from './models';
|
||||||
|
|
||||||
export interface IRSButtonData {
|
export interface IRSButtonData {
|
||||||
text: string
|
text: string
|
||||||
|
@ -13,177 +13,177 @@ export interface IStatusInfo {
|
||||||
|
|
||||||
export function getTypeLabel(cst: IConstituenta) {
|
export function getTypeLabel(cst: IConstituenta) {
|
||||||
if (cst.parse?.typification) {
|
if (cst.parse?.typification) {
|
||||||
return cst.parse.typification;
|
return cst.parse.typification;
|
||||||
}
|
}
|
||||||
if (cst.parse?.status !== ParsingStatus.VERIFIED) {
|
if (cst.parse?.status !== ParsingStatus.VERIFIED) {
|
||||||
return 'N/A';
|
return 'N/A';
|
||||||
}
|
}
|
||||||
return 'Логический';
|
return 'Логический';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRSButtonData(id: TokenID): IRSButtonData {
|
export function getRSButtonData(id: TokenID): IRSButtonData {
|
||||||
switch(id) {
|
switch (id) {
|
||||||
case TokenID.BOOLEAN: return {
|
case TokenID.BOOLEAN: return {
|
||||||
text: 'ℬ()',
|
text: 'ℬ()',
|
||||||
tooltip: 'Булеан [Alt + E]',
|
tooltip: 'Булеан [Alt + E]'
|
||||||
};
|
};
|
||||||
case TokenID.DECART: return {
|
case TokenID.DECART: return {
|
||||||
text: '×',
|
text: '×',
|
||||||
tooltip: 'Декартово произведение [Shift + 8]',
|
tooltip: 'Декартово произведение [Shift + 8]'
|
||||||
};
|
};
|
||||||
case TokenID.PUNC_PL: return {
|
case TokenID.PUNC_PL: return {
|
||||||
text: '( )',
|
text: '( )',
|
||||||
tooltip: 'Скобки вокруг выражения [ Alt + Shift + 9 ]',
|
tooltip: 'Скобки вокруг выражения [ Alt + Shift + 9 ]'
|
||||||
};
|
};
|
||||||
case TokenID.PUNC_SL: return {
|
case TokenID.PUNC_SL: return {
|
||||||
text: '[ ]',
|
text: '[ ]',
|
||||||
tooltip: 'Скобки вокруг выражения [ Alt + [ ]',
|
tooltip: 'Скобки вокруг выражения [ Alt + [ ]'
|
||||||
};
|
};
|
||||||
case TokenID.FORALL: return {
|
case TokenID.FORALL: return {
|
||||||
text: '∀',
|
text: '∀',
|
||||||
tooltip: 'Квантор всеобщности [`]',
|
tooltip: 'Квантор всеобщности [`]'
|
||||||
};
|
};
|
||||||
case TokenID.EXISTS: return {
|
case TokenID.EXISTS: return {
|
||||||
text: '∃',
|
text: '∃',
|
||||||
tooltip: 'Квантор существования [Shift + `]',
|
tooltip: 'Квантор существования [Shift + `]'
|
||||||
};
|
};
|
||||||
case TokenID.NOT: return {
|
case TokenID.NOT: return {
|
||||||
text: '¬',
|
text: '¬',
|
||||||
tooltip: 'Отрицание [Alt + `]',
|
tooltip: 'Отрицание [Alt + `]'
|
||||||
};
|
};
|
||||||
case TokenID.AND: return {
|
case TokenID.AND: return {
|
||||||
text: '&',
|
text: '&',
|
||||||
tooltip: 'Конъюнкция [Alt + 3 ~ Shift + 7]',
|
tooltip: 'Конъюнкция [Alt + 3 ~ Shift + 7]'
|
||||||
};
|
};
|
||||||
case TokenID.OR: return {
|
case TokenID.OR: return {
|
||||||
text: '∨',
|
text: '∨',
|
||||||
tooltip: 'дизъюнкция [Alt + Shift + 3]',
|
tooltip: 'дизъюнкция [Alt + Shift + 3]'
|
||||||
};
|
};
|
||||||
case TokenID.IMPLICATION: return {
|
case TokenID.IMPLICATION: return {
|
||||||
text: '⇒',
|
text: '⇒',
|
||||||
tooltip: 'импликация [Alt + 4]',
|
tooltip: 'импликация [Alt + 4]'
|
||||||
};
|
};
|
||||||
case TokenID.EQUIVALENT: return {
|
case TokenID.EQUIVALENT: return {
|
||||||
text: '⇔',
|
text: '⇔',
|
||||||
tooltip: 'эквивалентность [Alt + Shift + 4]',
|
tooltip: 'эквивалентность [Alt + Shift + 4]'
|
||||||
};
|
};
|
||||||
case TokenID.LIT_EMPTYSET: return {
|
case TokenID.LIT_EMPTYSET: return {
|
||||||
text: '∅',
|
text: '∅',
|
||||||
tooltip: 'пустое множество [Alt + X]',
|
tooltip: 'пустое множество [Alt + X]'
|
||||||
};
|
};
|
||||||
case TokenID.LIT_INTSET: return {
|
case TokenID.LIT_INTSET: return {
|
||||||
text: 'Z',
|
text: 'Z',
|
||||||
tooltip: 'целые числа [Alt + Z]',
|
tooltip: 'целые числа [Alt + Z]'
|
||||||
};
|
};
|
||||||
case TokenID.EQUAL: return {
|
case TokenID.EQUAL: return {
|
||||||
text: '=',
|
text: '=',
|
||||||
tooltip: 'равенство',
|
tooltip: 'равенство'
|
||||||
};
|
};
|
||||||
case TokenID.NOTEQUAL: return {
|
case TokenID.NOTEQUAL: return {
|
||||||
text: '≠',
|
text: '≠',
|
||||||
tooltip: 'неравенство [Alt + Shift + `]',
|
tooltip: 'неравенство [Alt + Shift + `]'
|
||||||
};
|
};
|
||||||
case TokenID.GREATER_OR_EQ: return {
|
case TokenID.GREATER_OR_EQ: return {
|
||||||
text: '≥',
|
text: '≥',
|
||||||
tooltip: 'больше или равно',
|
tooltip: 'больше или равно'
|
||||||
};
|
};
|
||||||
case TokenID.LESSER_OR_EQ: return {
|
case TokenID.LESSER_OR_EQ: return {
|
||||||
text: '≤',
|
text: '≤',
|
||||||
tooltip: 'меньше или равно',
|
tooltip: 'меньше или равно'
|
||||||
};
|
};
|
||||||
case TokenID.IN: return {
|
case TokenID.IN: return {
|
||||||
text: '∈',
|
text: '∈',
|
||||||
tooltip: 'быть элементом (принадлежит) [Alt + \']',
|
tooltip: 'быть элементом (принадлежит) [Alt + \']'
|
||||||
};
|
};
|
||||||
case TokenID.NOTIN: return {
|
case TokenID.NOTIN: return {
|
||||||
text: '∉',
|
text: '∉',
|
||||||
tooltip: 'не принадлежит [Alt + Shift + \']',
|
tooltip: 'не принадлежит [Alt + Shift + \']'
|
||||||
};
|
};
|
||||||
case TokenID.SUBSET_OR_EQ: return {
|
case TokenID.SUBSET_OR_EQ: return {
|
||||||
text: '⊆',
|
text: '⊆',
|
||||||
tooltip: 'быть частью (нестрогое подмножество) [Alt + 2]',
|
tooltip: 'быть частью (нестрогое подмножество) [Alt + 2]'
|
||||||
};
|
};
|
||||||
case TokenID.SUBSET: return {
|
case TokenID.SUBSET: return {
|
||||||
text: '⊂',
|
text: '⊂',
|
||||||
tooltip: 'строгое подмножество [Alt + ;]',
|
tooltip: 'строгое подмножество [Alt + ;]'
|
||||||
};
|
};
|
||||||
case TokenID.NOTSUBSET: return {
|
case TokenID.NOTSUBSET: return {
|
||||||
text: '⊄',
|
text: '⊄',
|
||||||
tooltip: 'не подмножество [Alt + Shift + 2]',
|
tooltip: 'не подмножество [Alt + Shift + 2]'
|
||||||
};
|
};
|
||||||
case TokenID.INTERSECTION: return {
|
case TokenID.INTERSECTION: return {
|
||||||
text: '∩',
|
text: '∩',
|
||||||
tooltip: 'пересечение [Alt + Y]',
|
tooltip: 'пересечение [Alt + Y]'
|
||||||
};
|
};
|
||||||
case TokenID.UNION: return {
|
case TokenID.UNION: return {
|
||||||
text: '∪',
|
text: '∪',
|
||||||
tooltip: 'объединение [Alt + U]',
|
tooltip: 'объединение [Alt + U]'
|
||||||
};
|
};
|
||||||
case TokenID.SET_MINUS: return {
|
case TokenID.SET_MINUS: return {
|
||||||
text: '\\',
|
text: '\\',
|
||||||
tooltip: 'Разность множеств [Alt + 5]',
|
tooltip: 'Разность множеств [Alt + 5]'
|
||||||
};
|
};
|
||||||
case TokenID.SYMMINUS: return {
|
case TokenID.SYMMINUS: return {
|
||||||
text: '∆',
|
text: '∆',
|
||||||
tooltip: 'Симметрическая разность [Alt + Shift + 5]',
|
tooltip: 'Симметрическая разность [Alt + Shift + 5]'
|
||||||
};
|
};
|
||||||
case TokenID.NT_DECLARATIVE_EXPR: return {
|
case TokenID.NT_DECLARATIVE_EXPR: return {
|
||||||
text: 'D{}',
|
text: 'D{}',
|
||||||
tooltip: 'Декларативная форма определения терма [Alt + D]',
|
tooltip: 'Декларативная форма определения терма [Alt + D]'
|
||||||
};
|
};
|
||||||
case TokenID.NT_IMPERATIVE_EXPR: return {
|
case TokenID.NT_IMPERATIVE_EXPR: return {
|
||||||
text: 'I{}',
|
text: 'I{}',
|
||||||
tooltip: 'императивная форма определения терма [Alt + G]',
|
tooltip: 'императивная форма определения терма [Alt + G]'
|
||||||
};
|
};
|
||||||
case TokenID.NT_RECURSIVE_FULL: return {
|
case TokenID.NT_RECURSIVE_FULL: return {
|
||||||
text: 'R{}',
|
text: 'R{}',
|
||||||
tooltip: 'рекурсивная (цикличная) форма определения терма [Alt + T]',
|
tooltip: 'рекурсивная (цикличная) форма определения терма [Alt + T]'
|
||||||
};
|
};
|
||||||
case TokenID.BIGPR: return {
|
case TokenID.BIGPR: return {
|
||||||
text: 'Pr1()',
|
text: 'Pr1()',
|
||||||
tooltip: 'большая проекция [Alt + Q]',
|
tooltip: 'большая проекция [Alt + Q]'
|
||||||
};
|
};
|
||||||
case TokenID.SMALLPR: return {
|
case TokenID.SMALLPR: return {
|
||||||
text: 'pr1()',
|
text: 'pr1()',
|
||||||
tooltip: 'малая проекция [Alt + W]',
|
tooltip: 'малая проекция [Alt + W]'
|
||||||
};
|
};
|
||||||
case TokenID.FILTER: return {
|
case TokenID.FILTER: return {
|
||||||
text: 'Fi1[]()',
|
text: 'Fi1[]()',
|
||||||
tooltip: 'фильтр [Alt + F]',
|
tooltip: 'фильтр [Alt + F]'
|
||||||
};
|
};
|
||||||
case TokenID.REDUCE: return {
|
case TokenID.REDUCE: return {
|
||||||
text: 'red()',
|
text: 'red()',
|
||||||
tooltip: 'множество-сумма [Alt + R]',
|
tooltip: 'множество-сумма [Alt + R]'
|
||||||
};
|
};
|
||||||
case TokenID.CARD: return {
|
case TokenID.CARD: return {
|
||||||
text: 'card()',
|
text: 'card()',
|
||||||
tooltip: 'мощность [Alt + C]',
|
tooltip: 'мощность [Alt + C]'
|
||||||
};
|
};
|
||||||
case TokenID.BOOL: return {
|
case TokenID.BOOL: return {
|
||||||
text: 'bool()',
|
text: 'bool()',
|
||||||
tooltip: 'синглетон [Alt + B]',
|
tooltip: 'синглетон [Alt + B]'
|
||||||
};
|
};
|
||||||
case TokenID.DEBOOL: return {
|
case TokenID.DEBOOL: return {
|
||||||
text: 'debool()',
|
text: 'debool()',
|
||||||
tooltip: 'десинглетон [Alt + V]',
|
tooltip: 'десинглетон [Alt + V]'
|
||||||
};
|
};
|
||||||
case TokenID.PUNC_ASSIGN: return {
|
case TokenID.PUNC_ASSIGN: return {
|
||||||
text: ':=',
|
text: ':=',
|
||||||
tooltip: 'присвоение (императивный синтаксис)',
|
tooltip: 'присвоение (императивный синтаксис)'
|
||||||
};
|
};
|
||||||
case TokenID.PUNC_ITERATE: return {
|
case TokenID.PUNC_ITERATE: return {
|
||||||
text: ':∈',
|
text: ':∈',
|
||||||
tooltip: 'перебор элементов множества (императивный синтаксис)',
|
tooltip: 'перебор элементов множества (императивный синтаксис)'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
text: 'undefined',
|
text: 'undefined',
|
||||||
tooltip: 'undefined',
|
tooltip: 'undefined'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCstTypeLabel(type: CstType) {
|
export function getCstTypeLabel(type: CstType) {
|
||||||
switch(type) {
|
switch (type) {
|
||||||
case CstType.BASE: return 'Базисное множество';
|
case CstType.BASE: return 'Базисное множество';
|
||||||
case CstType.CONSTANT: return 'Константное множество';
|
case CstType.CONSTANT: return 'Константное множество';
|
||||||
case CstType.STRUCTURED: return 'Родовая структура';
|
case CstType.STRUCTURED: return 'Родовая структура';
|
||||||
|
@ -198,11 +198,11 @@ export function getCstTypeLabel(type: CstType) {
|
||||||
export const CstTypeSelector = (Object.values(CstType)).map(
|
export const CstTypeSelector = (Object.values(CstType)).map(
|
||||||
(typeStr) => {
|
(typeStr) => {
|
||||||
const type = typeStr as CstType;
|
const type = typeStr as CstType;
|
||||||
return {value: type, label: getCstTypeLabel(type)};
|
return { value: type, label: getCstTypeLabel(type) };
|
||||||
});
|
});
|
||||||
|
|
||||||
export function getCstTypePrefix(type: CstType) {
|
export function getCstTypePrefix(type: CstType) {
|
||||||
switch(type) {
|
switch (type) {
|
||||||
case CstType.BASE: return 'X';
|
case CstType.BASE: return 'X';
|
||||||
case CstType.CONSTANT: return 'C';
|
case CstType.CONSTANT: return 'C';
|
||||||
case CstType.STRUCTURED: return 'S';
|
case CstType.STRUCTURED: return 'S';
|
||||||
|
@ -255,11 +255,11 @@ export function getStatusInfo(status?: ExpressionStatus): IStatusInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractGlobals(expression: string): Set<string> {
|
export function extractGlobals(expression: string): Set<string> {
|
||||||
return new Set(expression.match(/[XCSADFPT]\d+/g) || []);
|
return new Set(expression.match(/[XCSADFPT]\d+/g) ?? []);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createAliasFor(type: CstType, schema: IRSForm): string {
|
export function createAliasFor(type: CstType, schema: IRSForm): string {
|
||||||
let prefix = getCstTypePrefix(type);
|
const prefix = getCstTypePrefix(type);
|
||||||
if (!schema.items || schema.items.length <= 0) {
|
if (!schema.items || schema.items.length <= 0) {
|
||||||
return `${prefix}1`;
|
return `${prefix}1`;
|
||||||
}
|
}
|
||||||
|
@ -271,4 +271,4 @@ export function createAliasFor(type: CstType, schema: IRSForm): string {
|
||||||
return Math.max(prev, index);
|
return Math.max(prev, index);
|
||||||
}, 1);
|
}, 1);
|
||||||
return `${prefix}${index}`;
|
return `${prefix}${index}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
export function assertIsNode(e: EventTarget | null): asserts e is Node {
|
export function assertIsNode(e: EventTarget | null): asserts e is Node {
|
||||||
if (!e || !('nodeType' in e)) {
|
if (!e || !('nodeType' in e)) {
|
||||||
throw new Error(`Node expected`);
|
throw new Error('Node expected');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function delay(ms: number) {
|
export async function delay(ms: number) {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return await new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user