mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 21:04:37 +03:00
Various bugfixes for scene tagger (#1014)
* Tagger fixes * Validate stash-box endpoint pattern
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
@@ -427,11 +428,18 @@ func ValidateCredentials(username string, password string) bool {
|
|||||||
func ValidateStashBoxes(boxes []*models.StashBoxInput) error {
|
func ValidateStashBoxes(boxes []*models.StashBoxInput) error {
|
||||||
isMulti := len(boxes) > 1
|
isMulti := len(boxes) > 1
|
||||||
|
|
||||||
|
re, err := regexp.Compile("^http.*graphql$")
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Failure to generate regular expression")
|
||||||
|
}
|
||||||
|
|
||||||
for _, box := range boxes {
|
for _, box := range boxes {
|
||||||
if box.APIKey == "" {
|
if box.APIKey == "" {
|
||||||
return errors.New("Stash-box API Key cannot be blank")
|
return errors.New("Stash-box API Key cannot be blank")
|
||||||
} else if box.Endpoint == "" {
|
} else if box.Endpoint == "" {
|
||||||
return errors.New("Stash-box Endpoint cannot be blank")
|
return errors.New("Stash-box Endpoint cannot be blank")
|
||||||
|
} else if !re.Match([]byte(box.Endpoint)) {
|
||||||
|
return errors.New("Stash-box Endpoint is invalid")
|
||||||
} else if isMulti && box.Name == "" {
|
} else if isMulti && box.Name == "" {
|
||||||
return errors.New("Stash-box Name cannot be blank")
|
return errors.New("Stash-box Name cannot be blank")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,3 +20,4 @@
|
|||||||
### 🐛 Bug fixes
|
### 🐛 Bug fixes
|
||||||
* Corrected file sizes on 32bit platforms
|
* Corrected file sizes on 32bit platforms
|
||||||
* Fixed login redirect to remember the current page.
|
* Fixed login redirect to remember the current page.
|
||||||
|
* Fixed scene tagger config saving
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const Instance: React.FC<IInstanceProps> = ({
|
|||||||
value={instance?.endpoint}
|
value={instance?.endpoint}
|
||||||
isValid={(instance?.endpoint?.length ?? 0) > 0}
|
isValid={(instance?.endpoint?.length ?? 0) > 0}
|
||||||
onInput={(e: React.ChangeEvent<HTMLInputElement>) =>
|
onInput={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
handleInput("endpoint", e.currentTarget.value)
|
handleInput("endpoint", e.currentTarget.value.trim())
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
@@ -50,7 +50,7 @@ const Instance: React.FC<IInstanceProps> = ({
|
|||||||
value={instance?.api_key}
|
value={instance?.api_key}
|
||||||
isValid={(instance?.api_key?.length ?? 0) > 0}
|
isValid={(instance?.api_key?.length ?? 0) > 0}
|
||||||
onInput={(e: React.ChangeEvent<HTMLInputElement>) =>
|
onInput={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
handleInput("api_key", e.currentTarget.value)
|
handleInput("api_key", e.currentTarget.value.trim())
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<InputGroup.Append>
|
<InputGroup.Append>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.inline {
|
&.inline {
|
||||||
display: inline-block;
|
display: inline;
|
||||||
height: auto;
|
height: auto;
|
||||||
margin-left: 0.5rem;
|
margin-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { Dispatch, useEffect, useState } from "react";
|
import React, { Dispatch, useRef } from "react";
|
||||||
import {
|
import {
|
||||||
Badge,
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
@@ -8,51 +8,9 @@ import {
|
|||||||
InputGroup,
|
InputGroup,
|
||||||
} from "react-bootstrap";
|
} from "react-bootstrap";
|
||||||
import { Icon } from "src/components/Shared";
|
import { Icon } from "src/components/Shared";
|
||||||
import localForage from "localforage";
|
|
||||||
|
|
||||||
import { useConfiguration } from "src/core/StashService";
|
import { useConfiguration } from "src/core/StashService";
|
||||||
|
|
||||||
const DEFAULT_BLACKLIST = [
|
import { ITaggerConfig, ParseMode, ModeDesc } from "./constants";
|
||||||
"\\sXXX\\s",
|
|
||||||
"1080p",
|
|
||||||
"720p",
|
|
||||||
"2160p",
|
|
||||||
"KTR",
|
|
||||||
"RARBG",
|
|
||||||
"\\scom\\s",
|
|
||||||
"\\[",
|
|
||||||
"\\]",
|
|
||||||
];
|
|
||||||
|
|
||||||
export const initialConfig: ITaggerConfig = {
|
|
||||||
blacklist: DEFAULT_BLACKLIST,
|
|
||||||
showMales: false,
|
|
||||||
mode: "auto",
|
|
||||||
setCoverImage: true,
|
|
||||||
setTags: false,
|
|
||||||
tagOperation: "merge",
|
|
||||||
fingerprintQueue: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ParseMode = "auto" | "filename" | "dir" | "path" | "metadata";
|
|
||||||
const ModeDesc = {
|
|
||||||
auto: "Uses metadata if present, or filename",
|
|
||||||
metadata: "Only uses metadata",
|
|
||||||
filename: "Only uses filename",
|
|
||||||
dir: "Only uses parent directory of video file",
|
|
||||||
path: "Uses entire file path",
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface ITaggerConfig {
|
|
||||||
blacklist: string[];
|
|
||||||
showMales: boolean;
|
|
||||||
mode: ParseMode;
|
|
||||||
setCoverImage: boolean;
|
|
||||||
setTags: boolean;
|
|
||||||
tagOperation: string;
|
|
||||||
selectedEndpoint?: string;
|
|
||||||
fingerprintQueue: Record<string, string[]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IConfigProps {
|
interface IConfigProps {
|
||||||
show: boolean;
|
show: boolean;
|
||||||
@@ -62,26 +20,7 @@ interface IConfigProps {
|
|||||||
|
|
||||||
const Config: React.FC<IConfigProps> = ({ show, config, setConfig }) => {
|
const Config: React.FC<IConfigProps> = ({ show, config, setConfig }) => {
|
||||||
const stashConfig = useConfiguration();
|
const stashConfig = useConfiguration();
|
||||||
const [blacklistInput, setBlacklistInput] = useState<string>("");
|
const blacklistRef = useRef<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
localForage.getItem<ITaggerConfig>("tagger").then((data) => {
|
|
||||||
setConfig({
|
|
||||||
blacklist: data?.blacklist ?? DEFAULT_BLACKLIST,
|
|
||||||
showMales: data?.showMales ?? false,
|
|
||||||
mode: data?.mode ?? "auto",
|
|
||||||
setCoverImage: data?.setCoverImage ?? true,
|
|
||||||
setTags: data?.setTags ?? false,
|
|
||||||
tagOperation: data?.tagOperation ?? "merge",
|
|
||||||
selectedEndpoint: data?.selectedEndpoint,
|
|
||||||
fingerprintQueue: data?.fingerprintQueue ?? {},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, [setConfig]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
localForage.setItem("tagger", config);
|
|
||||||
}, [config]);
|
|
||||||
|
|
||||||
const handleInstanceSelect = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
const handleInstanceSelect = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
const selectedEndpoint = e.currentTarget.value;
|
const selectedEndpoint = e.currentTarget.value;
|
||||||
@@ -102,11 +41,14 @@ const Config: React.FC<IConfigProps> = ({ show, config, setConfig }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleBlacklistAddition = () => {
|
const handleBlacklistAddition = () => {
|
||||||
|
if (!blacklistRef.current) return;
|
||||||
|
|
||||||
|
const input = blacklistRef.current.value;
|
||||||
setConfig({
|
setConfig({
|
||||||
...config,
|
...config,
|
||||||
blacklist: [...config.blacklist, blacklistInput],
|
blacklist: [...config.blacklist, input],
|
||||||
});
|
});
|
||||||
setBlacklistInput("");
|
blacklistRef.current.value = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
const stashBoxes = stashConfig.data?.configuration.general.stashBoxes ?? [];
|
const stashBoxes = stashConfig.data?.configuration.general.stashBoxes ?? [];
|
||||||
@@ -204,13 +146,7 @@ const Config: React.FC<IConfigProps> = ({ show, config, setConfig }) => {
|
|||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<h5>Blacklist</h5>
|
<h5>Blacklist</h5>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<Form.Control
|
<Form.Control className="text-input" ref={blacklistRef} />
|
||||||
className="text-input"
|
|
||||||
value={blacklistInput}
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
||||||
setBlacklistInput(e.currentTarget.value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<InputGroup.Append>
|
<InputGroup.Append>
|
||||||
<Button onClick={handleBlacklistAddition}>Add</Button>
|
<Button onClick={handleBlacklistAddition}>Add</Button>
|
||||||
</InputGroup.Append>
|
</InputGroup.Append>
|
||||||
|
|||||||
@@ -104,8 +104,18 @@ const StashSearchResult: React.FC<IStashSearchResultProps> = ({
|
|||||||
const updatePerformerStashID = useUpdatePerformerStashID();
|
const updatePerformerStashID = useUpdatePerformerStashID();
|
||||||
const updateStudioStashID = useUpdateStudioStashID();
|
const updateStudioStashID = useUpdateStudioStashID();
|
||||||
const [updateScene] = GQL.useSceneUpdateMutation({
|
const [updateScene] = GQL.useSceneUpdateMutation({
|
||||||
onError: (errors) => errors,
|
onError: (e) => {
|
||||||
|
const message =
|
||||||
|
e.message === "invalid JPEG format: short Huffman data"
|
||||||
|
? "Failed to save scene due to corrupted cover image"
|
||||||
|
: "Failed to save scene";
|
||||||
|
setError({
|
||||||
|
message,
|
||||||
|
details: e.message,
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const { data: allTags } = GQL.useAllTagsForFilterQuery();
|
const { data: allTags } = GQL.useAllTagsForFilterQuery();
|
||||||
|
|
||||||
const setPerformer = (
|
const setPerformer = (
|
||||||
@@ -302,12 +312,7 @@ const StashSearchResult: React.FC<IStashSearchResultProps> = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!sceneUpdateResult?.data?.sceneUpdate) {
|
if (sceneUpdateResult?.data?.sceneUpdate)
|
||||||
setError({
|
|
||||||
message: "Failed to save scene",
|
|
||||||
details: sceneUpdateResult?.errors?.[0].message,
|
|
||||||
});
|
|
||||||
} else if (sceneUpdateResult.data?.sceneUpdate)
|
|
||||||
setScene(sceneUpdateResult.data.sceneUpdate);
|
setScene(sceneUpdateResult.data.sceneUpdate);
|
||||||
|
|
||||||
queueFingerprintSubmission(stashScene.id, endpoint);
|
queueFingerprintSubmission(stashScene.id, endpoint);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Button, Card, Form, InputGroup } from "react-bootstrap";
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { HashLink } from "react-router-hash-link";
|
import { HashLink } from "react-router-hash-link";
|
||||||
import { ScenePreview } from "src/components/Scenes/SceneCard";
|
import { ScenePreview } from "src/components/Scenes/SceneCard";
|
||||||
|
import { useLocalForage } from "src/hooks";
|
||||||
|
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { LoadingIndicator, TruncatedText } from "src/components/Shared";
|
import { LoadingIndicator, TruncatedText } from "src/components/Shared";
|
||||||
@@ -14,7 +15,13 @@ import {
|
|||||||
import { Manual } from "src/components/Help/Manual";
|
import { Manual } from "src/components/Help/Manual";
|
||||||
|
|
||||||
import StashSearchResult from "./StashSearchResult";
|
import StashSearchResult from "./StashSearchResult";
|
||||||
import Config, { ITaggerConfig, initialConfig, ParseMode } from "./Config";
|
import Config from "./Config";
|
||||||
|
import {
|
||||||
|
LOCAL_FORAGE_KEY,
|
||||||
|
ITaggerConfig,
|
||||||
|
ParseMode,
|
||||||
|
initialConfig,
|
||||||
|
} from "./constants";
|
||||||
import {
|
import {
|
||||||
parsePath,
|
parsePath,
|
||||||
selectScenes,
|
selectScenes,
|
||||||
@@ -22,7 +29,79 @@ import {
|
|||||||
sortScenesByDuration,
|
sortScenesByDuration,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
|
|
||||||
const dateRegex = /\.(\d\d)\.(\d\d)\.(\d\d)\./;
|
const months = [
|
||||||
|
"jan",
|
||||||
|
"feb",
|
||||||
|
"mar",
|
||||||
|
"apr",
|
||||||
|
"may",
|
||||||
|
"jun",
|
||||||
|
"jul",
|
||||||
|
"aug",
|
||||||
|
"sep",
|
||||||
|
"oct",
|
||||||
|
"nov",
|
||||||
|
"dec",
|
||||||
|
];
|
||||||
|
|
||||||
|
const ddmmyyRegex = /\.(\d\d)\.(\d\d)\.(\d\d)\./;
|
||||||
|
const yyyymmddRegex = /(\d{4})[-.](\d{2})[-.](\d{2})/;
|
||||||
|
const mmddyyRegex = /(\d{2})[-.](\d{2})[-.](\d{4})/;
|
||||||
|
const ddMMyyRegex = new RegExp(
|
||||||
|
`(\\d{1,2}).(${months.join("|")})\\.?.(\\d{4})`,
|
||||||
|
"i"
|
||||||
|
);
|
||||||
|
const MMddyyRegex = new RegExp(
|
||||||
|
`(${months.join("|")})\\.?.(\\d{1,2}),?.(\\d{4})`,
|
||||||
|
"i"
|
||||||
|
);
|
||||||
|
const parseDate = (input: string): string => {
|
||||||
|
let output = input;
|
||||||
|
const ddmmyy = output.match(ddmmyyRegex);
|
||||||
|
if (ddmmyy) {
|
||||||
|
output = output.replace(
|
||||||
|
ddmmyy[0],
|
||||||
|
` 20${ddmmyy[1]}-${ddmmyy[2]}-${ddmmyy[3]} `
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const mmddyy = output.match(mmddyyRegex);
|
||||||
|
if (mmddyy) {
|
||||||
|
output = output.replace(
|
||||||
|
mmddyy[0],
|
||||||
|
` 20${mmddyy[1]}-${mmddyy[2]}-${mmddyy[3]} `
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const ddMMyy = output.match(ddMMyyRegex);
|
||||||
|
if (ddMMyy) {
|
||||||
|
const month = (months.indexOf(ddMMyy[2].toLowerCase()) + 1)
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0");
|
||||||
|
output = output.replace(
|
||||||
|
ddMMyy[0],
|
||||||
|
` ${ddMMyy[3]}-${month}-${ddMMyy[1].padStart(2, "0")} `
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const MMddyy = output.match(MMddyyRegex);
|
||||||
|
if (MMddyy) {
|
||||||
|
const month = (months.indexOf(MMddyy[1].toLowerCase()) + 1)
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0");
|
||||||
|
output = output.replace(
|
||||||
|
MMddyy[0],
|
||||||
|
` ${MMddyy[3]}-${month}-${MMddyy[2].padStart(2, "0")} `
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const yyyymmdd = output.search(yyyymmddRegex);
|
||||||
|
if (yyyymmdd !== -1)
|
||||||
|
return (
|
||||||
|
output.slice(0, yyyymmdd).replace(/-/g, " ") +
|
||||||
|
output.slice(yyyymmdd, yyyymmdd + 10) +
|
||||||
|
output.slice(yyyymmdd + 10).replace(/-/g, " ")
|
||||||
|
);
|
||||||
|
return output.replace(/-/g, " ");
|
||||||
|
};
|
||||||
|
|
||||||
function prepareQueryString(
|
function prepareQueryString(
|
||||||
scene: Partial<GQL.SlimSceneDataFragment>,
|
scene: Partial<GQL.SlimSceneDataFragment>,
|
||||||
paths: string[],
|
paths: string[],
|
||||||
@@ -45,6 +124,7 @@ function prepareQueryString(
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
let s = "";
|
let s = "";
|
||||||
|
|
||||||
if (mode === "auto" || mode === "filename") {
|
if (mode === "auto" || mode === "filename") {
|
||||||
s = filename;
|
s = filename;
|
||||||
} else if (mode === "path") {
|
} else if (mode === "path") {
|
||||||
@@ -55,11 +135,7 @@ function prepareQueryString(
|
|||||||
blacklist.forEach((b) => {
|
blacklist.forEach((b) => {
|
||||||
s = s.replace(new RegExp(b, "i"), "");
|
s = s.replace(new RegExp(b, "i"), "");
|
||||||
});
|
});
|
||||||
const date = s.match(dateRegex);
|
s = parseDate(s);
|
||||||
s = s.replace(/-/g, " ");
|
|
||||||
if (date) {
|
|
||||||
s = s.replace(date[0], ` 20${date[1]}-${date[2]}-${date[3]} `);
|
|
||||||
}
|
|
||||||
return s.replace(/\./g, " ");
|
return s.replace(/\./g, " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,6 +324,9 @@ const TaggerList: React.FC<ITaggerListProps> = ({
|
|||||||
} else if (!isTagged && !hasStashIDs) {
|
} else if (!isTagged && !hasStashIDs) {
|
||||||
mainContent = (
|
mainContent = (
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
|
<InputGroup.Prepend>
|
||||||
|
<InputGroup.Text>Query</InputGroup.Text>
|
||||||
|
</InputGroup.Prepend>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
className="text-input"
|
className="text-input"
|
||||||
value={modifiedQuery || defaultQueryString}
|
value={modifiedQuery || defaultQueryString}
|
||||||
@@ -417,12 +496,6 @@ const TaggerList: React.FC<ITaggerListProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Card className="tagger-table">
|
<Card className="tagger-table">
|
||||||
<div className="tagger-table-header d-flex flex-nowrap align-items-center">
|
<div className="tagger-table-header d-flex flex-nowrap align-items-center">
|
||||||
<div className="col-md-6 pl-0">
|
|
||||||
<b>Scene</b>
|
|
||||||
</div>
|
|
||||||
<div className="col-md-2">
|
|
||||||
<b>Query</b>
|
|
||||||
</div>
|
|
||||||
<b className="ml-auto mr-2 text-danger">{fingerprintError}</b>
|
<b className="ml-auto mr-2 text-danger">{fingerprintError}</b>
|
||||||
<div className="mr-2">
|
<div className="mr-2">
|
||||||
{fingerprintQueue.length > 0 && (
|
{fingerprintQueue.length > 0 && (
|
||||||
@@ -460,10 +533,15 @@ interface ITaggerProps {
|
|||||||
|
|
||||||
export const Tagger: React.FC<ITaggerProps> = ({ scenes }) => {
|
export const Tagger: React.FC<ITaggerProps> = ({ scenes }) => {
|
||||||
const stashConfig = useConfiguration();
|
const stashConfig = useConfiguration();
|
||||||
const [config, setConfig] = useState<ITaggerConfig>(initialConfig);
|
const [{ data: config }, setConfig] = useLocalForage<ITaggerConfig>(
|
||||||
|
LOCAL_FORAGE_KEY,
|
||||||
|
initialConfig
|
||||||
|
);
|
||||||
const [showConfig, setShowConfig] = useState(false);
|
const [showConfig, setShowConfig] = useState(false);
|
||||||
const [showManual, setShowManual] = useState(false);
|
const [showManual, setShowManual] = useState(false);
|
||||||
|
|
||||||
|
if (!config) return <LoadingIndicator />;
|
||||||
|
|
||||||
const savedEndpointIndex =
|
const savedEndpointIndex =
|
||||||
stashConfig.data?.configuration.general.stashBoxes.findIndex(
|
stashConfig.data?.configuration.general.stashBoxes.findIndex(
|
||||||
(s) => s.endpoint === config.selectedEndpoint
|
(s) => s.endpoint === config.selectedEndpoint
|
||||||
@@ -503,7 +581,7 @@ export const Tagger: React.FC<ITaggerProps> = ({ scenes }) => {
|
|||||||
onClose={() => setShowManual(false)}
|
onClose={() => setShowManual(false)}
|
||||||
defaultActiveTab="Tagger.md"
|
defaultActiveTab="Tagger.md"
|
||||||
/>
|
/>
|
||||||
<div className="tagger-container row mx-md-auto">
|
<div className="tagger-container mx-md-auto">
|
||||||
{selectedEndpointIndex !== -1 && selectedEndpoint ? (
|
{selectedEndpointIndex !== -1 && selectedEndpoint ? (
|
||||||
<>
|
<>
|
||||||
<div className="row mb-2 no-gutters">
|
<div className="row mb-2 no-gutters">
|
||||||
|
|||||||
42
ui/v2.5/src/components/Tagger/constants.ts
Normal file
42
ui/v2.5/src/components/Tagger/constants.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
export const LOCAL_FORAGE_KEY = "tagger";
|
||||||
|
export const DEFAULT_BLACKLIST = [
|
||||||
|
"\\sXXX\\s",
|
||||||
|
"1080p",
|
||||||
|
"720p",
|
||||||
|
"2160p",
|
||||||
|
"KTR",
|
||||||
|
"RARBG",
|
||||||
|
"\\scom\\s",
|
||||||
|
"\\[",
|
||||||
|
"\\]",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const initialConfig: ITaggerConfig = {
|
||||||
|
blacklist: DEFAULT_BLACKLIST,
|
||||||
|
showMales: false,
|
||||||
|
mode: "auto",
|
||||||
|
setCoverImage: true,
|
||||||
|
setTags: false,
|
||||||
|
tagOperation: "merge",
|
||||||
|
fingerprintQueue: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ParseMode = "auto" | "filename" | "dir" | "path" | "metadata";
|
||||||
|
export const ModeDesc = {
|
||||||
|
auto: "Uses metadata if present, or filename",
|
||||||
|
metadata: "Only uses metadata",
|
||||||
|
filename: "Only uses filename",
|
||||||
|
dir: "Only uses parent directory of video file",
|
||||||
|
path: "Uses entire file path",
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ITaggerConfig {
|
||||||
|
blacklist: string[];
|
||||||
|
showMales: boolean;
|
||||||
|
mode: ParseMode;
|
||||||
|
setCoverImage: boolean;
|
||||||
|
setTags: boolean;
|
||||||
|
tagOperation: string;
|
||||||
|
selectedEndpoint?: string;
|
||||||
|
fingerprintQueue: Record<string, string[]>;
|
||||||
|
}
|
||||||
@@ -27,8 +27,9 @@ interface ILocalForage<T> {
|
|||||||
const Loading: Record<string, boolean> = {};
|
const Loading: Record<string, boolean> = {};
|
||||||
const Cache: Record<string, {}> = {};
|
const Cache: Record<string, {}> = {};
|
||||||
|
|
||||||
function useLocalForage<T>(
|
export function useLocalForage<T>(
|
||||||
key: string
|
key: string,
|
||||||
|
defaultValue: T = {} as T
|
||||||
): [ILocalForage<T>, Dispatch<SetStateAction<T>>] {
|
): [ILocalForage<T>, Dispatch<SetStateAction<T>>] {
|
||||||
const [error, setError] = React.useState(null);
|
const [error, setError] = React.useState(null);
|
||||||
const [data, setData] = React.useState<T>(Cache[key] as T);
|
const [data, setData] = React.useState<T>(Cache[key] as T);
|
||||||
@@ -43,27 +44,32 @@ function useLocalForage<T>(
|
|||||||
setData(parsed);
|
setData(parsed);
|
||||||
Cache[key] = parsed;
|
Cache[key] = parsed;
|
||||||
} else {
|
} else {
|
||||||
setData({} as T);
|
setData(defaultValue);
|
||||||
Cache[key] = {};
|
Cache[key] = defaultValue;
|
||||||
}
|
}
|
||||||
setError(null);
|
setError(null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err);
|
setError(err);
|
||||||
|
Cache[key] = defaultValue;
|
||||||
} finally {
|
} finally {
|
||||||
Loading[key] = false;
|
Loading[key] = false;
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!loading && !Cache[key]) {
|
if (!loading && !Cache[key]) {
|
||||||
Loading[key] = true;
|
Loading[key] = true;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
runAsync();
|
runAsync();
|
||||||
}
|
}
|
||||||
}, [loading, data, key]);
|
}, [loading, key, defaultValue]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!_.isEqual(Cache[key], data)) {
|
if (!_.isEqual(Cache[key], data)) {
|
||||||
Cache[key] = _.merge(Cache[key], data);
|
Cache[key] = _.merge({
|
||||||
|
...Cache[key],
|
||||||
|
...data,
|
||||||
|
});
|
||||||
localForage.setItem(key, JSON.stringify(Cache[key]));
|
localForage.setItem(key, JSON.stringify(Cache[key]));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
export { default as useToast } from "./Toast";
|
export { default as useToast } from "./Toast";
|
||||||
export { useInterfaceLocalForage, useChangelogStorage } from "./LocalForage";
|
export {
|
||||||
|
useInterfaceLocalForage,
|
||||||
|
useChangelogStorage,
|
||||||
|
useLocalForage,
|
||||||
|
} from "./LocalForage";
|
||||||
export {
|
export {
|
||||||
useScenesList,
|
useScenesList,
|
||||||
useSceneMarkersList,
|
useSceneMarkersList,
|
||||||
|
|||||||
Reference in New Issue
Block a user