@hyperframes/studio 0.8.50 → 0.8.53-desktop.0
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-CxxDxHTW.js → hyperframes-player-PTEnAHhg.js} +3 -3
- package/dist/assets/{index-DUAIWxkI.js → index--myXp8ui.js} +1 -1
- package/dist/assets/{index-Dt3qBI-9.js → index-BjslOS9g.js} +1 -1
- package/dist/assets/index-BnXZuIJD.css +1 -0
- package/dist/assets/index-CNyviK_l.js +390 -0
- package/dist/index.css +29 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +825 -24
- package/dist/index.html +2 -2
- package/dist/index.js +8911 -9853
- package/dist/index.js.map +1 -1
- package/dist/styles/tailwind-preset.js.map +1 -1
- package/package.json +13 -12
- package/src/App.tsx +145 -163
- package/src/captions/components/CaptionAnimationPanel.tsx +4 -4
- package/src/captions/components/CaptionOverlay.sender.test.tsx +52 -0
- package/src/captions/components/CaptionOverlay.tsx +8 -4
- package/src/captions/components/CaptionOverlayUtils.ts +2 -3
- package/src/captions/components/CaptionPropertyPanel.tsx +3 -3
- package/src/captions/components/CaptionTimeline.tsx +1 -1
- package/src/captions/components/shared.tsx +2 -2
- package/src/components/AskAgentModal.tsx +5 -5
- package/src/components/EditorShell.selectionSync.test.tsx +9 -4
- package/src/components/EditorShell.tsx +59 -62
- package/src/components/ExternalFileConflictBanner.tsx +16 -16
- package/src/components/LintModal.tsx +6 -16
- package/src/components/MediaPreview.tsx +2 -2
- package/src/components/RightPanelTabs.test.tsx +151 -0
- package/src/components/RightPanelTabs.tsx +59 -0
- package/src/components/SaveQueuePausedBanner.tsx +2 -2
- package/src/components/StudioHeader.dom.test.tsx +181 -0
- package/src/components/StudioHeader.test.ts +0 -12
- package/src/components/StudioHeader.tsx +118 -221
- package/src/components/StudioLeftPanels.tsx +202 -0
- package/src/components/StudioOverlays.tsx +1 -1
- package/src/components/{StudioRightPanel.tsx → StudioRightPanels.tsx} +57 -194
- package/src/components/{StudioRightPanel.types.ts → StudioRightPanels.types.ts} +4 -3
- package/src/components/StudioToast.tsx +2 -2
- package/src/components/TimelineHistoryButtons.dom.test.tsx +117 -0
- package/src/components/TimelineHistoryButtons.tsx +54 -0
- package/src/components/TimelineToolbar.test.tsx +8 -0
- package/src/components/TimelineToolbar.tsx +8 -12
- package/src/components/dock/Dock.test.tsx +274 -0
- package/src/components/dock/Dock.tsx +291 -0
- package/src/components/dock/dock.css +28 -0
- package/src/components/dock/dockAccessibility.test.ts +166 -0
- package/src/components/dock/dockAccessibility.ts +98 -0
- package/src/components/dock/dockLayout.test.ts +134 -0
- package/src/components/dock/dockLayout.ts +105 -0
- package/src/components/dock/dockLayoutSchema.ts +66 -0
- package/src/components/dock/dockLayoutStore.test.ts +28 -0
- package/src/components/dock/dockLayoutStore.ts +108 -0
- package/src/components/dock/dockTestHarness.ts +11 -0
- package/src/components/dock/panelRegistry.ts +67 -0
- package/src/components/editor/AnimationCard.tsx +4 -4
- package/src/components/editor/AnimationCardParts.tsx +6 -6
- package/src/components/editor/BlockParamsPanel.tsx +4 -4
- package/src/components/editor/BorderRadiusEditor.tsx +1 -1
- package/src/components/editor/CanvasContextMenu.tsx +1 -1
- package/src/components/editor/ComputedTweenNotice.tsx +1 -1
- package/src/components/editor/DomEditOverlay.tsx +1 -1
- package/src/components/editor/EaseCurveSection.tsx +1 -1
- package/src/components/editor/EaseParamFields.tsx +5 -5
- package/src/components/editor/FileTree.tsx +4 -4
- package/src/components/editor/FileTreeIcons.tsx +1 -1
- package/src/components/editor/FileTreeNodes.tsx +13 -13
- package/src/components/editor/InlineTextToolbar.tsx +1 -1
- package/src/components/editor/InspectorHeaderActions.tsx +3 -3
- package/src/components/editor/KeyframeDiamond.tsx +1 -1
- package/src/components/editor/KeyframeEaseList.tsx +1 -1
- package/src/components/editor/LayersPanel.test.ts +97 -1
- package/src/components/editor/LayersPanel.tsx +7 -5
- package/src/components/editor/MarqueeOverlay.tsx +1 -1
- package/src/components/editor/OffCanvasIndicators.tsx +1 -1
- package/src/components/editor/PreviewGuides.tsx +1 -1
- package/src/components/editor/PromotableControl.tsx +2 -2
- package/src/components/editor/PropertyPanelEmptyState.tsx +7 -7
- package/src/components/editor/PropertyPanelFlatHeader.tsx +2 -2
- package/src/components/editor/SnapToolbar.test.tsx +0 -3
- package/src/components/editor/SnapToolbar.tsx +2 -2
- package/src/components/editor/TimelineFxPopover.tsx +1 -1
- package/src/components/editor/TopologyLens.test.tsx +28 -0
- package/src/components/editor/TopologyLens.tsx +7 -5
- package/src/components/editor/Transform3DCube.tsx +2 -2
- package/src/components/editor/audioFxRevealTarget.ts +1 -0
- package/src/components/editor/domEditing.test.ts +1 -1
- package/src/components/editor/domEditingAgentPrompt.test.ts +8 -6
- package/src/components/editor/domEditingAgentPrompt.ts +4 -3
- package/src/components/editor/propertyPanelColor.tsx +11 -11
- package/src/components/editor/propertyPanelColorCurveGraph.tsx +1 -1
- package/src/components/editor/propertyPanelColorGradingControls.tsx +7 -9
- package/src/components/editor/propertyPanelColorGradingSection.tsx +6 -6
- package/src/components/editor/propertyPanelColorGradingSlider.tsx +5 -5
- package/src/components/editor/propertyPanelColorWheels.tsx +1 -1
- package/src/components/editor/propertyPanelCommitField.tsx +1 -1
- package/src/components/editor/propertyPanelFill.tsx +4 -4
- package/src/components/editor/propertyPanelFlatColorGradingAccessory.tsx +3 -3
- package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +7 -7
- package/src/components/editor/propertyPanelFlatEffectsSection.tsx +4 -4
- package/src/components/editor/propertyPanelFlatMediaSection.tsx +3 -3
- package/src/components/editor/propertyPanelFlatOverlaysSection.tsx +1 -1
- package/src/components/editor/propertyPanelFlatPrimitives.tsx +10 -10
- package/src/components/editor/propertyPanelFlatSelectRow.tsx +3 -3
- package/src/components/editor/propertyPanelFlatTextSection.test.tsx +8 -9
- package/src/components/editor/propertyPanelFlatTextSection.tsx +5 -5
- package/src/components/editor/propertyPanelFlatToggle.tsx +1 -1
- package/src/components/editor/propertyPanelFont.tsx +5 -5
- package/src/components/editor/propertyPanelFxCarveModule.tsx +1 -1
- package/src/components/editor/propertyPanelFxControls.test.tsx +1 -1
- package/src/components/editor/propertyPanelFxControls.tsx +6 -6
- package/src/components/editor/propertyPanelFxEqModule.tsx +2 -2
- package/src/components/editor/propertyPanelFxPresetStyle.ts +2 -2
- package/src/components/editor/propertyPanelGradingNumberField.tsx +1 -1
- package/src/components/editor/propertyPanelMediaSection.tsx +1 -1
- package/src/components/editor/propertyPanelPrimitives.tsx +7 -7
- package/src/components/editor/propertyPanelSections.tsx +8 -8
- package/src/components/editor/useColorGradingController.test.ts +38 -0
- package/src/components/editor/useColorGradingController.ts +9 -1
- package/src/components/editor/useColorGradingPreviews.test.ts +76 -0
- package/src/components/editor/useColorGradingPreviews.ts +11 -1
- package/src/components/editor/usePreviewCompositionRect.test.tsx +95 -0
- package/src/components/editor/usePreviewCompositionRect.ts +21 -0
- package/src/components/feedback/StudioFeedbackCard.tsx +7 -7
- package/src/components/feedback/feedbackTrigger.ts +0 -1
- package/src/components/nle/AssetPreviewOverlay.tsx +6 -2
- package/src/components/nle/CompositionBreadcrumb.tsx +3 -3
- package/src/components/nle/NLEContext.tsx +0 -43
- package/src/components/nle/NLEPreview.tsx +3 -3
- package/src/components/nle/PreviewOverlays.tsx +6 -6
- package/src/components/nle/PreviewPane.tsx +3 -3
- package/src/components/nle/TimelinePane.tsx +53 -67
- package/src/components/panels/SlideshowPanel.tsx +3 -3
- package/src/components/panels/SlideshowSubPanels.tsx +20 -17
- package/src/components/panels/VariablesBindElement.tsx +2 -2
- package/src/components/panels/VariablesDeclarationForm.tsx +2 -2
- package/src/components/panels/VariablesOtherCompositions.tsx +1 -1
- package/src/components/panels/VariablesPanel.tsx +4 -4
- package/src/components/panels/VariablesValueControls.tsx +2 -2
- package/src/components/pointerTargetSize.test.tsx +8 -0
- package/src/components/renders/FfmpegRequiredNotice.tsx +5 -5
- package/src/components/renders/RenderQueue.test.tsx +7 -0
- package/src/components/renders/RenderQueue.tsx +7 -7
- package/src/components/renders/RenderQueueItem.tsx +6 -6
- package/src/components/renders/RenderQueuePanel.tsx +1 -1
- package/src/components/sidebar/AssetCard.tsx +8 -8
- package/src/components/sidebar/AssetContextMenu.tsx +8 -8
- package/src/components/sidebar/AssetsTab.tsx +2 -2
- package/src/components/sidebar/AudioRow.tsx +6 -6
- package/src/components/sidebar/BlocksTab.tsx +6 -6
- package/src/components/sidebar/{LeftSidebar.masterComposition.test.tsx → CompositionsPanel.masterComposition.test.tsx} +6 -9
- package/src/components/sidebar/CompositionsPanel.tsx +15 -0
- package/src/components/sidebar/CompositionsTab.tsx +5 -5
- package/src/components/sidebar/GlobalAssetsView.tsx +1 -1
- package/src/components/sidebar/PromptPreviewModal.tsx +3 -3
- package/src/components/sidebar/SidebarLintButton.test.tsx +53 -0
- package/src/components/sidebar/SidebarLintButton.tsx +50 -0
- package/src/components/timelineToolbarStyles.ts +5 -0
- package/src/components/ui/Button.test.tsx +252 -0
- package/src/components/ui/Button.tsx +89 -126
- package/src/components/ui/IconButton.tsx +38 -0
- package/src/components/ui/Input.tsx +115 -0
- package/src/components/ui/Menu.test.tsx +374 -0
- package/src/components/ui/Menu.tsx +213 -0
- package/src/components/ui/NumberField.tsx +122 -0
- package/src/components/ui/Popover.tsx +74 -0
- package/src/components/ui/SearchInput.tsx +2 -2
- package/src/components/ui/Select.tsx +100 -0
- package/src/components/ui/Slider.tsx +153 -0
- package/src/components/ui/Tabs.test.tsx +98 -0
- package/src/components/ui/Tabs.tsx +71 -0
- package/src/components/ui/Toggle.tsx +63 -0
- package/src/components/ui/Tooltip.test.tsx +110 -0
- package/src/components/ui/Tooltip.tsx +38 -112
- package/src/components/ui/cn.test.ts +30 -0
- package/src/components/ui/cn.ts +25 -0
- package/src/components/ui/index.ts +29 -2
- package/src/components/ui/mountHost.testHelpers.ts +24 -0
- package/src/components/ui/valueControls.test.tsx +524 -0
- package/src/contexts/PanelLayoutContext.tsx +3 -57
- package/src/hooks/appHotkeysDispatch.ts +3 -4
- package/src/hooks/timelineEditPermission.ts +31 -0
- package/src/hooks/useAppHotkeys.previewForwarding.test.tsx +0 -2
- package/src/hooks/useAppHotkeys.test.ts +0 -1
- package/src/hooks/useAppHotkeys.textEditing.test.tsx +0 -3
- package/src/hooks/useAppHotkeys.ts +21 -84
- package/src/hooks/useCaptionDetection.sender.test.tsx +46 -0
- package/src/hooks/useCaptionDetection.ts +3 -1
- package/src/hooks/useDomEditPreviewSync.ts +4 -6
- package/src/hooks/useDomEditSession.ts +0 -7
- package/src/hooks/useDomEditWiring.ts +4 -9
- package/src/hooks/useEditHistoryActions.test.tsx +90 -0
- package/src/hooks/useEditHistoryActions.ts +106 -0
- package/src/hooks/useFileManager.ts +21 -123
- package/src/hooks/useLintModal.test.tsx +65 -0
- package/src/hooks/useLintModal.ts +3 -0
- package/src/hooks/usePanelLayout.test.ts +108 -296
- package/src/hooks/usePanelLayout.ts +50 -255
- package/src/hooks/usePersistentEditHistory.ts +1 -1
- package/src/hooks/useProjectFileWriter.ts +150 -0
- package/src/hooks/useRightPanelIntents.test.ts +181 -0
- package/src/hooks/useRightPanelIntents.ts +63 -0
- package/src/hooks/useSdkSession.lifecycle.test.tsx +49 -4
- package/src/hooks/useSdkSession.ts +43 -10
- package/src/hooks/useServerConnection.hashProject.test.tsx +94 -0
- package/src/hooks/useServerConnection.ts +43 -10
- package/src/hooks/useSlideshowTabState.test.ts +9 -31
- package/src/hooks/useSlideshowTabState.ts +6 -19
- package/src/hooks/useStudioContextValue.test.ts +6 -18
- package/src/hooks/useStudioContextValue.ts +5 -26
- package/src/hooks/useTimelineAssetDropOps.test.tsx +41 -1
- package/src/hooks/useTimelineAssetDropOps.ts +52 -11
- package/src/hooks/useTimelineDeleteOps.test.tsx +111 -0
- package/src/hooks/useTimelineEditing.test.tsx +322 -3
- package/src/hooks/useTimelineEditing.ts +103 -16
- package/src/hooks/useTimelineEditingTypes.ts +3 -0
- package/src/hooks/useTrackPendingTimelineEdit.test.tsx +62 -0
- package/src/hooks/useTrackPendingTimelineEdit.ts +22 -0
- package/src/hostMount.test.tsx +222 -0
- package/src/index.ts +87 -0
- package/src/main.tsx +9 -3
- package/src/player/components/AudioWaveform.tsx +1 -1
- package/src/player/components/AutomationSelectionMenu.tsx +3 -3
- package/src/player/components/ClipContextMenu.tsx +3 -3
- package/src/player/components/CompositionThumbnail.test.ts +0 -3
- package/src/player/components/CompositionThumbnail.tsx +3 -4
- package/src/player/components/EditModal.tsx +1 -1
- package/src/player/components/ImageThumbnail.tsx +1 -1
- package/src/player/components/KeyframeDiamondContextMenu.tsx +4 -4
- package/src/player/components/LayerDisclosureRow.tsx +2 -2
- package/src/player/components/PlayerControls.tsx +1 -1
- package/src/player/components/ShortcutsPanel.tsx +9 -9
- package/src/player/components/SpeedMenu.tsx +3 -5
- package/src/player/components/Timeline.tsx +3 -2
- package/src/player/components/TimelineCanvas.tsx +21 -14
- package/src/player/components/TimelineClip.tsx +0 -1
- package/src/player/components/TimelineDiamondConnectors.tsx +1 -1
- package/src/player/components/TimelineEmptyState.tsx +5 -5
- package/src/player/components/TimelineFxButton.tsx +6 -6
- package/src/player/components/TimelineGroupHeader.tsx +5 -5
- package/src/player/components/TimelineGroupLaneLabels.tsx +1 -1
- package/src/player/components/TimelineGroupRow.tsx +1 -1
- package/src/player/components/TimelineLanes.test.tsx +32 -0
- package/src/player/components/TimelineLanes.tsx +2 -5
- package/src/player/components/TimelineLayerGroupHeader.tsx +2 -2
- package/src/player/components/TimelineRuler.tsx +1 -1
- package/src/player/components/TimelineShortcutHint.tsx +1 -1
- package/src/player/components/TimelineTrackHeader.test.tsx +26 -5
- package/src/player/components/TimelineTrackHeader.tsx +2 -2
- package/src/player/components/TimelineTrackPlainHeader.tsx +3 -3
- package/src/player/components/TimelineTypes.ts +2 -1
- package/src/player/components/TrackGapContextMenu.tsx +1 -1
- package/src/player/components/VideoThumbnail.tsx +4 -2
- package/src/player/components/VolumeControl.tsx +2 -2
- package/src/player/components/timelineCallbacks.ts +8 -8
- package/src/player/components/timelineClipDragPreview.ts +51 -27
- package/src/player/components/timelineClipDragTypes.ts +3 -0
- package/src/player/components/timelineDragDrop.test.tsx +61 -1
- package/src/player/components/timelineDragDrop.ts +57 -15
- package/src/player/components/timelineLaneProps.ts +3 -0
- package/src/player/components/timelineSnapping.test.ts +27 -0
- package/src/player/components/timelineSnapping.ts +14 -1
- package/src/player/components/timelineTheme.test.ts +4 -4
- package/src/player/components/timelineTheme.ts +30 -30
- package/src/player/components/trackHeaderLabelRows.tsx +4 -4
- package/src/player/components/useTimelineClipDrag.resize.test.tsx +136 -2
- package/src/player/components/useTimelineClipDrag.ts +30 -3
- package/src/player/lib/describeClips.ts +3 -54
- package/src/player/lib/playbackShortcuts.ts +2 -0
- package/src/player/lib/timelinePerformanceDiagnostics.test.ts +3 -2
- package/src/player/lib/timelinePerformanceDiagnostics.ts +1 -1
- package/src/player/store/previewIframeStore.notify.test.tsx +75 -0
- package/src/player/store/previewIframeStore.test.tsx +29 -0
- package/src/player/store/previewIframeStore.ts +25 -0
- package/src/renderQueueExports.test.tsx +58 -0
- package/src/styles/classCandidates.test.ts +105 -0
- package/src/styles/classCandidates.ts +281 -0
- package/src/styles/hex-baseline.json +84 -0
- package/src/styles/hexRatchet.test.ts +201 -0
- package/src/styles/iconTokens.ts +20 -0
- package/src/styles/palette-reset.css +6 -0
- package/src/styles/studio.css +24 -21
- package/src/styles/styleSources.test.ts +20 -0
- package/src/styles/styleSources.ts +47 -0
- package/src/styles/tailwind-preset.shared.js +3 -0
- package/src/styles/theme.css +270 -0
- package/src/styles/theme.test.ts +183 -0
- package/src/styles/tokenGate.test.ts +109 -0
- package/src/utils/sourcePatcher.ts +1 -1
- package/src/utils/studioHelpers.test.ts +18 -0
- package/src/utils/studioHelpers.ts +11 -8
- package/src/utils/studioUiPreferences.test.ts +10 -16
- package/src/utils/studioUiPreferences.ts +7 -16
- package/src/utils/timelineDropTrackInsert.test.ts +134 -0
- package/src/utils/timelineDropTrackInsert.ts +83 -0
- package/src/utils/typingTarget.ts +4 -0
- package/dist/assets/index-n2ml4tUn.js +0 -442
- package/dist/assets/index-pO6p0H27.css +0 -1
- package/src/components/PanelTabButton.tsx +0 -31
- package/src/components/StudioLeftSidebar.tsx +0 -204
- package/src/components/nle/TimelineResizeDivider.tsx +0 -101
- package/src/components/sidebar/LeftSidebar.storage.test.ts +0 -28
- package/src/components/sidebar/LeftSidebar.tsx +0 -334
- package/src/components/storyboard/AgentChatMessageButton.test.tsx +0 -47
- package/src/components/storyboard/AgentChatMessageButton.tsx +0 -45
- package/src/components/storyboard/FramePoster.test.tsx +0 -61
- package/src/components/storyboard/FramePoster.tsx +0 -86
- package/src/components/storyboard/StoryboardDirection.tsx +0 -45
- package/src/components/storyboard/StoryboardFrameFocus.tsx +0 -412
- package/src/components/storyboard/StoryboardFrameTile.tsx +0 -117
- package/src/components/storyboard/StoryboardGrid.tsx +0 -55
- package/src/components/storyboard/StoryboardLoaded.tsx +0 -307
- package/src/components/storyboard/StoryboardReviewGuide.tsx +0 -300
- package/src/components/storyboard/StoryboardScriptPanel.tsx +0 -26
- package/src/components/storyboard/StoryboardSourceEditor.tsx +0 -231
- package/src/components/storyboard/StoryboardStatusLegend.tsx +0 -21
- package/src/components/storyboard/StoryboardView.tsx +0 -169
- package/src/components/storyboard/StoryboardViewModeGuard.test.tsx +0 -187
- package/src/components/storyboard/frameComments.test.ts +0 -95
- package/src/components/storyboard/frameComments.ts +0 -115
- package/src/components/storyboard/frameStatus.ts +0 -36
- package/src/components/storyboard/storyboardReviewStage.test.ts +0 -40
- package/src/components/storyboard/storyboardReviewStage.ts +0 -45
- package/src/components/storyboard/useFrameComments.ts +0 -98
- package/src/contexts/ViewModeContext.tsx +0 -142
- package/src/hooks/useInspectorSplitResize.ts +0 -51
- package/src/hooks/useProjectSignaturePoll.test.tsx +0 -135
- package/src/hooks/useProjectSignaturePoll.ts +0 -68
- package/src/hooks/useStoryboard.ts +0 -99
- package/src/utils/fitPanels.test.ts +0 -164
- package/src/utils/fitPanels.ts +0 -151
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { useCallback } from "react";
|
|
2
|
-
import type {
|
|
2
|
+
import type { StudioRightPanelsProps } from "./StudioRightPanels.types";
|
|
3
3
|
|
|
4
|
-
export type { StudioRightPanelProps };
|
|
5
4
|
import { PropertyPanel } from "./editor/PropertyPanel";
|
|
6
5
|
import { LayersPanel } from "./editor/LayersPanel";
|
|
7
6
|
import { CaptionPropertyPanel } from "../captions/components/CaptionPropertyPanel";
|
|
@@ -9,14 +8,18 @@ import { BlockParamsPanel } from "./editor/BlockParamsPanel";
|
|
|
9
8
|
import { RenderQueuePanel } from "./renders/RenderQueuePanel";
|
|
10
9
|
import { SlideshowPanel } from "./panels/SlideshowPanel";
|
|
11
10
|
import { VariablesPanel } from "./panels/VariablesPanel";
|
|
12
|
-
import {
|
|
11
|
+
import { Dock } from "./dock/Dock";
|
|
12
|
+
import { useDockLayoutStore } from "./dock/dockLayoutStore";
|
|
13
13
|
import type { RenderJob } from "./renders/useRenderQueue";
|
|
14
|
-
import { STUDIO_FLAT_INSPECTOR_ENABLED } from "./editor/manualEditingAvailability";
|
|
15
14
|
import { useSlideshowPersist } from "../hooks/useSlideshowPersist";
|
|
16
15
|
import { useSlideshowTabState } from "../hooks/useSlideshowTabState";
|
|
16
|
+
import {
|
|
17
|
+
useBlockParamsDismissal,
|
|
18
|
+
useCaptionDesignFocus,
|
|
19
|
+
useSlideshowDockPanel,
|
|
20
|
+
} from "../hooks/useRightPanelIntents";
|
|
17
21
|
import { DesignPanelPromoteProvider } from "./DesignPanelPromoteProvider";
|
|
18
22
|
import { useStudioPlaybackContext, useStudioShellContext } from "../contexts/StudioContext";
|
|
19
|
-
import { usePanelLayoutContext } from "../contexts/PanelLayoutContext";
|
|
20
23
|
import { useFileManagerContext } from "../contexts/FileManagerContext";
|
|
21
24
|
import { useDomEditContext } from "../contexts/DomEditContext";
|
|
22
25
|
import { usePlayerStore } from "../player";
|
|
@@ -27,14 +30,13 @@ import {
|
|
|
27
30
|
} from "./studioColorGradingScope";
|
|
28
31
|
import { timelineKeysForSelections } from "../utils/studioHelpers";
|
|
29
32
|
import { canHideSelections } from "../utils/timelineInspector";
|
|
30
|
-
import { useInspectorSplitResize } from "../hooks/useInspectorSplitResize";
|
|
31
33
|
import { useRemoveBackground } from "../hooks/useRemoveBackground";
|
|
32
34
|
|
|
33
35
|
// fallow-ignore-next-line complexity
|
|
34
|
-
export function
|
|
35
|
-
designPanelActive,
|
|
36
|
+
export function StudioRightPanels({
|
|
36
37
|
activeBlockParams,
|
|
37
38
|
onCloseBlockParams,
|
|
39
|
+
onDismissBlockParams,
|
|
38
40
|
recordingState,
|
|
39
41
|
recordingDuration,
|
|
40
42
|
onToggleRecording,
|
|
@@ -46,20 +48,7 @@ export function StudioRightPanel({
|
|
|
46
48
|
onToggleElementHidden,
|
|
47
49
|
onAutoGroupCarveSources,
|
|
48
50
|
onAddMediaOverlay,
|
|
49
|
-
}:
|
|
50
|
-
const {
|
|
51
|
-
rightWidth,
|
|
52
|
-
adjustPanelWidth,
|
|
53
|
-
rightPanelTab,
|
|
54
|
-
setRightPanelTab,
|
|
55
|
-
rightInspectorPanes,
|
|
56
|
-
toggleRightInspectorPane,
|
|
57
|
-
setExclusiveRightInspectorPane,
|
|
58
|
-
handlePanelResizeStart,
|
|
59
|
-
handlePanelResizeMove,
|
|
60
|
-
handlePanelResizeEnd,
|
|
61
|
-
} = usePanelLayoutContext();
|
|
62
|
-
|
|
51
|
+
}: StudioRightPanelsProps) {
|
|
63
52
|
const {
|
|
64
53
|
previewIframeRef,
|
|
65
54
|
projectId,
|
|
@@ -154,41 +143,20 @@ export function StudioRightPanel({
|
|
|
154
143
|
coalesceKey: activeCompPath ? `slideshow-notes:${activeCompPath}` : "slideshow-notes",
|
|
155
144
|
});
|
|
156
145
|
|
|
157
|
-
const {
|
|
158
|
-
layersPanePercent,
|
|
159
|
-
splitContainerRef,
|
|
160
|
-
handleInspectorSplitResizeStart,
|
|
161
|
-
handleInspectorSplitResizeMove,
|
|
162
|
-
handleInspectorSplitResizeEnd,
|
|
163
|
-
} = useInspectorSplitResize();
|
|
164
|
-
|
|
165
146
|
const renderJobs = renderQueue.jobs as RenderJob[];
|
|
166
|
-
const
|
|
167
|
-
|
|
147
|
+
const slideshowVisible = useDockLayoutStore((state) => state.visiblePanels.has("slideshow"));
|
|
168
148
|
const { isSlideshowComposition, slideshowScenes } = useSlideshowTabState({
|
|
169
149
|
editingFileContent: editingFile?.content,
|
|
170
150
|
previewIframeRef,
|
|
171
151
|
refreshKey,
|
|
172
|
-
|
|
173
|
-
setRightPanelTab,
|
|
152
|
+
slideshowVisible,
|
|
174
153
|
});
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
// Flat inspector: Layers always renders full-height by itself (see the
|
|
184
|
-
// render branch below), so the two panes are mutually exclusive here —
|
|
185
|
-
// otherwise both tabs could show "active" while only one actually shows.
|
|
186
|
-
if (STUDIO_FLAT_INSPECTOR_ENABLED) {
|
|
187
|
-
setExclusiveRightInspectorPane(pane);
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
toggleRightInspectorPane(pane);
|
|
191
|
-
};
|
|
154
|
+
useSlideshowDockPanel(isSlideshowComposition);
|
|
155
|
+
useBlockParamsDismissal({
|
|
156
|
+
hasBlockParams: activeBlockParams != null,
|
|
157
|
+
onDismiss: onDismissBlockParams,
|
|
158
|
+
});
|
|
159
|
+
useCaptionDesignFocus(captionEditMode);
|
|
192
160
|
|
|
193
161
|
const handleApplyColorGradingScope = useCallback(
|
|
194
162
|
async (scope: ColorGradingScope, value: string | null) =>
|
|
@@ -337,153 +305,48 @@ export function StudioRightPanel({
|
|
|
337
305
|
</DesignPanelPromoteProvider>
|
|
338
306
|
);
|
|
339
307
|
|
|
340
|
-
|
|
308
|
+
let designBody = propertyPanel;
|
|
309
|
+
if (captionEditMode) {
|
|
310
|
+
designBody = <CaptionPropertyPanel iframeRef={previewIframeRef} />;
|
|
311
|
+
} else if (activeBlockParams) {
|
|
312
|
+
designBody = (
|
|
313
|
+
<BlockParamsPanel
|
|
314
|
+
blockName={activeBlockParams.blockName}
|
|
315
|
+
blockTitle={activeBlockParams.blockTitle}
|
|
316
|
+
params={activeBlockParams.params}
|
|
317
|
+
compositionPath={activeBlockParams.compositionPath}
|
|
318
|
+
onClose={onCloseBlockParams ?? (() => {})}
|
|
319
|
+
/>
|
|
320
|
+
);
|
|
321
|
+
}
|
|
341
322
|
|
|
342
323
|
return (
|
|
343
324
|
<>
|
|
344
|
-
|
|
345
|
-
<
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
style={{ touchAction: "none" }}
|
|
352
|
-
onPointerDown={(e) => handlePanelResizeStart("right", e)}
|
|
353
|
-
onPointerMove={handlePanelResizeMove}
|
|
354
|
-
onPointerUp={handlePanelResizeEnd}
|
|
355
|
-
onPointerCancel={handlePanelResizeEnd}
|
|
356
|
-
onKeyDown={(e) => {
|
|
357
|
-
if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
|
|
358
|
-
e.preventDefault();
|
|
359
|
-
// Panel is right-anchored: ArrowLeft grows it, ArrowRight shrinks it.
|
|
360
|
-
const delta = e.key === "ArrowLeft" ? 16 : -16;
|
|
361
|
-
adjustPanelWidth("right", delta);
|
|
362
|
-
}}
|
|
363
|
-
>
|
|
364
|
-
{/* Asymmetric hit zone: 8px into the preview's p-2 gutter (the only dead
|
|
365
|
-
space), the 3px seam, 2px into the card. Stops short of the 24px WCAG
|
|
366
|
-
2.5.8 target because the next pixel each way is live. */}
|
|
367
|
-
<div className="absolute inset-y-0 -left-[8px] w-[13px]" />
|
|
368
|
-
{/* Visible hairline */}
|
|
369
|
-
<div className="absolute top-1/2 left-0 h-[52px] w-[3px] -translate-y-1/2 bg-white/12 transition-colors group-hover:bg-white/18 group-active:bg-white/24" />
|
|
370
|
-
</div>
|
|
371
|
-
<div
|
|
372
|
-
className="flex min-w-0 flex-shrink-0 flex-col overflow-hidden rounded-lg border border-neutral-800 bg-neutral-950"
|
|
373
|
-
style={{ width: rightWidth }}
|
|
325
|
+
<Dock.Panel id="design">{designBody}</Dock.Panel>
|
|
326
|
+
<Dock.Panel id="layers">
|
|
327
|
+
<LayersPanel />
|
|
328
|
+
</Dock.Panel>
|
|
329
|
+
<Dock.Panel
|
|
330
|
+
id="renders"
|
|
331
|
+
title={renderJobs.length > 0 ? `Renders (${renderJobs.length})` : undefined}
|
|
374
332
|
>
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
<PanelTabButton
|
|
393
|
-
label={renderJobs.length > 0 ? `Renders (${renderJobs.length})` : "Renders"}
|
|
394
|
-
tooltip="Render queue and exports"
|
|
395
|
-
active={rightPanelTab === "renders"}
|
|
396
|
-
onClick={() => setRightPanelTab("renders")}
|
|
397
|
-
/>
|
|
398
|
-
{isSlideshowComposition && (
|
|
399
|
-
<PanelTabButton
|
|
400
|
-
label="Slideshow"
|
|
401
|
-
tooltip="Slideshow branching editor"
|
|
402
|
-
active={rightPanelTab === "slideshow"}
|
|
403
|
-
onClick={() => setRightPanelTab("slideshow")}
|
|
404
|
-
/>
|
|
405
|
-
)}
|
|
406
|
-
<PanelTabButton
|
|
407
|
-
label="Variables"
|
|
408
|
-
tooltip="Template variables — declare, preview with values"
|
|
409
|
-
active={rightPanelTab === "variables"}
|
|
410
|
-
onClick={() => setRightPanelTab("variables")}
|
|
411
|
-
/>
|
|
412
|
-
</div>
|
|
413
|
-
<div className="min-h-0 min-w-0 flex-1 overflow-hidden">
|
|
414
|
-
{rightPanelTab === "block-params" && activeBlockParams ? (
|
|
415
|
-
<BlockParamsPanel
|
|
416
|
-
blockName={activeBlockParams.blockName}
|
|
417
|
-
blockTitle={activeBlockParams.blockTitle}
|
|
418
|
-
params={activeBlockParams.params}
|
|
419
|
-
compositionPath={activeBlockParams.compositionPath}
|
|
420
|
-
onClose={onCloseBlockParams ?? (() => {})}
|
|
421
|
-
/>
|
|
422
|
-
) : rightPanelTab === "slideshow" && isSlideshowComposition ? (
|
|
423
|
-
<SlideshowPanel
|
|
424
|
-
scenes={slideshowScenes}
|
|
425
|
-
onPersist={onPersistSlideshow}
|
|
426
|
-
onPersistNotes={onPersistSlideshowNotes}
|
|
427
|
-
/>
|
|
428
|
-
) : rightPanelTab === "variables" ? (
|
|
429
|
-
<VariablesPanel
|
|
430
|
-
sdkSession={sdkSession}
|
|
431
|
-
publishSdkSession={publishSdkSession}
|
|
432
|
-
reloadPreview={reloadPreview}
|
|
433
|
-
recordEdit={recordEdit}
|
|
434
|
-
/>
|
|
435
|
-
) : layersPaneOpen && designPaneOpen && !STUDIO_FLAT_INSPECTOR_ENABLED ? (
|
|
436
|
-
<div ref={splitContainerRef} className="flex h-full min-h-0 min-w-0 flex-col">
|
|
437
|
-
<div
|
|
438
|
-
className="min-h-[120px] overflow-hidden"
|
|
439
|
-
style={{ flexBasis: `${layersPanePercent}%`, flexShrink: 0 }}
|
|
440
|
-
>
|
|
441
|
-
<LayersPanel />
|
|
442
|
-
</div>
|
|
443
|
-
<div
|
|
444
|
-
role="separator"
|
|
445
|
-
aria-label="Resize Layers and Design panes"
|
|
446
|
-
aria-orientation="horizontal"
|
|
447
|
-
className="group flex h-2 flex-shrink-0 cursor-row-resize items-center justify-center border-y border-neutral-800 bg-neutral-900"
|
|
448
|
-
style={{ touchAction: "none" }}
|
|
449
|
-
onPointerDown={handleInspectorSplitResizeStart}
|
|
450
|
-
onPointerMove={handleInspectorSplitResizeMove}
|
|
451
|
-
onPointerUp={handleInspectorSplitResizeEnd}
|
|
452
|
-
onPointerCancel={handleInspectorSplitResizeEnd}
|
|
453
|
-
>
|
|
454
|
-
<div className="h-px w-10 rounded-full bg-white/12 transition-colors group-hover:bg-white/24 group-active:bg-studio-accent/70" />
|
|
455
|
-
</div>
|
|
456
|
-
<div className="min-h-0 flex-1 overflow-hidden">{propertyPanel}</div>
|
|
457
|
-
</div>
|
|
458
|
-
) : layersPaneOpen ? (
|
|
459
|
-
<LayersPanel />
|
|
460
|
-
) : designPaneOpen ? (
|
|
461
|
-
propertyPanel
|
|
462
|
-
) : inspectorTabActive ? (
|
|
463
|
-
// Inspector tab selected but no pane can render (panes toggled
|
|
464
|
-
// off, or inspector inactive during playback/recording): show an
|
|
465
|
-
// explanation instead of silently rendering the render queue
|
|
466
|
-
// under a highlighted inspector tab.
|
|
467
|
-
<div className="flex h-full flex-col items-center justify-center gap-3 px-6 text-center">
|
|
468
|
-
<p className="text-xs text-neutral-500">
|
|
469
|
-
Inspector is unavailable right now — select the Design or Layers pane above, or
|
|
470
|
-
pause playback/recording to inspect elements.
|
|
471
|
-
</p>
|
|
472
|
-
<button
|
|
473
|
-
type="button"
|
|
474
|
-
onClick={() => setRightPanelTab("renders")}
|
|
475
|
-
className="h-7 rounded-md border border-neutral-800 px-3 text-[11px] font-medium text-neutral-400 transition-colors hover:border-neutral-700 hover:text-neutral-200 active:scale-[0.98]"
|
|
476
|
-
>
|
|
477
|
-
Show Renders
|
|
478
|
-
</button>
|
|
479
|
-
</div>
|
|
480
|
-
) : (
|
|
481
|
-
renderQueuePanel
|
|
482
|
-
)}
|
|
483
|
-
</div>
|
|
484
|
-
</>
|
|
485
|
-
)}
|
|
486
|
-
</div>
|
|
333
|
+
<RenderQueuePanel />
|
|
334
|
+
</Dock.Panel>
|
|
335
|
+
<Dock.Panel id="variables">
|
|
336
|
+
<VariablesPanel
|
|
337
|
+
sdkSession={sdkSession}
|
|
338
|
+
publishSdkSession={publishSdkSession}
|
|
339
|
+
reloadPreview={reloadPreview}
|
|
340
|
+
recordEdit={recordEdit}
|
|
341
|
+
/>
|
|
342
|
+
</Dock.Panel>
|
|
343
|
+
<Dock.Panel id="slideshow">
|
|
344
|
+
<SlideshowPanel
|
|
345
|
+
scenes={slideshowScenes}
|
|
346
|
+
onPersist={onPersistSlideshow}
|
|
347
|
+
onPersistNotes={onPersistSlideshowNotes}
|
|
348
|
+
/>
|
|
349
|
+
</Dock.Panel>
|
|
487
350
|
</>
|
|
488
351
|
);
|
|
489
352
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Props for
|
|
2
|
+
* Props for StudioRightPanels.
|
|
3
3
|
*
|
|
4
4
|
* Kept beside the component rather than inside it: the panel is at the file-size
|
|
5
5
|
* cap, and this block is the part that changes least, so moving it keeps the
|
|
@@ -14,8 +14,7 @@ import type { UseSlideshowPersistParams } from "../hooks/useSlideshowPersist";
|
|
|
14
14
|
import type { AddMediaOverlayHandler } from "./editor/propertyPanelTypes";
|
|
15
15
|
import type { ToggleHiddenHandler } from "../utils/studioHelpers";
|
|
16
16
|
|
|
17
|
-
export interface
|
|
18
|
-
designPanelActive: boolean;
|
|
17
|
+
export interface StudioRightPanelsProps extends StudioEditPersistenceProps {
|
|
19
18
|
activeBlockParams?: {
|
|
20
19
|
blockName: string;
|
|
21
20
|
blockTitle: string;
|
|
@@ -23,6 +22,8 @@ export interface StudioRightPanelProps extends StudioEditPersistenceProps {
|
|
|
23
22
|
compositionPath: string;
|
|
24
23
|
} | null;
|
|
25
24
|
onCloseBlockParams?: () => void;
|
|
25
|
+
/** Drops the block-params view without touching the dock (selection or leaving Design). */
|
|
26
|
+
onDismissBlockParams: () => void;
|
|
26
27
|
recordingState?: "idle" | "recording" | "preview";
|
|
27
28
|
recordingDuration?: number;
|
|
28
29
|
onToggleRecording?: () => void;
|
|
@@ -30,7 +30,7 @@ export function StudioToast({ message, tone, leaving, onDismiss }: StudioToastPr
|
|
|
30
30
|
}}
|
|
31
31
|
>
|
|
32
32
|
<span
|
|
33
|
-
className={`min-w-0 break-
|
|
33
|
+
className={`min-w-0 wrap-break-word leading-5 ${isError ? "text-red-200" : "text-neutral-200"}`}
|
|
34
34
|
>
|
|
35
35
|
{message}
|
|
36
36
|
</span>
|
|
@@ -38,7 +38,7 @@ export function StudioToast({ message, tone, leaving, onDismiss }: StudioToastPr
|
|
|
38
38
|
<button
|
|
39
39
|
type="button"
|
|
40
40
|
onClick={onDismiss}
|
|
41
|
-
className="flex h-5 w-5
|
|
41
|
+
className="flex h-5 w-5 shrink-0 items-center justify-center rounded-md text-neutral-500 transition-colors hover:bg-white/10 hover:text-neutral-300"
|
|
42
42
|
aria-label="Dismiss"
|
|
43
43
|
>
|
|
44
44
|
<svg
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import { act } from "react";
|
|
4
|
+
import { afterEach, beforeEach, expect, it, vi } from "vitest";
|
|
5
|
+
import { cleanupMounted, mountHost } from "./ui/mountHost.testHelpers";
|
|
6
|
+
import { isTypingTarget } from "../utils/typingTarget";
|
|
7
|
+
import { shouldIgnorePlaybackShortcutTarget } from "../player/lib/playbackShortcuts";
|
|
8
|
+
|
|
9
|
+
const editHistory = {
|
|
10
|
+
canUndo: false,
|
|
11
|
+
canRedo: false,
|
|
12
|
+
undoLabel: undefined as string | undefined,
|
|
13
|
+
redoLabel: undefined as string | undefined,
|
|
14
|
+
};
|
|
15
|
+
const handleUndo = vi.fn();
|
|
16
|
+
const handleRedo = vi.fn();
|
|
17
|
+
const trackStudioEvent = vi.fn();
|
|
18
|
+
|
|
19
|
+
vi.mock("../contexts/StudioContext", () => ({
|
|
20
|
+
useStudioShellContext: () => ({ editHistory, handleUndo, handleRedo }),
|
|
21
|
+
}));
|
|
22
|
+
vi.mock("../utils/studioTelemetry", () => ({ trackStudioEvent }));
|
|
23
|
+
|
|
24
|
+
const { TimelineHistoryButtons } = await import("./TimelineHistoryButtons");
|
|
25
|
+
const { TimelineToolbar } = await import("./TimelineToolbar");
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
Object.assign(editHistory, {
|
|
29
|
+
canUndo: false,
|
|
30
|
+
canRedo: false,
|
|
31
|
+
undoLabel: undefined,
|
|
32
|
+
redoLabel: undefined,
|
|
33
|
+
});
|
|
34
|
+
vi.clearAllMocks();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(cleanupMounted);
|
|
38
|
+
|
|
39
|
+
const mount = mountHost;
|
|
40
|
+
|
|
41
|
+
function button(host: HTMLElement, label: string): HTMLButtonElement {
|
|
42
|
+
const el = host.querySelector<HTMLButtonElement>(`button[aria-label="${label}"]`);
|
|
43
|
+
if (!el) throw new Error(`not rendered: ${label}`);
|
|
44
|
+
return el;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function click(el: HTMLElement): void {
|
|
48
|
+
act(() => {
|
|
49
|
+
el.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
it("calls handleUndo once and tracks the event when Undo is enabled", () => {
|
|
54
|
+
editHistory.canUndo = true;
|
|
55
|
+
const host = mount(<TimelineHistoryButtons />);
|
|
56
|
+
|
|
57
|
+
click(button(host, "Undo"));
|
|
58
|
+
|
|
59
|
+
expect(handleUndo).toHaveBeenCalledTimes(1);
|
|
60
|
+
expect(handleRedo).not.toHaveBeenCalled();
|
|
61
|
+
expect(trackStudioEvent).toHaveBeenCalledWith("toolbar_action", { action: "undo" });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("calls handleRedo once and tracks the event when Redo is enabled", () => {
|
|
65
|
+
editHistory.canRedo = true;
|
|
66
|
+
const host = mount(<TimelineHistoryButtons />);
|
|
67
|
+
|
|
68
|
+
click(button(host, "Redo"));
|
|
69
|
+
|
|
70
|
+
expect(handleRedo).toHaveBeenCalledTimes(1);
|
|
71
|
+
expect(trackStudioEvent).toHaveBeenCalledWith("toolbar_action", { action: "redo" });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("disables both buttons and ignores clicks on an empty history", () => {
|
|
75
|
+
const host = mount(<TimelineHistoryButtons />);
|
|
76
|
+
|
|
77
|
+
for (const label of ["Undo", "Redo"]) {
|
|
78
|
+
const el = button(host, label);
|
|
79
|
+
expect(el.disabled).toBe(true);
|
|
80
|
+
expect(el.className).toContain("cursor-not-allowed");
|
|
81
|
+
click(el);
|
|
82
|
+
}
|
|
83
|
+
expect(handleUndo).not.toHaveBeenCalled();
|
|
84
|
+
expect(handleRedo).not.toHaveBeenCalled();
|
|
85
|
+
expect(trackStudioEvent).not.toHaveBeenCalled();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("leaves enabled buttons without the disabled attribute", () => {
|
|
89
|
+
editHistory.canUndo = true;
|
|
90
|
+
const el = button(mount(<TimelineHistoryButtons />), "Undo");
|
|
91
|
+
|
|
92
|
+
expect(el.disabled).toBe(false);
|
|
93
|
+
expect(el.className).not.toContain("cursor-not-allowed");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("orders the toolbar Undo, Redo, Select, Razor", () => {
|
|
97
|
+
const host = mount(<TimelineToolbar />);
|
|
98
|
+
const labels = Array.from(host.querySelectorAll("button"))
|
|
99
|
+
.map((b) => b.getAttribute("aria-label"))
|
|
100
|
+
.filter((l) => l !== null)
|
|
101
|
+
.slice(0, 4);
|
|
102
|
+
|
|
103
|
+
expect(labels).toEqual(["Undo", "Redo", "Selection tool", "Razor tool"]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("classifies Undo and Redo for the hotkey filters at their new location (KTD13)", () => {
|
|
107
|
+
// The header's own version of this check dropped Undo/Redo when they moved
|
|
108
|
+
// here; a <button> is never a typing target and is always claimed by the
|
|
109
|
+
// playback filter, same as every other toolbar tool.
|
|
110
|
+
const host = mount(<TimelineHistoryButtons />);
|
|
111
|
+
|
|
112
|
+
for (const label of ["Undo", "Redo"]) {
|
|
113
|
+
const el = button(host, label);
|
|
114
|
+
expect(isTypingTarget(el), label).toBe(false);
|
|
115
|
+
expect(shouldIgnorePlaybackShortcutTarget(el), label).toBe(true);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useStudioShellContext } from "../contexts/StudioContext";
|
|
2
|
+
import { RotateCcw, RotateCw } from "../icons/SystemIcons";
|
|
3
|
+
import { trackStudioEvent } from "../utils/studioTelemetry";
|
|
4
|
+
import { historyTooltipLabel } from "../utils/studioHelpers";
|
|
5
|
+
import { flatDisabled, flatIdle } from "./timelineToolbarStyles";
|
|
6
|
+
import { Tooltip } from "./ui";
|
|
7
|
+
|
|
8
|
+
interface HistoryButtonProps {
|
|
9
|
+
action: "undo" | "redo";
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
label: string | undefined;
|
|
12
|
+
onClick: () => Promise<void> | void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function HistoryButton({ action, enabled, label, onClick }: HistoryButtonProps) {
|
|
16
|
+
const Icon = action === "undo" ? RotateCcw : RotateCw;
|
|
17
|
+
return (
|
|
18
|
+
<Tooltip label={historyTooltipLabel(action, label)}>
|
|
19
|
+
<button
|
|
20
|
+
type="button"
|
|
21
|
+
aria-label={action === "undo" ? "Undo" : "Redo"}
|
|
22
|
+
disabled={!enabled}
|
|
23
|
+
className={enabled ? flatIdle : flatDisabled}
|
|
24
|
+
onClick={() => {
|
|
25
|
+
trackStudioEvent("toolbar_action", { action });
|
|
26
|
+
void onClick();
|
|
27
|
+
}}
|
|
28
|
+
>
|
|
29
|
+
<Icon size={16} />
|
|
30
|
+
</button>
|
|
31
|
+
</Tooltip>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Undo and Redo, on the one edit-history path the shell already owns. */
|
|
36
|
+
export function TimelineHistoryButtons() {
|
|
37
|
+
const { editHistory, handleUndo, handleRedo } = useStudioShellContext();
|
|
38
|
+
return (
|
|
39
|
+
<>
|
|
40
|
+
<HistoryButton
|
|
41
|
+
action="undo"
|
|
42
|
+
enabled={editHistory.canUndo}
|
|
43
|
+
label={editHistory.undoLabel}
|
|
44
|
+
onClick={handleUndo}
|
|
45
|
+
/>
|
|
46
|
+
<HistoryButton
|
|
47
|
+
action="redo"
|
|
48
|
+
enabled={editHistory.canRedo}
|
|
49
|
+
label={editHistory.redoLabel}
|
|
50
|
+
onClick={handleRedo}
|
|
51
|
+
/>
|
|
52
|
+
</>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -8,6 +8,14 @@ import { usePlayerStore } from "../player/store/playerStore";
|
|
|
8
8
|
import { makeSelection } from "../hooks/domSelectionTestHarness";
|
|
9
9
|
import { TimelineToolbar } from "./TimelineToolbar";
|
|
10
10
|
|
|
11
|
+
vi.mock("../contexts/StudioContext", () => ({
|
|
12
|
+
useStudioShellContext: () => ({
|
|
13
|
+
editHistory: { canUndo: false, canRedo: false },
|
|
14
|
+
handleUndo: vi.fn(),
|
|
15
|
+
handleRedo: vi.fn(),
|
|
16
|
+
}),
|
|
17
|
+
}));
|
|
18
|
+
|
|
11
19
|
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
12
20
|
|
|
13
21
|
afterEach(() => {
|
|
@@ -22,6 +22,8 @@ import {
|
|
|
22
22
|
import { useTimelineZoom } from "../player/components/useTimelineZoom";
|
|
23
23
|
import { usePlayerStore, type TimelineElement } from "../player";
|
|
24
24
|
import { Tooltip } from "./ui";
|
|
25
|
+
import { flatActive, flatBtn, flatDisabled, flatIdle } from "./timelineToolbarStyles";
|
|
26
|
+
import { TimelineHistoryButtons } from "./TimelineHistoryButtons";
|
|
25
27
|
import { Scissors } from "../icons/SystemIcons";
|
|
26
28
|
import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
|
|
27
29
|
import type { DomEditSelection } from "./editor/domEditingTypes";
|
|
@@ -186,19 +188,13 @@ export function TimelineToolbar({ domEditSession, onSplitElement }: TimelineTool
|
|
|
186
188
|
return () => window.removeEventListener("keydown", onKeyDown);
|
|
187
189
|
}, []);
|
|
188
190
|
|
|
189
|
-
// CapCut-flat icon buttons: no per-button border/box chrome — a transparent
|
|
190
|
-
// 28px hit area with a subtle rounded hover wash, consistent 16px glyphs.
|
|
191
|
-
const flatBtn = "flex h-7 w-7 items-center justify-center rounded-md transition-colors";
|
|
192
|
-
const flatIdle = `${flatBtn} text-neutral-400 hover:bg-white/[0.06] hover:text-neutral-200 active:scale-[0.98]`;
|
|
193
|
-
const flatActive = `${flatBtn} bg-white/[0.08] text-neutral-100 active:scale-[0.98]`;
|
|
194
|
-
const flatDisabled = `${flatBtn} text-neutral-700 cursor-not-allowed`;
|
|
195
|
-
|
|
196
191
|
return (
|
|
197
192
|
// The "TIMELINE" label is dropped for CapCut-like density — the pane's
|
|
198
193
|
// position (tracks right below) makes it self-evident.
|
|
199
194
|
<div className="border-b border-neutral-800/60">
|
|
200
195
|
<div className="flex items-center justify-between px-2 py-0.5">
|
|
201
196
|
<div className="flex items-center gap-0.5">
|
|
197
|
+
<TimelineHistoryButtons />
|
|
202
198
|
<Tooltip label="Selection tool (V)">
|
|
203
199
|
<button
|
|
204
200
|
type="button"
|
|
@@ -297,7 +293,7 @@ export function TimelineToolbar({ domEditSession, onSplitElement }: TimelineTool
|
|
|
297
293
|
className={
|
|
298
294
|
!onToggleKeyframe
|
|
299
295
|
? flatDisabled
|
|
300
|
-
: `${flatBtn} active:scale-[0.98] hover:bg-white/
|
|
296
|
+
: `${flatBtn} active:scale-[0.98] hover:bg-white/6 ${
|
|
301
297
|
keyframeState === "active"
|
|
302
298
|
? "text-studio-accent"
|
|
303
299
|
: keyframeState === "inactive"
|
|
@@ -332,7 +328,7 @@ export function TimelineToolbar({ domEditSession, onSplitElement }: TimelineTool
|
|
|
332
328
|
onClick={() => setAutoKeyframeEnabled(!autoKeyframeEnabled)}
|
|
333
329
|
aria-label="Auto-record manual edits as keyframes"
|
|
334
330
|
aria-pressed={autoKeyframeEnabled}
|
|
335
|
-
className={`${flatBtn} active:scale-[0.98] hover:bg-white/
|
|
331
|
+
className={`${flatBtn} active:scale-[0.98] hover:bg-white/6 ${
|
|
336
332
|
autoKeyframeEnabled
|
|
337
333
|
? "text-red-400 hover:text-red-300"
|
|
338
334
|
: "text-neutral-600 hover:text-neutral-400"
|
|
@@ -429,7 +425,7 @@ export function TimelineToolbar({ domEditSession, onSplitElement }: TimelineTool
|
|
|
429
425
|
}}
|
|
430
426
|
className={
|
|
431
427
|
canAdd
|
|
432
|
-
? `${flatBtn} text-neutral-400 hover:bg-white/
|
|
428
|
+
? `${flatBtn} text-neutral-400 hover:bg-white/6 hover:text-[#22c55e] active:scale-[0.98]`
|
|
433
429
|
: flatDisabled
|
|
434
430
|
}
|
|
435
431
|
>
|
|
@@ -467,7 +463,7 @@ export function TimelineToolbar({ domEditSession, onSplitElement }: TimelineTool
|
|
|
467
463
|
className={`h-7 px-2 rounded-md text-[11px] font-medium transition-colors ${
|
|
468
464
|
thumbnailsVisible
|
|
469
465
|
? "bg-studio-accent/10 text-studio-accent"
|
|
470
|
-
: "text-neutral-400 hover:bg-white/
|
|
466
|
+
: "text-neutral-400 hover:bg-white/6 hover:text-neutral-200"
|
|
471
467
|
}`}
|
|
472
468
|
>
|
|
473
469
|
<Image size={16} aria-hidden="true" />
|
|
@@ -480,7 +476,7 @@ export function TimelineToolbar({ domEditSession, onSplitElement }: TimelineTool
|
|
|
480
476
|
className={`h-7 px-2 rounded-md text-[11px] font-medium transition-colors ${
|
|
481
477
|
zoomMode === "fit"
|
|
482
478
|
? "bg-studio-accent/10 text-studio-accent"
|
|
483
|
-
: "text-neutral-400 hover:bg-white/
|
|
479
|
+
: "text-neutral-400 hover:bg-white/6 hover:text-neutral-200"
|
|
484
480
|
}`}
|
|
485
481
|
>
|
|
486
482
|
Fit
|