diff --git a/graphql/documents/data/config.graphql b/graphql/documents/data/config.graphql index bb2e41d8a0d..9b502c9e056 100644 --- a/graphql/documents/data/config.graphql +++ b/graphql/documents/data/config.graphql @@ -58,6 +58,11 @@ fragment ConfigInterfaceData on ConfigInterfaceResult { cssEnabled language slideshowDelay + disabledDropdownCreate { + performer + tag + studio + } handyKey funscriptOffset } diff --git a/graphql/schema/types/config.graphql b/graphql/schema/types/config.graphql index 475d4b27231..1f65a5dc02c 100644 --- a/graphql/schema/types/config.graphql +++ b/graphql/schema/types/config.graphql @@ -188,6 +188,12 @@ type ConfigGeneralResult { stashBoxes: [StashBox!]! } +input ConfigDisableDropdownCreateInput { + performer: Boolean + tag: Boolean + studio: Boolean +} + input ConfigInterfaceInput { """Ordered list of items that should be shown in the menu""" menuItems: [String!] @@ -210,12 +216,20 @@ input ConfigInterfaceInput { language: String """Slideshow Delay""" slideshowDelay: Int + """Set to true to disable creating new objects via the dropdown menus""" + disableDropdownCreate: ConfigDisableDropdownCreateInput """Handy Connection Key""" handyKey: String """Funscript Time Offset""" funscriptOffset: Int } +type ConfigDisableDropdownCreate { + performer: Boolean! + tag: Boolean! + studio: Boolean! +} + type ConfigInterfaceResult { """Ordered list of items that should be shown in the menu""" menuItems: [String!] @@ -238,6 +252,8 @@ type ConfigInterfaceResult { language: String """Slideshow Delay""" slideshowDelay: Int + """Fields are true if creating via dropdown menus are disabled""" + disabledDropdownCreate: ConfigDisableDropdownCreate! """Handy Connection Key""" handyKey: String """Funscript Time Offset""" diff --git a/pkg/api/resolver_mutation_configure.go b/pkg/api/resolver_mutation_configure.go index 6672a199330..e864cd616b5 100644 --- a/pkg/api/resolver_mutation_configure.go +++ b/pkg/api/resolver_mutation_configure.go @@ -225,18 +225,20 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.Co func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models.ConfigInterfaceInput) (*models.ConfigInterfaceResult, error) { c := config.GetInstance() - if input.MenuItems != nil { - c.Set(config.MenuItems, input.MenuItems) - } - if input.SoundOnPreview != nil { - c.Set(config.SoundOnPreview, *input.SoundOnPreview) + setBool := func(key string, v *bool) { + if v != nil { + c.Set(key, *v) + } } - if input.WallShowTitle != nil { - c.Set(config.WallShowTitle, *input.WallShowTitle) + if input.MenuItems != nil { + c.Set(config.MenuItems, input.MenuItems) } + setBool(config.SoundOnPreview, input.SoundOnPreview) + setBool(config.WallShowTitle, input.WallShowTitle) + if input.WallPlayback != nil { c.Set(config.WallPlayback, *input.WallPlayback) } @@ -245,13 +247,8 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models. c.Set(config.MaximumLoopDuration, *input.MaximumLoopDuration) } - if input.AutostartVideo != nil { - c.Set(config.AutostartVideo, *input.AutostartVideo) - } - - if input.ShowStudioAsText != nil { - c.Set(config.ShowStudioAsText, *input.ShowStudioAsText) - } + setBool(config.AutostartVideo, input.AutostartVideo) + setBool(config.ShowStudioAsText, input.ShowStudioAsText) if input.Language != nil { c.Set(config.Language, *input.Language) @@ -269,8 +266,13 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models. c.SetCSS(css) - if input.CSSEnabled != nil { - c.Set(config.CSSEnabled, *input.CSSEnabled) + setBool(config.CSSEnabled, input.CSSEnabled) + + if input.DisableDropdownCreate != nil { + ddc := input.DisableDropdownCreate + setBool(config.DisableDropdownCreatePerformer, ddc.Performer) + setBool(config.DisableDropdownCreateStudio, ddc.Studio) + setBool(config.DisableDropdownCreateTag, ddc.Tag) } if input.HandyKey != nil { diff --git a/pkg/api/resolver_query_configuration.go b/pkg/api/resolver_query_configuration.go index 28314811311..aa1dbf1df92 100644 --- a/pkg/api/resolver_query_configuration.go +++ b/pkg/api/resolver_query_configuration.go @@ -115,19 +115,20 @@ func makeConfigInterfaceResult() *models.ConfigInterfaceResult { scriptOffset := config.GetFunscriptOffset() return &models.ConfigInterfaceResult{ - MenuItems: menuItems, - SoundOnPreview: &soundOnPreview, - WallShowTitle: &wallShowTitle, - WallPlayback: &wallPlayback, - MaximumLoopDuration: &maximumLoopDuration, - AutostartVideo: &autostartVideo, - ShowStudioAsText: &showStudioAsText, - CSS: &css, - CSSEnabled: &cssEnabled, - Language: &language, - SlideshowDelay: &slideshowDelay, - HandyKey: &handyKey, - FunscriptOffset: &scriptOffset, + MenuItems: menuItems, + SoundOnPreview: &soundOnPreview, + WallShowTitle: &wallShowTitle, + WallPlayback: &wallPlayback, + MaximumLoopDuration: &maximumLoopDuration, + AutostartVideo: &autostartVideo, + ShowStudioAsText: &showStudioAsText, + CSS: &css, + CSSEnabled: &cssEnabled, + Language: &language, + SlideshowDelay: &slideshowDelay, + DisabledDropdownCreate: config.GetDisableDropdownCreate(), + HandyKey: &handyKey, + FunscriptOffset: &scriptOffset, } } diff --git a/pkg/manager/config/config.go b/pkg/manager/config/config.go index 92235394bad..be6b1784bc5 100644 --- a/pkg/manager/config/config.go +++ b/pkg/manager/config/config.go @@ -135,6 +135,13 @@ const ShowStudioAsText = "show_studio_as_text" const CSSEnabled = "cssEnabled" const WallPlayback = "wall_playback" const SlideshowDelay = "slideshow_delay" + +const ( + DisableDropdownCreatePerformer = "disable_dropdown_create.performer" + DisableDropdownCreateStudio = "disable_dropdown_create.studio" + DisableDropdownCreateTag = "disable_dropdown_create.tag" +) + const HandyKey = "handy_key" const FunscriptOffset = "funscript_offset" @@ -787,6 +794,17 @@ func (i *Instance) GetSlideshowDelay() int { return viper.GetInt(SlideshowDelay) } +func (i *Instance) GetDisableDropdownCreate() *models.ConfigDisableDropdownCreate { + i.Lock() + defer i.Unlock() + + return &models.ConfigDisableDropdownCreate{ + Performer: viper.GetBool(DisableDropdownCreatePerformer), + Studio: viper.GetBool(DisableDropdownCreateStudio), + Tag: viper.GetBool(DisableDropdownCreateTag), + } +} + func (i *Instance) GetCSSPath() string { i.RLock() defer i.RUnlock() diff --git a/ui/v2.5/src/App.tsx b/ui/v2.5/src/App.tsx index c1560d1f53a..071f9954bc9 100755 --- a/ui/v2.5/src/App.tsx +++ b/ui/v2.5/src/App.tsx @@ -31,6 +31,7 @@ import { Setup } from "./components/Setup/Setup"; import { Migrate } from "./components/Setup/Migrate"; import * as GQL from "./core/generated-graphql"; import { LoadingIndicator } from "./components/Shared"; +import ConfigurationProvider from "./hooks/Config"; initPolyfills(); @@ -138,12 +139,17 @@ export const App: React.FC = () => { return ( - - - {maybeRenderNavbar()} -
{renderContent()}
-
-
+ + + + {maybeRenderNavbar()} +
{renderContent()}
+
+
+
); diff --git a/ui/v2.5/src/components/Changelog/versions/v0110.md b/ui/v2.5/src/components/Changelog/versions/v0110.md index 49a53a284fb..1fc0897d3aa 100644 --- a/ui/v2.5/src/components/Changelog/versions/v0110.md +++ b/ui/v2.5/src/components/Changelog/versions/v0110.md @@ -1,2 +1,5 @@ +### ✨ New Features +* Added interface options to disable creating performers/studios/tags from dropdown selectors. ([#1814](https://github.com/stashapp/stash/pull/1814)) + ### 🐛 Bug fixes * Fix huge memory usage spike during clean task. ([#1805](https://github.com/stashapp/stash/pull/1805)) diff --git a/ui/v2.5/src/components/Galleries/GalleryCard.tsx b/ui/v2.5/src/components/Galleries/GalleryCard.tsx index 4f9a9ade037..5a72a737c48 100644 --- a/ui/v2.5/src/components/Galleries/GalleryCard.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryCard.tsx @@ -2,7 +2,6 @@ import { Button, ButtonGroup } from "react-bootstrap"; import React from "react"; import { Link } from "react-router-dom"; import * as GQL from "src/core/generated-graphql"; -import { useConfiguration } from "src/core/StashService"; import { GridCard, HoverPopover, @@ -12,6 +11,7 @@ import { } from "src/components/Shared"; import { PopoverCountButton } from "src/components/Shared/PopoverCountButton"; import { NavUtils, TextUtils } from "src/utils"; +import { ConfigurationContext } from "src/hooks/Config"; import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton"; import { RatingBanner } from "../Shared/RatingBanner"; @@ -24,9 +24,8 @@ interface IProps { } export const GalleryCard: React.FC = (props) => { - const config = useConfiguration(); - const showStudioAsText = - config?.data?.configuration.interface.showStudioAsText ?? false; + const { configuration } = React.useContext(ConfigurationContext); + const showStudioAsText = configuration?.interface.showStudioAsText ?? false; function maybeRenderScenePopoverButton() { if (props.gallery.scenes.length === 0) return; diff --git a/ui/v2.5/src/components/MainNavbar.tsx b/ui/v2.5/src/components/MainNavbar.tsx index 54f34d4a8d6..3cde0140fc0 100644 --- a/ui/v2.5/src/components/MainNavbar.tsx +++ b/ui/v2.5/src/components/MainNavbar.tsx @@ -13,8 +13,8 @@ import Mousetrap from "mousetrap"; import { SessionUtils } from "src/utils"; import { Icon } from "src/components/Shared"; +import { ConfigurationContext } from "src/hooks/Config"; import { Manual } from "./Help/Manual"; -import { useConfiguration } from "../core/StashService"; interface IMenuItem { name: string; @@ -120,7 +120,7 @@ const allMenuItems: IMenuItem[] = [ export const MainNavbar: React.FC = () => { const history = useHistory(); const location = useLocation(); - const { data: config, loading } = useConfiguration(); + const { configuration, loading } = React.useContext(ConfigurationContext); // Show all menu items by default, unless config says otherwise const [menuItems, setMenuItems] = useState(allMenuItems); @@ -129,7 +129,7 @@ export const MainNavbar: React.FC = () => { const [showManual, setShowManual] = useState(false); useEffect(() => { - const iCfg = config?.configuration?.interface; + const iCfg = configuration?.interface; if (iCfg?.menuItems) { setMenuItems( allMenuItems.filter((menuItem) => @@ -137,7 +137,7 @@ export const MainNavbar: React.FC = () => { ) ); } - }, [config]); + }, [configuration]); // react-bootstrap typing bug // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx index 948ad7c860a..f5fb7dad041 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx @@ -20,7 +20,6 @@ import { usePerformerCreate, useTagCreate, queryScrapePerformerURL, - useConfiguration, } from "src/core/StashService"; import { Icon, @@ -41,6 +40,7 @@ import { genderToString, stringToGender, } from "src/utils/gender"; +import { ConfigurationContext } from "src/hooks/Config"; import { PerformerScrapeDialog } from "./PerformerScrapeDialog"; import PerformerScrapeModal from "./PerformerScrapeModal"; import PerformerStashBoxModal, { IStashBox } from "./PerformerStashBoxModal"; @@ -88,7 +88,7 @@ export const PerformerEditPanel: React.FC = ({ const [scrapedPerformer, setScrapedPerformer] = useState< GQL.ScrapedPerformer | undefined >(); - const stashConfig = useConfiguration(); + const { configuration: stashConfig } = React.useContext(ConfigurationContext); const imageEncoding = ImageUtils.usePasteImage(onImageLoad, true); @@ -601,7 +601,7 @@ export const PerformerEditPanel: React.FC = ({ if (!performer) { return; } - const stashBoxes = stashConfig.data?.configuration.general.stashBoxes ?? []; + const stashBoxes = stashConfig?.general.stashBoxes ?? []; const popover = ( diff --git a/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx b/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx index 1610e253dbe..f04411be574 100644 --- a/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx +++ b/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx @@ -2,8 +2,8 @@ import React from "react"; import ReactJWPlayer from "react-jw-player"; import * as GQL from "src/core/generated-graphql"; -import { useConfiguration } from "src/core/StashService"; import { JWUtils, ScreenUtils } from "src/utils"; +import { ConfigurationContext } from "src/hooks/Config"; import { ScenePlayerScrubber } from "./ScenePlayerScrubber"; import { Interactive } from "../../utils/interactive"; @@ -366,16 +366,12 @@ export class ScenePlayerImpl extends React.Component< export const ScenePlayer: React.FC = ( props: IScenePlayerProps ) => { - const config = useConfiguration(); + const { configuration } = React.useContext(ConfigurationContext); return ( ); }; diff --git a/ui/v2.5/src/components/Scenes/SceneCard.tsx b/ui/v2.5/src/components/Scenes/SceneCard.tsx index 4936175f95b..15cc5858520 100644 --- a/ui/v2.5/src/components/Scenes/SceneCard.tsx +++ b/ui/v2.5/src/components/Scenes/SceneCard.tsx @@ -3,7 +3,6 @@ import { Button, ButtonGroup } from "react-bootstrap"; import { Link } from "react-router-dom"; import cx from "classnames"; import * as GQL from "src/core/generated-graphql"; -import { useConfiguration } from "src/core/StashService"; import { Icon, TagLink, @@ -13,6 +12,7 @@ import { } from "src/components/Shared"; import { TextUtils } from "src/utils"; import { SceneQueue } from "src/models/sceneQueue"; +import { ConfigurationContext } from "src/hooks/Config"; import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton"; import { GridCard } from "../Shared/GridCard"; import { RatingBanner } from "../Shared/RatingBanner"; @@ -80,15 +80,14 @@ interface ISceneCardProps { export const SceneCard: React.FC = ( props: ISceneCardProps ) => { - const config = useConfiguration(); + const { configuration } = React.useContext(ConfigurationContext); // studio image is missing if it uses the default const missingStudioImage = props.scene.studio?.image_path?.endsWith( "?default=true" ); const showStudioAsText = - missingStudioImage || - (config?.data?.configuration.interface.showStudioAsText ?? false); + missingStudioImage || (configuration?.interface.showStudioAsText ?? false); function maybeRenderSceneSpecsOverlay() { return ( @@ -327,9 +326,7 @@ export const SceneCard: React.FC = ( image={props.scene.paths.screenshot ?? undefined} video={props.scene.paths.preview ?? undefined} isPortrait={isPortrait()} - soundActive={ - config.data?.configuration?.interface?.soundOnPreview ?? false - } + soundActive={configuration?.interface?.soundOnPreview ?? false} /> {maybeRenderSceneSpecsOverlay()} diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx index fc2cbe54a7b..d55a0e360bd 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx @@ -18,7 +18,6 @@ import { useListSceneScrapers, useSceneUpdate, mutateReloadScrapers, - useConfiguration, queryScrapeSceneQueryFragment, } from "src/core/StashService"; import { @@ -35,6 +34,7 @@ import { ImageUtils, FormUtils, TextUtils, getStashIDs } from "src/utils"; import { MovieSelect } from "src/components/Shared/Select"; import { useFormik } from "formik"; import { Prompt } from "react-router"; +import { ConfigurationContext } from "src/hooks/Config"; import { SceneMovieTable } from "./SceneMovieTable"; import { RatingStars } from "./RatingStars"; import { SceneScrapeDialog } from "./SceneScrapeDialog"; @@ -76,7 +76,7 @@ export const SceneEditPanel: React.FC = ({ string | undefined >(scene.paths.screenshot ?? undefined); - const stashConfig = useConfiguration(); + const { configuration: stashConfig } = React.useContext(ConfigurationContext); // Network state const [isLoading, setIsLoading] = useState(false); @@ -380,7 +380,7 @@ export const SceneEditPanel: React.FC = ({ } function renderScrapeQueryMenu() { - const stashBoxes = stashConfig.data?.configuration.general.stashBoxes ?? []; + const stashBoxes = stashConfig?.general.stashBoxes ?? []; if (stashBoxes.length === 0 && queryableScrapers.length === 0) return; @@ -450,7 +450,7 @@ export const SceneEditPanel: React.FC = ({ }; function renderScraperMenu() { - const stashBoxes = stashConfig.data?.configuration.general.stashBoxes ?? []; + const stashBoxes = stashConfig?.general.stashBoxes ?? []; return ( = ( props: ISceneGenerateDialogProps ) => { - const { data, error, loading } = useConfiguration(); + const { configuration } = React.useContext(ConfigurationContext); const [sprites, setSprites] = useState(true); const [phashes, setPhashes] = useState(true); @@ -49,17 +47,16 @@ export const SceneGenerateDialog: React.FC = ( const Toast = useToast(); useEffect(() => { - if (!data?.configuration) return; + if (!configuration) return; - const conf = data.configuration; - if (conf.general) { - setPreviewSegments(conf.general.previewSegments); - setPreviewSegmentDuration(conf.general.previewSegmentDuration); - setPreviewExcludeStart(conf.general.previewExcludeStart); - setPreviewExcludeEnd(conf.general.previewExcludeEnd); - setPreviewPreset(conf.general.previewPreset); + if (configuration.general) { + setPreviewSegments(configuration.general.previewSegments); + setPreviewSegmentDuration(configuration.general.previewSegmentDuration); + setPreviewExcludeStart(configuration.general.previewExcludeStart); + setPreviewExcludeEnd(configuration.general.previewExcludeEnd); + setPreviewPreset(configuration.general.previewPreset); } - }, [data]); + }, [configuration]); async function onGenerate() { try { @@ -90,15 +87,6 @@ export const SceneGenerateDialog: React.FC = ( } } - if (error) { - Toast.error(error); - props.onClose(); - } - - if (loading) { - return <>; - } - return ( { const [language, setLanguage] = useState("en"); const [handyKey, setHandyKey] = useState(); const [funscriptOffset, setFunscriptOffset] = useState(0); + const [ + disableDropdownCreate, + setDisableDropdownCreate, + ] = useState({}); const [updateInterfaceConfig] = useConfigureInterface({ menuItems: menuItemIds, @@ -53,6 +58,7 @@ export const SettingsInterfacePanel: React.FC = () => { slideshowDelay, handyKey, funscriptOffset, + disableDropdownCreate, }); useEffect(() => { @@ -70,6 +76,11 @@ export const SettingsInterfacePanel: React.FC = () => { setSlideshowDelay(iCfg?.slideshowDelay ?? 5000); setHandyKey(iCfg?.handyKey ?? ""); setFunscriptOffset(iCfg?.funscriptOffset ?? 0); + setDisableDropdownCreate({ + performer: iCfg?.disabledDropdownCreate.performer, + studio: iCfg?.disabledDropdownCreate.studio, + tag: iCfg?.disabledDropdownCreate.tag, + }); }, [config]); async function onSave() { @@ -257,6 +268,64 @@ export const SettingsInterfacePanel: React.FC = () => { + +
{intl.formatMessage({ id: "config.ui.editing.heading" })}
+ + +
+ {intl.formatMessage({ + id: "config.ui.editing.disable_dropdown_create.heading", + })} +
+ { + setDisableDropdownCreate({ + ...disableDropdownCreate, + performer: !disableDropdownCreate.performer ?? true, + }); + }} + /> + + { + setDisableDropdownCreate({ + ...disableDropdownCreate, + studio: !disableDropdownCreate.studio ?? true, + }); + }} + /> + + { + setDisableDropdownCreate({ + ...disableDropdownCreate, + tag: !disableDropdownCreate.tag ?? true, + }); + }} + /> + + {intl.formatMessage({ + id: "config.ui.editing.disable_dropdown_create.description", + })} + +
+
+
{intl.formatMessage({ id: "config.ui.custom_css.heading" })}
void; @@ -13,9 +13,9 @@ export const DirectorySelectionDialog: React.FC props: IDirectorySelectionDialogProps ) => { const intl = useIntl(); - const { data } = useConfiguration(); + const { configuration } = React.useContext(ConfigurationContext); - const libraryPaths = data?.configuration.general.stashes.map((s) => s.path); + const libraryPaths = configuration?.general.stashes.map((s) => s.path); const [paths, setPaths] = useState([]); const [currentDirectory, setCurrentDirectory] = useState(""); diff --git a/ui/v2.5/src/components/Shared/Select.tsx b/ui/v2.5/src/components/Shared/Select.tsx index c134e784697..0de6a48cc0d 100644 --- a/ui/v2.5/src/components/Shared/Select.tsx +++ b/ui/v2.5/src/components/Shared/Select.tsx @@ -24,6 +24,7 @@ import { import { useToast } from "src/hooks"; import { TextUtils } from "src/utils"; import { SelectComponents } from "react-select/src/components"; +import { ConfigurationContext } from "src/hooks/Config"; export type ValidTypes = | GQL.SlimPerformerDataFragment @@ -400,6 +401,10 @@ export const PerformerSelect: React.FC = (props) => { const { data, loading } = useAllPerformersForFilter(); const [createPerformer] = usePerformerCreate(); + const { configuration } = React.useContext(ConfigurationContext); + const defaultCreatable = + !configuration?.interface.disabledDropdownCreate.performer ?? true; + const performers = data?.allPerformers ?? []; const onCreate = async (name: string) => { @@ -416,7 +421,7 @@ export const PerformerSelect: React.FC = (props) => { props.excludeIds ?? [], [props.excludeIds]); const studios = useMemo( () => @@ -542,7 +551,7 @@ export const StudioSelect: React.FC< isLoading={loading} items={studios} placeholder={props.noSelectionString ?? "Select studio..."} - creatable={props.creatable ?? true} + creatable={props.creatable ?? defaultCreatable} onCreate={onCreate} /> ); @@ -573,6 +582,10 @@ export const TagSelect: React.FC = ( const [createTag] = useTagCreate(); const placeholder = props.noSelectionString ?? "Select tags..."; + const { configuration } = React.useContext(ConfigurationContext); + const defaultCreatable = + !configuration?.interface.disabledDropdownCreate.tag ?? true; + const exclude = useMemo(() => props.excludeIds ?? [], [props.excludeIds]); const tags = useMemo( () => (data?.allTags ?? []).filter((tag) => !exclude.includes(tag.id)), @@ -675,7 +688,7 @@ export const TagSelect: React.FC = ( components={{ Option: TagOption }} isMulti={props.isMulti ?? false} items={tags} - creatable={props.creatable ?? true} + creatable={props.creatable ?? defaultCreatable} type="tags" placeholder={placeholder} isLoading={loading} diff --git a/ui/v2.5/src/components/Tagger/Tagger.tsx b/ui/v2.5/src/components/Tagger/Tagger.tsx index eaeb9165623..981fadc801d 100755 --- a/ui/v2.5/src/components/Tagger/Tagger.tsx +++ b/ui/v2.5/src/components/Tagger/Tagger.tsx @@ -6,10 +6,11 @@ import { useLocalForage } from "src/hooks"; import * as GQL from "src/core/generated-graphql"; import { LoadingIndicator } from "src/components/Shared"; -import { stashBoxSceneQuery, useConfiguration } from "src/core/StashService"; +import { stashBoxSceneQuery } from "src/core/StashService"; import { Manual } from "src/components/Help/Manual"; import { SceneQueue } from "src/models/sceneQueue"; +import { ConfigurationContext } from "src/hooks/Config"; import Config from "./Config"; import { LOCAL_FORAGE_KEY, ITaggerConfig, initialConfig } from "./constants"; import { TaggerList } from "./TaggerList"; @@ -20,7 +21,7 @@ interface ITaggerProps { } export const Tagger: React.FC = ({ scenes, queue }) => { - const stashConfig = useConfiguration(); + const { configuration: stashConfig } = React.useContext(ConfigurationContext); const [{ data: config }, setConfig] = useLocalForage( LOCAL_FORAGE_KEY, initialConfig @@ -63,20 +64,19 @@ export const Tagger: React.FC = ({ scenes, queue }) => { if (!config) return ; const savedEndpointIndex = - stashConfig.data?.configuration.general.stashBoxes.findIndex( + stashConfig?.general.stashBoxes.findIndex( (s) => s.endpoint === config.selectedEndpoint ) ?? -1; const selectedEndpointIndex = - savedEndpointIndex === -1 && - stashConfig.data?.configuration.general.stashBoxes.length + savedEndpointIndex === -1 && stashConfig?.general.stashBoxes.length ? 0 : savedEndpointIndex; const selectedEndpoint = - stashConfig.data?.configuration.general.stashBoxes[selectedEndpointIndex]; + stashConfig?.general.stashBoxes[selectedEndpointIndex]; function getEndpointIndex(endpoint: string) { return ( - stashConfig.data?.configuration.general.stashBoxes.findIndex( + stashConfig?.general.stashBoxes.findIndex( (s) => s.endpoint === endpoint ) ?? -1 ); diff --git a/ui/v2.5/src/components/Tagger/performers/Config.tsx b/ui/v2.5/src/components/Tagger/performers/Config.tsx index 24934fa892f..967fbb2a3a8 100644 --- a/ui/v2.5/src/components/Tagger/performers/Config.tsx +++ b/ui/v2.5/src/components/Tagger/performers/Config.tsx @@ -1,6 +1,6 @@ import React, { Dispatch, useState } from "react"; import { Badge, Button, Card, Collapse, Form } from "react-bootstrap"; -import { useConfiguration } from "src/core/StashService"; +import { ConfigurationContext } from "src/hooks/Config"; import { TextUtils } from "src/utils"; import { ITaggerConfig, PERFORMER_FIELDS } from "../constants"; @@ -13,7 +13,7 @@ interface IConfigProps { } const Config: React.FC = ({ show, config, setConfig }) => { - const stashConfig = useConfiguration(); + const { configuration: stashConfig } = React.useContext(ConfigurationContext); const [showExclusionModal, setShowExclusionModal] = useState(false); const excludedFields = config.excludedPerformerFields ?? []; @@ -26,7 +26,7 @@ const Config: React.FC = ({ show, config, setConfig }) => { }); }; - const stashBoxes = stashConfig.data?.configuration.general.stashBoxes ?? []; + const stashBoxes = stashConfig?.general.stashBoxes ?? []; const handleFieldSelect = (fields: string[]) => { setConfig({ ...config, excludedPerformerFields: fields }); @@ -77,13 +77,11 @@ const Config: React.FC = ({ show, config, setConfig }) => { onChange={handleInstanceSelect} > {!stashBoxes.length && } - {stashConfig.data?.configuration.general.stashBoxes.map( - (i) => ( - - ) - )} + {stashConfig?.general.stashBoxes.map((i) => ( + + ))}
diff --git a/ui/v2.5/src/components/Tagger/performers/PerformerTagger.tsx b/ui/v2.5/src/components/Tagger/performers/PerformerTagger.tsx index 82e9cc62263..0fe50cb3195 100755 --- a/ui/v2.5/src/components/Tagger/performers/PerformerTagger.tsx +++ b/ui/v2.5/src/components/Tagger/performers/PerformerTagger.tsx @@ -9,11 +9,11 @@ import * as GQL from "src/core/generated-graphql"; import { LoadingIndicator, Modal } from "src/components/Shared"; import { stashBoxPerformerQuery, - useConfiguration, useJobsSubscribe, mutateStashBoxBatchPerformerTag, } from "src/core/StashService"; import { Manual } from "src/components/Help/Manual"; +import { ConfigurationContext } from "src/hooks/Config"; import StashSearchResult from "./StashSearchResult"; import PerformerConfig from "./Config"; @@ -491,7 +491,7 @@ interface ITaggerProps { export const PerformerTagger: React.FC = ({ performers }) => { const jobsSubscribe = useJobsSubscribe(); - const stashConfig = useConfiguration(); + const { configuration: stashConfig } = React.useContext(ConfigurationContext); const [{ data: config }, setConfig] = useLocalForage( LOCAL_FORAGE_KEY, initialConfig @@ -524,16 +524,15 @@ export const PerformerTagger: React.FC = ({ performers }) => { if (!config) return ; const savedEndpointIndex = - stashConfig.data?.configuration.general.stashBoxes.findIndex( + stashConfig?.general.stashBoxes.findIndex( (s) => s.endpoint === config.selectedEndpoint ) ?? -1; const selectedEndpointIndex = - savedEndpointIndex === -1 && - stashConfig.data?.configuration.general.stashBoxes.length + savedEndpointIndex === -1 && stashConfig?.general.stashBoxes.length ? 0 : savedEndpointIndex; const selectedEndpoint = - stashConfig.data?.configuration.general.stashBoxes[selectedEndpointIndex]; + stashConfig?.general.stashBoxes[selectedEndpointIndex]; async function batchAdd(performerInput: string) { if (performerInput && selectedEndpoint) { @@ -641,7 +640,7 @@ export const PerformerTagger: React.FC = ({ performers }) => { }} isIdle={batchJobID === undefined} config={config} - stashBoxes={stashConfig.data?.configuration.general.stashBoxes} + stashBoxes={stashConfig?.general.stashBoxes} onBatchAdd={batchAdd} onBatchUpdate={batchUpdate} /> diff --git a/ui/v2.5/src/components/Wall/WallItem.tsx b/ui/v2.5/src/components/Wall/WallItem.tsx index a824ab1f611..32281658d5a 100644 --- a/ui/v2.5/src/components/Wall/WallItem.tsx +++ b/ui/v2.5/src/components/Wall/WallItem.tsx @@ -1,10 +1,10 @@ import React, { useRef, useState, useEffect } from "react"; import { Link } from "react-router-dom"; import * as GQL from "src/core/generated-graphql"; -import { useConfiguration } from "src/core/StashService"; import { TextUtils, NavUtils } from "src/utils"; import cx from "classnames"; import { SceneQueue } from "src/models/sceneQueue"; +import { ConfigurationContext } from "src/hooks/Config"; interface IWallItemProps { index?: number; @@ -105,10 +105,9 @@ const Preview: React.FC<{ export const WallItem: React.FC = (props: IWallItemProps) => { const [active, setActive] = useState(false); const wallItem = useRef() as React.MutableRefObject; - const config = useConfiguration(); + const { configuration: config } = React.useContext(ConfigurationContext); - const showTextContainer = - config.data?.configuration.interface.wallShowTitle ?? true; + const showTextContainer = config?.interface.wallShowTitle ?? true; const previews = props.sceneMarker ? { @@ -203,11 +202,7 @@ export const WallItem: React.FC = (props: IWallItemProps) => {
- + {renderText()}
diff --git a/ui/v2.5/src/hooks/Config.tsx b/ui/v2.5/src/hooks/Config.tsx new file mode 100644 index 00000000000..a7cd5b19858 --- /dev/null +++ b/ui/v2.5/src/hooks/Config.tsx @@ -0,0 +1,28 @@ +import React from "react"; +import * as GQL from "src/core/generated-graphql"; + +interface IContext { + configuration?: GQL.ConfigDataFragment; + loading?: boolean; +} + +export const ConfigurationContext = React.createContext({}); + +export const ConfigurationProvider: React.FC = ({ + loading, + configuration, + children, +}) => { + return ( + + {children} + + ); +}; + +export default ConfigurationProvider; diff --git a/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx b/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx index 8b5fc8932d6..27bbca91ba6 100644 --- a/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx +++ b/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx @@ -15,9 +15,9 @@ import debounce from "lodash/debounce"; import { Icon, LoadingIndicator } from "src/components/Shared"; import { useInterval, usePageVisibility } from "src/hooks"; -import { useConfiguration } from "src/core/StashService"; import { FormattedMessage, useIntl } from "react-intl"; import { DisplayMode, LightboxImage, ScrollMode } from "./LightboxImage"; +import { ConfigurationContext } from "../Config"; const CLASSNAME = "Lightbox"; const CLASSNAME_HEADER = `${CLASSNAME}-header`; @@ -93,11 +93,10 @@ export const LightboxComponent: React.FC = ({ const allowNavigation = images.length > 1 || pageCallback; const intl = useIntl(); - const config = useConfiguration(); + const { configuration: config } = React.useContext(ConfigurationContext); const userSelectedSlideshowDelayOrDefault = - config?.data?.configuration.interface.slideshowDelay ?? - DEFAULT_SLIDESHOW_DELAY; + config?.interface.slideshowDelay ?? DEFAULT_SLIDESHOW_DELAY; // slideshowInterval is used for controlling the logic // displaySlideshowInterval is for display purposes only diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index 5edfa4ac276..2d77a815b67 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -327,6 +327,13 @@ "heading": "Custom CSS", "option_label": "Custom CSS enabled" }, + "editing": { + "disable_dropdown_create": { + "heading": "Disable dropdown create", + "description": "Remove the ability to create new objects from the dropdown selectors" + }, + "heading": "Editing" + }, "handy_connection_key": { "description": "Handy connection key to use for interactive scenes.", "heading": "Handy Connection Key"