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
-
+
You need to enable JavaScript to run this app.
-
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"
>
diff --git a/ui/v2.5/src/components/SceneFilenameParser/SceneFilenameParser.tsx b/ui/v2.5/src/components/SceneFilenameParser/SceneFilenameParser.tsx
index 0074435eb..3fac41159 100644
--- a/ui/v2.5/src/components/SceneFilenameParser/SceneFilenameParser.tsx
+++ b/ui/v2.5/src/components/SceneFilenameParser/SceneFilenameParser.tsx
@@ -40,9 +40,8 @@ export const SceneFilenameParser: React.FC = () => {
const intl = useIntl();
const Toast = useToast();
const [parserResult, setParserResult] = useState([]);
- const [parserInput, setParserInput] = useState(
- initialParserInput
- );
+ const [parserInput, setParserInput] =
+ useState(initialParserInput);
const prevParserInputRef = useRef();
const prevParserInput = prevParserInputRef.current;
diff --git a/ui/v2.5/src/components/SceneFilenameParser/ShowFields.tsx b/ui/v2.5/src/components/SceneFilenameParser/ShowFields.tsx
index ea1fcccf9..b707e9b17 100644
--- a/ui/v2.5/src/components/SceneFilenameParser/ShowFields.tsx
+++ b/ui/v2.5/src/components/SceneFilenameParser/ShowFields.tsx
@@ -14,7 +14,7 @@ interface IShowFieldsProps {
onShowFieldsChanged: (fields: Map) => void;
}
-export const ShowFields = (props: IShowFieldsProps) => {
+export const ShowFields: React.FC = (props) => {
const intl = useIntl();
const [open, setOpen] = useState(false);
diff --git a/ui/v2.5/src/components/ScenePlayer/track-activity.ts b/ui/v2.5/src/components/ScenePlayer/track-activity.ts
index f4ed2eb49..6b5cbd051 100644
--- a/ui/v2.5/src/components/ScenePlayer/track-activity.ts
+++ b/ui/v2.5/src/components/ScenePlayer/track-activity.ts
@@ -10,12 +10,10 @@ class TrackActivityPlugin extends videojs.getPlugin("plugin") {
incrementPlayCount: () => Promise = () => {
return Promise.resolve();
};
- saveActivity: (
- resumeTime: number,
- playDuration: number
- ) => Promise = () => {
- return Promise.resolve();
- };
+ saveActivity: (resumeTime: number, playDuration: number) => Promise =
+ () => {
+ return Promise.resolve();
+ };
private enabled = false;
private playCountIncremented = false;
diff --git a/ui/v2.5/src/components/Scenes/EditScenesDialog.tsx b/ui/v2.5/src/components/Scenes/EditScenesDialog.tsx
index 34947060f..b3551ae14 100644
--- a/ui/v2.5/src/components/Scenes/EditScenesDialog.tsx
+++ b/ui/v2.5/src/components/Scenes/EditScenesDialog.tsx
@@ -32,10 +32,8 @@ export const EditScenesDialog: 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/Scenes/SceneCard.tsx b/ui/v2.5/src/components/Scenes/SceneCard.tsx
index b7b79c0e7..d744ee348 100644
--- a/ui/v2.5/src/components/Scenes/SceneCard.tsx
+++ b/ui/v2.5/src/components/Scenes/SceneCard.tsx
@@ -99,9 +99,8 @@ export const SceneCard: React.FC = (
);
// studio image is missing if it uses the default
- const missingStudioImage = props.scene.studio?.image_path?.endsWith(
- "?default=true"
- );
+ const missingStudioImage =
+ props.scene.studio?.image_path?.endsWith("?default=true");
const showStudioAsText =
missingStudioImage || (configuration?.interface.showStudioAsText ?? false);
diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx
index d86973815..9c6d67574 100644
--- a/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx
+++ b/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx
@@ -1,5 +1,4 @@
import { Tab, Nav, Dropdown, Button, ButtonGroup } from "react-bootstrap";
-import queryString from "query-string";
import React, {
useEffect,
useState,
@@ -144,7 +143,9 @@ const ScenePage: React.FC = ({
Mousetrap.bind("e", () => setActiveTabKey("scene-edit-panel"));
Mousetrap.bind("k", () => setActiveTabKey("scene-markers-panel"));
Mousetrap.bind("i", () => setActiveTabKey("scene-file-info-panel"));
- Mousetrap.bind("o", () => onIncrementClick());
+ Mousetrap.bind("o", () => {
+ onIncrementClick();
+ });
Mousetrap.bind("p n", () => onQueueNext());
Mousetrap.bind("p p", () => onQueuePrevious());
Mousetrap.bind("p r", () => onQueueRandom());
@@ -521,7 +522,7 @@ const SceneLoader: React.FC = () => {
const { data, loading, error } = useFindScene(id ?? "");
const queryParams = useMemo(
- () => queryString.parse(location.search, { decode: false }),
+ () => new URLSearchParams(location.search),
[location.search]
);
const sceneQueue = useMemo(
@@ -529,13 +530,13 @@ const SceneLoader: React.FC = () => {
[queryParams]
);
const queryContinue = useMemo(() => {
- let cont = queryParams.continue;
- if (cont !== undefined) {
+ let cont = queryParams.get("continue");
+ if (cont) {
return cont === "true";
} else {
return !!configuration?.interface.continuePlaylistDefault;
}
- }, [configuration?.interface.continuePlaylistDefault, queryParams.continue]);
+ }, [configuration?.interface.continuePlaylistDefault, queryParams]);
const [queueScenes, setQueueScenes] = useState([]);
@@ -547,14 +548,13 @@ const SceneLoader: React.FC = () => {
const _setTimestamp = useRef<(value: number) => void>();
const initialTimestamp = useMemo(() => {
- const t = Array.isArray(queryParams.t) ? queryParams.t[0] : queryParams.t;
- return Number.parseInt(t ?? "0", 10);
+ return Number.parseInt(queryParams.get("t") ?? "0", 10);
}, [queryParams]);
const [queueTotal, setQueueTotal] = useState(0);
const [queueStart, setQueueStart] = useState(1);
- const autoplay = queryParams.autoplay === "true";
+ const autoplay = queryParams.get("autoplay") === "true";
const currentQueueIndex = queueScenes
? queueScenes.findIndex((s) => s.id === id)
: -1;
diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneCreate.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneCreate.tsx
index 535e8f7d8..a197a5c46 100644
--- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneCreate.tsx
+++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneCreate.tsx
@@ -1,7 +1,7 @@
import React, { useEffect, useMemo, useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";
+import { useLocation } from "react-router-dom";
import { SceneEditPanel } from "./SceneEditPanel";
-import queryString from "query-string";
import { useFindScene } from "src/core/StashService";
import { ImageUtils } from "src/utils";
import { LoadingIndicator } from "src/components/Shared";
@@ -9,13 +9,13 @@ import { LoadingIndicator } from "src/components/Shared";
const SceneCreate: React.FC = () => {
const intl = useIntl();
- // create scene from provided scene id if applicable
- const queryParams = queryString.parse(location.search);
+ const location = useLocation();
+ const query = useMemo(() => new URLSearchParams(location.search), [location]);
- const fromSceneID = (queryParams?.from_scene_id ?? "") as string;
- const { data, loading } = useFindScene(fromSceneID ?? "");
+ // create scene from provided scene id if applicable
+ const { data, loading } = useFindScene(query.get("from_scene_id") ?? "");
const [loadingCoverImage, setLoadingCoverImage] = useState(false);
- const [coverImage, setCoverImage] = useState(undefined);
+ const [coverImage, setCoverImage] = useState();
const scene = useMemo(() => {
if (data?.findScene) {
@@ -26,8 +26,10 @@ const SceneCreate: React.FC = () => {
};
}
- return {};
- }, [data?.findScene]);
+ return {
+ title: query.get("q") ?? undefined,
+ };
+ }, [data?.findScene, query]);
useEffect(() => {
async function fetchCoverImage() {
@@ -62,6 +64,7 @@ const SceneCreate: React.FC = () => {
import("./SceneQueryModal"));
interface IProps {
scene: Partial;
+ fileID?: string;
initialCoverImage?: string;
isNew?: boolean;
isVisible: boolean;
@@ -62,6 +62,7 @@ interface IProps {
export const SceneEditPanel: React.FC = ({
scene,
+ fileID,
initialCoverImage,
isNew = false,
isVisible,
@@ -71,10 +72,6 @@ export const SceneEditPanel: React.FC = ({
const Toast = useToast();
const history = useHistory();
- const queryParams = queryString.parse(location.search);
-
- const fileID = (queryParams?.file_id ?? "") as string;
-
const [galleries, setGalleries] = useState<{ id: string; title: string }[]>(
[]
);
@@ -83,17 +80,13 @@ export const SceneEditPanel: React.FC = ({
const [fragmentScrapers, setFragmentScrapers] = useState([]);
const [queryableScrapers, setQueryableScrapers] = useState([]);
- const [scraper, setScraper] = useState();
- const [
- isScraperQueryModalOpen,
- setIsScraperQueryModalOpen,
- ] = useState(false);
+ const [scraper, setScraper] = useState();
+ const [isScraperQueryModalOpen, setIsScraperQueryModalOpen] =
+ useState(false);
const [scrapedScene, setScrapedScene] = useState();
- const [endpoint, setEndpoint] = useState();
+ const [endpoint, setEndpoint] = useState();
- const [coverImagePreview, setCoverImagePreview] = useState<
- string | undefined
- >();
+ const [coverImagePreview, setCoverImagePreview] = useState();
useEffect(() => {
setCoverImagePreview(
@@ -286,7 +279,7 @@ export const SceneEditPanel: React.FC = ({
const createValues = getCreateValues(input);
const result = await mutateCreateScene({
...createValues,
- file_ids: fileID ? [fileID as string] : undefined,
+ file_ids: fileID ? [fileID] : undefined,
});
if (result.data?.sceneCreate?.id) {
history.push(`/scenes/${result.data?.sceneCreate.id}`);
@@ -901,9 +894,8 @@ export const SceneEditPanel: React.FC = ({
{formik.values.stash_ids.map((stashID) => {
- const base = stashID.endpoint.match(
- /https?:\/\/.*?\//
- )?.[0];
+ const base =
+ stashID.endpoint.match(/https?:\/\/.*?\//)?.[0];
const link = base ? (
= (
const Toast = useToast();
const [loading, setLoading] = useState(false);
- const [deletingFile, setDeletingFile] = useState<
- GQL.VideoFileDataFragment | undefined
- >();
- const [reassigningFile, setReassigningFile] = useState<
- GQL.VideoFileDataFragment | undefined
- >();
+ const [deletingFile, setDeletingFile] = useState();
+ const [reassigningFile, setReassigningFile] =
+ useState();
function renderStashIDs() {
if (!props.scene.stash_ids.length) {
diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneMarkersPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneMarkersPanel.tsx
index 10b8228b6..ed1f4d7c0 100644
--- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneMarkersPanel.tsx
+++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneMarkersPanel.tsx
@@ -22,10 +22,8 @@ export const SceneMarkersPanel: React.FC = (
},
});
const [isEditorOpen, setIsEditorOpen] = useState(false);
- const [
- editingMarker,
- setEditingMarker,
- ] = useState();
+ const [editingMarker, setEditingMarker] =
+ useState();
// set up hotkeys
useEffect(() => {
diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneMoviePanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneMoviePanel.tsx
index 6961c4761..b63fd195d 100644
--- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneMoviePanel.tsx
+++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneMoviePanel.tsx
@@ -1,4 +1,4 @@
-import React, { FunctionComponent } from "react";
+import React from "react";
import * as GQL from "src/core/generated-graphql";
import { MovieCard } from "src/components/Movies/MovieCard";
@@ -6,7 +6,7 @@ interface ISceneMoviePanelProps {
scene: GQL.SceneDataFragment;
}
-export const SceneMoviePanel: FunctionComponent = (
+export const SceneMoviePanel: React.FC = (
props: ISceneMoviePanelProps
) => {
const cards = props.scene.movies.map((sceneMovie) => (
diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneMovieTable.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneMovieTable.tsx
index af9f8305d..a7a8ba56b 100644
--- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneMovieTable.tsx
+++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneMovieTable.tsx
@@ -1,4 +1,4 @@
-import * as React from "react";
+import React from "react";
import { useIntl } from "react-intl";
import * as GQL from "src/core/generated-graphql";
import { useAllMoviesForFilter } from "src/core/StashService";
@@ -11,9 +11,7 @@ export interface IProps {
onUpdate: (value: GQL.SceneMovieInput[]) => void;
}
-export const SceneMovieTable: React.FunctionComponent = (
- props: IProps
-) => {
+export const SceneMovieTable: React.FC = (props) => {
const intl = useIntl();
const { data } = useAllMoviesForFilter();
diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneScrapeDialog.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneScrapeDialog.tsx
index 3f4accd29..eb10903dc 100644
--- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneScrapeDialog.tsx
+++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneScrapeDialog.tsx
@@ -97,14 +97,8 @@ interface IScrapedObjectsRow {
export const ScrapedObjectsRow = (
props: IScrapedObjectsRow
) => {
- const {
- title,
- result,
- onChange,
- newObjects,
- onCreateNew,
- renderObjects,
- } = props;
+ const { title, result, onChange, newObjects, onCreateNew, renderObjects } =
+ props;
return (
= ({
const history = useHistory();
const config = React.useContext(ConfigurationContext);
const [isGenerateDialogOpen, setIsGenerateDialogOpen] = useState(false);
- const [mergeScenes, setMergeScenes] = useState<
- { id: string; title: string }[] | undefined
- >(undefined);
+ const [mergeScenes, setMergeScenes] =
+ useState<{ id: string; title: string }[]>();
const [isIdentifyDialogOpen, setIsIdentifyDialogOpen] = useState(false);
const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);
const [isExportAll, setIsExportAll] = useState(false);
diff --git a/ui/v2.5/src/components/Scenes/SceneListTable.tsx b/ui/v2.5/src/components/Scenes/SceneListTable.tsx
index 7bf546257..16b47f436 100644
--- a/ui/v2.5/src/components/Scenes/SceneListTable.tsx
+++ b/ui/v2.5/src/components/Scenes/SceneListTable.tsx
@@ -36,7 +36,10 @@ export const SceneListTable: React.FC = (
const renderMovies = (scene: GQL.SlimSceneDataFragment) =>
scene.movies.map((sceneMovie) =>
!sceneMovie.movie ? undefined : (
-
+
{sceneMovie.movie.name}
)
diff --git a/ui/v2.5/src/components/Scenes/SceneMergeDialog.tsx b/ui/v2.5/src/components/Scenes/SceneMergeDialog.tsx
index dfbeacb74..df2f77383 100644
--- a/ui/v2.5/src/components/Scenes/SceneMergeDialog.tsx
+++ b/ui/v2.5/src/components/Scenes/SceneMergeDialog.tsx
@@ -133,7 +133,7 @@ const SceneMergeDetails: React.FC = ({
setLoading(true);
const destData = await ImageUtils.imageToDataURL(dest.paths.screenshot);
- const srcData = await ImageUtils.imageToDataURL(src.paths!.screenshot!);
+ const srcData = await ImageUtils.imageToDataURL(src.paths.screenshot!);
// keep destination image by default
const useNewValue = false;
diff --git a/ui/v2.5/src/components/Scenes/SceneRecommendationRow.tsx b/ui/v2.5/src/components/Scenes/SceneRecommendationRow.tsx
index 2d9aa7371..86ae558f0 100644
--- a/ui/v2.5/src/components/Scenes/SceneRecommendationRow.tsx
+++ b/ui/v2.5/src/components/Scenes/SceneRecommendationRow.tsx
@@ -1,6 +1,6 @@
-import React, { FunctionComponent, useMemo } from "react";
+import React, { useMemo } from "react";
import { useFindScenes } from "src/core/StashService";
-import Slider from "react-slick";
+import Slider from "@ant-design/react-slick";
import { SceneCard } from "./SceneCard";
import { SceneQueue } from "src/models/sceneQueue";
import { ListFilterModel } from "src/models/list-filter/filter";
@@ -14,9 +14,7 @@ interface IProps {
header: string;
}
-export const SceneRecommendationRow: FunctionComponent = (
- props: IProps
-) => {
+export const SceneRecommendationRow: React.FC = (props) => {
const result = useFindScenes(props.filter);
const cardCount = result.data?.findScenes.count;
diff --git a/ui/v2.5/src/components/Scenes/styles.scss b/ui/v2.5/src/components/Scenes/styles.scss
index 20d073024..53b1a205b 100644
--- a/ui/v2.5/src/components/Scenes/styles.scss
+++ b/ui/v2.5/src/components/Scenes/styles.scss
@@ -463,7 +463,7 @@ input[type="range"].blue-slider {
}
@media (min-width: 1200px), (max-width: 575px) {
- .performer-card .flag-icon {
+ .performer-card .fi {
height: 1.33rem;
width: 2rem;
}
diff --git a/ui/v2.5/src/components/Settings/Inputs.tsx b/ui/v2.5/src/components/Settings/Inputs.tsx
index b14774e9c..f8bc07461 100644
--- a/ui/v2.5/src/components/Settings/Inputs.tsx
+++ b/ui/v2.5/src/components/Settings/Inputs.tsx
@@ -1,8 +1,7 @@
import { faChevronDown, faChevronUp } from "@fortawesome/free-solid-svg-icons";
-import React, { useState } from "react";
+import React, { PropsWithChildren, useState } from "react";
import { Button, Collapse, Form, Modal, ModalProps } from "react-bootstrap";
import { FormattedMessage, useIntl } from "react-intl";
-import { PropsWithChildren } from "react-router/node_modules/@types/react";
import { Icon } from "../Shared";
import { StringListInput } from "../Shared/StringListInput";
@@ -154,7 +153,7 @@ export const BooleanSetting: React.FC = (props) => {
};
interface ISelectSetting extends ISetting {
- value?: string | number | string[] | undefined;
+ value?: string | number | string[];
onChange: (v: string) => void;
}
diff --git a/ui/v2.5/src/components/Settings/SettingSection.tsx b/ui/v2.5/src/components/Settings/SettingSection.tsx
index a14ab4c8d..41d365088 100644
--- a/ui/v2.5/src/components/Settings/SettingSection.tsx
+++ b/ui/v2.5/src/components/Settings/SettingSection.tsx
@@ -1,7 +1,6 @@
-import React from "react";
+import React, { PropsWithChildren } from "react";
import { Card } from "react-bootstrap";
import { useIntl } from "react-intl";
-import { PropsWithChildren } from "react-router/node_modules/@types/react";
interface ISettingGroup {
id?: string;
diff --git a/ui/v2.5/src/components/Settings/Settings.tsx b/ui/v2.5/src/components/Settings/Settings.tsx
index 1a408dcc1..5041653ca 100644
--- a/ui/v2.5/src/components/Settings/Settings.tsx
+++ b/ui/v2.5/src/components/Settings/Settings.tsx
@@ -1,5 +1,4 @@
import React from "react";
-import queryString from "query-string";
import { Tab, Nav, Row, Col } from "react-bootstrap";
import { useHistory, useLocation } from "react-router-dom";
import { FormattedMessage, useIntl } from "react-intl";
@@ -23,7 +22,7 @@ export const Settings: React.FC = () => {
const intl = useIntl();
const location = useLocation();
const history = useHistory();
- const defaultTab = queryString.parse(location.search).tab ?? "tasks";
+ const defaultTab = new URLSearchParams(location.search).get("tab") ?? "tasks";
const onSelect = (val: string) => history.push(`?tab=${val}`);
diff --git a/ui/v2.5/src/components/Settings/SettingsLibraryPanel.tsx b/ui/v2.5/src/components/Settings/SettingsLibraryPanel.tsx
index b00e22e57..d4de046c8 100644
--- a/ui/v2.5/src/components/Settings/SettingsLibraryPanel.tsx
+++ b/ui/v2.5/src/components/Settings/SettingsLibraryPanel.tsx
@@ -9,14 +9,8 @@ import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
export const SettingsLibraryPanel: React.FC = () => {
const intl = useIntl();
- const {
- general,
- loading,
- error,
- saveGeneral,
- defaults,
- saveDefaults,
- } = React.useContext(SettingStateContext);
+ const { general, loading, error, saveGeneral, defaults, saveDefaults } =
+ React.useContext(SettingStateContext);
function commaDelimitedToList(value: string | undefined) {
if (value) {
diff --git a/ui/v2.5/src/components/Settings/SettingsScrapingPanel.tsx b/ui/v2.5/src/components/Settings/SettingsScrapingPanel.tsx
index ecd80ae8a..99af505c0 100644
--- a/ui/v2.5/src/components/Settings/SettingsScrapingPanel.tsx
+++ b/ui/v2.5/src/components/Settings/SettingsScrapingPanel.tsx
@@ -75,31 +75,17 @@ const URLList: React.FC = ({ urls }) => {
export const SettingsScrapingPanel: React.FC = () => {
const Toast = useToast();
const intl = useIntl();
- const {
- data: performerScrapers,
- loading: loadingPerformers,
- } = useListPerformerScrapers();
- const {
- data: sceneScrapers,
- loading: loadingScenes,
- } = useListSceneScrapers();
- const {
- data: galleryScrapers,
- loading: loadingGalleries,
- } = useListGalleryScrapers();
- const {
- data: movieScrapers,
- loading: loadingMovies,
- } = useListMovieScrapers();
+ const { data: performerScrapers, loading: loadingPerformers } =
+ useListPerformerScrapers();
+ const { data: sceneScrapers, loading: loadingScenes } =
+ useListSceneScrapers();
+ const { data: galleryScrapers, loading: loadingGalleries } =
+ useListGalleryScrapers();
+ const { data: movieScrapers, loading: loadingMovies } =
+ useListMovieScrapers();
- const {
- general,
- scraping,
- loading,
- error,
- saveGeneral,
- saveScraping,
- } = React.useContext(SettingStateContext);
+ const { general, scraping, loading, error, saveGeneral, saveScraping } =
+ React.useContext(SettingStateContext);
async function onReloadScrapers() {
await mutateReloadScrapers().catch((e) => Toast.error(e));
diff --git a/ui/v2.5/src/components/Settings/SettingsSecurityPanel.tsx b/ui/v2.5/src/components/Settings/SettingsSecurityPanel.tsx
index 02d1fae1d..b0fd269e6 100644
--- a/ui/v2.5/src/components/Settings/SettingsSecurityPanel.tsx
+++ b/ui/v2.5/src/components/Settings/SettingsSecurityPanel.tsx
@@ -71,14 +71,8 @@ export const SettingsSecurityPanel: React.FC = () => {
const intl = useIntl();
const Toast = useToast();
- const {
- general,
- apiKey,
- loading,
- error,
- saveGeneral,
- refetch,
- } = React.useContext(SettingStateContext);
+ const { general, apiKey, loading, error, saveGeneral, refetch } =
+ React.useContext(SettingStateContext);
const [generateAPIKey] = useGenerateAPIKey();
diff --git a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx
index 51d30b1dc..248a6e3ac 100644
--- a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx
+++ b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx
@@ -23,9 +23,12 @@ export const SettingsServicesPanel: React.FC = () => {
const intl = useIntl();
const Toast = useToast();
- const { dlna, loading: configLoading, error, saveDLNA } = React.useContext(
- SettingStateContext
- );
+ const {
+ dlna,
+ loading: configLoading,
+ error,
+ saveDLNA,
+ } = React.useContext(SettingStateContext);
// undefined to hide dialog, true for enable, false for disable
const [enableDisable, setEnableDisable] = useState(
diff --git a/ui/v2.5/src/components/Settings/SettingsSystemPanel.tsx b/ui/v2.5/src/components/Settings/SettingsSystemPanel.tsx
index 32c164c5b..43108a056 100644
--- a/ui/v2.5/src/components/Settings/SettingsSystemPanel.tsx
+++ b/ui/v2.5/src/components/Settings/SettingsSystemPanel.tsx
@@ -17,9 +17,8 @@ import {
} from "./GeneratePreviewOptions";
export const SettingsConfigurationPanel: React.FC = () => {
- const { general, loading, error, saveGeneral } = React.useContext(
- SettingStateContext
- );
+ const { general, loading, error, saveGeneral } =
+ React.useContext(SettingStateContext);
const transcodeQualities = [
GQL.StreamingResolutionEnum.Low,
diff --git a/ui/v2.5/src/components/Settings/Tasks/DirectorySelectionDialog.tsx b/ui/v2.5/src/components/Settings/Tasks/DirectorySelectionDialog.tsx
index 84b1e0d5a..5868037f5 100644
--- a/ui/v2.5/src/components/Settings/Tasks/DirectorySelectionDialog.tsx
+++ b/ui/v2.5/src/components/Settings/Tasks/DirectorySelectionDialog.tsx
@@ -17,12 +17,9 @@ interface IDirectorySelectionDialogProps {
onClose: (paths?: string[]) => void;
}
-export const DirectorySelectionDialog: React.FC = ({
- animation,
- allowEmpty = false,
- initialPaths = [],
- onClose,
-}) => {
+export const DirectorySelectionDialog: React.FC<
+ IDirectorySelectionDialogProps
+> = ({ animation, allowEmpty = false, initialPaths = [], onClose }) => {
const intl = useIntl();
const { configuration } = React.useContext(ConfigurationContext);
diff --git a/ui/v2.5/src/components/Settings/Tasks/LibraryTasks.tsx b/ui/v2.5/src/components/Settings/Tasks/LibraryTasks.tsx
index 82c4c3b17..d3d5bd3a6 100644
--- a/ui/v2.5/src/components/Settings/Tasks/LibraryTasks.tsx
+++ b/ui/v2.5/src/components/Settings/Tasks/LibraryTasks.tsx
@@ -81,14 +81,12 @@ export const LibraryTasks: React.FC = () => {
});
const [scanOptions, setScanOptions] = useState({});
- const [
- autoTagOptions,
- setAutoTagOptions,
- ] = useState({
- performers: ["*"],
- studios: ["*"],
- tags: ["*"],
- });
+ const [autoTagOptions, setAutoTagOptions] =
+ useState({
+ performers: ["*"],
+ studios: ["*"],
+ tags: ["*"],
+ });
function getDefaultGenerateOptions(): GQL.GenerateMetadataInput {
return {
@@ -104,10 +102,8 @@ export const LibraryTasks: React.FC = () => {
};
}
- const [
- generateOptions,
- setGenerateOptions,
- ] = useState(getDefaultGenerateOptions());
+ const [generateOptions, setGenerateOptions] =
+ useState(getDefaultGenerateOptions());
type DialogOpenState = typeof dialogOpen;
diff --git a/ui/v2.5/src/components/Setup/Setup.tsx b/ui/v2.5/src/components/Setup/Setup.tsx
index 16a10fcd5..5e8b542d0 100644
--- a/ui/v2.5/src/components/Setup/Setup.tsx
+++ b/ui/v2.5/src/components/Setup/Setup.tsx
@@ -22,9 +22,8 @@ import {
} from "@fortawesome/free-solid-svg-icons";
export const Setup: React.FC = () => {
- const { configuration, loading: configLoading } = useContext(
- ConfigurationContext
- );
+ const { configuration, loading: configLoading } =
+ useContext(ConfigurationContext);
const [step, setStep] = useState(0);
const [configLocation, setConfigLocation] = useState("");
diff --git a/ui/v2.5/src/components/Shared/CountryFlag.tsx b/ui/v2.5/src/components/Shared/CountryFlag.tsx
index 3d73c280e..4ee5fb1a3 100644
--- a/ui/v2.5/src/components/Shared/CountryFlag.tsx
+++ b/ui/v2.5/src/components/Shared/CountryFlag.tsx
@@ -19,9 +19,7 @@ const CountryFlag: React.FC = ({
return (
);
diff --git a/ui/v2.5/src/components/Shared/CountrySelect.tsx b/ui/v2.5/src/components/Shared/CountrySelect.tsx
index e354279fd..0062b8ffd 100644
--- a/ui/v2.5/src/components/Shared/CountrySelect.tsx
+++ b/ui/v2.5/src/components/Shared/CountrySelect.tsx
@@ -5,7 +5,7 @@ import { getCountries } from "src/utils";
import CountryLabel from "./CountryLabel";
interface IProps {
- value?: string | undefined;
+ value?: string;
onChange?: (value: string) => void;
disabled?: boolean;
className?: string;
diff --git a/ui/v2.5/src/components/Shared/FolderSelect/FolderSelect.tsx b/ui/v2.5/src/components/Shared/FolderSelect/FolderSelect.tsx
index f6c6de544..fc167ecc2 100644
--- a/ui/v2.5/src/components/Shared/FolderSelect/FolderSelect.tsx
+++ b/ui/v2.5/src/components/Shared/FolderSelect/FolderSelect.tsx
@@ -20,9 +20,8 @@ export const FolderSelect: React.FC = ({
defaultDirectories,
appendButton,
}) => {
- const [debouncedDirectory, setDebouncedDirectory] = useState(
- currentDirectory
- );
+ const [debouncedDirectory, setDebouncedDirectory] =
+ useState(currentDirectory);
const { data, error, loading } = useDirectory(debouncedDirectory);
const intl = useIntl();
diff --git a/ui/v2.5/src/components/Shared/MarkdownPage.tsx b/ui/v2.5/src/components/Shared/MarkdownPage.tsx
index b9fc93eba..b179092ae 100644
--- a/ui/v2.5/src/components/Shared/MarkdownPage.tsx
+++ b/ui/v2.5/src/components/Shared/MarkdownPage.tsx
@@ -1,11 +1,10 @@
import React, { useEffect, useState } from "react";
import ReactMarkdown from "react-markdown";
-import gfm from "remark-gfm";
+import remarkGfm from "remark-gfm";
interface IPageProps {
// page is a markdown module
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- page: any;
+ page: string;
}
export const MarkdownPage: React.FC = ({ page }) => {
@@ -20,7 +19,7 @@ export const MarkdownPage: React.FC = ({ page }) => {
}, [page, markdown]);
return (
-
+
{markdown}
);
diff --git a/ui/v2.5/src/components/Shared/MultiSet.tsx b/ui/v2.5/src/components/Shared/MultiSet.tsx
index 6f7aa08e4..3ffa83367 100644
--- a/ui/v2.5/src/components/Shared/MultiSet.tsx
+++ b/ui/v2.5/src/components/Shared/MultiSet.tsx
@@ -1,4 +1,4 @@
-import * as React from "react";
+import React from "react";
import { useIntl } from "react-intl";
import * as GQL from "src/core/generated-graphql";
@@ -21,9 +21,7 @@ interface IMultiSetProps {
onSetMode: (mode: GQL.BulkUpdateIdMode) => void;
}
-const MultiSet: React.FunctionComponent = (
- props: IMultiSetProps
-) => {
+const MultiSet: React.FC = (props) => {
const intl = useIntl();
const modes = [
GQL.BulkUpdateIdMode.Set,
diff --git a/ui/v2.5/src/components/Shared/Select.tsx b/ui/v2.5/src/components/Shared/Select.tsx
index 3ff947974..9149d7d8a 100644
--- a/ui/v2.5/src/components/Shared/Select.tsx
+++ b/ui/v2.5/src/components/Shared/Select.tsx
@@ -1,13 +1,13 @@
import React, { useEffect, useMemo, useState } from "react";
import Select, {
- ValueType,
- Styles,
+ OnChangeValue,
+ StylesConfig,
OptionProps,
components as reactSelectComponents,
- GroupedOptionsType,
- OptionsType,
- MenuListComponentProps,
- GroupTypeBase,
+ Options,
+ MenuListProps,
+ GroupBase,
+ OptionsOrGroups,
} from "react-select";
import CreatableSelect from "react-select/creatable";
import debounce from "lodash-es/debounce";
@@ -24,7 +24,7 @@ import {
usePerformerCreate,
} from "src/core/StashService";
import { useToast } from "src/hooks";
-import { SelectComponents } from "react-select/src/components";
+import { SelectComponents } from "react-select/dist/declarations/src/components";
import { ConfigurationContext } from "src/hooks/Config";
import { useIntl } from "react-intl";
import { objectTitle } from "src/core/files";
@@ -65,22 +65,22 @@ interface IFilterProps {
interface ISelectProps {
className?: string;
items: Option[];
- selectedOptions?: ValueType;
+ selectedOptions?: OnChangeValue ;
creatable?: boolean;
onCreateOption?: (value: string) => void;
isLoading: boolean;
isDisabled?: boolean;
- onChange: (item: ValueType ) => void;
+ onChange: (item: OnChangeValue ) => void;
initialIds?: string[];
isMulti: T;
isClearable?: boolean;
onInputChange?: (input: string) => void;
- components?: Partial>;
+ components?: Partial>>;
filterOption?: (option: Option, rawInput: string) => boolean;
isValidNewOption?: (
inputValue: string,
- value: ValueType,
- options: OptionsType | GroupedOptionsType
+ value: Options ,
+ options: OptionsOrGroups >
) => boolean;
placeholder?: string;
showDropdown?: boolean;
@@ -95,7 +95,16 @@ interface IFilterComponentProps extends IFilterProps {
onCreate?: (name: string) => Promise<{ item: ValidTypes; message: string }>;
}
interface IFilterSelectProps
- extends Omit, "onChange" | "items" | "onCreateOption"> {}
+ extends Pick<
+ ISelectProps,
+ | "isLoading"
+ | "isMulti"
+ | "components"
+ | "filterOption"
+ | "isValidNewOption"
+ | "placeholder"
+ | "closeMenuOnSelect"
+ > {}
type TitledObject = { id: string; title: string };
interface ITitledSelect {
@@ -106,18 +115,21 @@ interface ITitledSelect {
disabled?: boolean;
}
-const getSelectedItems = (selectedItems: ValueType) =>
- selectedItems
- ? Array.isArray(selectedItems)
- ? selectedItems
- : [selectedItems]
- : [];
+const getSelectedItems = (selectedItems: OnChangeValue ) => {
+ if (Array.isArray(selectedItems)) {
+ return selectedItems;
+ } else if (selectedItems) {
+ return [selectedItems];
+ } else {
+ return [];
+ }
+};
-const getSelectedValues = (selectedItems: ValueType ) =>
+const getSelectedValues = (selectedItems: OnChangeValue ) =>
getSelectedItems(selectedItems).map((item) => item.value);
const LimitedSelectMenu = (
- props: MenuListComponentProps>
+ props: MenuListProps >
) => {
const maxOptionsShown = 200;
const [hiddenCount, setHiddenCount] = useState(0);
@@ -129,13 +141,13 @@ const LimitedSelectMenu = (
if (Array.isArray(props.children)) {
// limit the number of select options showing in the select dropdowns
// always showing the 'Create "..."' option when it exists
- let creationOptionIndex = (props.children as React.ReactNodeArray).findIndex(
+ let creationOptionIndex = (props.children as React.ReactNode[]).findIndex(
(child: React.ReactNode) => {
let maybeCreatableOption = child as React.ReactElement<
OptionProps<
Option & { __isNew__: boolean },
T,
- GroupTypeBase
+ GroupBase
>,
""
>;
@@ -190,7 +202,7 @@ const SelectComponent = ({
noOptionsMessage = type !== "tags" ? "None" : null,
}: ISelectProps & ITypeProps) => {
const values = items.filter((item) => initialIds?.indexOf(item.value) !== -1);
- const defaultValue = (isMulti ? values : values[0] ?? null) as ValueType<
+ const defaultValue = (isMulti ? values : values[0] ?? null) as OnChangeValue<
Option,
T
>;
@@ -204,18 +216,18 @@ const SelectComponent = ({
]
: items;
- const styles: Partial> = {
+ const styles: StylesConfig = {
option: (base) => ({
...base,
color: "#000",
}),
- container: (base, props) => ({
+ container: (base, state) => ({
...base,
- zIndex: props.selectProps.isFocused ? 10 : base.zIndex,
+ zIndex: state.isFocused ? 10 : base.zIndex,
}),
- multiValueRemove: (base, props) => ({
+ multiValueRemove: (base, state) => ({
...base,
- color: props.selectProps.isFocused ? base.color : "#333333",
+ color: state.isFocused ? base.color : "#333333",
}),
};
@@ -279,11 +291,11 @@ const FilterSelectComponent = (
const selected = options.filter((option) =>
selectedIds.includes(option.value)
);
- const selectedOptions = (isMulti
- ? selected
- : selected[0] ?? null) as ValueType;
+ const selectedOptions = (
+ isMulti ? selected : selected[0] ?? null
+ ) as OnChangeValue ;
- const onChange = (selectedItems: ValueType ) => {
+ const onChange = (selectedItems: OnChangeValue ) => {
const selectedValues = getSelectedValues(selectedItems);
onSelect?.(items.filter((item) => selectedValues.includes(item.id)));
};
@@ -342,7 +354,7 @@ export const GallerySelect: React.FC = (props) => {
setQuery(input);
}, 500);
- const onChange = (selectedItems: ValueType) => {
+ const onChange = (selectedItems: OnChangeValue ) => {
const selected = getSelectedItems(selectedItems);
props.onSelect(
selected.map((s) => ({
@@ -369,6 +381,7 @@ export const GallerySelect: React.FC = (props) => {
placeholder="Search for gallery..."
noOptionsMessage={query === "" ? null : "No galleries found."}
showDropdown={false}
+ isDisabled={props.disabled}
/>
);
};
@@ -394,7 +407,7 @@ export const SceneSelect: React.FC = (props) => {
setQuery(input);
}, 500);
- const onChange = (selectedItems: ValueType) => {
+ const onChange = (selectedItems: OnChangeValue ) => {
const selected = getSelectedItems(selectedItems);
props.onSelect(
(selected ?? []).map((s) => ({
@@ -446,7 +459,7 @@ export const ImageSelect: React.FC = (props) => {
setQuery(input);
}, 500);
- const onChange = (selectedItems: ValueType) => {
+ const onChange = (selectedItems: OnChangeValue ) => {
const selected = getSelectedItems(selectedItems);
props.onSelect(
(selected ?? []).map((s) => ({
@@ -485,7 +498,7 @@ export const MarkerTitleSuggest: React.FC = (props) => {
const { data, loading } = useMarkerStrings();
const suggestions = data?.markerStrings ?? [];
- const onChange = (selectedItem: ValueType) =>
+ const onChange = (selectedItem: OnChangeValue ) =>
props.onChange(selectedItem?.value ?? "");
const items = suggestions.map((item) => ({
@@ -535,9 +548,10 @@ export const PerformerSelect: React.FC = (props) => {
const defaultCreatable =
!configuration?.interface.disableDropdownCreate.performer ?? true;
- const performers = useMemo(() => data?.allPerformers ?? [], [
- data?.allPerformers,
- ]);
+ const performers = useMemo(
+ () => data?.allPerformers ?? [],
+ [data?.allPerformers]
+ );
useEffect(() => {
// build the tag aliases map
@@ -609,15 +623,15 @@ export const PerformerSelect: React.FC = (props) => {
const isValidNewOption = (
inputValue: string,
- value: ValueType,
- options: OptionsType | GroupedOptionsType
+ value: Options ,
+ options: OptionsOrGroups >
) => {
if (!inputValue) {
return false;
}
if (
- (options as OptionsType ).some((o: Option) => {
+ (options as Options ).some((o: Option) => {
return o.label.toLowerCase() === inputValue.toLowerCase();
})
) {
@@ -752,15 +766,15 @@ export const StudioSelect: React.FC<
const isValidNewOption = (
inputValue: string,
- value: ValueType ,
- options: OptionsType | GroupedOptionsType
+ value: OnChangeValue ,
+ options: OptionsOrGroups >
) => {
if (!inputValue) {
return false;
}
if (
- (options as OptionsType ).some((o: Option) => {
+ (options as Options ).some((o: Option) => {
return o.label.toLowerCase() === inputValue.toLowerCase();
})
) {
@@ -873,7 +887,9 @@ export const TagSelect: React.FC = (
};
}
- const id = optionProps.data.__isNew__ ? "" : optionProps.data.value;
+ const id = (optionProps.data as Option & { __isNew__: boolean }).__isNew__
+ ? ""
+ : optionProps.data.value;
return (
@@ -917,15 +933,15 @@ export const TagSelect: React.FC = (
const isValidNewOption = (
inputValue: string,
- value: ValueType,
- options: OptionsType | GroupedOptionsType
+ value: OnChangeValue ,
+ options: OptionsOrGroups >
) => {
if (!inputValue) {
return false;
}
if (
- (options as OptionsType ).some((o: Option) => {
+ (options as Options ).some((o: Option) => {
return o.label.toLowerCase() === inputValue.toLowerCase();
})
) {
@@ -957,16 +973,17 @@ export const TagSelect: React.FC = (
);
};
-export const FilterSelect: React.FC = (props) =>
- props.type === "performers" ? (
-
- ) : props.type === "studios" || props.type === "parent_studios" ? (
-
- ) : props.type === "movies" ? (
-
- ) : (
-
- );
+export const FilterSelect: React.FC = (props) => {
+ if (props.type === "performers") {
+ return ;
+ } else if (props.type === "studios" || props.type === "parent_studios") {
+ return ;
+ } else if (props.type === "movies") {
+ return ;
+ } else {
+ return ;
+ }
+};
interface IStringListSelect {
options?: string[];
@@ -988,18 +1005,18 @@ export const StringListSelect: React.FC = ({
});
}, [value]);
- const styles: Partial> = {
+ const styles: StylesConfig = {
option: (base) => ({
...base,
color: "#000",
}),
- container: (base, props) => ({
+ container: (base, state) => ({
...base,
- zIndex: props.selectProps.isFocused ? 10 : base.zIndex,
+ zIndex: state.isFocused ? 10 : base.zIndex,
}),
- multiValueRemove: (base, props) => ({
+ multiValueRemove: (base, state) => ({
...base,
- color: props.selectProps.isFocused ? base.color : "#333333",
+ color: state.isFocused ? base.color : "#333333",
}),
};
@@ -1038,18 +1055,18 @@ export const ListSelect = (props: IListSelect) => {
return value.map(toOptionType);
}, [value, toOptionType]);
- const styles: Partial> = {
+ const styles: StylesConfig = {
option: (base) => ({
...base,
color: "#000",
}),
- container: (base, p) => ({
+ container: (base, state) => ({
...base,
- zIndex: p.selectProps.isFocused ? 10 : base.zIndex,
+ zIndex: state.isFocused ? 10 : base.zIndex,
}),
- multiValueRemove: (base, p) => ({
+ multiValueRemove: (base, state) => ({
...base,
- color: p.selectProps.isFocused ? base.color : "#333333",
+ color: state.isFocused ? base.color : "#333333",
}),
};
diff --git a/ui/v2.5/src/components/Shared/SweatDrops.tsx b/ui/v2.5/src/components/Shared/SweatDrops.tsx
index 180e91cc6..594d1acf1 100644
--- a/ui/v2.5/src/components/Shared/SweatDrops.tsx
+++ b/ui/v2.5/src/components/Shared/SweatDrops.tsx
@@ -1,6 +1,6 @@
import React from "react";
-export const SweatDrops = () => (
+export const SweatDrops: React.FC = () => (
= ({ studio }) => {
// 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/Studios/StudioDetails/StudioCreate.tsx b/ui/v2.5/src/components/Studios/StudioDetails/StudioCreate.tsx
index a590ca770..1e69aea93 100644
--- a/ui/v2.5/src/components/Studios/StudioDetails/StudioCreate.tsx
+++ b/ui/v2.5/src/components/Studios/StudioDetails/StudioCreate.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from "react";
+import React, { useMemo, useState } from "react";
import { useHistory, useLocation } from "react-router-dom";
import { useIntl } from "react-intl";
@@ -11,15 +11,13 @@ import { StudioEditPanel } from "./StudioEditPanel";
const StudioCreate: React.FC = () => {
const history = useHistory();
+ const location = useLocation();
const Toast = useToast();
- function useQuery() {
- const { search } = useLocation();
- return React.useMemo(() => new URLSearchParams(search), [search]);
- }
-
- const query = useQuery();
- const nameQuery = query.get("name");
+ const query = useMemo(() => new URLSearchParams(location.search), [location]);
+ const studio = {
+ name: query.get("q") ?? undefined,
+ };
const intl = useIntl();
@@ -74,7 +72,7 @@ const StudioCreate: React.FC = () => {
)}
history.push("/studios")}
diff --git a/ui/v2.5/src/components/Studios/StudioDetails/StudioEditPanel.tsx b/ui/v2.5/src/components/Studios/StudioDetails/StudioEditPanel.tsx
index 0eb0629fa..4e19a4c26 100644
--- a/ui/v2.5/src/components/Studios/StudioDetails/StudioEditPanel.tsx
+++ b/ui/v2.5/src/components/Studios/StudioDetails/StudioEditPanel.tsx
@@ -35,10 +35,9 @@ export const StudioEditPanel: React.FC = ({
}) => {
const intl = useIntl();
+ const isNew = studio.id === undefined;
const { configuration } = React.useContext(ConfigurationContext);
- const isNew = !studio || !studio.id;
-
const imageEncoding = ImageUtils.usePasteImage(onImageLoad, true);
const schema = yup.object({
@@ -130,10 +129,10 @@ export const StudioEditPanel: React.FC = ({
return () => onImageChange?.();
}, [formik.values.image, onImageChange]);
- useEffect(() => onImageEncoding?.(imageEncoding), [
- onImageEncoding,
- imageEncoding,
- ]);
+ useEffect(
+ () => onImageEncoding?.(imageEncoding),
+ [onImageEncoding, imageEncoding]
+ );
function onImageChangeHandler(event: React.FormEvent) {
ImageUtils.onImageChange(event, onImageLoad);
diff --git a/ui/v2.5/src/components/Studios/StudioRecommendationRow.tsx b/ui/v2.5/src/components/Studios/StudioRecommendationRow.tsx
index a35337daf..ec639aeb6 100644
--- a/ui/v2.5/src/components/Studios/StudioRecommendationRow.tsx
+++ b/ui/v2.5/src/components/Studios/StudioRecommendationRow.tsx
@@ -1,6 +1,6 @@
-import React, { FunctionComponent } from "react";
+import React from "react";
import { useFindStudios } from "src/core/StashService";
-import Slider from "react-slick";
+import Slider from "@ant-design/react-slick";
import { StudioCard } from "./StudioCard";
import { ListFilterModel } from "src/models/list-filter/filter";
import { getSlickSliderSettings } from "src/core/recommendations";
@@ -13,9 +13,7 @@ interface IProps {
header: string;
}
-export const StudioRecommendationRow: FunctionComponent = (
- props: IProps
-) => {
+export const StudioRecommendationRow: React.FC = (props) => {
const result = useFindStudios(props.filter);
const cardCount = result.data?.findStudios.count;
diff --git a/ui/v2.5/src/components/Tagger/context.tsx b/ui/v2.5/src/components/Tagger/context.tsx
index 9d1a19ca7..dc9b3aa7b 100644
--- a/ui/v2.5/src/components/Tagger/context.tsx
+++ b/ui/v2.5/src/components/Tagger/context.tsx
@@ -201,9 +201,8 @@ export const TaggerContext: React.FC = ({ children }) => {
});
}
- const [
- submitFingerprintsMutation,
- ] = GQL.useSubmitStashBoxFingerprintsMutation();
+ const [submitFingerprintsMutation] =
+ GQL.useSubmitStashBoxFingerprintsMutation();
async function submitFingerprints() {
const endpoint = currentSource?.stashboxEndpoint;
diff --git a/ui/v2.5/src/components/Tagger/scenes/PerformerResult.tsx b/ui/v2.5/src/components/Tagger/scenes/PerformerResult.tsx
index 12bbef5d9..94065fb05 100755
--- a/ui/v2.5/src/components/Tagger/scenes/PerformerResult.tsx
+++ b/ui/v2.5/src/components/Tagger/scenes/PerformerResult.tsx
@@ -30,13 +30,11 @@ const PerformerResult: React.FC = ({
onLink,
endpoint,
}) => {
- const {
- data: performerData,
- loading: stashLoading,
- } = GQL.useFindPerformerQuery({
- variables: { id: performer.stored_id ?? "" },
- skip: !performer.stored_id,
- });
+ const { data: performerData, loading: stashLoading } =
+ GQL.useFindPerformerQuery({
+ variables: { id: performer.stored_id ?? "" },
+ skip: !performer.stored_id,
+ });
const matchedPerformer = performerData?.findPerformer;
const matchedStashID = matchedPerformer?.stash_ids.some(
diff --git a/ui/v2.5/src/components/Tagger/scenes/SceneTagger.tsx b/ui/v2.5/src/components/Tagger/scenes/SceneTagger.tsx
index 69270317d..1c853a2d5 100755
--- a/ui/v2.5/src/components/Tagger/scenes/SceneTagger.tsx
+++ b/ui/v2.5/src/components/Tagger/scenes/SceneTagger.tsx
@@ -181,14 +181,10 @@ export const Tagger: React.FC = ({ scenes, queue }) => {
return -1;
}
- const [
- nbPhashMatchSceneA,
- ratioPhashMatchSceneA,
- ] = calculatePhashComparisonScore(stashScene, sceneA);
- const [
- nbPhashMatchSceneB,
- ratioPhashMatchSceneB,
- ] = calculatePhashComparisonScore(stashScene, sceneB);
+ const [nbPhashMatchSceneA, ratioPhashMatchSceneA] =
+ calculatePhashComparisonScore(stashScene, sceneA);
+ const [nbPhashMatchSceneB, ratioPhashMatchSceneB] =
+ calculatePhashComparisonScore(stashScene, sceneB);
if (nbPhashMatchSceneA != nbPhashMatchSceneB) {
return nbPhashMatchSceneB - nbPhashMatchSceneA;
diff --git a/ui/v2.5/src/components/Tagger/scenes/sceneTaggerModals.tsx b/ui/v2.5/src/components/Tagger/scenes/sceneTaggerModals.tsx
index fe670e32b..12161aaa3 100644
--- a/ui/v2.5/src/components/Tagger/scenes/sceneTaggerModals.tsx
+++ b/ui/v2.5/src/components/Tagger/scenes/sceneTaggerModals.tsx
@@ -21,12 +21,11 @@ export interface ISceneTaggerModalsContextState {
) => void;
}
-export const SceneTaggerModalsState = React.createContext(
- {
+export const SceneTaggerModalsState =
+ React.createContext({
createPerformerModal: () => {},
createStudioModal: () => {},
- }
-);
+ });
export const SceneTaggerModals: React.FC = ({ children }) => {
const { currentSource } = useContext(TaggerStateContext);
diff --git a/ui/v2.5/src/components/Tags/TagDetails/Tag.tsx b/ui/v2.5/src/components/Tags/TagDetails/Tag.tsx
index 57665b18d..7cb4685c2 100644
--- a/ui/v2.5/src/components/Tags/TagDetails/Tag.tsx
+++ b/ui/v2.5/src/components/Tags/TagDetails/Tag.tsx
@@ -87,7 +87,9 @@ const TagPage: React.FC = ({ tag }) => {
// set up hotkeys
useEffect(() => {
Mousetrap.bind("e", () => setIsEditing(true));
- Mousetrap.bind("d d", () => onDelete());
+ Mousetrap.bind("d d", () => {
+ onDelete();
+ });
return () => {
if (isEditing) {
diff --git a/ui/v2.5/src/components/Tags/TagDetails/TagCreate.tsx b/ui/v2.5/src/components/Tags/TagDetails/TagCreate.tsx
index f599161b3..b11a45352 100644
--- a/ui/v2.5/src/components/Tags/TagDetails/TagCreate.tsx
+++ b/ui/v2.5/src/components/Tags/TagDetails/TagCreate.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from "react";
+import React, { useMemo, useState } from "react";
import { useHistory, useLocation } from "react-router-dom";
import * as GQL from "src/core/generated-graphql";
@@ -11,15 +11,13 @@ import { TagEditPanel } from "./TagEditPanel";
const TagCreate: React.FC = () => {
const history = useHistory();
+ const location = useLocation();
const Toast = useToast();
- function useQuery() {
- const { search } = useLocation();
- return React.useMemo(() => new URLSearchParams(search), [search]);
- }
-
- const query = useQuery();
- const nameQuery = query.get("name");
+ const query = useMemo(() => new URLSearchParams(location.search), [location]);
+ const tag = {
+ name: query.get("q") ?? undefined,
+ };
// Editing tag state
const [image, setImage] = useState();
@@ -86,7 +84,7 @@ const TagCreate: React.FC = () => {
)}
history.push("/tags")}
onDelete={() => {}}
diff --git a/ui/v2.5/src/components/Tags/TagDetails/TagEditPanel.tsx b/ui/v2.5/src/components/Tags/TagDetails/TagEditPanel.tsx
index 4e153359e..7491b56ce 100644
--- a/ui/v2.5/src/components/Tags/TagDetails/TagEditPanel.tsx
+++ b/ui/v2.5/src/components/Tags/TagDetails/TagEditPanel.tsx
@@ -6,12 +6,12 @@ import { DetailsEditNavbar, TagSelect } from "src/components/Shared";
import { Form, Col, Row } from "react-bootstrap";
import { FormUtils, ImageUtils } from "src/utils";
import { useFormik } from "formik";
-import { Prompt, useParams } from "react-router-dom";
+import { Prompt } from "react-router-dom";
import Mousetrap from "mousetrap";
import { StringListInput } from "src/components/Shared/StringListInput";
interface ITagEditPanel {
- tag?: Partial;
+ tag: Partial;
// returns id
onSubmit: (tag: Partial) => void;
onCancel: () => void;
@@ -19,10 +19,6 @@ interface ITagEditPanel {
setImage: (image?: string | null) => void;
}
-interface ITagEditPanelParams {
- id?: string;
-}
-
export const TagEditPanel: React.FC = ({
tag,
onSubmit,
@@ -32,10 +28,7 @@ export const TagEditPanel: React.FC = ({
}) => {
const intl = useIntl();
- const params = useParams();
- const idParam = params.id;
-
- const isNew = idParam === undefined;
+ const isNew = tag.id === undefined;
const labelXS = 3;
const labelXL = 3;
diff --git a/ui/v2.5/src/components/Tags/TagList.tsx b/ui/v2.5/src/components/Tags/TagList.tsx
index e6201064f..a2569a660 100644
--- a/ui/v2.5/src/components/Tags/TagList.tsx
+++ b/ui/v2.5/src/components/Tags/TagList.tsx
@@ -33,10 +33,8 @@ interface ITagList {
export const TagList: React.FC = ({ filterHook }) => {
const Toast = useToast();
- const [
- deletingTag,
- setDeletingTag,
- ] = useState | null>(null);
+ const [deletingTag, setDeletingTag] =
+ useState | null>(null);
const [deleteTag] = useTagDestroy(getDeleteTagInput() as GQL.TagDestroyInput);
diff --git a/ui/v2.5/src/components/Tags/TagRecommendationRow.tsx b/ui/v2.5/src/components/Tags/TagRecommendationRow.tsx
index c684bc2c4..4e50b4989 100644
--- a/ui/v2.5/src/components/Tags/TagRecommendationRow.tsx
+++ b/ui/v2.5/src/components/Tags/TagRecommendationRow.tsx
@@ -1,6 +1,6 @@
-import React, { FunctionComponent } from "react";
+import React from "react";
import { useFindTags } from "src/core/StashService";
-import Slider from "react-slick";
+import Slider from "@ant-design/react-slick";
import { TagCard } from "./TagCard";
import { ListFilterModel } from "src/models/list-filter/filter";
import { getSlickSliderSettings } from "src/core/recommendations";
@@ -13,9 +13,7 @@ interface IProps {
header: string;
}
-export const TagRecommendationRow: FunctionComponent = (
- props: IProps
-) => {
+export const TagRecommendationRow: React.FC = (props) => {
const result = useFindTags(props.filter);
const cardCount = result.data?.findTags.count;
diff --git a/ui/v2.5/src/components/Wall/WallPanel.tsx b/ui/v2.5/src/components/Wall/WallPanel.tsx
index a50ce1414..2d7d5c932 100644
--- a/ui/v2.5/src/components/Wall/WallPanel.tsx
+++ b/ui/v2.5/src/components/Wall/WallPanel.tsx
@@ -53,16 +53,16 @@ export const WallPanel: React.FC = (
/>
));
- const sceneMarkers = (
- props.sceneMarkers ?? []
- ).map((marker, index, markerArray) => (
-
- ));
+ const sceneMarkers = (props.sceneMarkers ?? []).map(
+ (marker, index, markerArray) => (
+
+ )
+ );
const images = (props.images ?? []).map((image, index, imageArray) => (
,
updatedOCount?: number
) => {
- const scene = cache.readQuery<
- GQL.FindSceneQuery,
- GQL.FindSceneQueryVariables
- >({
- query: GQL.FindSceneDocument,
- variables: { id },
- });
- if (updatedOCount === undefined || !scene?.findScene) return;
+ if (updatedOCount === undefined) return;
- cache.writeQuery({
- query: GQL.FindSceneDocument,
- variables: { id },
- data: {
- ...scene,
- findScene: {
- ...scene.findScene,
- o_counter: updatedOCount,
+ cache.modify({
+ id: cache.identify({ __typename: "Scene", id }),
+ fields: {
+ o_counter() {
+ return updatedOCount;
},
},
});
diff --git a/ui/v2.5/src/core/createClient.ts b/ui/v2.5/src/core/createClient.ts
index f86de7990..c76e61097 100644
--- a/ui/v2.5/src/core/createClient.ts
+++ b/ui/v2.5/src/core/createClient.ts
@@ -6,7 +6,8 @@ import {
ServerError,
TypePolicies,
} from "@apollo/client";
-import { WebSocketLink } from "@apollo/client/link/ws";
+import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
+import { createClient as createWSClient } from "graphql-ws";
import { onError } from "@apollo/client/link/error";
import { getMainDefinition } from "@apollo/client/utilities";
import { createUploadLink } from "apollo-upload-client";
@@ -105,7 +106,11 @@ export const getPlatformURL = (ws?: boolean) => {
}
if (ws) {
- platformUrl.protocol = "ws:";
+ if (platformUrl.protocol === "https:") {
+ platformUrl.protocol = "wss:";
+ } else {
+ platformUrl.protocol = "ws:";
+ }
}
return platformUrl;
@@ -115,23 +120,20 @@ export const createClient = () => {
const platformUrl = getPlatformURL();
const wsPlatformUrl = getPlatformURL(true);
- if (platformUrl.protocol === "https:") {
- wsPlatformUrl.protocol = "wss:";
- }
-
const url = `${platformUrl.toString()}graphql`;
const wsUrl = `${wsPlatformUrl.toString()}graphql`;
- const httpLink = createUploadLink({
- uri: url,
- });
+ const httpLink = createUploadLink({ uri: url });
- const wsLink = new WebSocketLink({
- uri: wsUrl,
- options: {
- reconnect: true,
- },
- });
+ const wsLink = new GraphQLWsLink(
+ createWSClient({
+ url: wsUrl,
+ retryAttempts: Infinity,
+ shouldRetry() {
+ return true;
+ },
+ })
+ );
const errorLink = onError(({ networkError }) => {
// handle unauthorized error by redirecting to the login page
@@ -155,7 +157,6 @@ export const createClient = () => {
);
},
wsLink,
- // @ts-ignore
httpLink
);
diff --git a/ui/v2.5/src/docs/en/MigrationNotes/index.ts b/ui/v2.5/src/docs/en/MigrationNotes/index.ts
index 407c49eeb..6611333cf 100644
--- a/ui/v2.5/src/docs/en/MigrationNotes/index.ts
+++ b/ui/v2.5/src/docs/en/MigrationNotes/index.ts
@@ -1,9 +1,7 @@
import migration32 from "./32.md";
import migration39 from "./39.md";
-type Module = typeof migration32;
-
-export const migrationNotes: Record = {
+export const migrationNotes: Record = {
32: migration32,
39: migration39,
};
diff --git a/ui/v2.5/src/docs/en/ReleaseNotes/index.ts b/ui/v2.5/src/docs/en/ReleaseNotes/index.ts
index 8a1e5000e..87a22e709 100644
--- a/ui/v2.5/src/docs/en/ReleaseNotes/index.ts
+++ b/ui/v2.5/src/docs/en/ReleaseNotes/index.ts
@@ -1,11 +1,9 @@
import v0170 from "./v0170.md";
-export type Module = typeof v0170;
-
interface IReleaseNotes {
// handle should be in the form of YYYYMMDD
date: number;
- content: Module;
+ content: string;
}
export const releaseNotes: IReleaseNotes[] = [
diff --git a/ui/v2.5/src/globals.d.ts b/ui/v2.5/src/globals.d.ts
index 1874dfb59..3bd1b67fe 100644
--- a/ui/v2.5/src/globals.d.ts
+++ b/ui/v2.5/src/globals.d.ts
@@ -1,23 +1,17 @@
// eslint-disable-next-line no-var
declare var STASH_BASE_URL: string;
-declare module "*.md";
-declare module "string.prototype.replaceall";
-declare module "mousetrap-pause";
-declare module "hamming-distance";
-declare module "@formatjs/intl-pluralrules/locale-data/en";
-declare module "@formatjs/intl-numberformat/locale-data/en";
-declare module "@formatjs/intl-numberformat/locale-data/en-GB";
+declare module "intersection-observer";
-/* eslint-disable @typescript-eslint/naming-convention */
-interface ImportMetaEnv extends Readonly> {
+declare module "*.md" {
+ const src: string;
+ export default src;
+}
+
+/* eslint-disable-next-line @typescript-eslint/naming-convention */
+interface ImportMetaEnv {
readonly VITE_APP_GITHASH?: string;
readonly VITE_APP_STASH_VERSION?: string;
readonly VITE_APP_DATE?: string;
readonly VITE_APP_PLATFORM_PORT?: string;
readonly VITE_APP_HTTPS?: string;
}
-
-interface ImportMeta {
- readonly env: ImportMetaEnv;
-}
-/* eslint-enable @typescript-eslint/no-unused-vars */
diff --git a/ui/v2.5/src/hooks/Interval.ts b/ui/v2.5/src/hooks/Interval.ts
index 838ea9764..bfad6a45d 100644
--- a/ui/v2.5/src/hooks/Interval.ts
+++ b/ui/v2.5/src/hooks/Interval.ts
@@ -8,7 +8,7 @@ const useInterval = (
delay: number | null = 5000
): (() => void)[] => {
const savedCallback = useRef<() => void>();
- const savedIntervalId = useRef();
+ const savedIntervalId = useRef();
const [savedDelay, setSavedDelay] = useState(delay);
useEffect(() => {
diff --git a/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx b/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx
index a16a765cc..0e99a3289 100644
--- a/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx
+++ b/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx
@@ -119,10 +119,8 @@ export const LightboxComponent: React.FC = ({
const Toast = useToast();
const intl = useIntl();
const { configuration: config } = React.useContext(ConfigurationContext);
- const [
- interfaceLocalForage,
- setInterfaceLocalForage,
- ] = useInterfaceLocalForage();
+ const [interfaceLocalForage, setInterfaceLocalForage] =
+ useInterfaceLocalForage();
const lightboxSettings = interfaceLocalForage.data?.imageLightbox;
@@ -186,10 +184,8 @@ export const LightboxComponent: React.FC = ({
null
);
- const [
- displayedSlideshowInterval,
- setDisplayedSlideshowInterval,
- ] = useState((slideshowDelay / SECONDS_TO_MS).toString());
+ const [displayedSlideshowInterval, setDisplayedSlideshowInterval] =
+ useState((slideshowDelay / SECONDS_TO_MS).toString());
useEffect(() => {
if (images !== oldImages.current && isSwitchingPage) {
diff --git a/ui/v2.5/src/hooks/ListHook.tsx b/ui/v2.5/src/hooks/ListHook.tsx
index 620950951..74f5c6608 100644
--- a/ui/v2.5/src/hooks/ListHook.tsx
+++ b/ui/v2.5/src/hooks/ListHook.tsx
@@ -1,7 +1,6 @@
import clone from "lodash-es/clone";
import cloneDeep from "lodash-es/cloneDeep";
import isEqual from "lodash-es/isEqual";
-import queryString from "query-string";
import React, {
useCallback,
useRef,
@@ -204,9 +203,8 @@ const useRenderList = <
const [selectedIds, setSelectedIds] = useState>(new Set());
const [lastClickedId, setLastClickedId] = useState();
- const [editingCriterion, setEditingCriterion] = useState<
- Criterion
- >();
+ const [editingCriterion, setEditingCriterion] =
+ useState>();
const [newCriterion, setNewCriterion] = useState(false);
const result = useData(filter);
@@ -617,18 +615,18 @@ const useList = (
if (!prevState.queryConfig) {
prevState.queryConfig = {};
}
+
+ const oldFilter = prevState.queryConfig[persistanceKey]?.filter ?? "";
+ const newFilter = new URLSearchParams(oldFilter);
+ newFilter.set("disp", String(updatedFilter.displayMode));
+
return {
...prevState,
queryConfig: {
...prevState.queryConfig,
[persistanceKey]: {
...prevState.queryConfig[persistanceKey],
- filter: queryString.stringify({
- ...queryString.parse(
- prevState.queryConfig[persistanceKey]?.filter ?? ""
- ),
- disp: updatedFilter.displayMode,
- }),
+ filter: newFilter.toString(),
},
},
};
@@ -637,10 +635,8 @@ const useList = (
[persistanceKey, setInterfaceState]
);
- const {
- data: defaultFilter,
- loading: defaultFilterLoading,
- } = useFindDefaultFilter(options.filterMode);
+ const { data: defaultFilter, loading: defaultFilterLoading } =
+ useFindDefaultFilter(options.filterMode);
const updateQueryParams = useCallback(
(newFilter: ListFilterModel) => {
@@ -692,10 +688,9 @@ const useList = (
const storedQuery = interfaceState.data?.queryConfig?.[persistanceKey];
if (options.persistState === PersistanceLevel.VIEW && storedQuery) {
- const storedFilter = queryString.parse(storedQuery.filter);
- if (storedFilter.disp !== undefined) {
- const displayMode = Number.parseInt(storedFilter.disp as string, 10);
- newFilter.displayMode = displayMode;
+ const displayMode = new URLSearchParams(storedQuery.filter).get("disp");
+ if (displayMode) {
+ newFilter.displayMode = Number.parseInt(displayMode, 10);
}
}
}
diff --git a/ui/v2.5/src/hooks/LocalForage.ts b/ui/v2.5/src/hooks/LocalForage.ts
index 3632198f9..cf3d43ea0 100644
--- a/ui/v2.5/src/hooks/LocalForage.ts
+++ b/ui/v2.5/src/hooks/LocalForage.ts
@@ -29,7 +29,7 @@ interface ILocalForage {
const Loading: Record = {};
const Cache: Record = {};
-export function useLocalForage(
+export function useLocalForage(
key: string,
defaultValue: T = {} as T
): [ILocalForage, Dispatch