React code splitting (#2603)

* Code split using react lazy
* Split locales
* Move to lodash-es
* Import individual icons
This commit is contained in:
WithoutPants
2022-06-22 14:41:31 +10:00
committed by GitHub
parent 33b68b4464
commit 3b4b20e9b2
147 changed files with 969 additions and 610 deletions

View File

@@ -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",

View File

@@ -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;
// use en-GB as default messages if any messages aren't found in the chosen language
const [messages, setMessages] = useState<{}>();
useEffect(() => {
const setLocale = async () => {
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) => {
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;
}
}
);
const messages = flattenMessages(mergedMessages);
});
setMessages(flattenMessages(mergedMessages));
};
setLocale();
}, [language]);
const setupMatch = useRouteMatch(["/setup", "/migrate"]);
@@ -118,6 +131,7 @@ export const App: React.FC = () => {
}
return (
<Suspense fallback={<LoadingIndicator />}>
<Switch>
<Route exact path="/" component={FrontPage} />
<Route path="/scenes" component={Scenes} />
@@ -138,17 +152,24 @@ export const App: React.FC = () => {
<Route path="/migrate" component={Migrate} />
<Route component={PageNotFound} />
</Switch>
</Suspense>
);
}
return (
<ErrorBoundary>
<IntlProvider locale={language} messages={messages} formats={intlFormats}>
{messages ? (
<IntlProvider
locale={language}
messages={messages}
formats={intlFormats}
>
<ConfigurationProvider
configuration={config.data?.configuration}
loading={config.loading}
>
<ToastProvider>
<Suspense fallback={<LoadingIndicator />}>
<LightboxProvider>
<ManualProvider>
<InteractiveProvider>
@@ -157,13 +178,17 @@ export const App: React.FC = () => {
defaultTitle="Stash"
/>
{maybeRenderNavbar()}
<div className="main container-fluid">{renderContent()}</div>
<div className="main container-fluid">
{renderContent()}
</div>
</InteractiveProvider>
</ManualProvider>
</LightboxProvider>
</Suspense>
</ToastProvider>
</ConfigurationProvider>
</IntlProvider>
) : null}
</ErrorBoundary>
);
};

View File

@@ -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" />

View File

@@ -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;

View File

@@ -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>
</>
)}

View File

@@ -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>
}
>

View File

@@ -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>
)}

View File

@@ -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;

View File

@@ -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",

View File

@@ -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" },
{

View File

@@ -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>
);

View File

@@ -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 ? (

View File

@@ -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,
},
];

View File

@@ -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" />

View File

@@ -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",
},
];

View File

@@ -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);

View File

@@ -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);

View File

@@ -38,3 +38,5 @@ export const GalleryViewer: React.FC<IProps> = ({ galleryId }) => {
</div>
);
};
export default GalleryViewer;

View File

@@ -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;

View 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>
);
};

View File

@@ -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",

View File

@@ -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" },
{

View File

@@ -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}

View File

@@ -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

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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>
));

View File

@@ -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>

View File

@@ -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}

View File

@@ -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) {

View File

@@ -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>

View File

@@ -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>

View File

@@ -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" }) }

View File

@@ -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>

View File

@@ -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",

View File

@@ -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);

View File

@@ -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" }) }

View File

@@ -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>

View File

@@ -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>
)}

View File

@@ -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>

View File

@@ -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);

View File

@@ -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";

View File

@@ -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";

View File

@@ -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);

View File

@@ -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>

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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",

View File

@@ -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;

View 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();

View File

@@ -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;

View File

@@ -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" },
{

View File

@@ -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>
);

View File

@@ -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;

View File

@@ -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>

View File

@@ -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>
);
};

View File

@@ -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[];

View File

@@ -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;

View File

@@ -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>
);

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -175,3 +175,5 @@ export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
</dl>
);
};
export default SceneFileInfoPanel;

View File

@@ -15,3 +15,5 @@ export const SceneGalleriesPanel: React.FC<ISceneGalleriesPanelProps> = ({
return <div className="row justify-content-center">{cards}</div>;
};
export default SceneGalleriesPanel;

View File

@@ -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;

View File

@@ -89,3 +89,5 @@ export const SceneMarkersPanel: React.FC<ISceneMarkersPanelProps> = (
</div>
);
};
export default SceneMarkersPanel;

View File

@@ -23,3 +23,5 @@ export const SceneMoviePanel: FunctionComponent<ISceneMoviePanelProps> = (
</>
);
};
export default SceneMoviePanel;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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>
)}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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>
);
}

View File

@@ -148,3 +148,5 @@ export const Settings: React.FC = () => {
</Tab.Container>
);
};
export default Settings;

View File

@@ -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>
}

View File

@@ -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" />

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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()}>

View File

@@ -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>
</>
}

View File

@@ -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>
}
/>

View File

@@ -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: () => {

View File

@@ -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>

View File

@@ -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>
</>
),

View File

@@ -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>
);
}

View File

@@ -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>
);
};

View File

@@ -180,3 +180,5 @@ export const Migrate: React.FC = () => {
</Container>
);
};
export default Migrate;

View File

@@ -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;

View File

@@ -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>

View File

@@ -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}>

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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>
);
}

View File

@@ -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,

View File

@@ -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}

View File

@@ -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;

View File

@@ -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