From 90345f158ff2eb12e619dbd7406f2fa81c379637 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Fri, 18 Jun 2021 02:01:37 +0300
Subject: gallery now supports flash, fixes for flash component. refactored
 media modal

---
 src/components/media_modal/media_modal.vue | 7 +++++++
 1 file changed, 7 insertions(+)

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index 54bc5335..a578bc71 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -28,6 +28,13 @@
       :title="currentMedia.description"
       controls
     />
+    <Flash
+      v-if="type === 'flash'"
+      class="modal-image"
+      :src="currentMedia.url"
+      :alt="currentMedia.description"
+      :title="currentMedia.description"
+    />
     <button
       v-if="canNavigate"
       :title="$t('media_modal.previous')"
-- 
cgit v1.2.3-70-g09d2


From f5823a96e970b6936185b7545c68d9401f8b3281 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 15 Aug 2021 19:43:52 +0300
Subject: add key attribute to make image refresh in media modal to give
 feedback when images are still loaded

---
 src/components/media_modal/media_modal.vue | 4 ++++
 1 file changed, 4 insertions(+)

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index a578bc71..31455e48 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -8,6 +8,7 @@
       v-if="type === 'image'"
       class="modal-image"
       :src="currentMedia.url"
+      :key="currentMedia.url"
       :alt="currentMedia.description"
       :title="currentMedia.description"
       @touchstart.stop="mediaTouchStart"
@@ -19,11 +20,13 @@
       class="modal-image"
       :attachment="currentMedia"
       :controls="true"
+      :key="currentMedia.url"
     />
     <audio
       v-if="type === 'audio'"
       class="modal-image"
       :src="currentMedia.url"
+      :key="currentMedia.url"
       :alt="currentMedia.description"
       :title="currentMedia.description"
       controls
@@ -31,6 +34,7 @@
     <Flash
       v-if="type === 'flash'"
       class="modal-image"
+      :key="currentMedia.url"
       :src="currentMedia.url"
       :alt="currentMedia.description"
       :title="currentMedia.description"
-- 
cgit v1.2.3-70-g09d2


From 34d265467a77d4e680e3c56c254df7dfb904fc18 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 15 Aug 2021 19:45:48 +0300
Subject: add media description into media modal

