@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
|
@@ -88,7 +88,7 @@ export function AutomationToggle({
|
|
|
88
88
|
<Tooltip label={automated ? "Automated" : "Automate"}>
|
|
89
89
|
<button
|
|
90
90
|
type="button"
|
|
91
|
-
className={`hf-fx-automate w-[16px]
|
|
91
|
+
className={`hf-fx-automate w-[16px] shrink-0 rounded-[3px] border font-mono text-[9px] leading-none ${
|
|
92
92
|
automated
|
|
93
93
|
? "border-panel-accent text-panel-accent"
|
|
94
94
|
: "border-panel-border-input text-panel-text-2 hover:text-panel-text-0"
|
|
@@ -192,10 +192,10 @@ export function FxParamRow({
|
|
|
192
192
|
big the space is" — so 86px of truncation left "How big the sp…", and
|
|
193
193
|
three rows of that read as the same word four times. A title only
|
|
194
194
|
answers it on hover, one row at a time; wrapping answers it for the
|
|
195
|
-
whole column at rest. `break-
|
|
195
|
+
whole column at rest. `wrap-break-word` so a long single token breaks
|
|
196
196
|
instead of widening the column. The row keeps `title={param.hint}`:
|
|
197
197
|
the name and the explanation are different questions. */}
|
|
198
|
-
<span className="hf-fx-label w-[86px]
|
|
198
|
+
<span className="hf-fx-label w-[86px] shrink-0 wrap-break-word text-[10px] leading-tight text-panel-text-2">
|
|
199
199
|
{param.label}
|
|
200
200
|
</span>
|
|
201
201
|
<select
|
|
@@ -236,7 +236,7 @@ export function FxParamRow({
|
|
|
236
236
|
>
|
|
237
237
|
{/* See the enum row above for why the name wraps instead of truncating. */}
|
|
238
238
|
<span
|
|
239
|
-
className={`hf-fx-label w-[86px]
|
|
239
|
+
className={`hf-fx-label w-[86px] shrink-0 wrap-break-word text-[10px] leading-tight ${
|
|
240
240
|
automated ? "text-panel-accent" : "text-panel-text-2"
|
|
241
241
|
}`}
|
|
242
242
|
>
|
|
@@ -258,7 +258,7 @@ export function FxParamRow({
|
|
|
258
258
|
onBlur={commit}
|
|
259
259
|
/>
|
|
260
260
|
<input
|
|
261
|
-
className="hf-fx-number w-[54px]
|
|
261
|
+
className="hf-fx-number w-[54px] shrink-0 rounded-[3px] bg-panel-surface px-1 py-0.5 text-right font-mono text-[10px] text-panel-text-0"
|
|
262
262
|
type="number"
|
|
263
263
|
min={param.min}
|
|
264
264
|
max={param.max}
|
|
@@ -287,7 +287,7 @@ export function FxParamRow({
|
|
|
287
287
|
}}
|
|
288
288
|
/>
|
|
289
289
|
{param.unit ? (
|
|
290
|
-
<span className="hf-fx-unit w-[22px]
|
|
290
|
+
<span className="hf-fx-unit w-[22px] shrink-0 font-mono text-[9px] text-panel-text-2">
|
|
291
291
|
{param.unit}
|
|
292
292
|
</span>
|
|
293
293
|
) : null}
|
|
@@ -87,7 +87,7 @@ function Fader({
|
|
|
87
87
|
<div className="relative h-[74px] w-full">
|
|
88
88
|
<span className="pointer-events-none absolute inset-x-0 top-1/2 h-px bg-panel-border-input" />
|
|
89
89
|
<span
|
|
90
|
-
className="pointer-events-none absolute left-1/2 w-[3px] -translate-x-1/2 rounded-
|
|
90
|
+
className="pointer-events-none absolute left-1/2 w-[3px] -translate-x-1/2 rounded-xs bg-panel-accent"
|
|
91
91
|
style={
|
|
92
92
|
// `value`, not `band.gain`: mid-drag across zero the fill would
|
|
93
93
|
// otherwise keep pointing the way it started.
|
|
@@ -95,7 +95,7 @@ function Fader({
|
|
|
95
95
|
}
|
|
96
96
|
/>
|
|
97
97
|
<input
|
|
98
|
-
className="hf-fx-eq-fader absolute left-1/2 h-[19px] w-[74px] -translate-x-1/2 -translate-y-1/2 rotate-
|
|
98
|
+
className="hf-fx-eq-fader absolute left-1/2 h-[19px] w-[74px] -translate-x-1/2 -translate-y-1/2 -rotate-90 cursor-ns-resize appearance-none bg-transparent"
|
|
99
99
|
style={{ top: "50%" }}
|
|
100
100
|
type="range"
|
|
101
101
|
min={-HF_AUDIO_EQ_RANGE_DB}
|
|
@@ -146,7 +146,7 @@ export const FX_PRESET_STYLE: Record<string, FxPresetStyle> = {
|
|
|
146
146
|
},
|
|
147
147
|
// The name is a joke and the type is in on it.
|
|
148
148
|
"doofus-worble": {
|
|
149
|
-
type: "text-[16px] tracking-
|
|
149
|
+
type: "text-[16px] tracking-widest",
|
|
150
150
|
color: "hsl(288, 85%, 72%)",
|
|
151
151
|
family: FACE.theatrical,
|
|
152
152
|
},
|
|
@@ -164,7 +164,7 @@ export const FX_PRESET_STYLE: Record<string, FxPresetStyle> = {
|
|
|
164
164
|
},
|
|
165
165
|
// Rough and too close — heavy industrial caps, growled rather than shouted.
|
|
166
166
|
monster: {
|
|
167
|
-
type: "text-[15px] font-black uppercase tracking-
|
|
167
|
+
type: "text-[15px] font-black uppercase tracking-wider",
|
|
168
168
|
color: "hsl(350, 78%, 64%)",
|
|
169
169
|
family: FACE.condensed,
|
|
170
170
|
},
|
|
@@ -109,7 +109,7 @@ export function GradingNumberField({
|
|
|
109
109
|
event.currentTarget.blur();
|
|
110
110
|
}
|
|
111
111
|
}}
|
|
112
|
-
className={`border-b border-panel-border-input/50 bg-transparent py-0.5 text-right font-mono text-[9px] text-panel-text-2 outline-
|
|
112
|
+
className={`border-b border-panel-border-input/50 bg-transparent py-0.5 text-right font-mono text-[9px] text-panel-text-2 outline-hidden focus:border-panel-accent disabled:opacity-40 ${inputClassName}`}
|
|
113
113
|
/>
|
|
114
114
|
</label>
|
|
115
115
|
);
|
|
@@ -182,7 +182,7 @@ export function MediaSection({
|
|
|
182
182
|
event.stopPropagation();
|
|
183
183
|
void runBackgroundRemoval();
|
|
184
184
|
}}
|
|
185
|
-
className="flex h-8
|
|
185
|
+
className="flex h-8 shrink-0 items-center gap-1.5 rounded-md bg-panel-input px-2.5 text-[11px] font-medium text-panel-text-2 transition-colors hover:bg-panel-hover hover:text-panel-text-1 disabled:cursor-not-allowed disabled:opacity-50"
|
|
186
186
|
title={
|
|
187
187
|
canRemoveBackground
|
|
188
188
|
? "Remove background and save a transparent asset"
|
|
@@ -70,21 +70,21 @@ export function MetricField({
|
|
|
70
70
|
scrub && !disabled
|
|
71
71
|
? ({
|
|
72
72
|
className:
|
|
73
|
-
"
|
|
73
|
+
"shrink-0 text-[11px] font-medium text-neutral-500 cursor-ew-resize select-none",
|
|
74
74
|
onPointerDown: handleScrubPointerDown,
|
|
75
75
|
onPointerMove: handleScrubPointerMove,
|
|
76
76
|
onPointerUp: handleScrubPointerUp,
|
|
77
77
|
onPointerCancel: handleScrubPointerUp,
|
|
78
78
|
onLostPointerCapture: handleScrubPointerUp,
|
|
79
79
|
} as const)
|
|
80
|
-
: ({ className: "
|
|
80
|
+
: ({ className: "shrink-0 text-[11px] font-medium text-neutral-500" } as const);
|
|
81
81
|
|
|
82
82
|
return (
|
|
83
83
|
<div className={FIELD} title={tooltip}>
|
|
84
84
|
<div className="flex min-w-0 items-center gap-3">
|
|
85
85
|
<span {...scrubProps}>{label}</span>
|
|
86
86
|
<CommitField value={value} disabled={disabled} liveCommit={liveCommit} onCommit={commit} />
|
|
87
|
-
{suffix && <span className="
|
|
87
|
+
{suffix && <span className="shrink-0 text-[10px] text-neutral-600">{suffix}</span>}
|
|
88
88
|
</div>
|
|
89
89
|
</div>
|
|
90
90
|
);
|
|
@@ -262,7 +262,7 @@ export function SelectField({
|
|
|
262
262
|
const renderedOptions = value && !options.includes(value) ? [value, ...options] : options;
|
|
263
263
|
return (
|
|
264
264
|
<label className={`${FIELD} flex items-center gap-3`}>
|
|
265
|
-
<span className="
|
|
265
|
+
<span className="shrink-0 text-[11px] font-medium text-neutral-500">{label}</span>
|
|
266
266
|
<select
|
|
267
267
|
value={value}
|
|
268
268
|
disabled={disabled}
|
|
@@ -270,7 +270,7 @@ export function SelectField({
|
|
|
270
270
|
track("select", label);
|
|
271
271
|
onChange(e.target.value);
|
|
272
272
|
}}
|
|
273
|
-
className="min-w-0 w-full appearance-none bg-transparent text-[11px] font-medium text-neutral-100 outline-
|
|
273
|
+
className="min-w-0 w-full appearance-none bg-transparent text-[11px] font-medium text-neutral-100 outline-hidden disabled:cursor-not-allowed disabled:text-neutral-600"
|
|
274
274
|
>
|
|
275
275
|
{renderedOptions.map((option) => (
|
|
276
276
|
<option key={option} value={option}>
|
|
@@ -302,7 +302,7 @@ export function Section({
|
|
|
302
302
|
height="10"
|
|
303
303
|
viewBox="0 0 10 10"
|
|
304
304
|
fill="currentColor"
|
|
305
|
-
className={`
|
|
305
|
+
className={`shrink-0 text-panel-text-5 transition-transform duration-150 ${
|
|
306
306
|
collapsed ? "-rotate-90" : ""
|
|
307
307
|
}`}
|
|
308
308
|
>
|
|
@@ -324,7 +324,7 @@ export function Section({
|
|
|
324
324
|
<h3 className="text-[12px] font-semibold text-panel-text-1">{title}</h3>
|
|
325
325
|
{collapseIcon}
|
|
326
326
|
</button>
|
|
327
|
-
{accessory && <div className="flex
|
|
327
|
+
{accessory && <div className="flex shrink-0 items-center">{accessory}</div>}
|
|
328
328
|
</div>
|
|
329
329
|
{!collapsed && <div className="px-4 pb-3">{children}</div>}
|
|
330
330
|
</section>
|
|
@@ -147,7 +147,7 @@ export function TextAreaField({
|
|
|
147
147
|
onFocus={handleFocus}
|
|
148
148
|
onChange={handleChange}
|
|
149
149
|
onBlur={handleBlur}
|
|
150
|
-
className="
|
|
150
|
+
className="field-sizing-content max-h-[40vh] min-h-12 w-full resize-y overflow-x-hidden overflow-y-auto bg-transparent font-mono text-[11px] leading-normal text-panel-text-0 outline-hidden disabled:cursor-not-allowed disabled:text-panel-text-4"
|
|
151
151
|
/>
|
|
152
152
|
</div>
|
|
153
153
|
);
|
|
@@ -165,7 +165,7 @@ export function TextAreaField({
|
|
|
165
165
|
onFocus={handleFocus}
|
|
166
166
|
onChange={handleChange}
|
|
167
167
|
onBlur={handleBlur}
|
|
168
|
-
className="
|
|
168
|
+
className="field-sizing-content max-h-[40vh] min-h-20 w-full resize-y overflow-x-hidden overflow-y-auto bg-transparent text-[11px] font-medium text-neutral-100 outline-hidden disabled:cursor-not-allowed disabled:text-neutral-600"
|
|
169
169
|
/>
|
|
170
170
|
</div>
|
|
171
171
|
</label>
|
|
@@ -189,7 +189,7 @@ function FontWeightField({
|
|
|
189
189
|
return (
|
|
190
190
|
<div className={FIELD}>
|
|
191
191
|
<div className="flex min-w-0 items-center gap-3">
|
|
192
|
-
<span className="
|
|
192
|
+
<span className="shrink-0 text-[11px] font-medium text-neutral-500">Weight</span>
|
|
193
193
|
<select
|
|
194
194
|
value={value}
|
|
195
195
|
disabled={disabled}
|
|
@@ -197,7 +197,7 @@ function FontWeightField({
|
|
|
197
197
|
track("select", "Weight");
|
|
198
198
|
onCommit(e.target.value);
|
|
199
199
|
}}
|
|
200
|
-
className="min-w-0 w-full appearance-none bg-transparent text-[11px] font-medium text-neutral-100 outline-
|
|
200
|
+
className="min-w-0 w-full appearance-none bg-transparent text-[11px] font-medium text-neutral-100 outline-hidden disabled:cursor-not-allowed disabled:text-neutral-600"
|
|
201
201
|
>
|
|
202
202
|
{displayOptions.map((o) => (
|
|
203
203
|
<option key={o} value={o}>
|
|
@@ -323,7 +323,7 @@ function TextFieldEditor({
|
|
|
323
323
|
track("button", "Remove text field");
|
|
324
324
|
onRemoveTextField(field.key);
|
|
325
325
|
}}
|
|
326
|
-
className="inline-flex h-7
|
|
326
|
+
className="inline-flex h-7 shrink-0 items-center rounded-lg border border-neutral-700 bg-neutral-950 px-2.5 text-[11px] font-medium text-neutral-300 transition-colors hover:border-neutral-600 hover:text-white"
|
|
327
327
|
>
|
|
328
328
|
Remove
|
|
329
329
|
</button>
|
|
@@ -476,7 +476,7 @@ export function TextSection({
|
|
|
476
476
|
}}
|
|
477
477
|
className="inline-flex h-7 max-w-full items-center gap-1.5 rounded-lg border border-neutral-700 bg-neutral-950 px-2.5 text-[11px] font-medium text-neutral-300 transition-colors hover:border-neutral-600 hover:text-white"
|
|
478
478
|
>
|
|
479
|
-
<Plus size={12} className="
|
|
479
|
+
<Plus size={12} className="shrink-0" />
|
|
480
480
|
<span className="truncate">Add text</span>
|
|
481
481
|
</button>
|
|
482
482
|
</div>
|
|
@@ -497,14 +497,14 @@ export function TextSection({
|
|
|
497
497
|
<div className="flex min-w-0 items-center justify-between gap-2">
|
|
498
498
|
<div className="flex min-w-0 items-center gap-2">
|
|
499
499
|
<span
|
|
500
|
-
className="h-4 w-4
|
|
500
|
+
className="h-4 w-4 shrink-0 rounded-sm border border-neutral-700 bg-neutral-950"
|
|
501
501
|
style={{ backgroundColor: getTextFieldColor(field, styles) }}
|
|
502
502
|
/>
|
|
503
503
|
<span className="min-w-0 truncate text-[11px] font-medium text-neutral-100">
|
|
504
504
|
{formatTextFieldPreview(field.value) || `Text ${index + 1}`}
|
|
505
505
|
</span>
|
|
506
506
|
</div>
|
|
507
|
-
<span className="
|
|
507
|
+
<span className="shrink-0 rounded-md border border-neutral-700 bg-neutral-950 px-1.5 py-0.5 text-[10px] text-neutral-500">
|
|
508
508
|
{field.tagName}
|
|
509
509
|
</span>
|
|
510
510
|
</div>
|
|
@@ -4,6 +4,8 @@ import React, { act } from "react";
|
|
|
4
4
|
import { createRoot } from "react-dom/client";
|
|
5
5
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
6
6
|
import { normalizeHfColorGrading } from "@hyperframes/core/color-grading";
|
|
7
|
+
import { runtimeProtocolMetadata } from "@hyperframes/core/runtime/protocol";
|
|
8
|
+
import { usePreviewIframeStore } from "../../player/store/previewIframeStore";
|
|
7
9
|
import { useColorGradingController } from "./useColorGradingController";
|
|
8
10
|
import type { DomEditSelection } from "./domEditing";
|
|
9
11
|
|
|
@@ -22,6 +24,7 @@ function cleanStudioGrading() {
|
|
|
22
24
|
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
23
25
|
|
|
24
26
|
afterEach(() => {
|
|
27
|
+
usePreviewIframeStore.setState({ iframe: null });
|
|
25
28
|
document.body.innerHTML = "";
|
|
26
29
|
});
|
|
27
30
|
|
|
@@ -737,3 +740,38 @@ describe("useColorGradingController", () => {
|
|
|
737
740
|
vi.unstubAllGlobals();
|
|
738
741
|
});
|
|
739
742
|
});
|
|
743
|
+
|
|
744
|
+
describe("useColorGradingController after the preview iframe is replaced", () => {
|
|
745
|
+
it("replays the grade to the new iframe on its ready message and ignores the old one", () => {
|
|
746
|
+
vi.useFakeTimers();
|
|
747
|
+
const a = createPreviewFrame();
|
|
748
|
+
const b = createPreviewFrame();
|
|
749
|
+
const ref = { current: a.iframe as HTMLIFrameElement | null };
|
|
750
|
+
usePreviewIframeStore.getState().setIframe(a.iframe);
|
|
751
|
+
const initialElement = makeElement({
|
|
752
|
+
dataAttributes: { "color-grading": JSON.stringify({ preset: "bright-pop", intensity: 1 }) },
|
|
753
|
+
});
|
|
754
|
+
const { root } = renderHook(vi.fn(), initialElement, ref);
|
|
755
|
+
act(() => vi.advanceTimersByTime(100));
|
|
756
|
+
|
|
757
|
+
const postToA = vi.spyOn(a.contentWindow, "postMessage");
|
|
758
|
+
const postToB = vi.spyOn(b.contentWindow, "postMessage");
|
|
759
|
+
act(() => {
|
|
760
|
+
ref.current = b.iframe;
|
|
761
|
+
usePreviewIframeStore.getState().setIframe(b.iframe);
|
|
762
|
+
});
|
|
763
|
+
const ready = { source: "hf-preview", type: "ready", ...runtimeProtocolMetadata(30) };
|
|
764
|
+
act(() => {
|
|
765
|
+
window.dispatchEvent(new MessageEvent("message", { data: ready, source: a.contentWindow }));
|
|
766
|
+
});
|
|
767
|
+
expect(colorGradingMessages(postToB.mock.calls)).toHaveLength(0);
|
|
768
|
+
act(() => {
|
|
769
|
+
window.dispatchEvent(new MessageEvent("message", { data: ready, source: b.contentWindow }));
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
expect(colorGradingMessages(postToB.mock.calls)).toHaveLength(1);
|
|
773
|
+
expect(postToA).not.toHaveBeenCalled();
|
|
774
|
+
act(() => root.unmount());
|
|
775
|
+
vi.useRealTimers();
|
|
776
|
+
});
|
|
777
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useLivePreviewIframe } from "../../player/store/previewIframeStore";
|
|
1
2
|
import { useCallback, useEffect, useMemo, useRef, useState, type RefObject } from "react";
|
|
2
3
|
import {
|
|
3
4
|
HF_COLOR_GRADING_ATTR,
|
|
@@ -430,6 +431,7 @@ export function useColorGradingController({
|
|
|
430
431
|
[previewIframeRef, target],
|
|
431
432
|
);
|
|
432
433
|
|
|
434
|
+
const livePreviewIframe = useLivePreviewIframe();
|
|
433
435
|
useEffect(() => {
|
|
434
436
|
const iframe = previewIframeRef?.current;
|
|
435
437
|
if (!iframe) return;
|
|
@@ -455,7 +457,13 @@ export function useColorGradingController({
|
|
|
455
457
|
window.removeEventListener("message", onMessage);
|
|
456
458
|
window.clearTimeout(timer);
|
|
457
459
|
};
|
|
458
|
-
}, [
|
|
460
|
+
}, [
|
|
461
|
+
postColorGrading,
|
|
462
|
+
postCompare,
|
|
463
|
+
previewIframeRef,
|
|
464
|
+
scheduleRuntimeStatusRefresh,
|
|
465
|
+
livePreviewIframe,
|
|
466
|
+
]);
|
|
459
467
|
|
|
460
468
|
useEffect(
|
|
461
469
|
() => () => {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import React, { act } from "react";
|
|
4
|
+
import { createRoot } from "react-dom/client";
|
|
5
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { normalizeHfColorGrading } from "@hyperframes/core/color-grading";
|
|
7
|
+
import { usePreviewIframeStore } from "../../player/store/previewIframeStore";
|
|
8
|
+
import { useColorGradingPreviews } from "./useColorGradingPreviews";
|
|
9
|
+
|
|
10
|
+
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
usePreviewIframeStore.setState({ iframe: null });
|
|
14
|
+
document.body.innerHTML = "";
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
function createFrame() {
|
|
18
|
+
return document.body.appendChild(document.createElement("iframe"));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function installRenderer(iframe: HTMLIFrameElement) {
|
|
22
|
+
const renderPreviews = vi.fn().mockResolvedValue({
|
|
23
|
+
width: 160,
|
|
24
|
+
height: 90,
|
|
25
|
+
images: [{ id: "bright-pop", dataUrl: "data:image/png;base64,x" }],
|
|
26
|
+
});
|
|
27
|
+
(iframe.contentWindow as unknown as { __hf: unknown }).__hf = {
|
|
28
|
+
colorGrading: { renderPreviews },
|
|
29
|
+
};
|
|
30
|
+
return renderPreviews;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("useColorGradingPreviews after the preview iframe is replaced", () => {
|
|
34
|
+
it("requests preset previews from the new iframe's runtime", async () => {
|
|
35
|
+
vi.useFakeTimers();
|
|
36
|
+
const a = createFrame();
|
|
37
|
+
const b = createFrame();
|
|
38
|
+
const renderOnB = installRenderer(b);
|
|
39
|
+
const ref = { current: a as HTMLIFrameElement | null };
|
|
40
|
+
usePreviewIframeStore.getState().setIframe(a);
|
|
41
|
+
const grading = normalizeHfColorGrading({ preset: "neutral" });
|
|
42
|
+
if (!grading) throw new Error("expected neutral grading");
|
|
43
|
+
const gradingRef = { current: grading };
|
|
44
|
+
const target = { selector: "#v" } as never;
|
|
45
|
+
const postColorGrading = vi.fn();
|
|
46
|
+
let requestPresetPreviews: () => void = () => {};
|
|
47
|
+
function Host() {
|
|
48
|
+
requestPresetPreviews = useColorGradingPreviews({
|
|
49
|
+
grading,
|
|
50
|
+
gradingRef,
|
|
51
|
+
identityKey: "k",
|
|
52
|
+
target,
|
|
53
|
+
previewIframeRef: ref,
|
|
54
|
+
postColorGrading,
|
|
55
|
+
}).requestPresetPreviews;
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const root = createRoot(document.body.appendChild(document.createElement("div")));
|
|
59
|
+
act(() => root.render(React.createElement(Host)));
|
|
60
|
+
act(() => requestPresetPreviews());
|
|
61
|
+
act(() => vi.advanceTimersByTime(0));
|
|
62
|
+
|
|
63
|
+
act(() => {
|
|
64
|
+
ref.current = b;
|
|
65
|
+
usePreviewIframeStore.getState().setIframe(b);
|
|
66
|
+
});
|
|
67
|
+
act(() => vi.advanceTimersByTime(0));
|
|
68
|
+
await act(async () => {
|
|
69
|
+
await Promise.resolve();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(renderOnB).toHaveBeenCalled();
|
|
73
|
+
act(() => root.unmount());
|
|
74
|
+
vi.useRealTimers();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useLivePreviewIframe } from "../../player/store/previewIframeStore";
|
|
1
2
|
import { useCallback, useEffect, useRef, useState, type RefObject } from "react";
|
|
2
3
|
import {
|
|
3
4
|
HF_COLOR_GRADING_ACTIVE_EFFECT_KEYS,
|
|
@@ -155,6 +156,7 @@ export function useColorGradingPreviews({
|
|
|
155
156
|
}
|
|
156
157
|
const previews = state.identityKey === identityKey ? state.previews : emptyPreviews();
|
|
157
158
|
|
|
159
|
+
const livePreviewIframe = useLivePreviewIframe();
|
|
158
160
|
useEffect(() => {
|
|
159
161
|
if (!request) return;
|
|
160
162
|
const { kind } = request;
|
|
@@ -245,7 +247,15 @@ export function useColorGradingPreviews({
|
|
|
245
247
|
iframe.removeEventListener("load", attempt);
|
|
246
248
|
window.removeEventListener("message", onMessage);
|
|
247
249
|
};
|
|
248
|
-
}, [
|
|
250
|
+
}, [
|
|
251
|
+
grading.effects,
|
|
252
|
+
grading.lut,
|
|
253
|
+
grading.palette,
|
|
254
|
+
previewIframeRef,
|
|
255
|
+
livePreviewIframe,
|
|
256
|
+
request,
|
|
257
|
+
target,
|
|
258
|
+
]);
|
|
249
259
|
|
|
250
260
|
const stopAnimatedPreview = useCallback(() => {
|
|
251
261
|
const session = animatedRef.current;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import { act, useRef } from "react";
|
|
4
|
+
import { createRoot } from "react-dom/client";
|
|
5
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
6
|
+
import { usePreviewIframeStore } from "../../player/store/previewIframeStore";
|
|
7
|
+
import {
|
|
8
|
+
usePreviewCompositionRect,
|
|
9
|
+
type PreviewCompositionRect,
|
|
10
|
+
} from "./usePreviewCompositionRect";
|
|
11
|
+
|
|
12
|
+
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
usePreviewIframeStore.getState().setIframe(null);
|
|
16
|
+
document.body.innerHTML = "";
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
function boxed<T extends HTMLElement>(el: T, box: [number, number, number, number]): T {
|
|
20
|
+
const [left, top, width, height] = box;
|
|
21
|
+
el.getBoundingClientRect = () => new DOMRect(left, top, width, height);
|
|
22
|
+
return el;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function mountProbe() {
|
|
26
|
+
let rect: PreviewCompositionRect | undefined;
|
|
27
|
+
function Probe() {
|
|
28
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
29
|
+
rect = usePreviewCompositionRect(ref);
|
|
30
|
+
return (
|
|
31
|
+
<div
|
|
32
|
+
ref={(el) => {
|
|
33
|
+
ref.current = el;
|
|
34
|
+
if (el) boxed(el, [10, 20, 1000, 600]);
|
|
35
|
+
}}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
const host = document.createElement("div");
|
|
40
|
+
document.body.append(host);
|
|
41
|
+
act(() => createRoot(host).render(<Probe />));
|
|
42
|
+
return () => rect;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function compositionFrame(box: [number, number, number, number], width: number, height: number) {
|
|
46
|
+
const iframe = boxed(document.createElement("iframe"), box);
|
|
47
|
+
document.body.append(iframe);
|
|
48
|
+
const root = iframe.contentDocument?.createElement("div");
|
|
49
|
+
root?.setAttribute("data-composition-id", "main");
|
|
50
|
+
root?.setAttribute("data-width", String(width));
|
|
51
|
+
root?.setAttribute("data-height", String(height));
|
|
52
|
+
if (root) iframe.contentDocument?.body.append(root);
|
|
53
|
+
return iframe;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const settle = () => new Promise((r) => setTimeout(r, 400));
|
|
57
|
+
|
|
58
|
+
describe("usePreviewCompositionRect", () => {
|
|
59
|
+
it("reports the live preview iframe's box relative to the overlay, scaled to the composition", async () => {
|
|
60
|
+
const read = mountProbe();
|
|
61
|
+
const iframe = compositionFrame([110, 70, 960, 540], 1920, 1080);
|
|
62
|
+
await act(async () => {
|
|
63
|
+
usePreviewIframeStore.getState().setIframe(iframe);
|
|
64
|
+
await settle();
|
|
65
|
+
});
|
|
66
|
+
expect(read()).toEqual({
|
|
67
|
+
left: 100,
|
|
68
|
+
top: 50,
|
|
69
|
+
width: 960,
|
|
70
|
+
height: 540,
|
|
71
|
+
scaleX: 0.5,
|
|
72
|
+
scaleY: 0.5,
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("follows the iframe when a reload replaces it", async () => {
|
|
77
|
+
const read = mountProbe();
|
|
78
|
+
await act(async () => {
|
|
79
|
+
usePreviewIframeStore.getState().setIframe(compositionFrame([110, 70, 960, 540], 1920, 1080));
|
|
80
|
+
await settle();
|
|
81
|
+
});
|
|
82
|
+
await act(async () => {
|
|
83
|
+
usePreviewIframeStore.getState().setIframe(compositionFrame([60, 40, 500, 500], 1000, 1000));
|
|
84
|
+
await settle();
|
|
85
|
+
});
|
|
86
|
+
expect(read()).toEqual({
|
|
87
|
+
left: 50,
|
|
88
|
+
top: 20,
|
|
89
|
+
width: 500,
|
|
90
|
+
height: 500,
|
|
91
|
+
scaleX: 0.5,
|
|
92
|
+
scaleY: 0.5,
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useMemo, type RefObject } from "react";
|
|
2
|
+
import { usePreviewIframeStore } from "../../player/store/previewIframeStore";
|
|
3
|
+
import { useDomEditCompositionRect } from "./useDomEditCompositionRect";
|
|
4
|
+
|
|
5
|
+
export type { DomEditCompositionRect as PreviewCompositionRect } from "./useDomEditCompositionRect";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Where the composition sits inside `overlayRef`, in overlay pixels, plus the scale from
|
|
9
|
+
* composition units to pixels. Follows the live preview iframe across reloads.
|
|
10
|
+
*/
|
|
11
|
+
export function usePreviewCompositionRect(overlayRef: RefObject<HTMLDivElement | null>) {
|
|
12
|
+
const iframeRef = useMemo<RefObject<HTMLIFrameElement | null>>(
|
|
13
|
+
() => ({
|
|
14
|
+
get current() {
|
|
15
|
+
return usePreviewIframeStore.getState().iframe;
|
|
16
|
+
},
|
|
17
|
+
}),
|
|
18
|
+
[],
|
|
19
|
+
);
|
|
20
|
+
return useDomEditCompositionRect({ iframeRef, overlayRef });
|
|
21
|
+
}
|
|
@@ -243,7 +243,7 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
|
|
|
243
243
|
if (req) trackStudioFeedbackInterviewClick({ reason: req.reason });
|
|
244
244
|
close("sent");
|
|
245
245
|
}}
|
|
246
|
-
className="h-6
|
|
246
|
+
className="h-6 shrink-0 whitespace-nowrap rounded-full border border-white/20 bg-white/8 px-2.5 text-[11px] leading-6 text-neutral-100 transition-[background-color,border-color,transform] duration-150 ease-out hover:border-white/35 hover:bg-white/16 active:scale-[0.97] motion-reduce:transition-none"
|
|
247
247
|
>
|
|
248
248
|
Talk to us, 30 min
|
|
249
249
|
</a>
|
|
@@ -251,7 +251,7 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
|
|
|
251
251
|
<button
|
|
252
252
|
type="button"
|
|
253
253
|
onClick={() => close("close")}
|
|
254
|
-
className="-mr-1 -mt-0.5 flex h-5 w-5
|
|
254
|
+
className="-mr-1 -mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-md text-neutral-500 transition-colors duration-150 hover:bg-white/10 hover:text-neutral-200"
|
|
255
255
|
aria-label="Dismiss"
|
|
256
256
|
>
|
|
257
257
|
<svg
|
|
@@ -283,7 +283,7 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
|
|
|
283
283
|
key={n}
|
|
284
284
|
// A resting fill, so the row reads as eleven controls rather
|
|
285
285
|
// than as a line of text that happens to be clickable.
|
|
286
|
-
className="flex h-7 cursor-pointer items-center justify-center rounded-md bg-white/
|
|
286
|
+
className="flex h-7 cursor-pointer items-center justify-center rounded-md bg-white/4 text-[11px] tabular-nums text-neutral-400 transition-[background-color,color,transform] duration-150 ease-out hover:bg-white/[0.14] hover:text-neutral-100 active:scale-[0.97] has-checked:bg-white/90 has-checked:text-neutral-900 has-focus-visible:ring-1 has-focus-visible:ring-white/50 motion-reduce:transition-none"
|
|
287
287
|
>
|
|
288
288
|
<input
|
|
289
289
|
type="radio"
|
|
@@ -328,8 +328,8 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
|
|
|
328
328
|
// attached to nothing.
|
|
329
329
|
className={`h-6 rounded-full border px-2.5 text-[11px] transition-[background-color,border-color,color,transform] duration-150 ease-out active:scale-[0.97] motion-reduce:transition-none ${
|
|
330
330
|
failed
|
|
331
|
-
? "border-red-300/20 bg-red-100/
|
|
332
|
-
: "border-white/10 bg-white/
|
|
331
|
+
? "border-red-300/20 bg-red-100/6 text-red-100/80 hover:border-red-200/70 hover:bg-red-100/25 hover:text-white"
|
|
332
|
+
: "border-white/10 bg-white/4 text-neutral-300 hover:border-white/60 hover:bg-white/25 hover:text-white"
|
|
333
333
|
}`}
|
|
334
334
|
>
|
|
335
335
|
{preset.label}
|
|
@@ -355,12 +355,12 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
|
|
|
355
355
|
placeholder={followUp.placeholder}
|
|
356
356
|
aria-label={followUp.prompt}
|
|
357
357
|
maxLength={500}
|
|
358
|
-
className="h-7 min-w-0 flex-1 rounded-md border border-white/10 bg-black/20 px-2 text-[11px] text-neutral-100 outline-
|
|
358
|
+
className="h-7 min-w-0 flex-1 rounded-md border border-white/10 bg-black/20 px-2 text-[11px] text-neutral-100 outline-hidden transition-colors duration-150 placeholder:text-neutral-500 focus:border-white/25"
|
|
359
359
|
/>
|
|
360
360
|
<button
|
|
361
361
|
type="button"
|
|
362
362
|
onClick={() => submit()}
|
|
363
|
-
className="h-7
|
|
363
|
+
className="h-7 shrink-0 rounded-md bg-white/10 px-2.5 text-[11px] text-neutral-100 transition-[background-color,transform] duration-150 ease-out hover:bg-white/16 active:scale-[0.97] motion-reduce:transition-none"
|
|
364
364
|
>
|
|
365
365
|
Send
|
|
366
366
|
</button>
|
|
@@ -76,7 +76,6 @@ const FOLLOW_UPS: readonly FollowUpQuestion[] = [
|
|
|
76
76
|
{ label: "Layers panel", hint: "The layer tree on the right" },
|
|
77
77
|
{ label: "Variables panel", hint: "Composition variables on the right" },
|
|
78
78
|
{ label: "Blocks browser", hint: "The block library in the sidebar" },
|
|
79
|
-
{ label: "Storyboard mode", hint: "The storyboard view next to Preview" },
|
|
80
79
|
{ label: "Caption editing", hint: "Editing caption words and presets in Studio" },
|
|
81
80
|
{ label: "Render history", hint: "The list of past exports" },
|
|
82
81
|
{ label: "Nothing", hint: "It all earns its place" },
|
|
@@ -42,7 +42,11 @@ function AssetPreviewMedia({
|
|
|
42
42
|
}) {
|
|
43
43
|
if (kind === "image") {
|
|
44
44
|
return (
|
|
45
|
-
<img
|
|
45
|
+
<img
|
|
46
|
+
src={serveUrl}
|
|
47
|
+
alt={name}
|
|
48
|
+
className="max-w-full max-h-[40vh] rounded-sm object-contain"
|
|
49
|
+
/>
|
|
46
50
|
);
|
|
47
51
|
}
|
|
48
52
|
if (kind === "video") {
|
|
@@ -53,7 +57,7 @@ function AssetPreviewMedia({
|
|
|
53
57
|
autoPlay
|
|
54
58
|
muted
|
|
55
59
|
playsInline
|
|
56
|
-
className="max-w-full max-h-[40vh] rounded"
|
|
60
|
+
className="max-w-full max-h-[40vh] rounded-sm"
|
|
57
61
|
/>
|
|
58
62
|
);
|
|
59
63
|
}
|