From 4119aedf501fbd0c7ad2c04cc0a219f66e709caf Mon Sep 17 00:00:00 2001 From: Charles Perier <82757576+perierc@users.noreply.github.com> Date: Wed, 27 Mar 2024 16:33:44 +0100 Subject: [PATCH] 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 --- taxonomy-editor-frontend/package-lock.json | 543 +++++++++++++++++- taxonomy-editor-frontend/package.json | 1 + .../src/assets/icons/pushpin-line-grey.svg | 1 + .../editentry/LanguageSelectionDialog.tsx | 87 ++- .../project/editentry/ListTranslations.tsx | 259 ++++++--- taxonomy-editor-frontend/src/vite-env.d.ts | 1 + taxonomy-editor-frontend/vite.config.ts | 3 +- 7 files changed, 753 insertions(+), 142 deletions(-) create mode 100644 taxonomy-editor-frontend/src/assets/icons/pushpin-line-grey.svg 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/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/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..d09a5fbb 100644 --- a/taxonomy-editor-frontend/src/pages/project/editentry/ListTranslations.tsx +++ b/taxonomy-editor-frontend/src/pages/project/editentry/ListTranslations.tsx @@ -6,7 +6,11 @@ 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"; @@ -14,6 +18,30 @@ import { TranslationTags } from "./TranslationTags"; 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 */} +/// 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",