macro-clips slice 4a: script frame dialog + reusable action editor
- ActionEditor.vue — reusable list editor for Action[]: add/remove/reorder, per-type parameter forms (jump to time/frame/marker, set button state/disabled, script source). Marker and button dropdowns pull from the store's current scene. - ScriptFrameDialog.vue — modal wrapping ActionEditor plus time and label inputs; commits on close, supports deletion. - Timeline wires the dialog: clicking a script frame opens it for edit, clicking the empty script lane creates a new frame at that time and opens the editor. - i18n keys for all action types, parameters, and dialog labels. Button-authoring UI (definitions + instance placement + properties panel) is its own slice (4b) — this commit ships script authoring only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
92d58937e3
commit
b9d1ca16f7
4 files changed
+635
No files matched your search
@@ -30,6 +30,7 @@ import { useSceneStore } from '@/stores/scene'
|
||||
import { sampleLayer } from '@/lib/interpolate'
|
||||
import { snapToCandidates } from '@/lib/timelineSnap'
|
||||
import { confirmDialog, promptDialog } from '@/components/ui'
|
||||
import ScriptFrameDialog from './ScriptFrameDialog.vue'
|
||||
import { t } from '@/i18n'
|
||||
import { kfSelectionKey, useKfSelection, type KfSelectionKey } from '@/composables/useKfSelection'
|
||||
import { showLocked } from '@/composables/useShowLocked'
|
||||
@@ -1478,6 +1479,27 @@ function clipBlockStyle(ci: ClipInstance) {
|
||||
}
|
||||
|
||||
const BUTTON_STATES = ['normal', 'hover', 'pressed', 'disabled'] as const
|
||||
|
||||
// Script frame dialog state
|
||||
const scriptDialogOpen = ref(false)
|
||||
const scriptDialogFrameId = ref<string | null>(null)
|
||||
|
||||
function openScriptFrame(frameId: string) {
|
||||
scriptDialogFrameId.value = frameId
|
||||
scriptDialogOpen.value = true
|
||||
}
|
||||
|
||||
function onScriptLaneClick(e: MouseEvent, trackId: string) {
|
||||
// Only handle clicks on the bare lane (not on an existing frame).
|
||||
if ((e.target as HTMLElement).closest('.script-frame')) return
|
||||
const el = e.currentTarget as HTMLElement
|
||||
const rect = el.getBoundingClientRect()
|
||||
if (rect.width <= 0) return
|
||||
const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))
|
||||
const time = snapTime(ratio * duration.value)
|
||||
const frame = store.addScriptFrame(trackId, time)
|
||||
if (frame) openScriptFrame(frame.id)
|
||||
}
|
||||
const BUTTON_CELL_SEC = 0.5 // visual width of each of the 4 state cells
|
||||
function buttonInstanceStyle(bi: ButtonInstance) {
|
||||
// Four cells, each with a fixed per-cell visual width. Kept as a fixed
|
||||
@@ -2311,6 +2333,7 @@ onBeforeUnmount(() => {
|
||||
<div
|
||||
class="track-lane script-lane"
|
||||
:style="{ width: laneWidth + 'px' }"
|
||||
@click="track.locked ? null : onScriptLaneClick($event, track.scriptTrackId)"
|
||||
>
|
||||
<span class="playhead" :style="{ left: pxFromTime(store.playhead) + 'px' }" />
|
||||
<span
|
||||
@@ -2319,6 +2342,7 @@ onBeforeUnmount(() => {
|
||||
class="script-frame"
|
||||
:style="{ left: pxFromTime(frame.time) + 'px' }"
|
||||
:title="frame.label ?? t('timeline.scriptFrameTooltip', { n: frame.actions.length })"
|
||||
@click.stop="openScriptFrame(frame.id)"
|
||||
>
|
||||
<span class="script-frame-dot"></span>
|
||||
<span v-if="frame.actions.length > 1" class="script-frame-count">{{ frame.actions.length }}</span>
|
||||
@@ -2550,6 +2574,10 @@ onBeforeUnmount(() => {
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
<ScriptFrameDialog
|
||||
v-model="scriptDialogOpen"
|
||||
:frame-id="scriptDialogFrameId"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in new issue
Block a user