Add batch still arrows, hide output only on tap, and cut studio lag.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+223
-36
@@ -1361,6 +1361,7 @@
|
||||
:src="editResultUrl"
|
||||
alt="Edited still"
|
||||
class="mx-auto max-h-[min(70dvh,24rem)] w-full object-contain"
|
||||
decoding="async"
|
||||
>
|
||||
<span
|
||||
v-if="!editAwaitingReveal"
|
||||
@@ -1394,6 +1395,11 @@
|
||||
>
|
||||
Edited still appears here
|
||||
</div>
|
||||
<FamilyNav
|
||||
:count="outputFamilyStills.length"
|
||||
:index="outputFamilyIndex"
|
||||
@step="stepOutputFamily"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="editResultUrl && !editLockedSave && !editAwaitingReveal" class="mt-4 flex flex-wrap gap-3">
|
||||
<a
|
||||
@@ -1434,12 +1440,12 @@
|
||||
<div class="flex items-center gap-3">
|
||||
<NuxtLink to="/library" class="text-xs text-amber-300 hover:text-amber-100">Open full library</NuxtLink>
|
||||
<button
|
||||
v-if="libraryEntries.length"
|
||||
v-if="visibleLibraryEntries.length"
|
||||
type="button"
|
||||
class="text-xs text-zinc-400 hover:text-white"
|
||||
@click="selectedKeys.length === libraryEntries.length ? clearSelection() : selectAllLibrary()"
|
||||
@click="selectedKeys.length === visibleLibraryEntries.length ? clearSelection() : selectAllLibrary()"
|
||||
>
|
||||
{{ selectedKeys.length && selectedKeys.length === libraryEntries.length ? 'Clear selection' : 'Select all' }}
|
||||
{{ selectedKeys.length && selectedKeys.length === visibleLibraryEntries.length ? 'Clear selection' : 'Select all' }}
|
||||
</button>
|
||||
<button
|
||||
v-if="browseFolder?.protected && browseFolder.unlocked"
|
||||
@@ -1512,11 +1518,11 @@
|
||||
<button type="button" class="text-red-300 hover:text-red-200" @click="askRemoveSelected">Delete</button>
|
||||
<button type="button" class="text-zinc-400 hover:text-white" @click="clearSelection">Clear</button>
|
||||
</div>
|
||||
<ul class="mt-4 space-y-3">
|
||||
<ul class="mt-4 max-h-[min(28rem,50vh)] space-y-3 overflow-y-auto">
|
||||
<li
|
||||
v-for="entry in libraryEntries"
|
||||
v-for="entry in visibleLibraryEntries"
|
||||
:key="entry.key"
|
||||
class="cursor-pointer rounded-2xl border p-3"
|
||||
class="studio-library-card cursor-pointer rounded-2xl border p-3"
|
||||
:class="libraryEntryClass(entry)"
|
||||
:aria-current="isLibraryEntryFocused(entry) ? 'true' : undefined"
|
||||
@click="onLibraryCardClick(entry, $event)"
|
||||
@@ -1871,6 +1877,10 @@
|
||||
</template>
|
||||
</li>
|
||||
</ul>
|
||||
<p v-if="libraryCapped" class="mt-3 text-xs text-zinc-500">
|
||||
Showing {{ visibleLibraryEntries.length }} of {{ libraryEntries.length }}.
|
||||
<NuxtLink to="/library" class="text-amber-300 hover:text-amber-100">Open full library</NuxtLink>
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1924,12 +1934,19 @@
|
||||
aria-label="Edited still fullscreen"
|
||||
@click.self="closeEditLightbox"
|
||||
>
|
||||
<img
|
||||
:src="editLightboxUrl"
|
||||
alt="Edited still"
|
||||
class="max-h-[90vh] max-w-[min(96vw,80rem)] object-contain"
|
||||
@click.stop
|
||||
>
|
||||
<div class="relative max-h-[90vh] max-w-[min(96vw,80rem)]" @click.stop>
|
||||
<img
|
||||
:src="editLightboxUrl"
|
||||
alt="Edited still"
|
||||
class="max-h-[90vh] max-w-[min(96vw,80rem)] object-contain"
|
||||
decoding="async"
|
||||
>
|
||||
<FamilyNav
|
||||
:count="outputFamilyStills.length"
|
||||
:index="outputFamilyIndex"
|
||||
@step="stepOutputFamily"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
|
||||
@@ -2613,7 +2630,15 @@ let videoSource: EventSource | null = null
|
||||
let editSource: EventSource | null = null
|
||||
let videoPollTimer: ReturnType<typeof setInterval> | null = null
|
||||
let editPollTimer: ReturnType<typeof setInterval> | null = null
|
||||
let healthTimer: ReturnType<typeof setInterval> | null = null
|
||||
let healthTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let studioPollAlive = false
|
||||
let loraPollAt = 0
|
||||
let concealPointers = 0
|
||||
let concealPinch = false
|
||||
let concealGestureAt = 0
|
||||
let concealStart: { pointerId: number; x: number; y: number; moved: boolean } | null = null
|
||||
const CONCEAL_MOVE_PX = 12
|
||||
const CONCEAL_GESTURE_MS = 500
|
||||
let videoSettledUi = false
|
||||
let editSettledUi = false
|
||||
let videoListenGen = 0
|
||||
@@ -3077,6 +3102,30 @@ const reviewingStillId = computed(() => {
|
||||
if (outputPreviewKind.value !== 'edit' || editLockedSave.value) return ''
|
||||
return stillIdFromLibraryUrl(editResultUrl.value)
|
||||
})
|
||||
const STUDIO_LIBRARY_CAP = 36
|
||||
const visibleLibraryEntries = computed(() => libraryEntries.value.slice(0, STUDIO_LIBRARY_CAP))
|
||||
const libraryCapped = computed(() => libraryEntries.value.length > STUDIO_LIBRARY_CAP)
|
||||
const outputFamilyStills = computed(() => {
|
||||
const id = reviewingStillId.value
|
||||
if (!id) return [] as LibraryStill[]
|
||||
const group = stillGroups.value.find(entry => entry.parts.some(part => part.id === id))
|
||||
let ordered = chronologicalStills(group?.parts || [])
|
||||
if (ordered.length < 2) {
|
||||
const current = stills.value.find(still => still.id === id)
|
||||
if (current?.familyId) {
|
||||
ordered = chronologicalStills(stills.value.filter(still => still.familyId === current.familyId && still.role !== 'input'))
|
||||
} else if (current) {
|
||||
ordered = [current]
|
||||
}
|
||||
}
|
||||
return ordered.length > 1 ? ordered : []
|
||||
})
|
||||
const outputFamilyIndex = computed(() => {
|
||||
const id = reviewingStillId.value
|
||||
if (!id) return 0
|
||||
const index = outputFamilyStills.value.findIndex(still => still.id === id)
|
||||
return index < 0 ? 0 : index
|
||||
})
|
||||
type LibraryEntry = (typeof libraryEntries.value)[number]
|
||||
function isLibraryEntryFocused(entry: LibraryEntry) {
|
||||
if (entry.kind === 'clip' && reviewingClipId.value) {
|
||||
@@ -3468,13 +3517,14 @@ async function pollLoras() {
|
||||
const listed = await $fetch<{ image?: string[]; video?: string[] }>('/api/loras').catch(() => ({ image: [], video: [] }))
|
||||
const nextVideo = listed.video || []
|
||||
const nextImage = listed.image || []
|
||||
if (nextVideo.length) {
|
||||
const sameNames = (a: string[], b: string[]) => a.length === b.length && a.every((name, i) => name === b[i])
|
||||
if (nextVideo.length && !sameNames(videoLoras.value, nextVideo)) {
|
||||
videoLoras.value = nextVideo
|
||||
rememberLoraNames('aigen-lora-names-video', nextVideo)
|
||||
} else if (!videoLoras.value.length) {
|
||||
videoLoras.value = readStoredLoraNames('aigen-lora-names-video')
|
||||
}
|
||||
if (nextImage.length) {
|
||||
if (nextImage.length && !sameNames(imageLoras.value, nextImage)) {
|
||||
imageLoras.value = nextImage
|
||||
rememberLoraNames('aigen-lora-names-image', nextImage)
|
||||
} else if (!imageLoras.value.length) {
|
||||
@@ -3487,6 +3537,37 @@ async function pollComfyHealth() {
|
||||
applyComfyHealth(health)
|
||||
}
|
||||
|
||||
async function pollStudioBackground() {
|
||||
await pollComfyHealth()
|
||||
if (Date.now() - loraPollAt > 30_000) {
|
||||
loraPollAt = Date.now()
|
||||
void pollLoras()
|
||||
}
|
||||
void refreshStudioQueue()
|
||||
if (activeQueueId.value) void refreshLiveQueue()
|
||||
}
|
||||
|
||||
function studioBackgroundBusy() {
|
||||
return videoBusy.value
|
||||
|| editBusy.value
|
||||
|| comfyBusy.value
|
||||
|| imageComfyBusy.value
|
||||
|| studioJobs.value.some(job => job.status === 'running' || job.status === 'waiting' || job.status === 'held')
|
||||
}
|
||||
|
||||
function scheduleStudioBackground() {
|
||||
if (!studioPollAlive) return
|
||||
const ms = studioBackgroundBusy() ? 8000 : 20000
|
||||
healthTimer = setTimeout(() => {
|
||||
if (!studioPollAlive) return
|
||||
const run = typeof document === 'undefined' || !document.hidden
|
||||
const done = run ? pollStudioBackground() : Promise.resolve()
|
||||
void done.finally(() => {
|
||||
if (studioPollAlive) scheduleStudioBackground()
|
||||
})
|
||||
}, ms)
|
||||
}
|
||||
|
||||
async function pokeGpu() {
|
||||
if (gpuWaking.value) return
|
||||
gpuWaking.value = true
|
||||
@@ -3550,7 +3631,7 @@ onMounted(async () => {
|
||||
if (savedPreset) recommendPresetId.value = savedPreset
|
||||
} catch { /* ignore */ }
|
||||
applyInstancePrivacy()
|
||||
window.addEventListener('click', onSitePointerDown, true)
|
||||
bindConcealListeners()
|
||||
const [health, me] = await Promise.all([
|
||||
$fetch<{ comfy?: { ok?: boolean; busy?: boolean } }>('/api/health').catch(() => ({ comfy: { ok: false, busy: false } })),
|
||||
$fetch<{ user?: { name?: string; email?: string }; authEnabled?: boolean; authMode?: string; instanceName?: string }>('/api/auth/me').catch(() => ({ user: null })),
|
||||
@@ -3558,6 +3639,7 @@ onMounted(async () => {
|
||||
])
|
||||
applyComfyHealth(health)
|
||||
void pollLoras()
|
||||
loraPollAt = Date.now()
|
||||
void loadGenerationPresets()
|
||||
await loadDirectorScripts().catch(() => null)
|
||||
userName.value = me.user?.name || me.user?.email || ''
|
||||
@@ -3565,25 +3647,22 @@ onMounted(async () => {
|
||||
instanceName.value = me.instanceName || instanceName.value || 'AIGen'
|
||||
applyInstancePrivacy()
|
||||
setTimeout(() => { void loadLibrary(true).catch(() => null) }, 6000)
|
||||
healthTimer = setInterval(() => {
|
||||
if (typeof document !== 'undefined' && document.hidden) return
|
||||
void pollComfyHealth()
|
||||
void pollLoras()
|
||||
void refreshStudioQueue()
|
||||
if (activeQueueId.value) void refreshLiveQueue()
|
||||
}, 5000)
|
||||
studioPollAlive = true
|
||||
scheduleStudioBackground()
|
||||
await resumeActiveJob()
|
||||
await refreshStudioQueue()
|
||||
await refreshLiveQueue()
|
||||
await loadPromptPresets()
|
||||
if (route.query.clip || route.query.still) applyIncomingLibraryItem()
|
||||
else applyIncomingDirectorScript()
|
||||
window.addEventListener('keydown', onStudioKey)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('click', onSitePointerDown, true)
|
||||
window.removeEventListener('keydown', onEditLightboxKey)
|
||||
if (healthTimer) clearInterval(healthTimer)
|
||||
unbindConcealListeners()
|
||||
window.removeEventListener('keydown', onStudioKey)
|
||||
studioPollAlive = false
|
||||
if (healthTimer) clearTimeout(healthTimer)
|
||||
healthTimer = null
|
||||
if (libraryRefreshTimer) clearTimeout(libraryRefreshTimer)
|
||||
libraryRefreshTimer = null
|
||||
@@ -3623,11 +3702,6 @@ watch(videoBusy, (busy) => {
|
||||
if (busy) outputFocus.value = 'video'
|
||||
})
|
||||
|
||||
watch(editLightboxUrl, (value) => {
|
||||
if (value) window.addEventListener('keydown', onEditLightboxKey)
|
||||
else window.removeEventListener('keydown', onEditLightboxKey)
|
||||
})
|
||||
|
||||
watch([editResultUrl, editAwaitingReveal, editLockedSave], () => {
|
||||
if (!editResultUrl.value || editAwaitingReveal.value || editLockedSave.value) closeEditLightbox()
|
||||
})
|
||||
@@ -4593,7 +4667,7 @@ function startTimer(kind: 'video' | 'edit', offset = 0) {
|
||||
const started = Date.now() - offset
|
||||
const handle = setInterval(() => {
|
||||
elapsed.value = Date.now() - started
|
||||
}, 250)
|
||||
}, 1000)
|
||||
if (kind === 'edit') editTimer = handle
|
||||
else videoTimer = handle
|
||||
}
|
||||
@@ -4938,11 +5012,124 @@ function closeEditLightbox() {
|
||||
editLightboxUrl.value = ''
|
||||
}
|
||||
|
||||
function onEditLightboxKey(event: KeyboardEvent) {
|
||||
if (event.key === 'Escape') closeEditLightbox()
|
||||
function typingInField(event: KeyboardEvent) {
|
||||
const target = event.target as HTMLElement | null
|
||||
if (!target) return false
|
||||
const tag = target.tagName
|
||||
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || target.isContentEditable
|
||||
}
|
||||
|
||||
function onSitePointerDown(event: Event) {
|
||||
function onStudioKey(event: KeyboardEvent) {
|
||||
if (event.key === 'Escape') {
|
||||
if (editLightboxUrl.value) closeEditLightbox()
|
||||
return
|
||||
}
|
||||
if (typingInField(event)) return
|
||||
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return
|
||||
if (outputFamilyStills.value.length < 2) return
|
||||
event.preventDefault()
|
||||
stepOutputFamily(event.key === 'ArrowLeft' ? -1 : 1)
|
||||
}
|
||||
|
||||
function showOutputStill(still: LibraryStill) {
|
||||
editResultUrl.value = `/api/library/stills/${still.id}`
|
||||
lastV2StillId.value = still.id
|
||||
editDownloadName.value = clipFileName(stillTitle(still))
|
||||
if (editLightboxUrl.value) editLightboxUrl.value = `/api/library/stills/${still.id}`
|
||||
}
|
||||
|
||||
function stepOutputFamily(delta: number) {
|
||||
const family = outputFamilyStills.value
|
||||
if (family.length < 2) return
|
||||
const current = outputFamilyIndex.value
|
||||
const next = family[(current + delta + family.length) % family.length]
|
||||
if (next) showOutputStill(next)
|
||||
}
|
||||
|
||||
function markConcealGesture() {
|
||||
concealGestureAt = Date.now()
|
||||
}
|
||||
|
||||
function bindConcealListeners() {
|
||||
window.addEventListener('pointerdown', onConcealPointerDown, true)
|
||||
window.addEventListener('pointermove', onConcealPointerMove, true)
|
||||
window.addEventListener('pointerup', onConcealPointerUp, true)
|
||||
window.addEventListener('pointercancel', onConcealPointerCancel, true)
|
||||
window.addEventListener('touchstart', onConcealTouchStart, { capture: true, passive: true })
|
||||
window.addEventListener('scroll', markConcealGesture, true)
|
||||
window.addEventListener('gesturestart', markConcealGesture, true)
|
||||
const viewport = window.visualViewport
|
||||
if (viewport) {
|
||||
viewport.addEventListener('resize', markConcealGesture)
|
||||
viewport.addEventListener('scroll', markConcealGesture)
|
||||
}
|
||||
}
|
||||
|
||||
function unbindConcealListeners() {
|
||||
window.removeEventListener('pointerdown', onConcealPointerDown, true)
|
||||
window.removeEventListener('pointermove', onConcealPointerMove, true)
|
||||
window.removeEventListener('pointerup', onConcealPointerUp, true)
|
||||
window.removeEventListener('pointercancel', onConcealPointerCancel, true)
|
||||
window.removeEventListener('touchstart', onConcealTouchStart, true)
|
||||
window.removeEventListener('scroll', markConcealGesture, true)
|
||||
window.removeEventListener('gesturestart', markConcealGesture, true)
|
||||
const viewport = window.visualViewport
|
||||
if (viewport) {
|
||||
viewport.removeEventListener('resize', markConcealGesture)
|
||||
viewport.removeEventListener('scroll', markConcealGesture)
|
||||
}
|
||||
}
|
||||
|
||||
function onConcealPointerDown(event: PointerEvent) {
|
||||
concealPointers += 1
|
||||
if (concealPointers > 1) {
|
||||
concealPinch = true
|
||||
markConcealGesture()
|
||||
}
|
||||
if (concealPointers === 1) {
|
||||
concealStart = { pointerId: event.pointerId, x: event.clientX, y: event.clientY, moved: false }
|
||||
}
|
||||
}
|
||||
|
||||
function onConcealPointerMove(event: PointerEvent) {
|
||||
const start = concealStart
|
||||
if (!start || event.pointerId !== start.pointerId) return
|
||||
const dx = event.clientX - start.x
|
||||
const dy = event.clientY - start.y
|
||||
if ((dx * dx) + (dy * dy) > CONCEAL_MOVE_PX * CONCEAL_MOVE_PX) start.moved = true
|
||||
}
|
||||
|
||||
function onConcealPointerUp(event: PointerEvent) {
|
||||
concealPointers = Math.max(0, concealPointers - 1)
|
||||
const start = concealStart
|
||||
const pinched = concealPinch
|
||||
if (event.pointerId === start?.pointerId) concealStart = null
|
||||
if (concealPointers === 0) concealPinch = false
|
||||
if (!start || event.pointerId !== start.pointerId) return
|
||||
if (start.moved || pinched) {
|
||||
markConcealGesture()
|
||||
return
|
||||
}
|
||||
if (Date.now() - concealGestureAt < CONCEAL_GESTURE_MS) return
|
||||
if (event.pointerType === 'mouse' && event.button !== 0) return
|
||||
concealFromOutsideTap(event)
|
||||
}
|
||||
|
||||
function onConcealPointerCancel() {
|
||||
concealPointers = Math.max(0, concealPointers - 1)
|
||||
concealStart = null
|
||||
if (concealPointers === 0) concealPinch = false
|
||||
markConcealGesture()
|
||||
}
|
||||
|
||||
function onConcealTouchStart(event: TouchEvent) {
|
||||
if (event.touches.length > 1) {
|
||||
concealPinch = true
|
||||
markConcealGesture()
|
||||
}
|
||||
}
|
||||
|
||||
function concealFromOutsideTap(event: Event) {
|
||||
const target = event.target as Node | null
|
||||
if (editLightbox.value && target && editLightbox.value.contains(target)) return
|
||||
if (editLightboxUrl.value) closeEditLightbox()
|
||||
@@ -5641,7 +5828,7 @@ function clearSelection() {
|
||||
}
|
||||
|
||||
function selectAllLibrary() {
|
||||
selectedKeys.value = libraryEntries.value.map(entry => entry.key)
|
||||
selectedKeys.value = visibleLibraryEntries.value.map(entry => entry.key)
|
||||
lastSelectedKey.value = selectedKeys.value.at(-1) || ''
|
||||
}
|
||||
|
||||
|
||||
+34
-1
@@ -206,10 +206,17 @@
|
||||
alt=""
|
||||
class="aspect-video w-full object-cover"
|
||||
>
|
||||
<FamilyNav
|
||||
:count="focusedNavParts.length"
|
||||
:index="focusedNavIndex"
|
||||
:prev-label="focused?.kind === 'clip' ? 'Previous clip' : 'Previous image'"
|
||||
:next-label="focused?.kind === 'clip' ? 'Next clip' : 'Next image'"
|
||||
@step="stepFocusedFamily"
|
||||
/>
|
||||
<button
|
||||
v-if="showPrivacyToggles"
|
||||
type="button"
|
||||
class="absolute right-2 top-2 z-10 flex h-8 w-8 items-center justify-center rounded-full border border-white/20 bg-black/60 text-zinc-100 hover:border-amber-300/50 hover:text-amber-100"
|
||||
class="absolute right-2 top-2 z-30 flex h-8 w-8 items-center justify-center rounded-full border border-white/20 bg-black/60 text-zinc-100 hover:border-amber-300/50 hover:text-amber-100"
|
||||
:title="detailsHidden ? 'Show preview' : 'Hide preview'"
|
||||
:aria-label="detailsHidden ? 'Show preview' : 'Hide preview'"
|
||||
@click.stop="detailsHidden = !detailsHidden"
|
||||
@@ -329,6 +336,8 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
buildLibraryEntries,
|
||||
chronologicalParts,
|
||||
chronologicalStills,
|
||||
formatClipDuration,
|
||||
formatWhen,
|
||||
type LibraryClip,
|
||||
@@ -458,6 +467,17 @@ const focusedParts = computed(() => {
|
||||
if (focused.value?.kind === 'clip') return focused.value.group?.parts || []
|
||||
return focused.value?.stillGroup?.parts || []
|
||||
})
|
||||
const focusedNavParts = computed(() => {
|
||||
const parts = focusedParts.value
|
||||
if (parts.length < 2) return []
|
||||
if (focused.value?.kind === 'still') return chronologicalStills(parts as LibraryStill[])
|
||||
return chronologicalParts(parts as LibraryClip[])
|
||||
})
|
||||
const focusedNavIndex = computed(() => {
|
||||
const id = focusedPartId.value
|
||||
const index = focusedNavParts.value.findIndex(part => part.id === id)
|
||||
return index < 0 ? 0 : index
|
||||
})
|
||||
const detailPrompt = computed(() => {
|
||||
if (focused.value?.kind === 'clip') return focused.value.group?.head.prompt || ''
|
||||
return focused.value?.stillGroup?.head.prompt || ''
|
||||
@@ -559,6 +579,13 @@ function focusPart(id: string) {
|
||||
focusedPartId.value = id
|
||||
}
|
||||
|
||||
function stepFocusedFamily(delta: number) {
|
||||
const parts = focusedNavParts.value
|
||||
if (parts.length < 2) return
|
||||
const next = parts[(focusedNavIndex.value + delta + parts.length) % parts.length]
|
||||
if (next) focusPart(next.id)
|
||||
}
|
||||
|
||||
function onScroll() {
|
||||
if (scrollRaf) return
|
||||
scrollRaf = requestAnimationFrame(() => {
|
||||
@@ -806,6 +833,8 @@ async function lockFolder() {
|
||||
}
|
||||
|
||||
function onKey(event: KeyboardEvent) {
|
||||
const target = event.target as HTMLElement | null
|
||||
const typing = Boolean(target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.tagName === 'SELECT' || target.isContentEditable))
|
||||
if (event.key === 'Escape') {
|
||||
if (pendingDelete.value) pendingDelete.value = null
|
||||
else if (renaming.value) cancelRename()
|
||||
@@ -816,6 +845,10 @@ function onKey(event: KeyboardEvent) {
|
||||
selectAll()
|
||||
}
|
||||
if (event.key === 'Delete' && selectedKeys.value.length && !renaming.value) askRemoveSelected()
|
||||
if (!typing && (event.key === 'ArrowLeft' || event.key === 'ArrowRight') && focusedNavParts.value.length > 1) {
|
||||
event.preventDefault()
|
||||
stepFocusedFamily(event.key === 'ArrowLeft' ? -1 : 1)
|
||||
}
|
||||
}
|
||||
|
||||
watch(searchDraft, (value) => {
|
||||
|
||||
+1
-1
@@ -1023,7 +1023,7 @@ onMounted(async () => {
|
||||
pollTimer = setInterval(() => {
|
||||
if (typeof document !== 'undefined' && document.hidden) return
|
||||
void loadQueues().catch(() => null)
|
||||
}, 4000)
|
||||
}, 8000)
|
||||
})
|
||||
|
||||
function onRemoveKey(event: KeyboardEvent) {
|
||||
|
||||
Reference in New Issue
Block a user