From a1851b37131501ebc9c449f072f31b3ca9bbaca7 Mon Sep 17 00:00:00 2001 From: DingDongSoLong4 <99329275+DingDongSoLong4@users.noreply.github.com> Date: Thu, 16 Feb 2023 05:06:44 +0200 Subject: [PATCH] Update dependencies (#3123) * Update localforage, remove query-string * Update fontawesome and flag-icons * Update formatjs * Update axios and videojs * Update apollo client and graphql * Update bootstrap and react * Update polyfills * Update vite * Update ESLint * Update stylelint * Update configs * Rebuild yarn.lock --- .gitignore | 1 - ui/v2.5/.env | 1 - ui/v2.5/.eslintrc.json | 90 +- ui/v2.5/.gitignore | 5 +- ui/v2.5/.prettierignore | 18 + ui/v2.5/.stylelintrc | 45 +- ui/v2.5/.vscode/launch.json | 18 - ui/v2.5/.vscode/settings.json | 26 - ui/v2.5/codegen.yml | 4 +- ui/v2.5/index.html | 20 +- ui/v2.5/package.json | 170 +- ui/v2.5/src/@types/mousetrap-pause.d.ts | 24 + .../@types/string.prototype.replaceall.d.ts | 19 + ui/v2.5/src/@types/videojs-vtt.d.ts | 210 +- ui/v2.5/src/App.tsx | 6 +- .../src/components/Changelog/Changelog.tsx | 5 +- .../Dialogs/IdentifyDialog/FieldOptions.tsx | 5 +- .../Dialogs/IdentifyDialog/IdentifyDialog.tsx | 10 +- .../Dialogs/IdentifyDialog/constants.ts | 2 +- .../components/Dialogs/ReleaseNotesDialog.tsx | 3 +- .../Galleries/EditGalleriesDialog.tsx | 6 +- .../src/components/Galleries/Galleries.tsx | 2 +- .../Galleries/GalleryDetails/Gallery.tsx | 1 - .../GalleryDetails/GalleryCreate.tsx | 22 +- .../GalleryDetails/GalleryEditPanel.tsx | 26 +- .../Galleries/GalleryRecommendationRow.tsx | 8 +- ui/v2.5/src/components/Galleries/styles.scss | 6 +- ui/v2.5/src/components/Help/Manual.tsx | 6 +- .../components/Images/EditImagesDialog.tsx | 6 +- .../components/Images/ImageDetails/Image.tsx | 4 +- .../Images/ImageRecommendationRow.tsx | 8 +- .../Filters/HierarchicalLabelValueFilter.tsx | 7 +- ui/v2.5/src/components/MainNavbar.tsx | 10 +- ui/v2.5/src/components/Movies/MovieCard.tsx | 4 +- .../components/Movies/MovieDetails/Movie.tsx | 4 +- .../Movies/MovieDetails/MovieCreate.tsx | 19 +- .../Movies/MovieDetails/MovieEditPanel.tsx | 20 +- .../Movies/MovieRecommendationRow.tsx | 2 +- ui/v2.5/src/components/PageNotFound.tsx | 4 +- .../Performers/EditPerformersDialog.tsx | 6 +- .../Performers/PerformerDetails/Performer.tsx | 15 +- .../PerformerDetails/PerformerCreate.tsx | 17 +- .../PerformerDetails/PerformerEditPanel.tsx | 14 +- .../Performers/PerformerRecommendationRow.tsx | 8 +- ui/v2.5/src/components/Performers/styles.scss | 2 +- .../SceneDuplicateChecker.tsx | 43 +- .../SceneFilenameParser.tsx | 5 +- .../SceneFilenameParser/ShowFields.tsx | 2 +- .../components/ScenePlayer/track-activity.ts | 10 +- .../components/Scenes/EditScenesDialog.tsx | 6 +- ui/v2.5/src/components/Scenes/SceneCard.tsx | 5 +- .../components/Scenes/SceneDetails/Scene.tsx | 18 +- .../Scenes/SceneDetails/SceneCreate.tsx | 19 +- .../Scenes/SceneDetails/SceneEditPanel.tsx | 28 +- .../SceneDetails/SceneFileInfoPanel.tsx | 9 +- .../Scenes/SceneDetails/SceneMarkersPanel.tsx | 6 +- .../Scenes/SceneDetails/SceneMoviePanel.tsx | 4 +- .../Scenes/SceneDetails/SceneMovieTable.tsx | 6 +- .../Scenes/SceneDetails/SceneScrapeDialog.tsx | 10 +- ui/v2.5/src/components/Scenes/SceneList.tsx | 5 +- .../src/components/Scenes/SceneListTable.tsx | 5 +- .../components/Scenes/SceneMergeDialog.tsx | 2 +- .../Scenes/SceneRecommendationRow.tsx | 8 +- ui/v2.5/src/components/Scenes/styles.scss | 2 +- ui/v2.5/src/components/Settings/Inputs.tsx | 5 +- .../components/Settings/SettingSection.tsx | 3 +- ui/v2.5/src/components/Settings/Settings.tsx | 3 +- .../Settings/SettingsLibraryPanel.tsx | 10 +- .../Settings/SettingsScrapingPanel.tsx | 34 +- .../Settings/SettingsSecurityPanel.tsx | 10 +- .../Settings/SettingsServicesPanel.tsx | 9 +- .../Settings/SettingsSystemPanel.tsx | 5 +- .../Tasks/DirectorySelectionDialog.tsx | 9 +- .../Settings/Tasks/LibraryTasks.tsx | 20 +- ui/v2.5/src/components/Setup/Setup.tsx | 5 +- ui/v2.5/src/components/Shared/CountryFlag.tsx | 4 +- .../src/components/Shared/CountrySelect.tsx | 2 +- .../Shared/FolderSelect/FolderSelect.tsx | 5 +- .../src/components/Shared/MarkdownPage.tsx | 7 +- ui/v2.5/src/components/Shared/MultiSet.tsx | 6 +- ui/v2.5/src/components/Shared/Select.tsx | 157 +- ui/v2.5/src/components/Shared/SweatDrops.tsx | 2 +- .../Studios/StudioDetails/Studio.tsx | 4 +- .../Studios/StudioDetails/StudioCreate.tsx | 16 +- .../Studios/StudioDetails/StudioEditPanel.tsx | 11 +- .../Studios/StudioRecommendationRow.tsx | 8 +- ui/v2.5/src/components/Tagger/context.tsx | 5 +- .../Tagger/scenes/PerformerResult.tsx | 12 +- .../components/Tagger/scenes/SceneTagger.tsx | 12 +- .../Tagger/scenes/sceneTaggerModals.tsx | 7 +- .../src/components/Tags/TagDetails/Tag.tsx | 4 +- .../components/Tags/TagDetails/TagCreate.tsx | 16 +- .../Tags/TagDetails/TagEditPanel.tsx | 13 +- ui/v2.5/src/components/Tags/TagList.tsx | 6 +- .../components/Tags/TagRecommendationRow.tsx | 8 +- ui/v2.5/src/components/Wall/WallPanel.tsx | 20 +- ui/v2.5/src/core/StashService.ts | 22 +- ui/v2.5/src/core/createClient.ts | 33 +- ui/v2.5/src/docs/en/MigrationNotes/index.ts | 4 +- ui/v2.5/src/docs/en/ReleaseNotes/index.ts | 4 +- ui/v2.5/src/globals.d.ts | 22 +- ui/v2.5/src/hooks/Interval.ts | 2 +- ui/v2.5/src/hooks/Lightbox/Lightbox.tsx | 12 +- ui/v2.5/src/hooks/ListHook.tsx | 31 +- ui/v2.5/src/hooks/LocalForage.ts | 2 +- ui/v2.5/src/index.scss | 4 +- ui/v2.5/src/index.tsx | 1 - ui/v2.5/src/locales/da-DK.json | 2 +- ui/v2.5/src/locales/de-DE.json | 2 +- ui/v2.5/src/locales/en-GB.json | 2 +- ui/v2.5/src/locales/es-ES.json | 2 +- ui/v2.5/src/locales/et-EE.json | 2 +- ui/v2.5/src/locales/fr-FR.json | 2 +- ui/v2.5/src/locales/hu-HU.json | 2 +- ui/v2.5/src/locales/it-IT.json | 2 +- ui/v2.5/src/locales/ja-JP.json | 2 +- ui/v2.5/src/locales/ko-KR.json | 2 +- ui/v2.5/src/locales/nl-NL.json | 2 +- ui/v2.5/src/locales/pl-PL.json | 2 +- ui/v2.5/src/locales/pt-BR.json | 2 +- ui/v2.5/src/locales/ru-RU.json | 2 +- ui/v2.5/src/locales/sv-SE.json | 2 +- ui/v2.5/src/locales/tr-TR.json | 2 +- ui/v2.5/src/locales/zh-CN.json | 2 +- ui/v2.5/src/locales/zh-TW.json | 2 +- .../models/list-filter/criteria/is-missing.ts | 75 +- .../models/list-filter/criteria/resolution.ts | 5 +- ui/v2.5/src/models/list-filter/filter.ts | 191 +- ui/v2.5/src/models/sceneQueue.ts | 76 +- ui/v2.5/src/polyfills.ts | 2 +- ui/v2.5/src/react-app-env.d.ts | 1 - ui/v2.5/src/serviceWorker.ts | 5 +- ui/v2.5/src/utils/form.tsx | 1 - ui/v2.5/src/utils/table.tsx | 1 - ui/v2.5/tsconfig.json | 22 +- ui/v2.5/vite.config.js | 39 +- ui/v2.5/yarn.lock | 8700 ++++++++--------- 137 files changed, 5102 insertions(+), 5729 deletions(-) create mode 100644 ui/v2.5/.prettierignore delete mode 100644 ui/v2.5/.vscode/launch.json delete mode 100644 ui/v2.5/.vscode/settings.json create mode 100644 ui/v2.5/src/@types/mousetrap-pause.d.ts create mode 100644 ui/v2.5/src/@types/string.prototype.replaceall.d.ts delete mode 100644 ui/v2.5/src/react-app-env.d.ts diff --git a/.gitignore b/.gitignore index d3a1c21f0..7b392ac70 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,6 @@ # GraphQL generated output internal/api/generated_*.go -ui/v2.5/src/core/generated-*.tsx #### # Jetbrains diff --git a/ui/v2.5/.env b/ui/v2.5/.env index 66df2e28e..6e49b46e4 100644 --- a/ui/v2.5/.env +++ b/ui/v2.5/.env @@ -1,3 +1,2 @@ BROWSER=none -PORT=3000 ESLINT_NO_DEV_ERRORS=true diff --git a/ui/v2.5/.eslintrc.json b/ui/v2.5/.eslintrc.json index ca3e8c0ce..4438860ea 100644 --- a/ui/v2.5/.eslintrc.json +++ b/ui/v2.5/.eslintrc.json @@ -9,61 +9,69 @@ "parserOptions": { "project": "./tsconfig.json" }, - "plugins": [ - "@typescript-eslint", - "jsx-a11y" - ], + "plugins": ["@typescript-eslint", "jsx-a11y"], "extends": [ - "airbnb-typescript", - "airbnb/hooks", - "plugin:react/recommended", - "plugin:import/recommended", - "prettier", - "prettier/prettier" + "airbnb-typescript", + "plugin:import/recommended", + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "airbnb/hooks", + "prettier" ], "settings": { "react": { "version": "detect" } }, + "ignorePatterns": ["node_modules/", "src/core/generated-graphql.tsx"], "rules": { - "@typescript-eslint/no-explicit-any": 2, - "@typescript-eslint/naming-convention": [ - "error", - { + "@typescript-eslint/no-explicit-any": 2, + "@typescript-eslint/naming-convention": [ + "error", + { "selector": "interface", "format": ["PascalCase"], "custom": { "regex": "^I[A-Z]", "match": true - } } - ], - "lines-between-class-members": "off", - "@typescript-eslint/lines-between-class-members": "off", - "import/extensions": [ - "error", - "ignorePackages", - { - "js": "never", - "jsx": "never", - "ts": "never", - "tsx": "never" - } - ], - "import/named": "off", - "import/namespace": "off", - "import/no-unresolved": "off", - "react/display-name": "off", - "react/prop-types": "off", - "react/style-prop-object": ["error", { + } + ], + "lines-between-class-members": "off", + "@typescript-eslint/lines-between-class-members": "off", + "import/extensions": [ + "error", + "ignorePackages", + { + "js": "never", + "jsx": "never", + "ts": "never", + "tsx": "never" + } + ], + "import/named": "off", + "import/namespace": "off", + "import/no-unresolved": "off", + "react/display-name": "off", + "react/prop-types": "off", + "react/style-prop-object": [ + "error", + { "allow": ["FormattedNumber"] - }], - "spaced-comment": ["error", "always", { - "markers": ["/"] - }], - "prefer-destructuring": ["error", {"object": true, "array": false}], - "@typescript-eslint/no-use-before-define": ["error", { "functions": false, "classes": true }], - "no-nested-ternary": "off" + } + ], + "spaced-comment": [ + "error", + "always", + { + "markers": ["/"] + } + ], + "prefer-destructuring": ["error", { "object": true, "array": false }], + "@typescript-eslint/no-use-before-define": [ + "error", + { "functions": false, "classes": true } + ], + "no-nested-ternary": "off" } } diff --git a/ui/v2.5/.gitignore b/ui/v2.5/.gitignore index d7b8f1bd7..baf52f432 100755 --- a/ui/v2.5/.gitignore +++ b/ui/v2.5/.gitignore @@ -1,4 +1,5 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +# generated +src/core/generated-*.tsx # dependencies /node_modules @@ -12,6 +13,7 @@ /build # misc +.gitignore .DS_Store .env.local .env.development.local @@ -23,3 +25,4 @@ yarn-debug.log* yarn-error.log* .eslintcache +.stylelintcache diff --git a/ui/v2.5/.prettierignore b/ui/v2.5/.prettierignore new file mode 100644 index 000000000..aeb40cd1c --- /dev/null +++ b/ui/v2.5/.prettierignore @@ -0,0 +1,18 @@ +*.md + +# dependencies +/node_modules +/.pnp +.pnp.js + +# locales +src/locales/**/*.json + +# testing +/coverage + +# production +/build + +# generated +src/core/generated-graphql.tsx \ No newline at end of file diff --git a/ui/v2.5/.stylelintrc b/ui/v2.5/.stylelintrc index 1357ed90e..1025a90ab 100644 --- a/ui/v2.5/.stylelintrc +++ b/ui/v2.5/.stylelintrc @@ -1,14 +1,16 @@ { - "plugins": [ - "stylelint-order" - ], + "plugins": ["stylelint-order"], "extends": "stylelint-config-prettier", + "customSyntax": "postcss-scss", "rules": { "indentation": null, - "at-rule-empty-line-before": [ "always", { - except: ["after-same-name", "first-nested" ], - ignore: ["after-comment"], - } ], + "at-rule-empty-line-before": [ + "always", + { + "except": ["after-same-name", "first-nested"], + "ignore": ["after-comment"] + } + ], "at-rule-no-vendor-prefix": true, "selector-no-vendor-prefix": true, "block-closing-brace-newline-after": "always", @@ -21,10 +23,13 @@ "color-hex-case": "lower", "color-hex-length": "short", "color-no-invalid-hex": true, - "comment-empty-line-before": [ "always", { - except: ["first-nested"], - ignore: ["stylelint-commands"], - } ], + "comment-empty-line-before": [ + "always", + { + "except": ["first-nested"], + "ignore": ["stylelint-commands"] + } + ], "comment-whitespace-inside": "always", "declaration-bang-space-after": "never", "declaration-bang-space-before": "always", @@ -63,15 +68,15 @@ "no-missing-end-of-source-newline": true, "number-max-precision": 3, "number-no-trailing-zeros": true, - "order/order": [ - "custom-properties", - "declarations" - ], + "order/order": ["custom-properties", "declarations"], "order/properties-alphabetical-order": true, - "rule-empty-line-before": ["always-multi-line", { - except: ["after-single-line-comment", "first-nested" ], - ignore: ["after-comment"], - }], + "rule-empty-line-before": [ + "always-multi-line", + { + "except": ["after-single-line-comment", "first-nested"], + "ignore": ["after-comment"] + } + ], "selector-max-id": 1, "selector-max-type": 2, "selector-class-pattern": "^(\\.*[A-Z]*[a-z]+)+(-[a-z0-9]+)*$", @@ -87,5 +92,5 @@ "time-min-milliseconds": 100, "value-list-comma-space-after": "always-single-line", "value-list-comma-space-before": "never" - }, + } } diff --git a/ui/v2.5/.vscode/launch.json b/ui/v2.5/.vscode/launch.json deleted file mode 100644 index a0b21cbef..000000000 --- a/ui/v2.5/.vscode/launch.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "chrome", - "request": "launch", - "name": "Chrome", - "url": "http://localhost:3000", - "webRoot": "${workspaceFolder}/src", - "sourceMapPathOverrides": { - "webpack:///src/*": "${webRoot}/*" - } - } - ] -} \ No newline at end of file diff --git a/ui/v2.5/.vscode/settings.json b/ui/v2.5/.vscode/settings.json deleted file mode 100644 index ba5be62ee..000000000 --- a/ui/v2.5/.vscode/settings.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "typescript.tsdk": "node_modules/typescript/lib", - "editor.tabSize": 2, - "editor.renderWhitespace": "boundary", - "editor.wordWrap": "bounded", - "javascript.preferences.importModuleSpecifier": "relative", - "typescript.preferences.importModuleSpecifier": "relative", - "editor.wordWrapColumn": 120, - "editor.rulers": [ - 120 - ], - "i18n-ally.localesPaths": [ - "src/locales" - ], - "i18n-ally.keystyle": "nested", - "i18n-ally.sourceLanguage": "en-GB", - "spellright.language": [ - "en" - ], - "spellright.documentTypes": [ - "markdown", - "latex", - "plaintext", - "typescriptreact" - ] -} \ No newline at end of file diff --git a/ui/v2.5/codegen.yml b/ui/v2.5/codegen.yml index d648406a9..08b8c54fd 100644 --- a/ui/v2.5/codegen.yml +++ b/ui/v2.5/codegen.yml @@ -4,11 +4,9 @@ documents: "../../graphql/documents/**/*.graphql" generates: src/core/generated-graphql.tsx: plugins: - - add: - content: "/* eslint-disable */" - time - typescript - typescript-operations - typescript-react-apollo config: - withRefetchFn: true + withRefetchFn: true diff --git a/ui/v2.5/index.html b/ui/v2.5/index.html index b25ebc3d0..11bbb270d 100755 --- a/ui/v2.5/index.html +++ b/ui/v2.5/index.html @@ -1,7 +1,7 @@ - + @@ -10,27 +10,15 @@ content="width=device-width, initial-scale=1, maximum-scale=1" /> - Stash - +
- diff --git a/ui/v2.5/package.json b/ui/v2.5/package.json index 21c25ab58..02288b7a3 100644 --- a/ui/v2.5/package.json +++ b/ui/v2.5/package.json @@ -3,125 +3,117 @@ "version": "0.1.0", "private": true, "homepage": "./", - "sideEffects": false, "scripts": { "start": "vite", "build": "vite build", - "build-ci": "yarn validate && yarn build", - "validate": "yarn lint && tsc --noEmit && yarn format-check", - "lint": "yarn lint:css && yarn lint:js", - "lint:js": "eslint --cache src/**/*.{ts,tsx}", - "lint:css": "stylelint \"src/**/*.scss\"", - "format": "prettier --write \"src/**/!(generated-graphql).{js,jsx,ts,tsx,scss}\"", - "format-check": "prettier --check \"src/**/!(generated-graphql).{js,jsx,ts,tsx,scss}\"", + "build-ci": "yarn run validate && yarn run build", + "validate": "yarn run lint && yarn run check && yarn run format-check", + "lint": "yarn run lint:css && yarn run lint:js", + "lint:css": "stylelint --cache \"src/**/*.scss\"", + "lint:js": "eslint --cache src/", + "check": "tsc --noEmit", + "format": "prettier --write .", + "format-check": "prettier --check .", "gqlgen": "gql-gen --config codegen.yml", "extract": "NODE_ENV=development extract-messages -l=en,de -o src/locale -d en --flat false 'src/**/!(*.test).tsx'" }, "browserslist": [ - ">0.2%", - "not dead", - "not ie <= 11", - "not op_mini all" + ">0.5% and supports es6-module-dynamic-import" ], "dependencies": { - "@apollo/client": "^3.3.7", - "@formatjs/intl-getcanonicallocales": "^1.5.3", - "@formatjs/intl-locale": "^2.4.14", - "@formatjs/intl-numberformat": "^6.1.3", - "@formatjs/intl-pluralrules": "^4.0.6", - "@fortawesome/fontawesome-svg-core": "^1.2.34", - "@fortawesome/free-regular-svg-icons": "^5.15.2", - "@fortawesome/free-solid-svg-icons": "^5.15.2", - "@fortawesome/react-fontawesome": "^0.1.14", - "@types/react-select": "^4.0.8", - "ansi-regex": "^5.0.1", - "apollo-upload-client": "^14.1.3", - "axios": "^1.1.3", + "@ant-design/react-slick": "^1.0.0", + "@apollo/client": "^3.7.4", + "@formatjs/intl-getcanonicallocales": "^2.0.5", + "@formatjs/intl-locale": "^3.0.11", + "@formatjs/intl-numberformat": "^8.3.3", + "@formatjs/intl-pluralrules": "^5.1.8", + "@fortawesome/fontawesome-svg-core": "^6.2.1", + "@fortawesome/free-brands-svg-icons": "^6.2.1", + "@fortawesome/free-regular-svg-icons": "^6.2.1", + "@fortawesome/free-solid-svg-icons": "^6.2.1", + "@fortawesome/react-fontawesome": "^0.2.0", + "apollo-upload-client": "^17.0.0", + "axios": "^1.2.5", "base64-blob": "^1.4.1", - "bootstrap": "^4.6.0", - "classnames": "^2.2.6", - "flag-icon-css": "^3.5.0", + "bootstrap": "^4.6.2", + "classnames": "^2.3.2", + "flag-icons": "^6.6.6", "flexbin": "^0.2.0", - "formik": "^2.2.6", - "graphql": "^15.4.0", - "graphql-tag": "^2.11.0", - "i18n-iso-countries": "^6.4.0", - "intersection-observer": "^0.12.0", - "localforage": "^1.9.0", + "formik": "^2.2.9", + "graphql": "^16.6.0", + "graphql-tag": "^2.12.6", + "graphql-ws": "^5.11.2", + "i18n-iso-countries": "^7.5.0", + "intersection-observer": "^0.12.2", + "localforage": "^1.10.0", "lodash-es": "^4.17.21", "mousetrap": "^1.6.5", "mousetrap-pause": "^1.0.0", "normalize-url": "^4.5.1", - "postcss": "^8.2.10", - "query-string": "6.13.8", - "react": "17.0.2", - "react-bootstrap": "1.4.3", - "react-dom": "17.0.2", + "react": "^17.0.2", + "react-bootstrap": "^1.6.6", + "react-dom": "^17.0.2", "react-helmet": "^6.1.0", - "react-intl": "^5.10.16", - "react-markdown": "^7.1.0", + "react-intl": "^6.2.6", + "react-markdown": "^8.0.5", "react-router-bootstrap": "^0.25.0", - "react-router-dom": "^5.2.0", - "react-router-hash-link": "^2.3.1", - "react-select": "^4.0.2", - "react-slick": "^0.29.0", - "remark-gfm": "^1.0.0", + "react-router-dom": "^5.3.4", + "react-router-hash-link": "^2.4.3", + "react-select": "^5.6.1", + "remark-gfm": "^3.0.1", "resize-observer-polyfill": "^1.5.1", - "sass": "^1.32.5", "slick-carousel": "^1.8.1", - "string.prototype.replaceall": "^1.0.4", - "subscriptions-transport-ws": "^0.9.18", + "string.prototype.replaceall": "^1.0.7", "thehandy": "^1.0.3", "universal-cookie": "^4.0.4", - "video.js": "^7.20.3", + "video.js": "^7.21.1", "videojs-mobile-ui": "^0.8.0", "videojs-seek-buttons": "^3.0.1", "videojs-vtt.js": "^0.15.4", - "vite": "^2.9.13", - "vite-plugin-compression": "^0.3.5", - "vite-tsconfig-paths": "^3.3.17", - "ws": "^7.4.6", - "yup": "^0.32.9" + "yup": "^0.32.11" }, "devDependencies": { - "@graphql-codegen/add": "^2.0.2", - "@graphql-codegen/cli": "^1.20.0", - "@graphql-codegen/time": "^2.0.2", - "@graphql-codegen/typescript": "^1.20.00", - "@graphql-codegen/typescript-operations": "^1.17.13", - "@graphql-codegen/typescript-react-apollo": "^2.2.1", - "@types/apollo-upload-client": "^14.1.0", - "@types/classnames": "^2.2.11", - "@types/fslightbox-react": "^1.4.0", + "@babel/core": "^7.20.5", + "@graphql-codegen/cli": "^2.16.4", + "@graphql-codegen/time": "^3.2.3", + "@graphql-codegen/typescript": "^2.8.7", + "@graphql-codegen/typescript-operations": "^2.5.12", + "@graphql-codegen/typescript-react-apollo": "^3.3.7", + "@types/apollo-upload-client": "^17.0.2", "@types/lodash-es": "^4.17.6", - "@types/mousetrap": "^1.6.5", - "@types/node": "14.14.22", - "@types/react": "17.0.31", - "@types/react-dom": "^17.0.10", - "@types/react-helmet": "^6.1.3", + "@types/mousetrap": "^1.6.11", + "@types/node": "^14.18.36", + "@types/react": "^17.0.53", + "@types/react-dom": "^17.0.18", + "@types/react-helmet": "^6.1.6", "@types/react-router-bootstrap": "^0.24.5", - "@types/react-router-dom": "5.1.7", - "@types/react-router-hash-link": "^1.2.1", - "@types/react-slick": "^0.23.8", - "@types/video.js": "^7.3.49", + "@types/react-router-hash-link": "^2.4.5", + "@types/video.js": "^7.3.50", "@types/videojs-mobile-ui": "^0.5.0", "@types/videojs-seek-buttons": "^2.1.0", - "@typescript-eslint/eslint-plugin": "^4.33.0", - "@typescript-eslint/parser": "^4.33.0", - "eslint": "^7.32.0", - "eslint-config-airbnb": "^18.2.1", - "eslint-config-airbnb-typescript": "^14.0.1", - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-import": "^2.25.2", - "eslint-plugin-jsx-a11y": "^6.4.1", - "eslint-plugin-react": "^7.26.1", - "eslint-plugin-react-hooks": "^4.2.0", + "@typescript-eslint/eslint-plugin": "^5.49.0", + "@typescript-eslint/parser": "^5.49.0", + "@vitejs/plugin-react": "^3.0.1", + "eslint": "^8.32.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-airbnb-typescript": "^17.0.0", + "eslint-config-prettier": "^8.6.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.32.1", + "eslint-plugin-react-hooks": "^4.6.0", "extract-react-intl-messages": "^4.1.1", - "postcss-safe-parser": "^5.0.2", - "prettier": "2.2.1", - "stylelint": "^13.9.0", - "stylelint-config-prettier": "^8.0.2", - "stylelint-order": "^4.1.0", - "typescript": "~4.4.4" + "postcss": "^8.4.21", + "postcss-scss": "^4.0.6", + "prettier": "^2.8.3", + "sass": "^1.57.1", + "stylelint": "^14.16.1", + "stylelint-config-prettier": "^9.0.4", + "stylelint-order": "^6.0.1", + "ts-node": "^10.9.1", + "typescript": "~4.8.4", + "vite": "^4.0.4", + "vite-plugin-compression": "^0.5.1", + "vite-tsconfig-paths": "^4.0.5" } } diff --git a/ui/v2.5/src/@types/mousetrap-pause.d.ts b/ui/v2.5/src/@types/mousetrap-pause.d.ts new file mode 100644 index 000000000..8abd93fcb --- /dev/null +++ b/ui/v2.5/src/@types/mousetrap-pause.d.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + +declare module "mousetrap-pause" { + import { MousetrapStatic } from "mousetrap"; + + function MousetrapPause(mousetrap: MousetrapStatic): MousetrapStatic; + + export default MousetrapPause; + + module "mousetrap" { + interface MousetrapStatic { + pause(): void; + unpause(): void; + pauseCombo(combo: string): void; + unpauseCombo(combo: string): void; + } + interface MousetrapInstance { + pause(): void; + unpause(): void; + pauseCombo(combo: string): void; + unpauseCombo(combo: string): void; + } + } +} diff --git a/ui/v2.5/src/@types/string.prototype.replaceall.d.ts b/ui/v2.5/src/@types/string.prototype.replaceall.d.ts new file mode 100644 index 000000000..fa87eec06 --- /dev/null +++ b/ui/v2.5/src/@types/string.prototype.replaceall.d.ts @@ -0,0 +1,19 @@ +declare module "string.prototype.replaceall" { + function replaceAll( + searchValue: string | RegExp, + replaceValue: string + ): string; + function replaceAll( + searchValue: string | RegExp, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + replacer: (substring: string, ...args: any[]) => string + ): string; + + namespace replaceAll { + function getPolyfill(): typeof replaceAll; + function implementation(): typeof replaceAll; + function shim(): void; + } + + export default replaceAll; +} diff --git a/ui/v2.5/src/@types/videojs-vtt.d.ts b/ui/v2.5/src/@types/videojs-vtt.d.ts index 7140e5b6e..191c0d27d 100644 --- a/ui/v2.5/src/@types/videojs-vtt.d.ts +++ b/ui/v2.5/src/@types/videojs-vtt.d.ts @@ -1,111 +1,111 @@ /* eslint-disable @typescript-eslint/naming-convention */ declare module "videojs-vtt.js" { - namespace vttjs { - /** - * A custom JS error object that is reported through the parser's `onparsingerror` callback. - * It has a name, code, and message property, along with all the regular properties that come with a JavaScript error object. - * - * There are two error codes that can be reported back currently: - * * 0 BadSignature - * * 1 BadTimeStamp - * - * Note: Exceptions other then ParsingError will be thrown and not reported. - */ - class ParsingError extends Error { - readonly name: string; - readonly code: number; - readonly message: string; - } - - namespace WebVTT { - /** - * A parser for the WebVTT spec in JavaScript. - */ - class Parser { - /** - * The Parser constructor is passed a window object with which it will create new `VTTCues` and `VTTRegions` - * as well as an optional `StringDecoder` object which it will use to decode the data that the `parse()` function receives. - * For ease of use, a `StringDecoder` is provided via `WebVTT.StringDecoder()`. - * If a custom `StringDecoder` object is passed in it must support the API specified by the #whatwg string encoding spec. - * - * @param window the window object to use - * @param vttjs the vtt.js module - * @param decoder the decoder to decode `parse()` data with - */ - constructor(window: Window); - constructor(window: Window, decoder: TextDecoder); - constructor(window: Window, vttjs: vttjs, decoder: TextDecoder); - - /** - * Callback that is invoked for every region that is correctly parsed. Is passed a `VTTRegion` object. - */ - onregion?: (cue: VTTRegion) => void; - - /** - * Callback that is invoked for every cue that is fully parsed. In case of streaming parsing, - * `oncue` is delayed until the cue has been completely received. Is passed a `VTTCue` object. - */ - oncue?: (cue: VTTCue) => void; - - /** - * Is invoked in response to `flush()` and after the content was parsed completely. - */ - onflush?: () => void; - - /** - * Is invoked when a parsing error has occurred. This means that some part of the WebVTT file markup is badly formed. - * Is passed a `ParsingError` object. - */ - onparsingerror?: (e: ParsingError) => void; - - /** - * Hands data in some format to the parser for parsing. The passed data format is expected to be decodable by the - * StringDecoder object that it has. The parser decodes the data and reassembles partial data (streaming), even across line breaks. - * - * @param data data to be parsed - */ - parse(data: string): this; - - /** - * Indicates that no more data is expected and will force the parser to parse any unparsed data that it may have. - * Will also trigger `onflush`. - */ - flush(): this; - } - - /** - * Helper to allow strings to be decoded instead of the default binary utf8 data. - */ - function StringDecoder(): TextDecoder; - - /** - * Parses the cue text handed to it into a tree of DOM nodes that mirrors the internal WebVTT node structure of the cue text. - * It uses the window object handed to it to construct new HTMLElements and returns a tree of DOM nodes attached to a top level div. - * - * @param window window object to use - * @param cuetext cue text to parse - */ - function convertCueToDOMTree( - window: Window, - cuetext: string - ): HTMLDivElement | null; - - /** - * Converts the cuetext of the cues passed to it to DOM trees - by calling convertCueToDOMTree - and then runs the - * processing model steps of the WebVTT specification on the divs. The processing model applies the necessary CSS styles - * to the cue divs to prepare them for display on the web page. During this process the cue divs get added to a block level element (overlay). - * The overlay should be a part of the live DOM as the algorithm will use the computed styles (only of the divs) to do overlap avoidance. - * - * @param overlay A block level element (usually a div) that the computed cues and regions will be placed into. - */ - function processCues( - window: Window, - cues: VTTCue[], - overlay: Element - ): void; - } + /** + * A custom JS error object that is reported through the parser's `onparsingerror` callback. + * It has a name, code, and message property, along with all the regular properties that come with a JavaScript error object. + * + * There are two error codes that can be reported back currently: + * * 0 BadSignature + * * 1 BadTimeStamp + * + * Note: Exceptions other then ParsingError will be thrown and not reported. + */ + class ParsingError extends Error { + readonly name: string; + readonly code: number; + readonly message: string; } - export = vttjs; + export namespace WebVTT { + /** + * A parser for the WebVTT spec in JavaScript. + */ + class Parser { + /** + * The Parser constructor is passed a window object with which it will create new `VTTCues` and `VTTRegions` + * as well as an optional `StringDecoder` object which it will use to decode the data that the `parse()` function receives. + * For ease of use, a `StringDecoder` is provided via `WebVTT.StringDecoder()`. + * If a custom `StringDecoder` object is passed in it must support the API specified by the #whatwg string encoding spec. + * + * @param window the window object to use + * @param vttjs the vtt.js module + * @param decoder the decoder to decode `parse()` data with + */ + constructor(window: Window); + constructor(window: Window, decoder: TextDecoder); + constructor( + window: Window, + vttjs: typeof import("videojs-vtt.js"), + decoder: TextDecoder + ); + + /** + * Callback that is invoked for every region that is correctly parsed. Is passed a `VTTRegion` object. + */ + onregion?: (cue: VTTRegion) => void; + + /** + * Callback that is invoked for every cue that is fully parsed. In case of streaming parsing, + * `oncue` is delayed until the cue has been completely received. Is passed a `VTTCue` object. + */ + oncue?: (cue: VTTCue) => void; + + /** + * Is invoked in response to `flush()` and after the content was parsed completely. + */ + onflush?: () => void; + + /** + * Is invoked when a parsing error has occurred. This means that some part of the WebVTT file markup is badly formed. + * Is passed a `ParsingError` object. + */ + onparsingerror?: (e: ParsingError) => void; + + /** + * Hands data in some format to the parser for parsing. The passed data format is expected to be decodable by the + * StringDecoder object that it has. The parser decodes the data and reassembles partial data (streaming), even across line breaks. + * + * @param data data to be parsed + */ + parse(data: string): this; + + /** + * Indicates that no more data is expected and will force the parser to parse any unparsed data that it may have. + * Will also trigger `onflush`. + */ + flush(): this; + } + + /** + * Helper to allow strings to be decoded instead of the default binary utf8 data. + */ + function StringDecoder(): TextDecoder; + + /** + * Parses the cue text handed to it into a tree of DOM nodes that mirrors the internal WebVTT node structure of the cue text. + * It uses the window object handed to it to construct new HTMLElements and returns a tree of DOM nodes attached to a top level div. + * + * @param window window object to use + * @param cuetext cue text to parse + */ + function convertCueToDOMTree( + window: Window, + cuetext: string + ): HTMLDivElement | null; + + /** + * Converts the cuetext of the cues passed to it to DOM trees - by calling convertCueToDOMTree - and then runs the + * processing model steps of the WebVTT specification on the divs. The processing model applies the necessary CSS styles + * to the cue divs to prepare them for display on the web page. During this process the cue divs get added to a block level element (overlay). + * The overlay should be a part of the live DOM as the algorithm will use the computed styles (only of the divs) to do overlap avoidance. + * + * @param overlay A block level element (usually a div) that the computed cues and regions will be placed into. + */ + function processCues( + window: Window, + cues: VTTCue[], + overlay: Element + ): void; + } } diff --git a/ui/v2.5/src/App.tsx b/ui/v2.5/src/App.tsx index 71a0755e8..11e813f89 100644 --- a/ui/v2.5/src/App.tsx +++ b/ui/v2.5/src/App.tsx @@ -91,10 +91,12 @@ export const App: React.FC = () => { const defaultMessages = (await locales[defaultMessageLanguage]()).default; const mergedMessages = cloneDeep(Object.assign({}, defaultMessages)); const chosenMessages = (await locales[messageLanguage]()).default; - const res = await fetch(getPlatformURL() + "customlocales"); let customMessages = {}; try { - customMessages = res.ok ? await res.json() : {}; + const res = await fetch(getPlatformURL() + "customlocales"); + if (res.ok) { + customMessages = await res.json(); + } } catch (err) { console.log(err); } diff --git a/ui/v2.5/src/components/Changelog/Changelog.tsx b/ui/v2.5/src/components/Changelog/Changelog.tsx index 506d995f4..f4d18b71e 100644 --- a/ui/v2.5/src/components/Changelog/Changelog.tsx +++ b/ui/v2.5/src/components/Changelog/Changelog.tsx @@ -26,9 +26,6 @@ import V0180 from "src/docs/en/Changelog/v0180.md"; import V0190 from "src/docs/en/Changelog/v0190.md"; import { MarkdownPage } from "../Shared/MarkdownPage"; -// to avoid use of explicit any -type Module = typeof V010; - const Changelog: React.FC = () => { const [{ data, loading }, setOpenState] = useChangelogStorage(); @@ -55,7 +52,7 @@ const Changelog: React.FC = () => { interface IStashRelease { version: string; date?: string; - page: Module; + page: string; defaultOpen?: boolean; } diff --git a/ui/v2.5/src/components/Dialogs/IdentifyDialog/FieldOptions.tsx b/ui/v2.5/src/components/Dialogs/IdentifyDialog/FieldOptions.tsx index f8c146fbb..de9bd3b7b 100644 --- a/ui/v2.5/src/components/Dialogs/IdentifyDialog/FieldOptions.tsx +++ b/ui/v2.5/src/components/Dialogs/IdentifyDialog/FieldOptions.tsx @@ -261,9 +261,8 @@ export const FieldOptionsList: React.FC = ({ allowSetDefault = true, defaultOptions, }) => { - const [localFieldOptions, setLocalFieldOptions] = useState< - GQL.IdentifyFieldOptions[] - >(); + const [localFieldOptions, setLocalFieldOptions] = + useState(); const [editField, setEditField] = useState(); useEffect(() => { diff --git a/ui/v2.5/src/components/Dialogs/IdentifyDialog/IdentifyDialog.tsx b/ui/v2.5/src/components/Dialogs/IdentifyDialog/IdentifyDialog.tsx index f5964ee97..366ae4687 100644 --- a/ui/v2.5/src/components/Dialogs/IdentifyDialog/IdentifyDialog.tsx +++ b/ui/v2.5/src/components/Dialogs/IdentifyDialog/IdentifyDialog.tsx @@ -202,9 +202,8 @@ export const IdentifyDialog: React.FC = ({ if (s.options) { const sourceOptions = withoutTypename(s.options); - sourceOptions.fieldOptions = sourceOptions.fieldOptions?.map( - withoutTypename - ); + sourceOptions.fieldOptions = + sourceOptions.fieldOptions?.map(withoutTypename); ret.options = sourceOptions; } @@ -215,9 +214,8 @@ export const IdentifyDialog: React.FC = ({ setSources(mappedSources); if (identifyDefaults.options) { const defaultOptions = withoutTypename(identifyDefaults.options); - defaultOptions.fieldOptions = defaultOptions.fieldOptions?.map( - withoutTypename - ); + defaultOptions.fieldOptions = + defaultOptions.fieldOptions?.map(withoutTypename); setOptions(defaultOptions); } } else { diff --git a/ui/v2.5/src/components/Dialogs/IdentifyDialog/constants.ts b/ui/v2.5/src/components/Dialogs/IdentifyDialog/constants.ts index 46ed88854..13889d037 100644 --- a/ui/v2.5/src/components/Dialogs/IdentifyDialog/constants.ts +++ b/ui/v2.5/src/components/Dialogs/IdentifyDialog/constants.ts @@ -20,7 +20,7 @@ export const sceneFields = [ "tags", "stash_ids", ] as const; -export type SceneField = typeof sceneFields[number]; +export type SceneField = (typeof sceneFields)[number]; export const multiValueSceneFields: SceneField[] = [ "studio", diff --git a/ui/v2.5/src/components/Dialogs/ReleaseNotesDialog.tsx b/ui/v2.5/src/components/Dialogs/ReleaseNotesDialog.tsx index fefc344be..16bd00375 100644 --- a/ui/v2.5/src/components/Dialogs/ReleaseNotesDialog.tsx +++ b/ui/v2.5/src/components/Dialogs/ReleaseNotesDialog.tsx @@ -4,10 +4,9 @@ import { Modal } from "src/components/Shared"; import { faCogs } from "@fortawesome/free-solid-svg-icons"; import { useIntl } from "react-intl"; import { MarkdownPage } from "../Shared/MarkdownPage"; -import { Module } from "src/docs/en/ReleaseNotes"; interface IReleaseNotesDialog { - notes: Module[]; + notes: string[]; onClose: () => void; } diff --git a/ui/v2.5/src/components/Galleries/EditGalleriesDialog.tsx b/ui/v2.5/src/components/Galleries/EditGalleriesDialog.tsx index 24e153acd..867f996b1 100644 --- a/ui/v2.5/src/components/Galleries/EditGalleriesDialog.tsx +++ b/ui/v2.5/src/components/Galleries/EditGalleriesDialog.tsx @@ -31,10 +31,8 @@ export const EditGalleriesDialog: React.FC = ( const Toast = useToast(); const [rating100, setRating] = useState(); const [studioId, setStudioId] = useState(); - const [ - performerMode, - setPerformerMode, - ] = React.useState(GQL.BulkUpdateIdMode.Add); + const [performerMode, setPerformerMode] = + React.useState(GQL.BulkUpdateIdMode.Add); const [performerIds, setPerformerIds] = useState(); const [existingPerformerIds, setExistingPerformerIds] = useState(); const [tagMode, setTagMode] = React.useState( diff --git a/ui/v2.5/src/components/Galleries/Galleries.tsx b/ui/v2.5/src/components/Galleries/Galleries.tsx index 7dd4d3b97..568d9a46c 100644 --- a/ui/v2.5/src/components/Galleries/Galleries.tsx +++ b/ui/v2.5/src/components/Galleries/Galleries.tsx @@ -8,7 +8,7 @@ import Gallery from "./GalleryDetails/Gallery"; import GalleryCreate from "./GalleryDetails/GalleryCreate"; import { GalleryList } from "./GalleryList"; -const Galleries = () => { +const Galleries: React.FC = () => { const intl = useIntl(); const title_template = `${intl.formatMessage({ diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx index 422e45d4e..11f643d1e 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx @@ -214,7 +214,6 @@ export const GalleryPage: React.FC = ({ gallery }) => { setIsDeleteAlertOpen(true)} /> diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryCreate.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryCreate.tsx index 09ad8d17f..62e80e23e 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryCreate.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryCreate.tsx @@ -1,18 +1,15 @@ -import React from "react"; +import React, { useMemo } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import { useLocation } from "react-router-dom"; import { GalleryEditPanel } from "./GalleryEditPanel"; const GalleryCreate: React.FC = () => { const intl = useIntl(); - - function useQuery() { - const { search } = useLocation(); - return React.useMemo(() => new URLSearchParams(search), [search]); - } - - const query = useQuery(); - const nameQuery = query.get("name"); + const location = useLocation(); + const query = useMemo(() => new URLSearchParams(location.search), [location]); + const gallery = { + title: query.get("q") ?? undefined, + }; return (
@@ -23,12 +20,7 @@ const GalleryCreate: React.FC = () => { values={{ entityType: intl.formatMessage({ id: "gallery" }) }} /> - {}} - /> + {}} />
); diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx index 6e472412d..ca5e292fd 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx @@ -40,23 +40,16 @@ import { useRatingKeybinds } from "src/hooks/keybinds"; import { ConfigurationContext } from "src/hooks/Config"; interface IProps { + gallery: Partial; isVisible: boolean; onDelete: () => void; } -interface INewProps { - isNew: true; - gallery?: Partial; -} - -interface IExistingProps { - isNew: false; - gallery: GQL.GalleryDataFragment; -} - -export const GalleryEditPanel: React.FC< - IProps & (INewProps | IExistingProps) -> = ({ gallery, isNew, isVisible, onDelete }) => { +export const GalleryEditPanel: React.FC = ({ + gallery, + isVisible, + onDelete, +}) => { const intl = useIntl(); const Toast = useToast(); const history = useHistory(); @@ -67,15 +60,14 @@ export const GalleryEditPanel: React.FC< })) ); + const isNew = gallery.id === undefined; const { configuration: stashConfig } = React.useContext(ConfigurationContext); const Scrapers = useListGalleryScrapers(); const [queryableScrapers, setQueryableScrapers] = useState([]); - const [ - scrapedGallery, - setScrapedGallery, - ] = useState(); + const [scrapedGallery, setScrapedGallery] = + useState(); // Network state const [isLoading, setIsLoading] = useState(false); diff --git a/ui/v2.5/src/components/Galleries/GalleryRecommendationRow.tsx b/ui/v2.5/src/components/Galleries/GalleryRecommendationRow.tsx index 437eaeb94..dbd10e090 100644 --- a/ui/v2.5/src/components/Galleries/GalleryRecommendationRow.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryRecommendationRow.tsx @@ -1,6 +1,6 @@ -import React, { FunctionComponent } from "react"; +import React from "react"; import { useFindGalleries } from "src/core/StashService"; -import Slider from "react-slick"; +import Slider from "@ant-design/react-slick"; import { GalleryCard } from "./GalleryCard"; import { ListFilterModel } from "src/models/list-filter/filter"; import { getSlickSliderSettings } from "src/core/recommendations"; @@ -13,9 +13,7 @@ interface IProps { header: string; } -export const GalleryRecommendationRow: FunctionComponent = ( - props: IProps -) => { +export const GalleryRecommendationRow: React.FC = (props) => { const result = useFindGalleries(props.filter); const cardCount = result.data?.findGalleries.count; diff --git a/ui/v2.5/src/components/Galleries/styles.scss b/ui/v2.5/src/components/Galleries/styles.scss index 93079baf0..1ee4ee152 100644 --- a/ui/v2.5/src/components/Galleries/styles.scss +++ b/ui/v2.5/src/components/Galleries/styles.scss @@ -1,3 +1,5 @@ +@use "sass:math"; + .gallery-image { &:hover { cursor: pointer; @@ -138,14 +140,14 @@ $galleryTabWidth: 450px; } @mixin galleryWidth($width) { - height: ($width / 3) * 2; + height: math.div($width, 3) * 2; &-landscape { width: $width; } &-portrait { - width: $width / 2; + width: math.div($width, 2); } } diff --git a/ui/v2.5/src/components/Help/Manual.tsx b/ui/v2.5/src/components/Help/Manual.tsx index 12faa3e26..7af8756f8 100644 --- a/ui/v2.5/src/components/Help/Manual.tsx +++ b/ui/v2.5/src/components/Help/Manual.tsx @@ -173,11 +173,9 @@ export const Manual: React.FC = ({ event: React.MouseEvent ) { if (event.target instanceof HTMLAnchorElement) { - const href = (event.target as HTMLAnchorElement).getAttribute("href"); + const href = event.target.getAttribute("href"); if (href && href.startsWith("/help")) { - const newKey = (event.target as HTMLAnchorElement).pathname.substring( - "/help/".length - ); + const newKey = event.target.pathname.substring("/help/".length); setActiveTab(newKey); event.preventDefault(); } diff --git a/ui/v2.5/src/components/Images/EditImagesDialog.tsx b/ui/v2.5/src/components/Images/EditImagesDialog.tsx index 6d18f9df3..1f34ec078 100644 --- a/ui/v2.5/src/components/Images/EditImagesDialog.tsx +++ b/ui/v2.5/src/components/Images/EditImagesDialog.tsx @@ -31,10 +31,8 @@ export const EditImagesDialog: React.FC = ( const Toast = useToast(); const [rating100, setRating] = useState(); const [studioId, setStudioId] = useState(); - const [ - performerMode, - setPerformerMode, - ] = React.useState(GQL.BulkUpdateIdMode.Add); + const [performerMode, setPerformerMode] = + React.useState(GQL.BulkUpdateIdMode.Add); const [performerIds, setPerformerIds] = useState(); const [existingPerformerIds, setExistingPerformerIds] = useState(); const [tagMode, setTagMode] = React.useState( diff --git a/ui/v2.5/src/components/Images/ImageDetails/Image.tsx b/ui/v2.5/src/components/Images/ImageDetails/Image.tsx index 72d88bad9..0d70f3370 100644 --- a/ui/v2.5/src/components/Images/ImageDetails/Image.tsx +++ b/ui/v2.5/src/components/Images/ImageDetails/Image.tsx @@ -239,7 +239,9 @@ export const Image: React.FC = () => { Mousetrap.bind("a", () => setActiveTabKey("image-details-panel")); Mousetrap.bind("e", () => setActiveTabKey("image-edit-panel")); Mousetrap.bind("f", () => setActiveTabKey("image-file-info-panel")); - Mousetrap.bind("o", () => onIncrementClick()); + Mousetrap.bind("o", () => { + onIncrementClick(); + }); return () => { Mousetrap.unbind("a"); diff --git a/ui/v2.5/src/components/Images/ImageRecommendationRow.tsx b/ui/v2.5/src/components/Images/ImageRecommendationRow.tsx index 55f7e7b78..36f13b8d4 100644 --- a/ui/v2.5/src/components/Images/ImageRecommendationRow.tsx +++ b/ui/v2.5/src/components/Images/ImageRecommendationRow.tsx @@ -1,6 +1,6 @@ -import React, { FunctionComponent } from "react"; +import React from "react"; import { useFindImages } from "src/core/StashService"; -import Slider from "react-slick"; +import Slider from "@ant-design/react-slick"; import { ListFilterModel } from "src/models/list-filter/filter"; import { getSlickSliderSettings } from "src/core/recommendations"; import { RecommendationRow } from "../FrontPage/RecommendationRow"; @@ -13,9 +13,7 @@ interface IProps { header: string; } -export const ImageRecommendationRow: FunctionComponent = ( - props: IProps -) => { +export const ImageRecommendationRow: React.FC = (props: IProps) => { const result = useFindImages(props.filter); const cardCount = result.data?.findImages.count; diff --git a/ui/v2.5/src/components/List/Filters/HierarchicalLabelValueFilter.tsx b/ui/v2.5/src/components/List/Filters/HierarchicalLabelValueFilter.tsx index 52a3bae7e..68d75ad39 100644 --- a/ui/v2.5/src/components/List/Filters/HierarchicalLabelValueFilter.tsx +++ b/ui/v2.5/src/components/List/Filters/HierarchicalLabelValueFilter.tsx @@ -10,10 +10,9 @@ interface IHierarchicalLabelValueFilterProps { onValueChanged: (value: IHierarchicalLabelValue) => void; } -export const HierarchicalLabelValueFilter: React.FC = ({ - criterion, - onValueChanged, -}) => { +export const HierarchicalLabelValueFilter: React.FC< + IHierarchicalLabelValueFilterProps +> = ({ criterion, onValueChanged }) => { const intl = useIntl(); if ( diff --git a/ui/v2.5/src/components/MainNavbar.tsx b/ui/v2.5/src/components/MainNavbar.tsx index d48fb015a..ba69ad52a 100644 --- a/ui/v2.5/src/components/MainNavbar.tsx +++ b/ui/v2.5/src/components/MainNavbar.tsx @@ -18,7 +18,7 @@ import { ManualStateContext } from "./Help/context"; import { SettingsButton } from "./SettingsButton"; import { faBars, - faChartBar, + faChartColumn, faFilm, faHeart, faImage, @@ -220,10 +220,10 @@ export const MainNavbar: React.FC = () => { const pathname = location.pathname.replace(/\/$/, ""); let newPath = newPathsList.includes(pathname) ? `${pathname}/new` : null; - if (newPath != null) { + if (newPath !== null) { let queryParam = new URLSearchParams(location.search).get("q"); - if (queryParam != null) { - newPath += "?name=" + encodeURIComponent(queryParam); + if (queryParam) { + newPath += "?q=" + encodeURIComponent(queryParam); } } @@ -296,7 +296,7 @@ export const MainNavbar: React.FC = () => { className="minimal d-flex align-items-center h-100" title={intl.formatMessage({ id: "statistics" })} > - + void; } -export const MovieCard: FunctionComponent = (props: IProps) => { +export const MovieCard: React.FC = (props: IProps) => { function maybeRenderSceneNumber() { if (!props.sceneIndex) return; diff --git a/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx b/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx index 3984d4c33..2f7076078 100644 --- a/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx +++ b/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx @@ -51,7 +51,9 @@ const MoviePage: React.FC = ({ movie }) => { // set up hotkeys useEffect(() => { Mousetrap.bind("e", () => setIsEditing(true)); - Mousetrap.bind("d d", () => onDelete()); + Mousetrap.bind("d d", () => { + onDelete(); + }); return () => { Mousetrap.unbind("e"); diff --git a/ui/v2.5/src/components/Movies/MovieDetails/MovieCreate.tsx b/ui/v2.5/src/components/Movies/MovieDetails/MovieCreate.tsx index faef1c20c..38437a395 100644 --- a/ui/v2.5/src/components/Movies/MovieDetails/MovieCreate.tsx +++ b/ui/v2.5/src/components/Movies/MovieDetails/MovieCreate.tsx @@ -1,22 +1,24 @@ -import React, { useState } from "react"; +import React, { useMemo, useState } from "react"; import * as GQL from "src/core/generated-graphql"; import { useMovieCreate } from "src/core/StashService"; -import { useHistory } from "react-router-dom"; +import { useHistory, useLocation } from "react-router-dom"; import { LoadingIndicator } from "src/components/Shared"; import { useToast } from "src/hooks"; import { MovieEditPanel } from "./MovieEditPanel"; const MovieCreate: React.FC = () => { const history = useHistory(); + const location = useLocation(); const Toast = useToast(); + const query = useMemo(() => new URLSearchParams(location.search), [location]); + const movie = { + name: query.get("q") ?? undefined, + }; + // Editing movie state - const [frontImage, setFrontImage] = useState( - undefined - ); - const [backImage, setBackImage] = useState( - undefined - ); + const [frontImage, setFrontImage] = useState(); + const [backImage, setBackImage] = useState(); const [encodingImage, setEncodingImage] = useState(false); const [createMovie] = useMovieCreate(); @@ -84,6 +86,7 @@ const MovieCreate: React.FC = () => { history.push("/movies")} onDelete={() => {}} diff --git a/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx b/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx index b822724b5..8f25000bf 100644 --- a/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx +++ b/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx @@ -25,7 +25,7 @@ import { useRatingKeybinds } from "src/hooks/keybinds"; import { ConfigurationContext } from "src/hooks/Config"; interface IMovieEditPanel { - movie?: Partial; + movie: Partial; onSubmit: ( movie: Partial ) => void; @@ -49,19 +49,15 @@ export const MovieEditPanel: React.FC = ({ const Toast = useToast(); const { configuration: stashConfig } = React.useContext(ConfigurationContext); - const isNew = movie === undefined; + const isNew = movie.id === undefined; const [isLoading, setIsLoading] = useState(false); const [isImageAlertOpen, setIsImageAlertOpen] = useState(false); - const [imageClipboard, setImageClipboard] = useState( - undefined - ); + const [imageClipboard, setImageClipboard] = useState(); const Scrapers = useListMovieScrapers(); - const [scrapedMovie, setScrapedMovie] = useState< - GQL.ScrapedMovie | undefined - >(); + const [scrapedMovie, setScrapedMovie] = useState(); const schema = yup.object({ name: yup.string().required(), @@ -113,10 +109,10 @@ export const MovieEditPanel: React.FC = ({ setBackImage(formik.values.back_image); }, [formik.values.back_image, setBackImage]); - useEffect(() => onImageEncoding(encodingImage), [ - onImageEncoding, - encodingImage, - ]); + useEffect( + () => onImageEncoding(encodingImage), + [onImageEncoding, encodingImage] + ); function setRating(v: number) { formik.setFieldValue("rating100", v); diff --git a/ui/v2.5/src/components/Movies/MovieRecommendationRow.tsx b/ui/v2.5/src/components/Movies/MovieRecommendationRow.tsx index 710b54b59..34d5119ff 100644 --- a/ui/v2.5/src/components/Movies/MovieRecommendationRow.tsx +++ b/ui/v2.5/src/components/Movies/MovieRecommendationRow.tsx @@ -1,6 +1,6 @@ import React from "react"; import { useFindMovies } from "src/core/StashService"; -import Slider from "react-slick"; +import Slider from "@ant-design/react-slick"; import { MovieCard } from "./MovieCard"; import { ListFilterModel } from "src/models/list-filter/filter"; import { getSlickSliderSettings } from "src/core/recommendations"; diff --git a/ui/v2.5/src/components/PageNotFound.tsx b/ui/v2.5/src/components/PageNotFound.tsx index 645709fcf..67cb6b38c 100644 --- a/ui/v2.5/src/components/PageNotFound.tsx +++ b/ui/v2.5/src/components/PageNotFound.tsx @@ -1,5 +1,5 @@ -import React, { FunctionComponent } from "react"; +import React from "react"; -export const PageNotFound: FunctionComponent = () => { +export const PageNotFound: React.FC = () => { return

Page not found.

; }; diff --git a/ui/v2.5/src/components/Performers/EditPerformersDialog.tsx b/ui/v2.5/src/components/Performers/EditPerformersDialog.tsx index fc189eeb7..4d25e7fab 100644 --- a/ui/v2.5/src/components/Performers/EditPerformersDialog.tsx +++ b/ui/v2.5/src/components/Performers/EditPerformersDialog.tsx @@ -60,10 +60,8 @@ export const EditPerformersDialog: React.FC = ( mode: GQL.BulkUpdateIdMode.Add, }); const [existingTagIds, setExistingTagIds] = useState(); - const [ - aggregateState, - setAggregateState, - ] = useState({}); + const [aggregateState, setAggregateState] = + useState({}); // weight needs conversion to/from number const [weight, setWeight] = useState(); const [updateInput, setUpdateInput] = useState( diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx index 8d7077406..c021441c6 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx @@ -32,12 +32,8 @@ import { PerformerImagesPanel } from "./PerformerImagesPanel"; import { PerformerEditPanel } from "./PerformerEditPanel"; import { PerformerSubmitButton } from "./PerformerSubmitButton"; import GenderIcon from "../GenderIcon"; -import { - faCamera, - faDove, - faHeart, - faLink, -} from "@fortawesome/free-solid-svg-icons"; +import { faHeart, faLink } from "@fortawesome/free-solid-svg-icons"; +import { faInstagram, faTwitter } from "@fortawesome/free-brands-svg-icons"; import { IUIConfig } from "src/core/config"; import { useRatingKeybinds } from "src/hooks/keybinds"; @@ -247,7 +243,6 @@ const PerformerPage: React.FC = ({ performer }) => { { @@ -351,7 +346,7 @@ const PerformerPage: React.FC = ({ performer }) => { target="_blank" rel="noopener noreferrer" > - + )} @@ -366,7 +361,7 @@ const PerformerPage: React.FC = ({ performer }) => { target="_blank" rel="noopener noreferrer" > - + )} @@ -405,7 +400,7 @@ const PerformerPage: React.FC = ({ performer }) => {

