@linker-design-plus/timeline-track 2.1.2 → 2.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/components/track/Track.d.ts +3 -1
- package/dist/core/controllers/index.d.ts +1 -0
- package/dist/core/controllers/timelinePlaybackViewportFollow.d.ts +16 -0
- package/dist/core/controllers/timelinePreviewSession.d.ts +1 -0
- package/dist/core/controllers/timelineStructureSession.d.ts +67 -0
- package/dist/core/facade/timelineManager.d.ts +33 -4
- package/dist/core/history/history.d.ts +4 -3
- package/dist/core/history/timelineHistoryExecutor.d.ts +5 -0
- package/dist/core/history/timelineHistoryRecorder.d.ts +3 -2
- package/dist/core/models/types.d.ts +27 -1
- package/dist/core/theme/colorTokens.d.ts +3 -0
- package/dist/core/tracks/timelineTrackBridge.d.ts +2 -1
- package/dist/core/tracks/trackManager.d.ts +1 -1
- package/dist/index.cjs.js +92 -92
- package/dist/index.es.js +4447 -3715
- package/dist/utils/rendering/KonvaUtils.d.ts +3 -3
- package/dist/utils/rendering/clipCoverRenderer.d.ts +2 -0
- package/dist/utils/rendering/clipVisualRenderer.d.ts +3 -3
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -135,6 +135,7 @@ pnpm install
|
|
|
135
135
|
pnpm dev
|
|
136
136
|
pnpm build
|
|
137
137
|
pnpm test
|
|
138
|
+
pnpm harness:test
|
|
138
139
|
pnpm exec tsc -p tsconfig.json --noEmit
|
|
139
140
|
```
|
|
140
141
|
|
|
@@ -146,3 +147,4 @@ pnpm exec tsc -p tsconfig.json --noEmit
|
|
|
146
147
|
- [docs/resource-cache.md](./docs/resource-cache.md): 预览资源缓存的能力边界与配置
|
|
147
148
|
- [docs/interaction-model.md](./docs/interaction-model.md): 指针交互分层和拖拽约束
|
|
148
149
|
- [docs/preview-playback-recovery-flow.md](./docs/preview-playback-recovery-flow.md): 预览媒体时钟驱动播放、buffering 阻塞与恢复续播流程
|
|
150
|
+
- [docs/testing-harness-roadmap.md](./docs/testing-harness-roadmap.md): Harness Engineering 导向的测试与诊断重构方案
|
|
@@ -35,6 +35,7 @@ export declare class Track {
|
|
|
35
35
|
private onClipSelect;
|
|
36
36
|
private onTimeJump;
|
|
37
37
|
private onClipOverlap?;
|
|
38
|
+
private onPrimaryTrackMagnetMove?;
|
|
38
39
|
private hasBoundGlobalPointerListenersForDrag;
|
|
39
40
|
private hasLockedGlobalCursor;
|
|
40
41
|
private edgeAutoScrollAnimationFrameId;
|
|
@@ -56,7 +57,7 @@ export declare class Track {
|
|
|
56
57
|
private handleGlobalPointerMove;
|
|
57
58
|
private handleGlobalPointerEnd;
|
|
58
59
|
private handleWindowBlur;
|
|
59
|
-
constructor(layer: Konva.Layer, config: TrackConfig, trackType: TrackType, zoom: number, trackY: number, trackHeight: number, theme: Theme, onClipUpdate: (clip: ClipType, originalClip?: ClipType, clipUpdates?: ClipStateUpdate[]) => void, onClipAdd: (clip: ClipType) => void, onClipRemove: (clipId: string) => void, onClipSplit: (clip1: ClipType, clip2: ClipType) => void, onClipSelect: (clip: ClipType) => void, onTimeJump: (time: TimeMs) => void, onHorizontalDragAutoScroll?: (nextScrollLeft: number) => number, onClipOverlap?: (clip: ClipType, currentTrackId: string, originalClip?: ClipType | null) => void, onClipCrossTrackPreview?: (clip: ClipType, targetTrackY: number, currentTrackId: string) => 'self' | 'external' | 'clear', onClipCrossTrack?: (clip: ClipType, originalClip: ClipType | null, targetTrackY: number, currentTrackId: string) => boolean | void, onClearDropPreview?: () => void, onClearSelection?: () => void, onSnapGuideChange?: (guideTime: TimeMs | null) => void, onClipToggleSelection?: (clipId: string) => void, onSetSingleSelection?: (clipId: string) => void, getMultiDragClipIds?: (clipId: string) => string[] | null, onMultiDragMove?: (request: MultiDragMoveRequest) => boolean | void, onMultiDragInteractionEnd?: () => void, dragActivationThreshold?: number, enableClipSnap?: boolean, clipSnapThreshold?: number);
|
|
60
|
+
constructor(layer: Konva.Layer, config: TrackConfig, trackType: TrackType, zoom: number, trackY: number, trackHeight: number, theme: Theme, onClipUpdate: (clip: ClipType, originalClip?: ClipType, clipUpdates?: ClipStateUpdate[]) => void, onClipAdd: (clip: ClipType) => void, onClipRemove: (clipId: string) => void, onClipSplit: (clip1: ClipType, clip2: ClipType) => void, onClipSelect: (clip: ClipType) => void, onTimeJump: (time: TimeMs) => void, onHorizontalDragAutoScroll?: (nextScrollLeft: number) => number, onClipOverlap?: (clip: ClipType, currentTrackId: string, originalClip?: ClipType | null) => void, onPrimaryTrackMagnetMove?: (clip: ClipType, originalClip: ClipType | null, peerClips: ClipType[], currentTrackId: string) => boolean | void, onClipCrossTrackPreview?: (clip: ClipType, targetTrackY: number, currentTrackId: string) => 'self' | 'external' | 'clear', onClipCrossTrack?: (clip: ClipType, originalClip: ClipType | null, targetTrackY: number, currentTrackId: string) => boolean | void, onClearDropPreview?: () => void, onClearSelection?: () => void, onSnapGuideChange?: (guideTime: TimeMs | null) => void, onClipToggleSelection?: (clipId: string) => void, onSetSingleSelection?: (clipId: string) => void, getMultiDragClipIds?: (clipId: string) => string[] | null, onMultiDragMove?: (request: MultiDragMoveRequest) => boolean | void, onMultiDragInteractionEnd?: () => void, dragActivationThreshold?: number, enableClipSnap?: boolean, clipSnapThreshold?: number);
|
|
60
61
|
private initClips;
|
|
61
62
|
private ensureDropPreviewGroup;
|
|
62
63
|
private createDropPreviewRect;
|
|
@@ -83,6 +84,7 @@ export declare class Track {
|
|
|
83
84
|
private resolveDraggedClipForInteraction;
|
|
84
85
|
private applyInteractionTransition;
|
|
85
86
|
private handleClipMoveEnd;
|
|
87
|
+
private compactPrimaryTrackClips;
|
|
86
88
|
private updateAllClips;
|
|
87
89
|
private buildAndSendUpdates;
|
|
88
90
|
private updateHoverCursor;
|
|
@@ -12,6 +12,7 @@ export { TimelinePreviewRuntimeController } from './timelinePreviewRuntimeContro
|
|
|
12
12
|
export { TimelinePreviewStateController, type TimelinePendingPreviewState } from './timelinePreviewStateController';
|
|
13
13
|
export { TimelineKeyboardShortcutsController, type TimelineKeyboardShortcutsControllerCallbacks } from './timelineKeyboardShortcutsController';
|
|
14
14
|
export * from './timelineSelectionController';
|
|
15
|
+
export { TimelineStructureSession, type TimelineStructureSessionSummary } from './timelineStructureSession';
|
|
15
16
|
export * from './timelineTrackMutationController';
|
|
16
17
|
export { TimelineClipConfigController } from './timelineClipConfigController';
|
|
17
18
|
export { TimelineTrackInfoPanelController } from './timelineTrackInfoPanelController';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { TimeMs } from '../models/types';
|
|
2
|
+
export declare const PLAYBACK_VIEWPORT_FOLLOW_MARGIN_PX = 48;
|
|
3
|
+
export declare function getPlayheadScreenX(time: TimeMs, zoom: number, scrollLeft: number): number;
|
|
4
|
+
export interface PlaybackViewportFollowInput {
|
|
5
|
+
time: TimeMs;
|
|
6
|
+
zoom: number;
|
|
7
|
+
scrollLeft: number;
|
|
8
|
+
viewportWidth: number;
|
|
9
|
+
contentWidth: number;
|
|
10
|
+
margin?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function clampScrollLeft(scrollLeft: number, viewportWidth: number, contentWidth: number): number;
|
|
13
|
+
/**
|
|
14
|
+
* 播放边距跟随:播放头超出左右安全边距时返回目标 scrollLeft,否则返回 null。
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveScrollLeftForPlaybackFollow(input: PlaybackViewportFollowInput): number | null;
|
|
@@ -204,6 +204,7 @@ export declare class TimelinePreviewSession {
|
|
|
204
204
|
private ensureTextPreviewFontReady;
|
|
205
205
|
private resetTextPreviewFontState;
|
|
206
206
|
private refreshSlotVisualLayout;
|
|
207
|
+
private syncSlotFailureVisualState;
|
|
207
208
|
private buildSelectedOverlayState;
|
|
208
209
|
private getFrameSize;
|
|
209
210
|
private getDocument;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Clip, Track, TrackRestoreAnchor } from '../models/types';
|
|
2
|
+
import type { TimelineTrackCollection, TimelineTrackCollectionItem } from '../tracks/timelineTrackCollection';
|
|
3
|
+
import type { TimelineHistoryExecutionTarget } from '../history/timelineHistoryExecutor';
|
|
4
|
+
export interface TimelineStructureSessionEvent {
|
|
5
|
+
type: 'clip_added' | 'clip_removed' | 'clip_updated';
|
|
6
|
+
payload: Record<string, unknown>;
|
|
7
|
+
}
|
|
8
|
+
export interface TimelineStructureSessionSummary {
|
|
9
|
+
cleanupEmptyTracks: boolean;
|
|
10
|
+
durationDirty: boolean;
|
|
11
|
+
canPlayDirty: boolean;
|
|
12
|
+
selectionDirty: boolean;
|
|
13
|
+
trackLayoutDirty: boolean;
|
|
14
|
+
trackInfoDirty: boolean;
|
|
15
|
+
stageDirty: boolean;
|
|
16
|
+
previewDirty: boolean;
|
|
17
|
+
events: TimelineStructureSessionEvent[];
|
|
18
|
+
}
|
|
19
|
+
interface TimelineStructureSessionOptions {
|
|
20
|
+
trackCollection: TimelineTrackCollection;
|
|
21
|
+
ensureTrackFromHistorySnapshot: (trackSnapshot: Track, restoreAnchor?: TrackRestoreAnchor | null) => string | null;
|
|
22
|
+
removeClipGaps: () => void;
|
|
23
|
+
setPrimaryTrackMagnetEnabled: (enabled: boolean) => void;
|
|
24
|
+
loadClipThumbnails?: (clip: Clip) => Promise<boolean> | void;
|
|
25
|
+
addClipToTrack?: (trackId: string, clip: Clip) => boolean;
|
|
26
|
+
removeClip?: (clipId: string) => void;
|
|
27
|
+
removeClipFromTrack?: (trackId: string, clipId: string) => boolean;
|
|
28
|
+
updateClip?: (clipId: string, updates: Partial<Clip>) => void;
|
|
29
|
+
moveClipToTrack?: (clip: Clip, sourceTrackId: string, targetTrackId: string) => boolean;
|
|
30
|
+
getClips?: () => Clip[];
|
|
31
|
+
findTrackById?: (trackId: string) => TimelineTrackCollectionItem | null;
|
|
32
|
+
findTrackByClipId?: (clipId: string) => TimelineTrackCollectionItem | null;
|
|
33
|
+
getDefaultTrack?: () => TimelineTrackCollectionItem | null;
|
|
34
|
+
onCommit: (summary: TimelineStructureSessionSummary) => void;
|
|
35
|
+
}
|
|
36
|
+
export declare class TimelineStructureSession {
|
|
37
|
+
private readonly options;
|
|
38
|
+
private cleanupEmptyTracks;
|
|
39
|
+
private durationDirty;
|
|
40
|
+
private canPlayDirty;
|
|
41
|
+
private selectionDirty;
|
|
42
|
+
private trackLayoutDirty;
|
|
43
|
+
private trackInfoDirty;
|
|
44
|
+
private stageDirty;
|
|
45
|
+
private previewDirty;
|
|
46
|
+
private committed;
|
|
47
|
+
private readonly events;
|
|
48
|
+
constructor(options: TimelineStructureSessionOptions);
|
|
49
|
+
addClipToTrack(trackId: string, clip: Clip): boolean;
|
|
50
|
+
removeClip(clipId: string): void;
|
|
51
|
+
removeClipFromTrack(trackId: string, clipId: string): boolean;
|
|
52
|
+
updateClip(clipId: string, updates: Partial<Clip>): void;
|
|
53
|
+
moveClipToTrack(clipId: string, targetTrackId: string): boolean;
|
|
54
|
+
ensureTrackFromHistorySnapshot(trackSnapshot: Track, restoreAnchor?: TrackRestoreAnchor | null): string | null;
|
|
55
|
+
removeClipGaps(): void;
|
|
56
|
+
getClips(): Clip[];
|
|
57
|
+
findTrackByClipId(clipId: string): TimelineTrackCollectionItem | null;
|
|
58
|
+
getDefaultTrack(): TimelineTrackCollectionItem | null;
|
|
59
|
+
queueEvent(type: TimelineStructureSessionEvent['type'], payload: Record<string, unknown>): void;
|
|
60
|
+
markStructureMutation(): void;
|
|
61
|
+
markClipStateMutation(): void;
|
|
62
|
+
markTrackLayoutMutation(): void;
|
|
63
|
+
commit(): void;
|
|
64
|
+
createHistoryExecutionTarget(): TimelineHistoryExecutionTarget;
|
|
65
|
+
private resolveTrackId;
|
|
66
|
+
}
|
|
67
|
+
export {};
|
|
@@ -49,7 +49,6 @@ export declare class TimelineManager {
|
|
|
49
49
|
private sourceLoadingCount;
|
|
50
50
|
private timelineStore?;
|
|
51
51
|
private timelineCommands?;
|
|
52
|
-
private timelineHistoryExecutor?;
|
|
53
52
|
private timelineHistoryRecorder?;
|
|
54
53
|
private timelinePresentationAdapter?;
|
|
55
54
|
private timelineTrackBridge?;
|
|
@@ -74,6 +73,7 @@ export declare class TimelineManager {
|
|
|
74
73
|
private splitOperationDepth;
|
|
75
74
|
private readonly diagnostics;
|
|
76
75
|
private previewRuntimeState;
|
|
76
|
+
private failedPreviewClipIds?;
|
|
77
77
|
private previewActiveClipIds;
|
|
78
78
|
private previewClockSeekAlignmentTargetTime;
|
|
79
79
|
private previewClockSeekAlignmentDeadlineMs;
|
|
@@ -86,6 +86,11 @@ export declare class TimelineManager {
|
|
|
86
86
|
private voiceLinkedTextRegenerationTimer;
|
|
87
87
|
private pendingVoiceLinkedTextRegeneration;
|
|
88
88
|
private historyRecordingSuppressionDepth;
|
|
89
|
+
private activeStructureSession;
|
|
90
|
+
private structureSessionDepth;
|
|
91
|
+
private preserveEmptyTracksDepth;
|
|
92
|
+
private playbackViewportFollowSuspended;
|
|
93
|
+
private isProgrammaticScrollChange;
|
|
89
94
|
constructor(config?: Partial<TimelineConfig>);
|
|
90
95
|
private createPlaybackAttemptId;
|
|
91
96
|
private refreshPlaybackAttempt;
|
|
@@ -121,13 +126,20 @@ export declare class TimelineManager {
|
|
|
121
126
|
private getEventDispatcher;
|
|
122
127
|
private getDefaultTrackForHistory;
|
|
123
128
|
private getTimelineHistoryRecorder;
|
|
124
|
-
private
|
|
129
|
+
private withStructureSession;
|
|
130
|
+
private getActiveStructureSession;
|
|
131
|
+
private withPreservedEmptyTracks;
|
|
132
|
+
private applyStructureSessionSummary;
|
|
125
133
|
private withHistoryBoundary;
|
|
126
134
|
private shouldSkipHistoryRecording;
|
|
127
135
|
private withHistoryTransaction;
|
|
128
136
|
private get selectedClipId();
|
|
129
137
|
private set selectedClipId(value);
|
|
130
138
|
private ensureConfigState;
|
|
139
|
+
private isPlaybackViewportFollowEnabled;
|
|
140
|
+
private suspendPlaybackViewportFollowIfUserScrolling;
|
|
141
|
+
private runProgrammaticScrollChange;
|
|
142
|
+
private followPlaybackViewportIfNeeded;
|
|
131
143
|
private getCurrentTimeState;
|
|
132
144
|
private getPlayStateState;
|
|
133
145
|
private setPlayStateState;
|
|
@@ -188,7 +200,7 @@ export declare class TimelineManager {
|
|
|
188
200
|
private retryPendingPreview;
|
|
189
201
|
private handlePreviewBackendRuntimeError;
|
|
190
202
|
init(container: HTMLElement): void;
|
|
191
|
-
createTrack(type: TrackType, name?: string, insertionPlacement?: TrackInsertionPlacement, referenceTrackId?: string, roleOverride?: TrackEntity['role']): string;
|
|
203
|
+
createTrack(type: TrackType, name?: string, insertionPlacement?: TrackInsertionPlacement, referenceTrackId?: string, roleOverride?: TrackEntity['role'], explicitTrackId?: string): string;
|
|
192
204
|
removeTrack(trackId: string): boolean;
|
|
193
205
|
renameTrack(trackId: string, newName: string): boolean;
|
|
194
206
|
getTracks(): any[];
|
|
@@ -198,6 +210,7 @@ export declare class TimelineManager {
|
|
|
198
210
|
private calculateTrackY;
|
|
199
211
|
private calculateTotalHeight;
|
|
200
212
|
private updateAllTrackPositions;
|
|
213
|
+
private removeEmptyTracksFromStructure;
|
|
201
214
|
private cleanupEmptyTracks;
|
|
202
215
|
private clearAllTrackDropPreviews;
|
|
203
216
|
private showClipDropPreview;
|
|
@@ -240,6 +253,17 @@ export declare class TimelineManager {
|
|
|
240
253
|
getCurrentTime(): TimeMs;
|
|
241
254
|
setEnableClipSnap(enabled: boolean): void;
|
|
242
255
|
getEnableClipSnap(): boolean;
|
|
256
|
+
setEnablePrimaryTrackMagnet(enabled: boolean): void;
|
|
257
|
+
getEnablePrimaryTrackMagnet(): boolean;
|
|
258
|
+
private setPrimaryTrackMagnetEnabledFromHistory;
|
|
259
|
+
private setPrimaryTrackMagnetEnabledState;
|
|
260
|
+
private getPrimaryTrackId;
|
|
261
|
+
private isPrimaryTrackId;
|
|
262
|
+
private shouldNormalizePrimaryTrackForTrackIds;
|
|
263
|
+
private getStableStartOrderedClips;
|
|
264
|
+
private buildPrimaryTrackContinuousClips;
|
|
265
|
+
private buildPrimaryTrackMultiDragOrderedClips;
|
|
266
|
+
private normalizePrimaryTrackInSession;
|
|
243
267
|
setZoom(zoom: number): void;
|
|
244
268
|
/**
|
|
245
269
|
* 以游标(playhead)为中心设置缩放,用于外置 slider 等非幕布上的缩放操作
|
|
@@ -320,6 +344,10 @@ export declare class TimelineManager {
|
|
|
320
344
|
splitClip(clipId: string, time: TimeMs): void;
|
|
321
345
|
splitCurrentClip(): void;
|
|
322
346
|
getClips(): Clip[];
|
|
347
|
+
private syncTimelineClipMediaStateFromPreview;
|
|
348
|
+
private applyTimelineClipMediaStatus;
|
|
349
|
+
private clearTimelineClipMediaStatus;
|
|
350
|
+
private getFailedPreviewClipIds;
|
|
323
351
|
private getExportComposition;
|
|
324
352
|
private getExportCoverUrl;
|
|
325
353
|
exportTimeline(): TimelineExportData;
|
|
@@ -404,6 +432,7 @@ export declare class TimelineManager {
|
|
|
404
432
|
private emitSelectedClipChangeIfNeeded;
|
|
405
433
|
private beginSourceLoading;
|
|
406
434
|
private endSourceLoading;
|
|
435
|
+
private handlePrimaryTrackMagnetMove;
|
|
407
436
|
private handleClipOverlap;
|
|
408
437
|
private handleClipCrossTrackPreview;
|
|
409
438
|
private handleClipCrossTrack;
|
|
@@ -575,6 +604,7 @@ export declare class TimelineManager {
|
|
|
575
604
|
private resolveMultiDragOverlapTargetTrackId;
|
|
576
605
|
private commitMultiDragPlacements;
|
|
577
606
|
private finalizeMultiDragSameTrack;
|
|
607
|
+
private compactPrimaryTrackAfterMultiDrag;
|
|
578
608
|
private previewMultiDragCrossTrack;
|
|
579
609
|
private finalizeMultiDragCrossTrack;
|
|
580
610
|
private resolveTrackIndexDelta;
|
|
@@ -597,7 +627,6 @@ export declare class TimelineManager {
|
|
|
597
627
|
private isPointOnClip;
|
|
598
628
|
private getClipsIntersectingBox;
|
|
599
629
|
private resolveTrackRenderHeight;
|
|
600
|
-
private addClipToTrack;
|
|
601
630
|
private cloneTrackSnapshot;
|
|
602
631
|
private ensureTrackFromHistorySnapshot;
|
|
603
632
|
private getTrackRestoreAnchor;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Action, Clip, ClipStateUpdate, CompoundAction, HistoryState, MoveClipBetweenTracksAction, MoveClipAction, MultiClipUpdateAction, RemoveClipAction, RemoveGapsAction, RestoreClipAudioAction, ResizeClipAction, SeparateClipAudioAction, SetTimeAction, SplitClipAction, Track, TrackRestoreAnchor, UpdateClipAction, AddClipAction } from '../models/types';
|
|
1
|
+
import { Action, Clip, ClipStateUpdate, CompoundAction, HistoryState, MoveClipBetweenTracksAction, MoveClipAction, MultiClipUpdateAction, RemoveClipAction, RemoveGapsAction, RestoreClipAudioAction, ResizeClipAction, SeparateClipAudioAction, SetPrimaryTrackMagnetAction, SetTimeAction, SplitClipAction, Track, TrackRestoreAnchor, TrackHistoryReference, UpdateClipAction, AddClipAction } from '../models/types';
|
|
2
2
|
export declare class HistoryManager {
|
|
3
3
|
private state;
|
|
4
4
|
private maxHistorySize;
|
|
@@ -50,7 +50,7 @@ export declare class HistoryManager {
|
|
|
50
50
|
/**
|
|
51
51
|
* 创建分割片段操作
|
|
52
52
|
*/
|
|
53
|
-
createSplitClipAction(originalClip: Clip, clip1: Clip, clip2: Clip): SplitClipAction;
|
|
53
|
+
createSplitClipAction(originalClip: Clip, clip1: Clip, clip2: Clip, sourceTrackReference?: Partial<TrackHistoryReference>): SplitClipAction;
|
|
54
54
|
/**
|
|
55
55
|
* 创建移动片段操作
|
|
56
56
|
*/
|
|
@@ -74,8 +74,9 @@ export declare class HistoryManager {
|
|
|
74
74
|
/**
|
|
75
75
|
* 创建跨轨道移动片段操作
|
|
76
76
|
*/
|
|
77
|
-
createMoveClipBetweenTracksAction(clipId: string, sourceTrackId: string, targetTrackId: string, clipBefore: Clip, clipAfter: Clip, sourceTrackSnapshot: Track | null, targetTrackSnapshot: Track | null): MoveClipBetweenTracksAction;
|
|
77
|
+
createMoveClipBetweenTracksAction(clipId: string, sourceTrackId: string, targetTrackId: string, clipBefore: Clip, clipAfter: Clip, sourceTrackSnapshot: Track | null, targetTrackSnapshot: Track | null, sourceTrackRestoreAnchor?: TrackRestoreAnchor | null, targetTrackRestoreAnchor?: TrackRestoreAnchor | null): MoveClipBetweenTracksAction;
|
|
78
78
|
createSeparateClipAudioAction(videoClipBefore: Clip, videoClipAfter: Clip, audioClip: Clip | null, audioTrackId: string | null): SeparateClipAudioAction;
|
|
79
79
|
createRestoreClipAudioAction(videoClipBefore: Clip, videoClipAfter: Clip, audioClip: Clip | null, audioTrackId: string | null): RestoreClipAudioAction;
|
|
80
|
+
createSetPrimaryTrackMagnetAction(previousEnabled: boolean, nextEnabled: boolean): SetPrimaryTrackMagnetAction;
|
|
80
81
|
createCompoundAction(actions: Action[], label?: string): CompoundAction;
|
|
81
82
|
}
|
|
@@ -7,12 +7,14 @@ export interface TimelineHistoryExecutionTarget {
|
|
|
7
7
|
addClipToTrack(trackId: string, clip: Clip): boolean;
|
|
8
8
|
updateClip(clipId: string, updates: Partial<Clip>): void;
|
|
9
9
|
moveClipToTrack(clipId: string, targetTrackId: string): boolean;
|
|
10
|
+
setPrimaryTrackMagnetEnabled?(enabled: boolean): void;
|
|
10
11
|
ensureTrackFromHistorySnapshot(trackSnapshot: Track, restoreAnchor?: TrackRestoreAnchor | null): string | null;
|
|
11
12
|
removeClipGaps(): void;
|
|
12
13
|
getClips(): Clip[];
|
|
13
14
|
findTrackByClipId(clipId: string): TimelineHistoryExecutionTrack | null;
|
|
14
15
|
getDefaultTrack(): TimelineHistoryExecutionTrack | null;
|
|
15
16
|
loadClipThumbnails?(clip: Clip): Promise<boolean> | void;
|
|
17
|
+
withClipRemovalBatch?<T>(callback: () => T): T;
|
|
16
18
|
}
|
|
17
19
|
export declare class TimelineHistoryExecutor {
|
|
18
20
|
private readonly target;
|
|
@@ -22,8 +24,11 @@ export declare class TimelineHistoryExecutor {
|
|
|
22
24
|
private executeUndoInternal;
|
|
23
25
|
private executeRedoInternal;
|
|
24
26
|
private restoreMovedClip;
|
|
27
|
+
private withClipRemovalBatch;
|
|
25
28
|
private normalizeRemoveClipActionData;
|
|
26
29
|
private normalizeAddClipActionData;
|
|
30
|
+
private normalizeSplitClipActionData;
|
|
31
|
+
private normalizeMoveClipBetweenTracksActionData;
|
|
27
32
|
private resolveTrackIdForHistorySnapshot;
|
|
28
33
|
private resolveRestoreAnchor;
|
|
29
34
|
}
|
|
@@ -9,13 +9,14 @@ export declare class TimelineHistoryRecorder {
|
|
|
9
9
|
createRemoveClipAction(clip: Clip, sourceTrackId?: string | null, sourceTrackSnapshot?: Track | null, sourceTrackRestoreAnchor?: TrackRestoreAnchor | null): Action;
|
|
10
10
|
recordRemoveClip(clip: Clip, sourceTrackId?: string | null, sourceTrackSnapshot?: Track | null, sourceTrackRestoreAnchor?: TrackRestoreAnchor | null): Action;
|
|
11
11
|
recordClipUpdate(clip: Clip, originalClip?: Clip, clipUpdates?: ClipStateUpdate[]): Action | null;
|
|
12
|
-
recordSplitClip(clip1: Clip, clip2: Clip): Action;
|
|
12
|
+
recordSplitClip(clip1: Clip, clip2: Clip, sourceTrackId?: string | null, sourceTrackSnapshot?: Track | null, sourceTrackRestoreAnchor?: TrackRestoreAnchor | null): Action;
|
|
13
13
|
recordRemoveGaps(clipsBefore: Clip[]): Action | null;
|
|
14
|
-
recordMoveClipBetweenTracks(clipId: string, sourceTrackId: string, targetTrackId: string, clipBefore: Clip, clipAfter: Clip, sourceTrackSnapshot: Track | null, targetTrackSnapshot: Track | null): Action;
|
|
14
|
+
recordMoveClipBetweenTracks(clipId: string, sourceTrackId: string, targetTrackId: string, clipBefore: Clip, clipAfter: Clip, sourceTrackSnapshot: Track | null, targetTrackSnapshot: Track | null, sourceTrackRestoreAnchor?: TrackRestoreAnchor | null, targetTrackRestoreAnchor?: TrackRestoreAnchor | null): Action;
|
|
15
15
|
recordSeparateClipAudio(videoClipBefore: Clip, videoClipAfter: Clip, audioClip: Clip | null, audioTrackId: string | null): Action;
|
|
16
16
|
recordRestoreClipAudio(videoClipBefore: Clip, videoClipAfter: Clip, audioClip: Clip | null, audioTrackId: string | null): Action;
|
|
17
17
|
createSeparateClipAudioAction(videoClipBefore: Clip, videoClipAfter: Clip, audioClip: Clip | null, audioTrackId: string | null): Action;
|
|
18
18
|
createRestoreClipAudioAction(videoClipBefore: Clip, videoClipAfter: Clip, audioClip: Clip | null, audioTrackId: string | null): Action;
|
|
19
|
+
recordSetPrimaryTrackMagnet(previousEnabled: boolean, nextEnabled: boolean): Action;
|
|
19
20
|
recordCompoundAction(actions: Action[], label?: string): Action | null;
|
|
20
21
|
createClipUpdateAction(clip: Clip, originalClip?: Clip, clipUpdates?: ClipStateUpdate[]): Action | null;
|
|
21
22
|
withTransaction<T>(label: string | undefined, callback: () => T): T;
|
|
@@ -76,6 +76,7 @@ export interface GenerateVoiceResult {
|
|
|
76
76
|
voiceId: string;
|
|
77
77
|
voiceName: string;
|
|
78
78
|
}
|
|
79
|
+
export type ClipMediaStatus = 'ready' | 'failed';
|
|
79
80
|
export interface ClipConfigVoicePanelOptions {
|
|
80
81
|
voiceCatalog?: VoiceOption[];
|
|
81
82
|
generateVoiceBatch?: (requests: GenerateVoiceRequest[]) => Promise<GenerateVoiceResult[]>;
|
|
@@ -105,6 +106,8 @@ export interface ClipConfig {
|
|
|
105
106
|
ttsVoiceId?: string;
|
|
106
107
|
ttsVoiceName?: string;
|
|
107
108
|
ttsFollowTextUpdates?: boolean;
|
|
109
|
+
mediaStatus?: ClipMediaStatus;
|
|
110
|
+
mediaStatusMessage?: string | null;
|
|
108
111
|
}
|
|
109
112
|
export interface ClipEntity {
|
|
110
113
|
id: string;
|
|
@@ -131,6 +134,8 @@ export interface ClipEntity {
|
|
|
131
134
|
ttsVoiceId?: string;
|
|
132
135
|
ttsVoiceName?: string;
|
|
133
136
|
ttsFollowTextUpdates?: boolean;
|
|
137
|
+
mediaStatus?: ClipMediaStatus;
|
|
138
|
+
mediaStatusMessage?: string | null;
|
|
134
139
|
}
|
|
135
140
|
export interface ClipViewState {
|
|
136
141
|
isDragging: boolean;
|
|
@@ -218,6 +223,7 @@ export interface TimelineConfig {
|
|
|
218
223
|
speed?: number;
|
|
219
224
|
dragActivationThreshold?: number;
|
|
220
225
|
enableClipSnap?: boolean;
|
|
226
|
+
enablePrimaryTrackMagnet?: boolean;
|
|
221
227
|
clipSnapThreshold?: number;
|
|
222
228
|
thumbnailProvider?: ThumbnailProvider;
|
|
223
229
|
previewBackend?: PreviewBackendType;
|
|
@@ -226,6 +232,8 @@ export interface TimelineConfig {
|
|
|
226
232
|
textPreviewFont?: TextPreviewFontConfig | null;
|
|
227
233
|
draftData?: TimelineExportData;
|
|
228
234
|
keyboardShortcuts?: false | TimelineKeyboardShortcutsConfig;
|
|
235
|
+
/** 播放时自动横向滚动以保持播放头在可视安全区内;默认开启 */
|
|
236
|
+
playbackViewportFollow?: boolean;
|
|
229
237
|
}
|
|
230
238
|
export type PreviewAspectRatioMode = 'auto-first-added-video' | 'auto-first-video' | 'manual';
|
|
231
239
|
export interface PreviewAspectRatio {
|
|
@@ -297,6 +305,11 @@ export interface TrackRestoreAnchor {
|
|
|
297
305
|
previousTrackId: string | null;
|
|
298
306
|
nextTrackId: string | null;
|
|
299
307
|
}
|
|
308
|
+
export interface TrackHistoryReference {
|
|
309
|
+
trackId: string | null;
|
|
310
|
+
trackSnapshot: Track | null;
|
|
311
|
+
trackRestoreAnchor: TrackRestoreAnchor | null;
|
|
312
|
+
}
|
|
300
313
|
export interface UpdateClipAction {
|
|
301
314
|
type: 'update_clip';
|
|
302
315
|
data: {
|
|
@@ -312,6 +325,9 @@ export interface SplitClipAction {
|
|
|
312
325
|
originalClip: Clip;
|
|
313
326
|
clip1: Clip;
|
|
314
327
|
clip2: Clip;
|
|
328
|
+
sourceTrackId?: string | null;
|
|
329
|
+
sourceTrackSnapshot?: Track | null;
|
|
330
|
+
sourceTrackRestoreAnchor?: TrackRestoreAnchor | null;
|
|
315
331
|
};
|
|
316
332
|
timestamp: number;
|
|
317
333
|
}
|
|
@@ -367,6 +383,8 @@ export interface MoveClipBetweenTracksAction {
|
|
|
367
383
|
clipAfter: Clip;
|
|
368
384
|
sourceTrackSnapshot: Track | null;
|
|
369
385
|
targetTrackSnapshot: Track | null;
|
|
386
|
+
sourceTrackRestoreAnchor?: TrackRestoreAnchor | null;
|
|
387
|
+
targetTrackRestoreAnchor?: TrackRestoreAnchor | null;
|
|
370
388
|
};
|
|
371
389
|
timestamp: number;
|
|
372
390
|
}
|
|
@@ -390,6 +408,14 @@ export interface RestoreClipAudioAction {
|
|
|
390
408
|
};
|
|
391
409
|
timestamp: number;
|
|
392
410
|
}
|
|
411
|
+
export interface SetPrimaryTrackMagnetAction {
|
|
412
|
+
type: 'set_primary_track_magnet';
|
|
413
|
+
data: {
|
|
414
|
+
previousEnabled: boolean;
|
|
415
|
+
nextEnabled: boolean;
|
|
416
|
+
};
|
|
417
|
+
timestamp: number;
|
|
418
|
+
}
|
|
393
419
|
export interface CompoundAction {
|
|
394
420
|
type: 'compound';
|
|
395
421
|
data: {
|
|
@@ -398,7 +424,7 @@ export interface CompoundAction {
|
|
|
398
424
|
};
|
|
399
425
|
timestamp: number;
|
|
400
426
|
}
|
|
401
|
-
export type Action = AddClipAction | RemoveClipAction | UpdateClipAction | SplitClipAction | MoveClipAction | ResizeClipAction | SetTimeAction | RemoveGapsAction | MultiClipUpdateAction | MoveClipBetweenTracksAction | SeparateClipAudioAction | RestoreClipAudioAction | CompoundAction;
|
|
427
|
+
export type Action = AddClipAction | RemoveClipAction | UpdateClipAction | SplitClipAction | MoveClipAction | ResizeClipAction | SetTimeAction | RemoveGapsAction | MultiClipUpdateAction | MoveClipBetweenTracksAction | SeparateClipAudioAction | RestoreClipAudioAction | SetPrimaryTrackMagnetAction | CompoundAction;
|
|
402
428
|
export type ActionType = Action['type'];
|
|
403
429
|
export interface Position {
|
|
404
430
|
x: number;
|
|
@@ -10,6 +10,7 @@ export declare const SHARED_COLOR_TOKENS: {
|
|
|
10
10
|
readonly videoClipBackground: "#1e5679";
|
|
11
11
|
readonly textClipBackground: "#604c32";
|
|
12
12
|
readonly audioClipBackground: "#254428";
|
|
13
|
+
readonly mediaClipFailedBackground: "#670014";
|
|
13
14
|
readonly selectedTrackSurface: "#292929";
|
|
14
15
|
readonly borderStrong: "#333333";
|
|
15
16
|
readonly borderMedium: "#444444";
|
|
@@ -125,6 +126,8 @@ export declare const PLAYHEAD_DEFAULT_COLORS: {
|
|
|
125
126
|
export declare const PREVIEW_PENDING_OVERLAY_COLORS: {
|
|
126
127
|
readonly background: "rgba(0, 0, 0, 0.56)";
|
|
127
128
|
readonly text: "#ffffff";
|
|
129
|
+
readonly failedBackground: "#670014";
|
|
130
|
+
readonly failedText: "#ffffff";
|
|
128
131
|
readonly actionBackground: "#4c8dff";
|
|
129
132
|
readonly actionText: "#ffffff";
|
|
130
133
|
};
|
|
@@ -18,6 +18,7 @@ export interface TimelineTrackBridgeHandlers {
|
|
|
18
18
|
onTimeJump: (time: TimeMs) => void;
|
|
19
19
|
onHorizontalDragAutoScroll?: (nextScrollLeft: number) => number;
|
|
20
20
|
onClipOverlap?: (clip: Clip, currentTrackId: string, originalClip?: Clip | null) => void;
|
|
21
|
+
onPrimaryTrackMagnetMove?: (clip: Clip, originalClip: Clip | null, peerClips: Clip[], currentTrackId: string) => boolean | void;
|
|
21
22
|
onClipCrossTrackPreview?: (clip: Clip, targetTrackY: number, currentTrackId: string) => 'self' | 'external' | 'clear';
|
|
22
23
|
onClipCrossTrack?: (clip: Clip, originalClip: Clip | null, targetTrackY: number, currentTrackId: string) => boolean | void;
|
|
23
24
|
onClearDropPreview?: () => void;
|
|
@@ -41,7 +42,7 @@ export interface TimelineTrackBridgeCreateOptions {
|
|
|
41
42
|
enableClipSnap?: boolean;
|
|
42
43
|
clipSnapThreshold?: number;
|
|
43
44
|
}
|
|
44
|
-
export type TimelineTrackConstructor<TTrack = Track> = new (layer: Konva.Layer, config: TrackConfig, trackType: TrackType, zoom: number, trackY: number, trackHeight: number, theme: Theme, onClipUpdate: (clip: Clip, originalClip?: Clip, clipUpdates?: ClipStateUpdate[]) => void, onClipAdd: (clip: Clip) => void, onClipRemove: (clipId: string) => void, onClipSplit: (clip1: Clip, clip2: Clip) => void, onClipSelect: (clip: Clip) => void, onTimeJump: (time: TimeMs) => void, onHorizontalDragAutoScroll?: (nextScrollLeft: number) => number, onClipOverlap?: (clip: Clip, currentTrackId: string, originalClip?: Clip | null) => void, onClipCrossTrackPreview?: (clip: Clip, targetTrackY: number, currentTrackId: string) => 'self' | 'external' | 'clear', onClipCrossTrack?: (clip: Clip, originalClip: Clip | null, targetTrackY: number, currentTrackId: string) => boolean | void, onClearDropPreview?: () => void, onClearSelection?: () => void, onSnapGuideChange?: (guideTime: TimeMs | null) => void, onClipToggleSelection?: (clipId: string) => void, onSetSingleSelection?: (clipId: string) => void, getMultiDragClipIds?: (clipId: string) => string[] | null, onMultiDragMove?: (request: MultiDragMoveRequest) => boolean | void, onMultiDragInteractionEnd?: () => void, dragActivationThreshold?: number, enableClipSnap?: boolean, clipSnapThreshold?: number) => TTrack;
|
|
45
|
+
export type TimelineTrackConstructor<TTrack = Track> = new (layer: Konva.Layer, config: TrackConfig, trackType: TrackType, zoom: number, trackY: number, trackHeight: number, theme: Theme, onClipUpdate: (clip: Clip, originalClip?: Clip, clipUpdates?: ClipStateUpdate[]) => void, onClipAdd: (clip: Clip) => void, onClipRemove: (clipId: string) => void, onClipSplit: (clip1: Clip, clip2: Clip) => void, onClipSelect: (clip: Clip) => void, onTimeJump: (time: TimeMs) => void, onHorizontalDragAutoScroll?: (nextScrollLeft: number) => number, onClipOverlap?: (clip: Clip, currentTrackId: string, originalClip?: Clip | null) => void, onPrimaryTrackMagnetMove?: (clip: Clip, originalClip: Clip | null, peerClips: Clip[], currentTrackId: string) => boolean | void, onClipCrossTrackPreview?: (clip: Clip, targetTrackY: number, currentTrackId: string) => 'self' | 'external' | 'clear', onClipCrossTrack?: (clip: Clip, originalClip: Clip | null, targetTrackY: number, currentTrackId: string) => boolean | void, onClearDropPreview?: () => void, onClearSelection?: () => void, onSnapGuideChange?: (guideTime: TimeMs | null) => void, onClipToggleSelection?: (clipId: string) => void, onSetSingleSelection?: (clipId: string) => void, getMultiDragClipIds?: (clipId: string) => string[] | null, onMultiDragMove?: (request: MultiDragMoveRequest) => boolean | void, onMultiDragInteractionEnd?: () => void, dragActivationThreshold?: number, enableClipSnap?: boolean, clipSnapThreshold?: number) => TTrack;
|
|
45
46
|
export declare class TimelineTrackBridge<TTrack = Track> {
|
|
46
47
|
private readonly handlers;
|
|
47
48
|
private readonly TrackCtor;
|
|
@@ -3,7 +3,7 @@ export declare class TrackManager {
|
|
|
3
3
|
private tracks;
|
|
4
4
|
private trackCounter;
|
|
5
5
|
constructor(initialTracks?: Track[]);
|
|
6
|
-
createTrack(type: TrackType, name?: string, insertionPlacement?: TrackInsertionPlacement, referenceTrackId?: string, roleOverride?: Track['role']): Track;
|
|
6
|
+
createTrack(type: TrackType, name?: string, insertionPlacement?: TrackInsertionPlacement, referenceTrackId?: string, roleOverride?: Track['role'], explicitTrackId?: string): Track;
|
|
7
7
|
removeTrack(trackId: string): boolean;
|
|
8
8
|
renameTrack(trackId: string, newName: string): boolean;
|
|
9
9
|
getTracks(): Track[];
|