diff --git a/packages/frontend/src/components/MkImageGallery.item.vue b/packages/frontend/src/components/MkImageGallery.item.vue index 4e5e515f64..741968a7ae 100644 --- a/packages/frontend/src/components/MkImageGallery.item.vue +++ b/packages/frontend/src/components/MkImageGallery.item.vue @@ -4,21 +4,26 @@ SPDX-License-Identifier: AGPL-3.0-only --> @@ -43,6 +48,10 @@ const props = withDefaults(defineProps<{ const emit = defineEmits<{ (ev: 'close'): void; + (ev: 'horizontalSwipe', offset: number): void; + (ev: 'next'): void; + (ev: 'prev'): void; + (ev: 'cancelHorizontalSwipe'): void; }>(); const rootEl = useTemplateRef('rootEl'); @@ -193,6 +202,7 @@ let currentPointerStartOffset = { x: 0, y: 0 }; let isVerticalSwiping = false; let isHorizontalSwiping = false; let verticalSwipeDelta = 0; +let horizontalSwipeDelta = 0; const pointerEventCache = new Map(); let pointerVec = { x: 0, y: 0 }; @@ -256,7 +266,9 @@ function onPointermove(ev: PointerEvent) { translation.value.y += deltaY; verticalSwipeDelta += deltaY; } else if (isHorizontalSwiping) { - translation.value.x += deltaX; + //translation.value.x += deltaX; + horizontalSwipeDelta += deltaX; + emit('horizontalSwipe', currentPointerStartOffset.x - ev.clientX); } else { const isVerticalVector = Math.abs(deltaY) > Math.abs(deltaX); if (isVerticalVector) { @@ -285,8 +297,8 @@ function onPointerup(ev: PointerEvent) { currentPointerId = null; if (isVerticalSwiping) { - const shouldCloseByUpwardSwipe = verticalSwipeDelta < -200 || pointerVec.y < -3; // 上の方で離された、または上に向かって強めに弾かれた - const shouldCloseByDownwardSwipe = verticalSwipeDelta > 200 || pointerVec.y > 3; // 下の方で離された、または下に向かって強めに弾かれた + const shouldCloseByUpwardSwipe = verticalSwipeDelta < -200 || (verticalSwipeDelta < 0 && pointerVec.y < -3); // 上の方で離された、または上に向かって強めに弾かれた + const shouldCloseByDownwardSwipe = verticalSwipeDelta > 200 || (verticalSwipeDelta > 0 && pointerVec.y > 3); // 下の方で離された、または下に向かって強めに弾かれた if (shouldCloseByUpwardSwipe || shouldCloseByDownwardSwipe) { emit('close'); return; @@ -298,6 +310,16 @@ function onPointerup(ev: PointerEvent) { x: defaultTranslation.x, y: defaultTranslation.y, }, ANIMATION_DURATION); + } else if (isHorizontalSwiping) { + const shouldNext = horizontalSwipeDelta < -200 || (horizontalSwipeDelta < 0 && pointerVec.x < -3); // 左の方で離された、または左に向かって強めに弾かれた + const shouldPrev = horizontalSwipeDelta > 200 || (horizontalSwipeDelta > 0 && pointerVec.x > 3); // 右の方で離された、または右に向かって強めに弾かれた + if (shouldNext) { + emit('next'); + } else if (shouldPrev) { + emit('prev'); + } else { + emit('cancelHorizontalSwipe'); + } } } isVerticalSwiping = false; @@ -366,14 +388,15 @@ onBeforeUnmount(() => { diff --git a/packages/frontend/src/components/MkImageGallery.vue b/packages/frontend/src/components/MkImageGallery.vue index edfde2215b..29b67d9c3e 100644 --- a/packages/frontend/src/components/MkImageGallery.vue +++ b/packages/frontend/src/components/MkImageGallery.vue @@ -7,9 +7,16 @@ SPDX-License-Identifier: AGPL-3.0-only
-
-
- +
+
+
@@ -31,7 +38,7 @@ type Image = { }; const props = withDefaults(defineProps<{ - defaultId?: string; + defaultIndex?: number; images: Image[]; }>(), { }); @@ -42,8 +49,48 @@ const emit = defineEmits<{ const rootEl = useTemplateRef('rootEl'); const mainEl = useTemplateRef('mainEl'); +const itemsEl = useTemplateRef('itemsEl'); +const itemEl = useTemplateRef('itemEl'); const zIndex = os.claimZIndex('high'); +let currentScrollLeft = 0; +let currentIndex = props.defaultIndex ?? 0; + +function onHorizontalSwipe(offset: number) { + itemsEl.value.scrollLeft = currentScrollLeft + offset; +} + +function onCancelHorizontalSwipe() { + itemsEl.value.scrollTo({ + left: currentScrollLeft, + behavior: 'smooth', + }); +} + +function onNext() { + if (currentIndex < props.images.length - 1) { + currentIndex++; + const el = itemEl.value[currentIndex]; + currentScrollLeft = el.offsetLeft; + itemsEl.value.scrollTo({ + left: currentScrollLeft, + behavior: 'smooth', + }); + } +} + +function onPrev() { + if (currentIndex > 0) { + currentIndex--; + const el = itemEl.value[currentIndex]; + currentScrollLeft = el.offsetLeft; + itemsEl.value.scrollTo({ + left: currentScrollLeft, + behavior: 'smooth', + }); + } +} + function onItemClose() { emit('closed'); } @@ -76,8 +123,7 @@ function onItemClose() { display: flex; width: 100dvw; height: 100dvh; - overflow-x: auto; - overflow-y: clip; + overflow: hidden; scrollbar-width: none; } diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue index 64264d01e4..c5e83db03f 100644 --- a/packages/frontend/src/components/MkMediaList.vue +++ b/packages/frontend/src/components/MkMediaList.vue @@ -94,15 +94,16 @@ const previewable = (file: Misskey.entities.DriveFile): boolean => { }; async function openGallery(id: string) { + const images = props.mediaList.filter(media => previewable(media)).map(media => ({ + id: media.id, + src: media.url, + width: media.properties.width ?? 0, + height: media.properties.height ?? 0, + sourceElement: gallery.value?.querySelector(`.image[data-id="${media.id}"]`) as HTMLElement | undefined, + })); const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkImageGallery.vue').then(x => x.default), { - defaultId: id, - images: props.mediaList.filter(media => previewable(media)).map(media => ({ - id: media.id, - src: media.url, - width: media.properties.width ?? 0, - height: media.properties.height ?? 0, - sourceElement: gallery.value?.querySelector(`.image[data-id="${media.id}"]`) as HTMLElement | undefined, - })), + defaultIndex: images.findIndex(image => image.id === id), + images: images, }, { closed: () => dispose(), });