import React, { useCallback, useMemo } from "react"; import { Button, Form } from "react-bootstrap"; import { FormattedMessage, useIntl } from "react-intl"; import { DurationInput } from "src/components/Shared/DurationInput"; import { PercentInput } from "src/components/Shared/PercentInput"; import { LoadingIndicator } from "src/components/Shared/LoadingIndicator"; import { CheckboxGroup } from "./CheckboxGroup"; import { SettingSection } from "../SettingSection"; import { BooleanSetting, ModalSetting, NumberSetting, SelectSetting, StringSetting, } from "../Inputs"; import { useSettings } from "../context"; import TextUtils from "src/utils/text"; import * as GQL from "src/core/generated-graphql"; import { imageLightboxDisplayModeIntlMap, imageLightboxScrollModeIntlMap, } from "src/core/enums"; import { useInterfaceLocalForage } from "src/hooks/LocalForage"; import { ConnectionState, connectionStateLabel, InteractiveContext, } from "src/hooks/Interactive/context"; import { defaultRatingStarPrecision, defaultRatingSystemOptions, defaultRatingSystemType, RatingStarPrecision, ratingStarPrecisionIntlMap, ratingSystemIntlMap, RatingSystemType, } from "src/utils/rating"; import { imageWallDirectionIntlMap, ImageWallDirection, defaultImageWallOptions, defaultImageWallDirection, defaultImageWallMargin, } from "src/utils/imageWall"; import { defaultMaxOptionsShown } from "src/core/config"; import { PatchComponent } from "src/patch"; const allMenuItems = [ { id: "scenes", headingID: "scenes" }, { id: "images", headingID: "images" }, { id: "groups", headingID: "groups" }, { id: "markers", headingID: "markers" }, { id: "galleries", headingID: "galleries" }, { id: "performers", headingID: "performers" }, { id: "studios", headingID: "studios" }, { id: "tags", headingID: "tags" }, ]; export const SettingsInterfacePanel: React.FC = PatchComponent( "SettingsInterfacePanel", function SettingsInterfacePanel() { const intl = useIntl(); const { interface: iface, saveInterface, ui, saveUI, loading, error, } = useSettings(); // convert old movies menu item to groups const massageMenuItems = useCallback((menuItems: string[]) => { return menuItems.map((item) => { if (item === "movies") { return "groups"; } return item; }); }, []); const massagedMenuItems = useMemo(() => { if (!iface.menuItems) return iface.menuItems; return massageMenuItems(iface.menuItems); }, [iface.menuItems, massageMenuItems]); const { interactive, state: interactiveState, error: interactiveError, serverOffset: interactiveServerOffset, initialised: interactiveInitialised, initialise: initialiseInteractive, sync: interactiveSync, } = React.useContext(InteractiveContext); const [, setInterfaceLocalForage] = useInterfaceLocalForage(); function saveLightboxSettings(v: Partial) { // save in local forage as well for consistency setInterfaceLocalForage((prev) => ({ ...prev, imageLightbox: { ...prev.imageLightbox, ...v, }, })); saveInterface({ imageLightbox: { ...iface.imageLightbox, ...v, }, }); } function saveImageWallMargin(m: number) { saveUI({ imageWallOptions: { ...(ui.imageWallOptions ?? defaultImageWallOptions), margin: m, }, }); } function saveImageWallDirection(d: ImageWallDirection) { saveUI({ imageWallOptions: { ...(ui.imageWallOptions ?? defaultImageWallOptions), direction: d, }, }); } function saveRatingSystemType(t: RatingSystemType) { saveUI({ ratingSystemOptions: { ...ui.ratingSystemOptions, type: t, }, }); } function saveRatingSystemStarPrecision(p: RatingStarPrecision) { saveUI({ ratingSystemOptions: { ...(ui.ratingSystemOptions ?? defaultRatingSystemOptions), starPrecision: p, }, }); } function validateLocaleString(v: string) { if (!v) return; try { JSON.parse(v); } catch (e) { throw new Error( intl.formatMessage( { id: "errors.invalid_json_string" }, { error: (e as SyntaxError).message, } ) ); } } function validateJavascriptString(v: string) { if (!v) return; try { // creates a function from the string to validate it but does not execute it // eslint-disable-next-line @typescript-eslint/no-implied-eval new Function(v); } catch (e) { throw new Error( intl.formatMessage( { id: "errors.invalid_javascript_string" }, { error: (e as SyntaxError).message, } ) ); } } if (error) return

{error.message}

; if (loading) return ; // https://en.wikipedia.org/wiki/List_of_language_names return ( <> saveInterface({ language: v })} > saveInterface({ sfwContentMode: v })} />

{intl.formatMessage({ id: "config.ui.menu_items.heading", })}

{intl.formatMessage({ id: "config.ui.menu_items.description", })}
saveInterface({ menuItems: massageMenuItems(v) }) } />
saveUI({ abbreviateCounters: v })} /> saveInterface({ noBrowser: v })} /> saveInterface({ notificationsEnabled: v })} /> saveInterface({ wallShowTitle: v })} /> saveInterface({ soundOnPreview: v })} /> saveInterface({ wallPlayback: v })} > saveInterface({ showStudioAsText: v })} /> saveUI({ enableChromecast: v })} /> saveUI({ disableMobileMediaAutoRotateEnabled: v })} /> saveInterface({ showScrubber: v })} /> saveUI({ showRangeMarkers: v })} /> saveUI({ alwaysStartFromBeginning: v })} /> saveUI({ trackActivity: v })} /> saveUI({ vrTag: v })} /> id="ignore-interval" headingID="config.ui.minimum_play_percent.heading" subHeadingID="config.ui.minimum_play_percent.description" value={ui.minimumPlayPercent ?? 0} onChange={(v) => saveUI({ minimumPlayPercent: v })} disabled={!ui.trackActivity} renderField={(value, setValue) => ( setValue(interval ?? 0)} /> )} renderValue={(v) => { return {v}%; }} /> saveLightboxSettings({ slideshowDelay: v })} /> saveInterface({ autostartVideo: v })} /> saveInterface({ autostartVideoOnPlaySelected: v })} /> saveInterface({ continuePlaylistDefault: v })} /> id="max-loop-duration" headingID="config.ui.max_loop_duration.heading" subHeadingID="config.ui.max_loop_duration.description" value={iface.maximumLoopDuration ?? undefined} onChange={(v) => saveInterface({ maximumLoopDuration: v })} renderField={(value, setValue) => ( setValue(duration ?? 0)} /> )} renderValue={(v) => { return {TextUtils.secondsToTimestamp(v ?? 0)}; }} /> saveUI({ showAbLoopControls: v })} /> saveUI({ showTagCardOnHover: v })} /> saveUI({ showChildTagContent: v })} /> saveUI({ showChildStudioContent: v })} /> saveUI({ showLinksOnPerformerCard: v })} /> saveImageWallMargin(v)} /> saveImageWallDirection(v as ImageWallDirection)} > {Array.from(imageWallDirectionIntlMap.entries()).map((v) => ( ))} saveLightboxSettings({ slideshowDelay: v })} /> saveLightboxSettings({ displayMode: v as GQL.ImageLightboxDisplayMode, }) } > {Array.from(imageLightboxDisplayModeIntlMap.entries()).map((v) => ( ))} saveLightboxSettings({ scaleUp: v })} /> saveLightboxSettings({ resetZoomOnNav: v })} /> saveLightboxSettings({ scrollMode: v as GQL.ImageLightboxScrollMode, }) } > {Array.from(imageLightboxScrollModeIntlMap.entries()).map((v) => ( ))} saveLightboxSettings({ scrollAttemptsBeforeChange: v }) } /> saveLightboxSettings({ disableAnimation: v })} />

