diff --git a/ui/v2.5/.eslintrc.json b/ui/v2.5/.eslintrc.json index 6a0c9d332..ca9002de9 100644 --- a/ui/v2.5/.eslintrc.json +++ b/ui/v2.5/.eslintrc.json @@ -3,7 +3,9 @@ "parserOptions": { "project": "./tsconfig.json" }, - "plugins": ["@typescript-eslint"], + "plugins": [ + "@typescript-eslint" + ], "extends": [ "airbnb-typescript", "prettier", diff --git a/ui/v2.5/package.json b/ui/v2.5/package.json index c608d826c..23d1d5f16 100644 --- a/ui/v2.5/package.json +++ b/ui/v2.5/package.json @@ -41,6 +41,7 @@ "eject": "react-scripts eject", "lint": "eslint --cache src/**/*.{ts,tsx}", "lint:fix": "eslint --fix src/**/*.{ts,tsx}", + "format": "prettier \"src/**/*.{js,jsx,ts,tsx}\"", "gqlgen": "gql-gen --config codegen.yml" }, "browserslist": [ @@ -68,14 +69,13 @@ "eslint-config-airbnb-typescript": "^6.3.1", "eslint-config-prettier": "^6.9.0", "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-prettier": "^3.1.2", "graphql-code-generator": "0.18.2", "graphql-codegen-add": "0.18.2", "graphql-codegen-time": "0.18.2", "graphql-codegen-typescript-client": "0.18.2", "graphql-codegen-typescript-common": "0.18.2", "graphql-codegen-typescript-react-apollo": "0.18.2", - "prettier": "^1.19.1", + "prettier": "1.19.1", "typescript": "~3.7.4" } } diff --git a/ui/v2.5/src/App.tsx b/ui/v2.5/src/App.tsx index 921917665..ebe714b21 100755 --- a/ui/v2.5/src/App.tsx +++ b/ui/v2.5/src/App.tsx @@ -1,8 +1,8 @@ import React from "react"; import { Route, Switch } from "react-router-dom"; -import { ToastProvider } from 'src/hooks/Toast'; -import { library } from '@fortawesome/fontawesome-svg-core' -import { fas } from '@fortawesome/free-solid-svg-icons' +import { ToastProvider } from "src/hooks/Toast"; +import { library } from "@fortawesome/fontawesome-svg-core"; +import { fas } from "@fortawesome/free-solid-svg-icons"; import { ErrorBoundary } from "./components/ErrorBoundary"; import Galleries from "./components/Galleries/Galleries"; import { MainNavbar } from "./components/MainNavbar"; @@ -15,8 +15,7 @@ import Studios from "./components/Studios/Studios"; import Tags from "./components/Tags/Tags"; import { SceneFilenameParser } from "./components/scenes/SceneFilenameParser"; - -import 'bootstrap/dist/css/bootstrap.min.css'; +import "bootstrap/dist/css/bootstrap.min.css"; library.add(fas); @@ -35,7 +34,10 @@ export const App: React.FC = () => ( - + diff --git a/ui/v2.5/src/components/ErrorBoundary.tsx b/ui/v2.5/src/components/ErrorBoundary.tsx index eb47f4547..84312a417 100644 --- a/ui/v2.5/src/components/ErrorBoundary.tsx +++ b/ui/v2.5/src/components/ErrorBoundary.tsx @@ -9,7 +9,7 @@ export class ErrorBoundary extends React.Component { public componentDidCatch(error: any, errorInfo: any) { this.setState({ error, - errorInfo, + errorInfo }); } diff --git a/ui/v2.5/src/components/Galleries/Gallery.tsx b/ui/v2.5/src/components/Galleries/Gallery.tsx index a895549ce..51375fb1d 100644 --- a/ui/v2.5/src/components/Galleries/Gallery.tsx +++ b/ui/v2.5/src/components/Galleries/Gallery.tsx @@ -1,22 +1,21 @@ import React from "react"; -import { Spinner } from 'react-bootstrap'; -import { useParams } from 'react-router-dom'; +import { Spinner } from "react-bootstrap"; +import { useParams } from "react-router-dom"; import { StashService } from "src/core/StashService"; import { GalleryViewer } from "./GalleryViewer"; export const Gallery: React.FC = () => { - const { id = '' } = useParams(); + const { id = "" } = useParams(); const { data, error, loading } = StashService.useFindGallery(id); const gallery = data?.findGallery; if (loading || !gallery) return ; - if (error) - return
{error.message}
; + if (error) return
{error.message}
; return ( -
+
); diff --git a/ui/v2.5/src/components/Galleries/GalleryList.tsx b/ui/v2.5/src/components/Galleries/GalleryList.tsx index 3bd296472..b8d805048 100644 --- a/ui/v2.5/src/components/Galleries/GalleryList.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryList.tsx @@ -1,24 +1,33 @@ import React from "react"; -import { Table } from 'react-bootstrap'; +import { Table } from "react-bootstrap"; import { QueryHookResult } from "react-apollo-hooks"; import { Link } from "react-router-dom"; -import { FindGalleriesQuery, FindGalleriesVariables } from "src/core/generated-graphql"; +import { + FindGalleriesQuery, + FindGalleriesVariables +} from "src/core/generated-graphql"; import { useGalleriesList } from "src/hooks"; import { ListFilterModel } from "src/models/list-filter/filter"; import { DisplayMode } from "src/models/list-filter/types"; export const GalleryList: React.FC = () => { const listData = useGalleriesList({ - renderContent, + renderContent }); - function renderContent(result: QueryHookResult, filter: ListFilterModel) { - if (!result.data || !result.data.findGalleries) { return; } + function renderContent( + result: QueryHookResult, + filter: ListFilterModel + ) { + if (!result.data || !result.data.findGalleries) { + return; + } if (filter.displayMode === DisplayMode.Grid) { return

TODO

; - } if (filter.displayMode === DisplayMode.List) { + } + if (filter.displayMode === DisplayMode.List) { return ( - +
@@ -26,20 +35,27 @@ export const GalleryList: React.FC = () => { - {result.data.findGalleries.galleries.map((gallery) => ( + {result.data.findGalleries.galleries.map(gallery => ( - + ))}
Preview
- {gallery.files.length > 0 ? : undefined} + {gallery.files.length > 0 ? ( + + ) : ( + undefined + )} {gallery.path} + {gallery.path} +
); - } if (filter.displayMode === DisplayMode.Wall) { + } + if (filter.displayMode === DisplayMode.Wall) { return

TODO

; } } diff --git a/ui/v2.5/src/components/Galleries/GalleryViewer.tsx b/ui/v2.5/src/components/Galleries/GalleryViewer.tsx index 25d96ef4b..b7cf9c1de 100644 --- a/ui/v2.5/src/components/Galleries/GalleryViewer.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryViewer.tsx @@ -11,7 +11,10 @@ export const GalleryViewer: FunctionComponent = ({ gallery }) => { const [currentImage, setCurrentImage] = useState(0); const [lightboxIsOpen, setLightboxIsOpen] = useState(false); - function openLightbox(_event: React.MouseEvent, obj: {index: number}) { + function openLightbox( + _event: React.MouseEvent, + obj: { index: number } + ) { setCurrentImage(obj.index); setLightboxIsOpen(true); } @@ -26,8 +29,15 @@ export const GalleryViewer: FunctionComponent = ({ gallery }) => { setCurrentImage(currentImage + 1); } - const photos = gallery.files.map((file) => ({src: file.path || "", caption: file.name})); - const thumbs = gallery.files.map((file) => ({src: `${file.path}?thumb=true` || "", width: 1, height: 1})); + const photos = gallery.files.map(file => ({ + src: file.path || "", + caption: file.name + })); + const thumbs = gallery.files.map(file => ({ + src: `${file.path}?thumb=true` || "", + width: 1, + height: 1 + })); return (
diff --git a/ui/v2.5/src/components/MainNavbar.tsx b/ui/v2.5/src/components/MainNavbar.tsx index 556577008..150ad31c4 100644 --- a/ui/v2.5/src/components/MainNavbar.tsx +++ b/ui/v2.5/src/components/MainNavbar.tsx @@ -1,72 +1,77 @@ import React from "react"; import { Nav, Navbar, Button } from "react-bootstrap"; -import { IconName } from '@fortawesome/fontawesome-svg-core'; -import { LinkContainer } from 'react-router-bootstrap'; +import { IconName } from "@fortawesome/fontawesome-svg-core"; +import { LinkContainer } from "react-router-bootstrap"; import { Link, useLocation } from "react-router-dom"; -import { Icon } from 'src/components/Shared' +import { Icon } from "src/components/Shared"; interface IMenuItem { - text: string; - href: string; - icon: IconName; + text: string; + href: string; + icon: IconName; } -const menuItems:IMenuItem[] = [ -{ - icon: "play-circle", - text: "Scenes", - href: "/scenes" -}, -{ - href: "/scenes/markers", - icon: "map-marker-alt", - text: "Markers" -}, -{ - href: "/galleries", - icon: "image", - text: "Galleries" -}, -{ - href: "/performers", - icon: "user", - text: "Performers" -}, -{ - href: "/studios", - icon: "video", - text: "Studios" -}, -{ - href: "/tags", - icon: "tag", - text: "Tags" -} +const menuItems: IMenuItem[] = [ + { + icon: "play-circle", + text: "Scenes", + href: "/scenes" + }, + { + href: "/scenes/markers", + icon: "map-marker-alt", + text: "Markers" + }, + { + href: "/galleries", + icon: "image", + text: "Galleries" + }, + { + href: "/performers", + icon: "user", + text: "Performers" + }, + { + href: "/studios", + icon: "video", + text: "Studios" + }, + { + href: "/tags", + icon: "tag", + text: "Tags" + } ]; export const MainNavbar: React.FC = () => { const location = useLocation(); - const path = location.pathname === '/performers' - ? '/performers/new' - : location.pathname === '/studios' - ? '/studios/new' : null; - const newButton = path === null ? '' : ( - - - - ); + const path = + location.pathname === "/performers" + ? "/performers/new" + : location.pathname === "/studios" + ? "/studios/new" + : null; + const newButton = + path === null ? ( + "" + ) : ( + + + + ); return ( - + diff --git a/ui/v2.5/src/components/PageNotFound.tsx b/ui/v2.5/src/components/PageNotFound.tsx index 8e7cfb32e..645709fcf 100644 --- a/ui/v2.5/src/components/PageNotFound.tsx +++ b/ui/v2.5/src/components/PageNotFound.tsx @@ -1,7 +1,5 @@ import React, { FunctionComponent } from "react"; export const PageNotFound: FunctionComponent = () => { - return ( -

Page not found.

- ); + return

Page not found.

; }; diff --git a/ui/v2.5/src/components/Settings/Settings.tsx b/ui/v2.5/src/components/Settings/Settings.tsx index f2f164cdc..e3e02686c 100644 --- a/ui/v2.5/src/components/Settings/Settings.tsx +++ b/ui/v2.5/src/components/Settings/Settings.tsx @@ -1,7 +1,7 @@ import React from "react"; import queryString from "query-string"; -import { Card, Tab, Nav, Row, Col } from 'react-bootstrap'; -import { useHistory, useLocation } from 'react-router-dom'; +import { Card, Tab, Nav, Row, Col } from "react-bootstrap"; +import { useHistory, useLocation } from "react-router-dom"; import { SettingsAboutPanel } from "./SettingsAboutPanel"; import { SettingsConfigurationPanel } from "./SettingsConfigurationPanel"; import { SettingsInterfacePanel } from "./SettingsInterfacePanel"; @@ -9,55 +9,59 @@ import { SettingsLogsPanel } from "./SettingsLogsPanel"; import { SettingsTasksPanel } from "./SettingsTasksPanel/SettingsTasksPanel"; export const Settings: React.FC = () => { - const location = useLocation(); + const location = useLocation(); const history = useHistory(); - const defaultTab = queryString.parse(location.search).tab ?? 'configuration'; + const defaultTab = queryString.parse(location.search).tab ?? "configuration"; - const onSelect = ((val:string) => history.push(`?tab=${val}`)); + const onSelect = (val: string) => history.push(`?tab=${val}`); return ( - - - - + + + + + + + + + + + + + + + + + + + + + ); diff --git a/ui/v2.5/src/components/Settings/SettingsAboutPanel.tsx b/ui/v2.5/src/components/Settings/SettingsAboutPanel.tsx index 70008e3e4..2e70f7abf 100644 --- a/ui/v2.5/src/components/Settings/SettingsAboutPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsAboutPanel.tsx @@ -1,12 +1,14 @@ import React from "react"; -import { Table, Spinner } from 'react-bootstrap'; +import { Table, Spinner } from "react-bootstrap"; import { StashService } from "src/core/StashService"; export const SettingsAboutPanel: React.FC = () => { const { data, error, loading } = StashService.useVersion(); function maybeRenderTag() { - if (!data || !data.version || !data.version.version) { return; } + if (!data || !data.version || !data.version.version) { + return; + } return ( Version: @@ -16,30 +18,32 @@ export const SettingsAboutPanel: React.FC = () => { } function renderVersion() { - if (!data || !data.version) { return; } + if (!data || !data.version) { + return; + } return ( <> - - - {maybeRenderTag()} - - - - - - - - - -
Build hash:{data.version.hash}
Build time:{data.version.build_time}
+ + + {maybeRenderTag()} + + + + + + + + + +
Build hash:{data.version.hash}
Build time:{data.version.build_time}
); } return ( <>

About

- {!data || loading ? : ''} - {error ? error.message : ''} + {!data || loading ? : ""} + {error ? error.message : ""} {renderVersion()} ); diff --git a/ui/v2.5/src/components/Settings/SettingsConfigurationPanel.tsx b/ui/v2.5/src/components/Settings/SettingsConfigurationPanel.tsx index b607bdbc2..eea36e1e4 100644 --- a/ui/v2.5/src/components/Settings/SettingsConfigurationPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsConfigurationPanel.tsx @@ -1,26 +1,34 @@ import React, { useEffect, useState } from "react"; -import { Button, Form, InputGroup, Spinner } from 'react-bootstrap'; +import { Button, Form, InputGroup, Spinner } from "react-bootstrap"; import * as GQL from "src/core/generated-graphql"; import { StashService } from "src/core/StashService"; -import { useToast } from 'src/hooks'; -import { Icon } from 'src/components/Shared'; +import { useToast } from "src/hooks"; +import { Icon } from "src/components/Shared"; import { FolderSelect } from "src/components/Shared/FolderSelect/FolderSelect"; export const SettingsConfigurationPanel: React.FC = () => { const Toast = useToast(); // Editing config state const [stashes, setStashes] = useState([]); - const [databasePath, setDatabasePath] = useState(undefined); - const [generatedPath, setGeneratedPath] = useState(undefined); - const [maxTranscodeSize, setMaxTranscodeSize] = useState(undefined); - const [maxStreamingTranscodeSize, setMaxStreamingTranscodeSize] = useState(undefined); + const [databasePath, setDatabasePath] = useState( + undefined + ); + const [generatedPath, setGeneratedPath] = useState( + undefined + ); + const [maxTranscodeSize, setMaxTranscodeSize] = useState< + GQL.StreamingResolutionEnum | undefined + >(undefined); + const [maxStreamingTranscodeSize, setMaxStreamingTranscodeSize] = useState< + GQL.StreamingResolutionEnum | undefined + >(undefined); const [username, setUsername] = useState(undefined); const [password, setPassword] = useState(undefined); const [logFile, setLogFile] = useState(); const [logOut, setLogOut] = useState(true); const [logLevel, setLogLevel] = useState("Info"); const [logAccess, setLogAccess] = useState(true); - const [excludes, setExcludes] = useState<(string)[]>([]); + const [excludes, setExcludes] = useState([]); const { data, error, loading } = StashService.useConfiguration(); @@ -36,12 +44,11 @@ export const SettingsConfigurationPanel: React.FC = () => { logOut, logLevel, logAccess, - excludes, + excludes }); useEffect(() => { - if (!data?.configuration || error) - return; + if (!data?.configuration || error) return; const conf = data.configuration; if (conf.general) { @@ -65,27 +72,26 @@ export const SettingsConfigurationPanel: React.FC = () => { } function excludeRegexChanged(idx: number, value: string) { - const newExcludes = excludes.map((regex, i)=> { - const ret = ( idx !== i ) ? regex : value ; - return ret - }) + const newExcludes = excludes.map((regex, i) => { + const ret = idx !== i ? regex : value; + return ret; + }); setExcludes(newExcludes); } function excludeRemoveRegex(idx: number) { - const newExcludes = excludes.filter((_regex, i) => i !== idx ); + const newExcludes = excludes.filter((_regex, i) => i !== idx); setExcludes(newExcludes); } function excludeAddRegex() { - const demo = "sample\\.mp4$" + const demo = "sample\\.mp4$"; const newExcludes = excludes.concat(demo); setExcludes(newExcludes); } - async function onSave() { try { const result = await updateGeneralConfig(); @@ -106,35 +112,46 @@ export const SettingsConfigurationPanel: React.FC = () => { GQL.StreamingResolutionEnum.Original ].map(resolutionToString); - function resolutionToString(r : GQL.StreamingResolutionEnum | undefined) { + function resolutionToString(r: GQL.StreamingResolutionEnum | undefined) { switch (r) { - case GQL.StreamingResolutionEnum.Low: return "240p"; - case GQL.StreamingResolutionEnum.Standard: return "480p"; - case GQL.StreamingResolutionEnum.StandardHd: return "720p"; - case GQL.StreamingResolutionEnum.FullHd: return "1080p"; - case GQL.StreamingResolutionEnum.FourK: return "4k"; - case GQL.StreamingResolutionEnum.Original: return "Original"; + case GQL.StreamingResolutionEnum.Low: + return "240p"; + case GQL.StreamingResolutionEnum.Standard: + return "480p"; + case GQL.StreamingResolutionEnum.StandardHd: + return "720p"; + case GQL.StreamingResolutionEnum.FullHd: + return "1080p"; + case GQL.StreamingResolutionEnum.FourK: + return "4k"; + case GQL.StreamingResolutionEnum.Original: + return "Original"; } return "Original"; } - function translateQuality(quality : string) { + function translateQuality(quality: string) { switch (quality) { - case "240p": return GQL.StreamingResolutionEnum.Low; - case "480p": return GQL.StreamingResolutionEnum.Standard; - case "720p": return GQL.StreamingResolutionEnum.StandardHd; - case "1080p": return GQL.StreamingResolutionEnum.FullHd; - case "4k": return GQL.StreamingResolutionEnum.FourK; - case "Original": return GQL.StreamingResolutionEnum.Original; + case "240p": + return GQL.StreamingResolutionEnum.Low; + case "480p": + return GQL.StreamingResolutionEnum.Standard; + case "720p": + return GQL.StreamingResolutionEnum.StandardHd; + case "1080p": + return GQL.StreamingResolutionEnum.FullHd; + case "4k": + return GQL.StreamingResolutionEnum.FourK; + case "Original": + return GQL.StreamingResolutionEnum.Original; } return GQL.StreamingResolutionEnum.Original; } - if(error) - return

{error.message}

; - if(!data?.configuration || loading) + if (error) return

{error.message}

; + if (!data?.configuration || loading) return ; return ( @@ -147,37 +164,56 @@ export const SettingsConfigurationPanel: React.FC = () => { directories={stashes} onDirectoriesChanged={onStashesChanged} /> - Directory locations to your content + + Directory locations to your content + Database Path - setDatabasePath(e.target.value)} /> - File location for the SQLite database (requires restart) + setDatabasePath(e.target.value)} + /> + + File location for the SQLite database (requires restart) + Generated Path - setGeneratedPath(e.target.value)} /> - Directory location for the generated files (scene markers, scene previews, sprites, etc) + setGeneratedPath(e.target.value)} + /> + + Directory location for the generated files (scene markers, scene + previews, sprites, etc) + Excluded Patterns - { excludes ? excludes.map((regexp, i) => ( - - excludeRegexChanged(i, e.target.value)} - /> - - - - - )) : '' - } + {excludes + ? excludes.map((regexp, i) => ( + + + excludeRegexChanged(i, e.target.value) + } + /> + + + + + )) + : ""} + ); }; diff --git a/ui/v2.5/src/components/Settings/SettingsInterfacePanel.tsx b/ui/v2.5/src/components/Settings/SettingsInterfacePanel.tsx index ae44e934b..4fc8f7abd 100644 --- a/ui/v2.5/src/components/Settings/SettingsInterfacePanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsInterfacePanel.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from "react"; -import { Button, Form, Spinner } from 'react-bootstrap'; +import { Button, Form, Spinner } from "react-bootstrap"; import { StashService } from "src/core/StashService"; -import { useToast } from 'src/hooks'; +import { useToast } from "src/hooks"; export const SettingsInterfacePanel: React.FC = () => { const Toast = useToast(); @@ -25,8 +25,7 @@ export const SettingsInterfacePanel: React.FC = () => { }); useEffect(() => { - if (config.error) - return; + if (config.error) return; const iCfg = config?.data?.configuration?.interface; setSoundOnPreview(iCfg?.soundOnPreview ?? true); @@ -51,8 +50,12 @@ export const SettingsInterfacePanel: React.FC = () => { return ( <> - {config.error ?

{config.error.message}

: ''} - {(!config?.data?.configuration || config.loading) ? : ''} + {config.error ?

{config.error.message}

: ""} + {!config?.data?.configuration || config.loading ? ( + + ) : ( + "" + )}

User Interface

Scene / Marker Wall @@ -66,7 +69,9 @@ export const SettingsInterfacePanel: React.FC = () => { label="Enable sound" onChange={() => setSoundOnPreview(!soundOnPreview)} /> - Configuration for wall items + + Configuration for wall items + @@ -75,31 +80,38 @@ export const SettingsInterfacePanel: React.FC = () => { checked={showStudioAsText} label="Show Studios as text" onChange={() => { - setShowStudioAsText(!showStudioAsText) + setShowStudioAsText(!showStudioAsText); }} /> - + Scene Player { - setAutostartVideo(!autostartVideo) + setAutostartVideo(!autostartVideo); }} /> - + Maximum loop duration ) => setMaximumLoopDuration(Number.parseInt(event.currentTarget.value, 10) ?? 0)} + onChange={(event: React.FormEvent) => + setMaximumLoopDuration( + Number.parseInt(event.currentTarget.value, 10) ?? 0 + ) + } min={0} step={1} /> - Maximum scene duration - in seconds - where scene player will loop the video - 0 to disable + + Maximum scene duration - in seconds - where scene player will loop + the video - 0 to disable + @@ -109,7 +121,7 @@ export const SettingsInterfacePanel: React.FC = () => { checked={cssEnabled} label="Custom CSS enabled" onChange={() => { - setCSSEnabled(!cssEnabled) + setCSSEnabled(!cssEnabled); }} /> @@ -117,13 +129,17 @@ export const SettingsInterfacePanel: React.FC = () => { as="textarea" value={css} onChange={(e: any) => setCSS(e.target.value)} - rows={16}> - - Page must be reloaded for changes to take effect. + rows={16} + > + + Page must be reloaded for changes to take effect. +
- + ); }; diff --git a/ui/v2.5/src/components/Settings/SettingsLogsPanel.tsx b/ui/v2.5/src/components/Settings/SettingsLogsPanel.tsx index aba2f4497..4a4d7d8bc 100644 --- a/ui/v2.5/src/components/Settings/SettingsLogsPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsLogsPanel.tsx @@ -1,13 +1,13 @@ import React, { useState } from "react"; -import { Form, Col } from 'react-bootstrap'; +import { Form, Col } from "react-bootstrap"; import * as GQL from "src/core/generated-graphql"; import { StashService } from "src/core/StashService"; function convertTime(logEntry: GQL.LogEntryDataFragment) { - function pad(val : number) { + function pad(val: number) { let ret = val.toString(); if (val <= 9) { - ret = `0${ ret}`; + ret = `0${ret}`; } return ret; @@ -16,21 +16,23 @@ function convertTime(logEntry: GQL.LogEntryDataFragment) { const date = new Date(logEntry.time); const month = date.getMonth() + 1; const day = date.getDate(); - let dateStr = `${date.getFullYear() }-${ pad(month) }-${ pad(day)}`; - dateStr += ` ${ pad(date.getHours()) }:${ pad(date.getMinutes()) }:${ pad(date.getSeconds())}`; + let dateStr = `${date.getFullYear()}-${pad(month)}-${pad(day)}`; + dateStr += ` ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad( + date.getSeconds() + )}`; return dateStr; } -function levelClass(level : string) { +function levelClass(level: string) { return level.toLowerCase().trim(); } interface ILogElementProps { - logEntry : LogEntry + logEntry: LogEntry; } -const LogElement: React.FC = ({ logEntry }) => { +const LogElement: React.FC = ({ logEntry }) => { // pad to maximum length of level enum const level = logEntry.level.padEnd(GQL.LogLevel.Progress.length); @@ -39,11 +41,10 @@ const LogElement: React.FC = ({ logEntry }) => { {logEntry.time}  {level}  {logEntry.message} -
+
); -} - +}; class LogEntry { public time: string; @@ -77,15 +78,17 @@ export const SettingsLogsPanel: React.FC = () => { const newData = (data?.loggingSubscribe ?? []).map(e => new LogEntry(e)); const filteredLogEntries = [...newData.reverse(), ...oldData] - .filter(filterByLogLevel).slice(0, MAX_LOG_ENTRIES); + .filter(filterByLogLevel) + .slice(0, MAX_LOG_ENTRIES); - const maybeRenderError = error - ?
Error connecting to log server: {error.message}
- : ''; + const maybeRenderError = error ? ( +
Error connecting to log server: {error.message}
+ ) : ( + "" + ); - function filterByLogLevel(logEntry : LogEntry) { - if (logLevel === "Debug") - return true; + function filterByLogLevel(logEntry: LogEntry) { + if (logLevel === "Debug") return true; const logLevelIndex = logLevels.indexOf(logLevel); const levelIndex = logLevels.indexOf(logEntry.level); @@ -104,17 +107,21 @@ export const SettingsLogsPanel: React.FC = () => { setLogLevel(event.currentTarget.value)} + onChange={event => setLogLevel(event.currentTarget.value)} > - { logLevels.map(level => ()) } + {logLevels.map(level => ( + + ))}
{maybeRenderError} - {filteredLogEntries.map((logEntry) => - - )} + {filteredLogEntries.map(logEntry => ( + + ))}
); diff --git a/ui/v2.5/src/components/Settings/SettingsTasksPanel/GenerateButton.tsx b/ui/v2.5/src/components/Settings/SettingsTasksPanel/GenerateButton.tsx index 21fcbe4e8..af280ce33 100644 --- a/ui/v2.5/src/components/Settings/SettingsTasksPanel/GenerateButton.tsx +++ b/ui/v2.5/src/components/Settings/SettingsTasksPanel/GenerateButton.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; -import { Button, Form } from 'react-bootstrap'; +import { Button, Form } from "react-bootstrap"; import { StashService } from "src/core/StashService"; -import { useToast } from 'src/hooks'; +import { useToast } from "src/hooks"; export const GenerateButton: React.FC = () => { const Toast = useToast(); @@ -12,7 +12,12 @@ export const GenerateButton: React.FC = () => { async function onGenerate() { try { - await StashService.queryMetadataGenerate({sprites, previews, markers, transcodes}); + await StashService.queryMetadataGenerate({ + sprites, + previews, + markers, + transcodes + }); Toast.success({ content: "Started generating" }); } catch (e) { Toast.error(e); @@ -21,12 +26,36 @@ export const GenerateButton: React.FC = () => { return ( - setSprites(!sprites)} /> - setPreviews(!previews)} /> - setMarkers(!markers)} /> - setTranscodes(!transcodes)} /> - - Generate supporting image, sprite, video, vtt and other files. + setSprites(!sprites)} + /> + setPreviews(!previews)} + /> + setMarkers(!markers)} + /> + setTranscodes(!transcodes)} + /> + + + Generate supporting image, sprite, video, vtt and other files. + ); }; diff --git a/ui/v2.5/src/components/Settings/SettingsTasksPanel/SettingsTasksPanel.tsx b/ui/v2.5/src/components/Settings/SettingsTasksPanel/SettingsTasksPanel.tsx index 7019141a4..b1a73a955 100644 --- a/ui/v2.5/src/components/Settings/SettingsTasksPanel/SettingsTasksPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsTasksPanel/SettingsTasksPanel.tsx @@ -1,9 +1,9 @@ import React, { useState, useEffect } from "react"; -import { Button, Form, ProgressBar } from 'react-bootstrap'; +import { Button, Form, ProgressBar } from "react-bootstrap"; import { Link } from "react-router-dom"; import { StashService } from "src/core/StashService"; -import { useToast } from 'src/hooks'; -import { Modal } from 'src/components/Shared'; +import { useToast } from "src/hooks"; +import { Modal } from "src/components/Shared"; import { GenerateButton } from "./GenerateButton"; export const SettingsTasksPanel: React.FC = () => { @@ -22,7 +22,7 @@ export const SettingsTasksPanel: React.FC = () => { const metadataUpdate = StashService.useMetadataUpdate(); function statusToText(s: string) { - switch(s) { + switch (s) { case "Idle": return "Idle"; case "Scan": @@ -38,7 +38,7 @@ export const SettingsTasksPanel: React.FC = () => { case "Auto Tag": return "Auto tagging scenes"; default: - return "Idle" + return "Idle"; } } @@ -68,7 +68,9 @@ export const SettingsTasksPanel: React.FC = () => { function onImport() { setIsImportAlertOpen(false); - StashService.queryMetadataImport().then(() => { jobStatus.refetch()}); + StashService.queryMetadataImport().then(() => { + jobStatus.refetch(); + }); } function renderImportAlert() { @@ -76,12 +78,12 @@ export const SettingsTasksPanel: React.FC = () => { setIsImportAlertOpen(false) }} >

- Are you sure you want to import? This will delete the database and re-import from - your exported metadata. + Are you sure you want to import? This will delete the database and + re-import from your exported metadata.

); @@ -89,7 +91,9 @@ export const SettingsTasksPanel: React.FC = () => { function onClean() { setIsCleanAlertOpen(false); - StashService.queryMetadataClean().then(() => { jobStatus.refetch()}); + StashService.queryMetadataClean().then(() => { + jobStatus.refetch(); + }); } function renderCleanAlert() { @@ -97,13 +101,13 @@ export const SettingsTasksPanel: React.FC = () => { setIsCleanAlertOpen(false) }} >

- Are you sure you want to Clean? - This will delete db information and generated content - for all scenes that are no longer found in the filesystem. + Are you sure you want to Clean? This will delete db information and + generated content for all scenes that are no longer found in the + filesystem.

); @@ -111,7 +115,7 @@ export const SettingsTasksPanel: React.FC = () => { async function onScan() { try { - await StashService.queryMetadataScan({useFileMetadata}); + await StashService.queryMetadataScan({ useFileMetadata }); Toast.success({ content: "Started scan" }); jobStatus.refetch(); } catch (e) { @@ -125,7 +129,7 @@ export const SettingsTasksPanel: React.FC = () => { performers: autoTagPerformers ? wildcard : [], studios: autoTagStudios ? wildcard : [], tags: autoTagTags ? wildcard : [] - } + }; } async function onAutoTag() { @@ -145,7 +149,15 @@ export const SettingsTasksPanel: React.FC = () => { return ( - + ); } @@ -153,11 +165,15 @@ export const SettingsTasksPanel: React.FC = () => { function renderJobStatus() { return ( <> - -
Status: {status}
- { status !== "Idle" ? : '' } -
- {maybeRenderStop()} + +
Status: {status}
+ {status !== "Idle" ? ( + + ) : ( + "" + )} +
+ {maybeRenderStop()} ); } @@ -180,8 +196,12 @@ export const SettingsTasksPanel: React.FC = () => { label="Set name, date, details from metadata (if present)" onChange={() => setUseFileMetadata(!useFileMetadata)} /> - - Scan for new content and add it to the database. + + + Scan for new content and add it to the database. +

@@ -204,8 +224,12 @@ export const SettingsTasksPanel: React.FC = () => { label="Tags" onChange={() => setAutoTagTags(!autoTagTags)} /> - - Auto-tag content based on filenames. + + + Auto-tag content based on filenames. +
@@ -219,21 +243,50 @@ export const SettingsTasksPanel: React.FC = () => {

Generated Content

- - Check for missing files and remove them from the database. This is a destructive action. + + + Check for missing files and remove them from the database. This is a + destructive action. +

Metadata

- - Export the database content into JSON format. + + + Export the database content into JSON format. + - - Import from exported JSON. This is a destructive action. + + + Import from exported JSON. This is a destructive action. + ); diff --git a/ui/v2.5/src/components/Shared/DetailsEditNavbar.tsx b/ui/v2.5/src/components/Shared/DetailsEditNavbar.tsx index 1d87e45d8..6db019597 100644 --- a/ui/v2.5/src/components/Shared/DetailsEditNavbar.tsx +++ b/ui/v2.5/src/components/Shared/DetailsEditNavbar.tsx @@ -1,4 +1,12 @@ -import { Button, Form, Modal, Nav, Navbar, OverlayTrigger, Popover } from 'react-bootstrap'; +import { + Button, + Form, + Modal, + Nav, + Navbar, + OverlayTrigger, + Popover +} from "react-bootstrap"; import React, { useState } from "react"; import { Link } from "react-router-dom"; import * as GQL from "src/core/generated-graphql"; @@ -17,56 +25,85 @@ interface IProps { // TODO: only for performers. make generic scrapers?: GQL.ListPerformerScrapersListPerformerScrapers[]; - onDisplayScraperDialog?: (scraper: GQL.ListPerformerScrapersListPerformerScrapers) => void; + onDisplayScraperDialog?: ( + scraper: GQL.ListPerformerScrapersListPerformerScrapers + ) => void; } export const DetailsEditNavbar: React.FC = (props: IProps) => { const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState(false); function renderEditButton() { - if (props.isNew) { return; } + if (props.isNew) { + return; + } return ( - ); } function renderSaveButton() { - if (!props.isEditing) { return; } - return ; + if (!props.isEditing) { + return; + } + return ( + + ); } function renderDeleteButton() { - if (props.isNew || props.isEditing) { return; } - return ; + if (props.isNew || props.isEditing) { + return; + } + return ( + + ); } function renderImageInput() { - if (!props.isEditing) { return; } - return ( - - Choose image... - - - ) + if (!props.isEditing) { + return; + } + return ( + + Choose image... + + + ); } function renderScraperMenu() { - if (!props.performer || !props.isEditing) { return; } + if (!props.performer || !props.isEditing) { + return; + } const popover = (
- { props.scrapers ? props.scrapers.map((s) => ( - - )) : ''} + {props.scrapers + ? props.scrapers.map(s => ( + + )) + : ""}
@@ -80,53 +117,63 @@ export const DetailsEditNavbar: React.FC = (props: IProps) => { } function renderAutoTagButton() { - if (props.isNew || props.isEditing) { return; } + if (props.isNew || props.isEditing) { + return; + } if (props.onAutoTag) { - return () + return ( + + ); } } function renderScenesButton() { - if (props.isEditing) { return; } + if (props.isEditing) { + return; + } let linkSrc: string = "#"; if (props.performer) { linkSrc = NavUtils.makePerformerScenesUrl(props.performer); } else if (props.studio) { linkSrc = NavUtils.makeStudioScenesUrl(props.studio); } - return ( - - Scenes - - ); + return Scenes; } function renderDeleteAlert() { const name = props?.studio?.name ?? props?.performer?.name; return ( - - - Are you sure you want to delete {name}? - + + Are you sure you want to delete {name}? - - + + ); } - return ( <> - {renderDeleteAlert()} - - + ); }; diff --git a/ui/v2.5/src/components/Shared/DurationInput.tsx b/ui/v2.5/src/components/Shared/DurationInput.tsx index cb2589799..2983d086c 100644 --- a/ui/v2.5/src/components/Shared/DurationInput.tsx +++ b/ui/v2.5/src/components/Shared/DurationInput.tsx @@ -1,23 +1,25 @@ import React, { useState, useEffect } from "react"; -import { Button, ButtonGroup, InputGroup, Form } from 'react-bootstrap'; -import { Icon } from 'src/components/Shared' +import { Button, ButtonGroup, InputGroup, Form } from "react-bootstrap"; +import { Icon } from "src/components/Shared"; import { TextUtils } from "src/utils"; interface IProps { - disabled?: boolean - numericValue: number - onValueChange(valueAsNumber: number): void - onReset?(): void + disabled?: boolean; + numericValue: number; + onValueChange(valueAsNumber: number): void; + onReset?(): void; } export const DurationInput: React.FC = (props: IProps) => { - const [value, setValue] = useState(secondsToString(props.numericValue)); + const [value, setValue] = useState( + secondsToString(props.numericValue) + ); useEffect(() => { setValue(secondsToString(props.numericValue)); }, [props.numericValue]); - function secondsToString(seconds : number) { + function secondsToString(seconds: number) { let ret = TextUtils.secondsToTimestamp(seconds); if (ret.startsWith("00:")) { @@ -31,7 +33,7 @@ export const DurationInput: React.FC = (props: IProps) => { return ret; } - function stringToSeconds(v : string) { + function stringToSeconds(v: string) { if (!v) { return 0; } @@ -44,7 +46,7 @@ export const DurationInput: React.FC = (props: IProps) => { let seconds = 0; let factor = 1; - while(splits.length > 0) { + while (splits.length > 0) { const thisSplit = splits.pop(); if (thisSplit === undefined) { return 0; @@ -76,23 +78,15 @@ export const DurationInput: React.FC = (props: IProps) => { function renderButtons() { return ( - - - - ) + ); } function onReset() { @@ -104,12 +98,10 @@ export const DurationInput: React.FC = (props: IProps) => { function maybeRenderReset() { if (props.onReset) { return ( - - ) + ); } } @@ -119,15 +111,15 @@ export const DurationInput: React.FC = (props: IProps) => { setValue(e.target.value)} + onChange={(e: any) => setValue(e.target.value)} onBlur={() => props.onValueChange(stringToSeconds(value))} placeholder="hh:mm:ss" /> - { maybeRenderReset() } - { renderButtons() } + {maybeRenderReset()} + {renderButtons()}
- ) + ); }; diff --git a/ui/v2.5/src/components/Shared/FolderSelect/FolderSelect.tsx b/ui/v2.5/src/components/Shared/FolderSelect/FolderSelect.tsx index 3bd5c274f..7c605cbce 100644 --- a/ui/v2.5/src/components/Shared/FolderSelect/FolderSelect.tsx +++ b/ui/v2.5/src/components/Shared/FolderSelect/FolderSelect.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from "react"; -import { Button, InputGroup, Form, Modal, Spinner } from 'react-bootstrap'; +import { Button, InputGroup, Form, Modal, Spinner } from "react-bootstrap"; import { StashService } from "src/core/StashService"; interface IProps { @@ -11,13 +11,15 @@ export const FolderSelect: React.FC = (props: IProps) => { const [currentDirectory, setCurrentDirectory] = useState(""); const [isDisplayingDialog, setIsDisplayingDialog] = useState(false); const [selectedDirectories, setSelectedDirectories] = useState([]); - const { data, error, loading } = StashService.useDirectories(currentDirectory); + const { data, error, loading } = StashService.useDirectories( + currentDirectory + ); useEffect(() => { setSelectedDirectories(props.directories); }, [props.directories]); - const selectableDirectories:string[] = data?.directories ?? []; + const selectableDirectories: string[] = data?.directories ?? []; function onSelectDirectory() { selectedDirectories.push(currentDirectory); @@ -28,7 +30,9 @@ export const FolderSelect: React.FC = (props: IProps) => { } function onRemoveDirectory(directory: string) { - const newSelectedDirectories = selectedDirectories.filter((dir) => dir !== directory); + const newSelectedDirectories = selectedDirectories.filter( + dir => dir !== directory + ); setSelectedDirectories(newSelectedDirectories); props.onDirectoriesChanged(newSelectedDirectories); } @@ -40,9 +44,7 @@ export const FolderSelect: React.FC = (props: IProps) => { onHide={() => setIsDisplayingDialog(false)} title="" > - - Select Directory - + Select Directory
@@ -52,11 +54,23 @@ export const FolderSelect: React.FC = (props: IProps) => { defaultValue={currentDirectory} /> - {(!data || !data.directories || loading) ? : ''} + {!data || !data.directories || loading ? ( + + ) : ( + "" + )} - {selectableDirectories.map((path) => { - return ; + {selectableDirectories.map(path => { + return ( + + ); })}
@@ -69,11 +83,18 @@ export const FolderSelect: React.FC = (props: IProps) => { return ( <> - {error ?

{error.message}

: ''} + {error ?

{error.message}

: ""} {renderDialog()} - {selectedDirectories.map((path) => { - return
{path}
; + {selectedDirectories.map(path => { + return ( +
+ {path}{" "} + +
+ ); })}
diff --git a/ui/v2.5/src/components/Shared/HoverPopover.tsx b/ui/v2.5/src/components/Shared/HoverPopover.tsx index 566c64720..1941f4e4d 100644 --- a/ui/v2.5/src/components/Shared/HoverPopover.tsx +++ b/ui/v2.5/src/components/Shared/HoverPopover.tsx @@ -1,15 +1,22 @@ -import React, { useState, useCallback, useEffect, useRef } from 'react' -import { Overlay, Popover, OverlayProps } from 'react-bootstrap' +import React, { useState, useCallback, useEffect, useRef } from "react"; +import { Overlay, Popover, OverlayProps } from "react-bootstrap"; interface IHoverPopover { - enterDelay?: number; - leaveDelay?: number; + enterDelay?: number; + leaveDelay?: number; content: JSX.Element[] | JSX.Element | string; className?: string; placement?: OverlayProps["placement"]; } -export const HoverPopover: React.FC = ({ enterDelay = 0, leaveDelay = 400, content, children, className, placement = 'top' }) => { +export const HoverPopover: React.FC = ({ + enterDelay = 0, + leaveDelay = 400, + content, + children, + className, + placement = "top" +}) => { const [show, setShow] = useState(false); const triggerRef = useRef(null); const enterTimer = useRef(); @@ -25,33 +32,35 @@ export const HoverPopover: React.FC = ({ enterDelay = 0, leaveDel leaveTimer.current = window.setTimeout(() => setShow(false), leaveDelay); }, [leaveDelay]); - useEffect(() => ( - () => { - window.clearTimeout(enterTimer.current) - window.clearTimeout(leaveTimer.current) - } - ), []); + useEffect( + () => () => { + window.clearTimeout(enterTimer.current); + window.clearTimeout(leaveTimer.current); + }, + [] + ); return ( <> -
+
{children}
- { triggerRef.current && - + {triggerRef.current && ( + {content} - } + )} ); -} +}; diff --git a/ui/v2.5/src/components/Shared/Icon.tsx b/ui/v2.5/src/components/Shared/Icon.tsx index f59238c54..5ce5bfe3f 100644 --- a/ui/v2.5/src/components/Shared/Icon.tsx +++ b/ui/v2.5/src/components/Shared/Icon.tsx @@ -1,6 +1,6 @@ -import React from 'react'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { IconName } from '@fortawesome/fontawesome-svg-core'; +import React from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { IconName } from "@fortawesome/fontawesome-svg-core"; interface IIcon { icon: IconName; diff --git a/ui/v2.5/src/components/Shared/Modal.tsx b/ui/v2.5/src/components/Shared/Modal.tsx index f90939b20..5fc9e2252 100644 --- a/ui/v2.5/src/components/Shared/Modal.tsx +++ b/ui/v2.5/src/components/Shared/Modal.tsx @@ -1,11 +1,11 @@ import React from "react"; -import { Button, Modal } from 'react-bootstrap'; -import { Icon } from 'src/components/Shared'; -import { IconName } from '@fortawesome/fontawesome-svg-core'; +import { Button, Modal } from "react-bootstrap"; +import { Icon } from "src/components/Shared"; +import { IconName } from "@fortawesome/fontawesome-svg-core"; interface IButton { text?: string; - variant?: 'danger'|'primary'; + variant?: "danger" | "primary"; onClick?: () => void; } @@ -18,27 +18,42 @@ interface IModal { accept?: IButton; } -const ModalComponent: React.FC = ({ children, show, icon, header, cancel, accept, onHide }) => (( - +const ModalComponent: React.FC = ({ + children, + show, + icon, + header, + cancel, + accept, + onHide +}) => ( + - { icon ? : '' } - { header ?? '' } + {icon ? : ""} + {header ?? ""} {children}
- { cancel - ? - : '' - } - + {cancel ? ( + + ) : ( + "" + )} +
-)); +); export default ModalComponent; diff --git a/ui/v2.5/src/components/Shared/Select.tsx b/ui/v2.5/src/components/Shared/Select.tsx index 9dd474ac7..b5fb3904a 100644 --- a/ui/v2.5/src/components/Shared/Select.tsx +++ b/ui/v2.5/src/components/Shared/Select.tsx @@ -1,20 +1,20 @@ import React, { useState, useCallback } from "react"; -import Select, { ValueType } from 'react-select'; -import CreatableSelect from 'react-select/creatable'; -import { debounce } from 'lodash'; +import Select, { ValueType } from "react-select"; +import CreatableSelect from "react-select/creatable"; +import { debounce } from "lodash"; import * as GQL from "src/core/generated-graphql"; import { StashService } from "src/core/StashService"; -import { useToast } from 'src/hooks'; +import { useToast } from "src/hooks"; type ValidTypes = - GQL.AllPerformersForFilterAllPerformers | - GQL.AllTagsForFilterAllTags | - GQL.AllStudiosForFilterAllStudios; -type Option = { value:string, label:string }; + | GQL.AllPerformersForFilterAllPerformers + | GQL.AllTagsForFilterAllTags + | GQL.AllStudiosForFilterAllStudios; +type Option = { value: string; label: string }; interface ITypeProps { - type?: 'performers' | 'studios' | 'tags'; + type?: "performers" | "studios" | "tags"; } interface IFilterProps { initialIds: string[]; @@ -40,131 +40,221 @@ interface ISelectProps { interface ISceneGallerySelect { initialId?: string; sceneId: string; - onSelect: (item: GQL.ValidGalleriesForSceneValidGalleriesForScene | undefined) => void; + onSelect: ( + item: GQL.ValidGalleriesForSceneValidGalleriesForScene | undefined + ) => void; } -const getSelectedValues = (selectedItems:ValueType