Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Persist lightbox settings #2406

Merged
merged 5 commits into from
Mar 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion graphql/documents/data/config.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,13 @@ fragment ConfigInterfaceData on ConfigInterfaceResult {
css
cssEnabled
language
slideshowDelay
imageLightbox {
slideshowDelay
displayMode
scaleUp
resetZoomOnNav
scrollMode
}
disableDropdownCreate {
performer
tag
Expand Down
37 changes: 34 additions & 3 deletions graphql/schema/types/config.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,33 @@ input ConfigDisableDropdownCreateInput {
studio: Boolean
}

enum ImageLightboxDisplayMode {
ORIGINAL
FIT_XY
FIT_X
}

enum ImageLightboxScrollMode {
ZOOM
PAN_Y
}

input ConfigImageLightboxInput {
slideshowDelay: Int
displayMode: ImageLightboxDisplayMode
scaleUp: Boolean
resetZoomOnNav: Boolean
scrollMode: ImageLightboxScrollMode
}

type ConfigImageLightboxResult {
slideshowDelay: Int
displayMode: ImageLightboxDisplayMode
scaleUp: Boolean
resetZoomOnNav: Boolean
scrollMode: ImageLightboxScrollMode
}

input ConfigInterfaceInput {
"""Ordered list of items that should be shown in the menu"""
menuItems: [String!]
Expand Down Expand Up @@ -229,9 +256,11 @@ input ConfigInterfaceInput {

"""Interface language"""
language: String

"""Slideshow Delay"""
slideshowDelay: Int
slideshowDelay: Int @deprecated(reason: "Use imageLightbox.slideshowDelay")

imageLightbox: ConfigImageLightboxInput

"""Set to true to disable creating new objects via the dropdown menus"""
disableDropdownCreate: ConfigDisableDropdownCreateInput
Expand Down Expand Up @@ -291,7 +320,9 @@ type ConfigInterfaceResult {
language: String

"""Slideshow Delay"""
slideshowDelay: Int
slideshowDelay: Int @deprecated(reason: "Use imageLightbox.slideshowDelay")

imageLightbox: ConfigImageLightboxResult!

"""Fields are true if creating via dropdown menus are disabled"""
disableDropdownCreate: ConfigDisableDropdownCreate!
Expand Down
22 changes: 21 additions & 1 deletion internal/api/resolver_mutation_configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,12 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models.
}
}

setString := func(key string, v *string) {
if v != nil {
c.Set(key, *v)
}
}

if input.MenuItems != nil {
c.Set(config.MenuItems, input.MenuItems)
}
Expand Down Expand Up @@ -316,8 +322,22 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models.
c.Set(config.Language, *input.Language)
}

// deprecated field
if input.SlideshowDelay != nil {
c.Set(config.SlideshowDelay, *input.SlideshowDelay)
c.Set(config.ImageLightboxSlideshowDelay, *input.SlideshowDelay)
}

if input.ImageLightbox != nil {
options := input.ImageLightbox

if options.SlideshowDelay != nil {
c.Set(config.ImageLightboxSlideshowDelay, *options.SlideshowDelay)
}

setString(config.ImageLightboxDisplayMode, (*string)(options.DisplayMode))
setBool(config.ImageLightboxScaleUp, options.ScaleUp)
setBool(config.ImageLightboxResetZoomOnNav, options.ResetZoomOnNav)
setString(config.ImageLightboxScrollMode, (*string)(options.ScrollMode))
}

if input.CSS != nil {
Expand Down
5 changes: 3 additions & 2 deletions internal/api/resolver_query_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,9 @@ func makeConfigInterfaceResult() *models.ConfigInterfaceResult {
css := config.GetCSS()
cssEnabled := config.GetCSSEnabled()
language := config.GetLanguage()
slideshowDelay := config.GetSlideshowDelay()
handyKey := config.GetHandyKey()
scriptOffset := config.GetFunscriptOffset()
imageLightboxOptions := config.GetImageLightboxOptions()

// FIXME - misnamed output field means we have redundant fields
disableDropdownCreate := config.GetDisableDropdownCreate()
Expand All @@ -163,7 +163,8 @@ func makeConfigInterfaceResult() *models.ConfigInterfaceResult {
CSS: &css,
CSSEnabled: &cssEnabled,
Language: &language,
SlideshowDelay: &slideshowDelay,

ImageLightbox: &imageLightboxOptions,

// FIXME - see above
DisabledDropdownCreate: disableDropdownCreate,
Expand Down
68 changes: 61 additions & 7 deletions internal/manager/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,15 @@ const (
WallPlayback = "wall_playback"
defaultWallPlayback = "video"

SlideshowDelay = "slideshow_delay"
defaultSlideshowDelay = 5000
// Image lightbox options
legacyImageLightboxSlideshowDelay = "slideshow_delay"
ImageLightboxSlideshowDelay = "image_lightbox.slideshow_delay"
ImageLightboxDisplayMode = "image_lightbox.display_mode"
ImageLightboxScaleUp = "image_lightbox.scale_up"
ImageLightboxResetZoomOnNav = "image_lightbox.reset_zoom_on_nav"
ImageLightboxScrollMode = "image_lightbox.scroll_mode"

defaultImageLightboxSlideshowDelay = 5000

DisableDropdownCreatePerformer = "disable_dropdown_create.performer"
DisableDropdownCreateStudio = "disable_dropdown_create.studio"
Expand Down Expand Up @@ -364,6 +371,18 @@ func (i *Instance) viper(key string) *viper.Viper {
return v
}

// viper returns the viper instance that has the key set. Returns nil
// if no instance has the key. Assumes read lock held.
func (i *Instance) viperWith(key string) *viper.Viper {
v := i.viper(key)

if v.IsSet(key) {
return v
}

return nil
}

func (i *Instance) HasOverride(key string) bool {
i.RLock()
defer i.RUnlock()
Expand Down Expand Up @@ -886,14 +905,49 @@ func (i *Instance) GetShowStudioAsText() bool {
return i.getBool(ShowStudioAsText)
}

func (i *Instance) GetSlideshowDelay() int {
func (i *Instance) getSlideshowDelay() int {
// assume have lock

ret := defaultImageLightboxSlideshowDelay
v := i.viper(ImageLightboxSlideshowDelay)
if v.IsSet(ImageLightboxSlideshowDelay) {
ret = v.GetInt(ImageLightboxSlideshowDelay)
} else {
// fallback to old location
v := i.viper(legacyImageLightboxSlideshowDelay)
if v.IsSet(legacyImageLightboxSlideshowDelay) {
ret = v.GetInt(legacyImageLightboxSlideshowDelay)
}
}

return ret
}

func (i *Instance) GetImageLightboxOptions() models.ConfigImageLightboxResult {
i.RLock()
defer i.RUnlock()

ret := defaultSlideshowDelay
v := i.viper(SlideshowDelay)
if v.IsSet(SlideshowDelay) {
ret = v.GetInt(SlideshowDelay)
delay := i.getSlideshowDelay()

ret := models.ConfigImageLightboxResult{
SlideshowDelay: &delay,
}

if v := i.viperWith(ImageLightboxDisplayMode); v != nil {
mode := models.ImageLightboxDisplayMode(v.GetString(ImageLightboxDisplayMode))
ret.DisplayMode = &mode
}
if v := i.viperWith(ImageLightboxScaleUp); v != nil {
value := v.GetBool(ImageLightboxScaleUp)
ret.ScaleUp = &value
}
if v := i.viperWith(ImageLightboxResetZoomOnNav); v != nil {
value := v.GetBool(ImageLightboxResetZoomOnNav)
ret.ResetZoomOnNav = &value
}
if v := i.viperWith(ImageLightboxScrollMode); v != nil {
mode := models.ImageLightboxScrollMode(v.GetString(ImageLightboxScrollMode))
ret.ScrollMode = &mode
}

return ret
Expand Down
3 changes: 2 additions & 1 deletion internal/manager/config/config_concurrency_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ func TestConcurrentConfigAccess(t *testing.T) {
i.Set(MaximumLoopDuration, i.GetMaximumLoopDuration())
i.Set(AutostartVideo, i.GetAutostartVideo())
i.Set(ShowStudioAsText, i.GetShowStudioAsText())
i.Set(SlideshowDelay, i.GetSlideshowDelay())
i.Set(legacyImageLightboxSlideshowDelay, *i.GetImageLightboxOptions().SlideshowDelay)
i.Set(ImageLightboxSlideshowDelay, *i.GetImageLightboxOptions().SlideshowDelay)
i.GetCSSPath()
i.GetCSS()
i.Set(CSSEnabled, i.GetCSSEnabled())
Expand Down
1 change: 1 addition & 0 deletions ui/v2.5/src/components/Changelog/versions/v0140.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
### 🎨 Improvements
* Maintain lightbox settings and add lightbox settings to Interface settings page. ([#2406](https://github.com/stashapp/stash/pull/2406))
* Image lightbox now transitions to next/previous image when scrolling in pan-Y mode. ([#2403](https://github.com/stashapp/stash/pull/2403))
* Allow customisation of UI theme color using `theme_color` property in `config.yml` ([#2365](https://github.com/stashapp/stash/pull/2365))
* Improved autotag performance. ([#2368](https://github.com/stashapp/stash/pull/2368))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ import {
} from "../Inputs";
import { SettingStateContext } from "../context";
import { DurationUtils } from "src/utils";
import * as GQL from "src/core/generated-graphql";
import {
imageLightboxDisplayModeIntlMap,
imageLightboxScrollModeIntlMap,
} from "src/core/enums";
import { useInterfaceLocalForage } from "src/hooks";

const allMenuItems = [
{ id: "scenes", headingID: "scenes" },
Expand All @@ -32,6 +38,28 @@ export const SettingsInterfacePanel: React.FC = () => {
SettingStateContext
);

const [, setInterfaceLocalForage] = useInterfaceLocalForage();

function saveLightboxSettings(v: Partial<GQL.ConfigImageLightboxInput>) {
// save in local forage as well for consistency
setInterfaceLocalForage((prev) => {
return {
...prev,
imageLightbox: {
...prev.imageLightbox,
...v,
},
};
});

saveInterface({
imageLightbox: {
...iface.imageLightbox,
...v,
},
});
}

if (error) return <h1>{error.message}</h1>;
if (loading) return <LoadingIndicator />;

Expand Down Expand Up @@ -195,13 +223,72 @@ export const SettingsInterfacePanel: React.FC = () => {
/>
</SettingSection>

<SettingSection headingID="config.ui.images.heading">
<SettingSection headingID="config.ui.image_lightbox.heading">
<NumberSetting
headingID="config.ui.slideshow_delay.heading"
subHeadingID="config.ui.slideshow_delay.description"
value={iface.slideshowDelay ?? undefined}
onChange={(v) => saveInterface({ slideshowDelay: v })}
value={iface.imageLightbox?.slideshowDelay ?? undefined}
onChange={(v) => saveLightboxSettings({ slideshowDelay: v })}
/>

<SelectSetting
id="lightbox_display_mode"
headingID="dialogs.lightbox.display_mode.label"
value={
iface.imageLightbox?.displayMode ??
GQL.ImageLightboxDisplayMode.FitXy
}
onChange={(v) =>
saveLightboxSettings({
displayMode: v as GQL.ImageLightboxDisplayMode,
})
}
>
{Array.from(imageLightboxDisplayModeIntlMap.entries()).map((v) => (
<option key={v[0]} value={v[0]}>
{intl.formatMessage({
id: v[1],
})}
</option>
))}
</SelectSetting>

<BooleanSetting
id="lightbox_scale_up"
headingID="dialogs.lightbox.scale_up.label"
subHeadingID="dialogs.lightbox.scale_up.description"
checked={iface.imageLightbox?.scaleUp ?? false}
onChange={(v) => saveLightboxSettings({ scaleUp: v })}
/>

<BooleanSetting
id="lightbox_reset_zoom_on_nav"
headingID="dialogs.lightbox.reset_zoom_on_nav"
checked={iface.imageLightbox?.resetZoomOnNav ?? false}
onChange={(v) => saveLightboxSettings({ resetZoomOnNav: v })}
/>

<SelectSetting
id="lightbox_scroll_mode"
headingID="dialogs.lightbox.scroll_mode.label"
subHeadingID="dialogs.lightbox.scroll_mode.description"
value={
iface.imageLightbox?.scrollMode ?? GQL.ImageLightboxScrollMode.Zoom
}
onChange={(v) =>
saveLightboxSettings({
scrollMode: v as GQL.ImageLightboxScrollMode,
})
}
>
{Array.from(imageLightboxScrollModeIntlMap.entries()).map((v) => (
<option key={v[0]} value={v[0]}>
{intl.formatMessage({
id: v[1],
})}
</option>
))}
</SelectSetting>
</SettingSection>

<SettingSection headingID="config.ui.editing.heading">
Expand Down
27 changes: 27 additions & 0 deletions ui/v2.5/src/core/enums.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {
ImageLightboxDisplayMode,
ImageLightboxScrollMode,
} from "../core/generated-graphql";

export const imageLightboxDisplayModeIntlMap = new Map<
ImageLightboxDisplayMode,
string
>([
[ImageLightboxDisplayMode.Original, "dialogs.lightbox.display_mode.original"],
[
ImageLightboxDisplayMode.FitXy,
"dialogs.lightbox.display_mode.fit_to_screen",
],
[
ImageLightboxDisplayMode.FitX,
"dialogs.lightbox.display_mode.fit_horizontally",
],
]);

export const imageLightboxScrollModeIntlMap = new Map<
ImageLightboxScrollMode,
string
>([
[ImageLightboxScrollMode.Zoom, "dialogs.lightbox.scroll_mode.zoom"],
[ImageLightboxScrollMode.PanY, "dialogs.lightbox.scroll_mode.pan_y"],
]);
Loading