diff --git a/pages/index.vue b/pages/index.vue
index 86abe06..1f2db9b 100644
--- a/pages/index.vue
+++ b/pages/index.vue
@@ -1261,6 +1261,11 @@
Output
+
+
+ {{ outputNavigationIndex + 1 }} / {{ outputNavigationItems.length }}
+
+
@@ -3536,6 +3543,15 @@ const reviewingStillId = computed(() => {
if (outputPreviewKind.value !== 'edit' || editLockedSave.value) return ''
return stillIdFromLibraryUrl(editResultUrl.value)
})
+type OutputNavigationItem = { kind: 'still'; item: LibraryStill } | { kind: 'clip'; item: LibraryClip }
+const outputNavigationItems = computed(() => libraryEntries.value.flatMap((entry): OutputNavigationItem[] =>
+ entry.kind === 'still'
+ ? chronologicalStills(entry.stillGroup.parts).map(item => ({ kind: 'still' as const, item }))
+ : chronologicalParts(entry.group.parts).map(item => ({ kind: 'clip' as const, item }))
+))
+const outputNavigationIndex = computed(() => outputNavigationItems.value.findIndex(entry =>
+ entry.kind === 'still' ? entry.item.id === reviewingStillId.value : entry.item.id === reviewingClipId.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)
@@ -5872,9 +5888,37 @@ function onStudioKey(event: KeyboardEvent) {
}
if (typingInField(event)) return
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return
- if (outputFamilyStills.value.length < 2) return
+ if (editLightboxUrl.value) {
+ if (outputFamilyStills.value.length < 2) return
+ event.preventDefault()
+ stepOutputFamily(event.key === 'ArrowLeft' ? -1 : 1)
+ return
+ }
+ if (outputNavigationIndex.value < 0 || outputNavigationItems.value.length < 2) return
event.preventDefault()
- stepOutputFamily(event.key === 'ArrowLeft' ? -1 : 1)
+ stepOutput(event.key === 'ArrowLeft' ? -1 : 1)
+}
+
+function stepOutput(delta: number) {
+ const items = outputNavigationItems.value
+ const index = outputNavigationIndex.value
+ if (index < 0 || items.length < 2) return
+ const next = items[(index + delta + items.length) % items.length]
+ if (!next) return
+ const covered = outputPreviewKind.value === 'edit' ? editAwaitingReveal.value : awaitingReveal.value
+ if (next.kind === 'still') {
+ openStill(next.item)
+ lastV2StillId.value = next.item.id
+ editAwaitingReveal.value = covered
+ concealEditOutput.value = covered || next.item.hideInput === true
+ if (!editBusy.value) editStatusMessage.value = covered ? 'Hidden still ready' : 'Loaded from library'
+ } else {
+ player.value?.pause()
+ openClip(next.item)
+ awaitingReveal.value = covered
+ concealOutput.value = covered || next.item.hideThumbnail === true
+ if (!videoBusy.value) statusMessage.value = covered ? 'Hidden clip ready' : 'Loaded from library'
+ }
}
function showOutputStill(still: LibraryStill) {
diff --git a/tests/output-still-actions.test.mjs b/tests/output-still-actions.test.mjs
index 0727788..091dc80 100644
--- a/tests/output-still-actions.test.mjs
+++ b/tests/output-still-actions.test.mjs
@@ -49,3 +49,28 @@ test('full input action restores the displayed image settings instead of the pre
await action('useOutputWithSettings', f.scope)()
assert.deepEqual(f.calls, ['displayed'])
})
+
+for (const from of ['edit', 'video']) {
+ for (const kind of ['still', 'clip']) {
+ for (const covered of [false, true]) {
+ test(`output navigation carries ${covered ? 'covered' : 'revealed'} state from ${from} to ${kind}`, () => {
+ const target = { id: 'next', hideInput: true, hideThumbnail: true }
+ const ref = value => ({ value })
+ const scope = {
+ outputNavigationItems: ref([{ kind, item: { id: 'first' } }, { kind, item: target }]),
+ outputNavigationIndex: ref(0), outputPreviewKind: ref(from),
+ editAwaitingReveal: ref(covered), awaitingReveal: ref(covered),
+ concealEditOutput: ref(false), concealOutput: ref(false),
+ lastV2StillId: ref('first'), editBusy: ref(false), videoBusy: ref(false),
+ editStatusMessage: ref(''), statusMessage: ref(''), player: ref({ pause() {} }),
+ openStill(item) { assert.equal(item, target); scope.editAwaitingReveal.value = true },
+ openClip(item) { assert.equal(item, target); scope.awaitingReveal.value = true }
+ }
+ action('stepOutput', scope)(-1)
+ assert.equal(kind === 'still' ? scope.editAwaitingReveal.value : scope.awaitingReveal.value, covered)
+ assert.equal(target.hideInput, true)
+ assert.equal(target.hideThumbnail, true)
+ })
+ }
+ }
+}