Refactor: make the UI buttons more consistent
This commit is contained in:
parent
c272fa903d
commit
6f717de345
11 changed files with 329 additions and 295 deletions
|
|
@ -45,12 +45,6 @@ hr {
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.user-hint {
|
||||
user-select: none;
|
||||
cursor: help;
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
|
||||
.has-tooltip {
|
||||
cursor: help;
|
||||
border-bottom: 1px dotted;
|
||||
|
|
|
|||
|
|
@ -1,135 +0,0 @@
|
|||
<template>
|
||||
<vTooltip @show="loadContent" @hide="stopTimer">
|
||||
<slot />
|
||||
<template #popper>
|
||||
<template v-if="error_msg">
|
||||
<Message message_class="is-danger" :newStyle="true">
|
||||
{{ error_msg }}
|
||||
</Message>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="icon" v-if="!url"><i class="fas fa-circle-notch fa-spin" /></span>
|
||||
<div v-else>
|
||||
<div style="min-width: 300px; width: 25vw; height: auto;" class="m-1">
|
||||
<div class="is-block" style="word-break: all;" v-if="props.title">
|
||||
<span style="font-size: 120%;">{{ props.title }}</span>
|
||||
</div>
|
||||
<figure :class="['image', thumbnail_ratio, 'is-hidden-mobile']">
|
||||
<img @load="e => pImg(e)" :src="url" :alt="props.title" @error="clearCache"
|
||||
:crossorigin="props.privacy ? 'anonymous' : 'use-credentials'"
|
||||
:referrerpolicy="props.privacy ? 'no-referrer' : 'origin'" />
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</vTooltip>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { disableOpacity, enableOpacity } from '~/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
image?: string
|
||||
title?: string
|
||||
loader?: () => Promise<void>
|
||||
privacy?: boolean
|
||||
}>()
|
||||
|
||||
const cache = useSessionCache()
|
||||
const toast = useNotification()
|
||||
const thumbnail_ratio = useStorage<'is-16by9' | 'is-3by1'>('thumbnail_ratio', 'is-3by1')
|
||||
const url = ref<string | null>(null)
|
||||
const error = ref(false)
|
||||
const isPreloading = ref(false)
|
||||
const error_msg = ref<string | null>(null)
|
||||
|
||||
const loadTimer: ReturnType<typeof setTimeout> | null = null
|
||||
const cancelRequest = new AbortController()
|
||||
|
||||
const defaultLoader = async (): Promise<void> => {
|
||||
try {
|
||||
if (!props.image) {
|
||||
return
|
||||
}
|
||||
|
||||
if (cache.has(props.image)) {
|
||||
url.value = cache.get(props.image)
|
||||
return
|
||||
}
|
||||
|
||||
const response = await request(props.image, { signal: cancelRequest.signal })
|
||||
|
||||
if (!response.ok) {
|
||||
error_msg.value = `ImageView Request error. ${response.status}: ${response.statusText}`
|
||||
return
|
||||
}
|
||||
|
||||
const objUrl = URL.createObjectURL(await response.blob())
|
||||
cache.set(props.image, objUrl)
|
||||
url.value = objUrl
|
||||
} catch (e: any) {
|
||||
if (e === 'not_needed') {
|
||||
return
|
||||
}
|
||||
console.error(e)
|
||||
toast.error(`ImageView Request failure. ${e}`)
|
||||
} finally {
|
||||
isPreloading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const stopTimer = async (): Promise<void> => {
|
||||
if (error.value) {
|
||||
return
|
||||
}
|
||||
|
||||
if (url.value) {
|
||||
isPreloading.value = false
|
||||
url.value = null
|
||||
return
|
||||
}
|
||||
|
||||
await awaiter(() => isPreloading.value)
|
||||
if (loadTimer !== null) {
|
||||
clearTimeout(loadTimer)
|
||||
}
|
||||
|
||||
isPreloading.value = false
|
||||
url.value = null
|
||||
cancelRequest.abort('not_needed')
|
||||
}
|
||||
|
||||
const loadContent = async (): Promise<void> => {
|
||||
if (props.loader) {
|
||||
return props.loader()
|
||||
}
|
||||
|
||||
return defaultLoader()
|
||||
}
|
||||
|
||||
const clearCache = async (): Promise<void> => {
|
||||
if (!props.image) return
|
||||
|
||||
cache.remove(props.image)
|
||||
url.value = ''
|
||||
return loadContent()
|
||||
}
|
||||
|
||||
const pImg = (e: Event): void => {
|
||||
const target = e.target as HTMLImageElement
|
||||
if (target.naturalHeight > target.naturalWidth) {
|
||||
target.classList.add('image-portrait')
|
||||
}
|
||||
}
|
||||
onMounted(() => disableOpacity())
|
||||
onBeforeUnmount(() => {
|
||||
enableOpacity()
|
||||
if (null !== loadTimer) {
|
||||
clearTimeout(loadTimer)
|
||||
}
|
||||
cancelRequest.abort('not_needed')
|
||||
})
|
||||
|
||||
</script>
|
||||
|
|
@ -114,17 +114,32 @@
|
|||
</label>
|
||||
</td>
|
||||
<td class="is-text-overflow is-vcentered">
|
||||
<div class="is-inline is-pulled-right" v-if="item.extras?.duration">
|
||||
<span class="tag is-info" v-if="item.extras?.duration">
|
||||
<div class="is-inline is-pulled-right" v-if="item.extras?.duration || item?.download_dir">
|
||||
<span class="tag is-info is-unselectable" v-if="item.extras?.duration">
|
||||
{{ formatTime(item.extras.duration) }}
|
||||
</span>
|
||||
<span class="icon is-pointer" v-if="item.download_dir"
|
||||
v-tooltip="`Path: ${getPath(config.app.download_path, item)}`">
|
||||
<i class="fa-solid fa-folder-open" />
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="showThumbnails && getImage(item)">
|
||||
<FloatingImage :image="getImage(item)" :title="`[${item.preset}] - ${item.title}`">
|
||||
<div class="is-text-overflow">
|
||||
<NuxtLink target="_blank" :href="item.url">{{ item.title }}</NuxtLink>
|
||||
</div>
|
||||
</FloatingImage>
|
||||
<div v-if="show_popover">
|
||||
<div class="is-text-overflow">
|
||||
<Popover :showDelay="400" :maxWidth="450">
|
||||
<template #trigger>
|
||||
<NuxtLink class="is-text-overflow" target="_blank" :href="item.url">{{ item.title }}</NuxtLink>
|
||||
</template>
|
||||
<template #title>
|
||||
<strong>
|
||||
{{ item.title }}
|
||||
<span class="tag is-info is-unselectable">{{ item.preset }}</span>
|
||||
</strong>
|
||||
</template>
|
||||
<img v-if="showThumbnails && getImage(config.app.download_path, item)"
|
||||
:src="getImage(config.app.download_path, item)" class="mt-2 mb-2" />
|
||||
<p v-if="item.description">{{ item.description }}</p>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="is-text-overflow" v-tooltip="`[${item.preset}] - ${item.title}`">
|
||||
|
|
@ -143,17 +158,17 @@
|
|||
<span>{{ setStatus(item) }}</span>
|
||||
</td>
|
||||
<td class="is-vcentered has-text-centered is-unselectable">
|
||||
<span class="user-hint" :date-datetime="item.datetime"
|
||||
<span class="has-tooltip" :date-datetime="item.datetime"
|
||||
v-tooltip="moment(item.datetime).format('YYYY-M-DD H:mm Z')" v-rtime="item.datetime" />
|
||||
</td>
|
||||
<td class="is-vcentered has-text-centered is-unselectable"
|
||||
v-if="item.live_in && 'not_live' === item.status">
|
||||
<span :date-datetime="item.live_in" class="user-hint"
|
||||
v-tooltip="'Will automatically be retried at: ' + moment(item.live_in).format('YYYY-M-DD H:mm Z')"
|
||||
v-rtime="item.live_in" />
|
||||
v-if="'not_live' === item.status && (item.live_in || item.extras?.release_in)">
|
||||
<span :date-datetime="item.live_in || item.extras?.release_in" class="has-tooltip"
|
||||
v-tooltip="`Retry at: ${moment(item.live_in || item.extras?.release_in).format('YYYY-M-DD H:mm Z')}`"
|
||||
v-rtime="item.live_in || item.extras?.release_in" />
|
||||
</td>
|
||||
<td class="is-vcentered has-text-centered is-unselectable" v-else>
|
||||
{{ item.file_size ? formatBytes(item.file_size) : '-' }}
|
||||
{{ item.file_size ? formatBytes(item.file_size) : 'N/A' }}
|
||||
</td>
|
||||
<td class="is-vcentered is-items-center">
|
||||
<div class="field is-grouped is-grouped-centered">
|
||||
|
|
@ -240,17 +255,33 @@
|
|||
<div class="card is-flex is-full-height is-flex-direction-column"
|
||||
:class="{ 'is-bordered-danger': item.status === 'error', 'is-bordered-info': item.live_in || item.is_live }">
|
||||
<header class="card-header">
|
||||
<div class="card-header-title is-text-overflow is-block" v-tooltip="item.title">
|
||||
<NuxtLink target="_blank" :href="item.url">{{ item.title }}</NuxtLink>
|
||||
<div class="card-header-title is-text-overflow is-block">
|
||||
<Popover :showDelay="400" :maxWidth="550" v-if="show_popover">
|
||||
<template #trigger>
|
||||
<NuxtLink class="is-text-overflow" target="_blank" :href="item.url">{{ item.title }}</NuxtLink>
|
||||
</template>
|
||||
<template #title><strong>{{ item.title }}</strong></template>
|
||||
<p v-if="item.description">{{ item.description }}</p>
|
||||
</Popover>
|
||||
<template v-else>
|
||||
<NuxtLink target="_blank" :href="item.url">{{ item.title }}</NuxtLink>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="card-header-icon">
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<span class="tag is-info" v-if="item.extras?.duration">
|
||||
<span class="tag is-info is-unselectable" v-if="item.extras?.duration">
|
||||
{{ formatTime(item.extras.duration) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="control" v-if="item.download_dir">
|
||||
<span class="icon" v-tooltip="`Path: ${getPath(config.app.download_path, item)}`">
|
||||
<i class="fa-solid fa-folder-open" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="control">
|
||||
<button @click="hideThumbnail = !hideThumbnail" v-if="thumbnails">
|
||||
<span class="icon"><i class="fa-solid"
|
||||
|
|
@ -263,7 +294,6 @@
|
|||
:value="item._id">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
|
@ -271,17 +301,20 @@
|
|||
<figure :class="['image', thumbnail_ratio]">
|
||||
<span v-if="'finished' === item.status && item.filename" @click="playVideo(item)" class="play-overlay">
|
||||
<div class="play-icon"></div>
|
||||
<img @load="pImg" @error="onImgError" :src="getImage(item)" v-if="getImage(item)" />
|
||||
<img @load="pImg" @error="onImgError" :src="getImage(config.app.download_path, item)"
|
||||
v-if="getImage(config.app.download_path, item)" />
|
||||
<img v-else src="/images/placeholder.png" />
|
||||
</span>
|
||||
<span v-else-if="isEmbedable(item.url)" @click="embed_url = getEmbedable(item.url) as string"
|
||||
class="play-overlay">
|
||||
<div class="play-icon embed-icon"></div>
|
||||
<img @load="pImg" @error="onImgError" :src="getImage(item)" v-if="getImage(item)" />
|
||||
<img @load="pImg" @error="onImgError" :src="getImage(config.app.download_path, item)"
|
||||
v-if="getImage(config.app.download_path, item)" />
|
||||
<img v-else src="/images/placeholder.png" />
|
||||
</span>
|
||||
<template v-else>
|
||||
<img @load="pImg" @error="onImgError" v-if="getImage(item)" :src="getImage(item)" />
|
||||
<img @load="pImg" @error="onImgError" v-if="getImage(config.app.download_path, item)"
|
||||
:src="getImage(config.app.download_path, item)" />
|
||||
<img v-else src="/images/placeholder.png" />
|
||||
</template>
|
||||
</figure>
|
||||
|
|
@ -294,21 +327,20 @@
|
|||
</div>
|
||||
<div class="column is-half-mobile has-text-centered is-text-overflow is-unselectable">
|
||||
<span class="icon"><i class="fa-solid fa-sliders" /></span>
|
||||
<span v-tooltip="`Preset: ${item.preset}`" class="user-hint">{{ item.preset }}</span>
|
||||
<span v-tooltip="`Preset: ${item.preset}`" class="has-tooltip">{{ item.preset }}</span>
|
||||
</div>
|
||||
<div class="column is-half-mobile has-text-centered is-text-overflow is-unselectable"
|
||||
v-if="'not_live' === item.status && (item.live_in || item.extras?.release_in)">
|
||||
<span :date-datetime="item.live_in || item.extras?.release_in" class="user-hint"
|
||||
v-tooltip="'Will be downloaded at: ' + moment(item.live_in || item.extras?.release_in).format('YYYY-M-DD H:mm Z')"
|
||||
<span :date-datetime="item.live_in || item.extras?.release_in" class="has-tooltip"
|
||||
v-tooltip="`Retry at: ${moment(item.live_in || item.extras?.release_in).format('YYYY-M-DD H:mm Z')}`"
|
||||
v-rtime="item.live_in || item.extras?.release_in" />
|
||||
</div>
|
||||
<div class="column is-half-mobile has-text-centered is-text-overflow is-unselectable">
|
||||
<span class="user-hint" :date-datetime="item.datetime"
|
||||
<span class="has-tooltip" :date-datetime="item.datetime"
|
||||
v-tooltip="moment(item.datetime).format('YYYY-M-DD H:mm Z')" v-rtime="item.datetime" />
|
||||
</div>
|
||||
<div class="column is-half-mobile has-text-centered is-text-overflow is-unselectable" v-if="item.file_size">
|
||||
<span class="has-tooltip" v-tooltip="`Path: ${makePath(item)}`">{{
|
||||
formatBytes(item.file_size) }}</span>
|
||||
<span class="has-tooltip">{{ formatBytes(item.file_size) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns is-mobile is-multiline">
|
||||
|
|
@ -477,7 +509,7 @@ import moment from 'moment'
|
|||
import { useStorage, useIntersectionObserver } from '@vueuse/core'
|
||||
import type { StoreItem } from '~/types/store'
|
||||
import { useConfirm } from '~/composables/useConfirm'
|
||||
import { deepIncludes } from '~/utils'
|
||||
import { deepIncludes, getPath, getImage } from '~/utils'
|
||||
|
||||
const emitter = defineEmits<{
|
||||
(e: 'getInfo', url: string, preset: string, cli: string): void
|
||||
|
|
@ -504,6 +536,7 @@ const display_style = useStorage<'grid' | 'list'>('display_style', 'grid')
|
|||
const bg_enable = useStorage<boolean>('random_bg', true)
|
||||
const bg_opacity = useStorage<number>('random_bg_opacity', 0.95)
|
||||
const thumbnail_ratio = useStorage<'is-16by9' | 'is-3by1'>('thumbnail_ratio', 'is-3by1')
|
||||
const show_popover = useStorage<boolean>('show_popover', true)
|
||||
|
||||
const selectedElms = ref<string[]>([])
|
||||
const masterSelectAll = ref(false)
|
||||
|
|
@ -979,24 +1012,4 @@ const is_queued = (item: StoreItem) => {
|
|||
}
|
||||
return item.live_in || item.extras?.live_in || item.extras?.release_in ? 'fa-spin fa-spin-10' : ''
|
||||
}
|
||||
|
||||
const makePath = (item: StoreItem) => {
|
||||
if (!item?.filename) {
|
||||
return ''
|
||||
}
|
||||
const real_path = eTrim(item.download_dir, '/') + '/' + sTrim(item.filename, '/')
|
||||
return stripPath(config.app.download_path, real_path)
|
||||
}
|
||||
|
||||
const getImage = (item: StoreItem): string => {
|
||||
if (item.sidecar?.image && item.sidecar.image.length > 0) {
|
||||
return uri('/api/download/' + encodeURIComponent(stripPath(config.app.download_path, item.sidecar.image[0]?.file || '')))
|
||||
}
|
||||
|
||||
if (!item?.extras?.thumbnail) {
|
||||
return '/images/placeholder.png'
|
||||
}
|
||||
|
||||
return uri('/api/thumbnail?id=' + item._id + '&url=' + encodePath(item.extras.thumbnail))
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -73,18 +73,31 @@
|
|||
<span class="tag is-info" v-if="item.extras?.duration">
|
||||
{{ formatTime(item.extras.duration) }}
|
||||
</span>
|
||||
<span class="icon is-pointer" v-if="item.download_dir"
|
||||
v-tooltip="`Path: ${getPath(config.app.download_path, item)}`">
|
||||
<i class="fa-solid fa-folder-open" />
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="showThumbnails && item.extras?.thumbnail">
|
||||
<FloatingImage
|
||||
:image="uri('/api/thumbnail?id=' + item._id + '&url=' + encodePath(item.extras.thumbnail))"
|
||||
:title="item.title">
|
||||
<div class="is-text-overflow">
|
||||
<NuxtLink target="_blank" :href="item.url">{{ item.title }}</NuxtLink>
|
||||
</div>
|
||||
</FloatingImage>
|
||||
<div v-if="show_popover">
|
||||
<div class="is-text-overflow">
|
||||
<Popover :showDelay="400" :maxWidth="450">
|
||||
<template #trigger>
|
||||
<NuxtLink class="is-text-overflow" target="_blank" :href="item.url">{{ item.title }}</NuxtLink>
|
||||
</template>
|
||||
<template #title>
|
||||
<strong>
|
||||
{{ item.title }}
|
||||
<span class="tag is-info is-unselectable">{{ item.preset }}</span>
|
||||
</strong>
|
||||
</template>
|
||||
<img v-if="showThumbnails && getImage(config.app.download_path, item)"
|
||||
:src="getImage(config.app.download_path, item)" class="mt-2 mb-2" />
|
||||
<p v-if="item.description">{{ item.description }}</p>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="is-text-overflow" v-tooltip="item.title">
|
||||
<div class="is-text-overflow" v-tooltip="`[${item.preset}] - ${item.title}`">
|
||||
<NuxtLink target="_blank" :href="item.url">{{ item.title }}</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -162,17 +175,30 @@
|
|||
v-for="item in filteredItems" :key="item._id">
|
||||
<div class="card">
|
||||
<header class="card-header">
|
||||
<div class="card-header-title is-text-overflow is-block" v-tooltip="item.title">
|
||||
<NuxtLink target="_blank" :href="item.url">{{ item.title }}</NuxtLink>
|
||||
<div class="card-header-title is-text-overflow is-block">
|
||||
<Popover :showDelay="400" :maxWidth="550" v-if="show_popover">
|
||||
<template #trigger>
|
||||
<NuxtLink class="is-text-overflow" target="_blank" :href="item.url">{{ item.title }}</NuxtLink>
|
||||
</template>
|
||||
<template #title><strong>{{ item.title }}</strong></template>
|
||||
<p v-if="item.description">{{ item.description }}</p>
|
||||
</Popover>
|
||||
<template v-else>
|
||||
<NuxtLink target="_blank" :href="item.url">{{ item.title }}</NuxtLink>
|
||||
</template>
|
||||
</div>
|
||||
<div class="card-header-icon">
|
||||
<div class="field is-grouped">
|
||||
|
||||
<div class="control">
|
||||
<span class="tag is-info" v-if="item.extras?.duration">
|
||||
<span class="tag is-info is-unselectable" v-if="item.extras?.duration">
|
||||
{{ formatTime(item.extras.duration) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="control" v-if="item.download_dir">
|
||||
<span class="icon" v-tooltip="`Path: ${getPath(config.app.download_path, item)}`">
|
||||
<i class="fa-solid fa-folder-open" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button @click="hideThumbnail = !hideThumbnail" v-if="thumbnails">
|
||||
<span class="icon"><i class="fa-solid"
|
||||
|
|
@ -193,14 +219,13 @@
|
|||
<span v-if="isEmbedable(item.url)" @click="embed_url = getEmbedable(item.url) as string"
|
||||
class="play-overlay">
|
||||
<div class="play-icon embed-icon"></div>
|
||||
<img @load="pImg" @error="onImgError"
|
||||
:src="uri('/api/thumbnail?id=' + item._id + '&url=' + encodePath(item.extras.thumbnail))"
|
||||
v-if="item.extras?.thumbnail" />
|
||||
<img @load="pImg" @error="onImgError" v-if="getImage(config.app.download_path, item)"
|
||||
:src="getImage(config.app.download_path, item)" />
|
||||
<img v-else src="/images/placeholder.png" />
|
||||
</span>
|
||||
<template v-else>
|
||||
<img @load="pImg" @error="onImgError" v-if="item.extras?.thumbnail"
|
||||
:src="uri('/api/thumbnail?id=' + item._id + '&url=' + encodePath(item.extras.thumbnail))" />
|
||||
<img @load="pImg" @error="onImgError" v-if="getImage(config.app.download_path, item)"
|
||||
:src="getImage(config.app.download_path, item)" />
|
||||
<img v-else src="/images/placeholder.png" />
|
||||
</template>
|
||||
</figure>
|
||||
|
|
@ -221,7 +246,7 @@
|
|||
</div>
|
||||
<div class="column is-half-mobile has-text-centered is-text-overflow is-unselectable">
|
||||
<span class="icon"><i class="fa-solid fa-sliders" /></span>
|
||||
<span v-tooltip="`Preset: ${item.preset}`" class="user-hint">{{ item.preset }}</span>
|
||||
<span v-tooltip="`Preset: ${item.preset}`" class="has-tooltip">{{ item.preset }}</span>
|
||||
</div>
|
||||
<div class="column is-half-mobile has-text-centered is-text-overflow is-unselectable">
|
||||
<span v-tooltip="moment(item.datetime).format('MMMM Do YYYY, h:mm:ss a')" :data-datetime="item.datetime"
|
||||
|
|
@ -344,6 +369,7 @@ const display_style = useStorage('display_style', 'grid')
|
|||
const bg_enable = useStorage('random_bg', true)
|
||||
const bg_opacity = useStorage('random_bg_opacity', 0.95)
|
||||
const thumbnail_ratio = useStorage<'is-16by9' | 'is-3by1'>('thumbnail_ratio', 'is-3by1')
|
||||
const show_popover = useStorage<boolean>('show_popover', true)
|
||||
|
||||
const selectedElms = ref<string[]>([])
|
||||
const masterSelectAll = ref(false)
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
<template v-if="simpleMode">
|
||||
<Connection :status="socket.connectionStatus" @reconnect="() => socket.reconnect()" />
|
||||
<Simple @show_settings="() =>show_settings = true"
|
||||
:class="{ 'settings-open': show_settings }" />
|
||||
<Simple @show_settings="() => show_settings = true" :class="{ 'settings-open': show_settings }" />
|
||||
</template>
|
||||
|
||||
<SettingsPanel :isOpen="show_settings" :isLoading="loadingImage" @close="closeSettings()"
|
||||
|
|
@ -179,7 +178,7 @@
|
|||
</div>
|
||||
<div class="column is-narrow" v-if="config.app?.started">
|
||||
<div class="has-text-right">
|
||||
<span class="user-hint"
|
||||
<span class="has-tooltip"
|
||||
v-tooltip="'App Started: ' + moment.unix(config.app?.started).format('YYYY-M-DD H:mm Z')">
|
||||
{{ moment.unix(config.app?.started).fromNow() }}
|
||||
</span>
|
||||
|
|
@ -428,10 +427,7 @@ const useVersionUpdate = () => {
|
|||
|
||||
const { newVersionIsAvailable } = useVersionUpdate()
|
||||
|
||||
const closeSettings = () => {
|
||||
show_settings.value = false
|
||||
navigateTo('/')
|
||||
}
|
||||
const closeSettings = () => show_settings.value = false
|
||||
|
||||
const shutdownApp = async () => {
|
||||
const { alertDialog, confirmDialog: confirm_message } = useDialog()
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@
|
|||
<button class="button is-info" @click="reloadContent(path, true)" :class="{ 'is-loading': isLoading }"
|
||||
:disabled="isLoading">
|
||||
<span class="icon"><i class="fas fa-refresh" /></span>
|
||||
<span v-if="!isMobile">Reload</span>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -186,7 +187,7 @@
|
|||
<td class="has-text-centered is-vcentered" v-if="config.app.browser_control_enabled">
|
||||
<input type="checkbox" v-model="selectedElms" :value="item.path" />
|
||||
</td>
|
||||
<td class="has-text-centered is-vcentered user-hint" v-tooltip="item.name">
|
||||
<td class="has-text-centered is-vcentered has-tooltip" v-tooltip="item.name">
|
||||
<span class="icon"><i class="fas fa-2x fa-solid" :class="setIcon(item)" /></span>
|
||||
</td>
|
||||
<td class="is-text-overflow is-vcentered">
|
||||
|
|
|
|||
|
|
@ -22,6 +22,17 @@
|
|||
<span v-if="!isMobile">New Condition</span>
|
||||
</button>
|
||||
</p>
|
||||
<p class="control">
|
||||
<button v-tooltip.bottom="'Change display style'" class="button has-tooltip-bottom"
|
||||
@click="() => display_style = display_style === 'list' ? 'grid' : 'list'">
|
||||
<span class="icon">
|
||||
<i class="fa-solid"
|
||||
:class="{ 'fa-table': display_style !== 'list', 'fa-table-list': display_style === 'list' }" /></span>
|
||||
<span v-if="!isMobile">
|
||||
{{ display_style === 'list' ? 'List' : 'Grid' }}
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
<p class="control">
|
||||
<button class="button is-info" @click="reloadContent(false)" :class="{ 'is-loading': isLoading }"
|
||||
:disabled="isLoading" v-if="items && items.length > 0">
|
||||
|
|
@ -45,73 +56,175 @@
|
|||
|
||||
<div class="column is-12" v-if="!toggleForm">
|
||||
<div class="columns is-multiline" v-if="items.length > 0">
|
||||
<div class="column is-6" v-for="cond in items" :key="cond.id">
|
||||
<div class="card is-flex is-full-height is-flex-direction-column">
|
||||
<header class="card-header">
|
||||
<div class="card-header-title is-text-overflow is-block" v-text="cond.name" />
|
||||
<div class="card-header-icon">
|
||||
<div class="field is-grouped">
|
||||
<div class="control" v-if="cond.priority > 0">
|
||||
<span class="tag is-dark">
|
||||
<span class="icon"><i class="fa-solid fa-sort-numeric-down" /></span>
|
||||
<span v-text="cond.priority" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="control" @click="toggleEnabled(cond)">
|
||||
<span class="icon" :class="cond.enabled ? 'has-text-success' : 'has-text-danger'"
|
||||
v-tooltip="`Condition is ${cond.enabled !== false ? 'enabled' : 'disabled'}. Click to toggle.`">
|
||||
<i class="fa-solid fa-power-off" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="has-text-info" v-tooltip="'Export item'" @click.prevent="exportItem(cond)">
|
||||
<span class="icon"><i class="fa-solid fa-file-export" /></span>
|
||||
</a>
|
||||
<template v-if="'list' === display_style">
|
||||
<div class="column is-12">
|
||||
<div class="table-container">
|
||||
<table class="table is-striped is-hoverable is-fullwidth is-bordered"
|
||||
style="min-width: 850px; table-layout: fixed;">
|
||||
<thead>
|
||||
<tr class="has-text-centered is-unselectable">
|
||||
<th width="80%">
|
||||
<span class="icon"><i class="fa-solid fa-filter" /></span>
|
||||
<span>Condition</span>
|
||||
</th>
|
||||
<th width="20%">
|
||||
<span class="icon"><i class="fa-solid fa-gear" /></span>
|
||||
<span>Actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="cond in items" :key="cond.id">
|
||||
<td class="is-vcentered">
|
||||
<div class="is-text-overflow is-bold">
|
||||
{{ cond.name }}
|
||||
</div>
|
||||
<div class="is-unselectable">
|
||||
<span class="icon-text is-clickable" @click="toggleEnabled(cond)"
|
||||
v-tooltip="'Click to ' + (cond.enabled !== false ? 'disable' : 'enable') + ' condition'">
|
||||
<span class="icon">
|
||||
<i class="fa-solid fa-power-off"
|
||||
:class="{ 'has-text-success': cond.enabled !== false, 'has-text-danger': cond.enabled === false }" />
|
||||
</span>
|
||||
<span>{{ cond.enabled !== false ? 'Enabled' : 'Disabled' }}</span>
|
||||
</span>
|
||||
|
||||
<Popover :maxWidth="450">
|
||||
<template #trigger>
|
||||
<span class="is-clickable">
|
||||
<span class="icon"> <i class="fa-solid fa-info-circle" /></span>
|
||||
<span>Show Details</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #title><strong>Condition Details</strong></template>
|
||||
|
||||
<div v-if="cond.filter">
|
||||
<span class="icon"><i class="fa-solid fa-filter" /></span>
|
||||
<code>{{ cond.filter }}</code>
|
||||
</div>
|
||||
|
||||
<div v-if="cond.cli">
|
||||
<span class="icon"><i class="fa-solid fa-terminal" /></span>
|
||||
<code>{{ cond.cli }}</code>
|
||||
</div>
|
||||
|
||||
<span v-if="cond.extras && Object.keys(cond.extras).length > 0">
|
||||
<template v-for="(value, key) in cond.extras" :key="key">
|
||||
<div>
|
||||
<span class="icon"><i class="fa-solid fa-list" /></span>
|
||||
<code>{{ key }}: {{ value }}</code>
|
||||
</div>
|
||||
</template>
|
||||
</span>
|
||||
</Popover>
|
||||
<template v-if="cond.priority > 0">
|
||||
|
||||
<span class="icon-text">
|
||||
<span class="icon"><i class="fa-solid fa-sort-numeric-down" /></span>
|
||||
<span>Priority: {{ cond.priority }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</td>
|
||||
<td class="is-vcentered is-items-center">
|
||||
<div class="field is-grouped is-grouped-centered">
|
||||
<div class="control">
|
||||
<button class="button is-info is-small is-fullwidth" @click="exportItem(cond)">
|
||||
<span class="icon"><i class="fa-solid fa-file-export" /></span>
|
||||
<span v-if="!isMobile">Export</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-warning is-small is-fullwidth" @click="editItem(cond)">
|
||||
<span class="icon"><i class="fa-solid fa-edit" /></span>
|
||||
<span v-if="!isMobile">Edit</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-danger is-small is-fullwidth" @click="deleteItem(cond)">
|
||||
<span class="icon"><i class="fa-solid fa-trash" /></span>
|
||||
<span v-if="!isMobile">Delete</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div class="column is-6" v-for="cond in items" :key="cond.id">
|
||||
<div class="card is-flex is-full-height is-flex-direction-column">
|
||||
<header class="card-header">
|
||||
<div class="card-header-title is-text-overflow is-block" v-text="cond.name" />
|
||||
<div class="card-header-icon">
|
||||
<div class="field is-grouped">
|
||||
<div class="control" v-if="cond.priority > 0">
|
||||
<span class="tag is-dark">
|
||||
<span class="icon"><i class="fa-solid fa-sort-numeric-down" /></span>
|
||||
<span v-text="cond.priority" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="control" @click="toggleEnabled(cond)">
|
||||
<span class="icon" :class="cond.enabled ? 'has-text-success' : 'has-text-danger'"
|
||||
v-tooltip="`Condition is ${cond.enabled !== false ? 'enabled' : 'disabled'}. Click to toggle.`">
|
||||
<i class="fa-solid fa-power-off" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="has-text-info" v-tooltip="'Export item'" @click.prevent="exportItem(cond)">
|
||||
<span class="icon"><i class="fa-solid fa-file-export" /></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="card-content is-flex-grow-1">
|
||||
<div class="content">
|
||||
<p class="is-text-overflow">
|
||||
<span class="icon"><i class="fa-solid fa-filter" /></span>
|
||||
<span v-text="cond.filter" />
|
||||
</p>
|
||||
<p class="is-text-overflow" v-if="cond.cli">
|
||||
<span class="icon"><i class="fa-solid fa-terminal" /></span>
|
||||
<span>{{ cond.cli }}</span>
|
||||
</p>
|
||||
<p class="is-text-overflow" v-if="cond.extras && Object.keys(cond.extras).length > 0">
|
||||
<span class="icon"><i class="fa-solid fa-list" /></span>
|
||||
<span>Extras:
|
||||
<span v-for="(value, key) in cond.extras" :key="key" class="tag is-info mr-2">
|
||||
<strong>{{ key }}</strong>: {{ value }}
|
||||
</header>
|
||||
<div class="card-content is-flex-grow-1">
|
||||
<div class="content">
|
||||
<p class="is-text-overflow">
|
||||
<span class="icon"><i class="fa-solid fa-filter" /></span>
|
||||
<span v-text="cond.filter" />
|
||||
</p>
|
||||
<p class="is-text-overflow" v-if="cond.cli">
|
||||
<span class="icon"><i class="fa-solid fa-terminal" /></span>
|
||||
<span>{{ cond.cli }}</span>
|
||||
</p>
|
||||
<p class="is-text-overflow" v-if="cond.extras && Object.keys(cond.extras).length > 0">
|
||||
<span class="icon"><i class="fa-solid fa-list" /></span>
|
||||
<span>Extras:
|
||||
<span v-for="(value, key) in cond.extras" :key="key" class="tag is-info mr-2">
|
||||
<strong>{{ key }}</strong>: {{ value }}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
<p class="is-clickable" :class="{ 'is-text-overflow': !isExpanded(cond.id, 'description') }"
|
||||
v-if="cond.description" @click="toggleExpand(cond.id, 'description')">
|
||||
<span class="icon"><i class="fa-solid fa-comment" /></span>
|
||||
<span>{{ cond.description }}</span>
|
||||
</p>
|
||||
</p>
|
||||
<p class="is-clickable" :class="{ 'is-text-overflow': !isExpanded(cond.id, 'description') }"
|
||||
v-if="cond.description" @click="toggleExpand(cond.id, 'description')">
|
||||
<span class="icon"><i class="fa-solid fa-comment" /></span>
|
||||
<span>{{ cond.description }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer mt-auto">
|
||||
<div class="card-footer-item">
|
||||
<button class="button is-warning is-fullwidth" @click="editItem(cond)">
|
||||
<span class="icon"><i class="fa-solid fa-edit" /></span>
|
||||
<span>Edit</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-footer-item">
|
||||
<button class="button is-danger is-fullwidth" @click="deleteItem(cond)">
|
||||
<span class="icon"><i class="fa-solid fa-trash" /></span>
|
||||
<span>Delete</span>
|
||||
</button>
|
||||
<div class="card-footer mt-auto">
|
||||
<div class="card-footer-item">
|
||||
<button class="button is-warning is-fullwidth" @click="editItem(cond)">
|
||||
<span class="icon"><i class="fa-solid fa-edit" /></span>
|
||||
<span>Edit</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-footer-item">
|
||||
<button class="button is-danger is-fullwidth" @click="deleteItem(cond)">
|
||||
<span class="icon"><i class="fa-solid fa-trash" /></span>
|
||||
<span>Delete</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<Message title="No items" message="There are no custom conditions defined."
|
||||
class="is-background-warning-80 has-text-dark" icon="fas fa-exclamation-circle"
|
||||
|
|
@ -148,6 +261,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import type { ConditionItem, ImportedConditionItem } from '~/types/conditions'
|
||||
import { useConfirm } from '~/composables/useConfirm'
|
||||
|
||||
|
|
@ -157,6 +271,7 @@ const toast = useNotification()
|
|||
const socket = useSocketStore()
|
||||
const box = useConfirm()
|
||||
const isMobile = useMediaQuery({ maxWidth: 1024 })
|
||||
const display_style = useStorage<'list' | 'grid'>('conditions_display_style', 'grid')
|
||||
|
||||
const items = ref<ConditionItemWithUI[]>([])
|
||||
const item = ref<Partial<ConditionItem>>({})
|
||||
|
|
|
|||
|
|
@ -19,14 +19,14 @@
|
|||
<p class="control">
|
||||
<button class="button is-primary" @click="resetForm(false); toggleForm = true"
|
||||
v-tooltip="'Add new notification target.'">
|
||||
<span class="icon"><i class="fas fa-add"></i></span>
|
||||
<span class="icon"><i class="fas fa-add" /></span>
|
||||
<span v-if="!isMobile">New Notification</span>
|
||||
</button>
|
||||
</p>
|
||||
<p class="control" v-if="notifications.length > 0">
|
||||
<button class="button is-warning" @click="sendTest" v-tooltip="'Send test notification.'"
|
||||
:class="{ 'is-loading': isLoading }" :disabled="isLoading">
|
||||
<span class="icon"><i class="fas fa-paper-plane"></i></span>
|
||||
<span class="icon"><i class="fas fa-paper-plane" /></span>
|
||||
<span v-if="!isMobile">Send Test</span>
|
||||
</button>
|
||||
</p>
|
||||
|
|
@ -45,7 +45,8 @@
|
|||
<p class="control" v-if="notifications.length > 0">
|
||||
<button class="button is-info" @click="reloadContent()" :class="{ 'is-loading': isLoading }"
|
||||
:disabled="isLoading || notifications.length < 1">
|
||||
<span class="icon"><i class="fas fa-refresh"></i></span>
|
||||
<span class="icon"><i class="fas fa-refresh" /></span>
|
||||
<span v-if="!isMobile">Reload</span>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -109,21 +110,21 @@
|
|||
<td class="is-vcentered is-items-center">
|
||||
<div class="field is-grouped is-grouped-centered">
|
||||
<div class="control">
|
||||
<button class="button is-info is-small is-fullwidth" v-tooltip="'Export'"
|
||||
@click="exportItem(item)">
|
||||
<button class="button is-info is-small is-fullwidth" @click="exportItem(item)">
|
||||
<span class="icon"><i class="fa-solid fa-file-export" /></span>
|
||||
<span v-if="!isMobile">Export</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-warning is-small is-fullwidth" v-tooltip="'Edit'"
|
||||
@click="editItem(item)">
|
||||
<button class="button is-warning is-small is-fullwidth" @click="editItem(item)">
|
||||
<span class="icon"><i class="fa-solid fa-edit" /></span>
|
||||
<span v-if="!isMobile">Edit</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-danger is-small is-fullwidth" v-tooltip="'Delete'"
|
||||
@click="deleteItem(item)">
|
||||
<button class="button is-danger is-small is-fullwidth" @click="deleteItem(item)">
|
||||
<span class="icon"><i class="fa-solid fa-trash" /></span>
|
||||
<span v-if="!isMobile">Delete</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -82,21 +82,21 @@
|
|||
<td class="is-vcentered is-items-center">
|
||||
<div class="field is-grouped is-grouped-centered">
|
||||
<div class="control">
|
||||
<button class="button is-info is-small is-fullwidth" v-tooltip="'Export'"
|
||||
@click="exportItem(item)">
|
||||
<button class="button is-info is-small is-fullwidth" @click="exportItem(item)">
|
||||
<span class="icon"><i class="fa-solid fa-file-export" /></span>
|
||||
<span v-if="!isMobile">Export</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-warning is-small is-fullwidth" v-tooltip="'Edit'"
|
||||
@click="editItem(item)">
|
||||
<button class="button is-warning is-small is-fullwidth" @click="editItem(item)">
|
||||
<span class="icon"><i class="fa-solid fa-cog" /></span>
|
||||
<span v-if="!isMobile">Edit</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-danger is-small is-fullwidth" v-tooltip="'Delete'"
|
||||
@click="deleteItem(item)">
|
||||
<button class="button is-danger is-small is-fullwidth" @click="deleteItem(item)">
|
||||
<span class="icon"><i class="fa-solid fa-trash" /></span>
|
||||
<span v-if="!isMobile">Delete</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
</p>
|
||||
|
||||
<p class="control">
|
||||
<button @click="() => inspect = true" class="button is-primary is-light">
|
||||
<button @click="() => inspect = true" class="button is-warning">
|
||||
<span class="icon"><i class="fa-solid fa-magnifying-glass" /></span>
|
||||
<span v-if="!isMobile">Inspect</span>
|
||||
</button>
|
||||
|
|
@ -80,7 +80,7 @@
|
|||
</td>
|
||||
<td class="is-vcentered has-text-centered">{{ definition.priority }}</td>
|
||||
<td class="is-vcentered has-text-centered">
|
||||
<span class="user-hint" :date-datetime="moment.unix(definition.updated_at).format('YYYY-M-DD H:mm Z')"
|
||||
<span class="has-tooltip" :date-datetime="moment.unix(definition.updated_at).format('YYYY-M-DD H:mm Z')"
|
||||
v-tooltip="moment.unix(definition.updated_at).format('YYYY-M-DD H:mm Z')"
|
||||
v-rtime="definition.updated_at" />
|
||||
</td>
|
||||
|
|
@ -137,7 +137,7 @@
|
|||
<p>
|
||||
<span class="icon-text">
|
||||
<span class="icon"><i class="fa-solid fa-clock" /></span>
|
||||
<span>Updated: <span class="user-hint"
|
||||
<span>Updated: <span class="has-tooltip"
|
||||
:date-datetime="moment.unix(definition.updated_at).format('YYYY-M-DD H:mm Z')"
|
||||
v-tooltip="moment.unix(definition.updated_at).format('YYYY-M-DD H:mm Z')"
|
||||
v-rtime="definition.updated_at" />
|
||||
|
|
|
|||
|
|
@ -791,10 +791,33 @@ const deepIncludes = (
|
|||
return false
|
||||
}
|
||||
|
||||
const getPath = (basePath: string, item: StoreItem): string => {
|
||||
if (!item.download_dir) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!item?.filename) {
|
||||
return item.download_dir
|
||||
}
|
||||
|
||||
return stripPath(basePath, eTrim(item.download_dir, '/') + '/' + sTrim(item.filename, '/'))
|
||||
}
|
||||
const getImage = (basePath: string, item: StoreItem): string => {
|
||||
if (item.sidecar?.image && item.sidecar.image.length > 0) {
|
||||
return uri('/api/download/' + encodeURIComponent(stripPath(basePath, item.sidecar.image[0]?.file || '')))
|
||||
}
|
||||
|
||||
if (!item?.extras?.thumbnail) {
|
||||
return '/images/placeholder.png'
|
||||
}
|
||||
|
||||
return uri('/api/thumbnail?id=' + item._id + '&url=' + encodePath(item.extras.thumbnail))
|
||||
}
|
||||
|
||||
export {
|
||||
separators, convertCliOptions, getSeparatorsName, iTrim, eTrim, sTrim, ucFirst,
|
||||
getValue, ag, ag_set, awaitElement, r, copyText, dEvent, makePagination, encodePath,
|
||||
request, removeANSIColors, dec2hex, makeId, basename, dirname, getQueryParams,
|
||||
makeDownload, formatBytes, has_data, toggleClass, cleanObject, uri, formatTime,
|
||||
sleep, awaiter, encode, decode, disableOpacity, enableOpacity, stripPath, shortPath, deepIncludes
|
||||
sleep, awaiter, encode, decode, disableOpacity, enableOpacity, stripPath, shortPath, deepIncludes, getPath, getImage
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue