From 764b21ea27dd2eaa0ee881c07b920aba4315eed0 Mon Sep 17 00:00:00 2001 From: Charles Perier <82757576+perierc@users.noreply.github.com> Date: Thu, 11 Apr 2024 11:05:58 +0200 Subject: [PATCH] feat: improve search results display (#484) * feat(frontend): make language selection for translations more intuitive (#461) The PR modifies the translations section of the edit entry page: * Changes "All languages" terminology to "Fallback translations" (fixes #458) * Adds an info alert if "en" (English) or "xx" (Fallback translations) is not the main language for an entry (fixes #457) / Fixes the fact that the alert message about changing the display name of a language appears even if the language had no translations and we add the first one (fixes #459) * Adds a "Show all existing translations" checkbox to see all the languages that currently have translations, with their translations Adds a possibility to "pin" languages to select them (so they stay in local storage and appear at the top for each entry), and a possibility to hide (unselect) these languages easily (with an icon next to their title) * Modifies the selection of new languages: I removed the "number of languages shown" button that had to be clicked to add a language, and created a "Show another language" button at the bottom of the section. Also, the dialog is now an autocomplete instead of a select, and you just type the languages that you want to add and see languages that are not selected, instead of seeing all current languages and being able to remove them. The autocomplete with the options is also automatically focused when opening the dialog. * Adds vite-plugin-svgr to easily import svg files in React --------- Co-authored-by: Charles Perier * feat: add property filter to search API (#456) * feat: add property filter to search API * chore: generate SDK * chore: Add info banners on the frontend (#473) * docs: add info banners * refactor: delete unnecessary components * fix: add line break * improve search results, and few other improvements * show translations instead of translated languages --------- Co-authored-by: Charles Perier Co-authored-by: Eric Nguyen --- backend/editor/models/node_models.py | 1 - backend/editor/models/search_models.py | 128 ++++- backend/openapi/openapi.json | 42 +- taxonomy-editor-frontend/package-lock.json | 543 +++++++++++++++++- taxonomy-editor-frontend/package.json | 1 + taxonomy-editor-frontend/src/App.tsx | 13 +- .../src/assets/icons/pushpin-line-grey.svg | 1 + .../src/backend-types/types.ts | 7 - taxonomy-editor-frontend/src/client/index.ts | 1 + .../client/models/EntryNodeSearchResult.ts | 2 + .../client/models/PropertyFilterSearchTerm.ts | 12 + .../src/components/CustomAlert.tsx | 23 - .../src/components/EntryNodesTableBody.tsx | 122 ++++ .../src/components/NodesTableBody.tsx | 48 -- .../src/components/ResponsiveAppBar.tsx | 4 +- .../src/components/WarningParsingErrors.tsx | 15 +- .../src/pages/go-to-project/index.tsx | 39 +- .../src/pages/home/index.tsx | 139 +++-- .../editentry/LanguageSelectionDialog.tsx | 87 ++- .../project/editentry/ListTranslations.tsx | 261 ++++++--- .../src/pages/project/editentry/index.tsx | 18 +- .../src/pages/project/export/index.tsx | 10 +- .../src/pages/project/root-nodes/index.tsx | 251 -------- .../pages/project/search/SearchResults.tsx | 49 +- .../src/pages/project/search/index.tsx | 11 +- .../src/pages/startproject/index.tsx | 247 ++++---- taxonomy-editor-frontend/src/vite-env.d.ts | 1 + taxonomy-editor-frontend/vite.config.ts | 3 +- 28 files changed, 1355 insertions(+), 724 deletions(-) create mode 100644 taxonomy-editor-frontend/src/assets/icons/pushpin-line-grey.svg create mode 100644 taxonomy-editor-frontend/src/client/models/PropertyFilterSearchTerm.ts delete mode 100644 taxonomy-editor-frontend/src/components/CustomAlert.tsx create mode 100644 taxonomy-editor-frontend/src/components/EntryNodesTableBody.tsx delete mode 100644 taxonomy-editor-frontend/src/components/NodesTableBody.tsx delete mode 100644 taxonomy-editor-frontend/src/pages/project/root-nodes/index.tsx diff --git a/backend/editor/models/node_models.py b/backend/editor/models/node_models.py index 28542888..1958010e 100644 --- a/backend/editor/models/node_models.py +++ b/backend/editor/models/node_models.py @@ -30,7 +30,6 @@ class EntryNodeCreate(BaseModel): class EntryNode(BaseModel): id: str preceding_lines: list[str] - src_position: int main_language: str tags: dict[str, list[str]] properties: dict[str, str] diff --git a/backend/editor/models/search_models.py b/backend/editor/models/search_models.py index 445bf1c3..b7443ded 100644 --- a/backend/editor/models/search_models.py +++ b/backend/editor/models/search_models.py @@ -1,8 +1,9 @@ +import re from abc import ABC, abstractmethod from dataclasses import dataclass, field from typing import Annotated, Literal -from pydantic import Field, StringConstraints, TypeAdapter, computed_field +from pydantic import Field, StringConstraints, TypeAdapter, computed_field, field_validator from .base_models import BaseModel from .node_models import EntryNode @@ -115,6 +116,130 @@ def build_cypher_query(self, param_name: str) -> CypherQuery: ) +class PropertyFilterSearchTerm(AbstractFilterSearchTerm): + filter_type: Literal["property"] + filter_value: str + + @field_validator("filter_value") + @classmethod + def validate_filter_value(cls, filter_value: str): + """ + The filter value is in the format `not:inherited:property_name:property_value` + where `not:` and `inherited:` and `:property_value` are optional. + Note that a property_name is always of format `name:lc`. + """ + parsed_value = filter_value + if parsed_value.startswith("not:"): + parsed_value = parsed_value[4:] + if parsed_value.startswith("inherited:"): + parsed_value = parsed_value[10:] + + assert ":" in parsed_value, "A property_name is mandatory and must contain a colon" + + terms = parsed_value.split(":") + property_name = terms[0] + ":" + terms[1] + + if not re.match(r"^[^:\\]+:[a-z]{2}$", property_name): + raise ValueError("Invalid property_name") + + return filter_value + + @computed_field + def negated(self) -> bool: + return self.filter_value.startswith("not:") + + @computed_field + def inherited(self) -> bool: + filter_value = self.get_parsed_filter_value(self.negated) + return filter_value.startswith("inherited:") + + @computed_field + def property_name(self) -> str: + filter_value = self.get_parsed_filter_value(self.negated, self.inherited) + terms = filter_value.split(":") + return terms[0] + "_" + terms[1] + + @computed_field + def property_value(self) -> str | None: + filter_value = self.get_parsed_filter_value(self.negated, self.inherited) + terms = filter_value.split(":") + return ":".join(terms[2:]) if len(terms) > 2 else None + + def get_parsed_filter_value(self, negated=False, inherited=False): + filter_value = self.filter_value + if negated: + filter_value = filter_value[4:] + if inherited: + filter_value = filter_value[10:] + return filter_value + + def build_cypher_query(self, param_name: str) -> CypherQuery: + branches = { + "negated": self.negated, + "inherited": self.inherited, + "with_value": self.property_value is not None, + } + match branches: + case {"negated": False, "inherited": False, "with_value": False}: + return CypherQuery(f"n.prop_{self.property_name} IS NOT NULL") + case {"negated": True, "inherited": False, "with_value": False}: + return CypherQuery(f"n.prop_{self.property_name} IS NULL") + case {"negated": False, "inherited": False, "with_value": True}: + return CypherQuery( + f"n.prop_{self.property_name} = ${param_name}", + {param_name: self.property_value}, + ) + case {"negated": True, "inherited": False, "with_value": True}: + return CypherQuery( + f"n.prop_{self.property_name} <> ${param_name}", + {param_name: self.property_value}, + ) + case {"negated": False, "inherited": True, "with_value": False}: + return CypherQuery( + f"""(n.prop_{self.property_name} IS NOT NULL OR + any( + ancestor IN [(n)<-[:is_child_of*]-(p:ENTRY) | p] + WHERE ancestor.prop_{self.property_name} IS NOT NULL) + )""", + ) + case {"negated": True, "inherited": True, "with_value": False}: + return CypherQuery( + f"""(n.prop_{self.property_name} IS NULL AND + all( + ancestor IN [(n)<-[:is_child_of*]-(p:ENTRY) | p] + WHERE ancestor.prop_{self.property_name} IS NULL) + )""", + ) + case {"negated": False, "inherited": True, "with_value": True}: + return CypherQuery( + f""" + [ + property IN + [n.prop_{self.property_name}] + + [(n)<-[:is_child_of*]-(p:ENTRY) | p.prop_{self.property_name}] + WHERE property IS NOT NULL + ][0] + = ${param_name}""", + {param_name: self.property_value}, + ) + case {"negated": True, "inherited": True, "with_value": True}: + return CypherQuery( + f"""((n.prop_{self.property_name} IS NULL AND + all( + ancestor IN [(n)<-[:is_child_of*]-(p:ENTRY) | p] + WHERE ancestor.prop_{self.property_name} IS NULL) + ) OR + [ + property IN + [n.prop_{self.property_name}] + + [(n)<-[:is_child_of*]-(p:ENTRY) | p.prop_{self.property_name}] + WHERE property IS NOT NULL + ][0] + <> ${param_name})""", + {param_name: self.property_value}, + ) + + FilterSearchTerm = Annotated[ ( IsFilterSearchTerm @@ -123,6 +248,7 @@ def build_cypher_query(self, param_name: str) -> CypherQuery: | ChildFilterSearchTerm | AncestorFilterSearchTerm | DescendantFilterSearchTerm + | PropertyFilterSearchTerm ), Field(discriminator="filter_type"), ] diff --git a/backend/openapi/openapi.json b/backend/openapi/openapi.json index 55db7b78..63b41911 100644 --- a/backend/openapi/openapi.json +++ b/backend/openapi/openapi.json @@ -1261,7 +1261,8 @@ { "$ref": "#/components/schemas/ParentFilterSearchTerm" }, { "$ref": "#/components/schemas/ChildFilterSearchTerm" }, { "$ref": "#/components/schemas/AncestorFilterSearchTerm" }, - { "$ref": "#/components/schemas/DescendantFilterSearchTerm" } + { "$ref": "#/components/schemas/DescendantFilterSearchTerm" }, + { "$ref": "#/components/schemas/PropertyFilterSearchTerm" } ], "discriminator": { "propertyName": "filterType", @@ -1271,7 +1272,8 @@ "descendant": "#/components/schemas/DescendantFilterSearchTerm", "is": "#/components/schemas/IsFilterSearchTerm", "language": "#/components/schemas/LanguageFilterSearchTerm", - "parent": "#/components/schemas/ParentFilterSearchTerm" + "parent": "#/components/schemas/ParentFilterSearchTerm", + "property": "#/components/schemas/PropertyFilterSearchTerm" } } }, @@ -1434,6 +1436,42 @@ "enum": ["OPEN", "EXPORTED", "LOADING", "FAILED"], "title": "ProjectStatus" }, + "PropertyFilterSearchTerm": { + "properties": { + "filterType": { "const": "property", "title": "Filtertype" }, + "filterValue": { "type": "string", "title": "Filtervalue" }, + "negated": { + "type": "boolean", + "title": "Negated", + "readOnly": true + }, + "inherited": { + "type": "boolean", + "title": "Inherited", + "readOnly": true + }, + "propertyName": { + "type": "string", + "title": "Propertyname", + "readOnly": true + }, + "propertyValue": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Propertyvalue", + "readOnly": true + } + }, + "type": "object", + "required": [ + "filterType", + "filterValue", + "negated", + "inherited", + "propertyName", + "propertyValue" + ], + "title": "PropertyFilterSearchTerm" + }, "ValidationError": { "properties": { "loc": { diff --git a/taxonomy-editor-frontend/package-lock.json b/taxonomy-editor-frontend/package-lock.json index caea5ab5..352cd9e5 100644 --- a/taxonomy-editor-frontend/package-lock.json +++ b/taxonomy-editor-frontend/package-lock.json @@ -49,6 +49,7 @@ "openapi-typescript-codegen": "^0.27.0", "prettier": "2.8.2", "vite": "^5.1.0", + "vite-plugin-svgr": "^4.2.0", "vite-tsconfig-paths": "^4.3.1" } }, @@ -8703,7 +8704,6 @@ "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", "dev": true, - "peer": true, "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" @@ -14606,7 +14606,6 @@ "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", "dev": true, - "peer": true, "dependencies": { "tslib": "^2.0.3" } @@ -14998,7 +14997,6 @@ "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", "dev": true, - "peer": true, "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" @@ -19963,6 +19961,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -20476,8 +20484,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/svgo": { "version": "1.3.2", @@ -20975,8 +20982,7 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true, - "peer": true + "dev": true }, "node_modules/tsutils": { "version": "3.21.0", @@ -21440,6 +21446,332 @@ } } }, + "node_modules/vite-plugin-svgr": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.2.0.tgz", + "integrity": "sha512-SC7+FfVtNQk7So0XMjrrtLAbEC8qjFPifyD7+fs/E6aaNdVde6umlVVh0QuwDLdOMu7vp5RiGFsB70nj5yo0XA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.5", + "@svgr/core": "^8.1.0", + "@svgr/plugin-jsx": "^8.1.0" + }, + "peerDependencies": { + "vite": "^2.6.0 || 3 || 4 || 5" + } + }, + "node_modules/vite-plugin-svgr/node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/vite-plugin-svgr/node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/vite-plugin-svgr/node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/vite-plugin-svgr/node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/vite-plugin-svgr/node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/vite-plugin-svgr/node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/vite-plugin-svgr/node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/vite-plugin-svgr/node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/vite-plugin-svgr/node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/vite-plugin-svgr/node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/vite-plugin-svgr/node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/vite-plugin-svgr/node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/vite-plugin-svgr/node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/vite-plugin-svgr/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/vite-plugin-svgr/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vite-plugin-svgr/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/vite-plugin-svgr/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/vite-plugin-svgr/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/vite-plugin-svgr/node_modules/typescript": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", + "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/vite-tsconfig-paths": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.1.tgz", @@ -28823,7 +29155,6 @@ "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", "dev": true, - "peer": true, "requires": { "no-case": "^3.0.4", "tslib": "^2.0.3" @@ -33244,7 +33575,6 @@ "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", "dev": true, - "peer": true, "requires": { "tslib": "^2.0.3" } @@ -33544,7 +33874,6 @@ "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", "dev": true, - "peer": true, "requires": { "lower-case": "^2.0.2", "tslib": "^2.0.3" @@ -37131,6 +37460,16 @@ } } }, + "snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "requires": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -37524,8 +37863,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", - "dev": true, - "peer": true + "dev": true }, "svgo": { "version": "1.3.2", @@ -37913,8 +38251,7 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true, - "peer": true + "dev": true }, "tsutils": { "version": "3.21.0", @@ -38245,6 +38582,184 @@ } } }, + "vite-plugin-svgr": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.2.0.tgz", + "integrity": "sha512-SC7+FfVtNQk7So0XMjrrtLAbEC8qjFPifyD7+fs/E6aaNdVde6umlVVh0QuwDLdOMu7vp5RiGFsB70nj5yo0XA==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.0.5", + "@svgr/core": "^8.1.0", + "@svgr/plugin-jsx": "^8.1.0" + }, + "dependencies": { + "@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + } + }, + "@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, + "requires": {} + }, + "@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, + "requires": {} + }, + "@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, + "requires": {} + }, + "@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, + "requires": {} + }, + "@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, + "requires": {} + }, + "@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, + "requires": {} + }, + "@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, + "requires": {} + }, + "@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, + "requires": {} + }, + "@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, + "requires": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + } + }, + "@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, + "requires": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + } + }, + "@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, + "requires": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + } + }, + "@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, + "requires": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "requires": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + } + }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true + }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "typescript": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", + "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", + "dev": true, + "optional": true, + "peer": true + } + } + }, "vite-tsconfig-paths": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.1.tgz", diff --git a/taxonomy-editor-frontend/package.json b/taxonomy-editor-frontend/package.json index 9273ed4e..321d2611 100644 --- a/taxonomy-editor-frontend/package.json +++ b/taxonomy-editor-frontend/package.json @@ -54,6 +54,7 @@ "openapi-typescript-codegen": "^0.27.0", "prettier": "2.8.2", "vite": "^5.1.0", + "vite-plugin-svgr": "^4.2.0", "vite-tsconfig-paths": "^4.3.1" }, "lint-staged": { diff --git a/taxonomy-editor-frontend/src/App.tsx b/taxonomy-editor-frontend/src/App.tsx index efc9ae87..514fd4df 100644 --- a/taxonomy-editor-frontend/src/App.tsx +++ b/taxonomy-editor-frontend/src/App.tsx @@ -1,9 +1,12 @@ -import { createBrowserRouter, RouterProvider } from "react-router-dom"; +import { + createBrowserRouter, + Navigate, + RouterProvider, +} from "react-router-dom"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { createTheme, CssBaseline, ThemeProvider } from "@mui/material"; -import { RootNodesWrapper } from "./pages/project/root-nodes"; import { EditEntryWrapper } from "./pages/project/editentry"; import { ExportTaxonomyWrapper } from "./pages/project/export"; import { GoToProject } from "./pages/go-to-project"; @@ -44,13 +47,17 @@ const router = createBrowserRouter([ loader: projectLoader(queryClient), errorElement: , children: [ + { + path: "", + element: , + }, { path: "export", element: , }, { path: "entry", - element: , + element: , }, { path: "entry/:id", diff --git a/taxonomy-editor-frontend/src/assets/icons/pushpin-line-grey.svg b/taxonomy-editor-frontend/src/assets/icons/pushpin-line-grey.svg new file mode 100644 index 00000000..e5cc0bbf --- /dev/null +++ b/taxonomy-editor-frontend/src/assets/icons/pushpin-line-grey.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/taxonomy-editor-frontend/src/backend-types/types.ts b/taxonomy-editor-frontend/src/backend-types/types.ts index 8822f20e..0749c0e6 100644 --- a/taxonomy-editor-frontend/src/backend-types/types.ts +++ b/taxonomy-editor-frontend/src/backend-types/types.ts @@ -1,8 +1 @@ -export type NodeInfo = { - id: string; - is_external: boolean; -}; - -export type RootEntriesAPIResponse = Array; - export type ParentsAPIResponse = string[]; diff --git a/taxonomy-editor-frontend/src/client/index.ts b/taxonomy-editor-frontend/src/client/index.ts index 120fac04..9f812d59 100644 --- a/taxonomy-editor-frontend/src/client/index.ts +++ b/taxonomy-editor-frontend/src/client/index.ts @@ -23,6 +23,7 @@ export type { LanguageFilterSearchTerm } from "./models/LanguageFilterSearchTerm export type { ParentFilterSearchTerm } from "./models/ParentFilterSearchTerm"; export type { Project } from "./models/Project"; export { ProjectStatus } from "./models/ProjectStatus"; +export type { PropertyFilterSearchTerm } from "./models/PropertyFilterSearchTerm"; export type { ValidationError } from "./models/ValidationError"; export { DefaultService } from "./services/DefaultService"; diff --git a/taxonomy-editor-frontend/src/client/models/EntryNodeSearchResult.ts b/taxonomy-editor-frontend/src/client/models/EntryNodeSearchResult.ts index 5be264be..779a67c7 100644 --- a/taxonomy-editor-frontend/src/client/models/EntryNodeSearchResult.ts +++ b/taxonomy-editor-frontend/src/client/models/EntryNodeSearchResult.ts @@ -9,6 +9,7 @@ import type { EntryNode } from "./EntryNode"; import type { IsFilterSearchTerm } from "./IsFilterSearchTerm"; import type { LanguageFilterSearchTerm } from "./LanguageFilterSearchTerm"; import type { ParentFilterSearchTerm } from "./ParentFilterSearchTerm"; +import type { PropertyFilterSearchTerm } from "./PropertyFilterSearchTerm"; export type EntryNodeSearchResult = { q: string; nodeCount: number; @@ -20,6 +21,7 @@ export type EntryNodeSearchResult = { | ChildFilterSearchTerm | AncestorFilterSearchTerm | DescendantFilterSearchTerm + | PropertyFilterSearchTerm >; nodes: Array; }; diff --git a/taxonomy-editor-frontend/src/client/models/PropertyFilterSearchTerm.ts b/taxonomy-editor-frontend/src/client/models/PropertyFilterSearchTerm.ts new file mode 100644 index 00000000..b1a12aad --- /dev/null +++ b/taxonomy-editor-frontend/src/client/models/PropertyFilterSearchTerm.ts @@ -0,0 +1,12 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type PropertyFilterSearchTerm = { + filterType: "property"; + filterValue: string; + readonly negated: boolean; + readonly inherited: boolean; + readonly propertyName: string; + readonly propertyValue: string | null; +}; diff --git a/taxonomy-editor-frontend/src/components/CustomAlert.tsx b/taxonomy-editor-frontend/src/components/CustomAlert.tsx deleted file mode 100644 index 139968ca..00000000 --- a/taxonomy-editor-frontend/src/components/CustomAlert.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Alert, AlertTitle } from "@mui/material"; -import { SxProps } from "@mui/material"; - -interface CustomAlertProps { - severity: "error" | "warning" | "info" | "success"; - title?: string; - message: string; - sx?: SxProps; -} - -export const CustomAlert: React.FC = ({ - severity, - title, - message, - sx, -}) => { - return ( - - {title && {title}} - {message} - - ); -}; diff --git a/taxonomy-editor-frontend/src/components/EntryNodesTableBody.tsx b/taxonomy-editor-frontend/src/components/EntryNodesTableBody.tsx new file mode 100644 index 00000000..8500c8ec --- /dev/null +++ b/taxonomy-editor-frontend/src/components/EntryNodesTableBody.tsx @@ -0,0 +1,122 @@ +import { EntryNode } from "@/client"; +import { + Chip, + Stack, + TableBody, + TableCell, + TableRow, + Tooltip, + Typography, +} from "@mui/material"; +import { Link } from "react-router-dom"; +import ISO6391 from "iso-639-1"; +import { useEffect, useState } from "react"; +import { SHOWN_LANGUAGES_KEY } from "@/pages/project/editentry/ListTranslations"; + +type Props = { + entryNodes: EntryNode[]; + taxonomyName: string; + branchName: string; +}; + +const EntryTitle = ({ id }: { id: string }) => { + const languageCode = id.split(":", 1)[0]; + const languageName = ISO6391.getName(languageCode); + return ( + + {id.slice(languageCode.length + 1)} + + + + + ); +}; + +const getTranslations = ( + tags: Record, + shownLanguageCodes: string[] +) => { + const result: string[] = []; + + shownLanguageCodes.forEach((languageCode) => { + const languageName = + languageCode === "xx" + ? "Fallback translations" + : ISO6391.getName(languageCode); + const translations = tags[`tags_${languageCode}`]; + if (translations) { + result.push(`${languageName}: ${translations.join(", ")}`); + } + }); + + return result; +}; + +export const EntryNodesTableBody = ({ + entryNodes, + taxonomyName, + branchName, +}: Props) => { + const [shownLanguageCodes, setShownLanguageCodes] = useState([]); + + useEffect(() => { + // get shown languages from local storage if it exists else use main language + try { + const rawLocalStorageShownLanguages = + localStorage.getItem(SHOWN_LANGUAGES_KEY); + let localStorageShownLanguages: string[] | null = + rawLocalStorageShownLanguages + ? JSON.parse(rawLocalStorageShownLanguages) + : null; + // validate that shown languages is an array of strings and filter all items that are valid language codes + if ( + Array.isArray(localStorageShownLanguages) && + localStorageShownLanguages.every((item) => typeof item === "string") + ) { + localStorageShownLanguages = localStorageShownLanguages.filter( + (item) => { + return item === "xx" || ISO6391.validate(item); + } + ); + } else { + localStorageShownLanguages = []; + } + setShownLanguageCodes(localStorageShownLanguages); + } catch (e) { + // shown languages is an empty list, when we can't parse the local storage + console.log(e); + } + }, []); + + return ( + <> + + {entryNodes.map(({ id, isExternal, tags }) => ( + + + + + {isExternal && ( + + External Node + + )} + {getTranslations(tags, shownLanguageCodes).map((line, i) => ( + + {line} + + ))} + + + + ))} + + + ); +}; diff --git a/taxonomy-editor-frontend/src/components/NodesTableBody.tsx b/taxonomy-editor-frontend/src/components/NodesTableBody.tsx deleted file mode 100644 index 3b94c731..00000000 --- a/taxonomy-editor-frontend/src/components/NodesTableBody.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { - IconButton, - TableBody, - TableCell, - TableRow, - Typography, -} from "@mui/material"; -import EditIcon from "@mui/icons-material/Edit"; -import { Link } from "react-router-dom"; -import { NodeInfo } from "@/backend-types/types"; - -type Props = { - nodeInfos: NodeInfo[]; - taxonomyName: string; - branchName: string; -}; - -const NodesTableBody = ({ nodeInfos, taxonomyName, branchName }: Props) => { - return ( - <> - - {nodeInfos.map(({ id, is_external: isExternal }) => ( - - - {id} - {isExternal && ( - - External Node - - )} - - - - - - - - ))} - - - ); -}; - -export default NodesTableBody; diff --git a/taxonomy-editor-frontend/src/components/ResponsiveAppBar.tsx b/taxonomy-editor-frontend/src/components/ResponsiveAppBar.tsx index 05b13b9d..3a6e8134 100644 --- a/taxonomy-editor-frontend/src/components/ResponsiveAppBar.tsx +++ b/taxonomy-editor-frontend/src/components/ResponsiveAppBar.tsx @@ -26,7 +26,6 @@ const getDisplayedPages = ( const navUrlPrefix = `${params.taxonomyName}/${params.branchName}/`; return [ - { url: navUrlPrefix + "entry", translationKey: "Nodes" }, { url: navUrlPrefix + "search", translationKey: "Search" }, { url: navUrlPrefix + "export", translationKey: "Export" }, { url: navUrlPrefix + "errors", translationKey: "Errors" }, @@ -46,7 +45,7 @@ export const ResponsiveAppBar = () => { }; return ( - + {/* Mobile content */} @@ -143,6 +142,7 @@ export const ResponsiveAppBar = () => { component={Link} to="/" target="_blank" + rel="noopener" > = ({ if (errorNode && errorNode?.errors.length !== 0) { return ( - + + Parsing errors + This taxonomy has encountered parsing errors. Please review and fix + errors on the 'Errors' page, so that the taxonomy can then be + edited. + ); } diff --git a/taxonomy-editor-frontend/src/pages/go-to-project/index.tsx b/taxonomy-editor-frontend/src/pages/go-to-project/index.tsx index 3ddfbe0d..57ef5ed5 100644 --- a/taxonomy-editor-frontend/src/pages/go-to-project/index.tsx +++ b/taxonomy-editor-frontend/src/pages/go-to-project/index.tsx @@ -1,6 +1,6 @@ import { useNavigate } from "react-router-dom"; -import { Typography, Box, Grid, Link as MuiLink } from "@mui/material"; +import { Typography, Box, Grid, Link as MuiLink, Alert } from "@mui/material"; import { DataGrid, GridColDef, GridRowParams } from "@mui/x-data-grid"; import CircularProgress from "@mui/material/CircularProgress"; import { useQuery } from "@tanstack/react-query"; @@ -105,21 +105,26 @@ export const GoToProject = () => { } return ( - - - - List of current projects - - - - - - + <> + + Please be careful when editing projects you do not own. + + + + + List of current projects + + + + + + + ); }; diff --git a/taxonomy-editor-frontend/src/pages/home/index.tsx b/taxonomy-editor-frontend/src/pages/home/index.tsx index 792d0fca..2dd7dbd3 100644 --- a/taxonomy-editor-frontend/src/pages/home/index.tsx +++ b/taxonomy-editor-frontend/src/pages/home/index.tsx @@ -1,7 +1,7 @@ import { Link } from "react-router-dom"; import Button from "@mui/material/Button"; -import { Link as MuiLink } from "@mui/material"; +import { Alert, Link as MuiLink } from "@mui/material"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import Stack from "@mui/material/Stack"; @@ -11,65 +11,86 @@ import classificationImgUrl from "@/assets/classification.png"; export const Home = () => { return ( - - - - + <> + + You may want to learn more about{" "} + + taxonomies + {" "} + or read this{" "} + + app's documentation + {" "} + before using the Taxonomy Editor. + + - - Taxonomy Editor + sx={{ + marginTop: 4, + display: "flex", + flexDirection: "column", + alignItems: "center", + }} + > + + + + + Taxonomy Editor + + + + + + + + {"Copyright © "} + + Open Food Facts + {" "} + {new Date().getFullYear()} + {"."} - - - - - - - {"Copyright © "} - - Open Food Facts - {" "} - {new Date().getFullYear()} - {"."} - - + + ); }; diff --git a/taxonomy-editor-frontend/src/pages/project/editentry/LanguageSelectionDialog.tsx b/taxonomy-editor-frontend/src/pages/project/editentry/LanguageSelectionDialog.tsx index 8296e0cc..0158a931 100644 --- a/taxonomy-editor-frontend/src/pages/project/editentry/LanguageSelectionDialog.tsx +++ b/taxonomy-editor-frontend/src/pages/project/editentry/LanguageSelectionDialog.tsx @@ -1,18 +1,13 @@ -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import ISO6391 from "iso-639-1"; import { - OutlinedInput, - InputLabel, - MenuItem, - FormControl, - ListItemText, - Select, - Checkbox, + Autocomplete, DialogTitle, DialogContent, DialogActions, Button, + TextField, } from "@mui/material"; type Props = { @@ -28,56 +23,50 @@ const LanguageSelectionDialog = ({ handleDialogConfirm, shownLanguageCodes, }: Props) => { - const [newShownLanguageCodes, setNewShownLanguageCodes] = useState([ - ...shownLanguageCodes, - ]); + const [newLanguageCodes, setNewLanguageCodes] = useState([]); + const inputRef = useRef(null); + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, []); return ( <> - Select shown languages + Show another language - - Languages - - + ISO6391.getName(langCode))} + onChange={(_event, newValue: string[]) => { + setNewLanguageCodes( + newValue.map((langName) => ISO6391.getCode(langName)) + ); + }} + options={ISO6391.getAllNames() + .sort() + .filter( + (languageName) => + !shownLanguageCodes.includes(ISO6391.getCode(languageName)) && + !newLanguageCodes.includes(ISO6391.getCode(languageName)) && + languageName !== ISO6391.getName(mainLanguageCode) + )} + getOptionLabel={(option) => option} + renderInput={(params) => ( + + )} + /> diff --git a/taxonomy-editor-frontend/src/pages/project/editentry/ListTranslations.tsx b/taxonomy-editor-frontend/src/pages/project/editentry/ListTranslations.tsx index 03e898c6..8bf6ca67 100644 --- a/taxonomy-editor-frontend/src/pages/project/editentry/ListTranslations.tsx +++ b/taxonomy-editor-frontend/src/pages/project/editentry/ListTranslations.tsx @@ -6,13 +6,41 @@ import { Box, Dialog, Checkbox, + IconButton, + Tooltip, } from "@mui/material"; +import VisibilityOffOutlinedIcon from "@mui/icons-material/VisibilityOffOutlined"; +import PushPinOutlinedIcon from "@/assets/icons/pushpin-line-grey.svg?react"; import LanguageSelectionDialog from "./LanguageSelectionDialog"; import { useMemo, useEffect, useState } from "react"; import ISO6391 from "iso-639-1"; import { TranslationTags } from "./TranslationTags"; -const SHOWN_LANGUAGES_KEY = "shownLanguages"; +export const SHOWN_LANGUAGES_KEY = "shownLanguages"; + +const getLanguageName = (languageCode: string): string => { + if (languageCode === "xx") { + return "Fallback translations"; + } + const languageName = ISO6391.getName(languageCode); + if (languageName === "") { + return languageCode; + } + return languageName; +}; + +const sortByLanguageName = (lcA: any, lcB: any): number => { + const languageNameA = getLanguageName(lcA); + const languageNameB = getLanguageName(lcB); + + if (languageNameA < languageNameB) { + return -1; + } else if (languageNameA > languageNameB) { + return 1; + } else { + return 0; + } +}; /** * Sub-component for rendering translation of an "entry" @@ -25,26 +53,8 @@ export const ListTranslations = ({ }) => { const [isDialogOpen, setIsDialogOpen] = useState(false); // Used for Dialog component const [shownLanguageCodes, setShownLanguageCodes] = useState([]); // Used for storing LCs that are shown in the interface - - // Helper functions for Dialog component - const handleClose = () => { - setIsDialogOpen(false); - }; - const handleOpen = () => { - setIsDialogOpen(true); - }; - - const handleXxLanguage = () => { - if (shownLanguageCodes.includes("xx")) { - const newShownLanguagesCodes = shownLanguageCodes.filter( - (lang) => lang !== "xx" - ); - setShownLanguageCodes(newShownLanguagesCodes); - } else { - const newShownLanguagesCodes = ["xx", ...shownLanguageCodes]; - setShownLanguageCodes(newShownLanguagesCodes); - } - }; + const [showExistingTranslations, setShowExistingTranslations] = + useState(false); const xxLanguageExists = useMemo(() => { const exists = @@ -54,15 +64,47 @@ export const ListTranslations = ({ return exists; }, [nodeObject]); - const handleDialogConfirm = (newShownLanguageCodes: string[]) => { + const handleLanguagePin = (languageCode: string) => { + let newShownLanguageCodes: string[]; + if (shownLanguageCodes.includes(languageCode)) { + newShownLanguageCodes = shownLanguageCodes.filter( + (langCode) => langCode !== languageCode + ); + } else { + newShownLanguageCodes = [...shownLanguageCodes, languageCode]; + newShownLanguageCodes.sort(sortByLanguageName); + } localStorage.setItem( SHOWN_LANGUAGES_KEY, JSON.stringify(newShownLanguageCodes) ); + setShownLanguageCodes(newShownLanguageCodes); + }; - if (xxLanguageExists && !newShownLanguageCodes.includes("xx")) { - newShownLanguageCodes = ["xx", ...newShownLanguageCodes]; - } + // Helper functions for Dialog component + const handleClose = () => { + setIsDialogOpen(false); + }; + const handleOpen = () => { + setIsDialogOpen(true); + }; + + const handleDialogConfirm = (newLanguageCodes: string[]) => { + let newShownLanguageCodes = [...shownLanguageCodes]; + newLanguageCodes.forEach((languageCode) => { + if (shownLanguageCodes.includes(languageCode)) { + newShownLanguageCodes = newShownLanguageCodes.filter( + (langCode) => langCode !== languageCode + ); + } else { + newShownLanguageCodes.push(languageCode); + newShownLanguageCodes.sort(sortByLanguageName); + } + }); + localStorage.setItem( + SHOWN_LANGUAGES_KEY, + JSON.stringify(newShownLanguageCodes) + ); setShownLanguageCodes(newShownLanguageCodes); setIsDialogOpen(false); @@ -114,29 +156,44 @@ export const ListTranslations = ({ }; }; - const languagesToShow: string[] = shownLanguageCodes.filter( + let languagesToShow: string[]; + languagesToShow = shownLanguageCodes.filter( (languageCode) => languageCode !== nodeObject.main_language ); + if (shownLanguageCodes.includes("xx")) { + languagesToShow = languagesToShow.filter( + (languageCode) => languageCode !== "xx" + ); + languagesToShow.unshift("xx"); + } languagesToShow.unshift(nodeObject.main_language); - const numberOfLanguagesShownMessage = - "(" + - languagesToShow.length + - " language" + - (languagesToShow.length === 1 ? "" : "s") + - " shown)"; + if (showExistingTranslations) { + languagesToShow.push( + ...Object.keys(nodeObject) + .filter( + (key) => + key.startsWith("tags_") && + !key.startsWith("tags_ids_") && + (nodeObject[key]?.length > 0 || originalNodeObject[key]?.length > 0) + ) + .map((key) => key.slice(5)) + .filter((languageCode) => !languagesToShow.includes(languageCode)) + .sort(sortByLanguageName) + ); + } const hasFirstTranslationChanged: boolean[] = languagesToShow.map( (language: string) => + originalNodeObject[`tags_${language}`]?.[0] && + nodeObject[`tags_${language}`]?.[0] && nodeObject[`tags_${language}`]?.[0] !== - originalNodeObject[`tags_${language}`]?.[0] + originalNodeObject[`tags_${language}`]?.[0] ); const shownLanguagesInfo = languagesToShow.map((languageCode: string) => { const languageName = - (languageCode === "xx" - ? "All languages" - : ISO6391.getName(languageCode)) + + getLanguageName(languageCode) + (languageCode === nodeObject.main_language ? " (main language)" : ""); const alertMessage = "Changing the first translation will modify " + @@ -151,58 +208,104 @@ export const ListTranslations = ({ {/* Title */} - + Translations - - {xxLanguageExists ? ( - // if "xx" words exist, the "All language" is always displayed, the user can't choose - <> - - - All languages - - - ) : ( - <> - - All languages - - )} + {/* if "xx" words exist, the "Fallback translations" are always + displayed, the user can't choose */} + handleLanguagePin("xx")} + /> + + Show fallback translations + + { + setShowExistingTranslations(!showExistingTranslations); + }} + /> + Show all existing translations + {!["en", "xx"].includes(nodeObject.main_language) && ( + + English or Fallback translations is not the main language for this + entry. Please consider changing it to adhere to the prevailing + convention. + + )} + {/* Render translation tags for each language to show */} {shownLanguagesInfo.map( - ({ languageCode, languageName, alertMessage }) => ( - - - {languageName} - - {hasFirstTranslationChanged[ - languagesToShow.indexOf(languageCode) - ] && ( - - {alertMessage} - - )} - - { - - } + ({ languageCode, languageName, alertMessage }) => { + const isLanguageSelected = shownLanguageCodes.includes(languageCode); + return ( + + + + {languageName} + + + {languageCode !== "xx" && ( + + handleLanguagePin(languageCode)} + > + {!isLanguageSelected ? ( + + ) : ( + + )} + + + )} + + {hasFirstTranslationChanged[ + languagesToShow.indexOf(languageCode) + ] && ( + + {alertMessage} + + )} + + { + + } + - - ) + ); + } )} + + {/* Dialog box for adding translations */} { )} {isExternalNode && ( - + {`This node has been imported from another taxonomy (${toTitleCase( removeTxtExtension(node.original_taxonomy) )}) to extend the current taxonomy. You can only add children from the current taxonomy to it.`} - severity="info" - sx={{ ml: 4, mb: 2, width: "fit-content" }} - /> + )} diff --git a/taxonomy-editor-frontend/src/pages/project/export/index.tsx b/taxonomy-editor-frontend/src/pages/project/export/index.tsx index b764b484..9f3bc48b 100644 --- a/taxonomy-editor-frontend/src/pages/project/export/index.tsx +++ b/taxonomy-editor-frontend/src/pages/project/export/index.tsx @@ -67,7 +67,8 @@ const ExportTaxonomyToGithub = ({ sx={{ mt: 10, flexGrow: 1, textAlign: "center" }} variant="h5" > - Click the button below to export to GitHub + Click the button below to export to GitHub.
+ Your changes will be reviewed by the community before being accepted. - diff --git a/taxonomy-editor-frontend/src/pages/project/root-nodes/index.tsx b/taxonomy-editor-frontend/src/pages/project/root-nodes/index.tsx deleted file mode 100644 index c677ecd8..00000000 --- a/taxonomy-editor-frontend/src/pages/project/root-nodes/index.tsx +++ /dev/null @@ -1,251 +0,0 @@ -import { useState } from "react"; -import { useParams } from "react-router-dom"; - -import { - Typography, - Snackbar, - Alert, - Box, - Stack, - IconButton, -} from "@mui/material"; -import Table from "@mui/material/Table"; -import TableBody from "@mui/material/TableBody"; -import TableCell from "@mui/material/TableCell"; -import TableContainer from "@mui/material/TableContainer"; -import TableHead from "@mui/material/TableHead"; -import TableRow from "@mui/material/TableRow"; -import AddBoxIcon from "@mui/icons-material/AddBox"; -import Dialog from "@mui/material/Dialog"; -import CircularProgress from "@mui/material/CircularProgress"; - -import CreateNodeDialogContent from "@/components/CreateNodeDialogContent"; -import { toTitleCase, createBaseURL } from "@/utils"; -import { greyHexCode } from "@/constants"; -import { - type RootEntriesAPIResponse, - type NodeInfo, -} from "@/backend-types/types"; -import NodesTableBody from "@/components/NodesTableBody"; -import { useQuery } from "@tanstack/react-query"; -import { DefaultService, Project, ProjectStatus } from "@/client"; - -type RootNodesProps = { - taxonomyName: string; - branchName: string; -}; - -const RootNodes = ({ taxonomyName, branchName }: RootNodesProps) => { - const [openCreateNodeDialog, setOpenCreateNodeDialog] = useState(false); - const [openCreateNodeSuccessSnackbar, setCreateNodeOpenSuccessSnackbar] = - useState(false); - - const baseUrl = createBaseURL(taxonomyName, branchName); - const rootNodesUrl = `${baseUrl}rootentries`; - - const { - data: info, - isPending: infoPending, - isError: infoIsError, - error: infoError, - } = useQuery({ - queryKey: [ - "getProjectInfoTaxonomyNameBranchProjectGet", - branchName, - taxonomyName, - ], - queryFn: async () => { - return await DefaultService.getProjectInfoTaxonomyNameBranchProjectGet( - branchName, - taxonomyName - ); - }, - refetchInterval: (d) => { - return d.state.status === "success" && - d.state.data?.status === ProjectStatus.LOADING - ? 1000 - : false; - }, - }); - - const { - data: nodes, - isPending, - isError, - error, - } = useQuery({ - queryKey: [rootNodesUrl], - queryFn: async () => { - const response = await fetch(rootNodesUrl); - if (!response.ok) { - throw new Error("Failed to fetch root nodes"); - } - return response.json(); - }, - // fetch root nodes after receiving project status - enabled: - !!info && - [ProjectStatus.OPEN, ProjectStatus.EXPORTED].includes(info.status), - }); - - let nodeInfos: NodeInfo[] = []; - if (nodes && nodes.length > 0) { - nodeInfos = nodes.map((node) => ({ - id: node[0].id, - is_external: node[0].is_external, - })); - } - - const handleCloseAddDialog = () => { - setOpenCreateNodeDialog(false); - }; - - const handleCloseSuccessSnackbar = () => { - setCreateNodeOpenSuccessSnackbar(false); - }; - - if (isError || infoIsError || !branchName || !taxonomyName) { - return ( - - - {error?.message ?? infoError?.message} - - - ); - } - - if (info && info["status"] === ProjectStatus.FAILED) { - return ( - - Parsing of the project has failed, rendering it uneditable. - - ); - } - - if (isPending || infoPending || !nodes) { - return ( - - - {info && info["status"] === ProjectStatus.LOADING && ( - - Taxonomy parsing may take several minutes, depending on the - complexity of the taxonomy being imported. - - )} - - ); - } - - return ( - - - Root Nodes: - - - - - - - Taxonomy Name - - - Branch Name - - - - - - {toTitleCase(taxonomyName ?? "")} - - - - {branchName} - - -
-
- - - Number of root nodes in taxonomy: {nodes.length} - - - {/* Table for listing all nodes in taxonomy */} - - - - - - - Nodes - - { - setOpenCreateNodeDialog(true); - }} - > - - - - - Action - - - - -
-
- - {/* Dialog box for adding nodes */} - - { - setOpenCreateNodeDialog(false); - setCreateNodeOpenSuccessSnackbar(true); - }} - /> - - - {/* Snackbar for acknowledgment of addition of node */} - - - The node has been successfully added! - - -
- ); -}; - -export const RootNodesWrapper = () => { - const { taxonomyName, branchName } = useParams(); - if (!taxonomyName || !branchName) - return ( - - Oops, something went wrong! Please try again later. - - ); - - return ; -}; diff --git a/taxonomy-editor-frontend/src/pages/project/search/SearchResults.tsx b/taxonomy-editor-frontend/src/pages/project/search/SearchResults.tsx index bb97074f..c740c12a 100644 --- a/taxonomy-editor-frontend/src/pages/project/search/SearchResults.tsx +++ b/taxonomy-editor-frontend/src/pages/project/search/SearchResults.tsx @@ -1,5 +1,5 @@ import CreateNodeDialogContent from "@/components/CreateNodeDialogContent"; -import NodesTableBody from "@/components/NodesTableBody"; +import { EntryNodesTableBody } from "@/components/EntryNodesTableBody"; import { greyHexCode } from "@/constants"; import { TableContainer, @@ -23,10 +23,10 @@ import { import { Dispatch, SetStateAction, useState } from "react"; import AddBoxIcon from "@mui/icons-material/AddBox"; import { useParams } from "react-router-dom"; -import { NodeInfo } from "@/backend-types/types"; +import { EntryNode } from "@/client"; -type AdvancedResearchResultsType = { - nodeInfos: NodeInfo[]; +type SearchResultsType = { + entryNodes: EntryNode[]; nodeCount: number | undefined; currentPage: number; setCurrentPage: Dispatch>; @@ -35,15 +35,15 @@ type AdvancedResearchResultsType = { isPending: boolean; }; -export const AdvancedResearchResults = ({ - nodeInfos, +export const SearchResults = ({ + entryNodes, nodeCount = 0, currentPage, setCurrentPage, isError, errorMessage, isPending, -}: AdvancedResearchResultsType) => { +}: SearchResultsType) => { const { taxonomyName, branchName } = useParams() as unknown as { taxonomyName: string; branchName: string; @@ -61,12 +61,25 @@ export const AdvancedResearchResults = ({ }; const handlePageChange = ( - event: React.MouseEvent | null, + _event: React.MouseEvent | null, newPage: number ) => { setCurrentPage(newPage + 1); }; + const SearchTablePagination = () => ( + + ); + // Displaying errorMessages if any if (isError) { return ( @@ -117,13 +130,14 @@ export const AdvancedResearchResults = ({ alignItems="center" justifyContent="center" > - + + - Nodes + Entries { @@ -134,25 +148,16 @@ export const AdvancedResearchResults = ({ - - Action - -
- + {/* Dialog box for adding nodes */} diff --git a/taxonomy-editor-frontend/src/pages/project/search/index.tsx b/taxonomy-editor-frontend/src/pages/project/search/index.tsx index 8e3ff63b..8cd2580a 100644 --- a/taxonomy-editor-frontend/src/pages/project/search/index.tsx +++ b/taxonomy-editor-frontend/src/pages/project/search/index.tsx @@ -4,7 +4,7 @@ import { Box } from "@mui/material"; import { DefaultService } from "@/client"; import { keepPreviousData, useQuery } from "@tanstack/react-query"; -import { AdvancedResearchResults } from "./SearchResults"; +import { SearchResults } from "./SearchResults"; import { SearchExpressionInput } from "./SearchExpressionInput"; import { FiltersArea } from "./FiltersArea"; @@ -73,13 +73,8 @@ export const AdvancedSearchForm = () => { setQ={setQ} filters={entryNodeSearchResult?.filters ?? []} /> - ({ - id: node.id, - is_external: node.isExternal, - })) ?? [] - } + { }; return ( - - - - Start a project - -
- - Taxonomy - - -
- - { - setOwnerName(event.target.value); - }} - value={ownerName} - variant="outlined" - label="Your Name" - required={true} - /> - - + + Feel free to start a new project. Any changes you make will first have + to be exported and reviewed by the community before being accepted. + Don't be scared to test things! + + + - Please use your Github account username if possible, or eventually - your id on open food facts slack (so that we can contact you) - - - { - setBranchName(event.target.value); - }} - value={branchName} - variant="outlined" - label="Branch Name" - required={true} - /> - - { - setDescription(event.target.value); - }} - value={description} - variant="outlined" - label="Description" - /> - - - Explain what is your goal with this new project, what changes are you - going to bring. Remember to privilege small projects (do big project - as a succession of small one). - - - -
- - + Start a project + +
+ + Taxonomy + + +
+ + { + setOwnerName(event.target.value); + }} + value={ownerName} + variant="outlined" + label="Your Name" + required={true} + /> + + + Please use your Github account username if possible, or eventually + your id on open food facts slack (so that we can contact you) + + + { + setBranchName(event.target.value); + }} + value={branchName} + variant="outlined" + label="Branch Name" + required={true} + /> + + { + setDescription(event.target.value); + }} + value={description} + variant="outlined" + label="Description" + /> + + + Explain what is your goal with this new project, what changes are + you going to bring. Remember to privilege small projects (do big + project as a succession of small one). + + + + + - {errorMessage} -
-
-
+ + {errorMessage} + + + + ); }; diff --git a/taxonomy-editor-frontend/src/vite-env.d.ts b/taxonomy-editor-frontend/src/vite-env.d.ts index bb6af783..beae17ef 100644 --- a/taxonomy-editor-frontend/src/vite-env.d.ts +++ b/taxonomy-editor-frontend/src/vite-env.d.ts @@ -1,4 +1,5 @@ /// +/// interface ImportMetaEnv { readonly VITE_APP_API_URL: string; diff --git a/taxonomy-editor-frontend/vite.config.ts b/taxonomy-editor-frontend/vite.config.ts index ce3d05b4..8a72a8b3 100644 --- a/taxonomy-editor-frontend/vite.config.ts +++ b/taxonomy-editor-frontend/vite.config.ts @@ -1,12 +1,13 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import viteTsconfigPaths from "vite-tsconfig-paths"; +import svgr from "vite-plugin-svgr"; export default defineConfig({ build: { outDir: "build", }, - plugins: [react(), viteTsconfigPaths()], + plugins: [react(), viteTsconfigPaths(), svgr()], server: { port: parseInt(process.env.VITE_SERVER_PORT || "3000"), host: process.env.VITE_SERVER_HOST || "localhost",