From 8fdb58917860d7d7dfaadf2676c437158aed8ec4 Mon Sep 17 00:00:00 2001 From: Angelo Date: Thu, 26 Oct 2023 06:15:54 +0100 Subject: [PATCH] Commit --- ui/v2.5/src/components/List/ItemList.tsx | 64 +++- ui/v2.5/src/components/List/ListFilter.tsx | 2 +- .../components/List/ListOperationButtons.tsx | 6 + .../List/ListToggleConfigButtons.tsx | 339 ++++++++++++++++++ ui/v2.5/src/components/List/styles.scss | 7 + ui/v2.5/src/components/Studios/StudioList.tsx | 46 +++ ui/v2.5/src/components/Tags/TagList.tsx | 6 + ui/v2.5/src/locales/en-GB.json | 7 + ui/v2.5/src/models/list-filter/types.ts | 6 + 9 files changed, 481 insertions(+), 2 deletions(-) create mode 100644 ui/v2.5/src/components/List/ListToggleConfigButtons.tsx diff --git a/ui/v2.5/src/components/List/ItemList.tsx b/ui/v2.5/src/components/List/ItemList.tsx index 8b3aa5898b1..5cd226f4b3d 100644 --- a/ui/v2.5/src/components/List/ItemList.tsx +++ b/ui/v2.5/src/components/List/ItemList.tsx @@ -29,11 +29,16 @@ import { EditFilterDialog } from "src/components/List/EditFilterDialog"; import { ListFilter } from "./ListFilter"; import { FilterTags } from "./FilterTags"; import { ListViewOptions } from "./ListViewOptions"; +import { ListToggleConfigButtons } from "./ListToggleConfigButtons"; import { ListOperationButtons } from "./ListOperationButtons"; import { LoadingIndicator } from "../Shared/LoadingIndicator"; +import { ConfigMode } from "src/models/list-filter/types"; import { DisplayMode } from "src/models/list-filter/types"; import { ButtonToolbar } from "react-bootstrap"; - +import { + useConfiguration, + useConfigureUI, +} from "src/core/StashService"; export enum PersistanceLevel { // do not load default query or persist display mode NONE, @@ -91,6 +96,7 @@ interface IItemListProps { selectable?: boolean; alterQuery?: boolean; defaultZoomIndex?: number; + configOperations?: IItemListOperation[]; otherOperations?: IItemListOperation[]; renderContent: ( result: T, @@ -142,6 +148,7 @@ export function makeItemList({ persistState, zoomable, selectable, + configOperations, otherOperations, renderContent, renderEditDialog, @@ -362,6 +369,9 @@ export function makeItemList({ result.refetch(); } + function refetch() { + result.refetch(); + }; function onDelete() { setIsDeleteDialogOpen(true); } @@ -430,6 +440,51 @@ export function makeItemList({ ); } + const activePage = location.pathname.match(/^\/([^/]+)(?:\/[^/]+)?/)![1]; + const config = useConfiguration(); + const configSettings = { + showChildStudioContent: config.data?.configuration.ui.showChildStudioContent, + showChildTagContent: config.data?.configuration.ui.showChildTagContent, + showTagCardOnHover: config.data?.configuration.ui.showTagCardOnHover, + }; + + const [childActive, setChildActive] = useState(false); + const [saveUI] = useConfigureUI(); + + function onSetToggleChildren(mode: string) { + // Clone the config object to avoid mutating the original + const updatedConfig = { ...config.data?.configuration.ui }; + + switch (mode) { + case "studios": + updatedConfig.showChildStudioContent = !childActive; + setChildActive(!childActive); + break; + case "tags": + updatedConfig.showChildTagContent = !childActive; + setChildActive(!childActive); + break; + default: + break; + } + + // Now, save the updated config object + saveUI({ + variables: { + input: { + ...config.data?.configuration.ui, + ...updatedConfig, + }, + }, + }); + } + function onChangeConfigMode(configMode: ConfigMode) { + const newFilter = cloneDeep(filter); + //newFilter.configMode = configMode; + // updateFilter(newFilter); + // _onChangePage(1) + } + function onChangeDisplayMode(displayMode: DisplayMode) { const newFilter = cloneDeep(filter); newFilter.displayMode = displayMode; @@ -477,10 +532,17 @@ export function makeItemList({ onSelectAll={selectable ? onSelectAll : undefined} onSelectNone={selectable ? onSelectNone : undefined} otherOperations={operations} + configOperations={operations} itemsSelected={selectedIds.size > 0} onEdit={renderEditDialog ? onEdit : undefined} onDelete={renderDeleteDialog ? onDelete : undefined} /> + = ({ openFilterDialog, persistState, }) => { + const [customPageSizeShowing, setCustomPageSizeShowing] = useState(false); const [queryRef, setQueryFocus] = useFocus(); const [queryClearShowing, setQueryClearShowing] = useState( diff --git a/ui/v2.5/src/components/List/ListOperationButtons.tsx b/ui/v2.5/src/components/List/ListOperationButtons.tsx index c279020e9e0..dd3cf86f019 100644 --- a/ui/v2.5/src/components/List/ListOperationButtons.tsx +++ b/ui/v2.5/src/components/List/ListOperationButtons.tsx @@ -16,6 +16,7 @@ import { faTrash, } from "@fortawesome/free-solid-svg-icons"; + interface IListFilterOperation { text: string; onClick: () => void; @@ -31,6 +32,8 @@ interface IListOperationButtonsProps { onDelete?: () => void; itemsSelected?: boolean; otherOperations?: IListFilterOperation[]; + configOperations?: IListFilterOperation[]; + } export const ListOperationButtons: React.FC = ({ @@ -43,6 +46,8 @@ export const ListOperationButtons: React.FC = ({ }) => { const intl = useIntl(); + +//alert(JSON.stringify(location.pathname)); useEffect(() => { Mousetrap.bind("s a", () => onSelectAll?.()); Mousetrap.bind("s n", () => onSelectNone?.()); @@ -68,6 +73,7 @@ export const ListOperationButtons: React.FC = ({ }); function maybeRenderButtons() { + const buttons = (otherOperations ?? []).filter((o) => { if (!o.icon) { return false; diff --git a/ui/v2.5/src/components/List/ListToggleConfigButtons.tsx b/ui/v2.5/src/components/List/ListToggleConfigButtons.tsx new file mode 100644 index 00000000000..db914c88b6c --- /dev/null +++ b/ui/v2.5/src/components/List/ListToggleConfigButtons.tsx @@ -0,0 +1,339 @@ +import React, { useEffect, useState } from "react"; +import { useLocation } from "react-router-dom"; +import { + Button, + ButtonGroup, + Form, + OverlayTrigger, + Tooltip, +} from "react-bootstrap"; +import { ConfigMode } from "src/models/list-filter/types"; +import { useIntl } from "react-intl"; +import { Icon } from "../Shared/Icon"; +import { + faEye, + faEyeSlash, + faTree, + faCircleCheck, + faSitemap, + faIoxhost, + faLayerGroup, + faTags, + faVolumeXmark, + faVolumeHigh, +} from "@fortawesome/free-solid-svg-icons"; +import { useConfiguration, useConfigureUI } from "src/core/StashService"; +import { ConfigurationContext } from "src/hooks/Config"; +import { IconDefinition } from "@fortawesome/fontawesome-svg-core"; + +interface Page { + page: string; +} +interface IButtonItem { + name: string; + pages: Page[]; + tooltipDisabled: string; + tooltipEnabled: string; + iconDisabled: IconDefinition; + iconEnabled: IconDefinition; + mode: string; + configString: [key: string]; +} + +const allButtonItems: IButtonItem[] = [ + { + name: "audio", + pages: [ + { page: "scenes" }, + { page: "markers" }, + ], + tooltipDisabled: "studios-disabled", + tooltipEnabled: "studios-enabled", + iconDisabled: faVolumeXmark, + iconEnabled: faVolumeHigh, + mode: "audio", + configString: "config", + }, + { + name: "studios", + pages: [{ page: "studios/" }], + tooltipDisabled: "studios-disabled", + tooltipEnabled: "studios-enabled", + iconDisabled: faSitemap, + iconEnabled: faSitemap, + mode: "studios", + configString: ["config.data?.configuration?.ui?.showChildStudioContent"], + }, + { + name: "tags", + pages: [{ page: "tags/" }], + tooltipDisabled: "tags-disabled", + tooltipEnabled: "tags-enabled", + iconDisabled: faSitemap, + iconEnabled: faSitemap, + mode: "tags", + configString: "config", + }, + { + name: "hover", + pages: [ + { page: "scenes" }, + { page: "images" }, + { page: "galleries" }, + { page: "performers" }, + { page: "tags" }, + ], + tooltipDisabled: "tags-hover-disabled", + tooltipEnabled: "tags-hover-enabled", + iconDisabled: faLayerGroup, + iconEnabled: faLayerGroup, + mode: "tagsHover", + configString: "config", + }, +]; + +interface IListToggleConfigSettingsProps { + activePage: string; + configMode: ConfigMode; + settings: { + showChildStudioContent: boolean | undefined; + showChildTagContent: boolean | undefined; + showTagCardOnHover: boolean | undefined; + }; + onSetConfigMode: (m: ConfigMode) => void; + configModeOptions: ConfigMode[]; +} + +export const ListToggleConfigButtons: React.FC< + IListToggleConfigSettingsProps +> = ({ + activePage, + configMode, + settings, + onSetConfigMode, + configModeOptions, +}) => { + const intl = useIntl(); + const { configuration, loading } = React.useContext(ConfigurationContext); + const [buttonItems, setButtonItems] = useState(allButtonItems); + const location = useLocation(); + const pathStudios = location.pathname.includes("studios"); + const pathTags = location.pathname.includes("tags"); + + useEffect(() => {}, [configuration]); + + //const [activePage, setActivePage] = useState(); + const config = useConfiguration(); + const [saveUI] = useConfigureUI(); + + const [childActive, setChildActive] = useState(false); + const [toggleAudio, setToggleAudio] = useState(false); + const [toggleChildStudios, setToggleChildStudios] = useState(false); + const [toggleChildTags, setToggleChildTags] = useState(false); + const [toggleTagHoverActive, setToggleTagHoverActive] = useState(false); + + const audio = config.data?.configuration?.interface?.soundOnPreview; + const childStudio = config.data?.configuration?.ui?.showChildStudioContent; + + const childTag = config.data?.configuration?.ui?.showChildTagContent; + + const tagHover = config.data?.configuration?.ui?.showTagCardOnHover; + + useEffect(() => { + if (audio) { + + } + if (childStudio) { + setChildActive(true); + } + // setActivePage("tags") + if (childTag) { + setChildActive(true); + } + }, [childStudio, childTag]); + + function oTs(mode: number, updatedConfig: string) { + switch (mode) { + case 1: + updatedConfig.ui.showTagCardOnHover = !childActive; + setChildActive(!childActive); + onSetConfigMode(); + break; + case 2: + updatedConfig.ui.showTagCardOnHover = !childActive; + setChildActive(!childActive); + onSetConfigMode(); + break; + default: + break; + } + } + function onSetToggleChildren(mode: string) { + // Clone the config object to avoid mutating the original + const updatedConfig = { ...config.data?.configuration.ui }; + + const updatedConfigs = { ...config.data?.configuration.interface }; + + switch (mode) { + case "audio": + updatedConfigs.soundOnPreview = !childActive; + setChildActive(!childActive); + onSetConfigMode(); + break; + case "studios": + alert("dd") + updatedConfig.showChildStudioContent = !childActive; + setChildActive(!childActive); + onSetConfigMode(); + break; + case "tags": + updatedConfig.ui.showChildTagContent = !childActive; + setChildActive(!childActive); + onSetConfigMode(); + break; + case "tagsHover": + updatedConfig.ui.showTagCardOnHover = !childActive; + setChildActive(!childActive); + onSetConfigMode(); + break; + default: + break; + } + // Now, save the updated config object + saveUI({ + variables: { + input: { + ...config.data?.configuration.ui, + ...updatedConfig, + }, + }, + }); + } + const ss = JSON.stringify(config.data); + function maybeRenderChildButtons(mode: string) { + let setMode: string = ""; + let childToolTip: string = ""; + + switch (mode) { + case "studios": + setMode = "studios"; + childToolTip = "Toggle display of child studios"; + break; + case "tags": + setMode = "tags"; + childToolTip = "Toggle display child tags"; + break; + default: + // Handle the default case + setMode = ""; + childToolTip = ""; + } + + function evaluateVariable(key: [key: string]) { + if (config[key] !== undefined && typeof config[key] === 'boolean') { + return config[key]; + } else { + return false; // Return false for any other cases + } + } + + // Filter the buttonItems to include only those with matching pages + const matchingPages = buttonItems.filter((item) => + item.pages.some((page) => location.pathname.includes(page.page)) + ); + + if (matchingPages.length > 0) { + return ( + <> + + {matchingPages.map((matchingPage) => ( + + {intl.formatMessage({ + id: `config_mode.${matchingPage.tooltipEnabled}`, + })} + + } + > + + + ))} + + + ); + } else { + return null; + } + } + + function maybeRenderConfigModeOptions() { + function getIcon(option: ConfigMode) { + switch (option) { + case ConfigMode.Studios: + return faTree; + case ConfigMode.Tags: + return faTree; + case ConfigMode.Hover: + return faTags; + } + } + + function getLabel(option: string) { + let configModeId = "unknown"; + switch (option) { + case "studios": + configModeId = "studios"; + break; + case "tags": + configModeId = "tags"; + break; + case "hover": + configModeId = "hover"; + break; + } + return intl.formatMessage({ id: `config_mode.${configModeId}` }); + } + + if (configModeOptions.length < 2) { + return; + } + + return ( + + {configModeOptions.map((option) => ( + {getLabel(option)} + } + > + + + ))} + + ); + } + + return <>{maybeRenderChildButtons(activePage || "")}; +}; diff --git a/ui/v2.5/src/components/List/styles.scss b/ui/v2.5/src/components/List/styles.scss index 1c6a390f411..1a716e292c5 100644 --- a/ui/v2.5/src/components/List/styles.scss +++ b/ui/v2.5/src/components/List/styles.scss @@ -359,3 +359,10 @@ input[type="range"].zoom-slider { .tilted { transform: rotate(45deg); } +.input-group { + padding: 0.15rem; +} + +.btn-group-actions { + margin-right: 0.5rem; +} \ No newline at end of file diff --git a/ui/v2.5/src/components/Studios/StudioList.tsx b/ui/v2.5/src/components/Studios/StudioList.tsx index 19710724b86..4640774ef03 100644 --- a/ui/v2.5/src/components/Studios/StudioList.tsx +++ b/ui/v2.5/src/components/Studios/StudioList.tsx @@ -6,6 +6,8 @@ import Mousetrap from "mousetrap"; import * as GQL from "src/core/generated-graphql"; import { queryFindStudios, + useConfiguration, + useConfigureUI, useFindStudios, useStudiosDestroy, } from "src/core/StashService"; @@ -47,6 +49,19 @@ export const StudioList: React.FC = ({ const history = useHistory(); const [isExportDialogOpen, setIsExportDialogOpen] = useState(false); const [isExportAll, setIsExportAll] = useState(false); + const config = useConfiguration(); + const [saveUI] = useConfigureUI(); + + const configOperations = [ + { + text: intl.formatMessage({ id: "actions.child_studios_scenes_include" }), + onClick: toggleChildStudios, + }, + { + text: intl.formatMessage({ id: "actions.child_studios_scenes_exclude" }), + onClick: toggleChildStudios, + }, + ]; const otherOperations = [ { @@ -108,6 +123,36 @@ export const StudioList: React.FC = ({ setIsExportDialogOpen(true); } + async function toggleChildStudios(result: GQL.FindStudiosQueryResult, + filter: ListFilterModel + ) { + if (result.data?.findStudios) { +alert("hello") + const currentConfig = config.data?.configuration.ui.showChildStudioContent; + const updateConfig = { ...config.data?.configuration.ui }; + + switch (currentConfig) { + case true: + updateConfig.showChildStudioContent = !currentConfig; + break; + case false: + updateConfig.showChildTagContent = !currentConfig; + break; + default: + break; + } + + saveUI({ + variables: { + input: { + ...config.data?.configuration.ui, + ...updateConfig, + }, + }, + }); + } + } + function renderContent( result: GQL.FindStudiosQueryResult, filter: ListFilterModel, @@ -191,6 +236,7 @@ export const StudioList: React.FC = ({ filterHook={filterHook} persistState={fromParent ? PersistanceLevel.NONE : PersistanceLevel.ALL} alterQuery={alterQuery} + configOperations={configOperations} otherOperations={otherOperations} addKeybinds={addKeybinds} renderContent={renderContent} diff --git a/ui/v2.5/src/components/Tags/TagList.tsx b/ui/v2.5/src/components/Tags/TagList.tsx index 98035f12ec0..932d1562c32 100644 --- a/ui/v2.5/src/components/Tags/TagList.tsx +++ b/ui/v2.5/src/components/Tags/TagList.tsx @@ -78,6 +78,12 @@ export const TagList: React.FC = ({ filterHook, alterQuery }) => { text: intl.formatMessage({ id: "actions.export_all" }), onClick: onExportAll, }, + { + text: intl.formatMessage({ id: "actions.child_studios_scenes_include" }), + }, + { + text: intl.formatMessage({ id: "actions.child_studios_scenes_exclude" }), + }, ]; function addKeybinds( diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index 073ce90432b..55aef4da172 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -13,6 +13,8 @@ "backup": "Backup", "browse_for_image": "Browse for imageā€¦", "cancel": "Cancel", + "child_studios_scenes_include": "Include child studios scenes", + "child_studios_scenes_exclude": "Exclude child studios scenes", "clean": "Clean", "clear": "Clear", "clear_back_image": "Clear back image", @@ -906,6 +908,11 @@ "dimensions": "Dimensions", "director": "Director", "disambiguation": "Disambiguation", + "config_mode": { + "studios": "Toggle display of child studios", + "tags": "Toggle display of child tags", + "hover": "Show tag card on hover" + }, "display_mode": { "grid": "Grid", "list": "List", diff --git a/ui/v2.5/src/models/list-filter/types.ts b/ui/v2.5/src/models/list-filter/types.ts index bd5aa783b39..368c9db9a3a 100644 --- a/ui/v2.5/src/models/list-filter/types.ts +++ b/ui/v2.5/src/models/list-filter/types.ts @@ -197,3 +197,9 @@ export type CriterionType = | "code" | "disambiguation" | "has_chapters"; + + export enum ConfigMode { + Studios, + Tags, + Hover, + } \ No newline at end of file