mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
Prevent lightbox transition until specific number of scroll events (#2544)
* Delay before nav to next image on scroll * Add config for scroll attempts before transition
This commit is contained in:
parent
c1a096a1a6
commit
73ded0d97d
9 changed files with 90 additions and 23 deletions
|
|
@ -69,6 +69,7 @@ fragment ConfigInterfaceData on ConfigInterfaceResult {
|
|||
scaleUp
|
||||
resetZoomOnNav
|
||||
scrollMode
|
||||
scrollAttemptsBeforeChange
|
||||
}
|
||||
disableDropdownCreate {
|
||||
performer
|
||||
|
|
|
|||
|
|
@ -217,6 +217,7 @@ input ConfigImageLightboxInput {
|
|||
scaleUp: Boolean
|
||||
resetZoomOnNav: Boolean
|
||||
scrollMode: ImageLightboxScrollMode
|
||||
scrollAttemptsBeforeChange: Int
|
||||
}
|
||||
|
||||
type ConfigImageLightboxResult {
|
||||
|
|
@ -225,6 +226,7 @@ type ConfigImageLightboxResult {
|
|||
scaleUp: Boolean
|
||||
resetZoomOnNav: Boolean
|
||||
scrollMode: ImageLightboxScrollMode
|
||||
scrollAttemptsBeforeChange: Int!
|
||||
}
|
||||
|
||||
input ConfigInterfaceInput {
|
||||
|
|
|
|||
|
|
@ -342,6 +342,10 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models.
|
|||
setBool(config.ImageLightboxScaleUp, options.ScaleUp)
|
||||
setBool(config.ImageLightboxResetZoomOnNav, options.ResetZoomOnNav)
|
||||
setString(config.ImageLightboxScrollMode, (*string)(options.ScrollMode))
|
||||
|
||||
if options.ScrollAttemptsBeforeChange != nil {
|
||||
c.Set(config.ImageLightboxScrollAttemptsBeforeChange, *options.ScrollAttemptsBeforeChange)
|
||||
}
|
||||
}
|
||||
|
||||
if input.CSS != nil {
|
||||
|
|
|
|||
|
|
@ -145,12 +145,13 @@ const (
|
|||
defaultWallPlayback = "video"
|
||||
|
||||
// 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"
|
||||
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"
|
||||
ImageLightboxScrollAttemptsBeforeChange = "image_lightbox.scroll_attempts_before_change"
|
||||
|
||||
defaultImageLightboxSlideshowDelay = 5000
|
||||
|
||||
|
|
@ -955,6 +956,9 @@ func (i *Instance) GetImageLightboxOptions() models.ConfigImageLightboxResult {
|
|||
mode := models.ImageLightboxScrollMode(v.GetString(ImageLightboxScrollMode))
|
||||
ret.ScrollMode = &mode
|
||||
}
|
||||
if v := i.viperWith(ImageLightboxScrollAttemptsBeforeChange); v != nil {
|
||||
ret.ScrollAttemptsBeforeChange = v.GetInt(ImageLightboxScrollAttemptsBeforeChange)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
### ✨ New Features
|
||||
* Add support for VTT and SRT captions for scenes. ([#2462](https://github.com/stashapp/stash/pull/2462))
|
||||
* Added option to require a number of scroll attempts before navigating to next/previous image in Lightbox. ([#2544](https://github.com/stashapp/stash/pull/2544))
|
||||
|
||||
### 🎨 Improvements
|
||||
* Changed playback rate options to be the same as those provided by YouTube. ([#2550](https://github.com/stashapp/stash/pull/2550))
|
||||
|
|
|
|||
|
|
@ -289,6 +289,15 @@ export const SettingsInterfacePanel: React.FC = () => {
|
|||
</option>
|
||||
))}
|
||||
</SelectSetting>
|
||||
|
||||
<NumberSetting
|
||||
headingID="config.ui.scroll_attempts_before_change.heading"
|
||||
subHeadingID="config.ui.scroll_attempts_before_change.description"
|
||||
value={iface.imageLightbox?.scrollAttemptsBeforeChange ?? 0}
|
||||
onChange={(v) =>
|
||||
saveLightboxSettings({ scrollAttemptsBeforeChange: v })
|
||||
}
|
||||
/>
|
||||
</SettingSection>
|
||||
|
||||
<SettingSection headingID="config.ui.editing.heading">
|
||||
|
|
|
|||
|
|
@ -158,6 +158,11 @@ export const LightboxComponent: React.FC<IProps> = ({
|
|||
const slideshowDelay =
|
||||
savedDelay ?? configuredDelay ?? DEFAULT_SLIDESHOW_DELAY;
|
||||
|
||||
const scrollAttemptsBeforeChange = Math.max(
|
||||
0,
|
||||
config?.interface.imageLightbox.scrollAttemptsBeforeChange ?? 0
|
||||
);
|
||||
|
||||
function setSlideshowDelay(v: number) {
|
||||
setLightboxSettings({ slideshowDelay: v });
|
||||
}
|
||||
|
|
@ -733,6 +738,8 @@ export const LightboxComponent: React.FC<IProps> = ({
|
|||
onRight={handleRight}
|
||||
alignBottom={movingLeft}
|
||||
zoom={i === currentIndex ? zoom : 1}
|
||||
current={i === currentIndex}
|
||||
scrollAttemptsBeforeChange={scrollAttemptsBeforeChange}
|
||||
setZoom={(v) => setZoom(v)}
|
||||
resetPosition={resetPosition}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ interface IProps {
|
|||
scrollMode: GQL.ImageLightboxScrollMode;
|
||||
resetPosition?: boolean;
|
||||
zoom: number;
|
||||
scrollAttemptsBeforeChange: number;
|
||||
current: boolean;
|
||||
// set to true to align image with bottom instead of top
|
||||
alignBottom?: boolean;
|
||||
setZoom: (v: number) => void;
|
||||
|
|
@ -68,6 +70,8 @@ export const LightboxImage: React.FC<IProps> = ({
|
|||
scrollMode,
|
||||
alignBottom,
|
||||
zoom,
|
||||
scrollAttemptsBeforeChange,
|
||||
current,
|
||||
setZoom,
|
||||
resetPosition,
|
||||
}) => {
|
||||
|
|
@ -88,6 +92,8 @@ export const LightboxImage: React.FC<IProps> = ({
|
|||
const pointerCache = useRef<React.PointerEvent<HTMLDivElement>[]>([]);
|
||||
const prevDiff = useRef<number | undefined>();
|
||||
|
||||
const scrollAttempts = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
const box = container.current;
|
||||
if (box) {
|
||||
|
|
@ -193,6 +199,12 @@ export const LightboxImage: React.FC<IProps> = ({
|
|||
|
||||
setPositionX(newPositionX);
|
||||
setPositionY(newPositionY);
|
||||
|
||||
if (alignBottom) {
|
||||
scrollAttempts.current = scrollAttemptsBeforeChange;
|
||||
} else {
|
||||
scrollAttempts.current = -scrollAttemptsBeforeChange;
|
||||
}
|
||||
}, [
|
||||
width,
|
||||
height,
|
||||
|
|
@ -202,6 +214,7 @@ export const LightboxImage: React.FC<IProps> = ({
|
|||
scaleUp,
|
||||
alignBottom,
|
||||
calculateInitialPosition,
|
||||
scrollAttemptsBeforeChange,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -241,26 +254,48 @@ export const LightboxImage: React.FC<IProps> = ({
|
|||
}
|
||||
|
||||
function onImageScrollPanY(ev: React.WheelEvent<HTMLDivElement>) {
|
||||
const [minY, maxY] = minMaxY(zoom * defaultZoom);
|
||||
if (current) {
|
||||
const [minY, maxY] = minMaxY(zoom * defaultZoom);
|
||||
|
||||
let newPositionY =
|
||||
positionY + (ev.deltaY < 0 ? SCROLL_PAN_STEP : -SCROLL_PAN_STEP);
|
||||
const scrollable = positionY !== maxY || positionY !== minY;
|
||||
|
||||
// #2389 - if scroll up and at top, then go to previous image
|
||||
// if scroll down and at bottom, then go to next image
|
||||
if (newPositionY > maxY && positionY === maxY) {
|
||||
onLeft();
|
||||
} else if (newPositionY < minY && positionY === minY) {
|
||||
onRight();
|
||||
} else {
|
||||
// ensure image doesn't go offscreen
|
||||
newPositionY = Math.max(newPositionY, minY);
|
||||
newPositionY = Math.min(newPositionY, maxY);
|
||||
let newPositionY =
|
||||
positionY + (ev.deltaY < 0 ? SCROLL_PAN_STEP : -SCROLL_PAN_STEP);
|
||||
|
||||
setPositionY(newPositionY);
|
||||
// #2389 - if scroll up and at top, then go to previous image
|
||||
// if scroll down and at bottom, then go to next image
|
||||
if (newPositionY > maxY && positionY === maxY) {
|
||||
// #2535 - require additional scrolls before changing page
|
||||
if (
|
||||
!scrollable ||
|
||||
scrollAttempts.current <= -scrollAttemptsBeforeChange
|
||||
) {
|
||||
onLeft();
|
||||
} else {
|
||||
scrollAttempts.current--;
|
||||
}
|
||||
} else if (newPositionY < minY && positionY === minY) {
|
||||
// #2535 - require additional scrolls before changing page
|
||||
if (
|
||||
!scrollable ||
|
||||
scrollAttempts.current >= scrollAttemptsBeforeChange
|
||||
) {
|
||||
onRight();
|
||||
} else {
|
||||
scrollAttempts.current++;
|
||||
}
|
||||
} else {
|
||||
scrollAttempts.current = 0;
|
||||
|
||||
// ensure image doesn't go offscreen
|
||||
newPositionY = Math.max(newPositionY, minY);
|
||||
newPositionY = Math.min(newPositionY, maxY);
|
||||
|
||||
setPositionY(newPositionY);
|
||||
}
|
||||
|
||||
ev.stopPropagation();
|
||||
}
|
||||
|
||||
ev.stopPropagation();
|
||||
}
|
||||
|
||||
function onImageScroll(ev: React.WheelEvent<HTMLDivElement>) {
|
||||
|
|
@ -418,7 +453,7 @@ export const LightboxImage: React.FC<IProps> = ({
|
|||
alt=""
|
||||
draggable={false}
|
||||
style={{ touchAction: "none" }}
|
||||
onWheel={(e) => onImageScroll(e)}
|
||||
onWheel={current ? (e) => onImageScroll(e) : undefined}
|
||||
onMouseDown={(e) => onImageMouseDown(e)}
|
||||
onMouseUp={(e) => onImageMouseUp(e)}
|
||||
onMouseMove={(e) => onImageMouseOver(e)}
|
||||
|
|
|
|||
|
|
@ -522,6 +522,10 @@
|
|||
"toggle_sound": "Enable sound"
|
||||
}
|
||||
},
|
||||
"scroll_attempts_before_change": {
|
||||
"description": "Number of times to attempt to scroll before moving to the next/previous item. Only applies for Pan Y scroll mode.",
|
||||
"heading": "Scroll attempts before transition"
|
||||
},
|
||||
"slideshow_delay": {
|
||||
"description": "Slideshow is available in galleries when in wall view mode",
|
||||
"heading": "Slideshow Delay (seconds)"
|
||||
|
|
|
|||
Loading…
Reference in a new issue