mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 16:34:02 +01:00
Persist lightbox settings (#2406)
* Persist lightbox settings in local forage * Add lightbox settings to backend * Add lightbox settings to interface settings page
This commit is contained in:
parent
4c4cdae1ed
commit
2afb467bb1
14 changed files with 382 additions and 114 deletions
|
|
@ -62,7 +62,13 @@ fragment ConfigInterfaceData on ConfigInterfaceResult {
|
||||||
css
|
css
|
||||||
cssEnabled
|
cssEnabled
|
||||||
language
|
language
|
||||||
|
imageLightbox {
|
||||||
slideshowDelay
|
slideshowDelay
|
||||||
|
displayMode
|
||||||
|
scaleUp
|
||||||
|
resetZoomOnNav
|
||||||
|
scrollMode
|
||||||
|
}
|
||||||
disableDropdownCreate {
|
disableDropdownCreate {
|
||||||
performer
|
performer
|
||||||
tag
|
tag
|
||||||
|
|
|
||||||
|
|
@ -196,6 +196,33 @@ input ConfigDisableDropdownCreateInput {
|
||||||
studio: Boolean
|
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 {
|
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!]
|
||||||
|
|
@ -231,7 +258,9 @@ input ConfigInterfaceInput {
|
||||||
language: String
|
language: String
|
||||||
|
|
||||||
"""Slideshow Delay"""
|
"""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"""
|
"""Set to true to disable creating new objects via the dropdown menus"""
|
||||||
disableDropdownCreate: ConfigDisableDropdownCreateInput
|
disableDropdownCreate: ConfigDisableDropdownCreateInput
|
||||||
|
|
@ -291,7 +320,9 @@ type ConfigInterfaceResult {
|
||||||
language: String
|
language: String
|
||||||
|
|
||||||
"""Slideshow Delay"""
|
"""Slideshow Delay"""
|
||||||
slideshowDelay: Int
|
slideshowDelay: Int @deprecated(reason: "Use imageLightbox.slideshowDelay")
|
||||||
|
|
||||||
|
imageLightbox: ConfigImageLightboxResult!
|
||||||
|
|
||||||
"""Fields are true if creating via dropdown menus are disabled"""
|
"""Fields are true if creating via dropdown menus are disabled"""
|
||||||
disableDropdownCreate: ConfigDisableDropdownCreate!
|
disableDropdownCreate: ConfigDisableDropdownCreate!
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
if input.MenuItems != nil {
|
||||||
c.Set(config.MenuItems, input.MenuItems)
|
c.Set(config.MenuItems, input.MenuItems)
|
||||||
}
|
}
|
||||||
|
|
@ -316,8 +322,22 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models.
|
||||||
c.Set(config.Language, *input.Language)
|
c.Set(config.Language, *input.Language)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deprecated field
|
||||||
if input.SlideshowDelay != nil {
|
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 {
|
if input.CSS != nil {
|
||||||
|
|
|
||||||
|
|
@ -140,9 +140,9 @@ func makeConfigInterfaceResult() *models.ConfigInterfaceResult {
|
||||||
css := config.GetCSS()
|
css := config.GetCSS()
|
||||||
cssEnabled := config.GetCSSEnabled()
|
cssEnabled := config.GetCSSEnabled()
|
||||||
language := config.GetLanguage()
|
language := config.GetLanguage()
|
||||||
slideshowDelay := config.GetSlideshowDelay()
|
|
||||||
handyKey := config.GetHandyKey()
|
handyKey := config.GetHandyKey()
|
||||||
scriptOffset := config.GetFunscriptOffset()
|
scriptOffset := config.GetFunscriptOffset()
|
||||||
|
imageLightboxOptions := config.GetImageLightboxOptions()
|
||||||
|
|
||||||
// FIXME - misnamed output field means we have redundant fields
|
// FIXME - misnamed output field means we have redundant fields
|
||||||
disableDropdownCreate := config.GetDisableDropdownCreate()
|
disableDropdownCreate := config.GetDisableDropdownCreate()
|
||||||
|
|
@ -163,7 +163,8 @@ func makeConfigInterfaceResult() *models.ConfigInterfaceResult {
|
||||||
CSS: &css,
|
CSS: &css,
|
||||||
CSSEnabled: &cssEnabled,
|
CSSEnabled: &cssEnabled,
|
||||||
Language: &language,
|
Language: &language,
|
||||||
SlideshowDelay: &slideshowDelay,
|
|
||||||
|
ImageLightbox: &imageLightboxOptions,
|
||||||
|
|
||||||
// FIXME - see above
|
// FIXME - see above
|
||||||
DisabledDropdownCreate: disableDropdownCreate,
|
DisabledDropdownCreate: disableDropdownCreate,
|
||||||
|
|
|
||||||
|
|
@ -142,8 +142,15 @@ const (
|
||||||
WallPlayback = "wall_playback"
|
WallPlayback = "wall_playback"
|
||||||
defaultWallPlayback = "video"
|
defaultWallPlayback = "video"
|
||||||
|
|
||||||
SlideshowDelay = "slideshow_delay"
|
// Image lightbox options
|
||||||
defaultSlideshowDelay = 5000
|
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"
|
DisableDropdownCreatePerformer = "disable_dropdown_create.performer"
|
||||||
DisableDropdownCreateStudio = "disable_dropdown_create.studio"
|
DisableDropdownCreateStudio = "disable_dropdown_create.studio"
|
||||||
|
|
@ -364,6 +371,18 @@ func (i *Instance) viper(key string) *viper.Viper {
|
||||||
return v
|
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 {
|
func (i *Instance) HasOverride(key string) bool {
|
||||||
i.RLock()
|
i.RLock()
|
||||||
defer i.RUnlock()
|
defer i.RUnlock()
|
||||||
|
|
@ -886,14 +905,49 @@ func (i *Instance) GetShowStudioAsText() bool {
|
||||||
return i.getBool(ShowStudioAsText)
|
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()
|
i.RLock()
|
||||||
defer i.RUnlock()
|
defer i.RUnlock()
|
||||||
|
|
||||||
ret := defaultSlideshowDelay
|
delay := i.getSlideshowDelay()
|
||||||
v := i.viper(SlideshowDelay)
|
|
||||||
if v.IsSet(SlideshowDelay) {
|
ret := models.ConfigImageLightboxResult{
|
||||||
ret = v.GetInt(SlideshowDelay)
|
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
|
return ret
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,8 @@ func TestConcurrentConfigAccess(t *testing.T) {
|
||||||
i.Set(MaximumLoopDuration, i.GetMaximumLoopDuration())
|
i.Set(MaximumLoopDuration, i.GetMaximumLoopDuration())
|
||||||
i.Set(AutostartVideo, i.GetAutostartVideo())
|
i.Set(AutostartVideo, i.GetAutostartVideo())
|
||||||
i.Set(ShowStudioAsText, i.GetShowStudioAsText())
|
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.GetCSSPath()
|
||||||
i.GetCSS()
|
i.GetCSS()
|
||||||
i.Set(CSSEnabled, i.GetCSSEnabled())
|
i.Set(CSSEnabled, i.GetCSSEnabled())
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
### 🎨 Improvements
|
### 🎨 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))
|
* 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))
|
* 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))
|
* Improved autotag performance. ([#2368](https://github.com/stashapp/stash/pull/2368))
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,12 @@ import {
|
||||||
} from "../Inputs";
|
} from "../Inputs";
|
||||||
import { SettingStateContext } from "../context";
|
import { SettingStateContext } from "../context";
|
||||||
import { DurationUtils } from "src/utils";
|
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 = [
|
const allMenuItems = [
|
||||||
{ id: "scenes", headingID: "scenes" },
|
{ id: "scenes", headingID: "scenes" },
|
||||||
|
|
@ -32,6 +38,28 @@ export const SettingsInterfacePanel: React.FC = () => {
|
||||||
SettingStateContext
|
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 (error) return <h1>{error.message}</h1>;
|
||||||
if (loading) return <LoadingIndicator />;
|
if (loading) return <LoadingIndicator />;
|
||||||
|
|
||||||
|
|
@ -195,13 +223,72 @@ export const SettingsInterfacePanel: React.FC = () => {
|
||||||
/>
|
/>
|
||||||
</SettingSection>
|
</SettingSection>
|
||||||
|
|
||||||
<SettingSection headingID="config.ui.images.heading">
|
<SettingSection headingID="config.ui.image_lightbox.heading">
|
||||||
<NumberSetting
|
<NumberSetting
|
||||||
headingID="config.ui.slideshow_delay.heading"
|
headingID="config.ui.slideshow_delay.heading"
|
||||||
subHeadingID="config.ui.slideshow_delay.description"
|
subHeadingID="config.ui.slideshow_delay.description"
|
||||||
value={iface.slideshowDelay ?? undefined}
|
value={iface.imageLightbox?.slideshowDelay ?? undefined}
|
||||||
onChange={(v) => saveInterface({ slideshowDelay: v })}
|
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>
|
||||||
|
|
||||||
<SettingSection headingID="config.ui.editing.heading">
|
<SettingSection headingID="config.ui.editing.heading">
|
||||||
|
|
|
||||||
27
ui/v2.5/src/core/enums.ts
Normal file
27
ui/v2.5/src/core/enums.ts
Normal file
|
|
@ -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"],
|
||||||
|
]);
|
||||||
|
|
@ -15,7 +15,7 @@ import debounce from "lodash/debounce";
|
||||||
import { Icon, LoadingIndicator } from "src/components/Shared";
|
import { Icon, LoadingIndicator } from "src/components/Shared";
|
||||||
import { useInterval, usePageVisibility, useToast } from "src/hooks";
|
import { useInterval, usePageVisibility, useToast } from "src/hooks";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { DisplayMode, LightboxImage, ScrollMode } from "./LightboxImage";
|
import { LightboxImage } from "./LightboxImage";
|
||||||
import { ConfigurationContext } from "../Config";
|
import { ConfigurationContext } from "../Config";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { RatingStars } from "src/components/Scenes/SceneDetails/RatingStars";
|
import { RatingStars } from "src/components/Scenes/SceneDetails/RatingStars";
|
||||||
|
|
@ -27,6 +27,8 @@ import {
|
||||||
mutateImageResetO,
|
mutateImageResetO,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
|
import { useInterfaceLocalForage } from "../LocalForage";
|
||||||
|
import { imageLightboxDisplayModeIntlMap } from "src/core/enums";
|
||||||
|
|
||||||
const CLASSNAME = "Lightbox";
|
const CLASSNAME = "Lightbox";
|
||||||
const CLASSNAME_HEADER = `${CLASSNAME}-header`;
|
const CLASSNAME_HEADER = `${CLASSNAME}-header`;
|
||||||
|
|
@ -98,12 +100,6 @@ export const LightboxComponent: React.FC<IProps> = ({
|
||||||
|
|
||||||
const oldImages = useRef<ILightboxImage[]>([]);
|
const oldImages = useRef<ILightboxImage[]>([]);
|
||||||
|
|
||||||
const [displayMode, setDisplayMode] = useState(DisplayMode.FIT_XY);
|
|
||||||
const oldDisplayMode = useRef(displayMode);
|
|
||||||
|
|
||||||
const [scaleUp, setScaleUp] = useState(false);
|
|
||||||
const [scrollMode, setScrollMode] = useState(ScrollMode.ZOOM);
|
|
||||||
const [resetZoomOnNav, setResetZoomOnNav] = useState(true);
|
|
||||||
const [zoom, setZoom] = useState(1);
|
const [zoom, setZoom] = useState(1);
|
||||||
const [resetPosition, setResetPosition] = useState(false);
|
const [resetPosition, setResetPosition] = useState(false);
|
||||||
|
|
||||||
|
|
@ -120,9 +116,53 @@ export const LightboxComponent: React.FC<IProps> = ({
|
||||||
const Toast = useToast();
|
const Toast = useToast();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { configuration: config } = React.useContext(ConfigurationContext);
|
const { configuration: config } = React.useContext(ConfigurationContext);
|
||||||
|
const [
|
||||||
|
interfaceLocalForage,
|
||||||
|
setInterfaceLocalForage,
|
||||||
|
] = useInterfaceLocalForage();
|
||||||
|
|
||||||
const userSelectedSlideshowDelayOrDefault =
|
const lightboxSettings = interfaceLocalForage.data?.imageLightbox;
|
||||||
config?.interface.slideshowDelay ?? DEFAULT_SLIDESHOW_DELAY;
|
|
||||||
|
function setLightboxSettings(v: Partial<GQL.ConfigImageLightboxInput>) {
|
||||||
|
setInterfaceLocalForage((prev) => {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
imageLightbox: {
|
||||||
|
...prev.imageLightbox,
|
||||||
|
...v,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setScaleUp(value: boolean) {
|
||||||
|
setLightboxSettings({ scaleUp: value });
|
||||||
|
}
|
||||||
|
|
||||||
|
function setResetZoomOnNav(v: boolean) {
|
||||||
|
setLightboxSettings({ resetZoomOnNav: v });
|
||||||
|
}
|
||||||
|
|
||||||
|
function setScrollMode(v: GQL.ImageLightboxScrollMode) {
|
||||||
|
setLightboxSettings({ scrollMode: v });
|
||||||
|
}
|
||||||
|
|
||||||
|
const slideshowDelay =
|
||||||
|
lightboxSettings?.slideshowDelay ??
|
||||||
|
config?.interface.imageLightbox.slideshowDelay ??
|
||||||
|
DEFAULT_SLIDESHOW_DELAY;
|
||||||
|
|
||||||
|
function setSlideshowDelay(v: number) {
|
||||||
|
setLightboxSettings({ slideshowDelay: v });
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayMode =
|
||||||
|
lightboxSettings?.displayMode ?? GQL.ImageLightboxDisplayMode.FitXy;
|
||||||
|
const oldDisplayMode = useRef(displayMode);
|
||||||
|
|
||||||
|
function setDisplayMode(v: GQL.ImageLightboxDisplayMode) {
|
||||||
|
setLightboxSettings({ displayMode: v });
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
|
@ -131,12 +171,11 @@ export const LightboxComponent: React.FC<IProps> = ({
|
||||||
const [slideshowInterval, setSlideshowInterval] = useState<number | null>(
|
const [slideshowInterval, setSlideshowInterval] = useState<number | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
const [
|
const [
|
||||||
displayedSlideshowInterval,
|
displayedSlideshowInterval,
|
||||||
setDisplayedSlideshowInterval,
|
setDisplayedSlideshowInterval,
|
||||||
] = useState<string>(
|
] = useState<string>(slideshowDelay.toString());
|
||||||
(userSelectedSlideshowDelayOrDefault / SECONDS_TO_MS).toString()
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (images !== oldImages.current && isSwitchingPage) {
|
if (images !== oldImages.current && isSwitchingPage) {
|
||||||
|
|
@ -164,7 +203,7 @@ export const LightboxComponent: React.FC<IProps> = ({
|
||||||
// reset zoom status
|
// reset zoom status
|
||||||
// setResetZoom((r) => !r);
|
// setResetZoom((r) => !r);
|
||||||
// setZoomed(false);
|
// setZoomed(false);
|
||||||
if (resetZoomOnNav) {
|
if (lightboxSettings?.resetZoomOnNav) {
|
||||||
setZoom(1);
|
setZoom(1);
|
||||||
}
|
}
|
||||||
setResetPosition((r) => !r);
|
setResetPosition((r) => !r);
|
||||||
|
|
@ -192,20 +231,20 @@ export const LightboxComponent: React.FC<IProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
oldIndex.current = index;
|
oldIndex.current = index;
|
||||||
}, [index, images.length, resetZoomOnNav]);
|
}, [index, images.length, lightboxSettings?.resetZoomOnNav]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (displayMode !== oldDisplayMode.current) {
|
if (displayMode !== oldDisplayMode.current) {
|
||||||
// reset zoom status
|
// reset zoom status
|
||||||
// setResetZoom((r) => !r);
|
// setResetZoom((r) => !r);
|
||||||
// setZoomed(false);
|
// setZoomed(false);
|
||||||
if (resetZoomOnNav) {
|
if (lightboxSettings?.resetZoomOnNav) {
|
||||||
setZoom(1);
|
setZoom(1);
|
||||||
}
|
}
|
||||||
setResetPosition((r) => !r);
|
setResetPosition((r) => !r);
|
||||||
}
|
}
|
||||||
oldDisplayMode.current = displayMode;
|
oldDisplayMode.current = displayMode;
|
||||||
}, [displayMode, resetZoomOnNav]);
|
}, [displayMode, lightboxSettings?.resetZoomOnNav]);
|
||||||
|
|
||||||
const selectIndex = (e: React.MouseEvent, i: number) => {
|
const selectIndex = (e: React.MouseEvent, i: number) => {
|
||||||
setIndex(i);
|
setIndex(i);
|
||||||
|
|
@ -224,20 +263,10 @@ export const LightboxComponent: React.FC<IProps> = ({
|
||||||
const toggleSlideshow = useCallback(() => {
|
const toggleSlideshow = useCallback(() => {
|
||||||
if (slideshowInterval) {
|
if (slideshowInterval) {
|
||||||
setSlideshowInterval(null);
|
setSlideshowInterval(null);
|
||||||
} else if (
|
|
||||||
displayedSlideshowInterval !== null &&
|
|
||||||
typeof displayedSlideshowInterval !== "undefined"
|
|
||||||
) {
|
|
||||||
const intervalNumber = Number.parseInt(displayedSlideshowInterval, 10);
|
|
||||||
setSlideshowInterval(intervalNumber * SECONDS_TO_MS);
|
|
||||||
} else {
|
} else {
|
||||||
setSlideshowInterval(userSelectedSlideshowDelayOrDefault);
|
setSlideshowInterval(slideshowDelay * SECONDS_TO_MS);
|
||||||
}
|
}
|
||||||
}, [
|
}, [slideshowInterval, slideshowDelay]);
|
||||||
slideshowInterval,
|
|
||||||
userSelectedSlideshowDelayOrDefault,
|
|
||||||
displayedSlideshowInterval,
|
|
||||||
]);
|
|
||||||
|
|
||||||
usePageVisibility(() => {
|
usePageVisibility(() => {
|
||||||
toggleSlideshow();
|
toggleSlideshow();
|
||||||
|
|
@ -352,10 +381,6 @@ export const LightboxComponent: React.FC<IProps> = ({
|
||||||
else document.exitFullscreen();
|
else document.exitFullscreen();
|
||||||
}, [isFullscreen]);
|
}, [isFullscreen]);
|
||||||
|
|
||||||
const handleSlideshowIntervalChange = (newSlideshowInterval: number) => {
|
|
||||||
setSlideshowInterval(newSlideshowInterval);
|
|
||||||
};
|
|
||||||
|
|
||||||
const navItems = images.map((image, i) => (
|
const navItems = images.map((image, i) => (
|
||||||
<img
|
<img
|
||||||
src={image.paths.thumbnail ?? ""}
|
src={image.paths.thumbnail ?? ""}
|
||||||
|
|
@ -372,19 +397,22 @@ export const LightboxComponent: React.FC<IProps> = ({
|
||||||
|
|
||||||
const onDelayChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const onDelayChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
let numberValue = Number.parseInt(e.currentTarget.value, 10);
|
let numberValue = Number.parseInt(e.currentTarget.value, 10);
|
||||||
|
setDisplayedSlideshowInterval(e.currentTarget.value);
|
||||||
|
|
||||||
// Without this exception, the blocking of updates for invalid values is even weirder
|
// Without this exception, the blocking of updates for invalid values is even weirder
|
||||||
if (e.currentTarget.value === "-" || e.currentTarget.value === "") {
|
if (e.currentTarget.value === "-" || e.currentTarget.value === "") {
|
||||||
setDisplayedSlideshowInterval(e.currentTarget.value);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setDisplayedSlideshowInterval(e.currentTarget.value);
|
|
||||||
if (slideshowInterval !== null) {
|
|
||||||
numberValue =
|
numberValue =
|
||||||
numberValue >= MIN_VALID_INTERVAL_SECONDS
|
numberValue >= MIN_VALID_INTERVAL_SECONDS
|
||||||
? numberValue
|
? numberValue
|
||||||
: MIN_VALID_INTERVAL_SECONDS;
|
: MIN_VALID_INTERVAL_SECONDS;
|
||||||
handleSlideshowIntervalChange(numberValue * SECONDS_TO_MS);
|
|
||||||
|
setSlideshowDelay(numberValue * SECONDS_TO_MS);
|
||||||
|
|
||||||
|
if (slideshowInterval !== null) {
|
||||||
|
setSlideshowInterval(numberValue * SECONDS_TO_MS);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -421,25 +449,19 @@ export const LightboxComponent: React.FC<IProps> = ({
|
||||||
<Col xs={8}>
|
<Col xs={8}>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
as="select"
|
as="select"
|
||||||
onChange={(e) => setDisplayMode(e.target.value as DisplayMode)}
|
onChange={(e) =>
|
||||||
|
setDisplayMode(e.target.value as GQL.ImageLightboxDisplayMode)
|
||||||
|
}
|
||||||
value={displayMode}
|
value={displayMode}
|
||||||
className="btn-secondary mx-1 mb-1"
|
className="btn-secondary mx-1 mb-1"
|
||||||
>
|
>
|
||||||
<option value={DisplayMode.ORIGINAL} key={DisplayMode.ORIGINAL}>
|
{Array.from(imageLightboxDisplayModeIntlMap.entries()).map((v) => (
|
||||||
|
<option key={v[0]} value={v[0]}>
|
||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
id: "dialogs.lightbox.display_mode.original",
|
id: v[1],
|
||||||
})}
|
|
||||||
</option>
|
|
||||||
<option value={DisplayMode.FIT_XY} key={DisplayMode.FIT_XY}>
|
|
||||||
{intl.formatMessage({
|
|
||||||
id: "dialogs.lightbox.display_mode.fit_to_screen",
|
|
||||||
})}
|
|
||||||
</option>
|
|
||||||
<option value={DisplayMode.FIT_X} key={DisplayMode.FIT_X}>
|
|
||||||
{intl.formatMessage({
|
|
||||||
id: "dialogs.lightbox.display_mode.fit_horizontally",
|
|
||||||
})}
|
})}
|
||||||
</option>
|
</option>
|
||||||
|
))}
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
</Col>
|
</Col>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
|
|
@ -451,8 +473,8 @@ export const LightboxComponent: React.FC<IProps> = ({
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage({
|
||||||
id: "dialogs.lightbox.scale_up.label",
|
id: "dialogs.lightbox.scale_up.label",
|
||||||
})}
|
})}
|
||||||
checked={scaleUp}
|
checked={lightboxSettings?.scaleUp ?? false}
|
||||||
disabled={displayMode === DisplayMode.ORIGINAL}
|
disabled={displayMode === GQL.ImageLightboxDisplayMode.Original}
|
||||||
onChange={(v) => setScaleUp(v.currentTarget.checked)}
|
onChange={(v) => setScaleUp(v.currentTarget.checked)}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
@ -471,7 +493,7 @@ export const LightboxComponent: React.FC<IProps> = ({
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage({
|
||||||
id: "dialogs.lightbox.reset_zoom_on_nav",
|
id: "dialogs.lightbox.reset_zoom_on_nav",
|
||||||
})}
|
})}
|
||||||
checked={resetZoomOnNav}
|
checked={lightboxSettings?.resetZoomOnNav ?? false}
|
||||||
onChange={(v) => setResetZoomOnNav(v.currentTarget.checked)}
|
onChange={(v) => setResetZoomOnNav(v.currentTarget.checked)}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
@ -487,16 +509,26 @@ export const LightboxComponent: React.FC<IProps> = ({
|
||||||
<Col xs={8}>
|
<Col xs={8}>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
as="select"
|
as="select"
|
||||||
onChange={(e) => setScrollMode(e.target.value as ScrollMode)}
|
onChange={(e) =>
|
||||||
value={scrollMode}
|
setScrollMode(e.target.value as GQL.ImageLightboxScrollMode)
|
||||||
|
}
|
||||||
|
value={
|
||||||
|
lightboxSettings?.scrollMode ?? GQL.ImageLightboxScrollMode.Zoom
|
||||||
|
}
|
||||||
className="btn-secondary mx-1 mb-1"
|
className="btn-secondary mx-1 mb-1"
|
||||||
>
|
>
|
||||||
<option value={ScrollMode.ZOOM} key={ScrollMode.ZOOM}>
|
<option
|
||||||
|
value={GQL.ImageLightboxScrollMode.Zoom}
|
||||||
|
key={GQL.ImageLightboxScrollMode.Zoom}
|
||||||
|
>
|
||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
id: "dialogs.lightbox.scroll_mode.zoom",
|
id: "dialogs.lightbox.scroll_mode.zoom",
|
||||||
})}
|
})}
|
||||||
</option>
|
</option>
|
||||||
<option value={ScrollMode.PAN_Y} key={ScrollMode.PAN_Y}>
|
<option
|
||||||
|
value={GQL.ImageLightboxScrollMode.PanY}
|
||||||
|
key={GQL.ImageLightboxScrollMode.PanY}
|
||||||
|
>
|
||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
id: "dialogs.lightbox.scroll_mode.pan_y",
|
id: "dialogs.lightbox.scroll_mode.pan_y",
|
||||||
})}
|
})}
|
||||||
|
|
@ -686,8 +718,11 @@ export const LightboxComponent: React.FC<IProps> = ({
|
||||||
<LightboxImage
|
<LightboxImage
|
||||||
src={image.paths.image ?? ""}
|
src={image.paths.image ?? ""}
|
||||||
displayMode={displayMode}
|
displayMode={displayMode}
|
||||||
scaleUp={scaleUp}
|
scaleUp={lightboxSettings?.scaleUp ?? false}
|
||||||
scrollMode={scrollMode}
|
scrollMode={
|
||||||
|
lightboxSettings?.scrollMode ??
|
||||||
|
GQL.ImageLightboxScrollMode.Zoom
|
||||||
|
}
|
||||||
onLeft={handleLeft}
|
onLeft={handleLeft}
|
||||||
onRight={handleRight}
|
onRight={handleRight}
|
||||||
alignBottom={movingLeft}
|
alignBottom={movingLeft}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useEffect, useRef, useState, useCallback } from "react";
|
import React, { useEffect, useRef, useState, useCallback } from "react";
|
||||||
|
import * as GQL from "src/core/generated-graphql";
|
||||||
|
|
||||||
const ZOOM_STEP = 1.1;
|
const ZOOM_STEP = 1.1;
|
||||||
const SCROLL_PAN_STEP = 75;
|
const SCROLL_PAN_STEP = 75;
|
||||||
|
|
@ -6,22 +7,11 @@ const CLASSNAME = "Lightbox";
|
||||||
const CLASSNAME_CAROUSEL = `${CLASSNAME}-carousel`;
|
const CLASSNAME_CAROUSEL = `${CLASSNAME}-carousel`;
|
||||||
const CLASSNAME_IMAGE = `${CLASSNAME_CAROUSEL}-image`;
|
const CLASSNAME_IMAGE = `${CLASSNAME_CAROUSEL}-image`;
|
||||||
|
|
||||||
export enum DisplayMode {
|
|
||||||
ORIGINAL = "ORIGINAL",
|
|
||||||
FIT_XY = "FIT_XY",
|
|
||||||
FIT_X = "FIT_X",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ScrollMode {
|
|
||||||
ZOOM = "ZOOM",
|
|
||||||
PAN_Y = "PAN_Y",
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
src: string;
|
src: string;
|
||||||
displayMode: DisplayMode;
|
displayMode: GQL.ImageLightboxDisplayMode;
|
||||||
scaleUp: boolean;
|
scaleUp: boolean;
|
||||||
scrollMode: ScrollMode;
|
scrollMode: GQL.ImageLightboxScrollMode;
|
||||||
resetPosition?: boolean;
|
resetPosition?: boolean;
|
||||||
zoom: number;
|
zoom: number;
|
||||||
// set to true to align image with bottom instead of top
|
// set to true to align image with bottom instead of top
|
||||||
|
|
@ -105,7 +95,7 @@ export const LightboxImage: React.FC<IProps> = ({
|
||||||
let newZoom = 1;
|
let newZoom = 1;
|
||||||
let newPositionY = 0;
|
let newPositionY = 0;
|
||||||
switch (displayMode) {
|
switch (displayMode) {
|
||||||
case DisplayMode.FIT_XY:
|
case GQL.ImageLightboxDisplayMode.FitXy:
|
||||||
xZoom = boxWidth / width;
|
xZoom = boxWidth / width;
|
||||||
yZoom = boxHeight / height;
|
yZoom = boxHeight / height;
|
||||||
|
|
||||||
|
|
@ -115,14 +105,14 @@ export const LightboxImage: React.FC<IProps> = ({
|
||||||
}
|
}
|
||||||
newZoom = Math.min(xZoom, yZoom);
|
newZoom = Math.min(xZoom, yZoom);
|
||||||
break;
|
break;
|
||||||
case DisplayMode.FIT_X:
|
case GQL.ImageLightboxDisplayMode.FitX:
|
||||||
newZoom = boxWidth / width;
|
newZoom = boxWidth / width;
|
||||||
|
|
||||||
if (!scaleUp) {
|
if (!scaleUp) {
|
||||||
newZoom = Math.min(newZoom, 1);
|
newZoom = Math.min(newZoom, 1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DisplayMode.ORIGINAL:
|
case GQL.ImageLightboxDisplayMode.Original:
|
||||||
newZoom = 1;
|
newZoom = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -131,7 +121,7 @@ export const LightboxImage: React.FC<IProps> = ({
|
||||||
const newPositionX = Math.min((boxWidth - width) / 2, 0);
|
const newPositionX = Math.min((boxWidth - width) / 2, 0);
|
||||||
|
|
||||||
// if fitting to screen, then centre, other
|
// if fitting to screen, then centre, other
|
||||||
if (displayMode === DisplayMode.FIT_XY) {
|
if (displayMode === GQL.ImageLightboxDisplayMode.FitXy) {
|
||||||
newPositionY = Math.min((boxHeight - height) / 2, 0);
|
newPositionY = Math.min((boxHeight - height) / 2, 0);
|
||||||
} else {
|
} else {
|
||||||
// otherwise, align top of image with container
|
// otherwise, align top of image with container
|
||||||
|
|
@ -178,10 +168,10 @@ export const LightboxImage: React.FC<IProps> = ({
|
||||||
function getScrollMode(ev: React.WheelEvent<HTMLDivElement>) {
|
function getScrollMode(ev: React.WheelEvent<HTMLDivElement>) {
|
||||||
if (ev.shiftKey) {
|
if (ev.shiftKey) {
|
||||||
switch (scrollMode) {
|
switch (scrollMode) {
|
||||||
case ScrollMode.ZOOM:
|
case GQL.ImageLightboxScrollMode.Zoom:
|
||||||
return ScrollMode.PAN_Y;
|
return GQL.ImageLightboxScrollMode.PanY;
|
||||||
case ScrollMode.PAN_Y:
|
case GQL.ImageLightboxScrollMode.PanY:
|
||||||
return ScrollMode.ZOOM;
|
return GQL.ImageLightboxScrollMode.Zoom;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -190,7 +180,7 @@ export const LightboxImage: React.FC<IProps> = ({
|
||||||
|
|
||||||
function onContainerScroll(ev: React.WheelEvent<HTMLDivElement>) {
|
function onContainerScroll(ev: React.WheelEvent<HTMLDivElement>) {
|
||||||
// don't zoom if mouse isn't over image
|
// don't zoom if mouse isn't over image
|
||||||
if (getScrollMode(ev) === ScrollMode.PAN_Y) {
|
if (getScrollMode(ev) === GQL.ImageLightboxScrollMode.PanY) {
|
||||||
onImageScroll(ev);
|
onImageScroll(ev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -244,10 +234,10 @@ export const LightboxImage: React.FC<IProps> = ({
|
||||||
const percent = ev.deltaY < 0 ? ZOOM_STEP : 1 / ZOOM_STEP;
|
const percent = ev.deltaY < 0 ? ZOOM_STEP : 1 / ZOOM_STEP;
|
||||||
|
|
||||||
switch (getScrollMode(ev)) {
|
switch (getScrollMode(ev)) {
|
||||||
case ScrollMode.ZOOM:
|
case GQL.ImageLightboxScrollMode.Zoom:
|
||||||
setZoom(zoom * percent);
|
setZoom(zoom * percent);
|
||||||
break;
|
break;
|
||||||
case ScrollMode.PAN_Y:
|
case GQL.ImageLightboxScrollMode.PanY:
|
||||||
onImageScrollPanY(ev);
|
onImageScrollPanY(ev);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -587,13 +587,19 @@ const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
|
||||||
if (level === PersistanceLevel.VIEW) {
|
if (level === PersistanceLevel.VIEW) {
|
||||||
setInterfaceState((prevState) => {
|
setInterfaceState((prevState) => {
|
||||||
return {
|
return {
|
||||||
|
...prevState,
|
||||||
|
queryConfig: {
|
||||||
|
...prevState.queryConfig,
|
||||||
[persistanceKey]: {
|
[persistanceKey]: {
|
||||||
...prevState[persistanceKey],
|
...prevState.queryConfig[persistanceKey],
|
||||||
filter: queryString.stringify({
|
filter: queryString.stringify({
|
||||||
...queryString.parse(prevState[persistanceKey]?.filter ?? ""),
|
...queryString.parse(
|
||||||
|
prevState.queryConfig[persistanceKey]?.filter ?? ""
|
||||||
|
),
|
||||||
disp: updatedFilter.displayMode,
|
disp: updatedFilter.displayMode,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -670,7 +676,7 @@ const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the display type if persisted
|
// set the display type if persisted
|
||||||
const storedQuery = interfaceState.data?.[persistanceKey];
|
const storedQuery = interfaceState.data?.queryConfig?.[persistanceKey];
|
||||||
|
|
||||||
if (options.persistState === PersistanceLevel.VIEW && storedQuery) {
|
if (options.persistState === PersistanceLevel.VIEW && storedQuery) {
|
||||||
const storedFilter = queryString.parse(storedQuery.filter);
|
const storedFilter = queryString.parse(storedQuery.filter);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import localForage from "localforage";
|
import localForage from "localforage";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import React, { Dispatch, SetStateAction, useEffect } from "react";
|
import React, { Dispatch, SetStateAction, useEffect } from "react";
|
||||||
|
import { ConfigImageLightboxInput } from "src/core/generated-graphql";
|
||||||
|
|
||||||
interface IInterfaceQueryConfig {
|
interface IInterfaceQueryConfig {
|
||||||
filter: string;
|
filter: string;
|
||||||
|
|
@ -8,7 +9,12 @@ interface IInterfaceQueryConfig {
|
||||||
currentPage: number;
|
currentPage: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type IInterfaceConfig = Record<string, IInterfaceQueryConfig>;
|
type IQueryConfig = Record<string, IInterfaceQueryConfig>;
|
||||||
|
|
||||||
|
interface IInterfaceConfig {
|
||||||
|
queryConfig: IQueryConfig;
|
||||||
|
imageLightbox: ConfigImageLightboxInput;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IChangelogConfig {
|
export interface IChangelogConfig {
|
||||||
versions: Record<string, boolean>;
|
versions: Record<string, boolean>;
|
||||||
|
|
|
||||||
|
|
@ -457,6 +457,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"image_lightbox": {
|
||||||
|
"heading": "Image Lightbox"
|
||||||
|
},
|
||||||
"interactive_options": "Interactive Options",
|
"interactive_options": "Interactive Options",
|
||||||
"language": {
|
"language": {
|
||||||
"heading": "Language"
|
"heading": "Language"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue