mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 12:24:38 +03:00
React code splitting (#2603)
* Code split using react lazy * Split locales * Move to lodash-es * Import individual icons
This commit is contained in:
@@ -48,7 +48,7 @@
|
||||
"i18n-iso-countries": "^6.4.0",
|
||||
"intersection-observer": "^0.12.0",
|
||||
"localforage": "1.9.0",
|
||||
"lodash": "^4.17.20",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mousetrap": "^1.6.5",
|
||||
"mousetrap-pause": "^1.0.0",
|
||||
"normalize-url": "^4.5.1",
|
||||
@@ -92,7 +92,7 @@
|
||||
"@types/apollo-upload-client": "^14.1.0",
|
||||
"@types/classnames": "^2.2.11",
|
||||
"@types/fslightbox-react": "^1.4.0",
|
||||
"@types/lodash": "^4.14.168",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/mousetrap": "^1.6.5",
|
||||
"@types/node": "14.14.22",
|
||||
"@types/react": "17.0.31",
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import React, { useEffect } from "react";
|
||||
import React, { lazy, Suspense, useEffect, useState } from "react";
|
||||
import { Route, Switch, useRouteMatch } from "react-router-dom";
|
||||
import { IntlProvider, CustomFormats } from "react-intl";
|
||||
import { Helmet } from "react-helmet";
|
||||
import { mergeWith } from "lodash";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
import mergeWith from "lodash-es/mergeWith";
|
||||
import { ToastProvider } from "src/hooks/Toast";
|
||||
import LightboxProvider from "src/hooks/Lightbox/context";
|
||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
import { fas } from "@fortawesome/free-solid-svg-icons";
|
||||
import { initPolyfills } from "src/polyfills";
|
||||
|
||||
import locales from "src/locales";
|
||||
@@ -15,41 +14,48 @@ import { flattenMessages } from "src/utils";
|
||||
import Mousetrap from "mousetrap";
|
||||
import MousetrapPause from "mousetrap-pause";
|
||||
import { ErrorBoundary } from "./components/ErrorBoundary";
|
||||
import Galleries from "./components/Galleries/Galleries";
|
||||
import { MainNavbar } from "./components/MainNavbar";
|
||||
import { PageNotFound } from "./components/PageNotFound";
|
||||
import Performers from "./components/Performers/Performers";
|
||||
import FrontPage from "./components/FrontPage/FrontPage";
|
||||
import Scenes from "./components/Scenes/Scenes";
|
||||
import { Settings } from "./components/Settings/Settings";
|
||||
import { Stats } from "./components/Stats";
|
||||
import Studios from "./components/Studios/Studios";
|
||||
import { SceneFilenameParser } from "./components/SceneFilenameParser/SceneFilenameParser";
|
||||
import { SceneDuplicateChecker } from "./components/SceneDuplicateChecker/SceneDuplicateChecker";
|
||||
import Movies from "./components/Movies/Movies";
|
||||
import Tags from "./components/Tags/Tags";
|
||||
import Images from "./components/Images/Images";
|
||||
import { Setup } from "./components/Setup/Setup";
|
||||
import { Migrate } from "./components/Setup/Migrate";
|
||||
import * as GQL from "./core/generated-graphql";
|
||||
import { LoadingIndicator, TITLE_SUFFIX } from "./components/Shared";
|
||||
|
||||
import { ConfigurationProvider } from "./hooks/Config";
|
||||
import { ManualProvider } from "./components/Help/Manual";
|
||||
import { ManualProvider } from "./components/Help/context";
|
||||
import { InteractiveProvider } from "./hooks/Interactive/context";
|
||||
|
||||
const Performers = lazy(() => import("./components/Performers/Performers"));
|
||||
const FrontPage = lazy(() => import("./components/FrontPage/FrontPage"));
|
||||
const Scenes = lazy(() => import("./components/Scenes/Scenes"));
|
||||
const Settings = lazy(() => import("./components/Settings/Settings"));
|
||||
const Stats = lazy(() => import("./components/Stats"));
|
||||
const Studios = lazy(() => import("./components/Studios/Studios"));
|
||||
const Galleries = lazy(() => import("./components/Galleries/Galleries"));
|
||||
|
||||
const Movies = lazy(() => import("./components/Movies/Movies"));
|
||||
const Tags = lazy(() => import("./components/Tags/Tags"));
|
||||
const Images = lazy(() => import("./components/Images/Images"));
|
||||
const Setup = lazy(() => import("./components/Setup/Setup"));
|
||||
const Migrate = lazy(() => import("./components/Setup/Migrate"));
|
||||
|
||||
const SceneFilenameParser = lazy(
|
||||
() => import("./components/SceneFilenameParser/SceneFilenameParser")
|
||||
);
|
||||
const SceneDuplicateChecker = lazy(
|
||||
() => import("./components/SceneDuplicateChecker/SceneDuplicateChecker")
|
||||
);
|
||||
|
||||
initPolyfills();
|
||||
|
||||
MousetrapPause(Mousetrap);
|
||||
|
||||
// Set fontawesome/free-solid-svg as default fontawesome icons
|
||||
library.add(fas);
|
||||
|
||||
const intlFormats: CustomFormats = {
|
||||
date: {
|
||||
long: { year: "numeric", month: "long", day: "numeric" },
|
||||
},
|
||||
};
|
||||
|
||||
const defaultLocale = "en-GB";
|
||||
|
||||
function languageMessageString(language: string) {
|
||||
return language.replace(/-/, "");
|
||||
}
|
||||
@@ -57,25 +63,32 @@ function languageMessageString(language: string) {
|
||||
export const App: React.FC = () => {
|
||||
const config = useConfiguration();
|
||||
const { data: systemStatusData } = useSystemStatus();
|
||||
const defaultLocale = "en-GB";
|
||||
|
||||
const language =
|
||||
config.data?.configuration?.interface?.language ?? defaultLocale;
|
||||
const defaultMessageLanguage = languageMessageString(defaultLocale);
|
||||
const messageLanguage = languageMessageString(language);
|
||||
|
||||
// use en-GB as default messages if any messages aren't found in the chosen language
|
||||
const mergedMessages = mergeWith(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(locales as any)[defaultMessageLanguage],
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(locales as any)[messageLanguage],
|
||||
(objVal, srcVal) => {
|
||||
if (srcVal === "") {
|
||||
return objVal;
|
||||
}
|
||||
}
|
||||
);
|
||||
const messages = flattenMessages(mergedMessages);
|
||||
const [messages, setMessages] = useState<{}>();
|
||||
|
||||
useEffect(() => {
|
||||
const setLocale = async () => {
|
||||
const defaultMessageLanguage = languageMessageString(defaultLocale);
|
||||
const messageLanguage = languageMessageString(language);
|
||||
|
||||
const defaultMessages = await locales[defaultMessageLanguage]();
|
||||
const mergedMessages = cloneDeep(Object.assign({}, defaultMessages));
|
||||
const chosenMessages = await locales[messageLanguage]();
|
||||
mergeWith(mergedMessages, chosenMessages, (objVal, srcVal) => {
|
||||
if (srcVal === "") {
|
||||
return objVal;
|
||||
}
|
||||
});
|
||||
|
||||
setMessages(flattenMessages(mergedMessages));
|
||||
};
|
||||
|
||||
setLocale();
|
||||
}, [language]);
|
||||
|
||||
const setupMatch = useRouteMatch(["/setup", "/migrate"]);
|
||||
|
||||
@@ -118,52 +131,64 @@ export const App: React.FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact path="/" component={FrontPage} />
|
||||
<Route path="/scenes" component={Scenes} />
|
||||
<Route path="/images" component={Images} />
|
||||
<Route path="/galleries" component={Galleries} />
|
||||
<Route path="/performers" component={Performers} />
|
||||
<Route path="/tags" component={Tags} />
|
||||
<Route path="/studios" component={Studios} />
|
||||
<Route path="/movies" component={Movies} />
|
||||
<Route path="/stats" component={Stats} />
|
||||
<Route path="/settings" component={Settings} />
|
||||
<Route path="/sceneFilenameParser" component={SceneFilenameParser} />
|
||||
<Route
|
||||
path="/sceneDuplicateChecker"
|
||||
component={SceneDuplicateChecker}
|
||||
/>
|
||||
<Route path="/setup" component={Setup} />
|
||||
<Route path="/migrate" component={Migrate} />
|
||||
<Route component={PageNotFound} />
|
||||
</Switch>
|
||||
<Suspense fallback={<LoadingIndicator />}>
|
||||
<Switch>
|
||||
<Route exact path="/" component={FrontPage} />
|
||||
<Route path="/scenes" component={Scenes} />
|
||||
<Route path="/images" component={Images} />
|
||||
<Route path="/galleries" component={Galleries} />
|
||||
<Route path="/performers" component={Performers} />
|
||||
<Route path="/tags" component={Tags} />
|
||||
<Route path="/studios" component={Studios} />
|
||||
<Route path="/movies" component={Movies} />
|
||||
<Route path="/stats" component={Stats} />
|
||||
<Route path="/settings" component={Settings} />
|
||||
<Route path="/sceneFilenameParser" component={SceneFilenameParser} />
|
||||
<Route
|
||||
path="/sceneDuplicateChecker"
|
||||
component={SceneDuplicateChecker}
|
||||
/>
|
||||
<Route path="/setup" component={Setup} />
|
||||
<Route path="/migrate" component={Migrate} />
|
||||
<Route component={PageNotFound} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<IntlProvider locale={language} messages={messages} formats={intlFormats}>
|
||||
<ConfigurationProvider
|
||||
configuration={config.data?.configuration}
|
||||
loading={config.loading}
|
||||
{messages ? (
|
||||
<IntlProvider
|
||||
locale={language}
|
||||
messages={messages}
|
||||
formats={intlFormats}
|
||||
>
|
||||
<ToastProvider>
|
||||
<LightboxProvider>
|
||||
<ManualProvider>
|
||||
<InteractiveProvider>
|
||||
<Helmet
|
||||
titleTemplate={`%s ${TITLE_SUFFIX}`}
|
||||
defaultTitle="Stash"
|
||||
/>
|
||||
{maybeRenderNavbar()}
|
||||
<div className="main container-fluid">{renderContent()}</div>
|
||||
</InteractiveProvider>
|
||||
</ManualProvider>
|
||||
</LightboxProvider>
|
||||
</ToastProvider>
|
||||
</ConfigurationProvider>
|
||||
</IntlProvider>
|
||||
<ConfigurationProvider
|
||||
configuration={config.data?.configuration}
|
||||
loading={config.loading}
|
||||
>
|
||||
<ToastProvider>
|
||||
<Suspense fallback={<LoadingIndicator />}>
|
||||
<LightboxProvider>
|
||||
<ManualProvider>
|
||||
<InteractiveProvider>
|
||||
<Helmet
|
||||
titleTemplate={`%s ${TITLE_SUFFIX}`}
|
||||
defaultTitle="Stash"
|
||||
/>
|
||||
{maybeRenderNavbar()}
|
||||
<div className="main container-fluid">
|
||||
{renderContent()}
|
||||
</div>
|
||||
</InteractiveProvider>
|
||||
</ManualProvider>
|
||||
</LightboxProvider>
|
||||
</Suspense>
|
||||
</ToastProvider>
|
||||
</ConfigurationProvider>
|
||||
</IntlProvider>
|
||||
) : null}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { faAngleDown, faAngleUp } from "@fortawesome/free-solid-svg-icons";
|
||||
import React, { useState } from "react";
|
||||
import { Button, Card, Collapse } from "react-bootstrap";
|
||||
import { FormattedDate, FormattedMessage } from "react-intl";
|
||||
@@ -33,7 +34,7 @@ const Version: React.FC<IVersionProps> = ({
|
||||
<Card.Header>
|
||||
<h4 className="changelog-version-header d-flex align-items-center">
|
||||
<Button onClick={updateState} variant="link">
|
||||
<Icon icon={open ? "angle-up" : "angle-down"} className="mr-3" />
|
||||
<Icon icon={open ? faAngleUp : faAngleDown} className="mr-3" />
|
||||
{version} (
|
||||
{date ? (
|
||||
<FormattedDate value={date} timeZone="utc" />
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Manual } from "../Help/Manual";
|
||||
import { withoutTypename } from "src/utils";
|
||||
import { GenerateOptions } from "../Settings/Tasks/GenerateOptions";
|
||||
import { SettingSection } from "../Settings/SettingSection";
|
||||
import { faCogs, faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface ISceneGenerateDialog {
|
||||
selectedIds?: string[];
|
||||
@@ -171,7 +172,7 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
|
||||
<Modal
|
||||
show
|
||||
modalProps={{ animation, size: "lg" }}
|
||||
icon="cogs"
|
||||
icon={faCogs}
|
||||
header={intl.formatMessage({ id: "actions.generate" })}
|
||||
accept={{
|
||||
onClick: onGenerate,
|
||||
@@ -188,7 +189,7 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
|
||||
className="minimal help-button"
|
||||
onClick={() => onShowManual()}
|
||||
>
|
||||
<Icon icon="question-circle" />
|
||||
<Icon icon={faQuestionCircle} />
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
@@ -205,3 +206,5 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default GenerateDialog;
|
||||
|
||||
@@ -5,6 +5,11 @@ import * as GQL from "src/core/generated-graphql";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { multiValueSceneFields, SceneField, sceneFields } from "./constants";
|
||||
import { ThreeStateBoolean } from "./ThreeStateBoolean";
|
||||
import {
|
||||
faCheck,
|
||||
faPencilAlt,
|
||||
faTimes,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IFieldOptionsEditor {
|
||||
options: GQL.IdentifyFieldOptions | undefined;
|
||||
@@ -148,10 +153,10 @@ const FieldOptionsEditor: React.FC<IFieldOptionsEditor> = ({
|
||||
return intl.formatMessage({ id: "actions.use_default" });
|
||||
}
|
||||
if (value) {
|
||||
return <Icon icon="check" className="text-success" />;
|
||||
return <Icon icon={faCheck} className="text-success" />;
|
||||
}
|
||||
|
||||
return <Icon icon="times" className="text-danger" />;
|
||||
return <Icon icon={faTimes} className="text-danger" />;
|
||||
}
|
||||
|
||||
const defaultVal = defaultOptions?.fieldOptions?.find(
|
||||
@@ -212,7 +217,7 @@ const FieldOptionsEditor: React.FC<IFieldOptionsEditor> = ({
|
||||
className="minimal text-success"
|
||||
onClick={() => onEditOptions()}
|
||||
>
|
||||
<Icon icon="check" />
|
||||
<Icon icon={faCheck} />
|
||||
</Button>
|
||||
<Button
|
||||
className="minimal text-danger"
|
||||
@@ -221,13 +226,13 @@ const FieldOptionsEditor: React.FC<IFieldOptionsEditor> = ({
|
||||
resetOptions();
|
||||
}}
|
||||
>
|
||||
<Icon icon="times" />
|
||||
<Icon icon={faTimes} />
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button className="minimal" onClick={() => editField()}>
|
||||
<Icon icon="pencil-alt" />
|
||||
<Icon icon={faPencilAlt} />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -20,6 +20,11 @@ import { Manual } from "src/components/Help/Manual";
|
||||
import { IScraperSource } from "./constants";
|
||||
import { OptionsEditor } from "./Options";
|
||||
import { SourcesEditor, SourcesList } from "./Sources";
|
||||
import {
|
||||
faCogs,
|
||||
faFolderOpen,
|
||||
faQuestionCircle,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
const autoTagScraperID = "builtin_autotag";
|
||||
|
||||
@@ -167,7 +172,7 @@ export const IdentifyDialog: React.FC<IIdentifyDialogProps> = ({
|
||||
title={intl.formatMessage({ id: "actions.select_folders" })}
|
||||
onClick={() => onClick()}
|
||||
>
|
||||
<Icon icon="folder-open" />
|
||||
<Icon icon={faFolderOpen} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -403,7 +408,7 @@ export const IdentifyDialog: React.FC<IIdentifyDialogProps> = ({
|
||||
<Modal
|
||||
modalProps={{ animation, size: "lg" }}
|
||||
show
|
||||
icon="cogs"
|
||||
icon={faCogs}
|
||||
header={intl.formatMessage({ id: "actions.identify" })}
|
||||
accept={{
|
||||
onClick: onIdentify,
|
||||
@@ -430,7 +435,7 @@ export const IdentifyDialog: React.FC<IIdentifyDialogProps> = ({
|
||||
className="minimal help-button"
|
||||
onClick={() => onShowManual()}
|
||||
>
|
||||
<Icon icon="question-circle" />
|
||||
<Icon icon={faQuestionCircle} />
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -5,6 +5,13 @@ import { FormattedMessage, useIntl } from "react-intl";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { IScraperSource } from "./constants";
|
||||
import { OptionsEditor } from "./Options";
|
||||
import {
|
||||
faCog,
|
||||
faGripVertical,
|
||||
faMinus,
|
||||
faPencilAlt,
|
||||
faPlus,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface ISourceEditor {
|
||||
isNew: boolean;
|
||||
@@ -50,7 +57,7 @@ export const SourcesEditor: React.FC<ISourceEditor> = ({
|
||||
dialogClassName="identify-source-editor"
|
||||
modalProps={{ animation: false, size: "lg" }}
|
||||
show
|
||||
icon={isNew ? "plus" : "pencil-alt"}
|
||||
icon={isNew ? faPlus : faPencilAlt}
|
||||
header={intl.formatMessage(
|
||||
{ id: headerMsgId },
|
||||
{
|
||||
@@ -184,19 +191,19 @@ export const SourcesList: React.FC<ISourcesList> = ({
|
||||
onMouseEnter={() => setMouseOverIndex(index)}
|
||||
onMouseLeave={() => setMouseOverIndex(undefined)}
|
||||
>
|
||||
<Icon icon="grip-vertical" />
|
||||
<Icon icon={faGripVertical} />
|
||||
</div>
|
||||
{s.displayName}
|
||||
</div>
|
||||
<div>
|
||||
<Button className="minimal" onClick={() => editSource(s)}>
|
||||
<Icon icon="cog" />
|
||||
<Icon icon={faCog} />
|
||||
</Button>
|
||||
<Button
|
||||
className="minimal text-danger"
|
||||
onClick={() => removeSource(index)}
|
||||
>
|
||||
<Icon icon="minus" />
|
||||
<Icon icon={faMinus} />
|
||||
</Button>
|
||||
</div>
|
||||
</ListGroup.Item>
|
||||
@@ -208,7 +215,7 @@ export const SourcesList: React.FC<ISourcesList> = ({
|
||||
className="minimal add-scraper-source-button"
|
||||
onClick={() => editSource()}
|
||||
>
|
||||
<Icon icon="plus" />
|
||||
<Icon icon={faPlus} />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -5,6 +5,7 @@ import * as GQL from "src/core/generated-graphql";
|
||||
import { Modal } from "src/components/Shared";
|
||||
import { getStashboxBase } from "src/utils";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { faPaperPlane } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IProps {
|
||||
show: boolean;
|
||||
@@ -72,7 +73,7 @@ export const SubmitStashBoxDraft: React.FC<IProps> = ({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
icon="paper-plane"
|
||||
icon={faPaperPlane}
|
||||
header={intl.formatMessage({ id: "actions.submit_stash_box" })}
|
||||
isRunning={loading}
|
||||
show={show}
|
||||
@@ -149,3 +150,5 @@ export const SubmitStashBoxDraft: React.FC<IProps> = ({
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default SubmitStashBoxDraft;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Modal } from "src/components/Shared";
|
||||
import { useToast } from "src/hooks";
|
||||
import { ConfigurationContext } from "src/hooks/Config";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { faTrashAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IDeleteGalleryDialogProps {
|
||||
selected: GQL.SlimGalleryDataFragment[];
|
||||
@@ -114,7 +115,7 @@ export const DeleteGalleriesDialog: React.FC<IDeleteGalleryDialogProps> = (
|
||||
return (
|
||||
<Modal
|
||||
show
|
||||
icon="trash-alt"
|
||||
icon={faTrashAlt}
|
||||
header={header}
|
||||
accept={{
|
||||
variant: "danger",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Form, Col, Row } from "react-bootstrap";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import _ from "lodash";
|
||||
import isEqual from "lodash-es/isEqual";
|
||||
import { useBulkGalleryUpdate } from "src/core/StashService";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StudioSelect, Modal } from "src/components/Shared";
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
getAggregateStudioId,
|
||||
getAggregateTagIds,
|
||||
} from "src/utils/bulkUpdate";
|
||||
import { faPencilAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IListOperationProps {
|
||||
selected: GQL.SlimGalleryDataFragment[];
|
||||
@@ -141,10 +142,10 @@ export const EditGalleriesDialog: React.FC<IListOperationProps> = (
|
||||
if (GalleriestudioID !== updateStudioID) {
|
||||
updateStudioID = undefined;
|
||||
}
|
||||
if (!_.isEqual(galleryPerformerIDs, updatePerformerIds)) {
|
||||
if (!isEqual(galleryPerformerIDs, updatePerformerIds)) {
|
||||
updatePerformerIds = [];
|
||||
}
|
||||
if (!_.isEqual(galleryTagIDs, updateTagIds)) {
|
||||
if (!isEqual(galleryTagIDs, updateTagIds)) {
|
||||
updateTagIds = [];
|
||||
}
|
||||
if (gallery.organized !== updateOrganized) {
|
||||
@@ -229,7 +230,7 @@ export const EditGalleriesDialog: React.FC<IListOperationProps> = (
|
||||
return (
|
||||
<Modal
|
||||
show
|
||||
icon="pencil-alt"
|
||||
icon={faPencilAlt}
|
||||
header={intl.formatMessage(
|
||||
{ id: "dialogs.edit_entity_title" },
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@ import { NavUtils, TextUtils } from "src/utils";
|
||||
import { ConfigurationContext } from "src/hooks/Config";
|
||||
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
||||
import { RatingBanner } from "../Shared/RatingBanner";
|
||||
import { faBox, faPlayCircle, faTag } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IProps {
|
||||
gallery: GQL.SlimGalleryDataFragment;
|
||||
@@ -41,7 +42,7 @@ export const GalleryCard: React.FC<IProps> = (props) => {
|
||||
content={popoverContent}
|
||||
>
|
||||
<Button className="minimal">
|
||||
<Icon icon="play-circle" />
|
||||
<Icon icon={faPlayCircle} />
|
||||
<span>{props.gallery.scenes.length}</span>
|
||||
</Button>
|
||||
</HoverPopover>
|
||||
@@ -62,7 +63,7 @@ export const GalleryCard: React.FC<IProps> = (props) => {
|
||||
content={popoverContent}
|
||||
>
|
||||
<Button className="minimal">
|
||||
<Icon icon="tag" />
|
||||
<Icon icon={faTag} />
|
||||
<span>{props.gallery.tags.length}</span>
|
||||
</Button>
|
||||
</HoverPopover>
|
||||
@@ -113,7 +114,7 @@ export const GalleryCard: React.FC<IProps> = (props) => {
|
||||
return (
|
||||
<div className="organized">
|
||||
<Button className="minimal">
|
||||
<Icon icon="box" />
|
||||
<Icon icon={faBox} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -21,6 +21,7 @@ import { GalleryImagesPanel } from "./GalleryImagesPanel";
|
||||
import { GalleryAddPanel } from "./GalleryAddPanel";
|
||||
import { GalleryFileInfoPanel } from "./GalleryFileInfoPanel";
|
||||
import { GalleryScenesPanel } from "./GalleryScenesPanel";
|
||||
import { faEllipsisV } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IProps {
|
||||
gallery: GQL.GalleryDataFragment;
|
||||
@@ -116,7 +117,7 @@ export const GalleryPage: React.FC<IProps> = ({ gallery }) => {
|
||||
className="minimal"
|
||||
title={intl.formatMessage({ id: "operations" })}
|
||||
>
|
||||
<Icon icon="ellipsis-v" />
|
||||
<Icon icon={faEllipsisV} />
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="bg-secondary text-white">
|
||||
{gallery.path ? (
|
||||
|
||||
@@ -8,7 +8,7 @@ import { mutateAddGalleryImages } from "src/core/StashService";
|
||||
import { useToast } from "src/hooks";
|
||||
import { TextUtils } from "src/utils";
|
||||
import { useIntl } from "react-intl";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IGalleryAddProps {
|
||||
gallery: GQL.GalleryDataFragment;
|
||||
@@ -88,7 +88,7 @@ export const GalleryAddPanel: React.FC<IGalleryAddProps> = ({ gallery }) => {
|
||||
onClick: addImages,
|
||||
isDisplayed: showWhenSelected,
|
||||
postRefetch: true,
|
||||
icon: "plus" as IconProp,
|
||||
icon: faPlus,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import { useFormik } from "formik";
|
||||
import { FormUtils, TextUtils } from "src/utils";
|
||||
import { RatingStars } from "src/components/Scenes/SceneDetails/RatingStars";
|
||||
import { GalleryScrapeDialog } from "./GalleryScrapeDialog";
|
||||
import { faSyncAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IProps {
|
||||
isVisible: boolean;
|
||||
@@ -314,7 +315,7 @@ export const GalleryEditPanel: React.FC<
|
||||
))}
|
||||
<Dropdown.Item onClick={() => onReloadScrapers()}>
|
||||
<span className="fa-icon">
|
||||
<Icon icon="sync-alt" />
|
||||
<Icon icon={faSyncAlt} />
|
||||
</span>
|
||||
<span>
|
||||
<FormattedMessage id="actions.reload_scrapers" />
|
||||
|
||||
@@ -8,7 +8,7 @@ import { showWhenSelected, PersistanceLevel } from "src/hooks/ListHook";
|
||||
import { useToast } from "src/hooks";
|
||||
import { TextUtils } from "src/utils";
|
||||
import { useIntl } from "react-intl";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faMinus } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IGalleryDetailsProps {
|
||||
gallery: GQL.GalleryDataFragment;
|
||||
@@ -82,7 +82,7 @@ export const GalleryImagesPanel: React.FC<IGalleryDetailsProps> = ({
|
||||
onClick: removeImages,
|
||||
isDisplayed: showWhenSelected,
|
||||
postRefetch: true,
|
||||
icon: "minus" as IconProp,
|
||||
icon: faMinus,
|
||||
buttonVariant: "danger",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
ScrapedInputGroupRow,
|
||||
ScrapedTextAreaRow,
|
||||
} from "src/components/Shared/ScrapeDialog";
|
||||
import _ from "lodash";
|
||||
import clone from "lodash-es/clone";
|
||||
import {
|
||||
useStudioCreate,
|
||||
usePerformerCreate,
|
||||
@@ -235,7 +235,7 @@ export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = (
|
||||
return;
|
||||
}
|
||||
|
||||
const ret = _.clone(idList);
|
||||
const ret = clone(idList);
|
||||
// sort by id numerically
|
||||
ret.sort((a, b) => {
|
||||
return parseInt(a, 10) - parseInt(b, 10);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import _ from "lodash";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
import { Table } from "react-bootstrap";
|
||||
import { Link, useHistory } from "react-router-dom";
|
||||
import Mousetrap from "mousetrap";
|
||||
@@ -84,7 +84,7 @@ export const GalleryList: React.FC<IGalleryList> = ({
|
||||
const { count } = result.data.findGalleries;
|
||||
|
||||
const index = Math.floor(Math.random() * count);
|
||||
const filterCopy = _.cloneDeep(filter);
|
||||
const filterCopy = cloneDeep(filter);
|
||||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await queryFindGalleries(filterCopy);
|
||||
|
||||
@@ -38,3 +38,5 @@ export const GalleryViewer: React.FC<IProps> = ({ galleryId }) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GalleryViewer;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, PropsWithChildren, useEffect } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Modal, Container, Row, Col, Nav, Tab } from "react-bootstrap";
|
||||
import Introduction from "src/docs/en/Introduction.md";
|
||||
import Tasks from "src/docs/en/Tasks.md";
|
||||
@@ -239,62 +239,4 @@ export const Manual: React.FC<IManualProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
interface IManualContextState {
|
||||
openManual: (tab?: string) => void;
|
||||
}
|
||||
|
||||
export const ManualStateContext = React.createContext<IManualContextState>({
|
||||
openManual: () => {},
|
||||
});
|
||||
|
||||
export const ManualProvider: React.FC = ({ children }) => {
|
||||
const [showManual, setShowManual] = useState(false);
|
||||
const [manualLink, setManualLink] = useState<string | undefined>();
|
||||
|
||||
function openManual(tab?: string) {
|
||||
setManualLink(tab);
|
||||
setShowManual(true);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (manualLink) setManualLink(undefined);
|
||||
}, [manualLink]);
|
||||
|
||||
return (
|
||||
<ManualStateContext.Provider
|
||||
value={{
|
||||
openManual,
|
||||
}}
|
||||
>
|
||||
<Manual
|
||||
show={showManual}
|
||||
onClose={() => setShowManual(false)}
|
||||
defaultActiveTab={manualLink}
|
||||
/>
|
||||
{children}
|
||||
</ManualStateContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
interface IManualLink {
|
||||
tab: string;
|
||||
}
|
||||
|
||||
export const ManualLink: React.FC<PropsWithChildren<IManualLink>> = ({
|
||||
tab,
|
||||
children,
|
||||
}) => {
|
||||
const { openManual } = React.useContext(ManualStateContext);
|
||||
|
||||
return (
|
||||
<a
|
||||
href={`/help/${tab}.md`}
|
||||
onClick={(e) => {
|
||||
openManual(`${tab}.md`);
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
export default Manual;
|
||||
|
||||
73
ui/v2.5/src/components/Help/context.tsx
Normal file
73
ui/v2.5/src/components/Help/context.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import React, {
|
||||
lazy,
|
||||
PropsWithChildren,
|
||||
Suspense,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
const Manual = lazy(() => import("./Manual"));
|
||||
|
||||
interface IManualContextState {
|
||||
openManual: (tab?: string) => void;
|
||||
}
|
||||
|
||||
export const ManualStateContext = React.createContext<IManualContextState>({
|
||||
openManual: () => {},
|
||||
});
|
||||
|
||||
export const ManualProvider: React.FC = ({ children }) => {
|
||||
const [showManual, setShowManual] = useState(false);
|
||||
const [manualLink, setManualLink] = useState<string | undefined>();
|
||||
|
||||
function openManual(tab?: string) {
|
||||
setManualLink(tab);
|
||||
setShowManual(true);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (manualLink) setManualLink(undefined);
|
||||
}, [manualLink]);
|
||||
|
||||
return (
|
||||
<ManualStateContext.Provider
|
||||
value={{
|
||||
openManual,
|
||||
}}
|
||||
>
|
||||
<Suspense fallback={<></>}>
|
||||
{showManual && (
|
||||
<Manual
|
||||
show={showManual}
|
||||
onClose={() => setShowManual(false)}
|
||||
defaultActiveTab={manualLink}
|
||||
/>
|
||||
)}
|
||||
</Suspense>
|
||||
{children}
|
||||
</ManualStateContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
interface IManualLink {
|
||||
tab: string;
|
||||
}
|
||||
|
||||
export const ManualLink: React.FC<PropsWithChildren<IManualLink>> = ({
|
||||
tab,
|
||||
children,
|
||||
}) => {
|
||||
const { openManual } = React.useContext(ManualStateContext);
|
||||
|
||||
return (
|
||||
<a
|
||||
href={`/help/${tab}.md`}
|
||||
onClick={(e) => {
|
||||
openManual(`${tab}.md`);
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
@@ -6,6 +6,7 @@ import { Modal } from "src/components/Shared";
|
||||
import { useToast } from "src/hooks";
|
||||
import { ConfigurationContext } from "src/hooks/Config";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { faTrashAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IDeleteImageDialogProps {
|
||||
selected: GQL.SlimImageDataFragment[];
|
||||
@@ -106,7 +107,7 @@ export const DeleteImagesDialog: React.FC<IDeleteImageDialogProps> = (
|
||||
return (
|
||||
<Modal
|
||||
show
|
||||
icon="trash-alt"
|
||||
icon={faTrashAlt}
|
||||
header={header}
|
||||
accept={{
|
||||
variant: "danger",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Form, Col, Row } from "react-bootstrap";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import _ from "lodash";
|
||||
import isEqual from "lodash-es/isEqual";
|
||||
import { useBulkImageUpdate } from "src/core/StashService";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StudioSelect, Modal } from "src/components/Shared";
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
getAggregateStudioId,
|
||||
getAggregateTagIds,
|
||||
} from "src/utils/bulkUpdate";
|
||||
import { faPencilAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IListOperationProps {
|
||||
selected: GQL.SlimImageDataFragment[];
|
||||
@@ -132,10 +133,10 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
|
||||
if (imageStudioID !== updateStudioID) {
|
||||
updateStudioID = undefined;
|
||||
}
|
||||
if (!_.isEqual(imagePerformerIDs, updatePerformerIds)) {
|
||||
if (!isEqual(imagePerformerIDs, updatePerformerIds)) {
|
||||
updatePerformerIds = [];
|
||||
}
|
||||
if (!_.isEqual(imageTagIDs, updateTagIds)) {
|
||||
if (!isEqual(imageTagIDs, updateTagIds)) {
|
||||
updateTagIds = [];
|
||||
}
|
||||
if (image.organized !== updateOrganized) {
|
||||
@@ -219,7 +220,7 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
|
||||
return (
|
||||
<Modal
|
||||
show
|
||||
icon="pencil-alt"
|
||||
icon={faPencilAlt}
|
||||
header={intl.formatMessage(
|
||||
{ id: "dialogs.edit_entity_title" },
|
||||
{
|
||||
|
||||
@@ -7,6 +7,12 @@ import { TextUtils } from "src/utils";
|
||||
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
||||
import { GridCard } from "../Shared/GridCard";
|
||||
import { RatingBanner } from "../Shared/RatingBanner";
|
||||
import {
|
||||
faBox,
|
||||
faImages,
|
||||
faSearch,
|
||||
faTag,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IImageCardProps {
|
||||
image: GQL.SlimImageDataFragment;
|
||||
@@ -34,7 +40,7 @@ export const ImageCard: React.FC<IImageCardProps> = (
|
||||
content={popoverContent}
|
||||
>
|
||||
<Button className="minimal">
|
||||
<Icon icon="tag" />
|
||||
<Icon icon={faTag} />
|
||||
<span>{props.image.tags.length}</span>
|
||||
</Button>
|
||||
</HoverPopover>
|
||||
@@ -76,7 +82,7 @@ export const ImageCard: React.FC<IImageCardProps> = (
|
||||
content={popoverContent}
|
||||
>
|
||||
<Button className="minimal">
|
||||
<Icon icon="images" />
|
||||
<Icon icon={faImages} />
|
||||
<span>{props.image.galleries.length}</span>
|
||||
</Button>
|
||||
</HoverPopover>
|
||||
@@ -88,7 +94,7 @@ export const ImageCard: React.FC<IImageCardProps> = (
|
||||
return (
|
||||
<div className="organized">
|
||||
<Button className="minimal">
|
||||
<Icon icon="box" />
|
||||
<Icon icon={faBox} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
@@ -146,7 +152,7 @@ export const ImageCard: React.FC<IImageCardProps> = (
|
||||
{props.onPreview ? (
|
||||
<div className="preview-button">
|
||||
<Button onClick={props.onPreview}>
|
||||
<Icon icon="search" />
|
||||
<Icon icon={faSearch} />
|
||||
</Button>
|
||||
</div>
|
||||
) : undefined}
|
||||
|
||||
@@ -21,6 +21,7 @@ import { ImageFileInfoPanel } from "./ImageFileInfoPanel";
|
||||
import { ImageEditPanel } from "./ImageEditPanel";
|
||||
import { ImageDetailPanel } from "./ImageDetailPanel";
|
||||
import { DeleteImagesDialog } from "../DeleteImagesDialog";
|
||||
import { faEllipsisV } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IImageParams {
|
||||
id?: string;
|
||||
@@ -132,7 +133,7 @@ export const Image: React.FC = () => {
|
||||
className="minimal"
|
||||
title="Operations"
|
||||
>
|
||||
<Icon icon="ellipsis-v" />
|
||||
<Icon icon={faEllipsisV} />
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="bg-secondary text-white">
|
||||
<Dropdown.Item
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useState, useMemo, MouseEvent } from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import _ from "lodash";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import Mousetrap from "mousetrap";
|
||||
import {
|
||||
@@ -253,7 +253,7 @@ export const ImageList: React.FC<IImageList> = ({
|
||||
const { count } = result.data.findImages;
|
||||
|
||||
const index = Math.floor(Math.random() * count);
|
||||
const filterCopy = _.cloneDeep(filter);
|
||||
const filterCopy = cloneDeep(filter);
|
||||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await queryFindImages(filterCopy);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import _ from "lodash";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Button, Form, Modal } from "react-bootstrap";
|
||||
import { CriterionModifier } from "src/core/generated-graphql";
|
||||
@@ -80,13 +80,13 @@ export const AddFilterDialog: React.FC<IAddFilterProps> = ({
|
||||
function onChangedModifierSelect(
|
||||
event: React.ChangeEvent<HTMLSelectElement>
|
||||
) {
|
||||
const newCriterion = _.cloneDeep(criterion);
|
||||
const newCriterion = cloneDeep(criterion);
|
||||
newCriterion.modifier = event.target.value as CriterionModifier;
|
||||
setCriterion(newCriterion);
|
||||
}
|
||||
|
||||
function onValueChanged(value: CriterionValue) {
|
||||
const newCriterion = _.cloneDeep(criterion);
|
||||
const newCriterion = cloneDeep(criterion);
|
||||
newCriterion.value = value;
|
||||
setCriterion(newCriterion);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from "src/models/list-filter/criteria/criterion";
|
||||
import { useIntl } from "react-intl";
|
||||
import { Icon } from "../Shared";
|
||||
import { faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IFilterTagsProps {
|
||||
criteria: Criterion<CriterionValue>[];
|
||||
@@ -48,7 +49,7 @@ export const FilterTags: React.FC<IFilterTagsProps> = ({
|
||||
variant="secondary"
|
||||
onClick={($event) => onRemoveCriterionTag(criterion, $event)}
|
||||
>
|
||||
<Icon icon="times" />
|
||||
<Icon icon={faTimes} />
|
||||
</Button>
|
||||
</Badge>
|
||||
));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import _, { debounce } from "lodash";
|
||||
import debounce from "lodash-es/debounce";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
import React, { HTMLAttributes, useEffect, useRef, useState } from "react";
|
||||
import cx from "classnames";
|
||||
import Mousetrap from "mousetrap";
|
||||
@@ -23,6 +24,15 @@ import { ListFilterOptions } from "src/models/list-filter/filter-options";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { PersistanceLevel } from "src/hooks/ListHook";
|
||||
import { SavedFilterList } from "./SavedFilterList";
|
||||
import {
|
||||
faBookmark,
|
||||
faCaretDown,
|
||||
faCaretUp,
|
||||
faCheck,
|
||||
faFilter,
|
||||
faRandom,
|
||||
faTimes,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
const maxPageSize = 1000;
|
||||
interface IListFilterProps {
|
||||
@@ -53,7 +63,7 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
||||
const [perPageInput, perPageFocus] = useFocus();
|
||||
|
||||
const searchCallback = debounce((value: string) => {
|
||||
const newFilter = _.cloneDeep(filter);
|
||||
const newFilter = cloneDeep(filter);
|
||||
newFilter.searchTerm = value;
|
||||
newFilter.currentPage = 1;
|
||||
onFilterUpdate(newFilter);
|
||||
@@ -101,7 +111,7 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
||||
pp = maxPageSize;
|
||||
}
|
||||
|
||||
const newFilter = _.cloneDeep(filter);
|
||||
const newFilter = cloneDeep(filter);
|
||||
newFilter.itemsPerPage = pp;
|
||||
newFilter.currentPage = 1;
|
||||
onFilterUpdate(newFilter);
|
||||
@@ -120,7 +130,7 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
||||
}
|
||||
|
||||
function onChangeSortDirection() {
|
||||
const newFilter = _.cloneDeep(filter);
|
||||
const newFilter = cloneDeep(filter);
|
||||
if (filter.sortDirection === SortDirectionEnum.Asc) {
|
||||
newFilter.sortDirection = SortDirectionEnum.Desc;
|
||||
} else {
|
||||
@@ -131,14 +141,14 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
||||
}
|
||||
|
||||
function onChangeSortBy(eventKey: string | null) {
|
||||
const newFilter = _.cloneDeep(filter);
|
||||
const newFilter = cloneDeep(filter);
|
||||
newFilter.sortBy = eventKey ?? undefined;
|
||||
newFilter.currentPage = 1;
|
||||
onFilterUpdate(newFilter);
|
||||
}
|
||||
|
||||
function onReshuffleRandomSort() {
|
||||
const newFilter = _.cloneDeep(filter);
|
||||
const newFilter = cloneDeep(filter);
|
||||
newFilter.currentPage = 1;
|
||||
newFilter.randomSeed = -1;
|
||||
onFilterUpdate(newFilter);
|
||||
@@ -225,7 +235,7 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
||||
queryClearShowing ? "" : "d-none"
|
||||
)}
|
||||
>
|
||||
<Icon icon="times" />
|
||||
<Icon icon={faTimes} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -241,7 +251,7 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
||||
}
|
||||
>
|
||||
<Dropdown.Toggle variant="secondary">
|
||||
<Icon icon="bookmark" />
|
||||
<Icon icon={faBookmark} />
|
||||
</Dropdown.Toggle>
|
||||
</OverlayTrigger>
|
||||
<Dropdown.Menu
|
||||
@@ -262,7 +272,7 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
||||
onClick={() => openFilterDialog()}
|
||||
active={filterDialogOpen}
|
||||
>
|
||||
<Icon icon="filter" />
|
||||
<Icon icon={faFilter} />
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
</ButtonGroup>
|
||||
@@ -291,8 +301,8 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
||||
<Icon
|
||||
icon={
|
||||
filter.sortDirection === SortDirectionEnum.Asc
|
||||
? "caret-up"
|
||||
: "caret-down"
|
||||
? faCaretUp
|
||||
: faCaretDown
|
||||
}
|
||||
/>
|
||||
</Button>
|
||||
@@ -306,7 +316,7 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
||||
}
|
||||
>
|
||||
<Button variant="secondary" onClick={onReshuffleRandomSort}>
|
||||
<Icon icon="random" />
|
||||
<Icon icon={faRandom} />
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
)}
|
||||
@@ -362,7 +372,7 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
||||
)
|
||||
}
|
||||
>
|
||||
<Icon icon="check" />
|
||||
<Icon icon={faCheck} />
|
||||
</Button>
|
||||
</InputGroup.Append>
|
||||
</InputGroup>
|
||||
|
||||
@@ -8,14 +8,19 @@ import {
|
||||
} from "react-bootstrap";
|
||||
import Mousetrap from "mousetrap";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import { Icon } from "../Shared";
|
||||
import {
|
||||
faEllipsisH,
|
||||
faPencilAlt,
|
||||
faTrash,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IListFilterOperation {
|
||||
text: string;
|
||||
onClick: () => void;
|
||||
isDisplayed?: () => boolean;
|
||||
icon?: IconProp;
|
||||
icon?: IconDefinition;
|
||||
buttonVariant?: string;
|
||||
}
|
||||
|
||||
@@ -78,14 +83,14 @@ export const ListOperationButtons: React.FC<IListOperationButtonsProps> = ({
|
||||
if (itemsSelected) {
|
||||
if (onEdit) {
|
||||
buttons.push({
|
||||
icon: "pencil-alt",
|
||||
icon: faPencilAlt,
|
||||
text: intl.formatMessage({ id: "actions.edit" }),
|
||||
onClick: onEdit,
|
||||
});
|
||||
}
|
||||
if (onDelete) {
|
||||
buttons.push({
|
||||
icon: "trash",
|
||||
icon: faTrash,
|
||||
text: intl.formatMessage({ id: "actions.delete" }),
|
||||
onClick: onDelete,
|
||||
buttonVariant: "danger",
|
||||
@@ -106,7 +111,7 @@ export const ListOperationButtons: React.FC<IListOperationButtonsProps> = ({
|
||||
variant={button.buttonVariant ?? "secondary"}
|
||||
onClick={button.onClick}
|
||||
>
|
||||
<Icon icon={button.icon as IconProp} />
|
||||
{button.icon ? <Icon icon={button.icon} /> : undefined}
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
@@ -173,7 +178,7 @@ export const ListOperationButtons: React.FC<IListOperationButtonsProps> = ({
|
||||
return (
|
||||
<Dropdown className="mb-1">
|
||||
<Dropdown.Toggle variant="secondary" id="more-menu">
|
||||
<Icon icon="ellipsis-h" />
|
||||
<Icon icon={faEllipsisH} />
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="bg-secondary text-white">
|
||||
{options}
|
||||
|
||||
@@ -10,6 +10,12 @@ import {
|
||||
import { DisplayMode } from "src/models/list-filter/types";
|
||||
import { useIntl } from "react-intl";
|
||||
import { Icon } from "../Shared";
|
||||
import {
|
||||
faList,
|
||||
faSquare,
|
||||
faTags,
|
||||
faThLarge,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IListViewOptionsProps {
|
||||
zoomIndex?: number;
|
||||
@@ -71,13 +77,13 @@ export const ListViewOptions: React.FC<IListViewOptionsProps> = ({
|
||||
function getIcon(option: DisplayMode) {
|
||||
switch (option) {
|
||||
case DisplayMode.Grid:
|
||||
return "th-large";
|
||||
return faThLarge;
|
||||
case DisplayMode.List:
|
||||
return "list";
|
||||
return faList;
|
||||
case DisplayMode.Wall:
|
||||
return "square";
|
||||
return faSquare;
|
||||
case DisplayMode.Tagger:
|
||||
return "tags";
|
||||
return faTags;
|
||||
}
|
||||
}
|
||||
function getLabel(option: DisplayMode) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import { LoadingIndicator } from "src/components/Shared";
|
||||
import { PersistanceLevel } from "src/hooks/ListHook";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { Icon } from "../Shared";
|
||||
import { faSave, faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface ISavedFilterListProps {
|
||||
filter: ListFilterModel;
|
||||
@@ -191,7 +192,7 @@ export const SavedFilterList: React.FC<ISavedFilterListProps> = ({
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<Icon icon="save" />
|
||||
<Icon icon={faSave} />
|
||||
</Button>
|
||||
<Button
|
||||
className="delete-button"
|
||||
@@ -203,7 +204,7 @@ export const SavedFilterList: React.FC<ISavedFilterListProps> = ({
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<Icon icon="times" />
|
||||
<Icon icon={faTimes} />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
@@ -344,7 +345,7 @@ export const SavedFilterList: React.FC<ISavedFilterListProps> = ({
|
||||
onSaveFilter(filterName);
|
||||
}}
|
||||
>
|
||||
<Icon icon="save" />
|
||||
<Icon icon={faSave} />
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
</InputGroup.Append>
|
||||
|
||||
@@ -6,22 +6,38 @@ import {
|
||||
useIntl,
|
||||
} from "react-intl";
|
||||
import { Nav, Navbar, Button, Fade } from "react-bootstrap";
|
||||
import { IconName } from "@fortawesome/fontawesome-svg-core";
|
||||
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import { LinkContainer } from "react-router-bootstrap";
|
||||
import { Link, NavLink, useLocation, useHistory } from "react-router-dom";
|
||||
import Mousetrap from "mousetrap";
|
||||
|
||||
import { SessionUtils } from "src/utils";
|
||||
import { Icon } from "src/components/Shared";
|
||||
import Icon from "src/components/Shared/Icon";
|
||||
import { ConfigurationContext } from "src/hooks/Config";
|
||||
import { ManualStateContext } from "./Help/Manual";
|
||||
import { ManualStateContext } from "./Help/context";
|
||||
import { SettingsButton } from "./SettingsButton";
|
||||
import {
|
||||
faBars,
|
||||
faChartBar,
|
||||
faFilm,
|
||||
faHeart,
|
||||
faImage,
|
||||
faImages,
|
||||
faMapMarkerAlt,
|
||||
faPlayCircle,
|
||||
faQuestionCircle,
|
||||
faSignOutAlt,
|
||||
faTag,
|
||||
faTimes,
|
||||
faUser,
|
||||
faVideo,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IMenuItem {
|
||||
name: string;
|
||||
message: MessageDescriptor;
|
||||
href: string;
|
||||
icon: IconName;
|
||||
icon: IconDefinition;
|
||||
hotkey: string;
|
||||
userCreatable?: boolean;
|
||||
}
|
||||
@@ -77,21 +93,21 @@ const allMenuItems: IMenuItem[] = [
|
||||
name: "scenes",
|
||||
message: messages.scenes,
|
||||
href: "/scenes",
|
||||
icon: "play-circle",
|
||||
icon: faPlayCircle,
|
||||
hotkey: "g s",
|
||||
},
|
||||
{
|
||||
name: "images",
|
||||
message: messages.images,
|
||||
href: "/images",
|
||||
icon: "image",
|
||||
icon: faImage,
|
||||
hotkey: "g i",
|
||||
},
|
||||
{
|
||||
name: "movies",
|
||||
message: messages.movies,
|
||||
href: "/movies",
|
||||
icon: "film",
|
||||
icon: faFilm,
|
||||
hotkey: "g v",
|
||||
userCreatable: true,
|
||||
},
|
||||
@@ -99,14 +115,14 @@ const allMenuItems: IMenuItem[] = [
|
||||
name: "markers",
|
||||
message: messages.markers,
|
||||
href: "/scenes/markers",
|
||||
icon: "map-marker-alt",
|
||||
icon: faMapMarkerAlt,
|
||||
hotkey: "g k",
|
||||
},
|
||||
{
|
||||
name: "galleries",
|
||||
message: messages.galleries,
|
||||
href: "/galleries",
|
||||
icon: "images",
|
||||
icon: faImages,
|
||||
hotkey: "g l",
|
||||
userCreatable: true,
|
||||
},
|
||||
@@ -114,7 +130,7 @@ const allMenuItems: IMenuItem[] = [
|
||||
name: "performers",
|
||||
message: messages.performers,
|
||||
href: "/performers",
|
||||
icon: "user",
|
||||
icon: faUser,
|
||||
hotkey: "g p",
|
||||
userCreatable: true,
|
||||
},
|
||||
@@ -122,7 +138,7 @@ const allMenuItems: IMenuItem[] = [
|
||||
name: "studios",
|
||||
message: messages.studios,
|
||||
href: "/studios",
|
||||
icon: "video",
|
||||
icon: faVideo,
|
||||
hotkey: "g u",
|
||||
userCreatable: true,
|
||||
},
|
||||
@@ -130,7 +146,7 @@ const allMenuItems: IMenuItem[] = [
|
||||
name: "tags",
|
||||
message: messages.tags,
|
||||
href: "/tags",
|
||||
icon: "tag",
|
||||
icon: faTag,
|
||||
hotkey: "g t",
|
||||
userCreatable: true,
|
||||
},
|
||||
@@ -236,7 +252,7 @@ export const MainNavbar: React.FC = () => {
|
||||
href="/logout"
|
||||
title={intl.formatMessage({ id: "actions.logout" })}
|
||||
>
|
||||
<Icon icon="sign-out-alt" />
|
||||
<Icon icon={faSignOutAlt} />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -257,7 +273,7 @@ export const MainNavbar: React.FC = () => {
|
||||
className="minimal donate"
|
||||
title={intl.formatMessage({ id: "donate" })}
|
||||
>
|
||||
<Icon icon="heart" />
|
||||
<Icon icon={faHeart} />
|
||||
<span className="d-none d-sm-inline">
|
||||
{intl.formatMessage(messages.donate)}
|
||||
</span>
|
||||
@@ -273,7 +289,7 @@ export const MainNavbar: React.FC = () => {
|
||||
className="minimal d-flex align-items-center h-100"
|
||||
title={intl.formatMessage({ id: "statistics" })}
|
||||
>
|
||||
<Icon icon="chart-bar" />
|
||||
<Icon icon={faChartBar} />
|
||||
</Button>
|
||||
</NavLink>
|
||||
<NavLink
|
||||
@@ -289,7 +305,7 @@ export const MainNavbar: React.FC = () => {
|
||||
onClick={() => openManual()}
|
||||
title={intl.formatMessage({ id: "help" })}
|
||||
>
|
||||
<Icon icon="question-circle" />
|
||||
<Icon icon={faQuestionCircle} />
|
||||
</Button>
|
||||
{maybeRenderLogout()}
|
||||
</>
|
||||
@@ -355,7 +371,7 @@ export const MainNavbar: React.FC = () => {
|
||||
)}
|
||||
{renderUtilityButtons()}
|
||||
<Navbar.Toggle className="nav-menu-toggle ml-sm-2">
|
||||
<Icon icon={expanded ? "times" : "bars"} />
|
||||
<Icon icon={expanded ? faTimes : faBars} />
|
||||
</Navbar.Toggle>
|
||||
</Nav>
|
||||
</Navbar>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
getAggregateRating,
|
||||
getAggregateStudioId,
|
||||
} from "src/utils/bulkUpdate";
|
||||
import { faPencilAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IListOperationProps {
|
||||
selected: GQL.MovieDataFragment[];
|
||||
@@ -101,7 +102,7 @@ export const EditMoviesDialog: React.FC<IListOperationProps> = (
|
||||
return (
|
||||
<Modal
|
||||
show
|
||||
icon="pencil-alt"
|
||||
icon={faPencilAlt}
|
||||
header={intl.formatMessage(
|
||||
{ id: "actions.edit_entity" },
|
||||
{ entityType: intl.formatMessage({ id: "movies" }) }
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "src/components/Shared";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { RatingBanner } from "../Shared/RatingBanner";
|
||||
import { faPlayCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IProps {
|
||||
movie: GQL.MovieDataFragment;
|
||||
@@ -47,7 +48,7 @@ export const MovieCard: FunctionComponent<IProps> = (props: IProps) => {
|
||||
content={popoverContent}
|
||||
>
|
||||
<Button className="minimal">
|
||||
<Icon icon="play-circle" />
|
||||
<Icon icon={faPlayCircle} />
|
||||
<span>{props.movie.scenes.length}</span>
|
||||
</Button>
|
||||
</HoverPopover>
|
||||
|
||||
@@ -19,6 +19,7 @@ import { useToast } from "src/hooks";
|
||||
import { MovieScenesPanel } from "./MovieScenesPanel";
|
||||
import { MovieDetailsPanel } from "./MovieDetailsPanel";
|
||||
import { MovieEditPanel } from "./MovieEditPanel";
|
||||
import { faTrashAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IProps {
|
||||
movie: GQL.MovieDataFragment;
|
||||
@@ -110,7 +111,7 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
|
||||
return (
|
||||
<Modal
|
||||
show={isDeleteAlertOpen}
|
||||
icon="trash-alt"
|
||||
icon={faTrashAlt}
|
||||
accept={{
|
||||
text: intl.formatMessage({ id: "actions.delete" }),
|
||||
variant: "danger",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import _ from "lodash";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
import Mousetrap from "mousetrap";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import {
|
||||
@@ -103,7 +103,7 @@ export const MovieList: React.FC<IMovieList> = ({ filterHook }) => {
|
||||
const { count } = result.data.findMovies;
|
||||
|
||||
const index = Math.floor(Math.random() * count);
|
||||
const filterCopy = _.cloneDeep(filter);
|
||||
const filterCopy = cloneDeep(filter);
|
||||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await queryFindMovies(filterCopy);
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
} from "src/utils/gender";
|
||||
import { IndeterminateCheckbox } from "../Shared/IndeterminateCheckbox";
|
||||
import { BulkUpdateTextInput } from "../Shared/BulkUpdateTextInput";
|
||||
import { faPencilAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IListOperationProps {
|
||||
selected: GQL.SlimPerformerDataFragment[];
|
||||
@@ -183,7 +184,7 @@ export const EditPerformersDialog: React.FC<IListOperationProps> = (
|
||||
return (
|
||||
<Modal
|
||||
show
|
||||
icon="pencil-alt"
|
||||
icon={faPencilAlt}
|
||||
header={intl.formatMessage(
|
||||
{ id: "actions.edit_entity" },
|
||||
{ entityType: intl.formatMessage({ id: "performers" }) }
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from "src/models/list-filter/criteria/criterion";
|
||||
import { PopoverCountButton } from "../Shared/PopoverCountButton";
|
||||
import GenderIcon from "./GenderIcon";
|
||||
import { faHeart, faTag } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
export interface IPerformerCardExtraCriteria {
|
||||
scenes: Criterion<CriterionValue>[];
|
||||
@@ -65,7 +66,7 @@ export const PerformerCard: React.FC<IPerformerCardProps> = ({
|
||||
}
|
||||
return (
|
||||
<div className="favorite">
|
||||
<Icon icon="heart" size="2x" />
|
||||
<Icon icon={faHeart} size="2x" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -122,7 +123,7 @@ export const PerformerCard: React.FC<IPerformerCardProps> = ({
|
||||
return (
|
||||
<HoverPopover placement="bottom" content={popoverContent}>
|
||||
<Button className="minimal tag-count">
|
||||
<Icon icon="tag" />
|
||||
<Icon icon={faTag} />
|
||||
<span>{performer.tags.length}</span>
|
||||
</Button>
|
||||
</HoverPopover>
|
||||
|
||||
@@ -30,6 +30,12 @@ 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";
|
||||
|
||||
interface IProps {
|
||||
performer: GQL.PerformerDataFragment;
|
||||
@@ -325,7 +331,7 @@ const PerformerPage: React.FC<IProps> = ({ performer }) => {
|
||||
)}
|
||||
onClick={() => setFavorite(!performer.favorite)}
|
||||
>
|
||||
<Icon icon="heart" />
|
||||
<Icon icon={faHeart} />
|
||||
</Button>
|
||||
{performer.url && (
|
||||
<Button className="minimal icon-link">
|
||||
@@ -335,7 +341,7 @@ const PerformerPage: React.FC<IProps> = ({ performer }) => {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Icon icon="link" />
|
||||
<Icon icon={faLink} />
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
@@ -350,7 +356,7 @@ const PerformerPage: React.FC<IProps> = ({ performer }) => {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Icon icon="dove" />
|
||||
<Icon icon={faDove} />
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
@@ -365,7 +371,7 @@ const PerformerPage: React.FC<IProps> = ({ performer }) => {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Icon icon="camera" />
|
||||
<Icon icon={faCamera} />
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -37,6 +37,11 @@ import { PerformerScrapeDialog } from "./PerformerScrapeDialog";
|
||||
import PerformerScrapeModal from "./PerformerScrapeModal";
|
||||
import PerformerStashBoxModal, { IStashBox } from "./PerformerStashBoxModal";
|
||||
import cx from "classnames";
|
||||
import {
|
||||
faPlus,
|
||||
faSyncAlt,
|
||||
faTrashAlt,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
const isScraper = (
|
||||
scraper: GQL.Scraper | GQL.StashBox
|
||||
@@ -192,7 +197,7 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
||||
>
|
||||
{t.name}
|
||||
<Button className="minimal ml-2">
|
||||
<Icon className="fa-fw" icon="plus" />
|
||||
<Icon className="fa-fw" icon={faPlus} />
|
||||
</Button>
|
||||
</Badge>
|
||||
))}
|
||||
@@ -594,7 +599,7 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
||||
onClick={() => onReloadScrapers()}
|
||||
>
|
||||
<span className="fa-icon">
|
||||
<Icon icon="sync-alt" />
|
||||
<Icon icon={faSyncAlt} />
|
||||
</span>
|
||||
<span>
|
||||
<FormattedMessage id="actions.reload_scrapers" />
|
||||
@@ -781,7 +786,7 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
||||
title={intl.formatMessage({ id: "actions.delete_stashid" })}
|
||||
onClick={() => removeStashID(stashID)}
|
||||
>
|
||||
<Icon icon="trash-alt" />
|
||||
<Icon icon={faTrashAlt} />
|
||||
</Button>
|
||||
{link}
|
||||
</li>
|
||||
|
||||
@@ -13,7 +13,7 @@ import { useTagCreate } from "src/core/StashService";
|
||||
import { Form } from "react-bootstrap";
|
||||
import { TagSelect } from "src/components/Shared";
|
||||
import { useToast } from "src/hooks";
|
||||
import _ from "lodash";
|
||||
import clone from "lodash-es/clone";
|
||||
import {
|
||||
genderStrings,
|
||||
genderToString,
|
||||
@@ -285,7 +285,7 @@ export const PerformerScrapeDialog: React.FC<IPerformerScrapeDialogProps> = (
|
||||
return;
|
||||
}
|
||||
|
||||
const ret = _.clone(idList);
|
||||
const ret = clone(idList);
|
||||
// sort by id numerically
|
||||
ret.sort((a, b) => {
|
||||
return parseInt(a, 10) - parseInt(b, 10);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { debounce } from "lodash";
|
||||
import debounce from "lodash-es/debounce";
|
||||
import { Button, Form } from "react-bootstrap";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { debounce } from "lodash";
|
||||
import debounce from "lodash-es/debounce";
|
||||
import { Button, Form } from "react-bootstrap";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import _ from "lodash";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
import React, { useState } from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import { useHistory } from "react-router-dom";
|
||||
@@ -138,7 +138,7 @@ export const PerformerList: React.FC<IPerformerList> = ({
|
||||
if (result.data?.findPerformers) {
|
||||
const { count } = result.data.findPerformers;
|
||||
const index = Math.floor(Math.random() * count);
|
||||
const filterCopy = _.cloneDeep(filter);
|
||||
const filterCopy = cloneDeep(filter);
|
||||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await queryFindPerformers(filterCopy);
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Link } from "react-router-dom";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { Icon } from "src/components/Shared";
|
||||
import { NavUtils } from "src/utils";
|
||||
import { faHeart } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IPerformerListTableProps {
|
||||
performers: GQL.PerformerDataFragment[];
|
||||
@@ -37,7 +38,7 @@ export const PerformerListTable: React.FC<IPerformerListTableProps> = (
|
||||
<td>
|
||||
{performer.favorite && (
|
||||
<Button disabled className="favorite">
|
||||
<Icon icon="heart" />
|
||||
<Icon icon={faHeart} />
|
||||
</Button>
|
||||
)}
|
||||
</td>
|
||||
|
||||
@@ -28,6 +28,16 @@ import { TextUtils } from "src/utils";
|
||||
import { DeleteScenesDialog } from "src/components/Scenes/DeleteScenesDialog";
|
||||
import { EditScenesDialog } from "../Scenes/EditScenesDialog";
|
||||
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
||||
import {
|
||||
faBox,
|
||||
faExclamationTriangle,
|
||||
faFilm,
|
||||
faImages,
|
||||
faMapMarkerAlt,
|
||||
faPencilAlt,
|
||||
faTag,
|
||||
faTrash,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
const CLASSNAME = "duplicate-checker";
|
||||
|
||||
@@ -144,7 +154,7 @@ export const SceneDuplicateChecker: React.FC = () => {
|
||||
if (missingPhashes > 0) {
|
||||
return (
|
||||
<p className="lead">
|
||||
<Icon icon="exclamation-triangle" className="text-warning" />
|
||||
<Icon icon={faExclamationTriangle} className="text-warning" />
|
||||
Missing phashes for {missingPhashes} scenes. Please run the phash
|
||||
generation task.
|
||||
</p>
|
||||
@@ -173,7 +183,7 @@ export const SceneDuplicateChecker: React.FC = () => {
|
||||
return (
|
||||
<HoverPopover placement="bottom" content={popoverContent}>
|
||||
<Button className="minimal">
|
||||
<Icon icon="tag" />
|
||||
<Icon icon={faTag} />
|
||||
<span>{scene.tags.length}</span>
|
||||
</Button>
|
||||
</HoverPopover>
|
||||
@@ -216,7 +226,7 @@ export const SceneDuplicateChecker: React.FC = () => {
|
||||
className="tag-tooltip"
|
||||
>
|
||||
<Button className="minimal">
|
||||
<Icon icon="film" />
|
||||
<Icon icon={faFilm} />
|
||||
<span>{scene.movies.length}</span>
|
||||
</Button>
|
||||
</HoverPopover>
|
||||
@@ -236,7 +246,7 @@ export const SceneDuplicateChecker: React.FC = () => {
|
||||
return (
|
||||
<HoverPopover placement="bottom" content={popoverContent}>
|
||||
<Button className="minimal">
|
||||
<Icon icon="map-marker-alt" />
|
||||
<Icon icon={faMapMarkerAlt} />
|
||||
<span>{scene.scene_markers.length}</span>
|
||||
</Button>
|
||||
</HoverPopover>
|
||||
@@ -268,7 +278,7 @@ export const SceneDuplicateChecker: React.FC = () => {
|
||||
return (
|
||||
<HoverPopover placement="bottom" content={popoverContent}>
|
||||
<Button className="minimal">
|
||||
<Icon icon="images" />
|
||||
<Icon icon={faImages} />
|
||||
<span>{scene.galleries.length}</span>
|
||||
</Button>
|
||||
</HoverPopover>
|
||||
@@ -280,7 +290,7 @@ export const SceneDuplicateChecker: React.FC = () => {
|
||||
return (
|
||||
<div>
|
||||
<Button className="minimal">
|
||||
<Icon icon="box" />
|
||||
<Icon icon={faBox} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
@@ -332,7 +342,7 @@ export const SceneDuplicateChecker: React.FC = () => {
|
||||
}
|
||||
>
|
||||
<Button variant="secondary" onClick={onEdit}>
|
||||
<Icon icon="pencil-alt" />
|
||||
<Icon icon={faPencilAlt} />
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
<OverlayTrigger
|
||||
@@ -343,7 +353,7 @@ export const SceneDuplicateChecker: React.FC = () => {
|
||||
}
|
||||
>
|
||||
<Button variant="danger" onClick={handleDeleteChecked}>
|
||||
<Icon icon="trash" />
|
||||
<Icon icon={faTrash} />
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
</ButtonGroup>
|
||||
@@ -550,3 +560,5 @@ export const SceneDuplicateChecker: React.FC = () => {
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default SceneDuplicateChecker;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import React, { useEffect, useState, useCallback, useRef } from "react";
|
||||
import { Button, Card, Form, Table } from "react-bootstrap";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import _ from "lodash";
|
||||
import clone from "lodash-es/clone";
|
||||
import {
|
||||
queryParseSceneFilenames,
|
||||
useScenesUpdate,
|
||||
@@ -155,7 +155,7 @@ export const SceneFilenameParser: React.FC = () => {
|
||||
}, [parserInput, parseSceneFilenames, prevParserInput]);
|
||||
|
||||
function onPageSizeChanged(newSize: number) {
|
||||
const newInput = _.clone(parserInput);
|
||||
const newInput = clone(parserInput);
|
||||
newInput.page = 1;
|
||||
newInput.pageSize = newSize;
|
||||
setParserInput(newInput);
|
||||
@@ -163,14 +163,14 @@ export const SceneFilenameParser: React.FC = () => {
|
||||
|
||||
function onPageChanged(newPage: number) {
|
||||
if (newPage !== parserInput.page) {
|
||||
const newInput = _.clone(parserInput);
|
||||
const newInput = clone(parserInput);
|
||||
newInput.page = newPage;
|
||||
setParserInput(newInput);
|
||||
}
|
||||
}
|
||||
|
||||
function onFindClicked(input: IParserInput) {
|
||||
const newInput = _.clone(input);
|
||||
const newInput = clone(input);
|
||||
newInput.page = 1;
|
||||
newInput.findClicked = true;
|
||||
setParserInput(newInput);
|
||||
@@ -423,3 +423,5 @@ export const SceneFilenameParser: React.FC = () => {
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default SceneFilenameParser;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import _ from "lodash";
|
||||
import isEqual from "lodash-es/isEqual";
|
||||
import clone from "lodash-es/clone";
|
||||
import { Form } from "react-bootstrap";
|
||||
import {
|
||||
ParseSceneFilenamesQuery,
|
||||
@@ -26,7 +27,7 @@ class ParserResult<T> {
|
||||
public setValue(value?: T) {
|
||||
if (value) {
|
||||
this.value = value;
|
||||
this.isSet = !_.isEqual(this.value, this.originalValue);
|
||||
this.isSet = !isEqual(this.value, this.originalValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -332,44 +333,44 @@ interface ISceneParserRowProps {
|
||||
|
||||
export const SceneParserRow = (props: ISceneParserRowProps) => {
|
||||
function changeParser<T>(result: ParserResult<T>, isSet: boolean, value?: T) {
|
||||
const newParser = _.clone(result);
|
||||
const newParser = clone(result);
|
||||
newParser.isSet = isSet;
|
||||
newParser.value = value;
|
||||
return newParser;
|
||||
}
|
||||
|
||||
function onTitleChanged(set: boolean, value: string) {
|
||||
const newResult = _.clone(props.scene);
|
||||
const newResult = clone(props.scene);
|
||||
newResult.title = changeParser(newResult.title, set, value);
|
||||
props.onChange(newResult);
|
||||
}
|
||||
|
||||
function onDateChanged(set: boolean, value: string) {
|
||||
const newResult = _.clone(props.scene);
|
||||
const newResult = clone(props.scene);
|
||||
newResult.date = changeParser(newResult.date, set, value);
|
||||
props.onChange(newResult);
|
||||
}
|
||||
|
||||
function onRatingChanged(set: boolean, value?: number) {
|
||||
const newResult = _.clone(props.scene);
|
||||
const newResult = clone(props.scene);
|
||||
newResult.rating = changeParser(newResult.rating, set, value);
|
||||
props.onChange(newResult);
|
||||
}
|
||||
|
||||
function onPerformerIdsChanged(set: boolean, value: string[]) {
|
||||
const newResult = _.clone(props.scene);
|
||||
const newResult = clone(props.scene);
|
||||
newResult.performers = changeParser(newResult.performers, set, value);
|
||||
props.onChange(newResult);
|
||||
}
|
||||
|
||||
function onTagIdsChanged(set: boolean, value: string[]) {
|
||||
const newResult = _.clone(props.scene);
|
||||
const newResult = clone(props.scene);
|
||||
newResult.tags = changeParser(newResult.tags, set, value);
|
||||
props.onChange(newResult);
|
||||
}
|
||||
|
||||
function onStudioIdChanged(set: boolean, value: string) {
|
||||
const newResult = _.clone(props.scene);
|
||||
const newResult = clone(props.scene);
|
||||
newResult.studio = changeParser(newResult.studio, set, value);
|
||||
props.onChange(newResult);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import {
|
||||
faCheck,
|
||||
faChevronDown,
|
||||
faChevronRight,
|
||||
faTimes,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import React, { useState } from "react";
|
||||
import { Button, Collapse } from "react-bootstrap";
|
||||
import { useIntl } from "react-intl";
|
||||
@@ -26,7 +32,7 @@ export const ShowFields = (props: IShowFieldsProps) => {
|
||||
handleClick(label);
|
||||
}}
|
||||
>
|
||||
<Icon icon={enabled ? "check" : "times"} />
|
||||
<Icon icon={enabled ? faCheck : faTimes} />
|
||||
<span>{label}</span>
|
||||
</Button>
|
||||
));
|
||||
@@ -34,7 +40,7 @@ export const ShowFields = (props: IShowFieldsProps) => {
|
||||
return (
|
||||
<div>
|
||||
<Button onClick={() => setOpen(!open)} className="minimal">
|
||||
<Icon icon={open ? "chevron-down" : "chevron-right"} />
|
||||
<Icon icon={open ? faChevronDown : faChevronRight} />
|
||||
<span>
|
||||
{intl.formatMessage({
|
||||
id: "config.tools.scene_filename_parser.display_fields",
|
||||
|
||||
@@ -27,8 +27,7 @@ import {
|
||||
} from "src/hooks/Interactive/context";
|
||||
import { SceneInteractiveStatus } from "src/hooks/Interactive/status";
|
||||
import { languageMap } from "src/utils/caption";
|
||||
|
||||
export const VIDEO_PLAYER_ID = "VideoJsPlayer";
|
||||
import { VIDEO_PLAYER_ID } from "./util";
|
||||
|
||||
function handleHotkeys(player: VideoJsPlayer, event: VideoJS.KeyboardEvent) {
|
||||
function seekPercent(percent: number) {
|
||||
@@ -679,5 +678,4 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const getPlayerPosition = () =>
|
||||
VideoJS.getPlayer(VIDEO_PLAYER_ID).currentTime();
|
||||
export default ScenePlayer;
|
||||
|
||||
6
ui/v2.5/src/components/ScenePlayer/util.ts
Normal file
6
ui/v2.5/src/components/ScenePlayer/util.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import VideoJS from "video.js";
|
||||
|
||||
export const VIDEO_PLAYER_ID = "VideoJsPlayer";
|
||||
|
||||
export const getPlayerPosition = () =>
|
||||
VideoJS.getPlayer(VIDEO_PLAYER_ID).currentTime();
|
||||
@@ -6,6 +6,7 @@ import { Modal } from "src/components/Shared";
|
||||
import { useToast } from "src/hooks";
|
||||
import { ConfigurationContext } from "src/hooks/Config";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { faTrashAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IDeleteSceneDialogProps {
|
||||
selected: GQL.SlimSceneDataFragment[];
|
||||
@@ -125,7 +126,7 @@ export const DeleteScenesDialog: React.FC<IDeleteSceneDialogProps> = (
|
||||
return (
|
||||
<Modal
|
||||
show
|
||||
icon="trash-alt"
|
||||
icon={faTrashAlt}
|
||||
header={header}
|
||||
accept={{
|
||||
variant: "danger",
|
||||
@@ -162,3 +163,5 @@ export const DeleteScenesDialog: React.FC<IDeleteSceneDialogProps> = (
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteScenesDialog;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Form, Col, Row } from "react-bootstrap";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import _ from "lodash";
|
||||
import isEqual from "lodash-es/isEqual";
|
||||
import { useBulkSceneUpdate } from "src/core/StashService";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { StudioSelect, Modal } from "src/components/Shared";
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
getAggregateStudioId,
|
||||
getAggregateTagIds,
|
||||
} from "src/utils/bulkUpdate";
|
||||
import { faPencilAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IListOperationProps {
|
||||
selected: GQL.SlimSceneDataFragment[];
|
||||
@@ -143,13 +144,13 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
||||
if (sceneStudioID !== updateStudioID) {
|
||||
updateStudioID = undefined;
|
||||
}
|
||||
if (!_.isEqual(scenePerformerIDs, updatePerformerIds)) {
|
||||
if (isEqual(scenePerformerIDs, updatePerformerIds)) {
|
||||
updatePerformerIds = [];
|
||||
}
|
||||
if (!_.isEqual(sceneTagIDs, updateTagIds)) {
|
||||
if (isEqual(sceneTagIDs, updateTagIds)) {
|
||||
updateTagIds = [];
|
||||
}
|
||||
if (!_.isEqual(sceneMovieIDs, updateMovieIds)) {
|
||||
if (isEqual(sceneMovieIDs, updateMovieIds)) {
|
||||
updateMovieIds = [];
|
||||
}
|
||||
if (scene.organized !== updateOrganized) {
|
||||
@@ -244,7 +245,7 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
||||
return (
|
||||
<Modal
|
||||
show
|
||||
icon="pencil-alt"
|
||||
icon={faPencilAlt}
|
||||
header={intl.formatMessage(
|
||||
{ id: "dialogs.edit_entity_title" },
|
||||
{
|
||||
|
||||
@@ -17,6 +17,14 @@ import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
||||
import { GridCard } from "../Shared/GridCard";
|
||||
import { RatingBanner } from "../Shared/RatingBanner";
|
||||
import { FormattedNumber } from "react-intl";
|
||||
import {
|
||||
faBox,
|
||||
faCopy,
|
||||
faFilm,
|
||||
faImages,
|
||||
faMapMarkerAlt,
|
||||
faTag,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IScenePreviewProps {
|
||||
isPortrait: boolean;
|
||||
@@ -171,7 +179,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
||||
content={popoverContent}
|
||||
>
|
||||
<Button className="minimal">
|
||||
<Icon icon="tag" />
|
||||
<Icon icon={faTag} />
|
||||
<span>{props.scene.tags.length}</span>
|
||||
</Button>
|
||||
</HoverPopover>
|
||||
@@ -214,7 +222,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
||||
className="movie-count tag-tooltip"
|
||||
>
|
||||
<Button className="minimal">
|
||||
<Icon icon="film" />
|
||||
<Icon icon={faFilm} />
|
||||
<span>{props.scene.movies.length}</span>
|
||||
</Button>
|
||||
</HoverPopover>
|
||||
@@ -236,7 +244,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
||||
content={popoverContent}
|
||||
>
|
||||
<Button className="minimal">
|
||||
<Icon icon="map-marker-alt" />
|
||||
<Icon icon={faMapMarkerAlt} />
|
||||
<span>{props.scene.scene_markers.length}</span>
|
||||
</Button>
|
||||
</HoverPopover>
|
||||
@@ -272,7 +280,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
||||
content={popoverContent}
|
||||
>
|
||||
<Button className="minimal">
|
||||
<Icon icon="images" />
|
||||
<Icon icon={faImages} />
|
||||
<span>{props.scene.galleries.length}</span>
|
||||
</Button>
|
||||
</HoverPopover>
|
||||
@@ -284,7 +292,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
||||
return (
|
||||
<div className="organized">
|
||||
<Button className="minimal">
|
||||
<Icon icon="box" />
|
||||
<Icon icon={faBox} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
@@ -299,7 +307,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
||||
href={NavUtils.makeScenesPHashMatchUrl(props.scene.phash)}
|
||||
className="minimal"
|
||||
>
|
||||
<Icon icon="copy" />
|
||||
<Icon icon={faCopy} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
import React from "react";
|
||||
import { Button } from "react-bootstrap";
|
||||
import { useIntl } from "react-intl";
|
||||
import { Icon } from "src/components/Shared";
|
||||
import Icon from "src/components/Shared/Icon";
|
||||
import { SceneDataFragment } from "src/core/generated-graphql";
|
||||
import { TextUtils } from "src/utils";
|
||||
import TextUtils from "src/utils/text";
|
||||
|
||||
export interface IExternalPlayerButtonProps {
|
||||
scene: SceneDataFragment;
|
||||
@@ -49,8 +50,10 @@ export const ExternalPlayerButton: React.FC<IExternalPlayerButtonProps> = ({
|
||||
title={intl.formatMessage({ id: "actions.open_in_external_player" })}
|
||||
>
|
||||
<a href={url}>
|
||||
<Icon icon="external-link-alt" color="white" />
|
||||
<Icon icon={faExternalLinkAlt} color="white" />
|
||||
</a>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExternalPlayerButton;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { faBan, faMinus } from "@fortawesome/free-solid-svg-icons";
|
||||
import React, { useState } from "react";
|
||||
import { Button, ButtonGroup, Dropdown, DropdownButton } from "react-bootstrap";
|
||||
import { useIntl } from "react-intl";
|
||||
@@ -58,11 +59,11 @@ export const OCounterButton: React.FC<IOCounterButtonProps> = (
|
||||
className="pl-0 show-carat"
|
||||
>
|
||||
<Dropdown.Item onClick={decrement}>
|
||||
<Icon icon="minus" />
|
||||
<Icon icon={faMinus} />
|
||||
<span>Decrement</span>
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item onClick={reset}>
|
||||
<Icon icon="ban" />
|
||||
<Icon icon={faBan} />
|
||||
<span>Reset</span>
|
||||
</Dropdown.Item>
|
||||
</DropdownButton>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from "react";
|
||||
import cx from "classnames";
|
||||
import { Button, Spinner } from "react-bootstrap";
|
||||
import { Icon } from "src/components/Shared";
|
||||
import Icon from "src/components/Shared/Icon";
|
||||
import { defineMessages, useIntl } from "react-intl";
|
||||
import { faBox } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
export interface IOrganizedButtonProps {
|
||||
loading: boolean;
|
||||
@@ -34,7 +35,7 @@ export const OrganizedButton: React.FC<IOrganizedButtonProps> = (
|
||||
)}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
<Icon icon="box" />
|
||||
<Icon icon={faBox} />
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { Button, Badge, Card } from "react-bootstrap";
|
||||
import { TextUtils } from "src/utils";
|
||||
import TextUtils from "src/utils/text";
|
||||
|
||||
interface IPrimaryTags {
|
||||
sceneMarkers: GQL.SceneMarkerDataFragment[];
|
||||
|
||||
@@ -2,10 +2,17 @@ import React, { useEffect, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import cx from "classnames";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { TextUtils } from "src/utils";
|
||||
import TextUtils from "src/utils/text";
|
||||
import { Button, Form, Spinner } from "react-bootstrap";
|
||||
import { Icon } from "src/components/Shared";
|
||||
import Icon from "src/components/Shared/Icon";
|
||||
import { useIntl } from "react-intl";
|
||||
import {
|
||||
faChevronDown,
|
||||
faChevronUp,
|
||||
faRandom,
|
||||
faStepBackward,
|
||||
faStepForward,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
export interface IPlaylistViewer {
|
||||
scenes?: GQL.SlimSceneDataFragment[];
|
||||
@@ -113,7 +120,7 @@ export const QueueViewer: React.FC<IPlaylistViewer> = ({
|
||||
variant="secondary"
|
||||
onClick={() => onPrevious()}
|
||||
>
|
||||
<Icon icon="step-backward" />
|
||||
<Icon icon={faStepBackward} />
|
||||
</Button>
|
||||
) : (
|
||||
""
|
||||
@@ -124,7 +131,7 @@ export const QueueViewer: React.FC<IPlaylistViewer> = ({
|
||||
variant="secondary"
|
||||
onClick={() => onNext()}
|
||||
>
|
||||
<Icon icon="step-forward" />
|
||||
<Icon icon={faStepForward} />
|
||||
</Button>
|
||||
) : (
|
||||
""
|
||||
@@ -134,7 +141,7 @@ export const QueueViewer: React.FC<IPlaylistViewer> = ({
|
||||
variant="secondary"
|
||||
onClick={() => onRandom()}
|
||||
>
|
||||
<Icon icon="random" />
|
||||
<Icon icon={faRandom} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -143,7 +150,7 @@ export const QueueViewer: React.FC<IPlaylistViewer> = ({
|
||||
<div className="d-flex justify-content-center">
|
||||
<Button onClick={() => lessClicked()} disabled={lessLoading}>
|
||||
{!lessLoading ? (
|
||||
<Icon icon="chevron-up" />
|
||||
<Icon icon={faChevronUp} />
|
||||
) : (
|
||||
<Spinner animation="border" role="status" />
|
||||
)}
|
||||
@@ -155,7 +162,7 @@ export const QueueViewer: React.FC<IPlaylistViewer> = ({
|
||||
<div className="d-flex justify-content-center">
|
||||
<Button onClick={() => moreClicked()} disabled={moreLoading}>
|
||||
{!moreLoading ? (
|
||||
<Icon icon="chevron-down" />
|
||||
<Icon icon={faChevronDown} />
|
||||
) : (
|
||||
<Spinner animation="border" role="status" />
|
||||
)}
|
||||
@@ -166,3 +173,5 @@ export const QueueViewer: React.FC<IPlaylistViewer> = ({
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default QueueViewer;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { useState } from "react";
|
||||
import { Button } from "react-bootstrap";
|
||||
import { Icon } from "src/components/Shared";
|
||||
import Icon from "src/components/Shared/Icon";
|
||||
import { faStar as fasStar } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faStar as farStar } from "@fortawesome/free-regular-svg-icons";
|
||||
|
||||
export interface IRatingStarsProps {
|
||||
value?: number;
|
||||
@@ -33,20 +35,20 @@ export const RatingStars: React.FC<IRatingStarsProps> = (
|
||||
props.onSetRating(newRating);
|
||||
}
|
||||
|
||||
function getIconPrefix(rating: number) {
|
||||
function getIcon(rating: number) {
|
||||
if (hoverRating && hoverRating >= rating) {
|
||||
if (hoverRating === props.value) {
|
||||
return "far";
|
||||
return farStar;
|
||||
}
|
||||
|
||||
return "fas";
|
||||
return fasStar;
|
||||
}
|
||||
|
||||
if (!hoverRating && props.value && props.value >= rating) {
|
||||
return "fas";
|
||||
return fasStar;
|
||||
}
|
||||
|
||||
return "far";
|
||||
return farStar;
|
||||
}
|
||||
|
||||
function onMouseOver(rating: number) {
|
||||
@@ -101,10 +103,7 @@ export const RatingStars: React.FC<IRatingStarsProps> = (
|
||||
title={getTooltip(rating)}
|
||||
key={`star-${rating}`}
|
||||
>
|
||||
<Icon
|
||||
icon={[getIconPrefix(rating), "star"]}
|
||||
className={getClassName(rating)}
|
||||
/>
|
||||
<Icon icon={getIcon(rating)} className={getClassName(rating)} />
|
||||
</Button>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Tab, Nav, Dropdown, Button, ButtonGroup } from "react-bootstrap";
|
||||
import queryString from "query-string";
|
||||
import React, { useEffect, useState, useMemo, useContext } from "react";
|
||||
import React, { useEffect, useState, useMemo, useContext, lazy } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { useParams, useLocation, useHistory, Link } from "react-router-dom";
|
||||
import { Helmet } from "react-helmet";
|
||||
@@ -16,29 +16,41 @@ import {
|
||||
queryFindScenes,
|
||||
queryFindScenesByID,
|
||||
} from "src/core/StashService";
|
||||
import { GalleryViewer } from "src/components/Galleries/GalleryViewer";
|
||||
import { Icon } from "src/components/Shared";
|
||||
|
||||
import Icon from "src/components/Shared/Icon";
|
||||
import { useToast } from "src/hooks";
|
||||
import { SubmitStashBoxDraft } from "src/components/Dialogs/SubmitDraft";
|
||||
import { ScenePlayer, getPlayerPosition } from "src/components/ScenePlayer";
|
||||
import SceneQueue from "src/models/sceneQueue";
|
||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||
import { TextUtils } from "src/utils";
|
||||
import TextUtils from "src/utils/text";
|
||||
import Mousetrap from "mousetrap";
|
||||
import { SceneQueue } from "src/models/sceneQueue";
|
||||
import { QueueViewer } from "./QueueViewer";
|
||||
import { SceneMarkersPanel } from "./SceneMarkersPanel";
|
||||
import { SceneFileInfoPanel } from "./SceneFileInfoPanel";
|
||||
import { SceneEditPanel } from "./SceneEditPanel";
|
||||
import { SceneDetailPanel } from "./SceneDetailPanel";
|
||||
import { OCounterButton } from "./OCounterButton";
|
||||
import { ExternalPlayerButton } from "./ExternalPlayerButton";
|
||||
import { SceneMoviePanel } from "./SceneMoviePanel";
|
||||
import { SceneGalleriesPanel } from "./SceneGalleriesPanel";
|
||||
import { DeleteScenesDialog } from "../DeleteScenesDialog";
|
||||
import { GenerateDialog } from "../../Dialogs/GenerateDialog";
|
||||
import { SceneVideoFilterPanel } from "./SceneVideoFilterPanel";
|
||||
import { OrganizedButton } from "./OrganizedButton";
|
||||
import { ConfigurationContext } from "src/hooks/Config";
|
||||
import { getPlayerPosition } from "src/components/ScenePlayer/util";
|
||||
import { faEllipsisV } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
const SubmitStashBoxDraft = lazy(
|
||||
() => import("src/components/Dialogs/SubmitDraft")
|
||||
);
|
||||
const ScenePlayer = lazy(
|
||||
() => import("src/components/ScenePlayer/ScenePlayer")
|
||||
);
|
||||
|
||||
const GalleryViewer = lazy(
|
||||
() => import("src/components/Galleries/GalleryViewer")
|
||||
);
|
||||
const ExternalPlayerButton = lazy(() => import("./ExternalPlayerButton"));
|
||||
|
||||
const QueueViewer = lazy(() => import("./QueueViewer"));
|
||||
const SceneMarkersPanel = lazy(() => import("./SceneMarkersPanel"));
|
||||
const SceneFileInfoPanel = lazy(() => import("./SceneFileInfoPanel"));
|
||||
const SceneEditPanel = lazy(() => import("./SceneEditPanel"));
|
||||
const SceneDetailPanel = lazy(() => import("./SceneDetailPanel"));
|
||||
const SceneMoviePanel = lazy(() => import("./SceneMoviePanel"));
|
||||
const SceneGalleriesPanel = lazy(() => import("./SceneGalleriesPanel"));
|
||||
const DeleteScenesDialog = lazy(() => import("../DeleteScenesDialog"));
|
||||
const GenerateDialog = lazy(() => import("../../Dialogs/GenerateDialog"));
|
||||
const SceneVideoFilterPanel = lazy(() => import("./SceneVideoFilterPanel"));
|
||||
|
||||
interface IProps {
|
||||
scene: GQL.SceneDataFragment;
|
||||
@@ -237,7 +249,7 @@ const ScenePage: React.FC<IProps> = ({
|
||||
className="minimal"
|
||||
title={intl.formatMessage({ id: "operations" })}
|
||||
>
|
||||
<Icon icon="ellipsis-v" />
|
||||
<Icon icon={faEllipsisV} />
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="bg-secondary text-white">
|
||||
<Dropdown.Item
|
||||
|
||||
@@ -2,8 +2,9 @@ import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { FormattedDate, FormattedMessage, useIntl } from "react-intl";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { TextUtils } from "src/utils";
|
||||
import { TagLink, TruncatedText } from "src/components/Shared";
|
||||
import TextUtils from "src/utils/text";
|
||||
import { TagLink } from "src/components/Shared/TagLink";
|
||||
import TruncatedText from "src/components/Shared/TruncatedText";
|
||||
import { PerformerCard } from "src/components/Performers/PerformerCard";
|
||||
import { sortPerformers } from "src/core/performers";
|
||||
import { RatingStars } from "./RatingStars";
|
||||
@@ -145,3 +146,5 @@ export const SceneDetailPanel: React.FC<ISceneDetailProps> = (props) => {
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SceneDetailPanel;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState, useMemo } from "react";
|
||||
import React, { useEffect, useState, useMemo, lazy } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import {
|
||||
Button,
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
ImageInput,
|
||||
URLField,
|
||||
} from "src/components/Shared";
|
||||
import { useToast } from "src/hooks";
|
||||
import useToast from "src/hooks/Toast";
|
||||
import { ImageUtils, FormUtils, TextUtils, getStashIDs } from "src/utils";
|
||||
import { MovieSelect } from "src/components/Shared/Select";
|
||||
import { useFormik } from "formik";
|
||||
@@ -39,8 +39,14 @@ import { ConfigurationContext } from "src/hooks/Config";
|
||||
import { stashboxDisplayName } from "src/utils/stashbox";
|
||||
import { SceneMovieTable } from "./SceneMovieTable";
|
||||
import { RatingStars } from "./RatingStars";
|
||||
import { SceneScrapeDialog } from "./SceneScrapeDialog";
|
||||
import { SceneQueryModal } from "./SceneQueryModal";
|
||||
import {
|
||||
faSearch,
|
||||
faSyncAlt,
|
||||
faTrashAlt,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
const SceneScrapeDialog = lazy(() => import("./SceneScrapeDialog"));
|
||||
const SceneQueryModal = lazy(() => import("./SceneQueryModal"));
|
||||
|
||||
interface IProps {
|
||||
scene: GQL.SceneDataFragment;
|
||||
@@ -401,7 +407,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
||||
return (
|
||||
<Dropdown title={intl.formatMessage({ id: "actions.scrape_query" })}>
|
||||
<Dropdown.Toggle variant="secondary">
|
||||
<Icon icon="search" />
|
||||
<Icon icon={faSearch} />
|
||||
</Dropdown.Toggle>
|
||||
|
||||
<Dropdown.Menu>
|
||||
@@ -428,7 +434,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
||||
))}
|
||||
<Dropdown.Item onClick={() => onReloadScrapers()}>
|
||||
<span className="fa-icon">
|
||||
<Icon icon="sync-alt" />
|
||||
<Icon icon={faSyncAlt} />
|
||||
</span>
|
||||
<span>
|
||||
<FormattedMessage id="actions.reload_scrapers" />
|
||||
@@ -500,7 +506,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
||||
))}
|
||||
<Dropdown.Item onClick={() => onReloadScrapers()}>
|
||||
<span className="fa-icon">
|
||||
<Icon icon="sync-alt" />
|
||||
<Icon icon={faSyncAlt} />
|
||||
</span>
|
||||
<span>
|
||||
<FormattedMessage id="actions.reload_scrapers" />
|
||||
@@ -857,7 +863,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
||||
)}
|
||||
onClick={() => removeStashID(stashID)}
|
||||
>
|
||||
<Icon icon="trash-alt" />
|
||||
<Icon icon={faTrashAlt} />
|
||||
</Button>
|
||||
{link}
|
||||
</li>
|
||||
@@ -908,3 +914,5 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SceneEditPanel;
|
||||
|
||||
@@ -175,3 +175,5 @@ export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
|
||||
</dl>
|
||||
);
|
||||
};
|
||||
|
||||
export default SceneFileInfoPanel;
|
||||
|
||||
@@ -15,3 +15,5 @@ export const SceneGalleriesPanel: React.FC<ISceneGalleriesPanelProps> = ({
|
||||
|
||||
return <div className="row justify-content-center">{cards}</div>;
|
||||
};
|
||||
|
||||
export default SceneGalleriesPanel;
|
||||
|
||||
@@ -13,8 +13,8 @@ import {
|
||||
TagSelect,
|
||||
MarkerTitleSuggest,
|
||||
} from "src/components/Shared";
|
||||
import { getPlayerPosition } from "src/components/ScenePlayer";
|
||||
import { useToast } from "src/hooks";
|
||||
import { getPlayerPosition } from "src/components/ScenePlayer/util";
|
||||
import useToast from "src/hooks/Toast";
|
||||
|
||||
interface IFormFields {
|
||||
title: string;
|
||||
|
||||
@@ -89,3 +89,5 @@ export const SceneMarkersPanel: React.FC<ISceneMarkersPanelProps> = (
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SceneMarkersPanel;
|
||||
|
||||
@@ -23,3 +23,5 @@ export const SceneMoviePanel: FunctionComponent<ISceneMoviePanelProps> = (
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SceneMoviePanel;
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
Icon,
|
||||
} from "src/components/Shared";
|
||||
import { queryScrapeSceneQuery } from "src/core/StashService";
|
||||
import { useToast } from "src/hooks";
|
||||
import useToast from "src/hooks/Toast";
|
||||
import { faSearch } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface ISceneSearchResultDetailsProps {
|
||||
scene: GQL.ScrapedSceneDataFragment;
|
||||
@@ -219,7 +220,7 @@ export const SceneQueryModal: React.FC<IProps> = ({
|
||||
variant="primary"
|
||||
title={intl.formatMessage({ id: "actions.search" })}
|
||||
>
|
||||
<Icon icon="search" />
|
||||
<Icon icon={faSearch} />
|
||||
</Button>
|
||||
</InputGroup.Append>
|
||||
</InputGroup>
|
||||
@@ -235,3 +236,5 @@ export const SceneQueryModal: React.FC<IProps> = ({
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default SceneQueryModal;
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
ScrapedTextAreaRow,
|
||||
ScrapedImageRow,
|
||||
} from "src/components/Shared/ScrapeDialog";
|
||||
import _ from "lodash";
|
||||
import clone from "lodash-es/clone";
|
||||
import {
|
||||
useStudioCreate,
|
||||
usePerformerCreate,
|
||||
@@ -18,8 +18,8 @@ import {
|
||||
useTagCreate,
|
||||
makePerformerCreateInput,
|
||||
} from "src/core/StashService";
|
||||
import { useToast } from "src/hooks";
|
||||
import { DurationUtils } from "src/utils";
|
||||
import useToast from "src/hooks/Toast";
|
||||
import DurationUtils from "src/utils/duration";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
function renderScrapedStudio(
|
||||
@@ -297,7 +297,7 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const ret = _.clone(idList);
|
||||
const ret = clone(idList);
|
||||
// sort by id numerically
|
||||
ret.sort((a, b) => {
|
||||
return parseInt(a, 10) - parseInt(b, 10);
|
||||
@@ -634,3 +634,5 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SceneScrapeDialog;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useState } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { Button, Form } from "react-bootstrap";
|
||||
import { TruncatedText } from "src/components/Shared";
|
||||
import { VIDEO_PLAYER_ID } from "src/components/ScenePlayer";
|
||||
import TruncatedText from "src/components/Shared/TruncatedText";
|
||||
import { VIDEO_PLAYER_ID } from "src/components/ScenePlayer/util";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
|
||||
interface ISceneVideoFilterPanelProps {
|
||||
@@ -670,3 +670,5 @@ export const SceneVideoFilterPanel: React.FC<ISceneVideoFilterPanelProps> = (
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SceneVideoFilterPanel;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React, { useState } from "react";
|
||||
import _ from "lodash";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
import { useIntl } from "react-intl";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import Mousetrap from "mousetrap";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import {
|
||||
FindScenesQueryResult,
|
||||
SlimSceneDataFragment,
|
||||
@@ -25,6 +24,7 @@ import { SceneCardsGrid } from "./SceneCardsGrid";
|
||||
import { TaggerContext } from "../Tagger/context";
|
||||
import { IdentifyDialog } from "../Dialogs/IdentifyDialog/IdentifyDialog";
|
||||
import { ConfigurationContext } from "src/hooks/Config";
|
||||
import { faPlay } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface ISceneList {
|
||||
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
||||
@@ -50,7 +50,7 @@ export const SceneList: React.FC<ISceneList> = ({
|
||||
text: intl.formatMessage({ id: "actions.play_selected" }),
|
||||
onClick: playSelected,
|
||||
isDisplayed: showWhenSelected,
|
||||
icon: "play" as IconProp,
|
||||
icon: faPlay,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.play_random" }),
|
||||
@@ -137,7 +137,7 @@ export const SceneList: React.FC<ISceneList> = ({
|
||||
const indexMax =
|
||||
filter.itemsPerPage < count ? filter.itemsPerPage : count;
|
||||
const index = Math.floor(Math.random() * indexMax);
|
||||
const filterCopy = _.cloneDeep(filter);
|
||||
const filterCopy = cloneDeep(filter);
|
||||
filterCopy.currentPage = page;
|
||||
filterCopy.sortBy = "random";
|
||||
const queryResults = await queryFindScenes(filterCopy);
|
||||
@@ -300,3 +300,5 @@ export const SceneList: React.FC<ISceneList> = ({
|
||||
|
||||
return <TaggerContext>{listData.template}</TaggerContext>;
|
||||
};
|
||||
|
||||
export default SceneList;
|
||||
|
||||
@@ -102,7 +102,7 @@ export const SceneListTable: React.FC<ISceneListTableProps> = (
|
||||
{scene.gallery && (
|
||||
<Button className="minimal">
|
||||
<Link to={`/galleries/${scene.gallery.id}`}>
|
||||
<Icon icon="image" />
|
||||
<Icon icon={faImage} />
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import _ from "lodash";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
import React from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useIntl } from "react-intl";
|
||||
@@ -58,7 +58,7 @@ export const SceneMarkerList: React.FC<ISceneMarkerList> = ({ filterHook }) => {
|
||||
const { count } = result.data.findSceneMarkers;
|
||||
|
||||
const index = Math.floor(Math.random() * count);
|
||||
const filterCopy = _.cloneDeep(filter);
|
||||
const filterCopy = cloneDeep(filter);
|
||||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await queryFindSceneMarkers(filterCopy);
|
||||
@@ -98,3 +98,5 @@ export const SceneMarkerList: React.FC<ISceneMarkerList> = ({ filterHook }) => {
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SceneMarkerList;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import React from "react";
|
||||
import React, { lazy } from "react";
|
||||
import { Route, Switch } from "react-router-dom";
|
||||
import { useIntl } from "react-intl";
|
||||
import { Helmet } from "react-helmet";
|
||||
import { TITLE_SUFFIX } from "src/components/Shared";
|
||||
import { PersistanceLevel } from "src/hooks/ListHook";
|
||||
import Scene from "./SceneDetails/Scene";
|
||||
import { SceneList } from "./SceneList";
|
||||
import { SceneMarkerList } from "./SceneMarkerList";
|
||||
|
||||
const SceneList = lazy(() => import("./SceneList"));
|
||||
const SceneMarkerList = lazy(() => import("./SceneMarkerList"));
|
||||
const Scene = lazy(() => import("./SceneDetails/Scene"));
|
||||
|
||||
const Scenes: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { faChevronDown, faChevronUp } from "@fortawesome/free-solid-svg-icons";
|
||||
import React, { useState } from "react";
|
||||
import { Button, Collapse, Form, Modal, ModalProps } from "react-bootstrap";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
@@ -94,7 +95,7 @@ export const SettingGroup: React.FC<PropsWithChildren<ISettingGroup>> = ({
|
||||
variant="minimal"
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
<Icon className="fa-fw" icon={open ? "chevron-up" : "chevron-down"} />
|
||||
<Icon className="fa-fw" icon={open ? faChevronUp : faChevronDown} />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -148,3 +148,5 @@ export const Settings: React.FC = () => {
|
||||
</Tab.Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { SettingSection } from "./SettingSection";
|
||||
import { BooleanSetting, StringListSetting, StringSetting } from "./Inputs";
|
||||
import { SettingStateContext } from "./context";
|
||||
import { useIntl } from "react-intl";
|
||||
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
export const SettingsLibraryPanel: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
@@ -85,7 +86,7 @@ export const SettingsLibraryPanel: React.FC = () => {
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<Icon icon="question-circle" />
|
||||
<Icon icon={faQuestionCircle} />
|
||||
</a>
|
||||
</span>
|
||||
}
|
||||
@@ -107,7 +108,7 @@ export const SettingsLibraryPanel: React.FC = () => {
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<Icon icon="question-circle" />
|
||||
<Icon icon={faQuestionCircle} />
|
||||
</a>
|
||||
</span>
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { TextUtils } from "src/utils";
|
||||
import { CollapseButton, Icon, LoadingIndicator } from "src/components/Shared";
|
||||
import { SettingSection } from "./SettingSection";
|
||||
import { Setting, SettingGroup } from "./Inputs";
|
||||
import { faLink, faSyncAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
export const SettingsPluginsPanel: React.FC = () => {
|
||||
const Toast = useToast();
|
||||
@@ -30,7 +31,7 @@ export const SettingsPluginsPanel: React.FC = () => {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Icon icon="link" />
|
||||
<Icon icon={faLink} />
|
||||
</a>
|
||||
</Button>
|
||||
);
|
||||
@@ -105,7 +106,7 @@ export const SettingsPluginsPanel: React.FC = () => {
|
||||
<Setting headingID="actions.reload_plugins">
|
||||
<Button onClick={() => onReloadPlugins()}>
|
||||
<span className="fa-icon">
|
||||
<Icon icon="sync-alt" />
|
||||
<Icon icon={faSyncAlt} />
|
||||
</span>
|
||||
<span>
|
||||
<FormattedMessage id="actions.reload_plugins" />
|
||||
|
||||
@@ -16,6 +16,7 @@ import { SettingSection } from "./SettingSection";
|
||||
import { BooleanSetting, StringListSetting, StringSetting } from "./Inputs";
|
||||
import { SettingStateContext } from "./context";
|
||||
import { StashBoxSetting } from "./StashBoxConfiguration";
|
||||
import { faSyncAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IURLList {
|
||||
urls: string[];
|
||||
@@ -361,7 +362,7 @@ export const SettingsScrapingPanel: React.FC = () => {
|
||||
<div className="content">
|
||||
<Button onClick={() => onReloadScrapers()}>
|
||||
<span className="fa-icon">
|
||||
<Icon icon="sync-alt" />
|
||||
<Icon icon={faSyncAlt} />
|
||||
</span>
|
||||
<span>
|
||||
<FormattedMessage id="actions.reload_scrapers" />
|
||||
|
||||
@@ -13,6 +13,11 @@ import { DurationInput, Icon, LoadingIndicator, Modal } from "../Shared";
|
||||
import { SettingSection } from "./SettingSection";
|
||||
import { BooleanSetting, StringListSetting, StringSetting } from "./Inputs";
|
||||
import { SettingStateContext } from "./context";
|
||||
import {
|
||||
faClock,
|
||||
faTimes,
|
||||
faUserClock,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
export const SettingsServicesPanel: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
@@ -231,7 +236,7 @@ export const SettingsServicesPanel: React.FC = () => {
|
||||
<Modal
|
||||
show={enableDisable !== undefined}
|
||||
header={capitalised}
|
||||
icon="clock"
|
||||
icon={faClock}
|
||||
accept={{
|
||||
text: capitalised,
|
||||
variant: "primary",
|
||||
@@ -273,7 +278,7 @@ export const SettingsServicesPanel: React.FC = () => {
|
||||
{ id: "config.dlna.allow_temp_ip" },
|
||||
{ tempIP }
|
||||
)}
|
||||
icon="clock"
|
||||
icon={faClock}
|
||||
accept={{
|
||||
text: intl.formatMessage({ id: "actions.allow" }),
|
||||
variant: "primary",
|
||||
@@ -335,7 +340,7 @@ export const SettingsServicesPanel: React.FC = () => {
|
||||
variant="danger"
|
||||
onClick={() => onDisallowTempIP(a.ipAddress)}
|
||||
>
|
||||
<Icon icon="times" />
|
||||
<Icon icon={faTimes} />
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
@@ -364,7 +369,7 @@ export const SettingsServicesPanel: React.FC = () => {
|
||||
title={intl.formatMessage({ id: "actions.allow_temporarily" })}
|
||||
onClick={() => setTempIP(a)}
|
||||
>
|
||||
<Icon icon="user-clock" />
|
||||
<Icon icon={faUserClock} />
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
@@ -386,7 +391,7 @@ export const SettingsServicesPanel: React.FC = () => {
|
||||
onClick={() => setTempIP(ipEntry)}
|
||||
disabled={!ipEntry}
|
||||
>
|
||||
<Icon icon="user-clock" />
|
||||
<Icon icon={faUserClock} />
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { faEllipsisV } from "@fortawesome/free-solid-svg-icons";
|
||||
import React, { useState } from "react";
|
||||
import { Button, Form, Row, Col, Dropdown } from "react-bootstrap";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
@@ -72,7 +73,7 @@ const Stash: React.FC<IStashProps> = ({
|
||||
id={`stash-menu-${index}`}
|
||||
className="minimal"
|
||||
>
|
||||
<Icon icon="ellipsis-v" />
|
||||
<Icon icon={faEllipsisV} />
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="bg-secondary text-white">
|
||||
<Dropdown.Item onClick={() => onEdit()}>
|
||||
|
||||
@@ -15,10 +15,16 @@ import { ImportDialog } from "./ImportDialog";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { SettingSection } from "../SettingSection";
|
||||
import { BooleanSetting, Setting } from "../Inputs";
|
||||
import { ManualLink } from "src/components/Help/Manual";
|
||||
import { ManualLink } from "src/components/Help/context";
|
||||
import { Icon } from "src/components/Shared";
|
||||
import { ConfigurationContext } from "src/hooks/Config";
|
||||
import { FolderSelect } from "src/components/Shared/FolderSelect/FolderSelect";
|
||||
import {
|
||||
faMinus,
|
||||
faPlus,
|
||||
faQuestionCircle,
|
||||
faTrashAlt,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface ICleanDialog {
|
||||
pathSelection?: boolean;
|
||||
@@ -63,7 +69,7 @@ const CleanDialog: React.FC<ICleanDialog> = ({
|
||||
return (
|
||||
<Modal
|
||||
show
|
||||
icon="trash-alt"
|
||||
icon={faTrashAlt}
|
||||
disabled={pathSelection && paths.length === 0}
|
||||
accept={{
|
||||
text: intl.formatMessage({ id: "actions.clean" }),
|
||||
@@ -87,7 +93,7 @@ const CleanDialog: React.FC<ICleanDialog> = ({
|
||||
title={intl.formatMessage({ id: "actions.delete" })}
|
||||
onClick={() => removePath(p)}
|
||||
>
|
||||
<Icon icon="minus" />
|
||||
<Icon icon={faMinus} />
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -103,7 +109,7 @@ const CleanDialog: React.FC<ICleanDialog> = ({
|
||||
variant="secondary"
|
||||
onClick={() => addPath(currentDirectory)}
|
||||
>
|
||||
<Icon icon="plus" />
|
||||
<Icon icon={faPlus} />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
@@ -188,7 +194,7 @@ export const DataManagementTasks: React.FC<IDataManagementTasks> = ({
|
||||
return (
|
||||
<Modal
|
||||
show={dialogOpen.importAlert}
|
||||
icon="trash-alt"
|
||||
icon={faTrashAlt}
|
||||
accept={{
|
||||
text: intl.formatMessage({ id: "actions.import" }),
|
||||
variant: "danger",
|
||||
@@ -316,7 +322,7 @@ export const DataManagementTasks: React.FC<IDataManagementTasks> = ({
|
||||
<>
|
||||
<FormattedMessage id="actions.clean" />
|
||||
<ManualLink tab="Tasks">
|
||||
<Icon icon="question-circle" />
|
||||
<Icon icon={faQuestionCircle} />
|
||||
</ManualLink>
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
faMinus,
|
||||
faPencilAlt,
|
||||
faPlus,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import React, { useState } from "react";
|
||||
import { Button, Col, Form, Row } from "react-bootstrap";
|
||||
import { useIntl } from "react-intl";
|
||||
@@ -41,7 +46,7 @@ export const DirectorySelectionDialog: React.FC<IDirectorySelectionDialogProps>
|
||||
show
|
||||
modalProps={{ animation }}
|
||||
disabled={!allowEmpty && paths.length === 0}
|
||||
icon="pencil-alt"
|
||||
icon={faPencilAlt}
|
||||
header={intl.formatMessage({ id: "actions.select_folders" })}
|
||||
accept={{
|
||||
onClick: () => {
|
||||
@@ -69,7 +74,7 @@ export const DirectorySelectionDialog: React.FC<IDirectorySelectionDialogProps>
|
||||
title={intl.formatMessage({ id: "actions.delete" })}
|
||||
onClick={() => removePath(p)}
|
||||
>
|
||||
<Icon icon="minus" />
|
||||
<Icon icon={faMinus} />
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -84,7 +89,7 @@ export const DirectorySelectionDialog: React.FC<IDirectorySelectionDialogProps>
|
||||
variant="secondary"
|
||||
onClick={() => addPath(currentDirectory)}
|
||||
>
|
||||
<Icon icon="plus" />
|
||||
<Icon icon={faPlus} />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Modal } from "src/components/Shared";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { useToast } from "src/hooks";
|
||||
import { useIntl } from "react-intl";
|
||||
import { faPencilAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IImportDialogProps {
|
||||
onClose: () => void;
|
||||
@@ -115,7 +116,7 @@ export const ImportDialog: React.FC<IImportDialogProps> = (
|
||||
return (
|
||||
<Modal
|
||||
show
|
||||
icon="pencil-alt"
|
||||
icon={faPencilAlt}
|
||||
header={intl.formatMessage({ id: "actions.import" })}
|
||||
accept={{
|
||||
onClick: () => {
|
||||
|
||||
@@ -7,8 +7,15 @@ import {
|
||||
} from "src/core/StashService";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { Icon } from "src/components/Shared";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { useIntl } from "react-intl";
|
||||
import {
|
||||
faBan,
|
||||
faCheck,
|
||||
faCircle,
|
||||
faCog,
|
||||
faHourglassStart,
|
||||
faTimes,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
type JobFragment = Pick<
|
||||
GQL.Job,
|
||||
@@ -68,25 +75,25 @@ const Task: React.FC<IJob> = ({ job }) => {
|
||||
}
|
||||
|
||||
function getStatusIcon() {
|
||||
let icon: IconProp = "circle";
|
||||
let icon = faCircle;
|
||||
let iconClass = "";
|
||||
switch (job.status) {
|
||||
case GQL.JobStatus.Ready:
|
||||
icon = "hourglass-start";
|
||||
icon = faHourglassStart;
|
||||
break;
|
||||
case GQL.JobStatus.Running:
|
||||
icon = "cog";
|
||||
icon = faCog;
|
||||
iconClass = "fa-spin";
|
||||
break;
|
||||
case GQL.JobStatus.Stopping:
|
||||
icon = "cog";
|
||||
icon = faCog;
|
||||
iconClass = "fa-spin";
|
||||
break;
|
||||
case GQL.JobStatus.Finished:
|
||||
icon = "check";
|
||||
icon = faCheck;
|
||||
break;
|
||||
case GQL.JobStatus.Cancelled:
|
||||
icon = "ban";
|
||||
icon = faBan;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -138,7 +145,7 @@ const Task: React.FC<IJob> = ({ job }) => {
|
||||
onClick={() => stopJob()}
|
||||
disabled={!canStop()}
|
||||
>
|
||||
<Icon icon="times" />
|
||||
<Icon icon={faTimes} />
|
||||
</Button>
|
||||
<div className={`job-status ${getStatusClass()}`}>
|
||||
<div>
|
||||
|
||||
@@ -17,8 +17,9 @@ import { useToast } from "src/hooks";
|
||||
import { GenerateOptions } from "./GenerateOptions";
|
||||
import { SettingSection } from "../SettingSection";
|
||||
import { BooleanSetting, Setting, SettingGroup } from "../Inputs";
|
||||
import { ManualLink } from "src/components/Help/Manual";
|
||||
import { ManualLink } from "src/components/Help/context";
|
||||
import { Icon } from "src/components/Shared";
|
||||
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IAutoTagOptions {
|
||||
options: GQL.AutoTagMetadataInput;
|
||||
@@ -296,7 +297,7 @@ export const LibraryTasks: React.FC = () => {
|
||||
<>
|
||||
<FormattedMessage id="actions.scan" />
|
||||
<ManualLink tab="Tasks">
|
||||
<Icon icon="question-circle" />
|
||||
<Icon icon={faQuestionCircle} />
|
||||
</ManualLink>
|
||||
</>
|
||||
),
|
||||
@@ -335,7 +336,7 @@ export const LibraryTasks: React.FC = () => {
|
||||
<>
|
||||
<FormattedMessage id="config.tasks.identify.heading" />
|
||||
<ManualLink tab="Identify">
|
||||
<Icon icon="question-circle" />
|
||||
<Icon icon={faQuestionCircle} />
|
||||
</ManualLink>
|
||||
</>
|
||||
}
|
||||
@@ -358,7 +359,7 @@ export const LibraryTasks: React.FC = () => {
|
||||
<>
|
||||
<FormattedMessage id="actions.auto_tag" />
|
||||
<ManualLink tab="AutoTagging">
|
||||
<Icon icon="question-circle" />
|
||||
<Icon icon={faQuestionCircle} />
|
||||
</ManualLink>
|
||||
</>
|
||||
),
|
||||
@@ -399,7 +400,7 @@ export const LibraryTasks: React.FC = () => {
|
||||
<>
|
||||
<FormattedMessage id="actions.generate" />
|
||||
<ManualLink tab="Tasks">
|
||||
<Icon icon="question-circle" />
|
||||
<Icon icon={faQuestionCircle} />
|
||||
</ManualLink>
|
||||
</>
|
||||
),
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { ApolloError } from "@apollo/client/errors";
|
||||
import { debounce } from "lodash";
|
||||
import {
|
||||
faCheckCircle,
|
||||
faTimesCircle,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import debounce from "lodash-es/debounce";
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
@@ -452,7 +456,7 @@ export const SettingsContext: React.FC = ({ children }) => {
|
||||
if (updateSuccess === false) {
|
||||
return (
|
||||
<div className="loading-indicator failed">
|
||||
<Icon icon="times-circle" className="fa-fw" />
|
||||
<Icon icon={faTimesCircle} className="fa-fw" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -477,7 +481,7 @@ export const SettingsContext: React.FC = ({ children }) => {
|
||||
if (updateSuccess) {
|
||||
return (
|
||||
<div className="loading-indicator success">
|
||||
<Icon icon="check-circle" className="fa-fw" />
|
||||
<Icon icon={faCheckCircle} className="fa-fw" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Button } from "react-bootstrap";
|
||||
import { useJobQueue, useJobsSubscribe } from "src/core/StashService";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { useIntl } from "react-intl";
|
||||
import { faCog } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
type JobFragment = Pick<
|
||||
GQL.Job,
|
||||
@@ -59,7 +60,7 @@ export const SettingsButton: React.FC = () => {
|
||||
className="minimal d-flex align-items-center h-100"
|
||||
title={intl.formatMessage({ id: "settings" })}
|
||||
>
|
||||
<FontAwesomeIcon icon="cog" spin={queue.length > 0} />
|
||||
<FontAwesomeIcon icon={faCog} spin={queue.length > 0} />
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -180,3 +180,5 @@ export const Migrate: React.FC = () => {
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default Migrate;
|
||||
|
||||
@@ -15,6 +15,11 @@ import { ConfigurationContext } from "src/hooks/Config";
|
||||
import StashConfiguration from "../Settings/StashConfiguration";
|
||||
import { Icon, LoadingIndicator, Modal } from "../Shared";
|
||||
import { FolderSelectDialog } from "../Shared/FolderSelect/FolderSelectDialog";
|
||||
import {
|
||||
faEllipsisH,
|
||||
faExclamationTriangle,
|
||||
faQuestionCircle,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
export const Setup: React.FC = () => {
|
||||
const { configuration, loading: configLoading } = useContext(
|
||||
@@ -108,7 +113,7 @@ export const Setup: React.FC = () => {
|
||||
return (
|
||||
<Modal
|
||||
show
|
||||
icon="exclamation-triangle"
|
||||
icon={faExclamationTriangle}
|
||||
accept={{
|
||||
text: intl.formatMessage({ id: "actions.confirm" }),
|
||||
variant: "danger",
|
||||
@@ -269,7 +274,7 @@ export const Setup: React.FC = () => {
|
||||
className="text-input"
|
||||
onClick={() => setShowGeneratedDialog(true)}
|
||||
>
|
||||
<Icon icon="ellipsis-h" />
|
||||
<Icon icon={faEllipsisH} />
|
||||
</Button>
|
||||
</InputGroup.Append>
|
||||
</InputGroup>
|
||||
@@ -528,7 +533,7 @@ export const Setup: React.FC = () => {
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="setup.success.in_app_manual_explained"
|
||||
values={{ icon: <Icon icon="question-circle" /> }}
|
||||
values={{ icon: <Icon icon={faQuestionCircle} /> }}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
@@ -640,3 +645,5 @@ export const Setup: React.FC = () => {
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default Setup;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { faBan } from "@fortawesome/free-solid-svg-icons";
|
||||
import React from "react";
|
||||
import { Button, Form, FormControlProps, InputGroup } from "react-bootstrap";
|
||||
import { useIntl } from "react-intl";
|
||||
import { Icon } from ".";
|
||||
import Icon from "./Icon";
|
||||
|
||||
interface IBulkUpdateTextInputProps extends FormControlProps {
|
||||
valueChanged: (value: string | undefined) => void;
|
||||
@@ -37,7 +38,7 @@ export const BulkUpdateTextInput: React.FC<IBulkUpdateTextInputProps> = ({
|
||||
onClick={() => valueChanged(undefined)}
|
||||
title={intl.formatMessage({ id: "actions.unset" })}
|
||||
>
|
||||
<Icon icon="ban" />
|
||||
<Icon icon={faBan} />
|
||||
</Button>
|
||||
) : undefined}
|
||||
</InputGroup>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import {
|
||||
faChevronDown,
|
||||
faChevronRight,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import React, { useState } from "react";
|
||||
import { Button, Collapse } from "react-bootstrap";
|
||||
import { Icon } from "src/components/Shared";
|
||||
import Icon from "src/components/Shared/Icon";
|
||||
|
||||
interface IProps {
|
||||
text: string;
|
||||
@@ -17,7 +21,7 @@ export const CollapseButton: React.FC<React.PropsWithChildren<IProps>> = (
|
||||
onClick={() => setOpen(!open)}
|
||||
className="minimal collapse-button"
|
||||
>
|
||||
<Icon icon={open ? "chevron-down" : "chevron-right"} />
|
||||
<Icon icon={open ? faChevronDown : faChevronRight} />
|
||||
<span>{props.text}</span>
|
||||
</Button>
|
||||
<Collapse in={open}>
|
||||
|
||||
@@ -2,8 +2,9 @@ import React, { useState } from "react";
|
||||
import { defineMessages, FormattedMessage, useIntl } from "react-intl";
|
||||
import { FetchResult } from "@apollo/client";
|
||||
|
||||
import { Modal } from "src/components/Shared";
|
||||
import Modal from "src/components/Shared/Modal";
|
||||
import { useToast } from "src/hooks";
|
||||
import { faTrashAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IDeletionEntity {
|
||||
id: string;
|
||||
@@ -78,7 +79,7 @@ const DeleteEntityDialog: React.FC<IDeleteEntityDialogProps> = ({
|
||||
return (
|
||||
<Modal
|
||||
show
|
||||
icon="trash-alt"
|
||||
icon={faTrashAlt}
|
||||
header={intl.formatMessage(messages.deleteHeader, {
|
||||
count,
|
||||
singularEntity,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Button, Modal } from "react-bootstrap";
|
||||
import React, { useState } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { ImageInput } from "src/components/Shared";
|
||||
import { ImageInput } from "src/components/Shared/ImageInput";
|
||||
import cx from "classnames";
|
||||
|
||||
interface IProps {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import {
|
||||
faChevronDown,
|
||||
faChevronUp,
|
||||
faClock,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Button, ButtonGroup, InputGroup, Form } from "react-bootstrap";
|
||||
import { Icon } from "src/components/Shared";
|
||||
import Icon from "src/components/Shared/Icon";
|
||||
import { DurationUtils } from "src/utils";
|
||||
|
||||
interface IProps {
|
||||
@@ -61,7 +66,7 @@ export const DurationInput: React.FC<IProps> = (props: IProps) => {
|
||||
disabled={props.disabled}
|
||||
onClick={() => increment()}
|
||||
>
|
||||
<Icon icon="chevron-up" />
|
||||
<Icon icon={faChevronUp} />
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
@@ -69,7 +74,7 @@ export const DurationInput: React.FC<IProps> = (props: IProps) => {
|
||||
disabled={props.disabled}
|
||||
onClick={() => decrement()}
|
||||
>
|
||||
<Icon icon="chevron-down" />
|
||||
<Icon icon={faChevronDown} />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
);
|
||||
@@ -86,7 +91,7 @@ export const DurationInput: React.FC<IProps> = (props: IProps) => {
|
||||
if (props.onReset) {
|
||||
return (
|
||||
<Button variant="secondary" onClick={onReset}>
|
||||
<Icon icon="clock" />
|
||||
<Icon icon={faClock} />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import React, { useState } from "react";
|
||||
import { Form } from "react-bootstrap";
|
||||
import { mutateExportObjects } from "src/core/StashService";
|
||||
import { Modal } from "src/components/Shared";
|
||||
import { useToast } from "src/hooks";
|
||||
import { downloadFile } from "src/utils";
|
||||
import Modal from "src/components/Shared/Modal";
|
||||
import useToast from "src/hooks/Toast";
|
||||
import downloadFile from "src/utils/download";
|
||||
import { ExportObjectsInput } from "src/core/generated-graphql";
|
||||
import { useIntl } from "react-intl";
|
||||
import { faCogs } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IExportDialogProps {
|
||||
exportInput: ExportObjectsInput;
|
||||
@@ -47,7 +48,7 @@ export const ExportDialog: React.FC<IExportDialogProps> = (
|
||||
return (
|
||||
<Modal
|
||||
show
|
||||
icon="cogs"
|
||||
icon={faCogs}
|
||||
header={intl.formatMessage({ id: "dialogs.export_title" })}
|
||||
accept={{
|
||||
onClick: onExport,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React, { useEffect, useState, useMemo } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { Button, InputGroup, Form } from "react-bootstrap";
|
||||
import { debounce } from "lodash";
|
||||
import { Icon, LoadingIndicator } from "src/components/Shared";
|
||||
import debounce from "lodash-es/debounce";
|
||||
import Icon from "src/components/Shared/Icon";
|
||||
import LoadingIndicator from "src/components/Shared/LoadingIndicator";
|
||||
import { useDirectory } from "src/core/StashService";
|
||||
import { faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IProps {
|
||||
currentDirectory: string;
|
||||
@@ -87,7 +89,7 @@ export const FolderSelect: React.FC<IProps> = ({
|
||||
{loading ? (
|
||||
<LoadingIndicator inline small message="" />
|
||||
) : (
|
||||
<Icon icon="times" color="red" className="ml-3" />
|
||||
<Icon icon={faTimes} color="red" className="ml-3" />
|
||||
)}
|
||||
</InputGroup.Append>
|
||||
) : undefined}
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
import React from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { IconProp, SizeProp, library } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faStar as fasStar } from "@fortawesome/free-solid-svg-icons";
|
||||
import {
|
||||
faCheckCircle as farCheckCircle,
|
||||
faStar as farStar,
|
||||
} from "@fortawesome/free-regular-svg-icons";
|
||||
|
||||
// need these to use far and fas styles of stars
|
||||
library.add(fasStar, farStar, farCheckCircle);
|
||||
import { IconDefinition, SizeProp } from "@fortawesome/fontawesome-svg-core";
|
||||
|
||||
interface IIcon {
|
||||
icon: IconProp;
|
||||
icon: IconDefinition;
|
||||
className?: string;
|
||||
color?: string;
|
||||
size?: SizeProp;
|
||||
|
||||
@@ -8,8 +8,9 @@ import {
|
||||
Row,
|
||||
} from "react-bootstrap";
|
||||
import { useIntl } from "react-intl";
|
||||
import { Modal } from ".";
|
||||
import Modal from "./Modal";
|
||||
import Icon from "./Icon";
|
||||
import { faFile, faLink } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface IImageInput {
|
||||
isEditing: boolean;
|
||||
@@ -100,7 +101,7 @@ export const ImageInput: React.FC<IImageInput> = ({
|
||||
<div>
|
||||
<Form.Label className="image-input">
|
||||
<Button variant="secondary">
|
||||
<Icon icon="file" className="fa-fw" />
|
||||
<Icon icon={faFile} className="fa-fw" />
|
||||
<span>{intl.formatMessage({ id: "actions.from_file" })}</span>
|
||||
</Button>
|
||||
<Form.Control
|
||||
@@ -112,7 +113,7 @@ export const ImageInput: React.FC<IImageInput> = ({
|
||||
</div>
|
||||
<div>
|
||||
<Button className="minimal" onClick={() => setIsShowDialog(true)}>
|
||||
<Icon icon="link" className="fa-fw" />
|
||||
<Icon icon={faLink} className="fa-fw" />
|
||||
<span>{intl.formatMessage({ id: "actions.from_url" })}</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user