@hyperframes/studio 0.6.0-alpha.12 → 0.6.0-alpha.14
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/dist/assets/hyperframes-player-BI1oj9hu.js +418 -0
- package/dist/assets/index-CBj2NLRG.js +117 -0
- package/dist/assets/index-D1JDq7Gg.css +1 -0
- package/dist/favicon.svg +14 -0
- package/dist/index.html +3 -2
- package/package.json +9 -9
- package/src/App.tsx +427 -4487
- package/src/components/AskAgentModal.tsx +120 -0
- package/src/components/StudioHeader.tsx +133 -0
- package/src/components/StudioLeftSidebar.tsx +125 -0
- package/src/components/StudioPreviewArea.tsx +163 -0
- package/src/components/StudioRightPanel.tsx +198 -0
- package/src/components/TimelineToolbar.tsx +89 -0
- package/src/components/editor/DomEditOverlay.tsx +15 -1
- package/src/components/editor/PropertyPanel.tsx +129 -2681
- package/src/components/editor/domEditing.ts +38 -5
- package/src/components/editor/manualEditingAvailability.test.ts +2 -2
- package/src/components/editor/manualEditingAvailability.ts +1 -1
- package/src/components/editor/manualEdits.ts +32 -0
- package/src/components/editor/propertyPanelColor.tsx +371 -0
- package/src/components/editor/propertyPanelFill.tsx +421 -0
- package/src/components/editor/propertyPanelFont.tsx +455 -0
- package/src/components/editor/propertyPanelHelpers.ts +401 -0
- package/src/components/editor/propertyPanelPrimitives.tsx +357 -0
- package/src/components/editor/propertyPanelSections.tsx +453 -0
- package/src/components/editor/propertyPanelStyleSections.tsx +411 -0
- package/src/components/nle/NLELayout.tsx +0 -10
- package/src/contexts/DomEditContext.tsx +137 -0
- package/src/contexts/FileManagerContext.tsx +110 -0
- package/src/contexts/PanelLayoutContext.tsx +68 -0
- package/src/contexts/StudioContext.tsx +135 -0
- package/src/hooks/useAppHotkeys.ts +326 -0
- package/src/hooks/useAskAgentModal.ts +162 -0
- package/src/hooks/useCaptionDetection.ts +132 -0
- package/src/hooks/useCompositionDimensions.ts +25 -0
- package/src/hooks/useConsoleErrorCapture.ts +60 -0
- package/src/hooks/useDomEditCommits.ts +437 -0
- package/src/hooks/useDomEditSession.ts +342 -0
- package/src/hooks/useDomEditTextCommits.ts +330 -0
- package/src/hooks/useDomSelection.ts +398 -0
- package/src/hooks/useFileManager.ts +431 -0
- package/src/hooks/useFrameCapture.ts +77 -0
- package/src/hooks/useLintModal.ts +35 -0
- package/src/hooks/useManifestPersistence.ts +492 -0
- package/src/hooks/usePanelLayout.ts +68 -0
- package/src/hooks/usePreviewInteraction.ts +153 -0
- package/src/hooks/useRenderClipContent.ts +124 -0
- package/src/hooks/useTimelineEditing.ts +472 -0
- package/src/player/components/Player.tsx +19 -1
- package/src/player/components/Timeline.test.ts +0 -8
- package/src/player/components/Timeline.tsx +2 -83
- package/src/player/components/TimelineClip.tsx +9 -244
- package/src/player/hooks/useTimelinePlayer.ts +140 -103
- package/src/utils/domEditHelpers.ts +50 -0
- package/src/utils/studioFontHelpers.ts +83 -0
- package/src/utils/studioHelpers.ts +214 -0
- package/src/utils/studioPreviewHelpers.ts +185 -0
- package/dist/assets/hyperframes-player-DjsVzYFP.js +0 -418
- package/dist/assets/index-FWg79aJz.css +0 -1
- package/dist/assets/index-xdyn_qRZ.js +0 -110
- package/src/player/components/TimelineClip.test.ts +0 -92
|
@@ -28,11 +28,6 @@ import {
|
|
|
28
28
|
} from "./timelineTheme";
|
|
29
29
|
import { getPinchTimelineZoomPercent, getTimelinePixelsPerSecond } from "./timelineZoom";
|
|
30
30
|
import { TIMELINE_ASSET_MIME } from "../../utils/timelineAssetDrop";
|
|
31
|
-
import {
|
|
32
|
-
canInspectTimelineElement,
|
|
33
|
-
getTimelineElementKey,
|
|
34
|
-
isAudioTimelineElement,
|
|
35
|
-
} from "../../utils/timelineInspector";
|
|
36
31
|
|
|
37
32
|
/* ── Layout ─────────────────────────────────────────────────────── */
|
|
38
33
|
const GUTTER = 32;
|
|
@@ -335,12 +330,6 @@ interface TimelineProps {
|
|
|
335
330
|
intent: BlockedTimelineEditIntent,
|
|
336
331
|
) => void;
|
|
337
332
|
onSelectElement?: (element: import("../store/playerStore").TimelineElement | null) => void;
|
|
338
|
-
onInspectElement?: (element: import("../store/playerStore").TimelineElement) => void;
|
|
339
|
-
inspectedElementId?: string | null;
|
|
340
|
-
layerChildCounts?: ReadonlyMap<string, number>;
|
|
341
|
-
thumbnailedElementIds?: ReadonlySet<string>;
|
|
342
|
-
onToggleElementThumbnail?: (element: import("../store/playerStore").TimelineElement) => void;
|
|
343
|
-
disabled?: boolean;
|
|
344
333
|
theme?: Partial<TimelineTheme>;
|
|
345
334
|
}
|
|
346
335
|
|
|
@@ -389,12 +378,6 @@ export const Timeline = memo(function Timeline({
|
|
|
389
378
|
onResizeElement,
|
|
390
379
|
onBlockedEditAttempt,
|
|
391
380
|
onSelectElement,
|
|
392
|
-
onInspectElement,
|
|
393
|
-
inspectedElementId,
|
|
394
|
-
layerChildCounts,
|
|
395
|
-
thumbnailedElementIds,
|
|
396
|
-
onToggleElementThumbnail,
|
|
397
|
-
disabled = false,
|
|
398
381
|
theme: themeOverrides,
|
|
399
382
|
}: TimelineProps = {}) {
|
|
400
383
|
const theme = useMemo(() => ({ ...defaultTimelineTheme, ...themeOverrides }), [themeOverrides]);
|
|
@@ -414,8 +397,6 @@ export const Timeline = memo(function Timeline({
|
|
|
414
397
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
415
398
|
const [hoveredClip, setHoveredClip] = useState<string | null>(null);
|
|
416
399
|
const isDragging = useRef(false);
|
|
417
|
-
const disabledRef = useRef(disabled);
|
|
418
|
-
disabledRef.current = disabled;
|
|
419
400
|
const shiftClickClipRef = useRef<{
|
|
420
401
|
element: TimelineElement;
|
|
421
402
|
anchorX: number;
|
|
@@ -501,19 +482,6 @@ export const Timeline = memo(function Timeline({
|
|
|
501
482
|
if (shortcutHintRafRef.current) cancelAnimationFrame(shortcutHintRafRef.current);
|
|
502
483
|
});
|
|
503
484
|
|
|
504
|
-
useEffect(() => {
|
|
505
|
-
if (!disabled) return;
|
|
506
|
-
stopClipDragAutoScrollRef.current();
|
|
507
|
-
isDragging.current = false;
|
|
508
|
-
isRangeSelecting.current = false;
|
|
509
|
-
blockedClipRef.current = null;
|
|
510
|
-
setDraggedClip(null);
|
|
511
|
-
setResizingClip(null);
|
|
512
|
-
setRangeSelection(null);
|
|
513
|
-
setShowPopover(false);
|
|
514
|
-
setIsDragOver(false);
|
|
515
|
-
}, [disabled]);
|
|
516
|
-
|
|
517
485
|
// Effective duration: max of store duration and the furthest element end.
|
|
518
486
|
// processTimelineMessage updates elements but not duration, so elements can
|
|
519
487
|
// extend beyond the store's duration — this ensures fit mode shows everything.
|
|
@@ -740,7 +708,6 @@ export const Timeline = memo(function Timeline({
|
|
|
740
708
|
|
|
741
709
|
const seekFromX = useCallback(
|
|
742
710
|
(clientX: number) => {
|
|
743
|
-
if (disabledRef.current) return;
|
|
744
711
|
const el = scrollRef.current;
|
|
745
712
|
if (!el || effectiveDuration <= 0) return;
|
|
746
713
|
const rect = el.getBoundingClientRect();
|
|
@@ -798,7 +765,6 @@ export const Timeline = memo(function Timeline({
|
|
|
798
765
|
};
|
|
799
766
|
|
|
800
767
|
const handleWindowPointerMove = (e: PointerEvent) => {
|
|
801
|
-
if (disabledRef.current) return;
|
|
802
768
|
const drag = draggedClipRef.current;
|
|
803
769
|
const resize = resizingClipRef.current;
|
|
804
770
|
const blocked = blockedClipRef.current;
|
|
@@ -883,7 +849,6 @@ export const Timeline = memo(function Timeline({
|
|
|
883
849
|
|
|
884
850
|
const handleWindowPointerUp = () => {
|
|
885
851
|
stopClipDragAutoScrollRef.current();
|
|
886
|
-
if (disabledRef.current) return;
|
|
887
852
|
const resize = resizingClipRef.current;
|
|
888
853
|
if (resize) {
|
|
889
854
|
resizingClipRef.current = null;
|
|
@@ -989,10 +954,6 @@ export const Timeline = memo(function Timeline({
|
|
|
989
954
|
|
|
990
955
|
const handlePointerDown = useCallback(
|
|
991
956
|
(e: React.PointerEvent) => {
|
|
992
|
-
if (disabledRef.current) {
|
|
993
|
-
e.preventDefault();
|
|
994
|
-
return;
|
|
995
|
-
}
|
|
996
957
|
if (e.button !== 0) return;
|
|
997
958
|
|
|
998
959
|
// Shift+click starts range selection — even on clips
|
|
@@ -1024,7 +985,6 @@ export const Timeline = memo(function Timeline({
|
|
|
1024
985
|
);
|
|
1025
986
|
const handlePointerMove = useCallback(
|
|
1026
987
|
(e: React.PointerEvent) => {
|
|
1027
|
-
if (disabledRef.current) return;
|
|
1028
988
|
if (isRangeSelecting.current) {
|
|
1029
989
|
const rect = scrollRef.current?.getBoundingClientRect();
|
|
1030
990
|
if (rect) {
|
|
@@ -1095,7 +1055,6 @@ export const Timeline = memo(function Timeline({
|
|
|
1095
1055
|
|
|
1096
1056
|
const [isDragOver, setIsDragOver] = useState(false);
|
|
1097
1057
|
const handleAssetDragOver = useCallback((e: React.DragEvent) => {
|
|
1098
|
-
if (disabledRef.current) return;
|
|
1099
1058
|
const hasFiles = e.dataTransfer.files.length > 0;
|
|
1100
1059
|
const hasAsset = Array.from(e.dataTransfer.types).includes(TIMELINE_ASSET_MIME);
|
|
1101
1060
|
if (!hasFiles && !hasAsset) return;
|
|
@@ -1110,7 +1069,6 @@ export const Timeline = memo(function Timeline({
|
|
|
1110
1069
|
(e: React.DragEvent) => {
|
|
1111
1070
|
e.preventDefault();
|
|
1112
1071
|
setIsDragOver(false);
|
|
1113
|
-
if (disabledRef.current) return;
|
|
1114
1072
|
if (onFileDrop && e.dataTransfer.files.length > 0) {
|
|
1115
1073
|
const scroll = scrollRef.current;
|
|
1116
1074
|
const rect = scroll?.getBoundingClientRect();
|
|
@@ -1167,7 +1125,6 @@ export const Timeline = memo(function Timeline({
|
|
|
1167
1125
|
|
|
1168
1126
|
const handlePinchWheel = useCallback(
|
|
1169
1127
|
(e: WheelEvent) => {
|
|
1170
|
-
if (disabledRef.current) return;
|
|
1171
1128
|
if (!e.ctrlKey) return;
|
|
1172
1129
|
const scroll = scrollRef.current;
|
|
1173
1130
|
if (!scroll || durationRef.current <= 0 || fitPpsRef.current <= 0 || ppsRef.current <= 0) {
|
|
@@ -1223,7 +1180,6 @@ export const Timeline = memo(function Timeline({
|
|
|
1223
1180
|
className={`h-full border-t bg-[#0a0a0b] flex flex-col select-none transition-colors duration-150 ${
|
|
1224
1181
|
isDragOver ? "border-studio-accent/50 bg-studio-accent/[0.03]" : "border-neutral-800/50"
|
|
1225
1182
|
}`}
|
|
1226
|
-
aria-disabled={disabled || undefined}
|
|
1227
1183
|
onDragOver={handleAssetDragOver}
|
|
1228
1184
|
onDragLeave={() => setIsDragOver(false)}
|
|
1229
1185
|
onDrop={handleAssetDrop}
|
|
@@ -1379,13 +1335,8 @@ export const Timeline = memo(function Timeline({
|
|
|
1379
1335
|
<div
|
|
1380
1336
|
ref={setContainerRef}
|
|
1381
1337
|
aria-label="Timeline"
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
disabled
|
|
1385
|
-
? "cursor-not-allowed opacity-45"
|
|
1386
|
-
: shiftHeld
|
|
1387
|
-
? "cursor-crosshair"
|
|
1388
|
-
: "cursor-default"
|
|
1338
|
+
className={`relative border-t select-none h-full overflow-hidden ${
|
|
1339
|
+
shiftHeld ? "cursor-crosshair" : "cursor-default"
|
|
1389
1340
|
}`}
|
|
1390
1341
|
style={{
|
|
1391
1342
|
touchAction: "pan-x pan-y",
|
|
@@ -1524,14 +1475,6 @@ export const Timeline = memo(function Timeline({
|
|
|
1524
1475
|
const elementKey = el.key ?? el.id;
|
|
1525
1476
|
const capabilities = getTimelineEditCapabilities(el);
|
|
1526
1477
|
const isSelected = selectedElementId === elementKey;
|
|
1527
|
-
const canInspectClip = canInspectTimelineElement(el);
|
|
1528
|
-
const isInspectorActive =
|
|
1529
|
-
canInspectClip && inspectedElementId === getTimelineElementKey(el);
|
|
1530
|
-
const childCount = canInspectClip
|
|
1531
|
-
? (layerChildCounts?.get(elementKey) ?? 0)
|
|
1532
|
-
: 0;
|
|
1533
|
-
const isThumbnailActive = thumbnailedElementIds?.has(elementKey) ?? false;
|
|
1534
|
-
const thumbnailLabel = isAudioTimelineElement(el) ? "waveform" : "thumbnail";
|
|
1535
1478
|
const isComposition = !!el.compositionSrc;
|
|
1536
1479
|
const clipKey = `${elementKey}-${i}`;
|
|
1537
1480
|
const isHovered = hoveredClip === clipKey;
|
|
@@ -1555,32 +1498,8 @@ export const Timeline = memo(function Timeline({
|
|
|
1555
1498
|
theme={theme}
|
|
1556
1499
|
trackStyle={clipStyle}
|
|
1557
1500
|
isComposition={isComposition}
|
|
1558
|
-
isInspectorActive={isInspectorActive}
|
|
1559
|
-
isThumbnailActive={isThumbnailActive}
|
|
1560
|
-
thumbnailLabel={thumbnailLabel}
|
|
1561
|
-
childCount={childCount}
|
|
1562
1501
|
onHoverStart={() => setHoveredClip(clipKey)}
|
|
1563
1502
|
onHoverEnd={() => setHoveredClip(null)}
|
|
1564
|
-
onInspectorClick={
|
|
1565
|
-
canInspectClip && onInspectElement
|
|
1566
|
-
? (e) => {
|
|
1567
|
-
e.stopPropagation();
|
|
1568
|
-
if (suppressClickRef.current) return;
|
|
1569
|
-
setSelectedElementId(elementKey);
|
|
1570
|
-
onSelectElement?.(el);
|
|
1571
|
-
onInspectElement(el);
|
|
1572
|
-
}
|
|
1573
|
-
: undefined
|
|
1574
|
-
}
|
|
1575
|
-
onThumbnailClick={
|
|
1576
|
-
onToggleElementThumbnail && canInspectClip
|
|
1577
|
-
? (e) => {
|
|
1578
|
-
e.stopPropagation();
|
|
1579
|
-
if (suppressClickRef.current) return;
|
|
1580
|
-
onToggleElementThumbnail(el);
|
|
1581
|
-
}
|
|
1582
|
-
: undefined
|
|
1583
|
-
}
|
|
1584
1503
|
onResizeStart={(edge, e) => {
|
|
1585
1504
|
if (e.button !== 0 || e.shiftKey || !onResizeElement) return;
|
|
1586
1505
|
if (edge === "start" && !capabilities.canTrimStart) return;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { TimelineTrackStyle } from "./timelineTheme";
|
|
2
|
-
// TimelineClip — Visual clip component for the NLE timeline.
|
|
3
2
|
|
|
4
3
|
import { memo, type ReactNode } from "react";
|
|
5
4
|
import type { TimelineElement } from "../store/playerStore";
|
|
@@ -17,67 +16,15 @@ interface TimelineClipProps {
|
|
|
17
16
|
theme?: TimelineTheme;
|
|
18
17
|
trackStyle: TimelineTrackStyle;
|
|
19
18
|
isComposition: boolean;
|
|
20
|
-
isInspectorActive?: boolean;
|
|
21
|
-
isThumbnailActive?: boolean;
|
|
22
|
-
thumbnailLabel?: string;
|
|
23
|
-
childCount?: number;
|
|
24
19
|
onHoverStart: () => void;
|
|
25
20
|
onHoverEnd: () => void;
|
|
26
21
|
onPointerDown?: (e: React.PointerEvent) => void;
|
|
27
22
|
onResizeStart?: (edge: "start" | "end", e: React.PointerEvent) => void;
|
|
28
|
-
onInspectorClick?: (e: React.MouseEvent) => void;
|
|
29
|
-
onThumbnailClick?: (e: React.MouseEvent) => void;
|
|
30
23
|
onClick: (e: React.MouseEvent) => void;
|
|
31
24
|
onDoubleClick: (e: React.MouseEvent) => void;
|
|
32
25
|
children?: ReactNode;
|
|
33
26
|
}
|
|
34
27
|
|
|
35
|
-
export const TIMELINE_CLIP_CONTROL_Z_INDEX = 20;
|
|
36
|
-
|
|
37
|
-
const COMPACT_CLIP_CONTROL_WIDTH = 112;
|
|
38
|
-
|
|
39
|
-
interface TimelineClipControlPresentationInput {
|
|
40
|
-
widthPx: number;
|
|
41
|
-
isSelected: boolean;
|
|
42
|
-
isHovered: boolean;
|
|
43
|
-
isInspectorActive: boolean;
|
|
44
|
-
isThumbnailActive: boolean;
|
|
45
|
-
isDragging: boolean;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface TimelineClipControlPresentation {
|
|
49
|
-
compact: boolean;
|
|
50
|
-
showControls: boolean;
|
|
51
|
-
containerClassName: string;
|
|
52
|
-
buttonClassName: string;
|
|
53
|
-
iconSize: number;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function getTimelineClipControlPresentation({
|
|
57
|
-
widthPx,
|
|
58
|
-
isSelected,
|
|
59
|
-
isHovered,
|
|
60
|
-
isInspectorActive,
|
|
61
|
-
isThumbnailActive,
|
|
62
|
-
isDragging,
|
|
63
|
-
}: TimelineClipControlPresentationInput): TimelineClipControlPresentation {
|
|
64
|
-
const compact = widthPx < COMPACT_CLIP_CONTROL_WIDTH;
|
|
65
|
-
const isInteractive = isHovered || isSelected || isInspectorActive || isThumbnailActive;
|
|
66
|
-
const showControls = !isDragging && (!compact || isInteractive);
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
compact,
|
|
70
|
-
showControls,
|
|
71
|
-
containerClassName: compact
|
|
72
|
-
? "absolute right-1 top-1 flex items-center gap-1"
|
|
73
|
-
: "absolute right-2 top-2 flex items-center gap-1",
|
|
74
|
-
buttonClassName: compact
|
|
75
|
-
? "flex h-5 w-5 items-center justify-center rounded-[7px]"
|
|
76
|
-
: "flex h-6 w-6 items-center justify-center rounded-md",
|
|
77
|
-
iconSize: compact ? 12 : 14,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
28
|
export const TimelineClip = memo(function TimelineClip({
|
|
82
29
|
el,
|
|
83
30
|
pps,
|
|
@@ -89,16 +36,10 @@ export const TimelineClip = memo(function TimelineClip({
|
|
|
89
36
|
theme = defaultTimelineTheme,
|
|
90
37
|
trackStyle,
|
|
91
38
|
isComposition,
|
|
92
|
-
isInspectorActive = false,
|
|
93
|
-
isThumbnailActive = false,
|
|
94
|
-
thumbnailLabel = "thumbnail",
|
|
95
|
-
childCount = 0,
|
|
96
39
|
onHoverStart,
|
|
97
40
|
onHoverEnd,
|
|
98
41
|
onPointerDown,
|
|
99
42
|
onResizeStart,
|
|
100
|
-
onInspectorClick,
|
|
101
|
-
onThumbnailClick,
|
|
102
43
|
onClick,
|
|
103
44
|
onDoubleClick,
|
|
104
45
|
children,
|
|
@@ -120,38 +61,7 @@ export const TimelineClip = memo(function TimelineClip({
|
|
|
120
61
|
: theme.clipShadow;
|
|
121
62
|
const capabilities = getTimelineEditCapabilities(el);
|
|
122
63
|
const displayLabel = el.label || el.id || el.tag;
|
|
123
|
-
const inspectorLabel =
|
|
124
|
-
childCount > 0
|
|
125
|
-
? `${childCount} nested selectable layer${childCount === 1 ? "" : "s"}`
|
|
126
|
-
: "Inspect clip layer";
|
|
127
64
|
const showHandles = handleOpacity > 0.01;
|
|
128
|
-
const baseBackgroundImage = isSelected ? theme.clipBackgroundActive : theme.clipBackground;
|
|
129
|
-
const controlPresentation = getTimelineClipControlPresentation({
|
|
130
|
-
widthPx,
|
|
131
|
-
isSelected,
|
|
132
|
-
isHovered,
|
|
133
|
-
isInspectorActive,
|
|
134
|
-
isThumbnailActive,
|
|
135
|
-
isDragging,
|
|
136
|
-
});
|
|
137
|
-
const glossBackgroundImage = isSelected
|
|
138
|
-
? "linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0))"
|
|
139
|
-
: "linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0))";
|
|
140
|
-
const accentBackgroundImage = `linear-gradient(120deg, ${trackStyle.accent}${
|
|
141
|
-
isSelected ? "22" : "1e"
|
|
142
|
-
}, transparent 28%)`;
|
|
143
|
-
const compositionStripeBackgroundImage =
|
|
144
|
-
isComposition && !hasCustomContent
|
|
145
|
-
? "repeating-linear-gradient(135deg, transparent, transparent 3px, rgba(255,255,255,0.05) 3px, rgba(255,255,255,0.05) 6px)"
|
|
146
|
-
: undefined;
|
|
147
|
-
const clipBackgroundImage = [
|
|
148
|
-
compositionStripeBackgroundImage,
|
|
149
|
-
glossBackgroundImage,
|
|
150
|
-
accentBackgroundImage,
|
|
151
|
-
baseBackgroundImage,
|
|
152
|
-
]
|
|
153
|
-
.filter(Boolean)
|
|
154
|
-
.join(", ");
|
|
155
65
|
|
|
156
66
|
return (
|
|
157
67
|
<div
|
|
@@ -165,7 +75,13 @@ export const TimelineClip = memo(function TimelineClip({
|
|
|
165
75
|
top: clipY,
|
|
166
76
|
bottom: clipY,
|
|
167
77
|
borderRadius: theme.clipRadius,
|
|
168
|
-
|
|
78
|
+
background: isSelected
|
|
79
|
+
? `linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0)), linear-gradient(120deg, ${trackStyle.accent}22, transparent 28%), ${theme.clipBackgroundActive}`
|
|
80
|
+
: `linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0)), linear-gradient(120deg, ${trackStyle.accent}1e, transparent 28%), ${theme.clipBackground}`,
|
|
81
|
+
backgroundImage:
|
|
82
|
+
isComposition && !hasCustomContent
|
|
83
|
+
? `repeating-linear-gradient(135deg, transparent, transparent 3px, rgba(255,255,255,0.05) 3px, rgba(255,255,255,0.05) 6px)`
|
|
84
|
+
: undefined,
|
|
169
85
|
border: `1px solid ${borderColor}`,
|
|
170
86
|
boxShadow,
|
|
171
87
|
transition:
|
|
@@ -176,8 +92,8 @@ export const TimelineClip = memo(function TimelineClip({
|
|
|
176
92
|
}}
|
|
177
93
|
title={
|
|
178
94
|
isComposition
|
|
179
|
-
? `${el.compositionSrc}
|
|
180
|
-
: `${displayLabel}
|
|
95
|
+
? `${el.compositionSrc} • Double-click to open`
|
|
96
|
+
: `${displayLabel} • ${el.start.toFixed(1)}s – ${(el.start + el.duration).toFixed(1)}s`
|
|
181
97
|
}
|
|
182
98
|
onPointerEnter={onHoverStart}
|
|
183
99
|
onPointerLeave={onHoverEnd}
|
|
@@ -185,157 +101,6 @@ export const TimelineClip = memo(function TimelineClip({
|
|
|
185
101
|
onClick={onClick}
|
|
186
102
|
onDoubleClick={onDoubleClick}
|
|
187
103
|
>
|
|
188
|
-
{childCount > 0 && controlPresentation.showControls && (
|
|
189
|
-
<button
|
|
190
|
-
type="button"
|
|
191
|
-
className={`absolute flex items-center gap-1 rounded-md border border-studio-accent/30 bg-neutral-950/75 text-[10px] font-semibold tabular-nums text-studio-accent shadow-lg shadow-black/25 backdrop-blur transition-colors hover:border-studio-accent/60 hover:bg-studio-accent/15 ${
|
|
192
|
-
controlPresentation.compact ? "left-1 top-1 h-5 px-1" : "left-2 top-2 h-6 px-1.5"
|
|
193
|
-
}`}
|
|
194
|
-
style={{ zIndex: TIMELINE_CLIP_CONTROL_Z_INDEX }}
|
|
195
|
-
title={inspectorLabel}
|
|
196
|
-
aria-label={inspectorLabel}
|
|
197
|
-
onPointerDown={(event) => {
|
|
198
|
-
event.stopPropagation();
|
|
199
|
-
}}
|
|
200
|
-
onClick={(event) => {
|
|
201
|
-
event.stopPropagation();
|
|
202
|
-
onInspectorClick?.(event);
|
|
203
|
-
}}
|
|
204
|
-
>
|
|
205
|
-
<svg
|
|
206
|
-
width={controlPresentation.compact ? "11" : "13"}
|
|
207
|
-
height={controlPresentation.compact ? "11" : "13"}
|
|
208
|
-
viewBox="0 0 24 24"
|
|
209
|
-
fill="none"
|
|
210
|
-
stroke="currentColor"
|
|
211
|
-
strokeWidth="1.8"
|
|
212
|
-
strokeLinecap="round"
|
|
213
|
-
strokeLinejoin="round"
|
|
214
|
-
aria-hidden="true"
|
|
215
|
-
>
|
|
216
|
-
<rect x="4" y="4" width="6" height="6" rx="1" />
|
|
217
|
-
<rect x="14" y="4" width="6" height="6" rx="1" />
|
|
218
|
-
<rect x="4" y="14" width="6" height="6" rx="1" />
|
|
219
|
-
<path d="M14 17h6" />
|
|
220
|
-
</svg>
|
|
221
|
-
{childCount}
|
|
222
|
-
</button>
|
|
223
|
-
)}
|
|
224
|
-
{onInspectorClick &&
|
|
225
|
-
controlPresentation.compact &&
|
|
226
|
-
!controlPresentation.showControls &&
|
|
227
|
-
!isDragging && (
|
|
228
|
-
<button
|
|
229
|
-
type="button"
|
|
230
|
-
className="group/clip-inspect absolute right-1 top-1/2 flex h-7 w-2 -translate-y-1/2 items-center justify-center rounded-full border border-white/15 bg-neutral-950/70 text-neutral-300 shadow-lg shadow-black/25 backdrop-blur transition-all hover:w-5 hover:border-white/30 hover:bg-neutral-950/90 focus:w-5 focus:border-studio-accent/60 focus:bg-studio-accent/15 focus:outline-none"
|
|
231
|
-
style={{ zIndex: TIMELINE_CLIP_CONTROL_Z_INDEX }}
|
|
232
|
-
title={inspectorLabel}
|
|
233
|
-
aria-label={inspectorLabel}
|
|
234
|
-
onPointerDown={(event) => {
|
|
235
|
-
event.stopPropagation();
|
|
236
|
-
}}
|
|
237
|
-
onClick={(event) => {
|
|
238
|
-
event.stopPropagation();
|
|
239
|
-
onInspectorClick(event);
|
|
240
|
-
}}
|
|
241
|
-
>
|
|
242
|
-
<svg
|
|
243
|
-
className="opacity-0 transition-opacity group-hover/clip-inspect:opacity-100 group-focus/clip-inspect:opacity-100"
|
|
244
|
-
width="12"
|
|
245
|
-
height="12"
|
|
246
|
-
viewBox="0 0 24 24"
|
|
247
|
-
fill="none"
|
|
248
|
-
stroke="currentColor"
|
|
249
|
-
strokeWidth="1.8"
|
|
250
|
-
strokeLinecap="round"
|
|
251
|
-
strokeLinejoin="round"
|
|
252
|
-
aria-hidden="true"
|
|
253
|
-
>
|
|
254
|
-
<path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6S2 12 2 12Z" />
|
|
255
|
-
<circle cx="12" cy="12" r="3" />
|
|
256
|
-
</svg>
|
|
257
|
-
</button>
|
|
258
|
-
)}
|
|
259
|
-
{(onThumbnailClick || onInspectorClick) && controlPresentation.showControls && (
|
|
260
|
-
<div
|
|
261
|
-
className={controlPresentation.containerClassName}
|
|
262
|
-
style={{ zIndex: TIMELINE_CLIP_CONTROL_Z_INDEX }}
|
|
263
|
-
>
|
|
264
|
-
{onThumbnailClick && (
|
|
265
|
-
<button
|
|
266
|
-
type="button"
|
|
267
|
-
className={`${controlPresentation.buttonClassName} border shadow-lg shadow-black/25 backdrop-blur transition-colors ${
|
|
268
|
-
isThumbnailActive
|
|
269
|
-
? "border-studio-accent/60 bg-studio-accent/18 text-studio-accent"
|
|
270
|
-
: "border-white/12 bg-neutral-950/70 text-neutral-400 hover:border-white/24 hover:text-neutral-100"
|
|
271
|
-
}`}
|
|
272
|
-
title={
|
|
273
|
-
isThumbnailActive ? `Hide clip ${thumbnailLabel}` : `Show clip ${thumbnailLabel}`
|
|
274
|
-
}
|
|
275
|
-
aria-label={
|
|
276
|
-
isThumbnailActive ? `Hide clip ${thumbnailLabel}` : `Show clip ${thumbnailLabel}`
|
|
277
|
-
}
|
|
278
|
-
onPointerDown={(event) => {
|
|
279
|
-
event.stopPropagation();
|
|
280
|
-
}}
|
|
281
|
-
onClick={(event) => {
|
|
282
|
-
event.stopPropagation();
|
|
283
|
-
onThumbnailClick(event);
|
|
284
|
-
}}
|
|
285
|
-
>
|
|
286
|
-
<svg
|
|
287
|
-
width={controlPresentation.iconSize}
|
|
288
|
-
height={controlPresentation.iconSize}
|
|
289
|
-
viewBox="0 0 24 24"
|
|
290
|
-
fill="none"
|
|
291
|
-
stroke="currentColor"
|
|
292
|
-
strokeWidth="1.8"
|
|
293
|
-
strokeLinecap="round"
|
|
294
|
-
strokeLinejoin="round"
|
|
295
|
-
aria-hidden="true"
|
|
296
|
-
>
|
|
297
|
-
<rect x="3" y="5" width="18" height="14" rx="2" />
|
|
298
|
-
<circle cx="8" cy="10" r="1.5" />
|
|
299
|
-
<path d="m4 17 5-5 4 4 2-2 5 5" />
|
|
300
|
-
</svg>
|
|
301
|
-
</button>
|
|
302
|
-
)}
|
|
303
|
-
{onInspectorClick && (
|
|
304
|
-
<button
|
|
305
|
-
type="button"
|
|
306
|
-
className={`${controlPresentation.buttonClassName} border shadow-lg shadow-black/25 backdrop-blur transition-colors ${
|
|
307
|
-
isInspectorActive
|
|
308
|
-
? "border-studio-accent/60 bg-studio-accent/18 text-studio-accent"
|
|
309
|
-
: "border-white/12 bg-neutral-950/70 text-neutral-400 hover:border-white/24 hover:text-neutral-100"
|
|
310
|
-
}`}
|
|
311
|
-
title={inspectorLabel}
|
|
312
|
-
aria-label={inspectorLabel}
|
|
313
|
-
onPointerDown={(event) => {
|
|
314
|
-
event.stopPropagation();
|
|
315
|
-
}}
|
|
316
|
-
onClick={(event) => {
|
|
317
|
-
event.stopPropagation();
|
|
318
|
-
onInspectorClick(event);
|
|
319
|
-
}}
|
|
320
|
-
>
|
|
321
|
-
<svg
|
|
322
|
-
width={controlPresentation.iconSize}
|
|
323
|
-
height={controlPresentation.iconSize}
|
|
324
|
-
viewBox="0 0 24 24"
|
|
325
|
-
fill="none"
|
|
326
|
-
stroke="currentColor"
|
|
327
|
-
strokeWidth="1.8"
|
|
328
|
-
strokeLinecap="round"
|
|
329
|
-
strokeLinejoin="round"
|
|
330
|
-
aria-hidden="true"
|
|
331
|
-
>
|
|
332
|
-
<path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6S2 12 2 12Z" />
|
|
333
|
-
<circle cx="12" cy="12" r="3" />
|
|
334
|
-
</svg>
|
|
335
|
-
</button>
|
|
336
|
-
)}
|
|
337
|
-
</div>
|
|
338
|
-
)}
|
|
339
104
|
<div
|
|
340
105
|
aria-hidden="true"
|
|
341
106
|
role="presentation"
|