@linker-design-plus/timeline-track 2.1.2 → 2.1.3

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 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 导向的测试与诊断重构方案
@@ -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';
@@ -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,66 @@
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
+ loadClipThumbnails?: (clip: Clip) => Promise<boolean> | void;
24
+ addClipToTrack?: (trackId: string, clip: Clip) => boolean;
25
+ removeClip?: (clipId: string) => void;
26
+ removeClipFromTrack?: (trackId: string, clipId: string) => boolean;
27
+ updateClip?: (clipId: string, updates: Partial<Clip>) => void;
28
+ moveClipToTrack?: (clip: Clip, sourceTrackId: string, targetTrackId: string) => boolean;
29
+ getClips?: () => Clip[];
30
+ findTrackById?: (trackId: string) => TimelineTrackCollectionItem | null;
31
+ findTrackByClipId?: (clipId: string) => TimelineTrackCollectionItem | null;
32
+ getDefaultTrack?: () => TimelineTrackCollectionItem | null;
33
+ onCommit: (summary: TimelineStructureSessionSummary) => void;
34
+ }
35
+ export declare class TimelineStructureSession {
36
+ private readonly options;
37
+ private cleanupEmptyTracks;
38
+ private durationDirty;
39
+ private canPlayDirty;
40
+ private selectionDirty;
41
+ private trackLayoutDirty;
42
+ private trackInfoDirty;
43
+ private stageDirty;
44
+ private previewDirty;
45
+ private committed;
46
+ private readonly events;
47
+ constructor(options: TimelineStructureSessionOptions);
48
+ addClipToTrack(trackId: string, clip: Clip): boolean;
49
+ removeClip(clipId: string): void;
50
+ removeClipFromTrack(trackId: string, clipId: string): boolean;
51
+ updateClip(clipId: string, updates: Partial<Clip>): void;
52
+ moveClipToTrack(clipId: string, targetTrackId: string): boolean;
53
+ ensureTrackFromHistorySnapshot(trackSnapshot: Track, restoreAnchor?: TrackRestoreAnchor | null): string | null;
54
+ removeClipGaps(): void;
55
+ getClips(): Clip[];
56
+ findTrackByClipId(clipId: string): TimelineTrackCollectionItem | null;
57
+ getDefaultTrack(): TimelineTrackCollectionItem | null;
58
+ queueEvent(type: TimelineStructureSessionEvent['type'], payload: Record<string, unknown>): void;
59
+ markStructureMutation(): void;
60
+ markClipStateMutation(): void;
61
+ markTrackLayoutMutation(): void;
62
+ commit(): void;
63
+ createHistoryExecutionTarget(): TimelineHistoryExecutionTarget;
64
+ private resolveTrackId;
65
+ }
66
+ 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,9 @@ 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;
89
92
  constructor(config?: Partial<TimelineConfig>);
90
93
  private createPlaybackAttemptId;
91
94
  private refreshPlaybackAttempt;