---
 src/components/media_modal/media_modal.js  |   3 +
 src/components/media_modal/media_modal.vue | 116 +++++++++++++++++------------
 2 files changed, 71 insertions(+), 48 deletions(-)

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js
index b6919366..2e6aa1e3 100644
--- a/src/components/media_modal/media_modal.js
+++ b/src/components/media_modal/media_modal.js
@@ -29,6 +29,9 @@ const MediaModal = {
     media () {
       return this.$store.state.mediaViewer.media
     },
+    description () {
+      return this.currentMedia.description
+    },
     currentIndex () {
       return this.$store.state.mediaViewer.currentIndex
     },
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index 31455e48..2a571056 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -61,6 +61,12 @@
         icon="chevron-right"
       />
     </button>
+    <span
+      v-if="description"
+      class="description"
+    >
+      {{ description }}
+    </span>
   </Modal>
 </template>
 
@@ -69,6 +75,7 @@
 <style lang="scss">
 .modal-view.media-modal-view {
   z-index: 1001;
+  flex-direction: column;
 
   .modal-view-button-arrow {
     opacity: 0.75;
@@ -84,63 +91,76 @@
   }
 }
 
-@keyframes media-fadein {
-  from {
-    opacity: 0;
-  }
-  to {
-    opacity: 1;
+.media-modal-view {
+  @keyframes media-fadein {
+    from {
+      opacity: 0;
+    }
+    to {
+      opacity: 1;
+    }
   }
-}
 
-.modal-image {
-  max-width: 90%;
-  max-height: 90%;
-  box-shadow: 0px 5px 15px 0 rgba(0, 0, 0, 0.5);
-  image-orientation: from-image; // NOTE: only FF supports this
-  animation: 0.1s cubic-bezier(0.7, 0, 1, 0.6) media-fadein;
-}
+  /* Hardcoded since background is also hardcoded */
+  .description {
+    color: white;
+    margin-top: 1em;
+    text-shadow: 0 0 10px black, 0 0 10px black;
+    max-width: 500px;
+    max-height: 9.5em;
+    overflow-y: auto;
+    padding: 0 2em;
+  }
 
-.modal-view-button-arrow {
-  position: absolute;
-  display: block;
-  top: 50%;
-  margin-top: -50px;
-  width: 70px;
-  height: 100px;
-  border: 0;
-  padding: 0;
-  opacity: 0;
-  box-shadow: none;
-  background: none;
-  appearance: none;
-  overflow: visible;
-  cursor: pointer;
-  transition: opacity 333ms cubic-bezier(.4,0,.22,1);
+  .modal-image {
+    max-width: 90%;
+    max-height: 90%;
+    box-shadow: 0px 5px 15px 0 rgba(0, 0, 0, 0.5);
+    image-orientation: from-image; // NOTE: only FF supports this
+    animation: 0.1s cubic-bezier(0.7, 0, 1, 0.6) media-fadein;
+  }
 
-  .arrow-icon {
+  .modal-view-button-arrow {
     position: absolute;
-    top: 35px;
-    height: 30px;
-    width: 32px;
-    font-size: 14px;
-    line-height: 30px;
-    color: #FFF;
-    text-align: center;
-    background-color: rgba(0,0,0,.3);
-  }
+    display: block;
+    top: 50%;
+    margin-top: -50px;
+    width: 70px;
+    height: 100px;
+    border: 0;
+    padding: 0;
+    opacity: 0;
+    box-shadow: none;
+    background: none;
+    appearance: none;
+    overflow: visible;
+    cursor: pointer;
+    transition: opacity 333ms cubic-bezier(.4,0,.22,1);
 
-  &--prev {
-    left: 0;
     .arrow-icon {
-      left: 6px;
+      position: absolute;
+      top: 35px;
+      height: 30px;
+      width: 32px;
+      font-size: 14px;
+      line-height: 30px;
+      color: #FFF;
+      text-align: center;
+      background-color: rgba(0,0,0,.3);
     }
-  }
 
-  &--next {
-    right: 0;
-    .arrow-icon {
-      right: 6px;
+    &--prev {
+      left: 0;
+      .arrow-icon {
+        left: 6px;
+      }
+    }
+
+    &--next {
+      right: 0;
+      .arrow-icon {
+        right: 6px;
+      }
     }
   }
 }
-- 
cgit v1.2.3-70-g09d2


From 7cc19ef2eadd9d2c41f46917b2ce80035712aec7 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 16 Aug 2021 01:02:56 +0300
Subject: better media modal loading

---
 src/components/media_modal/media_modal.js  | 31 ++++++++++++++++++++++-----
 src/components/media_modal/media_modal.vue | 34 ++++++++++++++++++++++++++----
 2 files changed, 56 insertions(+), 9 deletions(-)

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js
index 2e6aa1e3..b8bce730 100644
--- a/src/components/media_modal/media_modal.js
+++ b/src/components/media_modal/media_modal.js
@@ -7,12 +7,14 @@ import Flash from 'src/components/flash/flash.vue'
 import { library } from '@fortawesome/fontawesome-svg-core'
 import {
   faChevronLeft,
-  faChevronRight
+  faChevronRight,
+  faCircleNotch
 } from '@fortawesome/free-solid-svg-icons'
 
 library.add(
   faChevronLeft,
-  faChevronRight
+  faChevronRight,
+  faCircleNotch
 )
 
 const MediaModal = {
@@ -22,6 +24,11 @@ const MediaModal = {
     Modal,
     Flash
   },
+  data () {
+    return {
+      loading: false
+    }
+  },
   computed: {
     showing () {
       return this.$store.state.mediaViewer.activated
@@ -42,7 +49,7 @@ const MediaModal = {
       return this.media.length > 1
     },
     type () {
-      return this.currentMedia ? fileTypeService.fileType(this.currentMedia.mimetype) : null
+      return this.currentMedia ? this.getType(this.currentMedia) : null
     }
   },
   created () {
@@ -58,6 +65,9 @@ const MediaModal = {
     )
   },
   methods: {
+    getType (media) {
+      return fileTypeService.fileType(media.mimetype)
+    },
     mediaTouchStart (e) {
       GestureService.beginSwipe(e, this.mediaSwipeGestureRight)
       GestureService.beginSwipe(e, this.mediaSwipeGestureLeft)
@@ -72,15 +82,26 @@ const MediaModal = {
     goPrev () {
       if (this.canNavigate) {
         const prevIndex = this.currentIndex === 0 ? this.media.length - 1 : (this.currentIndex - 1)
-        this.$store.dispatch('setCurrentMedia', this.media[prevIndex])
+        const newMedia = this.media[prevIndex]
+        if (this.getType(newMedia) === 'image') {
+          this.loading = true
+        }
+        this.$store.dispatch('setCurrentMedia', newMedia)
       }
     },
     goNext () {
       if (this.canNavigate) {
         const nextIndex = this.currentIndex === this.media.length - 1 ? 0 : (this.currentIndex + 1)
-        this.$store.dispatch('setCurrentMedia', this.media[nextIndex])
+        const newMedia = this.media[nextIndex]
+        if (this.getType(newMedia) === 'image') {
+          this.loading = true
+        }
+        this.$store.dispatch('setCurrentMedia', newMedia)
       }
     },
+    onImageLoaded () {
+      this.loading = false
+    },
     handleKeyupEvent (e) {
       if (this.showing && e.keyCode === 27) { // escape
         this.hide()
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index 2a571056..e8bc8591 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -6,27 +6,26 @@
   >
     <img
       v-if="type === 'image'"
+      :class="{ loading }"
       class="modal-image"
       :src="currentMedia.url"
-      :key="currentMedia.url"
       :alt="currentMedia.description"
       :title="currentMedia.description"
       @touchstart.stop="mediaTouchStart"
       @touchmove.stop="mediaTouchMove"
       @click="hide"
+      @load="onImageLoaded"
     >
     <VideoAttachment
       v-if="type === 'video'"
       class="modal-image"
       :attachment="currentMedia"
       :controls="true"
-      :key="currentMedia.url"
     />
     <audio
       v-if="type === 'audio'"
       class="modal-image"
       :src="currentMedia.url"
-      :key="currentMedia.url"
       :alt="currentMedia.description"
       :title="currentMedia.description"
       controls
@@ -34,7 +33,6 @@
     <Flash
       v-if="type === 'flash'"
       class="modal-image"
-      :key="currentMedia.url"
       :src="currentMedia.url"
       :alt="currentMedia.description"
       :title="currentMedia.description"
@@ -67,6 +65,16 @@
     >
       {{ description }}
     </span>
+    <span
+      v-if="loading"
+      class="loading-spinner"
+    >
+      <FAIcon
+        spin
+        icon="circle-notch"
+        size="5x"
+      />
+    </span>
   </Modal>
 </template>
 
@@ -85,6 +93,7 @@
       outline: none;
       box-shadow: none;
     }
+
     &:hover {
       opacity: 1;
     }
@@ -118,6 +127,23 @@
     box-shadow: 0px 5px 15px 0 rgba(0, 0, 0, 0.5);
     image-orientation: from-image; // NOTE: only FF supports this
     animation: 0.1s cubic-bezier(0.7, 0, 1, 0.6) media-fadein;
+
+    &.loading {
+      opacity: 0.5;
+    }
+  }
+
+  .loading-spinner {
+    width: 100%;
+    height: 100%;
+    position: absolute;
+    pointer-events: none;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    svg {
+      color: white;
+    }
   }
 
   .modal-view-button-arrow {
-- 
cgit v1.2.3-70-g09d2


From 14ec7d6a41569af555ba5a361edbb413dbbd4c5d Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 16 Aug 2021 01:11:16 +0300
Subject: add attachment counter

---
 src/components/media_modal/media_modal.vue | 21 +++++++++++++++++----
 1 file changed, 17 insertions(+), 4 deletions(-)

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index e8bc8591..bfe7ee6a 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -65,6 +65,11 @@
     >
       {{ description }}
     </span>
+    <span
+      class="counter"
+    >
+      {{ currentIndex + 1 }} / {{ media.length }}
+    </span>
     <span
       v-if="loading"
       class="loading-spinner"
@@ -110,15 +115,22 @@
     }
   }
 
-  /* Hardcoded since background is also hardcoded */
-  .description {
+  .description,
+  .counter {
+    /* Hardcoded since background is also hardcoded */
     color: white;
     margin-top: 1em;
     text-shadow: 0 0 10px black, 0 0 10px black;
+    padding: 0.2em 2em;
+  }
+
+  .description {
+    flex: 0 0 auto;
+    overflow-y: auto;
+    min-height: 1em;
     max-width: 500px;
     max-height: 9.5em;
-    overflow-y: auto;
-    padding: 0 2em;
+    word-break: break-all;
   }
 
   .modal-image {
@@ -141,6 +153,7 @@
     display: flex;
     justify-content: center;
     align-items: center;
+
     svg {
       color: white;
     }
-- 
cgit v1.2.3-70-g09d2


From 1b32bb9c51bd6fd1a71d07c61a165004dab63aa6 Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Mon, 21 Feb 2022 10:33:47 -0500
Subject: Make media modal counter go through i18n

---
 src/components/media_modal/media_modal.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index bfe7ee6a..8680267b 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -68,7 +68,7 @@
     <span
       class="counter"
     >
-      {{ currentIndex + 1 }} / {{ media.length }}
+      {{ $tc('media_modal.counter', currentIndex + 1, { current: currentIndex + 1, total: media.length }) }}
     </span>
     <span
       v-if="loading"
-- 
cgit v1.2.3-70-g09d2


From f96e5882d16a8662ceb7b8cc6aa36fe131a2682f Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Sun, 1 Aug 2021 13:39:56 -0400
Subject: Make media modal be aware of multi-touch actions

Originally the media viewer would think every touch is a swipe (one-finger
touch event), so we would encounter the case where a two-finger scale event
would incorrectly change the current media. This is now fixed.
---
 src/components/media_modal/media_modal.js       | 25 ++++----
 src/components/media_modal/media_modal.vue      |  1 +
 src/services/gesture_service/gesture_service.js | 83 ++++++++++++++++++++++++-
 3 files changed, 93 insertions(+), 16 deletions(-)

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js
index b8bce730..f3d381ee 100644
--- a/src/components/media_modal/media_modal.js
+++ b/src/components/media_modal/media_modal.js
@@ -53,28 +53,25 @@ const MediaModal = {
     }
   },
   created () {
-    this.mediaSwipeGestureRight = GestureService.swipeGesture(
-      GestureService.DIRECTION_RIGHT,
-      this.goPrev,
-      50
-    )
-    this.mediaSwipeGestureLeft = GestureService.swipeGesture(
-      GestureService.DIRECTION_LEFT,
-      this.goNext,
-      50
-    )
+    this.mediaGesture = new GestureService.SwipeAndScaleGesture({
+      direction: GestureService.DIRECTION_LEFT,
+      callbackPositive: this.goNext,
+      callbackNegative: this.goPrev,
+      threshold: 50
+    })
   },
   methods: {
     getType (media) {
       return fileTypeService.fileType(media.mimetype)
     },
     mediaTouchStart (e) {
-      GestureService.beginSwipe(e, this.mediaSwipeGestureRight)
-      GestureService.beginSwipe(e, this.mediaSwipeGestureLeft)
+      this.mediaGesture.start(e)
     },
     mediaTouchMove (e) {
-      GestureService.updateSwipe(e, this.mediaSwipeGestureRight)
-      GestureService.updateSwipe(e, this.mediaSwipeGestureLeft)
+      this.mediaGesture.move(e)
+    },
+    mediaTouchEnd (e) {
+      this.mediaGesture.end(e)
     },
     hide () {
       this.$store.dispatch('closeMediaViewer')
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index 8680267b..b5a65d50 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -13,6 +13,7 @@
       :title="currentMedia.description"
       @touchstart.stop="mediaTouchStart"
       @touchmove.stop="mediaTouchMove"
+      @touchend.stop="mediaTouchEnd"
       @click="hide"
       @load="onImageLoaded"
     >
diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js
index 88a328f3..8df4f03d 100644
--- a/src/services/gesture_service/gesture_service.js
+++ b/src/services/gesture_service/gesture_service.js
@@ -4,9 +4,17 @@ const DIRECTION_RIGHT = [1, 0]
 const DIRECTION_UP = [0, -1]
 const DIRECTION_DOWN = [0, 1]
 
+const isSwipeEvent = e => (e.touches.length === 1)
+const isSwipeEventEnd = e => (e.changedTouches.length === 1)
+
+const isScaleEvent = e => (e.targetTouches.length === 2)
+// const isScaleEventEnd = e => (e.changedTouches.length === 2)
+
 const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]]
 
-const touchEventCoord = e => ([e.touches[0].screenX, e.touches[0].screenY])
+const touchCoord = touch => [touch.screenX, touch.screenY]
+
+const touchEventCoord = e => touchCoord(e.touches[0])
 
 const vectorLength = v => Math.sqrt(v[0] * v[0] + v[1] * v[1])
 
@@ -61,6 +69,76 @@ const updateSwipe = (event, gesture) => {
   gesture._swiping = false
 }
 
+class SwipeAndScaleGesture {
+  constructor ({
+    direction, callbackPositive, callbackNegative,
+    previewCallback, threshold = 30, perpendicularTolerance = 1.0
+  }) {
+    this.direction = direction
+    this.previewCallback = previewCallback
+    this.callbackPositive = callbackPositive
+    this.callbackNegative = callbackNegative
+    this.threshold = threshold
+    this.perpendicularTolerance = perpendicularTolerance
+    this._startPos = [0, 0]
+    this._swiping = false
+  }
+
+  start (event) {
+    console.log('start() called', event)
+    if (isSwipeEvent(event)) {
+      this._startPos = touchEventCoord(event)
+      console.log('start pos:', this._startPos)
+      this._swiping = true
+    } else if (isScaleEvent(event)) {
+      this._scalePoints = [...event.targetTouches]
+      this._swiping = false
+    }
+  }
+
+  move (event) {
+    if (isScaleEvent(event)) {
+    }
+  }
+
+  end (event) {
+    console.log('end() called', event)
+    if (!isSwipeEventEnd(event)) {
+      console.log('not swipe event')
+      return
+    }
+    if (!this._swiping) {
+      console.log('not swiping')
+      return
+    }
+    this.swiping = false
+
+    console.log('is swipe event')
+
+    // movement too small
+    const touch = event.changedTouches[0]
+    const delta = deltaCoord(this._startPos, touchCoord(touch))
+    if (vectorLength(delta) < this.threshold) return
+    // movement is opposite from direction
+    const isPositive = dotProduct(delta, this.direction) > 0
+
+    // movement perpendicular to direction is too much
+    const towardsDir = project(delta, this.direction)
+    const perpendicularDir = perpendicular(this.direction)
+    const towardsPerpendicular = project(delta, perpendicularDir)
+    if (
+      vectorLength(towardsDir) * this.perpendicularTolerance <
+        vectorLength(towardsPerpendicular)
+    ) return
+
+    if (isPositive) {
+      this.callbackPositive()
+    } else {
+      this.callbackNegative()
+    }
+  }
+}
+
 const GestureService = {
   DIRECTION_LEFT,
   DIRECTION_RIGHT,
@@ -68,7 +146,8 @@ const GestureService = {
   DIRECTION_DOWN,
   swipeGesture,
   beginSwipe,
-  updateSwipe
+  updateSwipe,
+  SwipeAndScaleGesture
 }
 
 export default GestureService
-- 
cgit v1.2.3-70-g09d2


From a7570f5eb2b8bc576edbcc8e212b2c873ac99e7e Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Sun, 1 Aug 2021 19:46:27 -0400
Subject: Preview swipe action

---
 src/components/media_modal/media_modal.js       | 26 +++++++++
 src/components/media_modal/media_modal.vue      |  1 +
 src/modules/media_viewer.js                     | 64 +++++++++++++++++++++-
 src/services/gesture_service/gesture_service.js | 73 +++++++++++++++++--------
 4 files changed, 138 insertions(+), 26 deletions(-)

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js
index f3d381ee..8f67db2b 100644
--- a/src/components/media_modal/media_modal.js
+++ b/src/components/media_modal/media_modal.js
@@ -4,6 +4,7 @@ import Modal from '../modal/modal.vue'
 import fileTypeService from '../../services/file_type/file_type.service.js'
 import GestureService from '../../services/gesture_service/gesture_service'
 import Flash from 'src/components/flash/flash.vue'
+import Vuex from 'vuex'
 import { library } from '@fortawesome/fontawesome-svg-core'
 import {
   faChevronLeft,
@@ -17,6 +18,8 @@ library.add(
   faCircleNotch
 )
 
+const onlyXAxis = ([x, y]) => [x, 0]
+
 const MediaModal = {
   components: {
     StillImage,
@@ -50,6 +53,15 @@ const MediaModal = {
     },
     type () {
       return this.currentMedia ? this.getType(this.currentMedia) : null
+    },
+    scaling () {
+      return this.$store.state.mediaViewer.swipeScaler.scaling
+    },
+    offsets () {
+      return this.$store.state.mediaViewer.swipeScaler.offsets
+    },
+    transform () {
+      return `scale(${this.scaling}, ${this.scaling}) translate(${this.offsets[0]}px, ${this.offsets[1]}px)`
     }
   },
   created () {
@@ -57,6 +69,8 @@ const MediaModal = {
       direction: GestureService.DIRECTION_LEFT,
       callbackPositive: this.goNext,
       callbackNegative: this.goPrev,
+      swipePreviewCallback: this.handleSwipePreview,
+      swipeEndCallback: this.handleSwipeEnd,
       threshold: 50
     })
   },
@@ -99,6 +113,18 @@ const MediaModal = {
     onImageLoaded () {
       this.loading = false
     },
+    handleSwipePreview (offsets) {
+      this.$store.dispatch('swipeScaler/apply', { offsets: onlyXAxis(offsets) })
+    },
+    handleSwipeEnd (sign) {
+      if (sign === 0) {
+        this.$store.dispatch('swipeScaler/revert')
+      } else if (sign > 0) {
+        this.goNext()
+      } else {
+        this.goPrev()
+      }
+    },
     handleKeyupEvent (e) {
       if (this.showing && e.keyCode === 27) { // escape
         this.hide()
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index b5a65d50..728c3035 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -11,6 +11,7 @@
       :src="currentMedia.url"
       :alt="currentMedia.description"
       :title="currentMedia.description"
+      :style="{ transform }"
       @touchstart.stop="mediaTouchStart"
       @touchmove.stop="mediaTouchMove"
       @touchend.stop="mediaTouchEnd"
diff --git a/src/modules/media_viewer.js b/src/modules/media_viewer.js
index ebcba01d..38723763 100644
--- a/src/modules/media_viewer.js
+++ b/src/modules/media_viewer.js
@@ -20,19 +20,79 @@ const mediaViewer = {
     }
   },
   actions: {
-    setMedia ({ commit }, attachments) {
+    setMedia ({ commit, dispatch }, attachments) {
       const media = attachments.filter(attachment => {
         const type = fileTypeService.fileType(attachment.mimetype)
         return supportedTypes.has(type)
       })
       commit('setMedia', media)
+      dispatch('swipeScaler/reset')
     },
     setCurrentMedia ({ commit, state }, current) {
       const index = state.media.indexOf(current)
       commit('setCurrentMedia', index || 0)
+      dispatch('swipeScaler/reset')
     },
-    closeMediaViewer ({ commit }) {
+    closeMediaViewer ({ commit, dispatch }) {
       commit('close')
+      dispatch('swipeScaler/reset')
+    }
+  },
+  modules: {
+    swipeScaler: {
+      namespaced: true,
+
+      state: {
+        origOffsets: [0, 0],
+        offsets: [0, 0],
+        origScaling: 1,
+        scaling: 1
+      },
+
+      mutations: {
+        reset (state) {
+          state.origOffsets = [0, 0]
+          state.offsets = [0, 0]
+          state.origScaling = 1
+          state.scaling = 1
+        },
+        applyOffsets (state, { offsets }) {
+          state.offsets = state.origOffsets.map((k, n) => k + offsets[n])
+        },
+        applyScaling (state, { scaling }) {
+          state.scaling = state.origScaling * scaling
+        },
+        finishOffsets (state) {
+          state.origOffsets = [...state.offsets]
+        },
+        finishScaling (state) {
+          state.origScaling = state.scaling
+        },
+        revertOffsets (state) {
+          state.offsets = [...state.origOffsets]
+        },
+        revertScaling (state) {
+          state.scaling = state.origScaling
+        }
+      },
+
+      actions: {
+        reset ({ commit }) {
+          commit('reset')
+        },
+        apply ({ commit }, { offsets, scaling = 1 }) {
+          commit('applyOffsets', { offsets })
+          commit('applyScaling', { scaling })
+        },
+        finish ({ commit }) {
+          commit('finishOffsets')
+          commit('finishScaling')
+        },
+        revert ({ commit }) {
+          commit('revertOffsets')
+          commit('revertScaling')
+        }
+      }
     }
   }
 }
diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js
index 8df4f03d..8f406762 100644
--- a/src/services/gesture_service/gesture_service.js
+++ b/src/services/gesture_service/gesture_service.js
@@ -70,14 +70,28 @@ const updateSwipe = (event, gesture) => {
 }
 
 class SwipeAndScaleGesture {
+  // swipePreviewCallback(offsets: Array[Number])
+  //   offsets: the offset vector which the underlying component should move, from the starting position
+  // pinchPreviewCallback(offsets: Array[Number], scaling: Number)
+  //   offsets: the offset vector which the underlying component should move, from the starting position
+  //   scaling: the scaling factor we should apply to the underlying component, from the starting position
+  // swipeEndcallback(sign: 0|-1|1)
+  //   sign: if the swipe does not meet the threshold, 0
+  //         if the swipe meets the threshold in the positive direction, 1
+  //         if the swipe meets the threshold in the negative direction, -1
   constructor ({
-    direction, callbackPositive, callbackNegative,
-    previewCallback, threshold = 30, perpendicularTolerance = 1.0
+    direction,
+    // swipeStartCallback, pinchStartCallback,
+    swipePreviewCallback, pinchPreviewCallback,
+    swipeEndCallback, pinchEndCallback,
+    threshold = 30, perpendicularTolerance = 1.0
   }) {
+    const nop = () => {}
     this.direction = direction
-    this.previewCallback = previewCallback
-    this.callbackPositive = callbackPositive
-    this.callbackNegative = callbackNegative
+    this.swipePreviewCallback = swipePreviewCallback || nop
+    this.pinchPreviewCallback = pinchPreviewCallback || nop
+    this.swipeEndCallback = swipeEndCallback || nop
+    this.pinchEndCallback = pinchEndCallback || nop
     this.threshold = threshold
     this.perpendicularTolerance = perpendicularTolerance
     this._startPos = [0, 0]
@@ -97,7 +111,12 @@ class SwipeAndScaleGesture {
   }
 
   move (event) {
-    if (isScaleEvent(event)) {
+    if (isSwipeEvent(event)) {
+      const touch = event.changedTouches[0]
+      const delta = deltaCoord(this._startPos, touchCoord(touch))
+
+      this.swipePreviewCallback(delta)
+    } else if (isScaleEvent(event)) {
     }
   }
 
@@ -118,24 +137,30 @@ class SwipeAndScaleGesture {
     // movement too small
     const touch = event.changedTouches[0]
     const delta = deltaCoord(this._startPos, touchCoord(touch))
-    if (vectorLength(delta) < this.threshold) return
-    // movement is opposite from direction
-    const isPositive = dotProduct(delta, this.direction) > 0
-
-    // movement perpendicular to direction is too much
-    const towardsDir = project(delta, this.direction)
-    const perpendicularDir = perpendicular(this.direction)
-    const towardsPerpendicular = project(delta, perpendicularDir)
-    if (
-      vectorLength(towardsDir) * this.perpendicularTolerance <
-        vectorLength(towardsPerpendicular)
-    ) return
-
-    if (isPositive) {
-      this.callbackPositive()
-    } else {
-      this.callbackNegative()
-    }
+    this.swipePreviewCallback(delta)
+
+    const sign = (() => {
+      if (vectorLength(delta) < this.threshold) {
+        return 0
+      }
+      // movement is opposite from direction
+      const isPositive = dotProduct(delta, this.direction) > 0
+
+      // movement perpendicular to direction is too much
+      const towardsDir = project(delta, this.direction)
+      const perpendicularDir = perpendicular(this.direction)
+      const towardsPerpendicular = project(delta, perpendicularDir)
+      if (
+        vectorLength(towardsDir) * this.perpendicularTolerance <
+          vectorLength(towardsPerpendicular)
+      ) {
+        return 0
+      }
+
+      return isPositive ? 1 : -1
+    })()
+
+    this.swipeEndCallback(sign)
   }
 }
 
-- 
cgit v1.2.3-70-g09d2


From d9030b4fddc9f47cb2f16a7a65f57a5703059e3f Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Sun, 1 Aug 2021 21:39:07 -0400
Subject: Handle pinch action

---
 src/components/media_modal/media_modal.js       | 11 ++++++-
 src/components/media_modal/media_modal.vue      |  6 ++--
 src/modules/media_viewer.js                     | 14 +++------
 src/services/gesture_service/gesture_service.js | 42 +++++++++++++++++++++++--
 4 files changed, 57 insertions(+), 16 deletions(-)

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js
index 8f67db2b..04dd8658 100644
--- a/src/components/media_modal/media_modal.js
+++ b/src/components/media_modal/media_modal.js
@@ -61,7 +61,7 @@ const MediaModal = {
       return this.$store.state.mediaViewer.swipeScaler.offsets
     },
     transform () {
-      return `scale(${this.scaling}, ${this.scaling}) translate(${this.offsets[0]}px, ${this.offsets[1]}px)`
+      return `translate(${this.offsets[0]}px, ${this.offsets[1]}px) scale(${this.scaling}, ${this.scaling})`
     }
   },
   created () {
@@ -71,6 +71,8 @@ const MediaModal = {
       callbackNegative: this.goPrev,
       swipePreviewCallback: this.handleSwipePreview,
       swipeEndCallback: this.handleSwipeEnd,
+      pinchPreviewCallback: this.handlePinchPreview,
+      pinchEndCallback: this.handlePinchEnd,
       threshold: 50
     })
   },
@@ -125,6 +127,13 @@ const MediaModal = {
         this.goPrev()
       }
     },
+    handlePinchPreview (offsets, scaling) {
+      console.log('handle pinch preview:', offsets, scaling)
+      this.$store.dispatch('swipeScaler/apply', { offsets, scaling })
+    },
+    handlePinchEnd () {
+      this.$store.dispatch('swipeScaler/finish')
+    },
     handleKeyupEvent (e) {
       if (this.showing && e.keyCode === 27) { // escape
         this.hide()
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index 728c3035..853dec1d 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -12,9 +12,9 @@
       :alt="currentMedia.description"
       :title="currentMedia.description"
       :style="{ transform }"
-      @touchstart.stop="mediaTouchStart"
-      @touchmove.stop="mediaTouchMove"
-      @touchend.stop="mediaTouchEnd"
+      @touchstart.stop.prevent="mediaTouchStart"
+      @touchmove.stop.prevent="mediaTouchMove"
+      @touchend.stop.prevent="mediaTouchEnd"
       @click="hide"
       @load="onImageLoaded"
     >
diff --git a/src/modules/media_viewer.js b/src/modules/media_viewer.js
index 38723763..d62aa7c1 100644
--- a/src/modules/media_viewer.js
+++ b/src/modules/media_viewer.js
@@ -62,16 +62,12 @@ const mediaViewer = {
         applyScaling (state, { scaling }) {
           state.scaling = state.origScaling * scaling
         },
-        finishOffsets (state) {
+        finish (state) {
           state.origOffsets = [...state.offsets]
-        },
-        finishScaling (state) {
           state.origScaling = state.scaling
         },
-        revertOffsets (state) {
+        revert (state) {
           state.offsets = [...state.origOffsets]
-        },
-        revertScaling (state) {
           state.scaling = state.origScaling
         }
       },
@@ -85,12 +81,10 @@ const mediaViewer = {
           commit('applyScaling', { scaling })
         },
         finish ({ commit }) {
-          commit('finishOffsets')
-          commit('finishScaling')
+          commit('finish')
         },
         revert ({ commit }) {
-          commit('revertOffsets')
-          commit('revertScaling')
+          commit('revert')
         }
       }
     }
diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js
index 8f406762..82337bc6 100644
--- a/src/services/gesture_service/gesture_service.js
+++ b/src/services/gesture_service/gesture_service.js
@@ -4,14 +4,21 @@ const DIRECTION_RIGHT = [1, 0]
 const DIRECTION_UP = [0, -1]
 const DIRECTION_DOWN = [0, 1]
 
+const DISTANCE_MIN = 1
+
 const isSwipeEvent = e => (e.touches.length === 1)
 const isSwipeEventEnd = e => (e.changedTouches.length === 1)
 
 const isScaleEvent = e => (e.targetTouches.length === 2)
-// const isScaleEventEnd = e => (e.changedTouches.length === 2)
+const isScaleEventEnd = e => (e.targetTouches.length === 1)
 
 const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]]
 
+const vectorMinus = (a, b) => a.map((k, n) => k - b[n])
+const vectorAdd = (a, b) => a.map((k, n) => k + b[n])
+
+const avgCoord = (coords) => [...coords].reduce(vectorAdd, [0, 0]).map(d => d / coords.length)
+
 const touchCoord = touch => [touch.screenX, touch.screenY]
 
 const touchEventCoord = e => touchCoord(e.touches[0])
@@ -22,6 +29,8 @@ const perpendicular = v => [v[1], -v[0]]
 
 const dotProduct = (v1, v2) => v1[0] * v2[0] + v1[1] * v2[1]
 
+// const numProduct = (num, v) => v.map(k => num * k)
+
 const project = (v1, v2) => {
   const scalar = (dotProduct(v1, v2) / dotProduct(v2, v2))
   return [scalar * v2[0], scalar * v2[1]]
@@ -86,7 +95,7 @@ class SwipeAndScaleGesture {
     swipeEndCallback, pinchEndCallback,
     threshold = 30, perpendicularTolerance = 1.0
   }) {
-    const nop = () => {}
+    const nop = () => { console.log('Warning: Not implemented') }
     this.direction = direction
     this.swipePreviewCallback = swipePreviewCallback || nop
     this.pinchPreviewCallback = pinchPreviewCallback || nop
@@ -95,6 +104,7 @@ class SwipeAndScaleGesture {
     this.threshold = threshold
     this.perpendicularTolerance = perpendicularTolerance
     this._startPos = [0, 0]
+    this._startDistance = DISTANCE_MIN
     this._swiping = false
   }
 
@@ -105,23 +115,51 @@ class SwipeAndScaleGesture {
       console.log('start pos:', this._startPos)
       this._swiping = true
     } else if (isScaleEvent(event)) {
+      const coords = [...event.targetTouches].map(touchCoord)
+      this._startPos = avgCoord(coords)
+      this._startDistance = vectorLength(deltaCoord(coords[0], coords[1]))
+      if (this._startDistance < DISTANCE_MIN) {
+        this._startDistance = DISTANCE_MIN
+      }
       this._scalePoints = [...event.targetTouches]
       this._swiping = false
+      console.log(
+        'is scale event, start =', this._startPos,
+        'dist =', this._startDistance)
     }
   }
 
   move (event) {
+    // console.log('move called', event)
     if (isSwipeEvent(event)) {
       const touch = event.changedTouches[0]
       const delta = deltaCoord(this._startPos, touchCoord(touch))
 
       this.swipePreviewCallback(delta)
     } else if (isScaleEvent(event)) {
+      console.log('is scale event')
+      const coords = [...event.targetTouches].map(touchCoord)
+      const curPos = avgCoord(coords)
+      const curDistance = vectorLength(deltaCoord(coords[0], coords[1]))
+      const scaling = curDistance / this._startDistance
+      const posDiff = vectorMinus(curPos, this._startPos)
+      // const delta = vectorAdd(numProduct((1 - scaling), this._startPos), posDiff)
+      const delta = posDiff
+      // console.log(
+      //   'is scale event, cur =', curPos,
+      //   'dist =', curDistance,
+      //   'scale =', scaling,
+      //   'delta =', delta)
+      this.pinchPreviewCallback(delta, scaling)
     }
   }
 
   end (event) {
     console.log('end() called', event)
+    if (isScaleEventEnd(event)) {
+      this.pinchEndCallback()
+    }
+
     if (!isSwipeEventEnd(event)) {
       console.log('not swipe event')
       return
-- 
cgit v1.2.3-70-g09d2


From a36673a6a8cace69979605f72bcc4d7a9d439814 Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Mon, 2 Aug 2021 17:42:10 -0400
Subject: Use pinch-zoom-element for pinch zoom functionality

---
 package.json                               |  1 +
 src/components/media_modal/media_modal.js  |  5 ++-
 src/components/media_modal/media_modal.vue | 72 ++++++++++++++++++++++++------
 src/components/pinch_zoom/pinch_zoom.js    |  6 +++
 src/components/pinch_zoom/pinch_zoom.vue   | 11 +++++
 src/main.js                                |  2 +
 src/modules/media_viewer.js                |  2 +-
 yarn.lock                                  | 11 +++++
 8 files changed, 94 insertions(+), 16 deletions(-)
 create mode 100644 src/components/pinch_zoom/pinch_zoom.js
 create mode 100644 src/components/pinch_zoom/pinch_zoom.vue

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/package.json b/package.json
index 5dd0e067..34029765 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
     "@fortawesome/free-regular-svg-icons": "5.15.1",
     "@fortawesome/free-solid-svg-icons": "5.15.1",
     "@fortawesome/vue-fontawesome": "2.0.0",
+    "@kazvmoe-infra/pinch-zoom-element": "https://lily.kazv.moe/infra/pinch-zoom-element.git",
     "body-scroll-lock": "2.6.4",
     "chromatism": "3.0.0",
     "cropperjs": "1.4.3",
diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js
index 222e3478..684fbe45 100644
--- a/src/components/media_modal/media_modal.js
+++ b/src/components/media_modal/media_modal.js
@@ -1,6 +1,7 @@
 import StillImage from '../still-image/still-image.vue'
 import VideoAttachment from '../video_attachment/video_attachment.vue'
 import Modal from '../modal/modal.vue'
+import PinchZoom from '../pinch_zoom/pinch_zoom.vue'
 import fileTypeService from '../../services/file_type/file_type.service.js'
 import GestureService from '../../services/gesture_service/gesture_service'
 import Flash from 'src/components/flash/flash.vue'
@@ -26,12 +27,14 @@ const MediaModal = {
   components: {
     StillImage,
     VideoAttachment,
+    PinchZoom,
     Modal,
     Flash
   },
   data () {
     return {
-      loading: false
+      loading: false,
+      pinchZoomOptions: {}
     }
   },
   computed: {
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index 853dec1d..a3fad4c5 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -4,20 +4,29 @@
     class="media-modal-view"
     @backdropClicked="hide"
   >
-    <img
-      v-if="type === 'image'"
-      :class="{ loading }"
-      class="modal-image"
-      :src="currentMedia.url"
-      :alt="currentMedia.description"
-      :title="currentMedia.description"
-      :style="{ transform }"
-      @touchstart.stop.prevent="mediaTouchStart"
-      @touchmove.stop.prevent="mediaTouchMove"
-      @touchend.stop.prevent="mediaTouchEnd"
-      @click="hide"
-      @load="onImageLoaded"
-    >
+    <div class="modal-image-container">
+      <PinchZoom
+        options="pinchZoomOptions"
+        class="modal-image-container-inner"
+        selector=".modal-image"
+        allow-pan-min-scale="1"
+        min-scale="1"
+        reset-to-min-scale-limit="1.2"
+        reach-min-scale-strategy="reset"
+        stop-propagate-handled="stop-propgate-handled"
+        :inner-class="'modal-image-container-inner'"
+      >
+        <img
+          v-if="type === 'image'"
+          :class="{ loading }"
+          class="modal-image"
+          :src="currentMedia.url"
+          :alt="currentMedia.description"
+          :title="currentMedia.description"
+          @load="onImageLoaded"
+        >
+      </PinchZoom>
+    </div>
     <VideoAttachment
       v-if="type === 'video'"
       class="modal-image"
@@ -105,6 +114,7 @@
       opacity: 1;
     }
   }
+  overflow: hidden;
 }
 
 .media-modal-view {
@@ -117,6 +127,40 @@
     }
   }
 
+.modal-image-container {
+  display: flex;
+  overflow: hidden;
+  align-items: center;
+  flex-direction: column;
+  max-width: 90%;
+  max-height: 95%;
+  flex-grow: 1;
+
+  &-inner {
+    width: 100%;
+    height: 100%;
+    flex-grow: 1;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+  }
+}
+
+.modal-image {
+  max-width: 100%;
+  max-height: 100%;
+  min-width: 0;
+  min-height: 0;
+  box-shadow: 0px 5px 15px 0 rgba(0, 0, 0, 0.5);
+  image-orientation: from-image; // NOTE: only FF supports this
+  animation: 0.1s cubic-bezier(0.7, 0, 1, 0.6) media-fadein;
+}
+
+//.modal-image {
+//  height: 90vh;
+//  width: 100%;
+//}
+
   .description,
   .counter {
     /* Hardcoded since background is also hardcoded */
diff --git a/src/components/pinch_zoom/pinch_zoom.js b/src/components/pinch_zoom/pinch_zoom.js
new file mode 100644
index 00000000..75bb6082
--- /dev/null
+++ b/src/components/pinch_zoom/pinch_zoom.js
@@ -0,0 +1,6 @@
+import PinchZoom from '@kazvmoe-infra/pinch-zoom-element'
+
+export default {
+  props: {
+  }
+}
diff --git a/src/components/pinch_zoom/pinch_zoom.vue b/src/components/pinch_zoom/pinch_zoom.vue
new file mode 100644
index 00000000..18d69719
--- /dev/null
+++ b/src/components/pinch_zoom/pinch_zoom.vue
@@ -0,0 +1,11 @@
+<template>
+  <pinch-zoom
+    class="pinch-zoom-parent"
+    v-bind="$attrs"
+    v-on="$listeners"
+  >
+    <slot />
+  </pinch-zoom>
+</template>
+
+<script src="./pinch_zoom.js"></script>
diff --git a/src/main.js b/src/main.js
index 3895da89..03493525 100644
--- a/src/main.js
+++ b/src/main.js
@@ -45,6 +45,8 @@ Vue.use(VueClickOutside)
 Vue.use(PortalVue)
 Vue.use(VBodyScrollLock)
 
+Vue.config.ignoredElements = ['pinch-zoom']
+
 Vue.component('FAIcon', FontAwesomeIcon)
 Vue.component('FALayers', FontAwesomeLayers)
 
diff --git a/src/modules/media_viewer.js b/src/modules/media_viewer.js
index d62aa7c1..ddcccb79 100644
--- a/src/modules/media_viewer.js
+++ b/src/modules/media_viewer.js
@@ -28,7 +28,7 @@ const mediaViewer = {
       commit('setMedia', media)
       dispatch('swipeScaler/reset')
     },
-    setCurrentMedia ({ commit, state }, current) {
+    setCurrentMedia ({ commit, state, dispatch }, current) {
       const index = state.media.indexOf(current)
       commit('setCurrentMedia', index || 0)
       dispatch('swipeScaler/reset')
diff --git a/yarn.lock b/yarn.lock
index 2d873c25..176b0c7d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -916,6 +916,12 @@
   resolved "https://registry.yarnpkg.com/@fortawesome/vue-fontawesome/-/vue-fontawesome-2.0.0.tgz#63da3e459147cebb0a8d58eed81d6071db9f5973"
   integrity sha512-N3VKw7KzRfOm8hShUVldpinlm13HpvLBQgT63QS+aCrIRLwjoEUXY5Rcmttbfb6HkzZaeqjLqd/aZCQ53UjQpg==
 
+"@kazvmoe-infra/pinch-zoom-element@https://lily.kazv.moe/infra/pinch-zoom-element.git":
+  version "1.1.1"
+  resolved "https://lily.kazv.moe/infra/pinch-zoom-element.git#de150c01057f0de0c3ef669917399fe370e36f40"
+  dependencies:
+    pointer-tracker "^2.0.3"
+
 "@nodelib/fs.scandir@2.1.3":
   version "2.1.3"
   resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b"
@@ -6995,6 +7001,11 @@ pngjs@^5.0.0:
   resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
   integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
 
+pointer-tracker@^2.0.3:
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/pointer-tracker/-/pointer-tracker-2.4.0.tgz#78721c2d2201486db11ec1094377f03023b621b3"
+  integrity sha512-pWI2tpaM/XNtc9mUTv42Rmjf6mkHvE8LT5DDEq0G7baPNhxNM9E3CepubPplSoSLk9E5bwQrAMyDcPVmJyTW4g==
+
 portal-vue@2.1.7:
   version "2.1.7"
   resolved "https://registry.yarnpkg.com/portal-vue/-/portal-vue-2.1.7.tgz#ea08069b25b640ca08a5b86f67c612f15f4e4ad4"
-- 
cgit v1.2.3-70-g09d2


From 29cd8fbd3bd9f936d2639a2edd773e1245b0b5a5 Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Mon, 2 Aug 2021 19:11:59 -0400
Subject: Add swipe-click handler to media modal

Now swiping will correctly change the current media, and with a good
preview. Clicking without swiping closes the overlay.
---
 src/components/media_modal/media_modal.js       |  58 ++++-------
 src/components/media_modal/media_modal.vue      |  13 ++-
 src/components/pinch_zoom/pinch_zoom.js         |   5 +
 src/modules/media_viewer.js                     |  56 +---------
 src/services/gesture_service/gesture_service.js | 130 +++++++++++-------------
 5 files changed, 94 insertions(+), 168 deletions(-)

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js
index 684fbe45..b4c0cfb4 100644
--- a/src/components/media_modal/media_modal.js
+++ b/src/components/media_modal/media_modal.js
@@ -2,10 +2,11 @@ import StillImage from '../still-image/still-image.vue'
 import VideoAttachment from '../video_attachment/video_attachment.vue'
 import Modal from '../modal/modal.vue'
 import PinchZoom from '../pinch_zoom/pinch_zoom.vue'
-import fileTypeService from '../../services/file_type/file_type.service.js'
+import SwipeClick from '../swipe_click/swipe_click.vue'
 import GestureService from '../../services/gesture_service/gesture_service'
 import Flash from 'src/components/flash/flash.vue'
 import Vuex from 'vuex'
+import fileTypeService from '../../services/file_type/file_type.service.js'
 import { library } from '@fortawesome/fontawesome-svg-core'
 import {
   faChevronLeft,
@@ -28,13 +29,15 @@ const MediaModal = {
     StillImage,
     VideoAttachment,
     PinchZoom,
+    SwipeClick,
     Modal,
     Flash
   },
   data () {
     return {
       loading: false,
-      pinchZoomOptions: {}
+      swipeDirection: GestureService.DIRECTION_LEFT,
+      swipeThreshold: 50
     }
   },
   computed: {
@@ -70,30 +73,19 @@ const MediaModal = {
     }
   },
   created () {
-    this.mediaGesture = new GestureService.SwipeAndScaleGesture({
-      direction: GestureService.DIRECTION_LEFT,
-      callbackPositive: this.goNext,
-      callbackNegative: this.goPrev,
-      swipePreviewCallback: this.handleSwipePreview,
-      swipeEndCallback: this.handleSwipeEnd,
-      pinchPreviewCallback: this.handlePinchPreview,
-      pinchEndCallback: this.handlePinchEnd,
-      threshold: 50
-    })
+    // this.mediaGesture = new GestureService.SwipeAndScaleGesture({
+    //   callbackPositive: this.goNext,
+    //   callbackNegative: this.goPrev,
+    //   swipePreviewCallback: this.handleSwipePreview,
+    //   swipeEndCallback: this.handleSwipeEnd,
+    //   pinchPreviewCallback: this.handlePinchPreview,
+    //   pinchEndCallback: this.handlePinchEnd
+    // })
   },
   methods: {
     getType (media) {
       return fileTypeService.fileType(media.mimetype)
     },
-    mediaTouchStart (e) {
-      this.mediaGesture.start(e)
-    },
-    mediaTouchMove (e) {
-      this.mediaGesture.move(e)
-    },
-    mediaTouchEnd (e) {
-      this.mediaGesture.end(e)
-    },
     hide () {
       this.$store.dispatch('closeMediaViewer')
     },
@@ -105,6 +97,7 @@ const MediaModal = {
           this.loading = true
         }
         this.$store.dispatch('setCurrentMedia', newMedia)
+        this.$refs.pinchZoom.setTransform({ scale: 1, x: 0, y: 0 })
       }
     },
     goNext () {
@@ -115,40 +108,25 @@ const MediaModal = {
           this.loading = true
         }
         this.$store.dispatch('setCurrentMedia', newMedia)
+        this.$refs.pinchZoom.setTransform({ scale: 1, x: 0, y: 0 })
       }
     },
     onImageLoaded () {
       this.loading = false
     },
     handleSwipePreview (offsets) {
-      this.$store.dispatch('swipeScaler/apply', {
-        offsets: this.scaling > SCALING_ENABLE_MOVE_THRESHOLD ? offsets : onlyXAxis(offsets)
-      })
+      this.$refs.pinchZoom.setTransform({ scale: 1, x: offsets[0], y: 0 })
     },
     handleSwipeEnd (sign) {
-      if (this.scaling > SCALING_ENABLE_MOVE_THRESHOLD) {
-        this.$store.dispatch('swipeScaler/finish')
-        return
-      }
+      console.log('handleSwipeEnd:', sign)
       if (sign === 0) {
-        this.$store.dispatch('swipeScaler/reset')
+        this.$refs.pinchZoom.setTransform({ scale: 1, x: 0, y: 0 })
       } else if (sign > 0) {
         this.goNext()
       } else {
         this.goPrev()
       }
     },
-    handlePinchPreview (offsets, scaling) {
-      console.log('handle pinch preview:', offsets, scaling)
-      this.$store.dispatch('swipeScaler/apply', { offsets, scaling })
-    },
-    handlePinchEnd () {
-      if (this.scaling > SCALING_RESET_MIN) {
-        this.$store.dispatch('swipeScaler/finish')
-      } else {
-        this.$store.dispatch('swipeScaler/reset')
-      }
-    },
     handleKeyupEvent (e) {
       if (this.showing && e.keyCode === 27) { // escape
         this.hide()
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index a3fad4c5..e385024e 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -4,9 +4,16 @@
     class="media-modal-view"
     @backdropClicked="hide"
   >
-    <div class="modal-image-container">
+    <SwipeClick
+      class="modal-image-container"
+      :direction="swipeDirection"
+      :threshold="swipeThreshold"
+      @preview-requested="handleSwipePreview"
+      @swipe-finished="handleSwipeEnd"
+      @swipeless-clicked="hide"
+    >
       <PinchZoom
-        options="pinchZoomOptions"
+        ref="pinchZoom"
         class="modal-image-container-inner"
         selector=".modal-image"
         allow-pan-min-scale="1"
@@ -26,7 +33,7 @@
           @load="onImageLoaded"
         >
       </PinchZoom>
-    </div>
+    </SwipeClick>
     <VideoAttachment
       v-if="type === 'video'"
       class="modal-image"
diff --git a/src/components/pinch_zoom/pinch_zoom.js b/src/components/pinch_zoom/pinch_zoom.js
index 75bb6082..36bebbce 100644
--- a/src/components/pinch_zoom/pinch_zoom.js
+++ b/src/components/pinch_zoom/pinch_zoom.js
@@ -2,5 +2,10 @@ import PinchZoom from '@kazvmoe-infra/pinch-zoom-element'
 
 export default {
   props: {
+  },
+  methods: {
+    setTransform ({ scale, x, y }) {
+      this.$el.setTransform({ scale, x, y })
+    }
   }
 }
diff --git a/src/modules/media_viewer.js b/src/modules/media_viewer.js
index ddcccb79..0299b04e 100644
--- a/src/modules/media_viewer.js
+++ b/src/modules/media_viewer.js
@@ -26,67 +26,13 @@ const mediaViewer = {
         return supportedTypes.has(type)
       })
       commit('setMedia', media)
-      dispatch('swipeScaler/reset')
     },
-    setCurrentMedia ({ commit, state, dispatch }, current) {
+    setCurrentMedia ({ commit, state }, current) {
       const index = state.media.indexOf(current)
       commit('setCurrentMedia', index || 0)
-      dispatch('swipeScaler/reset')
     },
     closeMediaViewer ({ commit, dispatch }) {
       commit('close')
-      dispatch('swipeScaler/reset')
-    }
-  },
-  modules: {
-    swipeScaler: {
-      namespaced: true,
-
-      state: {
-        origOffsets: [0, 0],
-        offsets: [0, 0],
-        origScaling: 1,
-        scaling: 1
-      },
-
-      mutations: {
-        reset (state) {
-          state.origOffsets = [0, 0]
-          state.offsets = [0, 0]
-          state.origScaling = 1
-          state.scaling = 1
-        },
-        applyOffsets (state, { offsets }) {
-          state.offsets = state.origOffsets.map((k, n) => k + offsets[n])
-        },
-        applyScaling (state, { scaling }) {
-          state.scaling = state.origScaling * scaling
-        },
-        finish (state) {
-          state.origOffsets = [...state.offsets]
-          state.origScaling = state.scaling
-        },
-        revert (state) {
-          state.offsets = [...state.origOffsets]
-          state.scaling = state.origScaling
-        }
-      },
-
-      actions: {
-        reset ({ commit }) {
-          commit('reset')
-        },
-        apply ({ commit }, { offsets, scaling = 1 }) {
-          commit('applyOffsets', { offsets })
-          commit('applyScaling', { scaling })
-        },
-        finish ({ commit }) {
-          commit('finish')
-        },
-        revert ({ commit }) {
-          commit('revert')
-        }
-      }
     }
   }
 }
diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js
index 82337bc6..f10dec3a 100644
--- a/src/services/gesture_service/gesture_service.js
+++ b/src/services/gesture_service/gesture_service.js
@@ -4,25 +4,27 @@ const DIRECTION_RIGHT = [1, 0]
 const DIRECTION_UP = [0, -1]
 const DIRECTION_DOWN = [0, 1]
 
-const DISTANCE_MIN = 1
+// const DISTANCE_MIN = 1
 
-const isSwipeEvent = e => (e.touches.length === 1)
-const isSwipeEventEnd = e => (e.changedTouches.length === 1)
+// const isSwipeEvent = e => (e.touches.length === 1)
+// const isSwipeEventEnd = e => (e.changedTouches.length === 1)
 
-const isScaleEvent = e => (e.targetTouches.length === 2)
-const isScaleEventEnd = e => (e.targetTouches.length === 1)
+// const isScaleEvent = e => (e.targetTouches.length === 2)
+// const isScaleEventEnd = e => (e.targetTouches.length === 1)
 
 const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]]
 
-const vectorMinus = (a, b) => a.map((k, n) => k - b[n])
-const vectorAdd = (a, b) => a.map((k, n) => k + b[n])
+// const vectorMinus = (a, b) => a.map((k, n) => k - b[n])
+// const vectorAdd = (a, b) => a.map((k, n) => k + b[n])
 
-const avgCoord = (coords) => [...coords].reduce(vectorAdd, [0, 0]).map(d => d / coords.length)
+// const avgCoord = (coords) => [...coords].reduce(vectorAdd, [0, 0]).map(d => d / coords.length)
 
 const touchCoord = touch => [touch.screenX, touch.screenY]
 
 const touchEventCoord = e => touchCoord(e.touches[0])
 
+const pointerEventCoord = e => [e.clientX, e.clientY]
+
 const vectorLength = v => Math.sqrt(v[0] * v[0] + v[1] * v[1])
 
 const perpendicular = v => [v[1], -v[0]]
@@ -78,104 +80,87 @@ const updateSwipe = (event, gesture) => {
   gesture._swiping = false
 }
 
-class SwipeAndScaleGesture {
+class SwipeAndClickGesture {
   // swipePreviewCallback(offsets: Array[Number])
   //   offsets: the offset vector which the underlying component should move, from the starting position
-  // pinchPreviewCallback(offsets: Array[Number], scaling: Number)
-  //   offsets: the offset vector which the underlying component should move, from the starting position
-  //   scaling: the scaling factor we should apply to the underlying component, from the starting position
-  // swipeEndcallback(sign: 0|-1|1)
+  // swipeEndCallback(sign: 0|-1|1)
   //   sign: if the swipe does not meet the threshold, 0
   //         if the swipe meets the threshold in the positive direction, 1
   //         if the swipe meets the threshold in the negative direction, -1
   constructor ({
     direction,
-    // swipeStartCallback, pinchStartCallback,
-    swipePreviewCallback, pinchPreviewCallback,
-    swipeEndCallback, pinchEndCallback,
+    // swipeStartCallback
+    swipePreviewCallback,
+    swipeEndCallback,
+    swipeCancelCallback,
+    swipelessClickCallback,
     threshold = 30, perpendicularTolerance = 1.0
   }) {
     const nop = () => { console.log('Warning: Not implemented') }
     this.direction = direction
     this.swipePreviewCallback = swipePreviewCallback || nop
-    this.pinchPreviewCallback = pinchPreviewCallback || nop
     this.swipeEndCallback = swipeEndCallback || nop
-    this.pinchEndCallback = pinchEndCallback || nop
+    this.swipeCancelCallback = swipeCancelCallback || nop
+    this.swipelessClickCallback = swipelessClickCallback || nop
     this.threshold = threshold
     this.perpendicularTolerance = perpendicularTolerance
+    this._reset()
+  }
+
+  _reset () {
     this._startPos = [0, 0]
-    this._startDistance = DISTANCE_MIN
+    this._pointerId = -1
     this._swiping = false
+    this._swiped = false
   }
 
   start (event) {
     console.log('start() called', event)
-    if (isSwipeEvent(event)) {
-      this._startPos = touchEventCoord(event)
-      console.log('start pos:', this._startPos)
-      this._swiping = true
-    } else if (isScaleEvent(event)) {
-      const coords = [...event.targetTouches].map(touchCoord)
-      this._startPos = avgCoord(coords)
-      this._startDistance = vectorLength(deltaCoord(coords[0], coords[1]))
-      if (this._startDistance < DISTANCE_MIN) {
-        this._startDistance = DISTANCE_MIN
-      }
-      this._scalePoints = [...event.targetTouches]
-      this._swiping = false
-      console.log(
-        'is scale event, start =', this._startPos,
-        'dist =', this._startDistance)
-    }
+
+    this._startPos = pointerEventCoord(event)
+    this._pointerId = event.pointerId
+    console.log('start pos:', this._startPos)
+    this._swiping = true
+    this._swiped = false
   }
 
   move (event) {
-    // console.log('move called', event)
-    if (isSwipeEvent(event)) {
-      const touch = event.changedTouches[0]
-      const delta = deltaCoord(this._startPos, touchCoord(touch))
+    if (this._swiping && this._pointerId === event.pointerId) {
+      this._swiped = true
+
+      const coord = pointerEventCoord(event)
+      const delta = deltaCoord(this._startPos, coord)
 
       this.swipePreviewCallback(delta)
-    } else if (isScaleEvent(event)) {
-      console.log('is scale event')
-      const coords = [...event.targetTouches].map(touchCoord)
-      const curPos = avgCoord(coords)
-      const curDistance = vectorLength(deltaCoord(coords[0], coords[1]))
-      const scaling = curDistance / this._startDistance
-      const posDiff = vectorMinus(curPos, this._startPos)
-      // const delta = vectorAdd(numProduct((1 - scaling), this._startPos), posDiff)
-      const delta = posDiff
-      // console.log(
-      //   'is scale event, cur =', curPos,
-      //   'dist =', curDistance,
-      //   'scale =', scaling,
-      //   'delta =', delta)
-      this.pinchPreviewCallback(delta, scaling)
     }
   }
 
-  end (event) {
-    console.log('end() called', event)
-    if (isScaleEventEnd(event)) {
-      this.pinchEndCallback()
-    }
-
-    if (!isSwipeEventEnd(event)) {
-      console.log('not swipe event')
+  cancel (event) {
+    if (!this._swiping || this._pointerId !== event.pointerId) {
       return
     }
+
+    this.swipeCancelCallback()
+  }
+
+  end (event) {
     if (!this._swiping) {
       console.log('not swiping')
       return
     }
-    this.swiping = false
 
-    console.log('is swipe event')
+    if (this._pointerId !== event.pointerId) {
+      console.log('pointer id does not match')
+      return
+    }
+
+    this._swiping = false
+
+    console.log('end: is swipe event')
 
     // movement too small
-    const touch = event.changedTouches[0]
-    const delta = deltaCoord(this._startPos, touchCoord(touch))
-    this.swipePreviewCallback(delta)
+    const coord = pointerEventCoord(event)
+    const delta = deltaCoord(this._startPos, coord)
 
     const sign = (() => {
       if (vectorLength(delta) < this.threshold) {
@@ -198,7 +183,12 @@ class SwipeAndScaleGesture {
       return isPositive ? 1 : -1
     })()
 
-    this.swipeEndCallback(sign)
+    if (this._swiped) {
+      this.swipeEndCallback(sign)
+    } else {
+      this.swipelessClickCallback()
+    }
+    this._reset()
   }
 }
 
@@ -210,7 +200,7 @@ const GestureService = {
   swipeGesture,
   beginSwipe,
   updateSwipe,
-  SwipeAndScaleGesture
+  SwipeAndClickGesture
 }
 
 export default GestureService
-- 
cgit v1.2.3-70-g09d2


From 23a6b86ef3c976509bad4fb4f9a223a5724ec7e5 Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Mon, 2 Aug 2021 19:21:18 -0400
Subject: Clean up

---
 src/components/media_modal/media_modal.js       | 23 +++----------------
 src/components/media_modal/media_modal.vue      |  7 +++---
 src/services/gesture_service/gesture_service.js | 30 ++++++++-----------------
 3 files changed, 15 insertions(+), 45 deletions(-)

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js
index b4c0cfb4..2f00f5f9 100644
--- a/src/components/media_modal/media_modal.js
+++ b/src/components/media_modal/media_modal.js
@@ -37,7 +37,9 @@ const MediaModal = {
     return {
       loading: false,
       swipeDirection: GestureService.DIRECTION_LEFT,
-      swipeThreshold: 50
+      swipeThreshold: 50,
+      pinchZoomMinScale: 1,
+      pinchZoomScaleResetLimit: 1.2
     }
   },
   computed: {
@@ -62,25 +64,6 @@ const MediaModal = {
     type () {
       return this.currentMedia ? this.getType(this.currentMedia) : null
     },
-    scaling () {
-      return this.$store.state.mediaViewer.swipeScaler.scaling
-    },
-    offsets () {
-      return this.$store.state.mediaViewer.swipeScaler.offsets
-    },
-    transform () {
-      return `translate(${this.offsets[0]}px, ${this.offsets[1]}px) scale(${this.scaling}, ${this.scaling})`
-    }
-  },
-  created () {
-    // this.mediaGesture = new GestureService.SwipeAndScaleGesture({
-    //   callbackPositive: this.goNext,
-    //   callbackNegative: this.goPrev,
-    //   swipePreviewCallback: this.handleSwipePreview,
-    //   swipeEndCallback: this.handleSwipeEnd,
-    //   pinchPreviewCallback: this.handlePinchPreview,
-    //   pinchEndCallback: this.handlePinchEnd
-    // })
   },
   methods: {
     getType (media) {
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index e385024e..76cf4319 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -16,12 +16,11 @@
         ref="pinchZoom"
         class="modal-image-container-inner"
         selector=".modal-image"
-        allow-pan-min-scale="1"
-        min-scale="1"
-        reset-to-min-scale-limit="1.2"
         reach-min-scale-strategy="reset"
         stop-propagate-handled="stop-propgate-handled"
-        :inner-class="'modal-image-container-inner'"
+        :allow-pan-min-scale="pinchZoomMinScale"
+        :min-scale="pinchZoomMinScale"
+        :reset-to-min-scale-limit="pinchZoomScaleResetLimit"
       >
         <img
           v-if="type === 'image'"
diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js
index f10dec3a..238b7875 100644
--- a/src/services/gesture_service/gesture_service.js
+++ b/src/services/gesture_service/gesture_service.js
@@ -4,21 +4,8 @@ const DIRECTION_RIGHT = [1, 0]
 const DIRECTION_UP = [0, -1]
 const DIRECTION_DOWN = [0, 1]
 
-// const DISTANCE_MIN = 1
-
-// const isSwipeEvent = e => (e.touches.length === 1)
-// const isSwipeEventEnd = e => (e.changedTouches.length === 1)
-
-// const isScaleEvent = e => (e.targetTouches.length === 2)
-// const isScaleEventEnd = e => (e.targetTouches.length === 1)
-
 const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]]
 
-// const vectorMinus = (a, b) => a.map((k, n) => k - b[n])
-// const vectorAdd = (a, b) => a.map((k, n) => k + b[n])
-
-// const avgCoord = (coords) => [...coords].reduce(vectorAdd, [0, 0]).map(d => d / coords.length)
-
 const touchCoord = touch => [touch.screenX, touch.screenY]
 
 const touchEventCoord = e => touchCoord(e.touches[0])
@@ -31,13 +18,14 @@ const perpendicular = v => [v[1], -v[0]]
 
 const dotProduct = (v1, v2) => v1[0] * v2[0] + v1[1] * v2[1]
 
-// const numProduct = (num, v) => v.map(k => num * k)
-
 const project = (v1, v2) => {
   const scalar = (dotProduct(v1, v2) / dotProduct(v2, v2))
   return [scalar * v2[0], scalar * v2[1]]
 }
 
+// const debug = console.log
+const debug = () => {}
+
 // direction: either use the constants above or an arbitrary 2d vector.
 // threshold: how many Px to move from touch origin before checking if the
 //    callback should be called.
@@ -96,7 +84,7 @@ class SwipeAndClickGesture {
     swipelessClickCallback,
     threshold = 30, perpendicularTolerance = 1.0
   }) {
-    const nop = () => { console.log('Warning: Not implemented') }
+    const nop = () => { debug('Warning: Not implemented') }
     this.direction = direction
     this.swipePreviewCallback = swipePreviewCallback || nop
     this.swipeEndCallback = swipeEndCallback || nop
@@ -115,11 +103,11 @@ class SwipeAndClickGesture {
   }
 
   start (event) {
-    console.log('start() called', event)
+    debug('start() called', event)
 
     this._startPos = pointerEventCoord(event)
     this._pointerId = event.pointerId
-    console.log('start pos:', this._startPos)
+    debug('start pos:', this._startPos)
     this._swiping = true
     this._swiped = false
   }
@@ -145,18 +133,18 @@ class SwipeAndClickGesture {
 
   end (event) {
     if (!this._swiping) {
-      console.log('not swiping')
+      debug('not swiping')
       return
     }
 
     if (this._pointerId !== event.pointerId) {
-      console.log('pointer id does not match')
+      debug('pointer id does not match')
       return
     }
 
     this._swiping = false
 
-    console.log('end: is swipe event')
+    debug('end: is swipe event')
 
     // movement too small
     const coord = pointerEventCoord(event)
-- 
cgit v1.2.3-70-g09d2


From ddf6c9cef085757ca008c9c7e67f998315948f45 Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Mon, 2 Aug 2021 20:46:10 -0400
Subject: Allow pinch-zoom to fill the whole screen

---
 src/components/media_modal/media_modal.vue | 4 ++++
 1 file changed, 4 insertions(+)

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index 76cf4319..80ce1dea 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -140,7 +140,10 @@
   flex-direction: column;
   max-width: 90%;
   max-height: 95%;
+  width: 100%;
+  height: 100%;
   flex-grow: 1;
+  justify-content: center;
 
   &-inner {
     width: 100%;
@@ -149,6 +152,7 @@
     display: flex;
     flex-direction: column;
     align-items: center;
+    justify-content: center;
   }
 }
 
-- 
cgit v1.2.3-70-g09d2


From 76727cd39cf588ffc1c15c0a2fd8f1ae30ada7ea Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Wed, 8 Sep 2021 21:28:29 -0400
Subject: Remove image box-shadow in media modal

The box-shadow causes an image to be very blurry on Webkit browsers
(experienced: Konqueror, Safari; heard of: Chrome) when scaled up
if the initial size of the image is much smaller than the actual size
(e.g. when viewing a very long picture).

The shadow is not really obvious anyway.
---
 src/components/media_modal/media_modal.vue | 1 -
 1 file changed, 1 deletion(-)

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index 80ce1dea..0fbdb558 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -161,7 +161,6 @@
   max-height: 100%;
   min-width: 0;
   min-height: 0;
-  box-shadow: 0px 5px 15px 0 rgba(0, 0, 0, 0.5);
   image-orientation: from-image; // NOTE: only FF supports this
   animation: 0.1s cubic-bezier(0.7, 0, 1, 0.6) media-fadein;
 }
-- 
cgit v1.2.3-70-g09d2


From 1128cc463c4b16c41ab21c554646a8104ba58826 Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Wed, 8 Sep 2021 21:50:28 -0400
Subject: Fix video in media modal not displaying properly

---
 src/components/media_modal/media_modal.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index 0fbdb558..abc1d797 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -5,6 +5,7 @@
     @backdropClicked="hide"
   >
     <SwipeClick
+      v-if="type === 'image'"
       class="modal-image-container"
       :direction="swipeDirection"
       :threshold="swipeThreshold"
@@ -23,7 +24,6 @@
         :reset-to-min-scale-limit="pinchZoomScaleResetLimit"
       >
         <img
-          v-if="type === 'image'"
           :class="{ loading }"
           class="modal-image"
           :src="currentMedia.url"
-- 
cgit v1.2.3-70-g09d2


From 90b066a7443ecb37b1a72d3b306471d0ad75c68c Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Sun, 20 Feb 2022 22:02:31 -0500
Subject: Fix webkit image blurs

---
 src/components/media_modal/media_modal.vue | 53 +++++++++++-------------------
 1 file changed, 19 insertions(+), 34 deletions(-)

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index abc1d797..ff9c4953 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -133,42 +133,28 @@
     }
   }
 
-.modal-image-container {
-  display: flex;
-  overflow: hidden;
-  align-items: center;
-  flex-direction: column;
-  max-width: 90%;
-  max-height: 95%;
-  width: 100%;
-  height: 100%;
-  flex-grow: 1;
-  justify-content: center;
-
-  &-inner {
+  .modal-image-container {
+    display: flex;
+    overflow: hidden;
+    align-items: center;
+    flex-direction: column;
+    max-width: 90%;
+    max-height: 95%;
     width: 100%;
     height: 100%;
     flex-grow: 1;
-    display: flex;
-    flex-direction: column;
-    align-items: center;
     justify-content: center;
-  }
-}
-
-.modal-image {
-  max-width: 100%;
-  max-height: 100%;
-  min-width: 0;
-  min-height: 0;
-  image-orientation: from-image; // NOTE: only FF supports this
-  animation: 0.1s cubic-bezier(0.7, 0, 1, 0.6) media-fadein;
-}
 
-//.modal-image {
-//  height: 90vh;
-//  width: 100%;
-//}
+    &-inner {
+      width: 100%;
+      height: 100%;
+      flex-grow: 1;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+    }
+  }
 
   .description,
   .counter {
@@ -189,9 +175,8 @@
   }
 
   .modal-image {
-    max-width: 90%;
-    max-height: 90%;
-    box-shadow: 0px 5px 15px 0 rgba(0, 0, 0, 0.5);
+    max-width: 100%;
+    max-height: 100%;
     image-orientation: from-image; // NOTE: only FF supports this
     animation: 0.1s cubic-bezier(0.7, 0, 1, 0.6) media-fadein;
 
-- 
cgit v1.2.3-70-g09d2


From 7dd1a0dd30773fd51508feaf93be53bdb744ec79 Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Sun, 20 Feb 2022 22:45:58 -0500
Subject: Prevent hiding media viewer if swiped over SwipeClick

---
 src/components/media_modal/media_modal.js       | 9 +++++++++
 src/components/media_modal/media_modal.vue      | 3 ++-
 src/services/gesture_service/gesture_service.js | 8 +++++---
 3 files changed, 16 insertions(+), 4 deletions(-)

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js
index 4ed0b66f..0c19ed50 100644
--- a/src/components/media_modal/media_modal.js
+++ b/src/components/media_modal/media_modal.js
@@ -76,6 +76,15 @@ const MediaModal = {
         this.$store.dispatch('closeMediaViewer')
       }, transitionTime)
     },
+    hideIfNotSwiped (event) {
+      // If we have swiped over SwipeClick, do not trigger hide
+      const comp = this.$refs.swipeClick
+      if (!comp) {
+        this.hide()
+      } else {
+        comp.$gesture.click(event)
+      }
+    },
     goPrev () {
       if (this.canNavigate) {
         const prevIndex = this.currentIndex === 0 ? this.media.length - 1 : (this.currentIndex - 1)
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index ff9c4953..1aa66a55 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -2,10 +2,11 @@
   <Modal
     v-if="showing"
     class="media-modal-view"
-    @backdropClicked="hide"
+    @backdropClicked="hideIfNotSwiped"
   >
     <SwipeClick
       v-if="type === 'image'"
+      ref="swipeClick"
       class="modal-image-container"
       :direction="swipeDirection"
       :threshold="swipeThreshold"
diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js
index 94da3f43..265a7f25 100644
--- a/src/services/gesture_service/gesture_service.js
+++ b/src/services/gesture_service/gesture_service.js
@@ -81,7 +81,9 @@ class SwipeAndClickGesture {
     swipeEndCallback,
     swipeCancelCallback,
     swipelessClickCallback,
-    threshold = 30, perpendicularTolerance = 1.0
+    threshold = 30,
+    perpendicularTolerance = 1.0,
+    disableClickThreshold = 1
   }) {
     const nop = () => {}
     this.direction = direction
@@ -90,6 +92,7 @@ class SwipeAndClickGesture {
     this.swipeCancelCallback = swipeCancelCallback || nop
     this.swipelessClickCallback = swipelessClickCallback || nop
     this.threshold = typeof threshold === 'function' ? threshold : () => threshold
+    this.disableClickThreshold = typeof disableClickThreshold === 'function' ? disableClickThreshold : () => disableClickThreshold
     this.perpendicularTolerance = perpendicularTolerance
     this._reset()
   }
@@ -169,7 +172,6 @@ class SwipeAndClickGesture {
       return isPositive ? 1 : -1
     })()
 
-    const swiped = this._swiped
     if (this._swiped) {
       this.swipeEndCallback(sign)
     }
@@ -178,7 +180,7 @@ class SwipeAndClickGesture {
     // the end point is far from the starting point
     // so for other kinds of pointers do not check
     // whether we have swiped
-    if (swiped && event.pointerType === 'mouse') {
+    if (vectorLength(delta) >= this.disableClickThreshold() && event.pointerType === 'mouse') {
       this._preventNextClick = true
     }
   }
-- 
cgit v1.2.3-70-g09d2


From 5fb302d0f1f311985f7d26a8e8f11e15ec72d26f Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Thu, 3 Mar 2022 12:51:13 -0500
Subject: Add hide button to media modal

---
 src/components/media_modal/media_modal.js  |  6 ++-
 src/components/media_modal/media_modal.vue | 74 ++++++++++++++++++++++--------
 2 files changed, 60 insertions(+), 20 deletions(-)

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js
index e7efc018..01a90377 100644
--- a/src/components/media_modal/media_modal.js
+++ b/src/components/media_modal/media_modal.js
@@ -10,13 +10,15 @@ import { library } from '@fortawesome/fontawesome-svg-core'
 import {
   faChevronLeft,
   faChevronRight,
-  faCircleNotch
+  faCircleNotch,
+  faTimes
 } from '@fortawesome/free-solid-svg-icons'
 
 library.add(
   faChevronLeft,
   faChevronRight,
-  faCircleNotch
+  faCircleNotch,
+  faTimes
 )
 
 const MediaModal = {
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index 1aa66a55..2d00d7fb 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -58,25 +58,36 @@
     <button
       v-if="canNavigate"
       :title="$t('media_modal.previous')"
-      class="modal-view-button-arrow modal-view-button-arrow--prev"
+      class="modal-view-button modal-view-button-arrow modal-view-button-arrow--prev"
       @click.stop.prevent="goPrev"
     >
       <FAIcon
-        class="arrow-icon"
+        class="button-icon arrow-icon"
         icon="chevron-left"
       />
     </button>
     <button
       v-if="canNavigate"
       :title="$t('media_modal.next')"
-      class="modal-view-button-arrow modal-view-button-arrow--next"
+      class="modal-view-button modal-view-button-arrow modal-view-button-arrow--next"
       @click.stop.prevent="goNext"
     >
       <FAIcon
-        class="arrow-icon"
+        class="button-icon arrow-icon"
         icon="chevron-right"
       />
     </button>
+    <button
+      class="modal-view-button modal-view-button-hide"
+      :title="$t('media_modal.hide')"
+      @click.stop.prevent="hide"
+    >
+      <FAIcon
+        class="button-icon"
+        icon="times"
+      />
+    </button>
+
     <span
       v-if="description"
       class="description"
@@ -104,11 +115,16 @@
 <script src="./media_modal.js"></script>
 
 <style lang="scss">
+$modal-view-button-icon-height: 30px;
+$modal-view-button-icon-width: 32px;
+$modal-view-button-icon-margin: 6px;
+
 .modal-view.media-modal-view {
   z-index: 1001;
   flex-direction: column;
 
-  .modal-view-button-arrow {
+  .modal-view-button-arrow,
+  .modal-view-button-hide {
     opacity: 0.75;
 
     &:focus,
@@ -139,8 +155,8 @@
     overflow: hidden;
     align-items: center;
     flex-direction: column;
-    max-width: 90%;
-    max-height: 95%;
+    max-width: 100%;
+    max-height: 100%;
     width: 100%;
     height: 100%;
     flex-grow: 1;
@@ -200,13 +216,7 @@
     }
   }
 
-  .modal-view-button-arrow {
-    position: absolute;
-    display: block;
-    top: 50%;
-    margin-top: -50px;
-    width: 70px;
-    height: 100px;
+  .modal-view-button {
     border: 0;
     padding: 0;
     opacity: 0;
@@ -217,11 +227,29 @@
     cursor: pointer;
     transition: opacity 333ms cubic-bezier(.4,0,.22,1);
 
+    .button-icon {
+      position: absolute;
+      height: $modal-view-button-icon-height;
+      width: $modal-view-button-icon-width;
+      font-size: 14px;
+      line-height: $modal-view-button-icon-height;
+      color: #FFF;
+      text-align: center;
+      background-color: rgba(0,0,0,.3);
+    }
+  }
+
+  .modal-view-button-arrow {
+    position: absolute;
+    display: block;
+    top: 50%;
+    margin-top: -50px;
+    width: 70px;
+    height: 100px;
+
     .arrow-icon {
       position: absolute;
       top: 35px;
-      height: 30px;
-      width: 32px;
       font-size: 14px;
       line-height: 30px;
       color: #FFF;
@@ -232,16 +260,26 @@
     &--prev {
       left: 0;
       .arrow-icon {
-        left: 6px;
+        left: $modal-view-button-icon-margin;
       }
     }
 
     &--next {
       right: 0;
       .arrow-icon {
-        right: 6px;
+        right: $modal-view-button-icon-margin;
       }
     }
   }
+
+  .modal-view-button-hide {
+    position: absolute;
+    top: 0;
+    right: 0;
+    .button-icon {
+      top: $modal-view-button-icon-margin;
+      right: $modal-view-button-icon-margin;
+    }
+  }
 }
 </style>
-- 
cgit v1.2.3-70-g09d2


From 1b204012eaf3fcc262689a070424c618d8d3a917 Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Sun, 6 Mar 2022 18:38:57 -0500
Subject: Make media modal buttons larger

---
 src/components/media_modal/media_modal.vue | 20 +++++++++++---------
 1 file changed, 11 insertions(+), 9 deletions(-)

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index 2d00d7fb..708a43c6 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -115,9 +115,10 @@
 <script src="./media_modal.js"></script>
 
 <style lang="scss">
-$modal-view-button-icon-height: 30px;
-$modal-view-button-icon-width: 32px;
-$modal-view-button-icon-margin: 6px;
+$modal-view-button-icon-height: 3em;
+$modal-view-button-icon-half-height: calc(#{$modal-view-button-icon-height} / 2);
+$modal-view-button-icon-width: 3em;
+$modal-view-button-icon-margin: 0.5em;
 
 .modal-view.media-modal-view {
   z-index: 1001;
@@ -226,6 +227,8 @@ $modal-view-button-icon-margin: 6px;
     overflow: visible;
     cursor: pointer;
     transition: opacity 333ms cubic-bezier(.4,0,.22,1);
+    height: $modal-view-button-icon-height;
+    width: $modal-view-button-icon-width;
 
     .button-icon {
       position: absolute;
@@ -243,15 +246,14 @@ $modal-view-button-icon-margin: 6px;
     position: absolute;
     display: block;
     top: 50%;
-    margin-top: -50px;
-    width: 70px;
-    height: 100px;
+    margin-top: $modal-view-button-icon-half-height;
+    width: $modal-view-button-icon-width;
+    height: $modal-view-button-icon-height;
 
     .arrow-icon {
       position: absolute;
-      top: 35px;
-      font-size: 14px;
-      line-height: 30px;
+      top: 0;
+      line-height: $modal-view-button-icon-height;
       color: #FFF;
       text-align: center;
       background-color: rgba(0,0,0,.3);
-- 
cgit v1.2.3-70-g09d2


From 6109fab14e4dac3a85ffa51ddfcd7e3aaef5149a Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Fri, 8 Apr 2022 10:17:23 +0300
Subject: cleanup, fix some things, try to disable scrollbars on mobile

---
 src/App.scss                               | 28 ++++++++++++++++------------
 src/App.vue                                |  2 +-
 src/components/chat/chat.scss              |  9 ---------
 src/components/media_modal/media_modal.vue |  2 +-
 src/components/modal/modal.vue             |  2 +-
 5 files changed, 19 insertions(+), 24 deletions(-)

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/src/App.scss b/src/App.scss
index dd3d8cbb..0deac2b1 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -116,7 +116,7 @@ nav {
 
 #content {
   overscroll-behavior-y: none;
-  overflow-y: auto;
+  overflow-y: scroll;
   overflow-x: hidden;
   position: sticky;
 }
@@ -198,19 +198,23 @@ nav {
       margin-left: -2em;
       padding-left: 2.5em;
 
-      &:not(.-show-scrollbar) {
-        scrollbar-width: none;
-        margin-right: -2em;
-        padding-right: 2.5em;
-
-        &::-webkit-scrollbar {
-          display: block;
-          width: 0;
+      // Only show custom scrollbars on devices which
+      // have a cursor/pointer to operate them
+      @media (pointer: fine) {
+        &:not(.-show-scrollbar) {
+          scrollbar-width: none;
+          margin-right: -2em;
+          padding-right: 2.5em;
+
+          &::-webkit-scrollbar {
+            display: block;
+            width: 0;
+          }
         }
-      }
 
-      .panel-heading.-sticky {
-        top: -10px;
+        .panel-heading.-sticky {
+          top: -10px;
+        }
       }
     }
   }
diff --git a/src/App.vue b/src/App.vue
index 243a3314..b5f6e7d3 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -41,8 +41,8 @@
         <router-view />
       </div>
       <div id="notifs-column" class="column -scrollable" :class="{ '-show-scrollbar': showScrollbars }"/>
-      <media-modal />
     </div>
+    <media-modal />
     <shout-panel
       v-if="currentUser && shout && !hideShoutbox"
       :floating="true"
diff --git a/src/components/chat/chat.scss b/src/components/chat/chat.scss
index 51bb3ea6..85e9f59d 100644
--- a/src/components/chat/chat.scss
+++ b/src/components/chat/chat.scss
@@ -133,18 +133,9 @@
     .chat-view-heading {
       box-sizing: border-box;
       position: static;
-      z-index: 9999;
       top: 0;
       margin-top: 0;
       border-radius: 0;
-
-      /* This practically overlays the panel heading color over panel background
-       * color. This is needed because we allow transparent panel background and
-       * it doesn't work well in this "disjointed panel header" case
-       */
-      background:
-        linear-gradient(to top, var(--panel), var(--panel)),
-        linear-gradient(to top, var(--bg), var(--bg));
       height: 50px;
     }
 
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index 708a43c6..c7dcd007 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -121,7 +121,7 @@ $modal-view-button-icon-width: 3em;
 $modal-view-button-icon-margin: 0.5em;
 
 .modal-view.media-modal-view {
-  z-index: 1001;
+  z-index: 9000;
   flex-direction: column;
 
   .modal-view-button-arrow,
diff --git a/src/components/modal/modal.vue b/src/components/modal/modal.vue
index 2b58913f..9394efff 100644
--- a/src/components/modal/modal.vue
+++ b/src/components/modal/modal.vue
@@ -35,7 +35,7 @@ export default {
 
 <style lang="scss">
 .modal-view {
-  z-index: 1000;
+  z-index: 2000;
   position: fixed;
   top: 0;
   left: 0;
-- 
cgit v1.2.3-70-g09d2


From 666498e7b76144b35d245d4b1c09e1dc62fac84b Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 10 Apr 2022 21:54:02 +0300
Subject: fix main post form having hidden emoji picker

---
 src/App.scss                                         | 12 ++++++------
 src/components/attachment/attachment.scss            |  2 +-
 src/components/desktop_nav/desktop_nav.scss          |  8 ++++----
 src/components/emoji_picker/emoji_picker.scss        |  2 +-
 src/components/media_modal/media_modal.vue           |  2 +-
 src/components/post_status_form/post_status_form.vue |  1 +
 src/components/user_panel/user_panel.vue             |  1 +
 src/hocs/with_load_more/with_load_more.scss          |  2 +-
 src/hocs/with_subscription/with_subscription.scss    |  2 +-
 9 files changed, 17 insertions(+), 15 deletions(-)

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/src/App.scss b/src/App.scss
index d8396562..cc8ef592 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -2,7 +2,7 @@
 @import './_variables.scss';
 
 :root {
-  --navbar-height: 50px;
+  --navbar-height: 3.5em;
 }
 
 html {
@@ -159,8 +159,8 @@ nav {
 }
 
 .app-layout {
-  --miniColumn: 25em;
-  --maxiColumn: minmax(var(--miniColumn), 45em);
+  --miniColumn: 25rem;
+  --maxiColumn: minmax(var(--miniColumn), 45rem);
   --columnGap: 1em;
   --status-margin: 0.75em;
 
@@ -227,7 +227,7 @@ nav {
 
   &.-has-new-post-button {
     .column {
-      padding-bottom: 20em;
+      padding-bottom: 10rem;
     }
   }
 
@@ -301,7 +301,7 @@ nav {
   cursor: pointer;
   box-shadow: $fallback--buttonShadow;
   box-shadow: var(--buttonShadow);
-  font-size: 14px;
+  font-size: 1rem;
   font-family: sans-serif;
   font-family: var(--interfaceFont, sans-serif);
 
@@ -429,7 +429,7 @@ textarea,
   color: var(--inputText, $fallback--lightText);
   font-family: sans-serif;
   font-family: var(--inputFont, sans-serif);
-  font-size: 14px;
+  font-size: 1rem;
   margin: 0;
   box-sizing: border-box;
   display: inline-block;
diff --git a/src/components/attachment/attachment.scss b/src/components/attachment/attachment.scss
index dfda15bf..b2dea98d 100644
--- a/src/components/attachment/attachment.scss
+++ b/src/components/attachment/attachment.scss
@@ -173,7 +173,7 @@
       margin: 8px;
       word-break: break-all;
       h1 {
-        font-size: 14px;
+        font-size: 1rem;
         margin: 0px;
       }
     }
diff --git a/src/components/desktop_nav/desktop_nav.scss b/src/components/desktop_nav/desktop_nav.scss
index 1e731c70..3c2e70b5 100644
--- a/src/components/desktop_nav/desktop_nav.scss
+++ b/src/components/desktop_nav/desktop_nav.scss
@@ -9,7 +9,7 @@
 
   .inner-nav {
     display: grid;
-    grid-template-rows: 50px;
+    grid-template-rows: var(--navbar-height);
     grid-template-columns: 2fr auto 2fr;
     grid-template-areas: "sitename logo actions";
     box-sizing: border-box;
@@ -75,7 +75,7 @@
 
     img {
       display: inline-block;
-      height: 50px;
+      height: var(--navbar-height);
     }
   }
 
@@ -101,8 +101,8 @@
 
   .item {
     flex: 1;
-    line-height: 50px;
-    height: 50px;
+    line-height: var(--navbar-height);
+    height: var(--navbar-height);
     overflow: hidden;
     display: flex;
     flex-wrap: wrap;
diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss
index ec711758..1a696584 100644
--- a/src/components/emoji_picker/emoji_picker.scss
+++ b/src/components/emoji_picker/emoji_picker.scss
@@ -7,7 +7,7 @@
   right: 0;
   left: 0;
   margin: 0 !important;
-  z-index: 1;
+  z-index: 100;
   background-color: $fallback--bg;
   background-color: var(--popover, $fallback--bg);
   color: $fallback--link;
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index c7dcd007..8b76aafb 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -234,7 +234,7 @@ $modal-view-button-icon-margin: 0.5em;
       position: absolute;
       height: $modal-view-button-icon-height;
       width: $modal-view-button-icon-width;
-      font-size: 14px;
+      font-size: 1rem;
       line-height: $modal-view-button-icon-height;
       color: #FFF;
       text-align: center;
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index 0fdb6dc7..d6595354 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -509,6 +509,7 @@
     flex-direction: column;
     padding: 0.25em 0.5em 0.5em;
     line-height:24px;
+    z-index: 50;
   }
 
   form textarea.form-cw {
diff --git a/src/components/user_panel/user_panel.vue b/src/components/user_panel/user_panel.vue
index 50949b98..243de387 100644
--- a/src/components/user_panel/user_panel.vue
+++ b/src/components/user_panel/user_panel.vue
@@ -24,5 +24,6 @@
 <style lang="scss">
 .user-panel .signed-in {
   overflow: visible;
+  z-index: 10;
 }
 </style>
diff --git a/src/hocs/with_load_more/with_load_more.scss b/src/hocs/with_load_more/with_load_more.scss
index 1a26eb8d..de86ed4a 100644
--- a/src/hocs/with_load_more/with_load_more.scss
+++ b/src/hocs/with_load_more/with_load_more.scss
@@ -10,7 +10,7 @@
     border-top-color: var(--border, $fallback--border);
 
     .error {
-      font-size: 14px;
+      font-size: 1rem;
     }
 
     a {
diff --git a/src/hocs/with_subscription/with_subscription.scss b/src/hocs/with_subscription/with_subscription.scss
index 52c7d94c..7fd83802 100644
--- a/src/hocs/with_subscription/with_subscription.scss
+++ b/src/hocs/with_subscription/with_subscription.scss
@@ -4,7 +4,7 @@
     text-align: center;
 
     .error {
-      font-size: 14px;
+      font-size: 1rem;
     }
   }
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From aca8cf64db26e8a883a8099462ce5381c7e330e4 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 21 Jun 2022 00:13:13 +0300
Subject: move media modal to the topmost layer

---
 src/App.vue                                | 2 +-
 src/components/media_modal/media_modal.vue | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/src/App.vue b/src/App.vue
index 5d7bb4e1..741786ec 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -42,7 +42,7 @@
       </div>
       <div id="notifs-column" class="column -scrollable" :class="{ '-show-scrollbar': showScrollbars }"/>
     </div>
-    <media-modal />
+    <MediaModal />
     <shout-panel
       v-if="currentUser && shout && !hideShoutbox"
       :floating="true"
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index 8b76aafb..b93d3d84 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -121,7 +121,7 @@ $modal-view-button-icon-width: 3em;
 $modal-view-button-icon-margin: 0.5em;
 
 .modal-view.media-modal-view {
-  z-index: 9000;
+  z-index: 900000;
   flex-direction: column;
 
   .modal-view-button-arrow,
-- 
cgit v1.2.3-70-g09d2


From 872db65fd86aaa605789383e629321e32447a997 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 22 Jun 2022 00:30:10 +0300
Subject: slight z-index refactor and attempt at organizing it

---
 src/App.scss                                             |  9 ++++++++-
 src/boot/after_store.js                                  |  3 +++
 src/components/desktop_nav/desktop_nav.scss              |  1 +
 src/components/emoji_picker/emoji_picker.scss            |  3 ++-
 src/components/global_notice_list/global_notice_list.vue |  2 +-
 src/components/media_modal/media_modal.vue               |  2 +-
 src/components/mobile_nav/mobile_nav.vue                 |  6 ++++--
 src/components/modal/modal.vue                           |  5 ++++-
 src/components/notifications/notifications.js            | 10 ++++++++++
 src/components/popover/popover.js                        |  4 ++++
 src/components/popover/popover.vue                       |  4 ++--
 src/components/shout_panel/shout_panel.vue               |  2 +-
 src/components/side_drawer/side_drawer.vue               |  2 +-
 13 files changed, 42 insertions(+), 11 deletions(-)

(limited to 'src/components/media_modal/media_modal.vue')

diff --git a/src/App.scss b/src/App.scss
index 79c07683..78835c61 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -4,6 +4,13 @@
 :root {
   --navbar-height: 3.5rem;
   --post-line-height: 1.4;
+  // Z-Index stuff
+  --ZI_media_modal: 90000;
+  --ZI_navbar_popovers: 85000;
+  --ZI_navbar: 80000;
+  --ZI_modals_popovers: 75000;
+  --ZI_modals: 70000;
+  --ZI_popovers: 60000;
 }
 
 html {
@@ -117,7 +124,7 @@ i[class*=icon-],
 }
 
 nav {
-  z-index: 1000;
+  z-index: var(--ZI_navbar);
   color: var(--topBarText);
   background-color: $fallback--fg;
   background-color: var(--topBar, $fallback--fg);
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index f655c38f..894a68e1 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -396,6 +396,9 @@ const afterStoreSetup = async ({ store, i18n }) => {
   app.component('FAIcon', FontAwesomeIcon)
   app.component('FALayers', FontAwesomeLayers)
 
+  // remove after vue 3.3
+  app.config.unwrapInjectedRef = true
+
   app.mount('#app')
 
   return app
diff --git a/src/components/desktop_nav/desktop_nav.scss b/src/components/desktop_nav/desktop_nav.scss
index 0ca9802e..e9a47632 100644
--- a/src/components/desktop_nav/desktop_nav.scss
+++ b/src/components/desktop_nav/desktop_nav.scss
@@ -2,6 +2,7 @@
 
 .DesktopNav {
   width: 100%;
+  z-index: var(--ZI_navbar);
 
   a {
     color: var(--topBarLink, $fallback--link);
diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss
index 2055e02e..a2f17c51 100644
--- a/src/components/emoji_picker/emoji_picker.scss
+++ b/src/components/emoji_picker/emoji_picker.scss
@@ -7,7 +7,8 @@
   right: 0;
   left: 0;
   margin: 0 !important;
-  z-index: 100;
+  // TODO: actually use popover in emoji picker
+  z-index: var(--ZI_popovers);
   background-color: $fallback--bg;
   background-color: var(--popover, $fallback--bg);
   color: $fallback--link;
diff --git a/src/components/global_notice_list/global_notice_list.vue b/src/components/global_notice_list/global_notice_list.vue
index ddc45b81..09904761 100644
--- a/src/components/global_notice_list/global_notice_list.vue
+++ b/src/components/global_notice_list/global_notice_list.vue
@@ -32,7 +32,7 @@
   top: 50px;
   width: 100%;
   pointer-events: none;
-  z-index: 1001;
+  z-index: var(--ZI_popovers);
   display: flex;
   flex-direction: column;
   align-items: center;
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index b93d3d84..d59055b3 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -121,7 +121,7 @@ $modal-view-button-icon-width: 3em;
 $modal-view-button-icon-margin: 0.5em;
 
 .modal-view.media-modal-view {
-  z-index: 900000;
+  z-index: var(--ZI_media_modal);
   flex-direction: column;
 
   .modal-view-button-arrow,
diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
index d2d48a03..c58d9e7c 100644
--- a/src/components/mobile_nav/mobile_nav.vue
+++ b/src/components/mobile_nav/mobile_nav.vue
@@ -86,6 +86,8 @@
 @import '../../_variables.scss';
 
 .MobileNav {
+  z-index: var(--ZI_navbar);
+
   .mobile-nav {
     display: grid;
     line-height: var(--navbar-height);
@@ -147,7 +149,7 @@
     transition-property: transform;
     transition-duration: 0.25s;
     transform: translateX(0);
-    z-index: 1001;
+    z-index: var(--ZI_navbar);
     -webkit-overflow-scrolling: touch;
 
     &.-closed {
@@ -160,7 +162,7 @@
     display: flex;
     align-items: center;
     justify-content: space-between;
-    z-index: 1;
+    z-index: calc(var(--ZI_navbar) + 100);
     width: 100%;
     height: 50px;
     line-height: 50px;
diff --git a/src/components/modal/modal.vue b/src/components/modal/modal.vue
index 9394efff..52d8d27e 100644
--- a/src/components/modal/modal.vue
+++ b/src/components/modal/modal.vue
@@ -22,6 +22,9 @@ export default {
       default: false
     }
   },
+  provide: {
+    popoversZLayer: 'modals'
+  },
   computed: {
     classes () {
       return {
@@ -35,7 +38,7 @@ export default {
 
 <style lang="scss">
 .modal-view {
-  z-index: 2000;
+  z-index: var(--ZI_modals);
   position: fixed;
   top: 0;
   left: 0;
diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js
index fb2579a5..63d827d8 100644
--- a/src/components/notifications/notifications.js
+++ b/src/components/notifications/notifications.js
@@ -1,3 +1,4 @@
+import { computed } from 'vue'
 import { mapGetters } from 'vuex'
 import Notification from '../notification/notification.vue'
 import NotificationFilters from './notification_filters.vue'
@@ -38,6 +39,11 @@ const Notifications = {
       seenToDisplayCount: DEFAULT_SEEN_TO_DISPLAY_COUNT
     }
   },
+  provide () {
+    return {
+      popoversZLayer: computed(() => this.popoversZLayer)
+    }
+  },
   computed: {
     mainClass () {
       return this.minimalMode ? '' : 'panel panel-default'
@@ -75,6 +81,10 @@ const Notifications = {
       }
       return map[layoutType] || '#notifs-sidebar'
     },
+    popoversZLayer () {
+      const { layoutType } = this.$store.state.interface
+      return layoutType === 'mobile' ? 'navbar' : null
+    },
     notificationsToDisplay () {
       return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount)
     },
diff --git a/src/components/popover/popover.js b/src/components/popover/popover.js
index cff9b174..5dfcf810 100644
--- a/src/components/popover/popover.js
+++ b/src/components/popover/popover.js
@@ -42,6 +42,7 @@ const Popover = {
     // What selector (witin popover!) to use for determining center of popover
     overlayCentersSelector: String
   },
+  inject: ['popoversZLayer'], // override popover z layer
   data () {
     return {
       hidden: true,
@@ -168,6 +169,9 @@ const Popover = {
         top: `${Math.round(translateY)}px`
       }
 
+      if (this.popoversZLayer) {
+        this.styles['--ZI_popover_override'] = `var(--ZI_${this.popoversZLayer}_popovers)`
+      }
       if (parentScreenBox) {
         this.styles.maxWidth = `${Math.round(parentScreenBox.width)}px`
       }
diff --git a/src/components/popover/popover.vue b/src/components/popover/popover.vue
index 0d37328e..f854515c 100644
--- a/src/components/popover/popover.vue
+++ b/src/components/popover/popover.vue
@@ -43,7 +43,7 @@
 }
 
 .popover {
-  z-index: 90000;
+  z-index: var(--ZI_popover_override, var(--ZI_popovers));
   position: fixed;
   min-width: 0;
   max-width: calc(100vw - 20px);
@@ -87,7 +87,7 @@
   text-align: left;
   list-style: none;
   max-width: 100vw;
-  z-index: 200;
+  z-index: var(--ZI_popover_override, var(--ZI_popovers));
   white-space: nowrap;
 
   .dropdown-divider {
diff --git a/src/components/shout_panel/shout_panel.vue b/src/components/shout_panel/shout_panel.vue
index 1eca88a7..688c2d61 100644
--- a/src/components/shout_panel/shout_panel.vue
+++ b/src/components/shout_panel/shout_panel.vue
@@ -80,7 +80,7 @@
 .floating-shout {
   position: fixed;
   bottom: 0.5em;
-  z-index: 1000;
+  z-index: var(--ZI_popovers);
   max-width: 25em;
 
   &.-left {
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index dd88de7d..aefd3915 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -211,7 +211,7 @@
 
 .side-drawer-container {
   position: fixed;
-  z-index: 1000;
+  z-index: var(--ZI_navbar);
   top: 0;
   left: 0;
   width: 100%;
-- 
cgit v1.2.3-70-g09d2

