macro-clips slice 4c: button creation in sidebar + drag to clip tracks

- store.seedDefaultButtonClip(name) adds a button clip with a single
  shared rect asset and four pre-populated state stacks in blue shades
  (normal/hover/pressed/disabled). Layer name 'Face' is consistent so
  transitions can later pair layers by name.
- AssetSidebar gets a Buttons section below Clips: create via prompt,
  rename, delete, drag-to-track. Drag uses a new dataTransfer type
  'application/x-animatr-button-clip' to keep it distinct from
  animation-clip drops.
- Timeline clip-lane drop accepts both types and dispatches
  addClipInstance / addButtonInstance accordingly; the new-track
  drop zone handles both too, so dragging a button to an empty area
  creates a fresh clip track + instance in one step.
- i18n for the sidebar strings and three dialogs.
- test covers seedDefaultButtonClip: four populated states, shared
  asset, consistent layer name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.7 committed 2026-04-20 21:31:41 +02:00
1 parent fb58cd18f7
commit 024883943b
5 files changed
+193 -6

No files matched your search

+44
View File
@@ -1145,6 +1145,50 @@ export const useSceneStore = defineStore('scene', {
;(this.scene.buttonClips ||= []).push(clip)
return clip
},
/**
* Create a ButtonClip pre-populated with a visible rectangle for each
* of the four states (different shades per state). The single shared
* asset is added to the scene. Intended for the "+ Button" sidebar
* flow — advanced users can still edit or replace the layers later.
*/
seedDefaultButtonClip(name?: string): ButtonClip {
const title = name?.trim() || 'Button'
const clip = this.addButtonClip(title)
const asset = this.addAsset({
kind: 'rect',
name: `${title} face`,
width: 120,
height: 40,
fill: '#3b82f6',
}) as Extract<Asset, { kind: 'rect' }>
const makeFace = (fill: string, layerName: string): Layer => ({
id: uid(),
name: layerName,
assetId: asset.id,
visible: true,
base: { ...DEFAULT_BASE_PROPS, fill },
keyframes: [
{
id: uid(),
time: 0,
props: { ...DEFAULT_BASE_PROPS, fill },
easing: 'linear',
},
],
offsetX: asset.width / 2,
offsetY: asset.height / 2,
})
const target = this.scene.buttonClips?.find((c) => c.id === clip.id)
if (target) {
target.states = {
normal: [makeFace('#3b82f6', 'Face')],
hover: [makeFace('#60a5fa', 'Face')],
pressed: [makeFace('#2563eb', 'Face')],
disabled: [makeFace('#9ca3af', 'Face')],
}
}
return target ?? clip
},
removeButtonClip(id: string) {
if (this.scene.buttonClips) {
this.scene.buttonClips = this.scene.buttonClips.filter((c) => c.id !== id)