@@ -121,7 +124,10 @@ export declare class TimelineManager {
121
124
  private getEventDispatcher;
122
125
  private getDefaultTrackForHistory;
123
126
  private getTimelineHistoryRecorder;
124
- private getTimelineHistoryExecutor;
127
+ private withStructureSession;
128
+ private getActiveStructureSession;
129
+ private withPreservedEmptyTracks;
130
+ private applyStructureSessionSummary;
125
131
  private withHistoryBoundary;
126
132
  private shouldSkipHistoryRecording;
127
133
  private withHistoryTransaction;
@@ -188,7 +194,7 @@ export declare class TimelineManager {
188
194
  private retryPendingPreview;
189
195
  private handlePreviewBackendRuntimeError;
190
196
  init(container: HTMLElement): void;
191
- createTrack(type: TrackType, name?: string, insertionPlacement?: TrackInsertionPlacement, referenceTrackId?: string, roleOverride?: TrackEntity['role']): string;
197
+ createTrack(type: TrackType, name?: string, insertionPlacement?: TrackInsertionPlacement, referenceTrackId?: string, roleOverride?: TrackEntity['role'], explicitTrackId?: string): string;
192
198
  removeTrack(trackId: string): boolean;
193
199
  renameTrack(trackId: string, newName: string): boolean;
194
200
  getTracks(): any[];
@@ -198,6 +204,7 @@ export declare class TimelineManager {
198
204
  private calculateTrackY;
199
205
  private calculateTotalHeight;
200
206
  private updateAllTrackPositions;
207
+ private removeEmptyTracksFromStructure;
201
208
  private cleanupEmptyTracks;
202
209
  private clearAllTrackDropPreviews;
203
210
  private showClipDropPreview;
@@ -320,6 +327,10 @@ export declare class TimelineManager {
320
327
  splitClip(clipId: string, time: TimeMs): void;
321
328
  splitCurrentClip(): void;
322
329
  getClips(): Clip[];
330
+ private syncTimelineClipMediaStateFromPreview;
331
+ private applyTimelineClipMediaStatus;
332
+ private clearTimelineClipMediaStatus;
333
+ private getFailedPreviewClipIds;
323
334
  private getExportComposition;
324
335
  private getExportCoverUrl;
325
336
  exportTimeline(): TimelineExportData;
@@ -597,7 +608,6 @@ export declare class TimelineManager {
597
608
  private isPointOnClip;
598
609
  private getClipsIntersectingBox;
599
610
  private resolveTrackRenderHeight;
600
- private addClipToTrack;
601
611
  private cloneTrackSnapshot;
602
612
  private ensureTrackFromHistorySnapshot;
603
613
  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, 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,7 +74,7 @@ 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
80
  createCompoundAction(actions: Action[], label?: string): CompoundAction;
@@ -13,6 +13,7 @@ export interface TimelineHistoryExecutionTarget {
13
13
  findTrackByClipId(clipId: string): TimelineHistoryExecutionTrack | null;
14
14
  getDefaultTrack(): TimelineHistoryExecutionTrack | null;
15
15
  loadClipThumbnails?(clip: Clip): Promise<boolean> | void;
16
+ withClipRemovalBatch?<T>(callback: () => T): T;
16
17
  }
17
18
  export declare class TimelineHistoryExecutor {
18
19
  private readonly target;
@@ -22,8 +23,11 @@ export declare class TimelineHistoryExecutor {
22
23
  private executeUndoInternal;
23
24
  private executeRedoInternal;
24
25
  private restoreMovedClip;
26
+ private withClipRemovalBatch;
25
27
  private normalizeRemoveClipActionData;
26
28
  private normalizeAddClipActionData;
29
+ private normalizeSplitClipActionData;
30
+ private normalizeMoveClipBetweenTracksActionData;
27
31
  private resolveTrackIdForHistorySnapshot;
28
32
  private resolveRestoreAnchor;
29
33
  }
@@ -9,9 +9,9 @@ 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;
@@ -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;
@@ -297,6 +302,11 @@ export interface TrackRestoreAnchor {
297
302
  previousTrackId: string | null;
298
303
  nextTrackId: string | null;
299
304
  }
305
+ export interface TrackHistoryReference {
306
+ trackId: string | null;
307
+ trackSnapshot: Track | null;
308
+ trackRestoreAnchor: TrackRestoreAnchor | null;
309
+ }
300
310
  export interface UpdateClipAction {
301
311
  type: 'update_clip';
302
312
  data: {
@@ -312,6 +322,9 @@ export interface SplitClipAction {
312
322
  originalClip: Clip;
313
323
  clip1: Clip;
314
324
  clip2: Clip;
325
+ sourceTrackId?: string | null;
326
+ sourceTrackSnapshot?: Track | null;
327
+ sourceTrackRestoreAnchor?: TrackRestoreAnchor | null;
315
328
  };
316
329
  timestamp: number;
317
330
  }
@@ -367,6 +380,8 @@ export interface MoveClipBetweenTracksAction {
367
380
  clipAfter: Clip;
368
381
  sourceTrackSnapshot: Track | null;
369
382
  targetTrackSnapshot: Track | null;
383
+ sourceTrackRestoreAnchor?: TrackRestoreAnchor | null;
384
+ targetTrackRestoreAnchor?: TrackRestoreAnchor | null;
370
385
  };
371
386
  timestamp: number;
372
387
  }
@@ -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
  };
@@ -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[];