Skip to content
Snippets Groups Projects
util.ts 3.62 KiB
Newer Older
import { cloneDeep, isEqual } from 'lodash'
import { computed, ComputedGetter, ComputedRef, readonly, Ref, ref, watch, watchEffect } from 'vue'
import { formatISO, parseISO } from 'date-fns'
import DOMPurify from 'dompurify'
import { useStore } from 'vuex'
import { Show } from '@/types'

export function computedIter<T>(fn: ComputedGetter<Iterable<T>>): ComputedRef<T[]> {
  return computed(() => Array.from(fn()))
}

export const useId = (() => {
  let _id = 0
  return function useId(prefix = 'component') {
    return readonly(ref(`${prefix}-${_id++}`))
  }
})()
export function useAsyncFunction<F extends (...args: never[]) => Promise<unknown>>(fn: F) {
  const isProcessing = ref(false)
  async function wrapper(...args: Parameters<F>): Promise<Awaited<ReturnType<F>>> {
    isProcessing.value = true
      return (await fn(...args)) as Awaited<ReturnType<F>>
    } finally {
      isProcessing.value = false
  return { fn: wrapper, isProcessing }
}

export function useUpdatableState<T>(
  externalStateRef: Ref<T>,
  onUpdate: (value: T) => void,
  clone: (value: T) => T = cloneDeep,
): Ref<T> {
  const localRef = ref()
  watchEffect(() => {
    localRef.value = clone(externalStateRef.value)
  })
  watch(
    localRef,
    (newValue) => {
      if (!isEqual(newValue, externalStateRef.value)) {
        onUpdate(newValue)
      }
    },
    { deep: true },
  )
  return localRef
}

export function useFormattedISODate(date: Ref<Date>) {
  return computed({
    get() {
      return formatISO(date.value, { representation: 'date' })
    },
    set(dateValue: string) {
      date.value = parseISO(dateValue)
    },
  })
}

export function getClosestSlot(slotDurationMinutes: number, date?: Date): Date {
  date = date ?? new Date()
  const slotDurationMillis = slotDurationMinutes * 60 * 1000
  const slots = Math.floor(date.getTime() / slotDurationMillis)
  const closestSlotTimestamp = slots * slotDurationMillis
  return new Date(closestSlotTimestamp)
}

export function getNextAvailableSlot(slotDurationMinutes: number, date?: Date): Date {
  date = date ?? new Date()
  const slotDurationMillis = slotDurationMinutes * 60 * 1000
  const slots = Math.ceil(date.getTime() / slotDurationMillis)
  const nextSlotTimestamp = slots * slotDurationMillis
  return new Date(nextSlotTimestamp)
}
export function calculateDurationSeconds(start: Date, end: Date): number {
  return Math.abs(end.getTime() - start.getTime()) / 1000
}

export function sanitizeHTML(
  html: string,
  preset?: 'inline-noninteractive' | 'safe-html' | 'strip',
): string {
  const inlineElements = [
    'b',
    'big',
    'i',
    'small',
    'tt',
    'abbr',
    'acronym',
    'cite',
    'code',
    'dfn',
    'em',
    'kbd',
    'strong',
    'samp',
    'var',
    'br',
    'q',
    's',
    'span',
    'sub',
    'sup',
  ]

  if (preset === 'inline-noninteractive') {
    return DOMPurify.sanitize(html, {
      ALLOWED_TAGS: inlineElements,
      ALLOWED_ATTR: ['style', 'title'],
    })
  }

  if (preset === 'safe-html') {
    return DOMPurify.sanitize(html, { USE_PROFILES: { html: true } })
  }

  return DOMPurify.sanitize(html, { ALLOWED_TAGS: [] })
}

export function useSelectedShow() {
  const store = useStore()
  return computed<Show>({
    get() {
      return store.state.shows.shows[store.state.shows.selected.index]
    },
    set(show) {
      store.commit('shows/switchShowById', show.id)
    },
  })
}

export function secondsToDurationString(seconds: number): string {
  const h = Math.floor(seconds / 3600)
  const m = Math.floor((seconds % 3600) / 60)
  const s = Math.round(seconds % 60)
  return `${h ? h + 'h ' : ''}${m ? m + 'min ' : ''}${s ? s + 's' : ''}`
}