{performer.name} diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerCreate.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerCreate.tsx index 1427ceb4a..9306caeba 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerCreate.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerCreate.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useMemo, useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import { LoadingIndicator } from "src/components/Shared"; import { PerformerEditPanel } from "./PerformerEditPanel"; @@ -8,13 +8,11 @@ const PerformerCreate: React.FC = () => { const [imagePreview, setImagePreview] = useState(); const [imageEncoding, setImageEncoding] = useState(false); - function useQuery() { - const { search } = useLocation(); - return React.useMemo(() => new URLSearchParams(search), [search]); - } - - const query = useQuery(); - const nameQuery = query.get("name"); + const location = useLocation(); + const query = useMemo(() => new URLSearchParams(location.search), [location]); + const performer = { + name: query.get("q") ?? undefined, + }; const activeImage = imagePreview ?? ""; const intl = useIntl(); @@ -50,9 +48,8 @@ const PerformerCreate: React.FC = () => { />

diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx index f542ee7de..40f87c2a5 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx @@ -50,7 +50,6 @@ const isScraper = ( interface IPerformerDetails { performer: Partial; - isNew?: boolean; isVisible: boolean; onImageChange?: (image?: string | null) => void; onImageEncoding?: (loading?: boolean) => void; @@ -59,7 +58,6 @@ interface IPerformerDetails { export const PerformerEditPanel: React.FC = ({ performer, - isNew, isVisible, onImageChange, onImageEncoding, @@ -68,8 +66,10 @@ export const PerformerEditPanel: React.FC = ({ const Toast = useToast(); const history = useHistory(); + const isNew = performer.id === undefined; + // Editing state - const [scraper, setScraper] = useState(); + const [scraper, setScraper] = useState(); const [newTags, setNewTags] = useState(); const [isScraperModalOpen, setIsScraperModalOpen] = useState(false); @@ -447,10 +447,10 @@ export const PerformerEditPanel: React.FC = ({ return () => onImageChange?.(); }, [formik.values.image, onImageChange]); - useEffect(() => onImageEncoding?.(imageEncoding), [ - onImageEncoding, - imageEncoding, - ]); + useEffect( + () => onImageEncoding?.(imageEncoding), + [onImageEncoding, imageEncoding] + ); useEffect(() => { const newQueryableScrapers = ( diff --git a/ui/v2.5/src/components/Performers/PerformerRecommendationRow.tsx b/ui/v2.5/src/components/Performers/PerformerRecommendationRow.tsx index 300b93f2f..40611967a 100644 --- a/ui/v2.5/src/components/Performers/PerformerRecommendationRow.tsx +++ b/ui/v2.5/src/components/Performers/PerformerRecommendationRow.tsx @@ -1,6 +1,6 @@ -import React, { FunctionComponent } from "react"; +import React from "react"; import { useFindPerformers } from "src/core/StashService"; -import Slider from "react-slick"; +import Slider from "@ant-design/react-slick"; import { PerformerCard } from "./PerformerCard"; import { ListFilterModel } from "src/models/list-filter/filter"; import { getSlickSliderSettings } from "src/core/recommendations"; @@ -13,9 +13,7 @@ interface IProps { header: string; } -export const PerformerRecommendationRow: FunctionComponent = ( - props: IProps -) => { +export const PerformerRecommendationRow: React.FC = (props) => { const result = useFindPerformers(props.filter); const cardCount = result.data?.findPerformers.count; diff --git a/ui/v2.5/src/components/Performers/styles.scss b/ui/v2.5/src/components/Performers/styles.scss index 1e8fbb7ba..910088a8b 100644 --- a/ui/v2.5/src/components/Performers/styles.scss +++ b/ui/v2.5/src/components/Performers/styles.scss @@ -76,7 +76,7 @@ width: 100%; } - .flag-icon { + .fi { bottom: 1rem; filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.9)); height: 2rem; diff --git a/ui/v2.5/src/components/SceneDuplicateChecker/SceneDuplicateChecker.tsx b/ui/v2.5/src/components/SceneDuplicateChecker/SceneDuplicateChecker.tsx index 521ec41c6..5617f7ac1 100644 --- a/ui/v2.5/src/components/SceneDuplicateChecker/SceneDuplicateChecker.tsx +++ b/ui/v2.5/src/components/SceneDuplicateChecker/SceneDuplicateChecker.tsx @@ -12,7 +12,6 @@ import { } from "react-bootstrap"; import { Link, useHistory } from "react-router-dom"; import { FormattedMessage, FormattedNumber, useIntl } from "react-intl"; -import querystring from "query-string"; import * as GQL from "src/core/generated-graphql"; import { @@ -47,20 +46,13 @@ const CLASSNAME = "duplicate-checker"; export const SceneDuplicateChecker: React.FC = () => { const intl = useIntl(); const history = useHistory(); - const { page, size, distance } = querystring.parse(history.location.search); - const currentPage = Number.parseInt( - Array.isArray(page) ? page[0] : page ?? "1", - 10 - ); - const pageSize = Number.parseInt( - Array.isArray(size) ? size[0] : size ?? "20", - 10 - ); + + const query = new URLSearchParams(history.location.search); + const currentPage = Number.parseInt(query.get("page") ?? "1", 10); + const pageSize = Number.parseInt(query.get("size") ?? "20", 10); + const hashDistance = Number.parseInt(query.get("distance") ?? "0", 10); + const [currentPageSize, setCurrentPageSize] = useState(pageSize); - const hashDistance = Number.parseInt( - Array.isArray(distance) ? distance[0] : distance ?? "0", - 10 - ); const [isMultiDelete, setIsMultiDelete] = useState(false); const [deletingScenes, setDeletingScenes] = useState(false); const [editingScenes, setEditingScenes] = useState(false); @@ -90,9 +82,8 @@ export const SceneDuplicateChecker: React.FC = () => { GQL.SlimSceneDataFragment[] | null >(null); - const [mergeScenes, setMergeScenes] = useState< - { id: string; title: string }[] | undefined - >(undefined); + const [mergeScenes, setMergeScenes] = + useState<{ id: string; title: string }[]>(); if (loading) return ; if (!data) return ; @@ -107,12 +98,16 @@ export const SceneDuplicateChecker: React.FC = () => { ).length; const setQuery = (q: Record) => { - history.push({ - search: querystring.stringify({ - ...querystring.parse(history.location.search), - ...q, - }), - }); + const newQuery = new URLSearchParams(query); + for (const key of Object.keys(q)) { + const value = q[key]; + if (value !== undefined) { + newQuery.set(key, String(value)); + } else { + newQuery.delete(key); + } + } + history.push({ search: newQuery.toString() }); }; function onDeleteDialogClosed(deleted: boolean) { @@ -504,7 +499,7 @@ export const SceneDuplicateChecker: React.FC = () => { page: undefined, }) } - defaultValue={distance ?? 0} + defaultValue={hashDistance} className="input-control ml-4" >