{intl.formatMessage({ id: "config.ui.detail.enable_background_image.heading", })}

{intl.formatMessage({ id: "config.ui.detail.enable_background_image.description", })}
saveUI({ enableMovieBackgroundImage: v })} /> saveUI({ enablePerformerBackgroundImage: v })} /> saveUI({ enableStudioBackgroundImage: v })} /> saveUI({ enableTagBackgroundImage: v })} />
saveUI({ showAllDetails: v })} /> saveUI({ compactExpandedDetails: v })} />

{intl.formatMessage({ id: "config.ui.editing.disable_dropdown_create.heading", })}

{intl.formatMessage({ id: "config.ui.editing.disable_dropdown_create.description", })}
saveInterface({ disableDropdownCreate: { ...iface.disableDropdownCreate, performer: v, }, }) } /> saveInterface({ disableDropdownCreate: { ...iface.disableDropdownCreate, studio: v, }, }) } /> saveInterface({ disableDropdownCreate: { ...iface.disableDropdownCreate, tag: v, }, }) } /> saveInterface({ disableDropdownCreate: { ...iface.disableDropdownCreate, movie: v, }, }) } />
saveUI({ maxOptionsShown: v })} /> saveRatingSystemType(v as RatingSystemType)} > {Array.from(ratingSystemIntlMap.entries()).map((v) => ( ))} {(ui.ratingSystemOptions?.type ?? defaultRatingSystemType) === RatingSystemType.Stars && ( saveRatingSystemStarPrecision(v as RatingStarPrecision) } > {Array.from(ratingStarPrecisionIntlMap.entries()).map((v) => ( ))} )} saveInterface({ cssEnabled: v })} /> id="custom-css" headingID="config.ui.custom_css.heading" subHeadingID="config.ui.custom_css.description" value={iface.css ?? undefined} onChange={(v) => saveInterface({ css: v })} renderField={(value, setValue) => ( ) => setValue(e.currentTarget.value) } rows={16} className="text-input code" /> )} renderValue={() => { return <>; }} /> saveInterface({ javascriptEnabled: v })} /> id="custom-javascript" headingID="config.ui.custom_javascript.heading" subHeadingID="config.ui.custom_javascript.description" value={iface.javascript ?? undefined} onChange={(v) => saveInterface({ javascript: v })} validateChange={validateJavascriptString} renderField={(value, setValue, err) => ( <> ) => setValue(e.currentTarget.value) } rows={16} className="text-input code" isInvalid={!!err} /> {err} )} renderValue={() => { return <>; }} /> saveInterface({ customLocalesEnabled: v })} /> id="custom-locales" headingID="config.ui.custom_locales.heading" subHeadingID="config.ui.custom_locales.description" value={iface.customLocales ?? undefined} onChange={(v) => saveInterface({ customLocales: v })} validateChange={validateLocaleString} renderField={(value, setValue, err) => ( <> ) => setValue(e.currentTarget.value) } rows={16} className="text-input code" isInvalid={!!err} /> {err} )} renderValue={() => { return <>; }} /> saveInterface({ handyKey: v })} /> {interactive.handyKey && ( <>

{intl.formatMessage({ id: "config.ui.handy_connection.status.heading", })}

{interactiveError && : {interactiveError}}
{!interactiveInitialised && ( )}

{intl.formatMessage({ id: "config.ui.handy_connection.server_offset.heading", })}

{interactiveServerOffset.toFixed()}ms
{interactiveInitialised && ( )}
)} saveInterface({ funscriptOffset: v })} /> saveInterface({ useStashHostedFunscript: v })} />
); } );