mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 12:24:38 +03:00
Add option to disable create from dropdown (#1814)
* Convert config hooks to common context * Add option to disable creating from dropdown
This commit is contained in:
@@ -58,6 +58,11 @@ fragment ConfigInterfaceData on ConfigInterfaceResult {
|
|||||||
cssEnabled
|
cssEnabled
|
||||||
language
|
language
|
||||||
slideshowDelay
|
slideshowDelay
|
||||||
|
disabledDropdownCreate {
|
||||||
|
performer
|
||||||
|
tag
|
||||||
|
studio
|
||||||
|
}
|
||||||
handyKey
|
handyKey
|
||||||
funscriptOffset
|
funscriptOffset
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,6 +188,12 @@ type ConfigGeneralResult {
|
|||||||
stashBoxes: [StashBox!]!
|
stashBoxes: [StashBox!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input ConfigDisableDropdownCreateInput {
|
||||||
|
performer: Boolean
|
||||||
|
tag: Boolean
|
||||||
|
studio: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
input ConfigInterfaceInput {
|
input ConfigInterfaceInput {
|
||||||
"""Ordered list of items that should be shown in the menu"""
|
"""Ordered list of items that should be shown in the menu"""
|
||||||
menuItems: [String!]
|
menuItems: [String!]
|
||||||
@@ -210,12 +216,20 @@ input ConfigInterfaceInput {
|
|||||||
language: String
|
language: String
|
||||||
"""Slideshow Delay"""
|
"""Slideshow Delay"""
|
||||||
slideshowDelay: Int
|
slideshowDelay: Int
|
||||||
|
"""Set to true to disable creating new objects via the dropdown menus"""
|
||||||
|
disableDropdownCreate: ConfigDisableDropdownCreateInput
|
||||||
"""Handy Connection Key"""
|
"""Handy Connection Key"""
|
||||||
handyKey: String
|
handyKey: String
|
||||||
"""Funscript Time Offset"""
|
"""Funscript Time Offset"""
|
||||||
funscriptOffset: Int
|
funscriptOffset: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ConfigDisableDropdownCreate {
|
||||||
|
performer: Boolean!
|
||||||
|
tag: Boolean!
|
||||||
|
studio: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
type ConfigInterfaceResult {
|
type ConfigInterfaceResult {
|
||||||
"""Ordered list of items that should be shown in the menu"""
|
"""Ordered list of items that should be shown in the menu"""
|
||||||
menuItems: [String!]
|
menuItems: [String!]
|
||||||
@@ -238,6 +252,8 @@ type ConfigInterfaceResult {
|
|||||||
language: String
|
language: String
|
||||||
"""Slideshow Delay"""
|
"""Slideshow Delay"""
|
||||||
slideshowDelay: Int
|
slideshowDelay: Int
|
||||||
|
"""Fields are true if creating via dropdown menus are disabled"""
|
||||||
|
disabledDropdownCreate: ConfigDisableDropdownCreate!
|
||||||
"""Handy Connection Key"""
|
"""Handy Connection Key"""
|
||||||
handyKey: String
|
handyKey: String
|
||||||
"""Funscript Time Offset"""
|
"""Funscript Time Offset"""
|
||||||
|
|||||||
@@ -225,17 +225,19 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.Co
|
|||||||
|
|
||||||
func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models.ConfigInterfaceInput) (*models.ConfigInterfaceResult, error) {
|
func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models.ConfigInterfaceInput) (*models.ConfigInterfaceResult, error) {
|
||||||
c := config.GetInstance()
|
c := config.GetInstance()
|
||||||
|
|
||||||
|
setBool := func(key string, v *bool) {
|
||||||
|
if v != nil {
|
||||||
|
c.Set(key, *v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if input.MenuItems != nil {
|
if input.MenuItems != nil {
|
||||||
c.Set(config.MenuItems, input.MenuItems)
|
c.Set(config.MenuItems, input.MenuItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.SoundOnPreview != nil {
|
setBool(config.SoundOnPreview, input.SoundOnPreview)
|
||||||
c.Set(config.SoundOnPreview, *input.SoundOnPreview)
|
setBool(config.WallShowTitle, input.WallShowTitle)
|
||||||
}
|
|
||||||
|
|
||||||
if input.WallShowTitle != nil {
|
|
||||||
c.Set(config.WallShowTitle, *input.WallShowTitle)
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.WallPlayback != nil {
|
if input.WallPlayback != nil {
|
||||||
c.Set(config.WallPlayback, *input.WallPlayback)
|
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)
|
c.Set(config.MaximumLoopDuration, *input.MaximumLoopDuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.AutostartVideo != nil {
|
setBool(config.AutostartVideo, input.AutostartVideo)
|
||||||
c.Set(config.AutostartVideo, *input.AutostartVideo)
|
setBool(config.ShowStudioAsText, input.ShowStudioAsText)
|
||||||
}
|
|
||||||
|
|
||||||
if input.ShowStudioAsText != nil {
|
|
||||||
c.Set(config.ShowStudioAsText, *input.ShowStudioAsText)
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.Language != nil {
|
if input.Language != nil {
|
||||||
c.Set(config.Language, *input.Language)
|
c.Set(config.Language, *input.Language)
|
||||||
@@ -269,8 +266,13 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models.
|
|||||||
|
|
||||||
c.SetCSS(css)
|
c.SetCSS(css)
|
||||||
|
|
||||||
if input.CSSEnabled != nil {
|
setBool(config.CSSEnabled, input.CSSEnabled)
|
||||||
c.Set(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 {
|
if input.HandyKey != nil {
|
||||||
|
|||||||
@@ -115,19 +115,20 @@ func makeConfigInterfaceResult() *models.ConfigInterfaceResult {
|
|||||||
scriptOffset := config.GetFunscriptOffset()
|
scriptOffset := config.GetFunscriptOffset()
|
||||||
|
|
||||||
return &models.ConfigInterfaceResult{
|
return &models.ConfigInterfaceResult{
|
||||||
MenuItems: menuItems,
|
MenuItems: menuItems,
|
||||||
SoundOnPreview: &soundOnPreview,
|
SoundOnPreview: &soundOnPreview,
|
||||||
WallShowTitle: &wallShowTitle,
|
WallShowTitle: &wallShowTitle,
|
||||||
WallPlayback: &wallPlayback,
|
WallPlayback: &wallPlayback,
|
||||||
MaximumLoopDuration: &maximumLoopDuration,
|
MaximumLoopDuration: &maximumLoopDuration,
|
||||||
AutostartVideo: &autostartVideo,
|
AutostartVideo: &autostartVideo,
|
||||||
ShowStudioAsText: &showStudioAsText,
|
ShowStudioAsText: &showStudioAsText,
|
||||||
CSS: &css,
|
CSS: &css,
|
||||||
CSSEnabled: &cssEnabled,
|
CSSEnabled: &cssEnabled,
|
||||||
Language: &language,
|
Language: &language,
|
||||||
SlideshowDelay: &slideshowDelay,
|
SlideshowDelay: &slideshowDelay,
|
||||||
HandyKey: &handyKey,
|
DisabledDropdownCreate: config.GetDisableDropdownCreate(),
|
||||||
FunscriptOffset: &scriptOffset,
|
HandyKey: &handyKey,
|
||||||
|
FunscriptOffset: &scriptOffset,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,6 +135,13 @@ const ShowStudioAsText = "show_studio_as_text"
|
|||||||
const CSSEnabled = "cssEnabled"
|
const CSSEnabled = "cssEnabled"
|
||||||
const WallPlayback = "wall_playback"
|
const WallPlayback = "wall_playback"
|
||||||
const SlideshowDelay = "slideshow_delay"
|
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 HandyKey = "handy_key"
|
||||||
const FunscriptOffset = "funscript_offset"
|
const FunscriptOffset = "funscript_offset"
|
||||||
|
|
||||||
@@ -787,6 +794,17 @@ func (i *Instance) GetSlideshowDelay() int {
|
|||||||
return viper.GetInt(SlideshowDelay)
|
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 {
|
func (i *Instance) GetCSSPath() string {
|
||||||
i.RLock()
|
i.RLock()
|
||||||
defer i.RUnlock()
|
defer i.RUnlock()
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import { Setup } from "./components/Setup/Setup";
|
|||||||
import { Migrate } from "./components/Setup/Migrate";
|
import { Migrate } from "./components/Setup/Migrate";
|
||||||
import * as GQL from "./core/generated-graphql";
|
import * as GQL from "./core/generated-graphql";
|
||||||
import { LoadingIndicator } from "./components/Shared";
|
import { LoadingIndicator } from "./components/Shared";
|
||||||
|
import ConfigurationProvider from "./hooks/Config";
|
||||||
|
|
||||||
initPolyfills();
|
initPolyfills();
|
||||||
|
|
||||||
@@ -138,12 +139,17 @@ export const App: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<IntlProvider locale={language} messages={messages} formats={intlFormats}>
|
<IntlProvider locale={language} messages={messages} formats={intlFormats}>
|
||||||
<ToastProvider>
|
<ConfigurationProvider
|
||||||
<LightboxProvider>
|
configuration={config.data?.configuration}
|
||||||
{maybeRenderNavbar()}
|
loading={config.loading}
|
||||||
<div className="main container-fluid">{renderContent()}</div>
|
>
|
||||||
</LightboxProvider>
|
<ToastProvider>
|
||||||
</ToastProvider>
|
<LightboxProvider>
|
||||||
|
{maybeRenderNavbar()}
|
||||||
|
<div className="main container-fluid">{renderContent()}</div>
|
||||||
|
</LightboxProvider>
|
||||||
|
</ToastProvider>
|
||||||
|
</ConfigurationProvider>
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
|
### 🐛 Bug fixes
|
||||||
* Fix huge memory usage spike during clean task. ([#1805](https://github.com/stashapp/stash/pull/1805))
|
* Fix huge memory usage spike during clean task. ([#1805](https://github.com/stashapp/stash/pull/1805))
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Button, ButtonGroup } from "react-bootstrap";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { useConfiguration } from "src/core/StashService";
|
|
||||||
import {
|
import {
|
||||||
GridCard,
|
GridCard,
|
||||||
HoverPopover,
|
HoverPopover,
|
||||||
@@ -12,6 +11,7 @@ import {
|
|||||||
} from "src/components/Shared";
|
} from "src/components/Shared";
|
||||||
import { PopoverCountButton } from "src/components/Shared/PopoverCountButton";
|
import { PopoverCountButton } from "src/components/Shared/PopoverCountButton";
|
||||||
import { NavUtils, TextUtils } from "src/utils";
|
import { NavUtils, TextUtils } from "src/utils";
|
||||||
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
||||||
import { RatingBanner } from "../Shared/RatingBanner";
|
import { RatingBanner } from "../Shared/RatingBanner";
|
||||||
|
|
||||||
@@ -24,9 +24,8 @@ interface IProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const GalleryCard: React.FC<IProps> = (props) => {
|
export const GalleryCard: React.FC<IProps> = (props) => {
|
||||||
const config = useConfiguration();
|
const { configuration } = React.useContext(ConfigurationContext);
|
||||||
const showStudioAsText =
|
const showStudioAsText = configuration?.interface.showStudioAsText ?? false;
|
||||||
config?.data?.configuration.interface.showStudioAsText ?? false;
|
|
||||||
|
|
||||||
function maybeRenderScenePopoverButton() {
|
function maybeRenderScenePopoverButton() {
|
||||||
if (props.gallery.scenes.length === 0) return;
|
if (props.gallery.scenes.length === 0) return;
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import Mousetrap from "mousetrap";
|
|||||||
|
|
||||||
import { SessionUtils } from "src/utils";
|
import { SessionUtils } from "src/utils";
|
||||||
import { Icon } from "src/components/Shared";
|
import { Icon } from "src/components/Shared";
|
||||||
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
import { Manual } from "./Help/Manual";
|
import { Manual } from "./Help/Manual";
|
||||||
import { useConfiguration } from "../core/StashService";
|
|
||||||
|
|
||||||
interface IMenuItem {
|
interface IMenuItem {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -120,7 +120,7 @@ const allMenuItems: IMenuItem[] = [
|
|||||||
export const MainNavbar: React.FC = () => {
|
export const MainNavbar: React.FC = () => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { data: config, loading } = useConfiguration();
|
const { configuration, loading } = React.useContext(ConfigurationContext);
|
||||||
|
|
||||||
// Show all menu items by default, unless config says otherwise
|
// Show all menu items by default, unless config says otherwise
|
||||||
const [menuItems, setMenuItems] = useState<IMenuItem[]>(allMenuItems);
|
const [menuItems, setMenuItems] = useState<IMenuItem[]>(allMenuItems);
|
||||||
@@ -129,7 +129,7 @@ export const MainNavbar: React.FC = () => {
|
|||||||
const [showManual, setShowManual] = useState(false);
|
const [showManual, setShowManual] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const iCfg = config?.configuration?.interface;
|
const iCfg = configuration?.interface;
|
||||||
if (iCfg?.menuItems) {
|
if (iCfg?.menuItems) {
|
||||||
setMenuItems(
|
setMenuItems(
|
||||||
allMenuItems.filter((menuItem) =>
|
allMenuItems.filter((menuItem) =>
|
||||||
@@ -137,7 +137,7 @@ export const MainNavbar: React.FC = () => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [config]);
|
}, [configuration]);
|
||||||
|
|
||||||
// react-bootstrap typing bug
|
// react-bootstrap typing bug
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
usePerformerCreate,
|
usePerformerCreate,
|
||||||
useTagCreate,
|
useTagCreate,
|
||||||
queryScrapePerformerURL,
|
queryScrapePerformerURL,
|
||||||
useConfiguration,
|
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import {
|
import {
|
||||||
Icon,
|
Icon,
|
||||||
@@ -41,6 +40,7 @@ import {
|
|||||||
genderToString,
|
genderToString,
|
||||||
stringToGender,
|
stringToGender,
|
||||||
} from "src/utils/gender";
|
} from "src/utils/gender";
|
||||||
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
import { PerformerScrapeDialog } from "./PerformerScrapeDialog";
|
import { PerformerScrapeDialog } from "./PerformerScrapeDialog";
|
||||||
import PerformerScrapeModal from "./PerformerScrapeModal";
|
import PerformerScrapeModal from "./PerformerScrapeModal";
|
||||||
import PerformerStashBoxModal, { IStashBox } from "./PerformerStashBoxModal";
|
import PerformerStashBoxModal, { IStashBox } from "./PerformerStashBoxModal";
|
||||||
@@ -88,7 +88,7 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
|||||||
const [scrapedPerformer, setScrapedPerformer] = useState<
|
const [scrapedPerformer, setScrapedPerformer] = useState<
|
||||||
GQL.ScrapedPerformer | undefined
|
GQL.ScrapedPerformer | undefined
|
||||||
>();
|
>();
|
||||||
const stashConfig = useConfiguration();
|
const { configuration: stashConfig } = React.useContext(ConfigurationContext);
|
||||||
|
|
||||||
const imageEncoding = ImageUtils.usePasteImage(onImageLoad, true);
|
const imageEncoding = ImageUtils.usePasteImage(onImageLoad, true);
|
||||||
|
|
||||||
@@ -601,7 +601,7 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
|||||||
if (!performer) {
|
if (!performer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const stashBoxes = stashConfig.data?.configuration.general.stashBoxes ?? [];
|
const stashBoxes = stashConfig?.general.stashBoxes ?? [];
|
||||||
|
|
||||||
const popover = (
|
const popover = (
|
||||||
<Dropdown.Menu id="performer-scraper-popover">
|
<Dropdown.Menu id="performer-scraper-popover">
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactJWPlayer from "react-jw-player";
|
import ReactJWPlayer from "react-jw-player";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { useConfiguration } from "src/core/StashService";
|
|
||||||
import { JWUtils, ScreenUtils } from "src/utils";
|
import { JWUtils, ScreenUtils } from "src/utils";
|
||||||
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
import { ScenePlayerScrubber } from "./ScenePlayerScrubber";
|
import { ScenePlayerScrubber } from "./ScenePlayerScrubber";
|
||||||
import { Interactive } from "../../utils/interactive";
|
import { Interactive } from "../../utils/interactive";
|
||||||
|
|
||||||
@@ -366,16 +366,12 @@ export class ScenePlayerImpl extends React.Component<
|
|||||||
export const ScenePlayer: React.FC<IScenePlayerProps> = (
|
export const ScenePlayer: React.FC<IScenePlayerProps> = (
|
||||||
props: IScenePlayerProps
|
props: IScenePlayerProps
|
||||||
) => {
|
) => {
|
||||||
const config = useConfiguration();
|
const { configuration } = React.useContext(ConfigurationContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScenePlayerImpl
|
<ScenePlayerImpl
|
||||||
{...props}
|
{...props}
|
||||||
config={
|
config={configuration ? configuration.interface : undefined}
|
||||||
config.data && config.data.configuration
|
|
||||||
? config.data.configuration.interface
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { Button, ButtonGroup } from "react-bootstrap";
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { useConfiguration } from "src/core/StashService";
|
|
||||||
import {
|
import {
|
||||||
Icon,
|
Icon,
|
||||||
TagLink,
|
TagLink,
|
||||||
@@ -13,6 +12,7 @@ import {
|
|||||||
} from "src/components/Shared";
|
} from "src/components/Shared";
|
||||||
import { TextUtils } from "src/utils";
|
import { TextUtils } from "src/utils";
|
||||||
import { SceneQueue } from "src/models/sceneQueue";
|
import { SceneQueue } from "src/models/sceneQueue";
|
||||||
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
||||||
import { GridCard } from "../Shared/GridCard";
|
import { GridCard } from "../Shared/GridCard";
|
||||||
import { RatingBanner } from "../Shared/RatingBanner";
|
import { RatingBanner } from "../Shared/RatingBanner";
|
||||||
@@ -80,15 +80,14 @@ interface ISceneCardProps {
|
|||||||
export const SceneCard: React.FC<ISceneCardProps> = (
|
export const SceneCard: React.FC<ISceneCardProps> = (
|
||||||
props: ISceneCardProps
|
props: ISceneCardProps
|
||||||
) => {
|
) => {
|
||||||
const config = useConfiguration();
|
const { configuration } = React.useContext(ConfigurationContext);
|
||||||
|
|
||||||
// studio image is missing if it uses the default
|
// studio image is missing if it uses the default
|
||||||
const missingStudioImage = props.scene.studio?.image_path?.endsWith(
|
const missingStudioImage = props.scene.studio?.image_path?.endsWith(
|
||||||
"?default=true"
|
"?default=true"
|
||||||
);
|
);
|
||||||
const showStudioAsText =
|
const showStudioAsText =
|
||||||
missingStudioImage ||
|
missingStudioImage || (configuration?.interface.showStudioAsText ?? false);
|
||||||
(config?.data?.configuration.interface.showStudioAsText ?? false);
|
|
||||||
|
|
||||||
function maybeRenderSceneSpecsOverlay() {
|
function maybeRenderSceneSpecsOverlay() {
|
||||||
return (
|
return (
|
||||||
@@ -327,9 +326,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||||||
image={props.scene.paths.screenshot ?? undefined}
|
image={props.scene.paths.screenshot ?? undefined}
|
||||||
video={props.scene.paths.preview ?? undefined}
|
video={props.scene.paths.preview ?? undefined}
|
||||||
isPortrait={isPortrait()}
|
isPortrait={isPortrait()}
|
||||||
soundActive={
|
soundActive={configuration?.interface?.soundOnPreview ?? false}
|
||||||
config.data?.configuration?.interface?.soundOnPreview ?? false
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<RatingBanner rating={props.scene.rating} />
|
<RatingBanner rating={props.scene.rating} />
|
||||||
{maybeRenderSceneSpecsOverlay()}
|
{maybeRenderSceneSpecsOverlay()}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import {
|
|||||||
useListSceneScrapers,
|
useListSceneScrapers,
|
||||||
useSceneUpdate,
|
useSceneUpdate,
|
||||||
mutateReloadScrapers,
|
mutateReloadScrapers,
|
||||||
useConfiguration,
|
|
||||||
queryScrapeSceneQueryFragment,
|
queryScrapeSceneQueryFragment,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import {
|
import {
|
||||||
@@ -35,6 +34,7 @@ import { ImageUtils, FormUtils, TextUtils, getStashIDs } from "src/utils";
|
|||||||
import { MovieSelect } from "src/components/Shared/Select";
|
import { MovieSelect } from "src/components/Shared/Select";
|
||||||
import { useFormik } from "formik";
|
import { useFormik } from "formik";
|
||||||
import { Prompt } from "react-router";
|
import { Prompt } from "react-router";
|
||||||
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
import { SceneMovieTable } from "./SceneMovieTable";
|
import { SceneMovieTable } from "./SceneMovieTable";
|
||||||
import { RatingStars } from "./RatingStars";
|
import { RatingStars } from "./RatingStars";
|
||||||
import { SceneScrapeDialog } from "./SceneScrapeDialog";
|
import { SceneScrapeDialog } from "./SceneScrapeDialog";
|
||||||
@@ -76,7 +76,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||||||
string | undefined
|
string | undefined
|
||||||
>(scene.paths.screenshot ?? undefined);
|
>(scene.paths.screenshot ?? undefined);
|
||||||
|
|
||||||
const stashConfig = useConfiguration();
|
const { configuration: stashConfig } = React.useContext(ConfigurationContext);
|
||||||
|
|
||||||
// Network state
|
// Network state
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
@@ -380,7 +380,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderScrapeQueryMenu() {
|
function renderScrapeQueryMenu() {
|
||||||
const stashBoxes = stashConfig.data?.configuration.general.stashBoxes ?? [];
|
const stashBoxes = stashConfig?.general.stashBoxes ?? [];
|
||||||
|
|
||||||
if (stashBoxes.length === 0 && queryableScrapers.length === 0) return;
|
if (stashBoxes.length === 0 && queryableScrapers.length === 0) return;
|
||||||
|
|
||||||
@@ -450,7 +450,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
function renderScraperMenu() {
|
function renderScraperMenu() {
|
||||||
const stashBoxes = stashConfig.data?.configuration.general.stashBoxes ?? [];
|
const stashBoxes = stashConfig?.general.stashBoxes ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Form, Button, Collapse } from "react-bootstrap";
|
import { Form, Button, Collapse } from "react-bootstrap";
|
||||||
import {
|
import { mutateMetadataGenerate } from "src/core/StashService";
|
||||||
mutateMetadataGenerate,
|
|
||||||
useConfiguration,
|
|
||||||
} from "src/core/StashService";
|
|
||||||
import { Modal, Icon } from "src/components/Shared";
|
import { Modal, Icon } from "src/components/Shared";
|
||||||
import { useToast } from "src/hooks";
|
import { useToast } from "src/hooks";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
|
|
||||||
interface ISceneGenerateDialogProps {
|
interface ISceneGenerateDialogProps {
|
||||||
selectedIds: string[];
|
selectedIds: string[];
|
||||||
@@ -17,7 +15,7 @@ interface ISceneGenerateDialogProps {
|
|||||||
export const SceneGenerateDialog: React.FC<ISceneGenerateDialogProps> = (
|
export const SceneGenerateDialog: React.FC<ISceneGenerateDialogProps> = (
|
||||||
props: ISceneGenerateDialogProps
|
props: ISceneGenerateDialogProps
|
||||||
) => {
|
) => {
|
||||||
const { data, error, loading } = useConfiguration();
|
const { configuration } = React.useContext(ConfigurationContext);
|
||||||
|
|
||||||
const [sprites, setSprites] = useState(true);
|
const [sprites, setSprites] = useState(true);
|
||||||
const [phashes, setPhashes] = useState(true);
|
const [phashes, setPhashes] = useState(true);
|
||||||
@@ -49,17 +47,16 @@ export const SceneGenerateDialog: React.FC<ISceneGenerateDialogProps> = (
|
|||||||
const Toast = useToast();
|
const Toast = useToast();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data?.configuration) return;
|
if (!configuration) return;
|
||||||
|
|
||||||
const conf = data.configuration;
|
if (configuration.general) {
|
||||||
if (conf.general) {
|
setPreviewSegments(configuration.general.previewSegments);
|
||||||
setPreviewSegments(conf.general.previewSegments);
|
setPreviewSegmentDuration(configuration.general.previewSegmentDuration);
|
||||||
setPreviewSegmentDuration(conf.general.previewSegmentDuration);
|
setPreviewExcludeStart(configuration.general.previewExcludeStart);
|
||||||
setPreviewExcludeStart(conf.general.previewExcludeStart);
|
setPreviewExcludeEnd(configuration.general.previewExcludeEnd);
|
||||||
setPreviewExcludeEnd(conf.general.previewExcludeEnd);
|
setPreviewPreset(configuration.general.previewPreset);
|
||||||
setPreviewPreset(conf.general.previewPreset);
|
|
||||||
}
|
}
|
||||||
}, [data]);
|
}, [configuration]);
|
||||||
|
|
||||||
async function onGenerate() {
|
async function onGenerate() {
|
||||||
try {
|
try {
|
||||||
@@ -90,15 +87,6 @@ export const SceneGenerateDialog: React.FC<ISceneGenerateDialogProps> = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
|
||||||
Toast.error(error);
|
|
||||||
props.onClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
show
|
show
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useIntl } from "react-intl";
|
|||||||
import { DurationInput, LoadingIndicator } from "src/components/Shared";
|
import { DurationInput, LoadingIndicator } from "src/components/Shared";
|
||||||
import { useConfiguration, useConfigureInterface } from "src/core/StashService";
|
import { useConfiguration, useConfigureInterface } from "src/core/StashService";
|
||||||
import { useToast } from "src/hooks";
|
import { useToast } from "src/hooks";
|
||||||
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { CheckboxGroup } from "./CheckboxGroup";
|
import { CheckboxGroup } from "./CheckboxGroup";
|
||||||
|
|
||||||
const allMenuItems = [
|
const allMenuItems = [
|
||||||
@@ -38,6 +39,10 @@ export const SettingsInterfacePanel: React.FC = () => {
|
|||||||
const [language, setLanguage] = useState<string>("en");
|
const [language, setLanguage] = useState<string>("en");
|
||||||
const [handyKey, setHandyKey] = useState<string>();
|
const [handyKey, setHandyKey] = useState<string>();
|
||||||
const [funscriptOffset, setFunscriptOffset] = useState<number>(0);
|
const [funscriptOffset, setFunscriptOffset] = useState<number>(0);
|
||||||
|
const [
|
||||||
|
disableDropdownCreate,
|
||||||
|
setDisableDropdownCreate,
|
||||||
|
] = useState<GQL.ConfigDisableDropdownCreateInput>({});
|
||||||
|
|
||||||
const [updateInterfaceConfig] = useConfigureInterface({
|
const [updateInterfaceConfig] = useConfigureInterface({
|
||||||
menuItems: menuItemIds,
|
menuItems: menuItemIds,
|
||||||
@@ -53,6 +58,7 @@ export const SettingsInterfacePanel: React.FC = () => {
|
|||||||
slideshowDelay,
|
slideshowDelay,
|
||||||
handyKey,
|
handyKey,
|
||||||
funscriptOffset,
|
funscriptOffset,
|
||||||
|
disableDropdownCreate,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -70,6 +76,11 @@ export const SettingsInterfacePanel: React.FC = () => {
|
|||||||
setSlideshowDelay(iCfg?.slideshowDelay ?? 5000);
|
setSlideshowDelay(iCfg?.slideshowDelay ?? 5000);
|
||||||
setHandyKey(iCfg?.handyKey ?? "");
|
setHandyKey(iCfg?.handyKey ?? "");
|
||||||
setFunscriptOffset(iCfg?.funscriptOffset ?? 0);
|
setFunscriptOffset(iCfg?.funscriptOffset ?? 0);
|
||||||
|
setDisableDropdownCreate({
|
||||||
|
performer: iCfg?.disabledDropdownCreate.performer,
|
||||||
|
studio: iCfg?.disabledDropdownCreate.studio,
|
||||||
|
tag: iCfg?.disabledDropdownCreate.tag,
|
||||||
|
});
|
||||||
}, [config]);
|
}, [config]);
|
||||||
|
|
||||||
async function onSave() {
|
async function onSave() {
|
||||||
@@ -257,6 +268,64 @@ export const SettingsInterfacePanel: React.FC = () => {
|
|||||||
</Form.Text>
|
</Form.Text>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
|
|
||||||
|
<Form.Group>
|
||||||
|
<h5>{intl.formatMessage({ id: "config.ui.editing.heading" })}</h5>
|
||||||
|
|
||||||
|
<Form.Group>
|
||||||
|
<h6>
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "config.ui.editing.disable_dropdown_create.heading",
|
||||||
|
})}
|
||||||
|
</h6>
|
||||||
|
<Form.Check
|
||||||
|
id="disableDropdownCreate_performer"
|
||||||
|
checked={disableDropdownCreate.performer ?? false}
|
||||||
|
label={intl.formatMessage({
|
||||||
|
id: "performer",
|
||||||
|
})}
|
||||||
|
onChange={() => {
|
||||||
|
setDisableDropdownCreate({
|
||||||
|
...disableDropdownCreate,
|
||||||
|
performer: !disableDropdownCreate.performer ?? true,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Form.Check
|
||||||
|
id="disableDropdownCreate_studio"
|
||||||
|
checked={disableDropdownCreate.studio ?? false}
|
||||||
|
label={intl.formatMessage({
|
||||||
|
id: "studio",
|
||||||
|
})}
|
||||||
|
onChange={() => {
|
||||||
|
setDisableDropdownCreate({
|
||||||
|
...disableDropdownCreate,
|
||||||
|
studio: !disableDropdownCreate.studio ?? true,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Form.Check
|
||||||
|
id="disableDropdownCreate_tag"
|
||||||
|
checked={disableDropdownCreate.tag ?? false}
|
||||||
|
label={intl.formatMessage({
|
||||||
|
id: "tag",
|
||||||
|
})}
|
||||||
|
onChange={() => {
|
||||||
|
setDisableDropdownCreate({
|
||||||
|
...disableDropdownCreate,
|
||||||
|
tag: !disableDropdownCreate.tag ?? true,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Text className="text-muted">
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "config.ui.editing.disable_dropdown_create.description",
|
||||||
|
})}
|
||||||
|
</Form.Text>
|
||||||
|
</Form.Group>
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
<Form.Group>
|
<Form.Group>
|
||||||
<h5>{intl.formatMessage({ id: "config.ui.custom_css.heading" })}</h5>
|
<h5>{intl.formatMessage({ id: "config.ui.custom_css.heading" })}</h5>
|
||||||
<Form.Check
|
<Form.Check
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Button, Col, Form, Row } from "react-bootstrap";
|
import { Button, Col, Form, Row } from "react-bootstrap";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import { useConfiguration } from "src/core/StashService";
|
|
||||||
import { Icon, Modal } from "src/components/Shared";
|
import { Icon, Modal } from "src/components/Shared";
|
||||||
import { FolderSelect } from "src/components/Shared/FolderSelect/FolderSelect";
|
import { FolderSelect } from "src/components/Shared/FolderSelect/FolderSelect";
|
||||||
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
|
|
||||||
interface IDirectorySelectionDialogProps {
|
interface IDirectorySelectionDialogProps {
|
||||||
onClose: (paths?: string[]) => void;
|
onClose: (paths?: string[]) => void;
|
||||||
@@ -13,9 +13,9 @@ export const DirectorySelectionDialog: React.FC<IDirectorySelectionDialogProps>
|
|||||||
props: IDirectorySelectionDialogProps
|
props: IDirectorySelectionDialogProps
|
||||||
) => {
|
) => {
|
||||||
const intl = useIntl();
|
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<string[]>([]);
|
const [paths, setPaths] = useState<string[]>([]);
|
||||||
const [currentDirectory, setCurrentDirectory] = useState<string>("");
|
const [currentDirectory, setCurrentDirectory] = useState<string>("");
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
import { useToast } from "src/hooks";
|
import { useToast } from "src/hooks";
|
||||||
import { TextUtils } from "src/utils";
|
import { TextUtils } from "src/utils";
|
||||||
import { SelectComponents } from "react-select/src/components";
|
import { SelectComponents } from "react-select/src/components";
|
||||||
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
|
|
||||||
export type ValidTypes =
|
export type ValidTypes =
|
||||||
| GQL.SlimPerformerDataFragment
|
| GQL.SlimPerformerDataFragment
|
||||||
@@ -400,6 +401,10 @@ export const PerformerSelect: React.FC<IFilterProps> = (props) => {
|
|||||||
const { data, loading } = useAllPerformersForFilter();
|
const { data, loading } = useAllPerformersForFilter();
|
||||||
const [createPerformer] = usePerformerCreate();
|
const [createPerformer] = usePerformerCreate();
|
||||||
|
|
||||||
|
const { configuration } = React.useContext(ConfigurationContext);
|
||||||
|
const defaultCreatable =
|
||||||
|
!configuration?.interface.disabledDropdownCreate.performer ?? true;
|
||||||
|
|
||||||
const performers = data?.allPerformers ?? [];
|
const performers = data?.allPerformers ?? [];
|
||||||
|
|
||||||
const onCreate = async (name: string) => {
|
const onCreate = async (name: string) => {
|
||||||
@@ -416,7 +421,7 @@ export const PerformerSelect: React.FC<IFilterProps> = (props) => {
|
|||||||
<FilterSelectComponent
|
<FilterSelectComponent
|
||||||
{...props}
|
{...props}
|
||||||
isMulti={props.isMulti ?? false}
|
isMulti={props.isMulti ?? false}
|
||||||
creatable={props.creatable ?? true}
|
creatable={props.creatable ?? defaultCreatable}
|
||||||
onCreate={onCreate}
|
onCreate={onCreate}
|
||||||
type="performers"
|
type="performers"
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
@@ -436,6 +441,10 @@ export const StudioSelect: React.FC<
|
|||||||
const { data, loading } = useAllStudiosForFilter();
|
const { data, loading } = useAllStudiosForFilter();
|
||||||
const [createStudio] = useStudioCreate();
|
const [createStudio] = useStudioCreate();
|
||||||
|
|
||||||
|
const { configuration } = React.useContext(ConfigurationContext);
|
||||||
|
const defaultCreatable =
|
||||||
|
!configuration?.interface.disabledDropdownCreate.studio ?? true;
|
||||||
|
|
||||||
const exclude = useMemo(() => props.excludeIds ?? [], [props.excludeIds]);
|
const exclude = useMemo(() => props.excludeIds ?? [], [props.excludeIds]);
|
||||||
const studios = useMemo(
|
const studios = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -542,7 +551,7 @@ export const StudioSelect: React.FC<
|
|||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
items={studios}
|
items={studios}
|
||||||
placeholder={props.noSelectionString ?? "Select studio..."}
|
placeholder={props.noSelectionString ?? "Select studio..."}
|
||||||
creatable={props.creatable ?? true}
|
creatable={props.creatable ?? defaultCreatable}
|
||||||
onCreate={onCreate}
|
onCreate={onCreate}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -573,6 +582,10 @@ export const TagSelect: React.FC<IFilterProps & { excludeIds?: string[] }> = (
|
|||||||
const [createTag] = useTagCreate();
|
const [createTag] = useTagCreate();
|
||||||
const placeholder = props.noSelectionString ?? "Select tags...";
|
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 exclude = useMemo(() => props.excludeIds ?? [], [props.excludeIds]);
|
||||||
const tags = useMemo(
|
const tags = useMemo(
|
||||||
() => (data?.allTags ?? []).filter((tag) => !exclude.includes(tag.id)),
|
() => (data?.allTags ?? []).filter((tag) => !exclude.includes(tag.id)),
|
||||||
@@ -675,7 +688,7 @@ export const TagSelect: React.FC<IFilterProps & { excludeIds?: string[] }> = (
|
|||||||
components={{ Option: TagOption }}
|
components={{ Option: TagOption }}
|
||||||
isMulti={props.isMulti ?? false}
|
isMulti={props.isMulti ?? false}
|
||||||
items={tags}
|
items={tags}
|
||||||
creatable={props.creatable ?? true}
|
creatable={props.creatable ?? defaultCreatable}
|
||||||
type="tags"
|
type="tags"
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ import { useLocalForage } from "src/hooks";
|
|||||||
|
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { LoadingIndicator } from "src/components/Shared";
|
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 { Manual } from "src/components/Help/Manual";
|
||||||
|
|
||||||
import { SceneQueue } from "src/models/sceneQueue";
|
import { SceneQueue } from "src/models/sceneQueue";
|
||||||
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
import Config from "./Config";
|
import Config from "./Config";
|
||||||
import { LOCAL_FORAGE_KEY, ITaggerConfig, initialConfig } from "./constants";
|
import { LOCAL_FORAGE_KEY, ITaggerConfig, initialConfig } from "./constants";
|
||||||
import { TaggerList } from "./TaggerList";
|
import { TaggerList } from "./TaggerList";
|
||||||
@@ -20,7 +21,7 @@ interface ITaggerProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Tagger: React.FC<ITaggerProps> = ({ scenes, queue }) => {
|
export const Tagger: React.FC<ITaggerProps> = ({ scenes, queue }) => {
|
||||||
const stashConfig = useConfiguration();
|
const { configuration: stashConfig } = React.useContext(ConfigurationContext);
|
||||||
const [{ data: config }, setConfig] = useLocalForage<ITaggerConfig>(
|
const [{ data: config }, setConfig] = useLocalForage<ITaggerConfig>(
|
||||||
LOCAL_FORAGE_KEY,
|
LOCAL_FORAGE_KEY,
|
||||||
initialConfig
|
initialConfig
|
||||||
@@ -63,20 +64,19 @@ export const Tagger: React.FC<ITaggerProps> = ({ scenes, queue }) => {
|
|||||||
if (!config) return <LoadingIndicator />;
|
if (!config) return <LoadingIndicator />;
|
||||||
|
|
||||||
const savedEndpointIndex =
|
const savedEndpointIndex =
|
||||||
stashConfig.data?.configuration.general.stashBoxes.findIndex(
|
stashConfig?.general.stashBoxes.findIndex(
|
||||||
(s) => s.endpoint === config.selectedEndpoint
|
(s) => s.endpoint === config.selectedEndpoint
|
||||||
) ?? -1;
|
) ?? -1;
|
||||||
const selectedEndpointIndex =
|
const selectedEndpointIndex =
|
||||||
savedEndpointIndex === -1 &&
|
savedEndpointIndex === -1 && stashConfig?.general.stashBoxes.length
|
||||||
stashConfig.data?.configuration.general.stashBoxes.length
|
|
||||||
? 0
|
? 0
|
||||||
: savedEndpointIndex;
|
: savedEndpointIndex;
|
||||||
const selectedEndpoint =
|
const selectedEndpoint =
|
||||||
stashConfig.data?.configuration.general.stashBoxes[selectedEndpointIndex];
|
stashConfig?.general.stashBoxes[selectedEndpointIndex];
|
||||||
|
|
||||||
function getEndpointIndex(endpoint: string) {
|
function getEndpointIndex(endpoint: string) {
|
||||||
return (
|
return (
|
||||||
stashConfig.data?.configuration.general.stashBoxes.findIndex(
|
stashConfig?.general.stashBoxes.findIndex(
|
||||||
(s) => s.endpoint === endpoint
|
(s) => s.endpoint === endpoint
|
||||||
) ?? -1
|
) ?? -1
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { Dispatch, useState } from "react";
|
import React, { Dispatch, useState } from "react";
|
||||||
import { Badge, Button, Card, Collapse, Form } from "react-bootstrap";
|
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 { TextUtils } from "src/utils";
|
||||||
import { ITaggerConfig, PERFORMER_FIELDS } from "../constants";
|
import { ITaggerConfig, PERFORMER_FIELDS } from "../constants";
|
||||||
@@ -13,7 +13,7 @@ interface IConfigProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Config: React.FC<IConfigProps> = ({ show, config, setConfig }) => {
|
const Config: React.FC<IConfigProps> = ({ show, config, setConfig }) => {
|
||||||
const stashConfig = useConfiguration();
|
const { configuration: stashConfig } = React.useContext(ConfigurationContext);
|
||||||
const [showExclusionModal, setShowExclusionModal] = useState(false);
|
const [showExclusionModal, setShowExclusionModal] = useState(false);
|
||||||
|
|
||||||
const excludedFields = config.excludedPerformerFields ?? [];
|
const excludedFields = config.excludedPerformerFields ?? [];
|
||||||
@@ -26,7 +26,7 @@ const Config: React.FC<IConfigProps> = ({ show, config, setConfig }) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const stashBoxes = stashConfig.data?.configuration.general.stashBoxes ?? [];
|
const stashBoxes = stashConfig?.general.stashBoxes ?? [];
|
||||||
|
|
||||||
const handleFieldSelect = (fields: string[]) => {
|
const handleFieldSelect = (fields: string[]) => {
|
||||||
setConfig({ ...config, excludedPerformerFields: fields });
|
setConfig({ ...config, excludedPerformerFields: fields });
|
||||||
@@ -77,13 +77,11 @@ const Config: React.FC<IConfigProps> = ({ show, config, setConfig }) => {
|
|||||||
onChange={handleInstanceSelect}
|
onChange={handleInstanceSelect}
|
||||||
>
|
>
|
||||||
{!stashBoxes.length && <option>No instances found</option>}
|
{!stashBoxes.length && <option>No instances found</option>}
|
||||||
{stashConfig.data?.configuration.general.stashBoxes.map(
|
{stashConfig?.general.stashBoxes.map((i) => (
|
||||||
(i) => (
|
<option value={i.endpoint} key={i.endpoint}>
|
||||||
<option value={i.endpoint} key={i.endpoint}>
|
{i.endpoint}
|
||||||
{i.endpoint}
|
</option>
|
||||||
</option>
|
))}
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ import * as GQL from "src/core/generated-graphql";
|
|||||||
import { LoadingIndicator, Modal } from "src/components/Shared";
|
import { LoadingIndicator, Modal } from "src/components/Shared";
|
||||||
import {
|
import {
|
||||||
stashBoxPerformerQuery,
|
stashBoxPerformerQuery,
|
||||||
useConfiguration,
|
|
||||||
useJobsSubscribe,
|
useJobsSubscribe,
|
||||||
mutateStashBoxBatchPerformerTag,
|
mutateStashBoxBatchPerformerTag,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import { Manual } from "src/components/Help/Manual";
|
import { Manual } from "src/components/Help/Manual";
|
||||||
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
|
|
||||||
import StashSearchResult from "./StashSearchResult";
|
import StashSearchResult from "./StashSearchResult";
|
||||||
import PerformerConfig from "./Config";
|
import PerformerConfig from "./Config";
|
||||||
@@ -491,7 +491,7 @@ interface ITaggerProps {
|
|||||||
|
|
||||||
export const PerformerTagger: React.FC<ITaggerProps> = ({ performers }) => {
|
export const PerformerTagger: React.FC<ITaggerProps> = ({ performers }) => {
|
||||||
const jobsSubscribe = useJobsSubscribe();
|
const jobsSubscribe = useJobsSubscribe();
|
||||||
const stashConfig = useConfiguration();
|
const { configuration: stashConfig } = React.useContext(ConfigurationContext);
|
||||||
const [{ data: config }, setConfig] = useLocalForage<ITaggerConfig>(
|
const [{ data: config }, setConfig] = useLocalForage<ITaggerConfig>(
|
||||||
LOCAL_FORAGE_KEY,
|
LOCAL_FORAGE_KEY,
|
||||||
initialConfig
|
initialConfig
|
||||||
@@ -524,16 +524,15 @@ export const PerformerTagger: React.FC<ITaggerProps> = ({ performers }) => {
|
|||||||
if (!config) return <LoadingIndicator />;
|
if (!config) return <LoadingIndicator />;
|
||||||
|
|
||||||
const savedEndpointIndex =
|
const savedEndpointIndex =
|
||||||
stashConfig.data?.configuration.general.stashBoxes.findIndex(
|
stashConfig?.general.stashBoxes.findIndex(
|
||||||
(s) => s.endpoint === config.selectedEndpoint
|
(s) => s.endpoint === config.selectedEndpoint
|
||||||
) ?? -1;
|
) ?? -1;
|
||||||
const selectedEndpointIndex =
|
const selectedEndpointIndex =
|
||||||
savedEndpointIndex === -1 &&
|
savedEndpointIndex === -1 && stashConfig?.general.stashBoxes.length
|
||||||
stashConfig.data?.configuration.general.stashBoxes.length
|
|
||||||
? 0
|
? 0
|
||||||
: savedEndpointIndex;
|
: savedEndpointIndex;
|
||||||
const selectedEndpoint =
|
const selectedEndpoint =
|
||||||
stashConfig.data?.configuration.general.stashBoxes[selectedEndpointIndex];
|
stashConfig?.general.stashBoxes[selectedEndpointIndex];
|
||||||
|
|
||||||
async function batchAdd(performerInput: string) {
|
async function batchAdd(performerInput: string) {
|
||||||
if (performerInput && selectedEndpoint) {
|
if (performerInput && selectedEndpoint) {
|
||||||
@@ -641,7 +640,7 @@ export const PerformerTagger: React.FC<ITaggerProps> = ({ performers }) => {
|
|||||||
}}
|
}}
|
||||||
isIdle={batchJobID === undefined}
|
isIdle={batchJobID === undefined}
|
||||||
config={config}
|
config={config}
|
||||||
stashBoxes={stashConfig.data?.configuration.general.stashBoxes}
|
stashBoxes={stashConfig?.general.stashBoxes}
|
||||||
onBatchAdd={batchAdd}
|
onBatchAdd={batchAdd}
|
||||||
onBatchUpdate={batchUpdate}
|
onBatchUpdate={batchUpdate}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React, { useRef, useState, useEffect } from "react";
|
import React, { useRef, useState, useEffect } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { useConfiguration } from "src/core/StashService";
|
|
||||||
import { TextUtils, NavUtils } from "src/utils";
|
import { TextUtils, NavUtils } from "src/utils";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { SceneQueue } from "src/models/sceneQueue";
|
import { SceneQueue } from "src/models/sceneQueue";
|
||||||
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
|
|
||||||
interface IWallItemProps {
|
interface IWallItemProps {
|
||||||
index?: number;
|
index?: number;
|
||||||
@@ -105,10 +105,9 @@ const Preview: React.FC<{
|
|||||||
export const WallItem: React.FC<IWallItemProps> = (props: IWallItemProps) => {
|
export const WallItem: React.FC<IWallItemProps> = (props: IWallItemProps) => {
|
||||||
const [active, setActive] = useState(false);
|
const [active, setActive] = useState(false);
|
||||||
const wallItem = useRef() as React.MutableRefObject<HTMLDivElement>;
|
const wallItem = useRef() as React.MutableRefObject<HTMLDivElement>;
|
||||||
const config = useConfiguration();
|
const { configuration: config } = React.useContext(ConfigurationContext);
|
||||||
|
|
||||||
const showTextContainer =
|
const showTextContainer = config?.interface.wallShowTitle ?? true;
|
||||||
config.data?.configuration.interface.wallShowTitle ?? true;
|
|
||||||
|
|
||||||
const previews = props.sceneMarker
|
const previews = props.sceneMarker
|
||||||
? {
|
? {
|
||||||
@@ -203,11 +202,7 @@ export const WallItem: React.FC<IWallItemProps> = (props: IWallItemProps) => {
|
|||||||
<div className="wall-item">
|
<div className="wall-item">
|
||||||
<div className={`wall-item-container ${props.className}`} ref={wallItem}>
|
<div className={`wall-item-container ${props.className}`} ref={wallItem}>
|
||||||
<Link onClick={clickHandler} to={linkSrc} className="wall-item-anchor">
|
<Link onClick={clickHandler} to={linkSrc} className="wall-item-anchor">
|
||||||
<Preview
|
<Preview previews={previews} config={config} active={active} />
|
||||||
previews={previews}
|
|
||||||
config={config.data?.configuration}
|
|
||||||
active={active}
|
|
||||||
/>
|
|
||||||
{renderText()}
|
{renderText()}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
28
ui/v2.5/src/hooks/Config.tsx
Normal file
28
ui/v2.5/src/hooks/Config.tsx
Normal file
@@ -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<IContext>({});
|
||||||
|
|
||||||
|
export const ConfigurationProvider: React.FC<IContext> = ({
|
||||||
|
loading,
|
||||||
|
configuration,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<ConfigurationContext.Provider
|
||||||
|
value={{
|
||||||
|
configuration,
|
||||||
|
loading,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ConfigurationContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConfigurationProvider;
|
||||||
@@ -15,9 +15,9 @@ import debounce from "lodash/debounce";
|
|||||||
|
|
||||||
import { Icon, LoadingIndicator } from "src/components/Shared";
|
import { Icon, LoadingIndicator } from "src/components/Shared";
|
||||||
import { useInterval, usePageVisibility } from "src/hooks";
|
import { useInterval, usePageVisibility } from "src/hooks";
|
||||||
import { useConfiguration } from "src/core/StashService";
|
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { DisplayMode, LightboxImage, ScrollMode } from "./LightboxImage";
|
import { DisplayMode, LightboxImage, ScrollMode } from "./LightboxImage";
|
||||||
|
import { ConfigurationContext } from "../Config";
|
||||||
|
|
||||||
const CLASSNAME = "Lightbox";
|
const CLASSNAME = "Lightbox";
|
||||||
const CLASSNAME_HEADER = `${CLASSNAME}-header`;
|
const CLASSNAME_HEADER = `${CLASSNAME}-header`;
|
||||||
@@ -93,11 +93,10 @@ export const LightboxComponent: React.FC<IProps> = ({
|
|||||||
const allowNavigation = images.length > 1 || pageCallback;
|
const allowNavigation = images.length > 1 || pageCallback;
|
||||||
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const config = useConfiguration();
|
const { configuration: config } = React.useContext(ConfigurationContext);
|
||||||
|
|
||||||
const userSelectedSlideshowDelayOrDefault =
|
const userSelectedSlideshowDelayOrDefault =
|
||||||
config?.data?.configuration.interface.slideshowDelay ??
|
config?.interface.slideshowDelay ?? DEFAULT_SLIDESHOW_DELAY;
|
||||||
DEFAULT_SLIDESHOW_DELAY;
|
|
||||||
|
|
||||||
// slideshowInterval is used for controlling the logic
|
// slideshowInterval is used for controlling the logic
|
||||||
// displaySlideshowInterval is for display purposes only
|
// displaySlideshowInterval is for display purposes only
|
||||||
|
|||||||
@@ -327,6 +327,13 @@
|
|||||||
"heading": "Custom CSS",
|
"heading": "Custom CSS",
|
||||||
"option_label": "Custom CSS enabled"
|
"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": {
|
"handy_connection_key": {
|
||||||
"description": "Handy connection key to use for interactive scenes.",
|
"description": "Handy connection key to use for interactive scenes.",
|
||||||
"heading": "Handy Connection Key"
|
"heading": "Handy Connection Key"
|
||||||
|
|||||||
Reference in New Issue
Block a user