Newer
Older
navi-1 / webclient / src / composables / useLightbox.js
import { computed, ref } from 'vue'

// The item the fullscreen overlay shows: { url, type, title }. `open(src)`
// keeps the markdown-image path (an image is what a bare src means);
// `openContent` carries the type so SVG, HTML, PDF, STL/glTF and video reach
// their viewer page instead of being rendered as an <img>.
const _item = ref(null)
const _src = computed(() => _item.value?.url ?? null)

// History integration: opening the overlay pushes a history entry, so the
// mobile back gesture closes the modal instead of walking the session
// history. Closing removes that entry again, keeping the gesture history
// unchanged for the app itself.

function _onPopState() {
  // Back gesture / browser back while the overlay is open → close it.
  // The entry that backs out IS our pushed entry — don't back() again.
  if (_item.value) _item.value = null
}

if (typeof window !== 'undefined') {
  window.addEventListener('popstate', _onPopState)
}

export function useLightbox() {
  function openContent({ url, type = 'image', title = '' } = {}) {
    if (!url) return
    // Don't stack a second entry if a reload left our stale one on top.
    if (!history.state?.lightbox) history.pushState({ lightbox: true }, '')
    _item.value = { url, type, title }
  }

  function open(src) {
    openContent({ url: src, type: 'image' })
  }

  function close() {
    if (!_item.value) return
    _item.value = null
    // Remove the pushed entry so the next gesture doesn't hit a stale
    // overlay record. The popstate handler sees null and no-ops.
    if (history.state?.lightbox) history.back()
  }

  return { item: _item, src: _src, open, openContent, close }
}