mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
Move to Vite. Refactor type system for data transport
This commit is contained in:
parent
a669f70efc
commit
20bea9c067
|
@ -10,7 +10,8 @@ function RunServer() {
|
|||
RunBackend
|
||||
RunFrontend
|
||||
Start-Sleep -Seconds 1
|
||||
Start-Process "http://127.0.0.1:8000/"
|
||||
Start-Process "http://localhost:8000/"
|
||||
Start-Process "http://localhost:3000/"
|
||||
}
|
||||
|
||||
function RunBackend() {
|
||||
|
@ -29,7 +30,8 @@ function RunBackend() {
|
|||
|
||||
function RunFrontend() {
|
||||
Set-Location $PSScriptRoot\frontend
|
||||
Invoke-Expression "cmd /c start powershell -Command { `$Host.UI.RawUI.WindowTitle = 'react'; & npm run start }"
|
||||
& npm install
|
||||
Invoke-Expression "cmd /c start powershell -Command { `$Host.UI.RawUI.WindowTitle = 'react'; & npm run dev }"
|
||||
}
|
||||
|
||||
function FlushData {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# Application settings
|
||||
SECRET_KEY=django-insecure-)rq@!&v7l2r%2%q#n!uq+zk@=&yc0^&ql^7%2!%9u)vt1x&j=d
|
||||
ALLOWED_HOSTS=rs.acconcept.ru;localhost
|
||||
CSRF_TRUSTED_ORIGINS=http://rs.acconcept.ru:3000;localhost:3000
|
||||
CORS_ALLOWED_ORIGINS=http://rs.acconcept.ru:3000;localhost:3000
|
||||
|
||||
|
||||
# File locations
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 4.2.3 on 2023-07-24 19:06
|
||||
# Generated by Django 4.2.3 on 2023-07-26 15:19
|
||||
|
||||
import apps.rsform.models
|
||||
from django.conf import settings
|
||||
|
@ -39,7 +39,7 @@ class Migration(migrations.Migration):
|
|||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('order', models.PositiveIntegerField(default=-1, validators=[django.core.validators.MinValueValidator(1)], verbose_name='Позиция')),
|
||||
('alias', models.CharField(default='undefined', max_length=8, verbose_name='Имя')),
|
||||
('csttype', models.CharField(choices=[('basic', 'Base'), ('constant', 'Constant'), ('structure', 'Structured'), ('axiom', 'Axiom'), ('term', 'Term'), ('function', 'Function'), ('predicate', 'Predicate'), ('theorem', 'Theorem')], default='basic', max_length=10, verbose_name='Тип')),
|
||||
('cst_type', models.CharField(choices=[('basic', 'Base'), ('constant', 'Constant'), ('structure', 'Structured'), ('axiom', 'Axiom'), ('term', 'Term'), ('function', 'Function'), ('predicate', 'Predicate'), ('theorem', 'Theorem')], default='basic', max_length=10, verbose_name='Тип')),
|
||||
('convention', models.TextField(blank=True, default='', verbose_name='Комментарий/Конвенция')),
|
||||
('term_raw', models.TextField(blank=True, default='', verbose_name='Термин (с отсылками)')),
|
||||
('term_resolved', models.TextField(blank=True, default='', verbose_name='Термин')),
|
||||
|
|
|
@ -86,11 +86,12 @@ class RSForm(models.Model):
|
|||
schema=self,
|
||||
order=position,
|
||||
alias=alias,
|
||||
csttype=type
|
||||
cst_type=type
|
||||
)
|
||||
self._update_from_core()
|
||||
self.save()
|
||||
return Constituenta.objects.get(pk=result.pk)
|
||||
result.refresh_from_db()
|
||||
return result
|
||||
|
||||
@transaction.atomic
|
||||
def insert_last(self, alias: str, type: CstType) -> 'Constituenta':
|
||||
|
@ -102,7 +103,7 @@ class RSForm(models.Model):
|
|||
schema=self,
|
||||
order=position,
|
||||
alias=alias,
|
||||
csttype=type
|
||||
cst_type=type
|
||||
)
|
||||
self._update_from_core()
|
||||
self.save()
|
||||
|
@ -217,7 +218,7 @@ class Constituenta(models.Model):
|
|||
max_length=8,
|
||||
default='undefined'
|
||||
)
|
||||
csttype = models.CharField(
|
||||
cst_type = models.CharField(
|
||||
verbose_name='Тип',
|
||||
max_length=10,
|
||||
choices=CstType.choices,
|
||||
|
@ -274,7 +275,7 @@ class Constituenta(models.Model):
|
|||
alias=data['alias'],
|
||||
schema=schema,
|
||||
order=order,
|
||||
csttype=data['cstType'],
|
||||
cst_type=data['cstType'],
|
||||
convention=data.get('convention', 'Без названия')
|
||||
)
|
||||
if 'definition' in data:
|
||||
|
@ -284,9 +285,9 @@ class Constituenta(models.Model):
|
|||
cst.definition_raw = data['definition']['text'].get('raw', '')
|
||||
cst.definition_resolved = data['definition']['text'].get('resolved', '')
|
||||
if 'term' in data:
|
||||
cst.term_raw = data['definition']['text'].get('raw', '')
|
||||
cst.term_resolved = data['definition']['text'].get('resolved', '')
|
||||
cst.term_forms = data['definition']['text'].get('forms', [])
|
||||
cst.term_raw = data['term'].get('raw', '')
|
||||
cst.term_resolved = data['term'].get('resolved', '')
|
||||
cst.term_forms = data['term'].get('forms', [])
|
||||
cst.save()
|
||||
return cst
|
||||
|
||||
|
@ -294,7 +295,7 @@ class Constituenta(models.Model):
|
|||
return {
|
||||
'entityUID': self.id,
|
||||
'type': 'constituenta',
|
||||
'cstType': self.csttype,
|
||||
'cstType': self.cst_type,
|
||||
'alias': self.alias,
|
||||
'convention': self.convention,
|
||||
'term': {
|
||||
|
|
|
@ -24,7 +24,7 @@ class ConstituentaSerializer(serializers.ModelSerializer):
|
|||
class Meta:
|
||||
model = Constituenta
|
||||
fields = '__all__'
|
||||
read_only_fields = ('id', 'order', 'alias', 'csttype')
|
||||
read_only_fields = ('id', 'order', 'alias', 'cst_type')
|
||||
|
||||
def update(self, instance: Constituenta, validated_data):
|
||||
instance.schema.save()
|
||||
|
@ -48,8 +48,8 @@ class StandaloneCstSerializer(serializers.ModelSerializer):
|
|||
|
||||
class CstCreateSerializer(serializers.Serializer):
|
||||
alias = serializers.CharField(max_length=8)
|
||||
csttype = serializers.CharField(max_length=10)
|
||||
insert_after = serializers.IntegerField(required=False)
|
||||
cst_type = serializers.CharField(max_length=10)
|
||||
insert_after = serializers.IntegerField(required=False, allow_null=True)
|
||||
|
||||
|
||||
class CstListSerlializer(serializers.Serializer):
|
||||
|
|
|
@ -53,7 +53,7 @@ class TestConstituenta(TestCase):
|
|||
self.assertEqual(cst.schema, self.schema1)
|
||||
self.assertEqual(cst.order, 1)
|
||||
self.assertEqual(cst.alias, 'X1')
|
||||
self.assertEqual(cst.csttype, CstType.BASE)
|
||||
self.assertEqual(cst.cst_type, CstType.BASE)
|
||||
self.assertEqual(cst.convention, '')
|
||||
self.assertEqual(cst.definition_formal, '')
|
||||
self.assertEqual(cst.term_raw, '')
|
||||
|
|
|
@ -174,14 +174,14 @@ class TestRSFormViewset(APITestCase):
|
|||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_create_constituenta(self):
|
||||
data = json.dumps({'alias': 'X3', 'csttype': 'basic'})
|
||||
data = json.dumps({'alias': 'X3', 'cst_type': 'basic'})
|
||||
response = self.client.post(f'/api/rsforms/{self.rsform_unowned.id}/cst-create/',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
schema = self.rsform_owned
|
||||
Constituenta.objects.create(schema=schema, alias='X1', csttype='basic', order=1)
|
||||
x2 = Constituenta.objects.create(schema=schema, alias='X2', csttype='basic', order=2)
|
||||
Constituenta.objects.create(schema=schema, alias='X1', cst_type='basic', order=1)
|
||||
x2 = Constituenta.objects.create(schema=schema, alias='X2', cst_type='basic', order=2)
|
||||
response = self.client.post(f'/api/rsforms/{schema.id}/cst-create/',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 201)
|
||||
|
@ -189,7 +189,7 @@ class TestRSFormViewset(APITestCase):
|
|||
x3 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
|
||||
self.assertEqual(x3.order, 3)
|
||||
|
||||
data = json.dumps({'alias': 'X4', 'csttype': 'basic', 'insert_after': x2.id})
|
||||
data = json.dumps({'alias': 'X4', 'cst_type': 'basic', 'insert_after': x2.id})
|
||||
response = self.client.post(f'/api/rsforms/{schema.id}/cst-create/',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 201)
|
||||
|
@ -204,8 +204,8 @@ class TestRSFormViewset(APITestCase):
|
|||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
x1 = Constituenta.objects.create(schema=schema, alias='X1', csttype='basic', order=1)
|
||||
x2 = Constituenta.objects.create(schema=schema, alias='X2', csttype='basic', order=2)
|
||||
x1 = Constituenta.objects.create(schema=schema, alias='X1', cst_type='basic', order=1)
|
||||
x2 = Constituenta.objects.create(schema=schema, alias='X2', cst_type='basic', order=2)
|
||||
data = json.dumps({'items': [{'id': x1.id}]})
|
||||
response = self.client.patch(f'/api/rsforms/{schema.id}/cst-multidelete/',
|
||||
data=data, content_type='application/json')
|
||||
|
@ -217,7 +217,7 @@ class TestRSFormViewset(APITestCase):
|
|||
self.assertEqual(x2.alias, 'X2')
|
||||
self.assertEqual(x2.order, 1)
|
||||
|
||||
x3 = Constituenta.objects.create(schema=self.rsform_unowned, alias='X1', csttype='basic', order=1)
|
||||
x3 = Constituenta.objects.create(schema=self.rsform_unowned, alias='X1', cst_type='basic', order=1)
|
||||
data = json.dumps({'items': [{'id': x3.id}]})
|
||||
response = self.client.patch(f'/api/rsforms/{schema.id}/cst-multidelete/',
|
||||
data=data, content_type='application/json')
|
||||
|
@ -230,8 +230,8 @@ class TestRSFormViewset(APITestCase):
|
|||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
x1 = Constituenta.objects.create(schema=schema, alias='X1', csttype='basic', order=1)
|
||||
x2 = Constituenta.objects.create(schema=schema, alias='X2', csttype='basic', order=2)
|
||||
x1 = Constituenta.objects.create(schema=schema, alias='X1', cst_type='basic', order=1)
|
||||
x2 = Constituenta.objects.create(schema=schema, alias='X2', cst_type='basic', order=2)
|
||||
data = json.dumps({'items': [{'id': x2.id}], 'move_to': 1})
|
||||
response = self.client.patch(f'/api/rsforms/{schema.id}/cst-moveto/',
|
||||
data=data, content_type='application/json')
|
||||
|
@ -242,7 +242,7 @@ class TestRSFormViewset(APITestCase):
|
|||
self.assertEqual(x1.order, 2)
|
||||
self.assertEqual(x2.order, 1)
|
||||
|
||||
x3 = Constituenta.objects.create(schema=self.rsform_unowned, alias='X1', csttype='basic', order=1)
|
||||
x3 = Constituenta.objects.create(schema=self.rsform_unowned, alias='X1', cst_type='basic', order=1)
|
||||
data = json.dumps({'items': [{'id': x3.id}], 'move_to': 1})
|
||||
response = self.client.patch(f'/api/rsforms/{schema.id}/cst-moveto/',
|
||||
data=data, content_type='application/json')
|
||||
|
|
|
@ -56,16 +56,18 @@ class RSFormViewSet(viewsets.ModelViewSet):
|
|||
schema: models.RSForm = self.get_object()
|
||||
serializer = serializers.CstCreateSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
if ('insert_after' in serializer.validated_data):
|
||||
if ('insert_after' in serializer.validated_data and serializer.validated_data['insert_after'] is not None):
|
||||
cstafter = models.Constituenta.objects.get(pk=serializer.validated_data['insert_after'])
|
||||
constituenta = schema.insert_at(cstafter.order + 1,
|
||||
serializer.validated_data['alias'],
|
||||
serializer.validated_data['csttype'])
|
||||
serializer.validated_data['cst_type'])
|
||||
else:
|
||||
constituenta = schema.insert_last(serializer.validated_data['alias'], serializer.validated_data['csttype'])
|
||||
constituenta = schema.insert_last(serializer.validated_data['alias'], serializer.validated_data['cst_type'])
|
||||
schema.refresh_from_db()
|
||||
outSerializer = serializers.RSFormDetailsSerlializer(schema)
|
||||
response = Response(status=201, data={'new_cst': constituenta.to_json(), 'schema': outSerializer.data})
|
||||
response = Response(status=201, data={
|
||||
'new_cst': serializers.ConstituentaSerializer(constituenta).data,
|
||||
'schema': outSerializer.data})
|
||||
response['Location'] = constituenta.get_absolute_url()
|
||||
return response
|
||||
|
||||
|
@ -175,13 +177,15 @@ def create_rsform(request):
|
|||
data = utils.read_trs(request.FILES['file'].file)
|
||||
if ('title' in request.data and request.data['title'] != ''):
|
||||
data['title'] = request.data['title']
|
||||
if data['title'] == '':
|
||||
data['title'] = 'Без названия ' + request.FILES['file'].fileName
|
||||
if ('alias' in request.data and request.data['alias'] != ''):
|
||||
data['alias'] = request.data['alias']
|
||||
if ('comment' in request.data and request.data['comment'] != ''):
|
||||
data['comment'] = request.data['comment']
|
||||
is_common = True
|
||||
if ('is_common' in request.data):
|
||||
is_common = request.data['is_common']
|
||||
is_common = request.data['is_common'] == 'true'
|
||||
schema = models.RSForm.import_json(owner, data, is_common)
|
||||
result = serializers.RSFormSerializer(schema)
|
||||
return Response(status=201, data=result.data)
|
||||
|
|
|
@ -64,12 +64,10 @@ REST_FRAMEWORK = {
|
|||
],
|
||||
}
|
||||
|
||||
|
||||
CORS_ORIGIN_ALLOW_ALL = True
|
||||
# CORS_ORIGIN_ALLOW_ALL = True
|
||||
CORS_ALLOW_CREDENTIALS = True
|
||||
# TODO: use env setup to populate allowed_origins in production
|
||||
CSRF_TRUSTED_ORIGINS = os.environ.get('CSRF_TRUSTED_ORIGINS', 'http://localhost:3000').split(';')
|
||||
CORS_ALLOWED_ORIGINS = os.environ.get('CORS_ALLOWED_ORIGINS', 'http://localhost:3000').split(';')
|
||||
CSRF_TRUSTED_ORIGINS = os.environ.get('CSRF_TRUSTED_ORIGINS', 'http://localhost:3000').split(';')
|
||||
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
|
|
@ -4,28 +4,27 @@
|
|||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"standard-with-typescript",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react/jsx-runtime"
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended-type-checked",
|
||||
"plugin:react-hooks/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module",
|
||||
"project": ["tsconfig.json"]
|
||||
"project": ["tsconfig.json", "tsconfig.node.json"]
|
||||
},
|
||||
"plugins": [
|
||||
"react", "simple-import-sort"
|
||||
"react-refresh", "simple-import-sort"
|
||||
],
|
||||
"rules": {
|
||||
"react-refresh/only-export-components": [
|
||||
"off",
|
||||
{ "allowConstantExport": true }
|
||||
],
|
||||
"simple-import-sort/imports": "warn",
|
||||
"@typescript-eslint/no-unused-vars": "warn",
|
||||
"no-trailing-spaces": "warn",
|
||||
"no-multiple-empty-lines": "warn",
|
||||
"@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"
|
||||
|
||||
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||
"@typescript-eslint/no-unsafe-argument": "off"
|
||||
}
|
||||
}
|
||||
|
|
41
rsconcept/frontend/.gitignore
vendored
41
rsconcept/frontend/.gitignore
vendored
|
@ -1,23 +1,24 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
|
|
@ -7,16 +7,10 @@ RUN apt-get update -qq && \
|
|||
# ======= Build =======
|
||||
FROM node-base as builder
|
||||
|
||||
ENV NODE_ENV production
|
||||
WORKDIR /result
|
||||
|
||||
# Install dependencies
|
||||
COPY *.json *.js ./
|
||||
RUN npm ci --only=production
|
||||
|
||||
# Build deployment files
|
||||
COPY ./public ./public
|
||||
COPY ./src ./src
|
||||
COPY ./ ./
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
|
||||
# ========= Server =======
|
||||
|
@ -33,7 +27,7 @@ USER node
|
|||
|
||||
# Bring up deployment files
|
||||
WORKDIR /home/node
|
||||
COPY --chown=node:node --from=builder /result/build ./
|
||||
COPY --chown=node:node --from=builder /result/dist ./
|
||||
|
||||
# Start server through docker-compose
|
||||
# serve -s /home/node -l 3000
|
|
@ -1,14 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="Веб-приложение для работы с концептуальными схемами" />
|
||||
|
||||
<link rel="manifest" id="manifest-placeholder" href="%PUBLIC_URL%/manifest.json" />
|
||||
|
||||
<title>Концепт Портал</title>
|
||||
|
||||
<script>
|
||||
|
@ -25,7 +23,7 @@
|
|||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>Включите использование JavaScript для работы с данным веб-приложением.</noscript>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
15824
rsconcept/frontend/package-lock.json
generated
15824
rsconcept/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"name": "frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.18.34",
|
||||
"@types/react": "^18.2.7",
|
||||
"@types/react-dom": "^18.2.4",
|
||||
"axios": "^1.4.0",
|
||||
"js-file-download": "^0.4.12",
|
||||
"react": "^18.2.0",
|
||||
|
@ -18,42 +18,26 @@
|
|||
"react-error-boundary": "^4.0.10",
|
||||
"react-intl": "^6.4.4",
|
||||
"react-loader-spinner": "^5.3.4",
|
||||
"react-router-dom": "^6.12.1",
|
||||
"react-scripts": "^5.0.1",
|
||||
"react-router-dom": "^6.14.2",
|
||||
"react-select": "^5.7.4",
|
||||
"react-tabs": "^6.0.1",
|
||||
"react-toastify": "^9.1.3",
|
||||
"styled-components": "^6.0.4",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
"react-tabs": "^6.0.2",
|
||||
"react-toastify": "^9.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
"@types/node": "^20.4.5",
|
||||
"@types/react": "^18.2.15",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"@vitejs/plugin-react": "^4.0.3",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"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-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.3",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"tailwindcss": "^3.3.2"
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.4.5"
|
||||
}
|
||||
}
|
||||
|
|
6
rsconcept/frontend/postcss.config.js
Normal file
6
rsconcept/frontend/postcss.config.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"short_name": "КонцептПортал",
|
||||
"name": "Портал для работы с концептуальными схемами",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.svg",
|
||||
"sizes": "64x64 32x32 24x24 16x16"
|
||||
},
|
||||
{
|
||||
"src": "logo.svg",
|
||||
"sizes": "512x512 256x256 64x64 32x32 24x24 16x16"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
|
@ -1,4 +1,4 @@
|
|||
import axios, { type AxiosError } from 'axios';
|
||||
import axios, { type AxiosError,AxiosHeaderValue } from 'axios';
|
||||
|
||||
import PrettyJson from './Common/PrettyJSON';
|
||||
|
||||
|
@ -29,15 +29,29 @@ function DescribeError(error: ErrorInfo) {
|
|||
);
|
||||
}
|
||||
|
||||
const isHtml = error.response.headers['content-type'].includes('text/html');
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const isHtml = (() => {
|
||||
if (!error.response) {
|
||||
return false;
|
||||
}
|
||||
const header = error.response.headers['content-type'] as AxiosHeaderValue;
|
||||
if (!header) {
|
||||
return false;
|
||||
}
|
||||
if (typeof header === 'number' || typeof header === 'boolean') {
|
||||
return false;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call
|
||||
return header.includes('text/html');
|
||||
})();
|
||||
return (
|
||||
<div className='flex flex-col justify-start'>
|
||||
<p className='underline'>Ошибка</p>
|
||||
<p>{error.message}</p>
|
||||
{error.response.data && (<>
|
||||
<p className='mt-2 underline'>Описание</p>
|
||||
{ isHtml && <div dangerouslySetInnerHTML={{ __html: error.response.data }} /> }
|
||||
{ !isHtml && <PrettyJson data={error.response.data} />}
|
||||
{ isHtml && <div dangerouslySetInnerHTML={{ __html: error.response.data as TrustedHTML }} /> }
|
||||
{ !isHtml && <PrettyJson data={error.response.data as object} />}
|
||||
</>)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -4,7 +4,8 @@ import { Tab } from 'react-tabs';
|
|||
function ConceptTab({ children, className, ...otherProps }: TabProps) {
|
||||
return (
|
||||
<Tab
|
||||
className={`px-2 py-1 text-sm hover:cursor-pointer clr-tab ${className?.toString() ?? ''} whitespace-nowrap`}
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
className={`px-2 py-1 text-sm hover:cursor-pointer clr-tab ${className} whitespace-nowrap`}
|
||||
{...otherProps}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
interface LabeledTextProps {
|
||||
id?: string
|
||||
label: string
|
||||
text: any
|
||||
text: string | number
|
||||
tooltip?: string
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
interface PrettyJsonProps {
|
||||
data: any
|
||||
data: unknown
|
||||
}
|
||||
|
||||
function PrettyJson({ data }: PrettyJsonProps) {
|
||||
|
|
|
@ -9,7 +9,7 @@ interface TextAreaProps {
|
|||
placeholder?: string
|
||||
widthClass?: string
|
||||
rows?: number
|
||||
value?: any
|
||||
value?: string | ReadonlyArray<string> | number | undefined;
|
||||
onChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void
|
||||
onFocus?: () => void
|
||||
}
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import 'react-toastify/dist/ReactToastify.css';
|
||||
|
||||
import { ToastContainer, type ToastContainerProps } from 'react-toastify';
|
||||
|
||||
import { useConceptTheme } from '../context/ThemeContext';
|
||||
|
||||
function ToasterThemed({ theme, ...props }: ToastContainerProps) {
|
||||
interface ToasterThemedProps extends Omit<ToastContainerProps, 'theme'>{}
|
||||
|
||||
function ToasterThemed({ ...props }: ToasterThemedProps) {
|
||||
const { darkMode } = useConceptTheme();
|
||||
|
||||
return (
|
||||
|
@ -12,4 +16,5 @@ function ToasterThemed({ theme, ...props }: ToastContainerProps) {
|
|||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default ToasterThemed;
|
||||
|
|
|
@ -2,14 +2,14 @@ import { createContext, useCallback, useContext, useLayoutEffect, useState } fro
|
|||
|
||||
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';
|
||||
import { type DataCallback, getAuth, postLogin, postLogout, postSignup } from '../utils/backendAPI';
|
||||
import { ICurrentUser, IUserLoginData, IUserProfile, IUserSignupData } from '../utils/models';
|
||||
|
||||
interface IAuthContext {
|
||||
user: ICurrentUser | undefined
|
||||
login: (username: string, password: string, callback?: BackendCallback) => void
|
||||
logout: (callback?: BackendCallback) => void
|
||||
signup: (data: IUserSignupData, callback?: BackendCallback) => void
|
||||
login: (data: IUserLoginData, callback?: DataCallback) => void
|
||||
logout: (callback?: DataCallback) => void
|
||||
signup: (data: IUserSignupData, callback?: DataCallback<IUserProfile>) => void
|
||||
loading: boolean
|
||||
error: ErrorInfo
|
||||
setError: (error: ErrorInfo) => void
|
||||
|
@ -35,69 +35,55 @@ export const AuthState = ({ children }: AuthStateProps) => {
|
|||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||
|
||||
const loadCurrentUser = useCallback(
|
||||
async () => {
|
||||
await getAuth({
|
||||
const reload = useCallback(
|
||||
(callback?: () => void) => {
|
||||
getAuth({
|
||||
onError: () => { setUser(undefined); },
|
||||
onSuccess: response => {
|
||||
if (response.data.id) {
|
||||
setUser(response.data);
|
||||
onSuccess: currentUser => {
|
||||
if (currentUser.id) {
|
||||
setUser(currentUser);
|
||||
} else {
|
||||
setUser(undefined)
|
||||
setUser(undefined);
|
||||
}
|
||||
if (callback) callback();
|
||||
}
|
||||
});
|
||||
}, [setUser]
|
||||
);
|
||||
|
||||
function login(uname: string, pw: string, callback?: BackendCallback) {
|
||||
function login(data: IUserLoginData, callback?: DataCallback) {
|
||||
setError(undefined);
|
||||
postLogin({
|
||||
data: { username: uname, password: pw },
|
||||
data: data,
|
||||
showError: true,
|
||||
setLoading,
|
||||
setLoading: setLoading,
|
||||
onError: error => { setError(error); },
|
||||
onSuccess:
|
||||
(response) => {
|
||||
loadCurrentUser()
|
||||
.then(() => { if (callback) callback(response); })
|
||||
.catch(console.error);
|
||||
}
|
||||
}).catch(console.error);
|
||||
onSuccess: newData => reload(() => { if (callback) callback(newData); })
|
||||
});
|
||||
}
|
||||
|
||||
function logout(callback?: BackendCallback) {
|
||||
function logout(callback?: DataCallback) {
|
||||
setError(undefined);
|
||||
postLogout({
|
||||
showError: true,
|
||||
onSuccess:
|
||||
(response) => {
|
||||
loadCurrentUser()
|
||||
.then(() => { if (callback) callback(response); })
|
||||
.catch(console.error);
|
||||
}
|
||||
}).catch(console.error);
|
||||
onSuccess: newData => reload(() => { if (callback) callback(newData); })
|
||||
});
|
||||
}
|
||||
|
||||
function signup(data: IUserSignupData, callback?: BackendCallback) {
|
||||
function signup(data: IUserSignupData, callback?: DataCallback<IUserProfile>) {
|
||||
setError(undefined);
|
||||
postSignup({
|
||||
data,
|
||||
data: data,
|
||||
showError: true,
|
||||
setLoading,
|
||||
setLoading: setLoading,
|
||||
onError: error => { setError(error); },
|
||||
onSuccess:
|
||||
(response) => {
|
||||
loadCurrentUser()
|
||||
.then(() => { if (callback) callback(response); })
|
||||
.catch(console.error);
|
||||
}
|
||||
}).catch(console.error);
|
||||
onSuccess: newData => reload(() => { if (callback) callback(newData); })
|
||||
});
|
||||
}
|
||||
|
||||
useLayoutEffect(() => {
|
||||
loadCurrentUser().catch(console.error);
|
||||
}, [loadCurrentUser])
|
||||
reload();
|
||||
}, [reload])
|
||||
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
|
|
|
@ -4,11 +4,14 @@ import { toast } from 'react-toastify'
|
|||
import { type ErrorInfo } from '../components/BackendError'
|
||||
import { useRSFormDetails } from '../hooks/useRSFormDetails'
|
||||
import {
|
||||
type BackendCallback, deleteRSForm, getTRSFile,
|
||||
type DataCallback, deleteRSForm, getTRSFile,
|
||||
patchConstituenta, patchDeleteConstituenta, patchMoveConstituenta, patchRSForm,
|
||||
postClaimRSForm, postNewConstituenta
|
||||
} from '../utils/backendAPI'
|
||||
import { type IConstituenta, type IRSForm } from '../utils/models'
|
||||
import {
|
||||
IConstituenta, IConstituentaList, IConstituentaMeta, ICstCreateData,
|
||||
ICstMovetoData, ICstUpdateData, IRSForm, IRSFormMeta, IRSFormUpdateData
|
||||
} from '../utils/models'
|
||||
import { useAuth } from './AuthContext'
|
||||
|
||||
interface IRSFormContext {
|
||||
|
@ -32,15 +35,15 @@ interface IRSFormContext {
|
|||
toggleReadonly: () => void
|
||||
toggleTracking: () => void
|
||||
|
||||
update: (data: any, callback?: BackendCallback) => void
|
||||
destroy: (callback?: BackendCallback) => void
|
||||
claim: (callback?: BackendCallback) => void
|
||||
download: (callback: BackendCallback) => void
|
||||
update: (data: IRSFormUpdateData, callback?: DataCallback<IRSFormMeta>) => void
|
||||
destroy: (callback?: DataCallback) => void
|
||||
claim: (callback?: DataCallback<IRSFormMeta>) => void
|
||||
download: (callback: DataCallback<Blob>) => void
|
||||
|
||||
cstUpdate: (cstdID: string, data: any, callback?: BackendCallback) => void
|
||||
cstCreate: (data: any, callback?: BackendCallback) => void
|
||||
cstDelete: (data: any, callback?: BackendCallback) => void
|
||||
cstMoveTo: (data: any, callback?: BackendCallback) => void
|
||||
cstCreate: (data: ICstCreateData, callback?: DataCallback<IConstituentaMeta>) => void
|
||||
cstUpdate: (data: ICstUpdateData, callback?: DataCallback<IConstituentaMeta>) => void
|
||||
cstDelete: (data: IConstituentaList, callback?: () => void) => void
|
||||
cstMoveTo: (data: ICstMovetoData, callback?: () => void) => void
|
||||
}
|
||||
|
||||
const RSFormContext = createContext<IRSFormContext | null>(null)
|
||||
|
@ -76,7 +79,7 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
|||
!loading && !isReadonly &&
|
||||
((isOwned || (isForceAdmin && user?.is_staff)) ?? false)
|
||||
)
|
||||
}, [user, isReadonly, isForceAdmin, isOwned, loading])
|
||||
}, [user?.is_staff, isReadonly, isForceAdmin, isOwned, loading])
|
||||
|
||||
const activeCst = useMemo(
|
||||
() => {
|
||||
|
@ -94,34 +97,39 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
|||
}, [])
|
||||
|
||||
const update = useCallback(
|
||||
(data: any, callback?: BackendCallback) => {
|
||||
(data: IRSFormUpdateData, callback?: DataCallback<IRSFormMeta>) => {
|
||||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
setError(undefined)
|
||||
patchRSForm(schemaID, {
|
||||
data,
|
||||
data: data,
|
||||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
onError: error => { setError(error) },
|
||||
onSuccess: (response) => {
|
||||
reload(setProcessing)
|
||||
.then(() => { if (callback != null) callback(response); })
|
||||
.catch(console.error);
|
||||
onSuccess: newData => {
|
||||
setSchema(Object.assign(schema, newData));
|
||||
if (callback) callback(newData);
|
||||
}
|
||||
}).catch(console.error);
|
||||
}, [schemaID, setError, reload])
|
||||
});
|
||||
}, [schemaID, setError, setSchema, schema])
|
||||
|
||||
const destroy = useCallback(
|
||||
(callback?: BackendCallback) => {
|
||||
(callback?: DataCallback) => {
|
||||
setError(undefined)
|
||||
deleteRSForm(schemaID, {
|
||||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
onError: error => { setError(error) },
|
||||
onSuccess: callback
|
||||
}).catch(console.error);
|
||||
}, [schemaID, setError])
|
||||
onSuccess: newData => {
|
||||
setSchema(undefined);
|
||||
if (callback) callback(newData);
|
||||
}
|
||||
});
|
||||
}, [schemaID, setError, setSchema])
|
||||
|
||||
const claim = useCallback(
|
||||
(callback?: BackendCallback) => {
|
||||
(callback?: DataCallback<IRSFormMeta>) => {
|
||||
if (!schema || !user) {
|
||||
return;
|
||||
}
|
||||
|
@ -130,85 +138,81 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
|||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
onError: error => { setError(error) },
|
||||
onSuccess: (response) => {
|
||||
schema.owner = user.id;
|
||||
schema.time_update = response.data.time_update;
|
||||
setSchema(schema);
|
||||
if (callback != null) callback(response);
|
||||
onSuccess: newData => {
|
||||
setSchema(Object.assign(schema, newData));
|
||||
if (callback) callback(newData);
|
||||
}
|
||||
}).catch(console.error);
|
||||
});
|
||||
}, [schemaID, setError, schema, user, setSchema])
|
||||
|
||||
const download = useCallback(
|
||||
(callback: BackendCallback) => {
|
||||
(callback: DataCallback<Blob>) => {
|
||||
setError(undefined)
|
||||
getTRSFile(schemaID, {
|
||||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
onError: error => { setError(error) },
|
||||
onSuccess: callback
|
||||
}).catch(console.error);
|
||||
});
|
||||
}, [schemaID, setError])
|
||||
|
||||
const cstUpdate = useCallback(
|
||||
(cstID: string, data: any, callback?: BackendCallback) => {
|
||||
setError(undefined)
|
||||
patchConstituenta(cstID, {
|
||||
data,
|
||||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
onError: error => { setError(error) },
|
||||
onSuccess: (response) => {
|
||||
reload(setProcessing)
|
||||
.then(() => { if (callback != null) callback(response); })
|
||||
.catch(console.error);
|
||||
}
|
||||
}).catch(console.error);
|
||||
}, [setError])
|
||||
|
||||
const cstCreate = useCallback(
|
||||
(data: any, callback?: BackendCallback) => {
|
||||
(data: ICstCreateData, callback?: DataCallback<IConstituentaMeta>) => {
|
||||
setError(undefined)
|
||||
postNewConstituenta(schemaID, {
|
||||
data,
|
||||
data: data,
|
||||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
onError: error => { setError(error) },
|
||||
onSuccess: (response) => {
|
||||
setSchema(response.data.schema);
|
||||
if (callback != null) callback(response);
|
||||
onSuccess: newData => {
|
||||
setSchema(newData.schema);
|
||||
if (callback) callback(newData.new_cst);
|
||||
}
|
||||
}).catch(console.error);
|
||||
});
|
||||
}, [schemaID, setError, setSchema]);
|
||||
|
||||
const cstDelete = useCallback(
|
||||
(data: any, callback?: BackendCallback) => {
|
||||
(data: IConstituentaList, callback?: () => void) => {
|
||||
setError(undefined)
|
||||
patchDeleteConstituenta(schemaID, {
|
||||
data,
|
||||
data: data,
|
||||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
onError: error => { setError(error) },
|
||||
onSuccess: (response) => {
|
||||
setSchema(response.data)
|
||||
if (callback != null) callback(response)
|
||||
onSuccess: newData => {
|
||||
setSchema(newData);
|
||||
if (callback) callback();
|
||||
}
|
||||
}).catch(console.error);
|
||||
});
|
||||
}, [schemaID, setError, setSchema]);
|
||||
|
||||
const cstMoveTo = useCallback(
|
||||
(data: any, callback?: BackendCallback) => {
|
||||
const cstUpdate = useCallback(
|
||||
(data: ICstUpdateData, callback?: DataCallback<IConstituentaMeta>) => {
|
||||
setError(undefined)
|
||||
patchMoveConstituenta(schemaID, {
|
||||
data,
|
||||
patchConstituenta(String(data.id), {
|
||||
data: data,
|
||||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
onError: error => { setError(error) },
|
||||
onSuccess: (response) => {
|
||||
setSchema(response.data);
|
||||
if (callback != null) callback(response);
|
||||
onSuccess: newData => {
|
||||
reload(setProcessing, () => { if (callback != null) callback(newData); })
|
||||
}
|
||||
}).catch(console.error);
|
||||
});
|
||||
}, [setError, reload])
|
||||
|
||||
const cstMoveTo = useCallback(
|
||||
(data: ICstMovetoData, callback?: () => void) => {
|
||||
setError(undefined)
|
||||
patchMoveConstituenta(schemaID, {
|
||||
data: data,
|
||||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
onError: error => { setError(error) },
|
||||
onSuccess: newData => {
|
||||
setSchema(newData);
|
||||
if (callback) callback();
|
||||
}
|
||||
});
|
||||
}, [schemaID, setError, setSchema]);
|
||||
|
||||
return (
|
||||
|
|
|
@ -5,8 +5,8 @@ import { type IUserInfo } from '../utils/models';
|
|||
|
||||
interface IUsersContext {
|
||||
users: IUserInfo[]
|
||||
reload: () => Promise<void>
|
||||
getUserLabel: (userID?: number) => string
|
||||
reload: () => void
|
||||
getUserLabel: (userID: number | null) => string
|
||||
}
|
||||
|
||||
const UsersContext = createContext<IUsersContext | null>(null)
|
||||
|
@ -27,13 +27,13 @@ interface UsersStateProps {
|
|||
export const UsersState = ({ children }: UsersStateProps) => {
|
||||
const [users, setUsers] = useState<IUserInfo[]>([])
|
||||
|
||||
const getUserLabel = (userID?: number) => {
|
||||
const getUserLabel = (userID: number | null) => {
|
||||
const user = users.find(({ id }) => id === userID)
|
||||
if (user == null) {
|
||||
if (!user) {
|
||||
return (userID ? userID.toString() : 'Отсутствует');
|
||||
}
|
||||
const hasFirstName = user.first_name != null && user.first_name !== '';
|
||||
const hasLastName = user.last_name != null && user.last_name !== '';
|
||||
const hasFirstName = user.first_name !== '';
|
||||
const hasLastName = user.last_name !== '';
|
||||
if (hasFirstName || hasLastName) {
|
||||
if (!hasLastName) {
|
||||
return user.first_name;
|
||||
|
@ -47,17 +47,17 @@ export const UsersState = ({ children }: UsersStateProps) => {
|
|||
}
|
||||
|
||||
const reload = useCallback(
|
||||
async () => {
|
||||
await getActiveUsers({
|
||||
() => {
|
||||
getActiveUsers({
|
||||
showError: true,
|
||||
onError: () => { setUsers([]); },
|
||||
onSuccess: response => { setUsers(response.data); }
|
||||
onSuccess: newData => { setUsers(newData); }
|
||||
});
|
||||
}, [setUsers]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
reload().catch(console.error);
|
||||
reload();
|
||||
}, [reload])
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,28 +1,27 @@
|
|||
import { type AxiosResponse } from 'axios';
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
import { type ErrorInfo } from '../components/BackendError';
|
||||
import { postCheckExpression } from '../utils/backendAPI';
|
||||
import { type IRSForm } from '../utils/models';
|
||||
import { DataCallback, postCheckExpression } from '../utils/backendAPI';
|
||||
import { ExpressionParse, type IRSForm } from '../utils/models';
|
||||
|
||||
function useCheckExpression({ schema }: { schema?: IRSForm }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||
const [parseData, setParseData] = useState<any | undefined>(undefined);
|
||||
const [parseData, setParseData] = useState<ExpressionParse | undefined>(undefined);
|
||||
|
||||
const resetParse = useCallback(() => { setParseData(undefined); }, []);
|
||||
|
||||
async function checkExpression(expression: string, onSuccess?: (response: AxiosResponse) => void) {
|
||||
function checkExpression(expression: string, onSuccess?: DataCallback<ExpressionParse>) {
|
||||
setError(undefined);
|
||||
setParseData(undefined);
|
||||
await postCheckExpression(String(schema?.id), {
|
||||
data: { expression },
|
||||
postCheckExpression(String(schema?.id), {
|
||||
data: { expression: expression },
|
||||
showError: true,
|
||||
setLoading,
|
||||
onError: error => { setError(error); },
|
||||
onSuccess: (response) => {
|
||||
setParseData(response.data);
|
||||
if (onSuccess) onSuccess(response);
|
||||
onSuccess: newData => {
|
||||
setParseData(newData);
|
||||
if (onSuccess) onSuccess(newData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -15,6 +15,6 @@ function useClickedOutside({ ref, callback }: { ref: React.RefObject<HTMLElement
|
|||
document.removeEventListener('mouseup', handleClickOutside);
|
||||
};
|
||||
}, [ref, callback]);
|
||||
};
|
||||
}
|
||||
|
||||
export default useClickedOutside;
|
||||
|
|
|
@ -15,6 +15,6 @@ function useDropdown() {
|
|||
toggle: () => { setIsActive(!isActive); },
|
||||
hide: () => { setIsActive(false); }
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export default useDropdown;
|
||||
|
|
|
@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
|
|||
|
||||
function getStorageValue<ValueType>(key: string, defaultValue: ValueType) {
|
||||
const saved = localStorage.getItem(key);
|
||||
const initial = saved ? JSON.parse(saved) : undefined;
|
||||
const initial = saved ? JSON.parse(saved) as ValueType : undefined;
|
||||
return initial || defaultValue;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,28 +1,21 @@
|
|||
import { useState } from 'react'
|
||||
|
||||
import { type ErrorInfo } from '../components/BackendError';
|
||||
import { postNewRSForm } from '../utils/backendAPI';
|
||||
import { DataCallback, postNewRSForm } from '../utils/backendAPI';
|
||||
import { IRSFormCreateData, IRSFormMeta } from '../utils/models';
|
||||
|
||||
function useNewRSForm() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||
|
||||
async function createSchema({ data, file, onSuccess }: {
|
||||
data: any
|
||||
file?: File
|
||||
onSuccess: (newID: string) => void
|
||||
}) {
|
||||
function createSchema(data: IRSFormCreateData, onSuccess: DataCallback<IRSFormMeta>) {
|
||||
setError(undefined);
|
||||
if (file) {
|
||||
data.file = file;
|
||||
data.fileName = file.name;
|
||||
}
|
||||
await postNewRSForm({
|
||||
data,
|
||||
postNewRSForm({
|
||||
data: data,
|
||||
showError: true,
|
||||
setLoading,
|
||||
setLoading: setLoading,
|
||||
onError: error => { setError(error); },
|
||||
onSuccess: response => { onSuccess(response.data.id); }
|
||||
onSuccess: onSuccess
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -2,40 +2,43 @@ import { useCallback, useEffect, useState } from 'react'
|
|||
|
||||
import { type ErrorInfo } from '../components/BackendError';
|
||||
import { getRSFormDetails } from '../utils/backendAPI';
|
||||
import { CalculateStats, type IRSForm } from '../utils/models'
|
||||
import { IRSForm, IRSFormData,LoadRSFormData } from '../utils/models'
|
||||
|
||||
export function useRSFormDetails({ target }: { target?: string }) {
|
||||
const [schema, setInnerSchema] = useState<IRSForm | undefined>(undefined);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||
|
||||
function setSchema(schema?: IRSForm) {
|
||||
if (schema) CalculateStats(schema);
|
||||
function setSchema(data?: IRSFormData) {
|
||||
if (!data) {
|
||||
setInnerSchema(undefined);
|
||||
return;
|
||||
}
|
||||
const schema = LoadRSFormData(data);
|
||||
setInnerSchema(schema);
|
||||
console.log('Loaded schema: ', schema);
|
||||
}
|
||||
|
||||
const fetchData = useCallback(
|
||||
async (setCustomLoading?: typeof setLoading) => {
|
||||
const reload = useCallback(
|
||||
(setCustomLoading?: typeof setLoading, callback?: () => void) => {
|
||||
setError(undefined);
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
await getRSFormDetails(target, {
|
||||
getRSFormDetails(target, {
|
||||
showError: true,
|
||||
setLoading: setCustomLoading ?? setLoading,
|
||||
onError: error => { setInnerSchema(undefined); setError(error); },
|
||||
onSuccess: (response) => { setSchema(response.data); }
|
||||
onSuccess: schema => {
|
||||
setSchema(schema);
|
||||
if (callback) callback();
|
||||
}
|
||||
});
|
||||
}, [target]);
|
||||
|
||||
async function reload(setCustomLoading?: typeof setLoading) {
|
||||
await fetchData(setCustomLoading);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchData().catch((error) => { setError(error); });
|
||||
}, [fetchData])
|
||||
reload();
|
||||
}, [reload])
|
||||
|
||||
return { schema, setSchema, reload, error, setError, loading };
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { useCallback, useState } from 'react'
|
|||
|
||||
import { type ErrorInfo } from '../components/BackendError';
|
||||
import { getRSForms } from '../utils/backendAPI';
|
||||
import { type IRSForm } from '../utils/models'
|
||||
import { IRSFormMeta } from '../utils/models'
|
||||
|
||||
export enum FilterType {
|
||||
PERSONAL = 'personal',
|
||||
|
@ -11,20 +11,20 @@ export enum FilterType {
|
|||
|
||||
export interface RSFormsFilter {
|
||||
type: FilterType
|
||||
data?: any
|
||||
data?: number | null
|
||||
}
|
||||
|
||||
export function useRSForms() {
|
||||
const [rsforms, setRSForms] = useState<IRSForm[]>([]);
|
||||
const [rsforms, setRSForms] = useState<IRSFormMeta[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||
|
||||
const loadList = useCallback(async (filter: RSFormsFilter) => {
|
||||
await getRSForms(filter, {
|
||||
const loadList = useCallback((filter: RSFormsFilter) => {
|
||||
getRSForms(filter, {
|
||||
showError: true,
|
||||
setLoading,
|
||||
onError: error => { setError(error); },
|
||||
onSuccess: response => { setRSForms(response.data); }
|
||||
onSuccess: newData => { setRSForms(newData); }
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
|
|
@ -10,21 +10,20 @@ export function useUserProfile() {
|
|||
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||
|
||||
const fetchUser = useCallback(
|
||||
async () => {
|
||||
() => {
|
||||
setError(undefined);
|
||||
setUser(undefined);
|
||||
|
||||
await getProfile({
|
||||
getProfile({
|
||||
showError: true,
|
||||
setLoading,
|
||||
setLoading: setLoading,
|
||||
onError: error => { setError(error); },
|
||||
onSuccess: response => { setUser(response.data); }
|
||||
onSuccess: newData => { setUser(newData); }
|
||||
});
|
||||
}, [setUser]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
fetchUser().catch((error) => { setError(error); });
|
||||
fetchUser();
|
||||
}, [fetchUser])
|
||||
|
||||
return { user, fetchUser, error, loading };
|
||||
|
|
|
@ -1,28 +1,21 @@
|
|||
'use client';
|
||||
|
||||
import './index.css';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import './index.css'
|
||||
|
||||
import axios from 'axios';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
|
||||
import App from './App';
|
||||
import ErrorFallback from './components/ErrorFallback';
|
||||
import { AuthState } from './context/AuthContext';
|
||||
import { ThemeState } from './context/ThemeContext';
|
||||
import { UsersState } from './context/UsersContext';
|
||||
import App from './App.tsx'
|
||||
import ErrorFallback from './components/ErrorFallback.tsx';
|
||||
import { AuthState } from './context/AuthContext.tsx';
|
||||
import { ThemeState } from './context/ThemeContext.tsx';
|
||||
import { UsersState } from './context/UsersContext.tsx';
|
||||
|
||||
axios.defaults.withCredentials = true
|
||||
axios.defaults.xsrfCookieName = 'csrftoken'
|
||||
axios.defaults.xsrfHeaderName = 'x-csrftoken'
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
);
|
||||
axios.defaults.withCredentials = true;
|
||||
axios.defaults.xsrfCookieName = 'csrftoken';
|
||||
axios.defaults.xsrfHeaderName = 'x-csrftoken';
|
||||
|
||||
const resetState = () => {
|
||||
console.log('Resetting state after error fallback')
|
||||
|
@ -33,7 +26,7 @@ const logError = (error: Error, info: { componentStack: string }) => {
|
|||
console.log('Component stack: ' + info.componentStack)
|
||||
};
|
||||
|
||||
root.render(
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<ErrorBoundary
|
||||
|
@ -52,5 +45,5 @@ root.render(
|
|||
</IntlProvider>
|
||||
</ErrorBoundary>
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>
|
||||
);
|
||||
</React.StrictMode>,
|
||||
)
|
|
@ -8,6 +8,7 @@ import TextInput from '../components/Common/TextInput';
|
|||
import TextURL from '../components/Common/TextURL';
|
||||
import InfoMessage from '../components/InfoMessage';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { IUserLoginData } from '../utils/models';
|
||||
|
||||
function LoginPage() {
|
||||
const [username, setUsername] = useState('');
|
||||
|
@ -30,7 +31,11 @@ function LoginPage() {
|
|||
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
if (!loading) {
|
||||
login(username, password, () => { navigate('/rsforms?filter=personal'); });
|
||||
const data: IUserLoginData = {
|
||||
username: username,
|
||||
password: password
|
||||
};
|
||||
login(data, () => { navigate('/rsforms?filter=personal'); });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ 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';
|
||||
import { IRSFormCreateData, IRSFormMeta } from '../utils/models';
|
||||
|
||||
function RSFormCreatePage() {
|
||||
const navigate = useNavigate();
|
||||
|
@ -30,9 +30,9 @@ function RSFormCreatePage() {
|
|||
}
|
||||
}
|
||||
|
||||
const onSuccess = (newID: string) => {
|
||||
const onSuccess = (newSchema: IRSFormMeta) => {
|
||||
toast.success('Схема успешно создана');
|
||||
navigate(`/rsforms/${newID}`);
|
||||
navigate(`/rsforms/${newSchema.id}`);
|
||||
}
|
||||
const { createSchema, error, setError, loading } = useNewRSForm()
|
||||
|
||||
|
@ -46,16 +46,14 @@ function RSFormCreatePage() {
|
|||
return;
|
||||
}
|
||||
const data: IRSFormCreateData = {
|
||||
title,
|
||||
alias,
|
||||
comment,
|
||||
is_common: common
|
||||
title: title,
|
||||
alias: alias,
|
||||
comment: comment,
|
||||
is_common: common,
|
||||
file: file,
|
||||
fileName: file?.name
|
||||
};
|
||||
void createSchema({
|
||||
data,
|
||||
file,
|
||||
onSuccess
|
||||
});
|
||||
void createSchema(data, onSuccess);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
import { type AxiosResponse } from 'axios';
|
||||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
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 { type CstType, EditMode, type ICstCreateData, ICstUpdateData } from '../../utils/models';
|
||||
import { createAliasFor, getCstTypeLabel } from '../../utils/staticUI';
|
||||
import ConstituentsSideList from './ConstituentsSideList';
|
||||
import CreateCstModal from './CreateCstModal';
|
||||
import ExpressionEditor from './ExpressionEditor';
|
||||
import { RSFormTabsList } from './RSFormTabs';
|
||||
|
||||
function ConstituentEditor() {
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
activeCst, activeID, schema, setActiveID, processing, isEditable,
|
||||
cstDelete, cstUpdate, cstCreate
|
||||
|
@ -33,10 +35,8 @@ function ConstituentEditor() {
|
|||
const isEnabled = useMemo(() => activeCst && isEditable, [activeCst, isEditable]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (schema?.items && schema?.items.length > 0) {
|
||||
// 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));
|
||||
if (schema && schema?.items.length > 0) {
|
||||
setActiveID((prev) => (prev ?? schema.items[0].id ?? undefined));
|
||||
}
|
||||
}, [schema, setActiveID]);
|
||||
|
||||
|
@ -46,12 +46,14 @@ function ConstituentEditor() {
|
|||
return;
|
||||
}
|
||||
setIsModified(
|
||||
activeCst.term?.raw !== term ||
|
||||
activeCst.definition?.text?.raw !== textDefinition ||
|
||||
activeCst.term.raw !== term ||
|
||||
activeCst.definition.text.raw !== textDefinition ||
|
||||
activeCst.convention !== convention ||
|
||||
activeCst.definition?.formal !== expression
|
||||
activeCst.definition.formal !== expression
|
||||
);
|
||||
}, [activeCst, term, textDefinition, expression, convention]);
|
||||
}, [activeCst, activeCst?.term, activeCst?.definition.formal,
|
||||
activeCst?.definition.text.raw, activeCst?.convention,
|
||||
term, textDefinition, expression, convention]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (activeCst) {
|
||||
|
@ -61,25 +63,25 @@ function ConstituentEditor() {
|
|||
setTerm(activeCst.term?.raw ?? '');
|
||||
setTextDefinition(activeCst.definition?.text?.raw ?? '');
|
||||
setExpression(activeCst.definition?.formal ?? '');
|
||||
setTypification(activeCst?.parse?.typification ?? 'N/A');
|
||||
setTypification(activeCst?.parse?.typification || 'N/A');
|
||||
}
|
||||
}, [activeCst]);
|
||||
|
||||
const handleSubmit =
|
||||
(event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
if (!processing) {
|
||||
const data = {
|
||||
if (!activeID || processing) {
|
||||
return;
|
||||
}
|
||||
const data: ICstUpdateData = {
|
||||
id: activeID,
|
||||
alias: alias,
|
||||
convention: convention,
|
||||
definition_formal: expression,
|
||||
definition_raw: textDefinition,
|
||||
term_raw: term
|
||||
};
|
||||
cstUpdate(String(activeID), data, () => {
|
||||
toast.success('Изменения сохранены');
|
||||
});
|
||||
}
|
||||
cstUpdate(data, () => { toast.success('Изменения сохранены'); });
|
||||
};
|
||||
|
||||
const handleDelete = useCallback(
|
||||
|
@ -98,25 +100,24 @@ function ConstituentEditor() {
|
|||
}, [activeID, schema, setActiveID, cstDelete]);
|
||||
|
||||
const handleAddNew = useCallback(
|
||||
(csttype?: CstType) => {
|
||||
(type?: CstType) => {
|
||||
if (!activeID || !schema?.items) {
|
||||
return;
|
||||
}
|
||||
if (!csttype) {
|
||||
if (!type) {
|
||||
setShowCstModal(true);
|
||||
} else {
|
||||
const data: INewCstData = {
|
||||
csttype: csttype,
|
||||
alias: createAliasFor(csttype, schema),
|
||||
const data: ICstCreateData = {
|
||||
cst_type: type,
|
||||
alias: createAliasFor(type, schema),
|
||||
insert_after: activeID
|
||||
}
|
||||
cstCreate(data,
|
||||
(response: AxiosResponse) => {
|
||||
setActiveID(response.data.new_cst.id);
|
||||
toast.success(`Конституента добавлена: ${response.data.new_cst.alias as string}`);
|
||||
cstCreate(data, newCst => {
|
||||
navigate(`/rsforms/${schema.id}?tab=${RSFormTabsList.CST_EDIT}&active=${newCst.id}`);
|
||||
toast.success(`Конституента добавлена: ${newCst.alias}`);
|
||||
});
|
||||
}
|
||||
}, [activeID, schema, cstCreate, setActiveID]);
|
||||
}, [activeID, schema, cstCreate, navigate]);
|
||||
|
||||
const handleRename = useCallback(() => {
|
||||
toast.info('Переименование в разработке');
|
||||
|
@ -202,7 +203,7 @@ function ConstituentEditor() {
|
|||
placeholder='Родоструктурное выражение, задающее формальное определение'
|
||||
value={expression}
|
||||
disabled={!isEnabled}
|
||||
isActive={editMode === 'rslang'}
|
||||
isActive={editMode === EditMode.RSLANG}
|
||||
toggleEditMode={() => { setEditMode(EditMode.RSLANG); }}
|
||||
onChange={event => { setExpression(event.target.value); }}
|
||||
setValue={setExpression}
|
||||
|
|
|
@ -5,7 +5,7 @@ import DataTableThemed from '../../components/Common/DataTableThemed';
|
|||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import useLocalStorage from '../../hooks/useLocalStorage';
|
||||
import { CstType, type IConstituenta, matchConstituenta } from '../../utils/models';
|
||||
import { extractGlobals } from '../../utils/staticUI';
|
||||
import { extractGlobals, getMockConstituenta } from '../../utils/staticUI';
|
||||
|
||||
interface ConstituentsSideListProps {
|
||||
expression: string
|
||||
|
@ -20,19 +20,16 @@ function ConstituentsSideList({ expression }: ConstituentsSideListProps) {
|
|||
useEffect(() => {
|
||||
if (!schema?.items) {
|
||||
setFilteredData([]);
|
||||
} else if (onlyExpression) {
|
||||
return;
|
||||
}
|
||||
if (onlyExpression) {
|
||||
const aliases = extractGlobals(expression);
|
||||
const filtered = schema?.items.filter((cst) => aliases.has(cst.alias));
|
||||
const names = filtered.map(cst => cst.alias)
|
||||
const diff = Array.from(aliases).filter(name => !names.includes(name));
|
||||
if (diff.length > 0) {
|
||||
diff.forEach(
|
||||
(alias, i) => filtered.push({
|
||||
id: -i,
|
||||
alias: alias,
|
||||
convention: 'Конституента отсутствует',
|
||||
cstType: CstType.BASE
|
||||
}));
|
||||
(alias, index) => filtered.push(getMockConstituenta(-index, alias, CstType.BASE, 'Конституента отсутствует')));
|
||||
}
|
||||
setFilteredData(filtered);
|
||||
} else if (!filterText) {
|
||||
|
@ -50,7 +47,7 @@ function ConstituentsSideList({ expression }: ConstituentsSideListProps) {
|
|||
}, [setActiveID]);
|
||||
|
||||
const handleDoubleClick = useCallback(
|
||||
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
||||
(cst: IConstituenta) => {
|
||||
if (cst.id > 0) setActiveID(cst.id);
|
||||
}, [setActiveID]);
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { type AxiosResponse } from 'axios';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
|
@ -8,8 +7,8 @@ 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 { CstType, type IConstituenta, type ICstCreateData, ICstMovetoData,inferStatus, ParsingStatus, ValueClass } from '../../utils/models'
|
||||
import { createAliasFor, getCstTypePrefix, getCstTypeShortcut, getStatusInfo, getTypeLabel } from '../../utils/staticUI';
|
||||
import CreateCstModal from './CreateCstModal';
|
||||
|
||||
interface ConstituentsTableProps {
|
||||
|
@ -51,8 +50,8 @@ function ConstituentsTable({ onOpenEdit }: ConstituentsTableProps) {
|
|||
const data = {
|
||||
items: selected.map(id => { return { id }; })
|
||||
}
|
||||
const deletedNames = selected.map(id => schema.items?.find((cst) => cst.id === id)?.alias);
|
||||
cstDelete(data, () => toast.success(`Конституенты удалены: ${deletedNames.toString()}`));
|
||||
const deletedNames = selected.map(id => schema.items?.find((cst) => cst.id === id)?.alias).join(', ');
|
||||
cstDelete(data, () => toast.success(`Конституенты удалены: ${deletedNames}`));
|
||||
}, [selected, schema?.items, cstDelete]);
|
||||
|
||||
// Move selected cst up
|
||||
|
@ -69,10 +68,10 @@ function ConstituentsTable({ onOpenEdit }: ConstituentsTableProps) {
|
|||
}
|
||||
return Math.min(prev, index);
|
||||
}, -1);
|
||||
const insertIndex = Math.max(0, currentIndex - 1) + 1
|
||||
const target = Math.max(0, currentIndex - 1) + 1
|
||||
const data = {
|
||||
items: selected.map(id => { return { id }; }),
|
||||
move_to: insertIndex
|
||||
move_to: target
|
||||
}
|
||||
cstMoveTo(data);
|
||||
}, [selected, schema?.items, cstMoveTo]);
|
||||
|
@ -95,10 +94,10 @@ function ConstituentsTable({ onOpenEdit }: ConstituentsTableProps) {
|
|||
return Math.max(prev, index);
|
||||
}
|
||||
}, -1);
|
||||
const insertIndex = Math.min(schema.items.length - 1, currentIndex - count + 2) + 1
|
||||
const data = {
|
||||
const target = Math.min(schema.items.length - 1, currentIndex - count + 2) + 1
|
||||
const data: ICstMovetoData = {
|
||||
items: selected.map(id => { return { id }; }),
|
||||
move_to: insertIndex
|
||||
move_to: target
|
||||
}
|
||||
cstMoveTo(data);
|
||||
}, [selected, schema?.items, cstMoveTo]);
|
||||
|
@ -109,38 +108,56 @@ function ConstituentsTable({ onOpenEdit }: ConstituentsTableProps) {
|
|||
}, []);
|
||||
|
||||
// Add new constituent
|
||||
const handleAddNew = useCallback((csttype?: CstType) => {
|
||||
const handleAddNew = useCallback((type?: CstType) => {
|
||||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
if (!csttype) {
|
||||
if (!type) {
|
||||
setShowCstModal(true);
|
||||
} else {
|
||||
const data: INewCstData = {
|
||||
csttype,
|
||||
alias: createAliasFor(csttype, schema)
|
||||
const selectedPosition = selected.reduce((prev, cstID) => {
|
||||
const position = schema.items.findIndex(cst => cst.id === cstID);
|
||||
return Math.max(position, prev);
|
||||
}, -1) + 1;
|
||||
const data: ICstCreateData = {
|
||||
cst_type: type,
|
||||
alias: createAliasFor(type, schema),
|
||||
insert_after: selectedPosition > 0 ? selectedPosition : null
|
||||
}
|
||||
if (selected.length > 0) {
|
||||
data.insert_after = selected[selected.length - 1]
|
||||
}
|
||||
cstCreate(data, (response: AxiosResponse) =>
|
||||
toast.success(`Добавлена конституента ${response.data.new_cst.alias as string}`));
|
||||
cstCreate(data, new_cst => toast.success(`Добавлена конституента ${new_cst.alias}`));
|
||||
}
|
||||
}, [schema, selected, cstCreate]);
|
||||
|
||||
// Implement hotkeys for working with constituents table
|
||||
const handleTableKey = useCallback((event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (!event.altKey) {
|
||||
function handleTableKey(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||
if (!event.altKey || !isEditable || event.shiftKey) {
|
||||
return;
|
||||
}
|
||||
if (!isEditable || selected.length === 0) {
|
||||
if (processAltKey(event.key)) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
switch (event.key) {
|
||||
case 'ArrowUp': handleMoveUp(); return;
|
||||
case 'ArrowDown': handleMoveDown();
|
||||
}
|
||||
}, [isEditable, selected, handleMoveUp, handleMoveDown]);
|
||||
|
||||
function processAltKey(key: string): boolean {
|
||||
if (selected.length > 0) {
|
||||
switch (key) {
|
||||
case 'ArrowUp': handleMoveUp(); return true;
|
||||
case 'ArrowDown': handleMoveDown(); return true;
|
||||
}
|
||||
}
|
||||
switch (key) {
|
||||
case '1': handleAddNew(CstType.BASE); return true;
|
||||
case '2': handleAddNew(CstType.STRUCTURED); return true;
|
||||
case '3': handleAddNew(CstType.TERM); return true;
|
||||
case '4': handleAddNew(CstType.AXIOM); return true;
|
||||
case 'q': handleAddNew(CstType.FUNCTION); return true;
|
||||
case 'w': handleAddNew(CstType.PREDICATE); return true;
|
||||
case '5': handleAddNew(CstType.CONSTANT); return true;
|
||||
case '6': handleAddNew(CstType.THEOREM); return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const columns = useMemo(() =>
|
||||
[
|
||||
|
@ -311,7 +328,7 @@ function ConstituentsTable({ onOpenEdit }: ConstituentsTableProps) {
|
|||
const type = typeStr as CstType;
|
||||
return <Button key={type}
|
||||
text={`${getCstTypePrefix(type)}`}
|
||||
tooltip={getCstTypeLabel(type)}
|
||||
tooltip={getCstTypeShortcut(type)}
|
||||
dense
|
||||
onClick={() => { handleAddNew(type); }}
|
||||
/>;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { type AxiosResponse } from 'axios';
|
||||
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
|
@ -7,7 +6,8 @@ import Label from '../../components/Common/Label';
|
|||
import { Loader } from '../../components/Common/Loader';
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import useCheckExpression from '../../hooks/useCheckExpression';
|
||||
import { CstType, TokenID } from '../../utils/models';
|
||||
import { TokenID } from '../../utils/enums';
|
||||
import { CstType } from '../../utils/models';
|
||||
import ParsingResult from './ParsingResult';
|
||||
import RSLocalButton from './RSLocalButton';
|
||||
import RSTokenButton from './RSTokenButton';
|
||||
|
@ -47,12 +47,16 @@ function ExpressionEditor({
|
|||
}
|
||||
const prefix = activeCst?.alias + (activeCst?.cstType === CstType.STRUCTURED ? '::=' : ':==');
|
||||
const expression = prefix + value;
|
||||
checkExpression(expression, (response: AxiosResponse) => {
|
||||
checkExpression(expression, parse => {
|
||||
// TODO: update cursor position
|
||||
if (!parse.parseResult && parse.errors.length > 0) {
|
||||
const errorPosition = parse.errors[0].position - prefix.length
|
||||
expressionCtrl.current!.selectionStart = errorPosition;
|
||||
expressionCtrl.current!.selectionEnd = errorPosition;
|
||||
}
|
||||
setIsModified(false);
|
||||
setTypification(response.data.typification);
|
||||
toast.success('проверка завершена');
|
||||
}).catch(console.error);
|
||||
setTypification(parse.typification);
|
||||
});
|
||||
}, [value, checkExpression, activeCst, setTypification]);
|
||||
|
||||
const handleEdit = useCallback((id: TokenID, key?: string) => {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import PrettyJson from '../../components/Common/PrettyJSON';
|
||||
import { ExpressionParse } from '../../utils/models';
|
||||
|
||||
interface ParsingResultProps {
|
||||
data?: any
|
||||
data?: ExpressionParse
|
||||
}
|
||||
|
||||
function ParsingResult({ data }: ParsingResultProps) {
|
||||
|
|
|
@ -12,6 +12,7 @@ import { CrownIcon, DownloadIcon, DumpBinIcon, SaveIcon, ShareIcon } from '../..
|
|||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import { useUsers } from '../../context/UsersContext';
|
||||
import { IRSFormCreateData } from '../../utils/models';
|
||||
import { claimOwnershipProc, deleteRSFormProc, downloadRSFormProc, shareCurrentURLProc } from '../../utils/procedures';
|
||||
|
||||
function RSFormCard() {
|
||||
|
@ -42,7 +43,8 @@ function RSFormCard() {
|
|||
schema.comment !== comment ||
|
||||
schema.is_common !== common
|
||||
);
|
||||
}, [schema, title, alias, comment, common]);
|
||||
}, [schema, schema?.title, schema?.alias, schema?.comment, schema?.is_common,
|
||||
title, alias, comment, common]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (schema) {
|
||||
|
@ -55,13 +57,12 @@ function RSFormCard() {
|
|||
|
||||
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
const data = {
|
||||
title,
|
||||
alias,
|
||||
comment,
|
||||
const data: IRSFormCreateData = {
|
||||
title: title,
|
||||
alias: alias,
|
||||
comment: comment,
|
||||
is_common: common
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
update(data, () => toast.success('Изменения сохранены'));
|
||||
};
|
||||
|
||||
|
@ -70,7 +71,6 @@ function RSFormCard() {
|
|||
|
||||
const handleDownload = useCallback(() => {
|
||||
const fileName = (schema?.alias ?? 'Schema') + '.trs';
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
downloadRSFormProc(download, fileName);
|
||||
}, [download, schema?.alias]);
|
||||
|
||||
|
@ -139,7 +139,7 @@ function RSFormCard() {
|
|||
<div className='flex justify-start mt-2'>
|
||||
<label className='font-semibold'>Владелец:</label>
|
||||
<span className='min-w-[200px] ml-2 overflow-ellipsis overflow-hidden whitespace-nowrap'>
|
||||
{getUserLabel(schema?.owner)}
|
||||
{getUserLabel(schema?.owner ?? null)}
|
||||
</span>
|
||||
</div>
|
||||
<div className='flex justify-start mt-2'>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { TokenID } from '../../utils/models'
|
||||
import { TokenID } from '../../utils/enums';
|
||||
|
||||
interface RSLocalButtonProps {
|
||||
text: string
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { type TokenID } from '../../utils/models'
|
||||
import { TokenID } from '../../utils/enums';
|
||||
import { getRSButtonData } from '../../utils/staticUI'
|
||||
|
||||
interface RSTokenButtonProps {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { useMemo } from 'react';
|
||||
|
||||
import { ExpressionStatus, type IConstituenta, inferStatus, ParsingStatus } from '../../utils/models';
|
||||
import { ExpressionParse,ExpressionStatus, type IConstituenta, inferStatus, ParsingStatus } from '../../utils/models';
|
||||
import { getStatusInfo } from '../../utils/staticUI';
|
||||
|
||||
interface StatusBarProps {
|
||||
isModified?: boolean
|
||||
parseData?: any
|
||||
parseData?: ExpressionParse
|
||||
constituenta?: IConstituenta
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Formatted text editing helpers
|
||||
|
||||
import { TokenID } from '../../utils/models'
|
||||
import { TokenID } from '../../utils/enums';
|
||||
|
||||
export function getSymbolSubstitute(input: string): string | undefined {
|
||||
switch (input) {
|
||||
|
@ -125,7 +125,7 @@ export class TextWrapper implements IManagedText {
|
|||
return true;
|
||||
}
|
||||
case TokenID.BOOLEAN: {
|
||||
if (this.selEnd !== this.selStart && this.value.at(this.selStart) === 'ℬ') {
|
||||
if (this.selEnd !== this.selStart && this.value[this.selStart] === 'ℬ') {
|
||||
this.envelopeWith('ℬ', '');
|
||||
} else {
|
||||
this.envelopeWith('ℬ(', ')');
|
||||
|
@ -209,4 +209,4 @@ export class TextWrapper implements IManagedText {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,10 +4,10 @@ import { useNavigate } from 'react-router-dom';
|
|||
|
||||
import DataTableThemed from '../../components/Common/DataTableThemed';
|
||||
import { useUsers } from '../../context/UsersContext';
|
||||
import { type IRSForm } from '../../utils/models'
|
||||
import { IRSFormMeta } from '../../utils/models'
|
||||
|
||||
interface RSFormsTableProps {
|
||||
schemas: IRSForm[]
|
||||
schemas: IRSFormMeta[]
|
||||
}
|
||||
|
||||
function RSFormsTable({ schemas }: RSFormsTableProps) {
|
||||
|
@ -15,7 +15,7 @@ function RSFormsTable({ schemas }: RSFormsTableProps) {
|
|||
const intl = useIntl();
|
||||
const { getUserLabel } = useUsers();
|
||||
|
||||
const openRSForm = (schema: IRSForm, event: React.MouseEvent<Element, MouseEvent>) => {
|
||||
const openRSForm = (schema: IRSFormMeta) => {
|
||||
navigate(`/rsforms/${schema.id}`);
|
||||
};
|
||||
|
||||
|
@ -25,7 +25,7 @@ function RSFormsTable({ schemas }: RSFormsTableProps) {
|
|||
name: 'Шифр',
|
||||
id: 'alias',
|
||||
maxWidth: '140px',
|
||||
selector: (schema: IRSForm) => schema.alias,
|
||||
selector: (schema: IRSFormMeta) => schema.alias,
|
||||
sortable: true,
|
||||
reorder: true
|
||||
},
|
||||
|
@ -33,15 +33,15 @@ function RSFormsTable({ schemas }: RSFormsTableProps) {
|
|||
name: 'Название',
|
||||
id: 'title',
|
||||
minWidth: '50%',
|
||||
selector: (schema: IRSForm) => schema.title,
|
||||
selector: (schema: IRSFormMeta) => schema.title,
|
||||
sortable: true,
|
||||
reorder: true
|
||||
},
|
||||
{
|
||||
name: 'Владелец',
|
||||
id: 'owner',
|
||||
selector: (schema: IRSForm) => schema.owner ?? 0,
|
||||
format: (schema: IRSForm) => {
|
||||
selector: (schema: IRSFormMeta) => schema.owner ?? 0,
|
||||
format: (schema: IRSFormMeta) => {
|
||||
return getUserLabel(schema.owner);
|
||||
},
|
||||
sortable: true,
|
||||
|
@ -50,8 +50,8 @@ function RSFormsTable({ schemas }: RSFormsTableProps) {
|
|||
{
|
||||
name: 'Обновлена',
|
||||
id: 'time_update',
|
||||
selector: (row: IRSForm) => row.time_update,
|
||||
format: (row: IRSForm) => new Date(row.time_update).toLocaleString(intl.locale),
|
||||
selector: (schema: IRSFormMeta) => schema.time_update,
|
||||
format: (schema: IRSFormMeta) => new Date(schema.time_update).toLocaleString(intl.locale),
|
||||
sortable: true,
|
||||
reorder: true
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ function RSFormsTable({ schemas }: RSFormsTableProps) {
|
|||
|
||||
noDataComponent={<span className='flex flex-col justify-center p-2 text-center'>
|
||||
<p>Список схем пуст</p>
|
||||
<p>Измените фильтр</p>
|
||||
<p>Измените фильтр или создайти новую концептуальную схему</p>
|
||||
</span>}
|
||||
|
||||
pagination
|
||||
|
|
|
@ -19,7 +19,7 @@ function RSFormsPage() {
|
|||
if (type === FilterType.PERSONAL) {
|
||||
filter.data = user?.id;
|
||||
}
|
||||
loadList(filter).catch(console.error);
|
||||
loadList(filter);
|
||||
}, [search, user, loadList]);
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import BackendError from '../components/BackendError';
|
||||
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 InfoMessage from '../components/InfoMessage';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { type IUserSignupData } from '../utils/models';
|
||||
|
||||
function RegisterPage() {
|
||||
const navigate = useNavigate();
|
||||
const [username, setUsername] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
|
@ -17,7 +19,6 @@ function RegisterPage() {
|
|||
const [firstName, setFirstName] = useState('');
|
||||
const [lastName, setLastName] = useState('');
|
||||
|
||||
const [success, setSuccess] = useState(false);
|
||||
const { user, signup, loading, error, setError } = useAuth()
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -35,20 +36,18 @@ function RegisterPage() {
|
|||
first_name: firstName,
|
||||
last_name: lastName
|
||||
};
|
||||
signup(data, () => { setSuccess(true); });
|
||||
signup(data, createdUser => {
|
||||
navigate(`/login?username=${createdUser.username}`);
|
||||
toast.success(`Пользователь успешно создан: ${createdUser.username}`);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='w-full py-2'>
|
||||
{ success &&
|
||||
<div className='flex flex-col items-center'>
|
||||
<InfoMessage message={`Вы успешно зарегистрировали пользователя ${username}`}/>
|
||||
<TextURL text='Войти в аккаунт' href={`/login?username=${username}`}/>
|
||||
</div>}
|
||||
{ !success && user &&
|
||||
{ user &&
|
||||
<InfoMessage message={`Вы вошли в систему как ${user.username}. Если хотите зарегистрировать нового пользователя, выйдите из системы (меню в правом верхнем углу экрана)`} /> }
|
||||
{ !success && !user &&
|
||||
{ !user &&
|
||||
<Form title='Регистрация пользователя' onSubmit={handleSubmit}>
|
||||
<TextInput id='username' label='Имя пользователя' type='text'
|
||||
required
|
||||
|
|
1
rsconcept/frontend/src/react-app-env.d.ts
vendored
1
rsconcept/frontend/src/react-app-env.d.ts
vendored
|
@ -1 +0,0 @@
|
|||
/// <reference types='react-scripts' />
|
|
@ -1,5 +0,0 @@
|
|||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
|
@ -1,253 +1,272 @@
|
|||
import axios, { type AxiosResponse } from 'axios'
|
||||
import axios, { AxiosRequestConfig } from 'axios'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import { type ErrorInfo } from '../components/BackendError'
|
||||
import { FilterType, type RSFormsFilter } from '../hooks/useRSForms'
|
||||
import { FilterType, RSFormsFilter } from '../hooks/useRSForms'
|
||||
import { config } from './constants'
|
||||
import { type ICurrentUser, type IRSForm, type IUserInfo, type IUserProfile } from './models'
|
||||
import {
|
||||
ExpressionParse,
|
||||
IConstituentaList,
|
||||
IConstituentaMeta,
|
||||
ICstCreateData,
|
||||
ICstCreatedResponse,
|
||||
ICstMovetoData,
|
||||
ICstUpdateData,
|
||||
ICurrentUser, IRSFormCreateData, IRSFormData,
|
||||
IRSFormMeta, IRSFormUpdateData, IUserInfo, IUserLoginData, IUserProfile, IUserSignupData, RSExpression
|
||||
} from './models'
|
||||
|
||||
export type BackendCallback = (response: AxiosResponse) => void;
|
||||
// ================ Data transfer types ================
|
||||
export type DataCallback<ResponseData = undefined> = (data: ResponseData) => void;
|
||||
|
||||
export interface IFrontRequest {
|
||||
onSuccess?: BackendCallback
|
||||
interface IFrontRequest<RequestData, ResponseData> {
|
||||
data?: RequestData
|
||||
onSuccess?: DataCallback<ResponseData>
|
||||
onError?: (error: ErrorInfo) => void
|
||||
setLoading?: (loading: boolean) => void
|
||||
showError?: boolean
|
||||
data?: any
|
||||
}
|
||||
|
||||
interface IAxiosRequest {
|
||||
export interface FrontPush<DataType> extends IFrontRequest<DataType, undefined> {
|
||||
data: DataType
|
||||
}
|
||||
export interface FrontPull<DataType> extends IFrontRequest<undefined, DataType>{
|
||||
onSuccess: DataCallback<DataType>
|
||||
}
|
||||
|
||||
export interface FrontExchange<RequestData, ResponseData> extends IFrontRequest<RequestData, ResponseData>{
|
||||
data: RequestData
|
||||
onSuccess: DataCallback<ResponseData>
|
||||
}
|
||||
|
||||
export interface FrontAction extends IFrontRequest<undefined, undefined>{}
|
||||
|
||||
interface IAxiosRequest<RequestData, ResponseData> {
|
||||
endpoint: string
|
||||
request?: IFrontRequest
|
||||
title?: string
|
||||
request: IFrontRequest<RequestData, ResponseData>
|
||||
title: string
|
||||
options?: AxiosRequestConfig
|
||||
}
|
||||
|
||||
// ================= Export API ==============
|
||||
export async function postLogin(request?: IFrontRequest) {
|
||||
await AxiosPost({
|
||||
title: 'Login',
|
||||
endpoint: `${config.url.AUTH}login`,
|
||||
request
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAuth(request?: IFrontRequest) {
|
||||
await AxiosGet<ICurrentUser>({
|
||||
// ==================== API ====================
|
||||
export function getAuth(request: FrontPull<ICurrentUser>) {
|
||||
AxiosGet({
|
||||
title: 'Current user',
|
||||
endpoint: `${config.url.AUTH}auth`,
|
||||
request
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export async function getProfile(request?: IFrontRequest) {
|
||||
await AxiosGet<IUserProfile>({
|
||||
title: 'Current user profile',
|
||||
endpoint: `${config.url.AUTH}profile`,
|
||||
request
|
||||
export function postLogin(request: FrontPush<IUserLoginData>) {
|
||||
AxiosPost({
|
||||
title: 'Login',
|
||||
endpoint: `${config.url.AUTH}login`,
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export async function postLogout(request?: IFrontRequest) {
|
||||
await AxiosPost({
|
||||
export function postLogout(request: FrontAction) {
|
||||
AxiosPost({
|
||||
title: 'Logout',
|
||||
endpoint: `${config.url.AUTH}logout`,
|
||||
request
|
||||
request: request
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export async function postSignup(request?: IFrontRequest) {
|
||||
await AxiosPost({
|
||||
export function postSignup(request: IFrontRequest<IUserSignupData, IUserProfile>) {
|
||||
AxiosPost({
|
||||
title: 'Register user',
|
||||
endpoint: `${config.url.AUTH}signup`,
|
||||
request
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export async function getActiveUsers(request?: IFrontRequest) {
|
||||
await AxiosGet<IUserInfo>({
|
||||
export function getProfile(request: FrontPull<IUserProfile>) {
|
||||
AxiosGet({
|
||||
title: 'Current user profile',
|
||||
endpoint: `${config.url.AUTH}profile`,
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export function getActiveUsers(request: FrontPull<IUserInfo[]>) {
|
||||
AxiosGet({
|
||||
title: 'Active users list',
|
||||
endpoint: `${config.url.AUTH}active-users`,
|
||||
request
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export async function getRSForms(filter: RSFormsFilter, request?: IFrontRequest) {
|
||||
let endpoint: string = ''
|
||||
if (filter.type === FilterType.PERSONAL) {
|
||||
endpoint = `${config.url.BASE}rsforms?owner=${filter.data as number}`
|
||||
} else {
|
||||
endpoint = `${config.url.BASE}rsforms?is_common=true`
|
||||
}
|
||||
|
||||
await AxiosGet<IRSForm[]>({
|
||||
export function getRSForms(filter: RSFormsFilter, request: FrontPull<IRSFormMeta[]>) {
|
||||
const endpoint =
|
||||
filter.type === FilterType.PERSONAL
|
||||
? `${config.url.BASE}rsforms?owner=${filter.data as number}`
|
||||
: `${config.url.BASE}rsforms?is_common=true`;
|
||||
AxiosGet({
|
||||
title: 'RSForms list',
|
||||
endpoint,
|
||||
request
|
||||
endpoint: endpoint,
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export async function postNewRSForm(request?: IFrontRequest) {
|
||||
await AxiosPost({
|
||||
export function postNewRSForm(request: FrontExchange<IRSFormCreateData, IRSFormMeta>) {
|
||||
AxiosPost({
|
||||
title: 'New RSForm',
|
||||
endpoint: `${config.url.BASE}rsforms/create-detailed/`,
|
||||
request
|
||||
request: request,
|
||||
options: {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function getRSFormDetails(target: string, request?: IFrontRequest) {
|
||||
await AxiosGet<IRSForm>({
|
||||
export function getRSFormDetails(target: string, request: FrontPull<IRSFormData>) {
|
||||
AxiosGet({
|
||||
title: `RSForm details for id=${target}`,
|
||||
endpoint: `${config.url.BASE}rsforms/${target}/details/`,
|
||||
request
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export async function patchRSForm(target: string, request?: IFrontRequest) {
|
||||
await AxiosPatch({
|
||||
export function patchRSForm(target: string, request: FrontExchange<IRSFormUpdateData, IRSFormMeta>) {
|
||||
AxiosPatch({
|
||||
title: `RSForm id=${target}`,
|
||||
endpoint: `${config.url.BASE}rsforms/${target}/`,
|
||||
request
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export async function patchConstituenta(target: string, request?: IFrontRequest) {
|
||||
await AxiosPatch({
|
||||
title: `Constituenta id=${target}`,
|
||||
endpoint: `${config.url.BASE}constituents/${target}/`,
|
||||
request
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteRSForm(target: string, request?: IFrontRequest) {
|
||||
await AxiosDelete({
|
||||
export function deleteRSForm(target: string, request: FrontAction) {
|
||||
AxiosDelete({
|
||||
title: `RSForm id=${target}`,
|
||||
endpoint: `${config.url.BASE}rsforms/${target}/`,
|
||||
request
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export async function getTRSFile(target: string, request?: IFrontRequest) {
|
||||
await AxiosGetBlob({
|
||||
title: `RSForm TRS file for id=${target}`,
|
||||
endpoint: `${config.url.BASE}rsforms/${target}/export-trs/`,
|
||||
request
|
||||
});
|
||||
}
|
||||
|
||||
export async function postClaimRSForm(target: string, request?: IFrontRequest) {
|
||||
await AxiosPost({
|
||||
export function postClaimRSForm(target: string, request: FrontPull<IRSFormMeta>) {
|
||||
AxiosPost({
|
||||
title: `Claim on RSForm id=${target}`,
|
||||
endpoint: `${config.url.BASE}rsforms/${target}/claim/`,
|
||||
request
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export async function postCheckExpression(schema: string, request?: IFrontRequest) {
|
||||
await AxiosPost({
|
||||
title: `Check expression for RSForm id=${schema}: ${request?.data.expression as string}`,
|
||||
endpoint: `${config.url.BASE}rsforms/${schema}/check/`,
|
||||
request
|
||||
export function getTRSFile(target: string, request: FrontPull<Blob>) {
|
||||
AxiosGet({
|
||||
title: `RSForm TRS file for id=${target}`,
|
||||
endpoint: `${config.url.BASE}rsforms/${target}/export-trs/`,
|
||||
request: request,
|
||||
options: { responseType: 'blob' }
|
||||
});
|
||||
}
|
||||
|
||||
export async function postNewConstituenta(schema: string, request?: IFrontRequest) {
|
||||
await AxiosPost({
|
||||
title: `New Constituenta for RSForm id=${schema}: ${request?.data.alias as string}`,
|
||||
export function postNewConstituenta(schema: string, request: FrontExchange<ICstCreateData, ICstCreatedResponse>) {
|
||||
AxiosPost({
|
||||
title: `New Constituenta for RSForm id=${schema}: ${request.data.alias}`,
|
||||
endpoint: `${config.url.BASE}rsforms/${schema}/cst-create/`,
|
||||
request
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export async function patchDeleteConstituenta(schema: string, request?: IFrontRequest) {
|
||||
await AxiosPatch<IRSForm>({
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
title: `Delete Constituents for RSForm id=${schema}: ${request?.data.items.toString()}`,
|
||||
export function patchDeleteConstituenta(schema: string, request: FrontExchange<IConstituentaList, IRSFormData>) {
|
||||
AxiosPatch({
|
||||
title: `Delete Constituents for RSForm id=${schema}: ${request.data.items.map(item => String(item.id)).join(' ')}`,
|
||||
endpoint: `${config.url.BASE}rsforms/${schema}/cst-multidelete/`,
|
||||
request
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export async function patchMoveConstituenta(schema: string, request?: IFrontRequest) {
|
||||
await AxiosPatch<IRSForm>({
|
||||
title: `Moving Constituents for RSForm id=${schema}: ${JSON.stringify(request?.data.items)} to ${request?.data.move_to as number}`,
|
||||
export function patchConstituenta(target: string, request: FrontExchange<ICstUpdateData, IConstituentaMeta>) {
|
||||
AxiosPatch({
|
||||
title: `Constituenta id=${target}`,
|
||||
endpoint: `${config.url.BASE}constituents/${target}/`,
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export function patchMoveConstituenta(schema: string, request: FrontExchange<ICstMovetoData, IRSFormData>) {
|
||||
AxiosPatch({
|
||||
title: `Moving Constituents for RSForm id=${schema}: ${JSON.stringify(request.data.items)} to ${request.data.move_to}`,
|
||||
endpoint: `${config.url.BASE}rsforms/${schema}/cst-moveto/`,
|
||||
request
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
// ====== Helper functions ===========
|
||||
async function AxiosGet<ReturnType>({ endpoint, request, title }: IAxiosRequest) {
|
||||
if (title) console.log(`[[${title}]] requested`);
|
||||
if (request?.setLoading) request?.setLoading(true);
|
||||
axios.get<ReturnType>(endpoint)
|
||||
export function postCheckExpression(schema: string, request: FrontExchange<RSExpression, ExpressionParse>) {
|
||||
AxiosPost({
|
||||
title: `Check expression for RSForm id=${schema}: ${request.data.expression }`,
|
||||
endpoint: `${config.url.BASE}rsforms/${schema}/check/`,
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
// ============ Helper functions =============
|
||||
function AxiosGet<ResponseData>({ endpoint, request, title, options }: IAxiosRequest<undefined, ResponseData>) {
|
||||
console.log(`[[${title}]] requested`);
|
||||
if (request.setLoading) request?.setLoading(true);
|
||||
axios.get<ResponseData>(endpoint, options)
|
||||
.then((response) => {
|
||||
if (request?.setLoading) request?.setLoading(false);
|
||||
if (request?.onSuccess) request.onSuccess(response);
|
||||
if (request.setLoading) request.setLoading(false);
|
||||
if (request.onSuccess) request.onSuccess(response.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (request?.setLoading) request?.setLoading(false);
|
||||
if (request?.showError) toast.error(error.message);
|
||||
if (request?.onError) request.onError(error);
|
||||
if (request.setLoading) request.setLoading(false);
|
||||
if (request.showError) toast.error(error.message);
|
||||
if (request.onError) request.onError(error);
|
||||
});
|
||||
}
|
||||
|
||||
async function AxiosGetBlob({ endpoint, request, title }: IAxiosRequest) {
|
||||
if (title) console.log(`[[${title}]] requested`);
|
||||
if (request?.setLoading) request?.setLoading(true);
|
||||
axios.get(endpoint, { responseType: 'blob' })
|
||||
function AxiosPost<RequestData, ResponseData>(
|
||||
{ endpoint, request, title, options }: IAxiosRequest<RequestData, ResponseData>
|
||||
) {
|
||||
console.log(`[[${title}]] posted`);
|
||||
if (request.setLoading) request.setLoading(true);
|
||||
axios.post<ResponseData>(endpoint, request.data, options)
|
||||
.then((response) => {
|
||||
if (request?.setLoading) request?.setLoading(false);
|
||||
if (request?.onSuccess) request.onSuccess(response);
|
||||
if (request.setLoading) request.setLoading(false);
|
||||
if (request.onSuccess) request.onSuccess(response.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (request?.setLoading) request?.setLoading(false);
|
||||
if (request?.showError) toast.error(error.message);
|
||||
if (request?.onError) request.onError(error);
|
||||
if (request.setLoading) request.setLoading(false);
|
||||
if (request.showError) toast.error(error.message);
|
||||
if (request.onError) request.onError(error);
|
||||
});
|
||||
}
|
||||
|
||||
async function AxiosPost({ endpoint, request, title }: IAxiosRequest) {
|
||||
if (title) console.log(`[[${title}]] posted`);
|
||||
if (request?.setLoading) request?.setLoading(true);
|
||||
axios.post(endpoint, request?.data)
|
||||
function AxiosDelete<RequestData, ResponseData>(
|
||||
{ endpoint, request, title, options }: IAxiosRequest<RequestData, ResponseData>
|
||||
) {
|
||||
console.log(`[[${title}]] is being deleted`);
|
||||
if (request.setLoading) request.setLoading(true);
|
||||
axios.delete<ResponseData>(endpoint, options)
|
||||
.then((response) => {
|
||||
if (request?.setLoading) request?.setLoading(false);
|
||||
if (request?.onSuccess) request.onSuccess(response);
|
||||
if (request.setLoading) request.setLoading(false);
|
||||
if (request.onSuccess) request.onSuccess(response.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (request?.setLoading) request?.setLoading(false);
|
||||
if (request?.showError) toast.error(error.message);
|
||||
if (request?.onError) request.onError(error);
|
||||
if (request.setLoading) request.setLoading(false);
|
||||
if (request.showError) toast.error(error.message);
|
||||
if (request.onError) request.onError(error);
|
||||
});
|
||||
}
|
||||
|
||||
async function AxiosDelete({ endpoint, request, title }: IAxiosRequest) {
|
||||
if (title) console.log(`[[${title}]] is being deleted`);
|
||||
if (request?.setLoading) request?.setLoading(true);
|
||||
axios.delete(endpoint)
|
||||
function AxiosPatch<RequestData, ResponseData>(
|
||||
{ endpoint, request, title, options }: IAxiosRequest<RequestData, ResponseData>
|
||||
) {
|
||||
console.log(`[[${title}]] is being patrially updated`);
|
||||
if (request.setLoading) request.setLoading(true);
|
||||
axios.patch<ResponseData>(endpoint, request.data, options)
|
||||
.then((response) => {
|
||||
if (request?.setLoading) request?.setLoading(false);
|
||||
if (request?.onSuccess) request.onSuccess(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (request?.setLoading) request?.setLoading(false);
|
||||
if (request?.showError) toast.error(error.message);
|
||||
if (request?.onError) request.onError(error);
|
||||
});
|
||||
}
|
||||
|
||||
async function AxiosPatch<ReturnType>({ endpoint, request, title }: IAxiosRequest) {
|
||||
if (title) console.log(`[[${title}]] is being patrially updated`);
|
||||
if (request?.setLoading) request?.setLoading(true);
|
||||
axios.patch<ReturnType>(endpoint, request?.data)
|
||||
.then((response) => {
|
||||
if (request?.setLoading) request?.setLoading(false);
|
||||
if (request?.onSuccess) request.onSuccess(response);
|
||||
if (request.setLoading) request.setLoading(false);
|
||||
if (request.onSuccess) request.onSuccess(response.data);
|
||||
return response.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
if (request?.setLoading) request?.setLoading(false);
|
||||
if (request?.showError) toast.error(error.message);
|
||||
if (request?.onError) request.onError(error);
|
||||
if (request.setLoading) request.setLoading(false);
|
||||
if (request.showError) toast.error(error.message);
|
||||
if (request.onError) request.onError(error);
|
||||
});
|
||||
}
|
||||
|
|
160
rsconcept/frontend/src/utils/enums.ts
Normal file
160
rsconcept/frontend/src/utils/enums.ts
Normal file
|
@ -0,0 +1,160 @@
|
|||
//! RS language token types enumeration
|
||||
export enum TokenID {
|
||||
// Global, local IDs and literals
|
||||
ID_LOCAL = 258,
|
||||
ID_GLOBAL,
|
||||
ID_FUNCTION,
|
||||
ID_PREDICATE,
|
||||
ID_RADICAL,
|
||||
LIT_INTEGER,
|
||||
LIT_INTSET,
|
||||
LIT_EMPTYSET,
|
||||
|
||||
// Aithmetic
|
||||
PLUS,
|
||||
MINUS,
|
||||
MULTIPLY,
|
||||
|
||||
// Integer predicate symbols
|
||||
GREATER,
|
||||
LESSER,
|
||||
GREATER_OR_EQ,
|
||||
LESSER_OR_EQ,
|
||||
|
||||
// Equality comparison
|
||||
EQUAL,
|
||||
NOTEQUAL,
|
||||
|
||||
// Logic predicate symbols
|
||||
FORALL,
|
||||
EXISTS,
|
||||
NOT,
|
||||
EQUIVALENT,
|
||||
IMPLICATION,
|
||||
OR,
|
||||
AND,
|
||||
|
||||
// Set theory predicate symbols
|
||||
IN,
|
||||
NOTIN,
|
||||
SUBSET,
|
||||
SUBSET_OR_EQ,
|
||||
NOTSUBSET,
|
||||
|
||||
// Set theory operators
|
||||
DECART,
|
||||
UNION,
|
||||
INTERSECTION,
|
||||
SET_MINUS,
|
||||
SYMMINUS,
|
||||
BOOLEAN,
|
||||
|
||||
// Structure operations
|
||||
BIGPR,
|
||||
SMALLPR,
|
||||
FILTER,
|
||||
CARD,
|
||||
BOOL,
|
||||
DEBOOL,
|
||||
REDUCE,
|
||||
|
||||
// Term constructions prefixes
|
||||
DECLARATIVE,
|
||||
RECURSIVE,
|
||||
IMPERATIVE,
|
||||
|
||||
// Punctuation
|
||||
PUNC_DEFINE,
|
||||
PUNC_STRUCT,
|
||||
PUNC_ASSIGN,
|
||||
PUNC_ITERATE,
|
||||
PUNC_PL,
|
||||
PUNC_PR,
|
||||
PUNC_CL,
|
||||
PUNC_CR,
|
||||
PUNC_SL,
|
||||
PUNC_SR,
|
||||
PUNC_BAR,
|
||||
PUNC_COMMA,
|
||||
PUNC_SEMICOLON,
|
||||
|
||||
// ======= Non-terminal tokens =========
|
||||
NT_ENUM_DECL, // Перечисление переменных в кванторной декларации
|
||||
NT_TUPLE, // Кортеж (a,b,c), типизация B(T(a)xT(b)xT(c))
|
||||
NT_ENUMERATION, // Задание множества перечислением
|
||||
NT_TUPLE_DECL, // Декларация переменных с помощью кортежа
|
||||
NT_ARG_DECL, // Объявление аргумента
|
||||
|
||||
NT_FUNC_DEFINITION, // Определение функции
|
||||
NT_ARGUMENTS, // Задание аргументов функции
|
||||
NT_FUNC_CALL, // Вызов функции
|
||||
|
||||
NT_DECLARATIVE_EXPR, // Задание множества с помощью выражения D{x из H | A(x) }
|
||||
NT_IMPERATIVE_EXPR, // Императивное определение
|
||||
NT_RECURSIVE_FULL, // Полная рекурсия
|
||||
NT_RECURSIVE_SHORT, // Сокращенная рекурсия
|
||||
|
||||
NT_IMP_DECLARE, // Блок декларации
|
||||
NT_IMP_ASSIGN, // Блок присвоения
|
||||
NT_IMP_LOGIC, // Блок проверки
|
||||
|
||||
// ======= Helper tokens ========
|
||||
INTERRUPT,
|
||||
END,
|
||||
}
|
||||
|
||||
export enum RSError {
|
||||
|
||||
}
|
||||
|
||||
// '8201': 'Число превышает максимально допустимое значение 2147483647!',
|
||||
// '8203': 'Нераспознанный символ!',
|
||||
// '8400': 'Неопределенная синтаксическая ошибка!',
|
||||
// '8406': 'Пропущена скобка ‘)’!',
|
||||
// '8407': 'Пропущена скобка ‘}’!',
|
||||
// '8408': 'Некорректная кванторная декларация переменной!',
|
||||
// '8414': 'Некорректное объявление аргументов функции!',
|
||||
// '8415': 'Некорректное имя локальной переменной в декларации функции!',
|
||||
// '2801': 'Повторное объявление локальной переменной!',
|
||||
// '2802': 'Локальная переменная объявлена, но не использована!',
|
||||
// '8801': 'Использование необъявленной локальной переменной!',
|
||||
// '8802': 'Повторное объявление локальной переменной внутри области действия!',
|
||||
// '8803': 'Типизация операндов не совпадает!',
|
||||
// '8804': 'Использована конституента с неопределенной типизацией!',
|
||||
// '8805': 'Одна из проекций декартова произведения не является типизированным множеством имеющим характер множества!',
|
||||
// '8806': 'Аргумент булеана не является типизированным множеством имеющим характер множества!',
|
||||
// '8807': 'Операнд теоретико-множественного оператора не является типизированным множеством имеющим характер множества!',
|
||||
// '8808': 'Операнд оператора card не является типизированным множеством имеющим характер множества!',
|
||||
// '8809': 'Операнд оператора debool не является типизированным множеством имеющим характер множества!',
|
||||
// '880A': 'Неизвестное имя функции!',
|
||||
// '880B': 'Некорректное использование имени функции без аргументов!',
|
||||
// '8810': 'Операнд оператора red не является типизированным множеством имеющим характер двойного булеана!',
|
||||
// '8811': 'Некорректная типизация аргумента: проекция не определена!',
|
||||
// '8812': 'Некорректная типизация аргумента: T(Pri(a)) = B(Pi(D(T(a))))!',
|
||||
// '8813': 'Типизация элементов перечисления не совпадает!',
|
||||
// '8814': 'Некорректная декларация связанных локальных переменных: количестве переменных в кортеже не соответствует размерности декартова произведения типизации!',
|
||||
// '8815': 'Локальная переменная используется вне области действия!',
|
||||
// '8816': 'Несоответствие типизаций операндов для предиката!',
|
||||
// '8818': 'Некорректное количество аргументов терм-функции!',
|
||||
// '8819': 'Типизация аргумента терм-функции не совпадает с объявленной!',
|
||||
// '881A': 'Сравнение кортежа или элемента с пустым множеством!',
|
||||
// '881C': 'Выражение родовой структуры должно быть ступенью!',
|
||||
// '881F': 'Ожидалось выражение объявления функции!',
|
||||
// '8820': 'Некорректное использование пустого множества как типизированного выражения!',
|
||||
// '8821': 'Радикалы запрещены вне деклараций терм-функций!',
|
||||
// '8822': 'Типизация аргумента фильтра не корректна!',
|
||||
// '8823': 'Количество параметров фильтра не соответствует количеству индексов!',
|
||||
// '8824': 'Для выбранного типа не поддерживаются арифметические операции!',
|
||||
// '8825': 'Типизации не совместимы для выбранной операции/предиката!',
|
||||
// '8826': 'Для выбранного типа не поддерживаются предикаты порядка!',
|
||||
// '8840': 'Используется неинтерпретируемый глобальный идентификатор!',
|
||||
// '8841': 'Использование свойства в качестве значения!',
|
||||
// '8842': 'Не удалось получить дерево разбора для глобального идентификатора!',
|
||||
// '8843': 'Функция не интерпретируется для данных аргументов!',
|
||||
// '8A00': 'Неизвестная ошибка: вычисление прервано!',
|
||||
// '8A01': 'Превышен пределен количества элементов множества!',
|
||||
// '8A02': 'Превышен пределен количества элементов в основании булеана!',
|
||||
// '8A03': 'Использование конституенты с неопределенным значением!',
|
||||
// '8A04': 'Превышен предел количества итераций!',
|
||||
// '8A05': 'Попытка взять debool от многоэлементного множества!',
|
||||
// '8A06': 'Попытка перебрать бесконечное множество!'
|
|
@ -1,56 +1,27 @@
|
|||
// Current user info
|
||||
export interface ICurrentUser {
|
||||
id: number
|
||||
// ========= Users ===========
|
||||
export interface IUser {
|
||||
id: number | null
|
||||
username: string
|
||||
is_staff: boolean
|
||||
}
|
||||
|
||||
// User profile data
|
||||
export interface IUserProfile {
|
||||
id: number
|
||||
username: string
|
||||
email: string
|
||||
first_name: string
|
||||
last_name: string
|
||||
}
|
||||
|
||||
// User base info
|
||||
export interface IUserInfo {
|
||||
id: number
|
||||
username: string
|
||||
first_name: string
|
||||
last_name: string
|
||||
export interface ICurrentUser extends Pick<IUser, 'id' | 'username' | 'is_staff'> {}
|
||||
|
||||
export interface IUserLoginData extends Pick<IUser, 'username'> {
|
||||
password: string
|
||||
}
|
||||
|
||||
// User data for signup
|
||||
export interface IUserSignupData {
|
||||
username: string
|
||||
email: string
|
||||
first_name: string
|
||||
last_name: string
|
||||
export interface IUserSignupData extends Omit<IUser, 'is_staff' | 'id'> {
|
||||
password: string
|
||||
password2: string
|
||||
}
|
||||
export interface IUserProfile extends Omit<IUser, 'is_staff'> {}
|
||||
export interface IUserInfo extends Omit<IUserProfile, 'email'> {}
|
||||
|
||||
// User data for signup
|
||||
export interface INewCstData {
|
||||
alias: string
|
||||
csttype: CstType
|
||||
insert_after?: number
|
||||
}
|
||||
|
||||
// Constituenta type
|
||||
export enum CstType {
|
||||
BASE = 'basic',
|
||||
CONSTANT = 'constant',
|
||||
STRUCTURED = 'structure',
|
||||
AXIOM = 'axiom',
|
||||
TERM = 'term',
|
||||
FUNCTION = 'function',
|
||||
PREDICATE = 'predicate',
|
||||
THEOREM = 'theorem'
|
||||
}
|
||||
|
||||
// ======== Parsing ============
|
||||
// ValueClass
|
||||
export enum ValueClass {
|
||||
INVALID = 'invalid',
|
||||
|
@ -72,25 +43,56 @@ export enum ParsingStatus {
|
|||
INCORRECT = 'incorrect'
|
||||
}
|
||||
|
||||
// Constituenta data
|
||||
export interface RSErrorDescription {
|
||||
errorType: number
|
||||
position: number
|
||||
isCritical: boolean
|
||||
params: string[]
|
||||
}
|
||||
|
||||
export interface ExpressionParse {
|
||||
parseResult: boolean
|
||||
syntax: Syntax
|
||||
typification: string
|
||||
valueClass: ValueClass
|
||||
astText: string
|
||||
errors: RSErrorDescription[]
|
||||
}
|
||||
|
||||
export interface RSExpression {
|
||||
expression: string
|
||||
}
|
||||
|
||||
// ====== Constituenta ==========
|
||||
export enum CstType {
|
||||
BASE = 'basic',
|
||||
STRUCTURED = 'structure',
|
||||
TERM = 'term',
|
||||
AXIOM = 'axiom',
|
||||
FUNCTION = 'function',
|
||||
PREDICATE = 'predicate',
|
||||
CONSTANT = 'constant',
|
||||
THEOREM = 'theorem'
|
||||
}
|
||||
|
||||
export interface IConstituenta {
|
||||
id: number
|
||||
alias: string
|
||||
cstType: CstType
|
||||
convention?: string
|
||||
term?: {
|
||||
convention: string
|
||||
term: {
|
||||
raw: string
|
||||
resolved?: string
|
||||
forms?: string[]
|
||||
resolved: string
|
||||
forms: string[]
|
||||
}
|
||||
definition?: {
|
||||
definition: {
|
||||
formal: string
|
||||
text: {
|
||||
raw: string
|
||||
resolved?: string
|
||||
resolved: string
|
||||
}
|
||||
}
|
||||
parse?: {
|
||||
parse: {
|
||||
status: ParsingStatus
|
||||
valueClass: ValueClass
|
||||
typification: string
|
||||
|
@ -98,7 +100,42 @@ export interface IConstituenta {
|
|||
}
|
||||
}
|
||||
|
||||
// RSForm stats
|
||||
export interface IConstituentaMeta {
|
||||
id: number
|
||||
schema: number
|
||||
order: number
|
||||
alias: string
|
||||
convention: string
|
||||
cst_type: CstType
|
||||
definition_formal: string
|
||||
definition_raw: string
|
||||
definition_resolved: string
|
||||
term_raw: string
|
||||
term_resolved: string
|
||||
}
|
||||
|
||||
export interface IConstituentaID extends Pick<IConstituentaMeta, 'id'>{}
|
||||
export interface IConstituentaList {
|
||||
items: IConstituentaID[]
|
||||
}
|
||||
|
||||
export interface ICstCreateData extends Pick<IConstituentaMeta, 'alias' | 'cst_type'> {
|
||||
insert_after: number | null
|
||||
}
|
||||
|
||||
export interface ICstMovetoData extends IConstituentaList {
|
||||
move_to: number
|
||||
}
|
||||
|
||||
export interface ICstUpdateData
|
||||
extends Pick<IConstituentaMeta, 'id' | 'alias' | 'convention' | 'definition_formal' | 'definition_raw' | 'term_raw'> {}
|
||||
|
||||
export interface ICstCreatedResponse {
|
||||
new_cst: IConstituentaMeta
|
||||
schema: IRSFormData
|
||||
}
|
||||
|
||||
// ========== RSForm ============
|
||||
export interface IRSFormStats {
|
||||
count_all: number
|
||||
count_errors: number
|
||||
|
@ -117,7 +154,6 @@ export interface IRSFormStats {
|
|||
count_theorem: number
|
||||
}
|
||||
|
||||
// RSForm data
|
||||
export interface IRSForm {
|
||||
id: number
|
||||
title: string
|
||||
|
@ -126,125 +162,24 @@ export interface IRSForm {
|
|||
is_common: boolean
|
||||
time_create: string
|
||||
time_update: string
|
||||
owner?: number
|
||||
items?: IConstituenta[]
|
||||
stats?: IRSFormStats
|
||||
owner: number | null
|
||||
items: IConstituenta[]
|
||||
stats: IRSFormStats
|
||||
}
|
||||
|
||||
// RSForm user input
|
||||
export interface IRSFormCreateData {
|
||||
title: string
|
||||
alias: string
|
||||
comment: string
|
||||
is_common: boolean
|
||||
export interface IRSFormData extends Omit<IRSForm, 'stats' > {}
|
||||
export interface IRSFormMeta extends Omit<IRSForm, 'items' | 'stats'> {}
|
||||
|
||||
export interface IRSFormUpdateData
|
||||
extends Omit<IRSFormMeta, 'time_create' | 'time_update' | 'id' | 'owner'> {}
|
||||
|
||||
export interface IRSFormCreateData
|
||||
extends IRSFormUpdateData {
|
||||
file?: File
|
||||
fileName?: string
|
||||
}
|
||||
|
||||
//! RS language token types enumeration
|
||||
export enum TokenID {
|
||||
// Global, local IDs and literals
|
||||
ID_LOCAL = 258,
|
||||
ID_GLOBAL,
|
||||
ID_FUNCTION,
|
||||
ID_PREDICATE,
|
||||
ID_RADICAL,
|
||||
LIT_INTEGER,
|
||||
LIT_INTSET,
|
||||
LIT_EMPTYSET,
|
||||
|
||||
// Aithmetic
|
||||
PLUS,
|
||||
MINUS,
|
||||
MULTIPLY,
|
||||
|
||||
// Integer predicate symbols
|
||||
GREATER,
|
||||
LESSER,
|
||||
GREATER_OR_EQ,
|
||||
LESSER_OR_EQ,
|
||||
|
||||
// Equality comparison
|
||||
EQUAL,
|
||||
NOTEQUAL,
|
||||
|
||||
// Logic predicate symbols
|
||||
FORALL,
|
||||
EXISTS,
|
||||
NOT,
|
||||
EQUIVALENT,
|
||||
IMPLICATION,
|
||||
OR,
|
||||
AND,
|
||||
|
||||
// Set theory predicate symbols
|
||||
IN,
|
||||
NOTIN,
|
||||
SUBSET,
|
||||
SUBSET_OR_EQ,
|
||||
NOTSUBSET,
|
||||
|
||||
// Set theory operators
|
||||
DECART,
|
||||
UNION,
|
||||
INTERSECTION,
|
||||
SET_MINUS,
|
||||
SYMMINUS,
|
||||
BOOLEAN,
|
||||
|
||||
// Structure operations
|
||||
BIGPR,
|
||||
SMALLPR,
|
||||
FILTER,
|
||||
CARD,
|
||||
BOOL,
|
||||
DEBOOL,
|
||||
REDUCE,
|
||||
|
||||
// Term constructions prefixes
|
||||
DECLARATIVE,
|
||||
RECURSIVE,
|
||||
IMPERATIVE,
|
||||
|
||||
// Punctuation
|
||||
PUNC_DEFINE,
|
||||
PUNC_STRUCT,
|
||||
PUNC_ASSIGN,
|
||||
PUNC_ITERATE,
|
||||
PUNC_PL,
|
||||
PUNC_PR,
|
||||
PUNC_CL,
|
||||
PUNC_CR,
|
||||
PUNC_SL,
|
||||
PUNC_SR,
|
||||
PUNC_BAR,
|
||||
PUNC_COMMA,
|
||||
PUNC_SEMICOLON,
|
||||
|
||||
// ======= Non-terminal tokens =========
|
||||
NT_ENUM_DECL, // Перечисление переменных в кванторной декларации
|
||||
NT_TUPLE, // Кортеж (a,b,c), типизация B(T(a)xT(b)xT(c))
|
||||
NT_ENUMERATION, // Задание множества перечислением
|
||||
NT_TUPLE_DECL, // Декларация переменных с помощью кортежа
|
||||
NT_ARG_DECL, // Объявление аргумента
|
||||
|
||||
NT_FUNC_DEFINITION, // Определение функции
|
||||
NT_ARGUMENTS, // Задание аргументов функции
|
||||
NT_FUNC_CALL, // Вызов функции
|
||||
|
||||
NT_DECLARATIVE_EXPR, // Задание множества с помощью выражения D{x из H | A(x) }
|
||||
NT_IMPERATIVE_EXPR, // Императивное определение
|
||||
NT_RECURSIVE_FULL, // Полная рекурсия
|
||||
NT_RECURSIVE_SHORT, // Сокращенная рекурсия
|
||||
|
||||
NT_IMP_DECLARE, // Блок декларации
|
||||
NT_IMP_ASSIGN, // Блок присвоения
|
||||
NT_IMP_LOGIC, // Блок проверки
|
||||
|
||||
// ======= Helper tokens ========
|
||||
INTERRUPT,
|
||||
END,
|
||||
};
|
||||
|
||||
// ================ Misc types ================
|
||||
// Constituenta edit mode
|
||||
export enum EditMode {
|
||||
TEXT = 'text',
|
||||
|
@ -280,9 +215,10 @@ export function inferStatus(parse?: ParsingStatus, value?: ValueClass): Expressi
|
|||
return ExpressionStatus.VERIFIED
|
||||
}
|
||||
|
||||
export function CalculateStats(schema: IRSForm) {
|
||||
if (!schema.items) {
|
||||
schema.stats = {
|
||||
export function LoadRSFormData(schema: IRSFormData): IRSForm {
|
||||
const result = schema as IRSForm
|
||||
if (!result.items) {
|
||||
result.stats = {
|
||||
count_all: 0,
|
||||
count_errors: 0,
|
||||
count_property: 0,
|
||||
|
@ -299,22 +235,22 @@ export function CalculateStats(schema: IRSForm) {
|
|||
count_predicate: 0,
|
||||
count_theorem: 0
|
||||
}
|
||||
return;
|
||||
return result;
|
||||
}
|
||||
schema.stats = {
|
||||
count_all: schema.items?.length || 0,
|
||||
count_errors: schema.items?.reduce(
|
||||
result.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(
|
||||
count_property: schema.items.reduce(
|
||||
(sum, cst) => sum + (cst.parse?.valueClass === ValueClass.PROPERTY ? 1 : 0) || 0, 0),
|
||||
count_incalc: schema.items?.reduce(
|
||||
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: schema.items.reduce(
|
||||
(sum, cst) => (sum + (cst.term?.raw ? 1 : 0) || 0), 0),
|
||||
|
||||
count_base: schema.items?.reduce(
|
||||
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),
|
||||
|
@ -322,15 +258,16 @@ export function CalculateStats(schema: IRSForm) {
|
|||
(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(
|
||||
count_term: schema.items.reduce(
|
||||
(sum, cst) => sum + (cst.cstType === CstType.TERM ? 1 : 0), 0),
|
||||
count_function: schema.items?.reduce(
|
||||
count_function: schema.items.reduce(
|
||||
(sum, cst) => sum + (cst.cstType === CstType.FUNCTION ? 1 : 0), 0),
|
||||
count_predicate: schema.items?.reduce(
|
||||
count_predicate: schema.items.reduce(
|
||||
(sum, cst) => sum + (cst.cstType === CstType.PREDICATE ? 1 : 0), 0),
|
||||
count_theorem: schema.items?.reduce(
|
||||
count_theorem: schema.items.reduce(
|
||||
(sum, cst) => sum + (cst.cstType === CstType.THEOREM ? 1 : 0), 0)
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function matchConstituenta(query: string, target?: IConstituenta) {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import fileDownload from 'js-file-download';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import { type BackendCallback } from './backendAPI';
|
||||
import { type DataCallback } from './backendAPI';
|
||||
import { IRSFormMeta } from './models';
|
||||
|
||||
export function shareCurrentURLProc() {
|
||||
const url = window.location.href + '&share';
|
||||
|
@ -11,7 +12,7 @@ export function shareCurrentURLProc() {
|
|||
}
|
||||
|
||||
export function claimOwnershipProc(
|
||||
claim: (callback: BackendCallback) => void
|
||||
claim: (callback: DataCallback<IRSFormMeta>) => void
|
||||
) {
|
||||
if (!window.confirm('Вы уверены, что хотите стать владельцем данной схемы?')) {
|
||||
return;
|
||||
|
@ -20,7 +21,7 @@ export function claimOwnershipProc(
|
|||
}
|
||||
|
||||
export function deleteRSFormProc(
|
||||
destroy: (callback: BackendCallback) => void,
|
||||
destroy: (callback: DataCallback) => void,
|
||||
navigate: (path: string) => void
|
||||
) {
|
||||
if (!window.confirm('Вы уверены, что хотите удалить данную схему?')) {
|
||||
|
@ -33,14 +34,14 @@ export function deleteRSFormProc(
|
|||
}
|
||||
|
||||
export function downloadRSFormProc(
|
||||
download: (callback: BackendCallback) => void,
|
||||
download: (callback: DataCallback<Blob>) => void,
|
||||
fileName: string
|
||||
) {
|
||||
download((response) => {
|
||||
download((data) => {
|
||||
try {
|
||||
fileDownload(response.data, fileName);
|
||||
} catch (error: any) {
|
||||
toast.error(error.message);
|
||||
fileDownload(data, fileName);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { CstType, ExpressionStatus, type IConstituenta, type IRSForm, ParsingStatus, TokenID } from './models';
|
||||
import { TokenID } from './enums';
|
||||
import { CstType, ExpressionStatus, type IConstituenta, type IRSForm, ParsingStatus, ValueClass } from './models';
|
||||
|
||||
export interface IRSButtonData {
|
||||
text: string
|
||||
|
@ -195,6 +196,20 @@ export function getCstTypeLabel(type: CstType) {
|
|||
}
|
||||
}
|
||||
|
||||
export function getCstTypeShortcut(type: CstType) {
|
||||
const prefix = getCstTypeLabel(type) + ' [Alt + ';
|
||||
switch (type) {
|
||||
case CstType.BASE: return prefix + '1]';
|
||||
case CstType.STRUCTURED: return prefix + '2]';
|
||||
case CstType.TERM: return prefix + '3]';
|
||||
case CstType.AXIOM: return prefix + '4]';
|
||||
case CstType.FUNCTION: return prefix + 'Q]';
|
||||
case CstType.PREDICATE: return prefix + 'W]';
|
||||
case CstType.CONSTANT: return prefix + '5]';
|
||||
case CstType.THEOREM: return prefix + '6]';
|
||||
}
|
||||
}
|
||||
|
||||
export const CstTypeSelector = (Object.values(CstType)).map(
|
||||
(typeStr) => {
|
||||
const type = typeStr as CstType;
|
||||
|
@ -272,3 +287,30 @@ export function createAliasFor(type: CstType, schema: IRSForm): string {
|
|||
}, 1);
|
||||
return `${prefix}${index}`;
|
||||
}
|
||||
|
||||
export function getMockConstituenta(id: number, alias: string, type: CstType, comment: string): IConstituenta {
|
||||
return {
|
||||
id: id,
|
||||
alias: alias,
|
||||
convention: comment,
|
||||
cstType: type,
|
||||
term: {
|
||||
raw: '',
|
||||
resolved: '',
|
||||
forms: []
|
||||
},
|
||||
definition: {
|
||||
formal: '',
|
||||
text: {
|
||||
raw: '',
|
||||
resolved: ''
|
||||
}
|
||||
},
|
||||
parse: {
|
||||
status: ParsingStatus.INCORRECT,
|
||||
valueClass: ValueClass.INVALID,
|
||||
typification: 'N/A',
|
||||
syntaxTree: ''
|
||||
}
|
||||
};
|
||||
}
|
1
rsconcept/frontend/src/vite-env.d.ts
vendored
Normal file
1
rsconcept/frontend/src/vite-env.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
|
@ -1,5 +1,5 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
export default {
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
'./src/**/*.{js,jsx,ts,tsx}',
|
||||
|
|
|
@ -1,26 +1,25 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
|
10
rsconcept/frontend/tsconfig.node.json
Normal file
10
rsconcept/frontend/tsconfig.node.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
10
rsconcept/frontend/vite.config.ts
Normal file
10
rsconcept/frontend/vite.config.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import react from '@vitejs/plugin-react'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
port: 3000
|
||||
}
|
||||
})
|
Loading…
Reference in New Issue
Block a user