@hyperframes/studio 0.8.52 → 0.8.53

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.
Files changed (75) hide show
  1. package/dist/assets/{hyperframes-player-I5YRzB02.js → hyperframes-player-DKWM-Ons.js} +1 -1
  2. package/dist/assets/index-DrHQAuGO.css +1 -0
  3. package/dist/assets/index-HfP3sap2.js +390 -0
  4. package/dist/assets/{index-BTAfgesM.js → index-UvW6BEwf.js} +1 -1
  5. package/dist/assets/{index-Du5x7KUR.js → index-Y4OnpTEM.js} +1 -1
  6. package/dist/index.css +29 -0
  7. package/dist/index.css.map +1 -0
  8. package/dist/index.d.ts +13 -6
  9. package/dist/index.html +2 -2
  10. package/dist/index.js +7664 -7953
  11. package/dist/index.js.map +1 -1
  12. package/dist/styles.css +1 -0
  13. package/package.json +8 -7
  14. package/src/App.tsx +42 -48
  15. package/src/components/EditorShell.selectionSync.test.tsx +9 -4
  16. package/src/components/EditorShell.tsx +52 -56
  17. package/src/components/StudioHeader.dom.test.tsx +1 -1
  18. package/src/components/StudioHeader.test.ts +0 -12
  19. package/src/components/StudioHeader.tsx +10 -13
  20. package/src/components/StudioLeftPanels.tsx +202 -0
  21. package/src/components/{StudioRightPanel.tsx → StudioRightPanels.tsx} +57 -206
  22. package/src/components/{StudioRightPanel.types.ts → StudioRightPanels.types.ts} +4 -3
  23. package/src/components/dock/Dock.test.tsx +16 -2
  24. package/src/components/dock/Dock.tsx +13 -3
  25. package/src/components/dock/dockLayoutStore.test.ts +28 -0
  26. package/src/components/dock/dockLayoutStore.ts +21 -4
  27. package/src/components/editor/SnapToolbar.test.tsx +0 -3
  28. package/src/components/nle/NLEContext.tsx +0 -43
  29. package/src/components/nle/TimelinePane.tsx +46 -67
  30. package/src/components/panels/SlideshowPanel.tsx +1 -1
  31. package/src/components/panels/VariablesPanel.tsx +1 -1
  32. package/src/components/renders/RenderQueue.test.tsx +133 -25
  33. package/src/components/renders/RenderQueue.tsx +99 -92
  34. package/src/components/renders/RenderQueueItem.tsx +69 -64
  35. package/src/components/renders/RenderQueuePanel.tsx +1 -1
  36. package/src/components/sidebar/{LeftSidebar.masterComposition.test.tsx → CompositionsPanel.masterComposition.test.tsx} +6 -9
  37. package/src/components/sidebar/CompositionsPanel.tsx +15 -0
  38. package/src/components/sidebar/{LeftSidebar.lintBadge.test.tsx → SidebarLintButton.test.tsx} +6 -11
  39. package/src/components/sidebar/SidebarLintButton.tsx +50 -0
  40. package/src/components/ui/Select.tsx +4 -0
  41. package/src/contexts/PanelLayoutContext.tsx +3 -57
  42. package/src/headerExports.test.tsx +23 -0
  43. package/src/hooks/appHotkeysDispatch.ts +3 -4
  44. package/src/hooks/useAppHotkeys.previewForwarding.test.tsx +0 -2
  45. package/src/hooks/useAppHotkeys.test.ts +0 -1
  46. package/src/hooks/useAppHotkeys.textEditing.test.tsx +0 -3
  47. package/src/hooks/useAppHotkeys.ts +0 -4
  48. package/src/hooks/useDomEditPreviewSync.ts +4 -6
  49. package/src/hooks/useDomEditSession.ts +0 -7
  50. package/src/hooks/useDomEditWiring.ts +4 -9
  51. package/src/hooks/usePanelLayout.test.ts +108 -296
  52. package/src/hooks/usePanelLayout.ts +50 -255
  53. package/src/hooks/useRightPanelIntents.test.ts +181 -0
  54. package/src/hooks/useRightPanelIntents.ts +64 -0
  55. package/src/hooks/useSlideshowTabState.test.ts +9 -31
  56. package/src/hooks/useSlideshowTabState.ts +6 -19
  57. package/src/hooks/useStudioContextValue.test.ts +6 -18
  58. package/src/hooks/useStudioContextValue.ts +5 -26
  59. package/src/index.ts +2 -1
  60. package/src/main.tsx +26 -0
  61. package/src/player/components/Timeline.tsx +1 -1
  62. package/src/player/lib/timelinePerformanceDiagnostics.test.ts +3 -2
  63. package/src/player/lib/timelinePerformanceDiagnostics.ts +1 -1
  64. package/src/utils/studioHelpers.ts +0 -7
  65. package/src/utils/studioUiPreferences.test.ts +10 -16
  66. package/src/utils/studioUiPreferences.ts +0 -16
  67. package/dist/assets/index-CxAu_Okg.css +0 -1
  68. package/dist/assets/index-DocJlW2v.js +0 -376
  69. package/src/components/StudioLeftSidebar.tsx +0 -207
  70. package/src/components/nle/TimelineResizeDivider.tsx +0 -101
  71. package/src/components/sidebar/LeftSidebar.storage.test.ts +0 -28
  72. package/src/components/sidebar/LeftSidebar.tsx +0 -348
  73. package/src/hooks/useInspectorSplitResize.ts +0 -51
  74. package/src/utils/fitPanels.test.ts +0 -164
  75. package/src/utils/fitPanels.ts +0 -151
@@ -1,29 +1,22 @@
1
- import { useEffect, useMemo, type MutableRefObject } from "react";
1
+ import { useMemo, type MutableRefObject } from "react";
2
2
  import { SLIDESHOW_ISLAND_TYPE, slideshowIslandRegex } from "@hyperframes/core/slideshow";
3
3
  import type { SceneInfo } from "../components/panels/SlideshowPanel";
4
4
  import type { IframeWindow } from "../player/lib/playbackTypes";
5
- import type { RightPanelTab } from "../utils/studioHelpers";
6
5
 
7
6
  /**
8
7
  * Derives whether the currently-edited composition is a slideshow (carries
9
8
  * the slideshow JSON island — the same definitive marker the CLI's `present`
10
9
  * command requires; it refuses to run without one) and the live scene list
11
- * for the Slideshow panel, and bounces `rightPanelTab` off "slideshow" the
12
- * moment it stops applying (e.g. the user switches to a non-slideshow file
13
- * while that tab was open) so the panel never shows a dangling active tab
14
- * whose button is no longer even rendered.
15
- *
16
- * Extracted from StudioRightPanel to keep that file under the 600-LOC gate.
10
+ * for the Slideshow panel. Extracted from StudioRightPanels to keep that file
11
+ * under the 600-LOC gate.
17
12
  */
18
13
  export function useSlideshowTabState(params: {
19
14
  editingFileContent: string | null | undefined;
20
15
  previewIframeRef: MutableRefObject<HTMLIFrameElement | null>;
21
16
  refreshKey: number;
22
- rightPanelTab: RightPanelTab;
23
- setRightPanelTab: (tab: RightPanelTab) => void;
17
+ slideshowVisible: boolean;
24
18
  }): { isSlideshowComposition: boolean; slideshowScenes: SceneInfo[] } {
25
- const { editingFileContent, previewIframeRef, refreshKey, rightPanelTab, setRightPanelTab } =
26
- params;
19
+ const { editingFileContent, previewIframeRef, refreshKey, slideshowVisible } = params;
27
20
 
28
21
  // Presence-only (not full manifest validation): a malformed island should
29
22
  // still surface the Slideshow tab so the user can see/fix it, rather than
@@ -49,13 +42,7 @@ export function useSlideshowTabState(params: {
49
42
  return [];
50
43
  }
51
44
  // eslint-disable-next-line react-hooks/exhaustive-deps
52
- }, [previewIframeRef, rightPanelTab, refreshKey]);
53
-
54
- useEffect(() => {
55
- if (rightPanelTab === "slideshow" && !isSlideshowComposition) {
56
- setRightPanelTab("renders");
57
- }
58
- }, [rightPanelTab, isSlideshowComposition, setRightPanelTab]);
45
+ }, [previewIframeRef, slideshowVisible, refreshKey]);
59
46
 
60
47
  return { isSlideshowComposition, slideshowScenes };
61
48
  }
@@ -4,14 +4,12 @@ import React from "react";
4
4
  import { renderToStaticMarkup } from "react-dom/server";
5
5
  import { describe, expect, it } from "vitest";
6
6
  import type { DomEditSelection } from "../components/editor/domEditing";
7
- import type { RightInspectorPanes } from "../utils/studioHelpers";
7
+ import type { PanelId } from "../components/dock/panelRegistry";
8
8
  import { makeSelection } from "./domSelectionTestHarness";
9
9
  import { useInspectorState, type InspectorState } from "./useStudioContextValue";
10
10
 
11
11
  interface HarnessProps {
12
- rightPanelTab: string;
13
- rightInspectorPanes: RightInspectorPanes;
14
- rightCollapsed: boolean;
12
+ rightPanel: PanelId | null;
15
13
  isPlaying: boolean;
16
14
  isGestureRecording: boolean;
17
15
  domEditSelection: DomEditSelection | null;
@@ -22,9 +20,7 @@ function renderInspectorState(props: HarnessProps): InspectorState {
22
20
 
23
21
  function Harness() {
24
22
  state = useInspectorState(
25
- props.rightPanelTab,
26
- props.rightInspectorPanes,
27
- props.rightCollapsed,
23
+ props.rightPanel,
28
24
  props.isPlaying,
29
25
  props.domEditSelection,
30
26
  props.isGestureRecording,
@@ -42,9 +38,7 @@ function selectedProps(
42
38
  ): HarnessProps & { domEditSelection: DomEditSelection } {
43
39
  const element = document.createElement("div");
44
40
  return {
45
- rightPanelTab: "renders",
46
- rightInspectorPanes: { layers: false, design: false },
47
- rightCollapsed: true,
41
+ rightPanel: "renders",
48
42
  isPlaying: false,
49
43
  isGestureRecording: false,
50
44
  domEditSelection: makeSelection("Selected", element),
@@ -78,16 +72,10 @@ describe("useInspectorState", () => {
78
72
  it("keeps selected DOM bounds coupled to the inspector or variables panel", () => {
79
73
  expect(renderInspectorState(selectedProps()).shouldShowSelectedDomBounds).toBe(false);
80
74
  expect(
81
- renderInspectorState(
82
- selectedProps({
83
- rightPanelTab: "design",
84
- rightInspectorPanes: { layers: false, design: true },
85
- }),
86
- ).shouldShowSelectedDomBounds,
75
+ renderInspectorState(selectedProps({ rightPanel: "design" })).shouldShowSelectedDomBounds,
87
76
  ).toBe(true);
88
77
  expect(
89
- renderInspectorState(selectedProps({ rightPanelTab: "variables" }))
90
- .shouldShowSelectedDomBounds,
78
+ renderInspectorState(selectedProps({ rightPanel: "variables" })).shouldShowSelectedDomBounds,
91
79
  ).toBe(true);
92
80
  });
93
81
  });
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useMemo, type DragEvent } from "react";
2
2
  import type { DomEditSelection } from "../components/editor/domEditing";
3
3
  import type { StudioContextValue } from "../contexts/StudioContext";
4
- import type { RightInspectorPanes } from "../utils/studioHelpers";
4
+ import type { PanelId } from "../components/dock/panelRegistry";
5
5
  import type { TimelineFileDropHandler } from "./useTimelineEditingTypes";
6
6
  import { usePlayerStore } from "../player";
7
7
 
@@ -66,33 +66,21 @@ export function buildStudioContextValue(input: StudioContextInput): StudioContex
66
66
  }
67
67
 
68
68
  export interface InspectorState {
69
- layersPanelActive: boolean;
70
- designPanelActive: boolean;
71
69
  inspectorPanelActive: boolean;
72
- inspectorButtonActive: boolean;
73
70
  shouldShowMotionPath: boolean;
74
71
  shouldShowSelectedDomBounds: boolean;
75
72
  }
76
73
 
77
74
  export function useInspectorState(
78
- rightPanelTab: string,
79
- rightInspectorPanes: RightInspectorPanes,
80
- rightCollapsed: boolean,
75
+ rightPanel: PanelId | null,
81
76
  isPlaying: boolean,
82
77
  domEditSelection: DomEditSelection | null,
83
78
  isGestureRecording?: boolean,
84
79
  ): InspectorState {
85
- // fallow-ignore-next-line complexity
86
80
  return useMemo(() => {
87
- const inspectorTabActive = rightPanelTab === "design" || rightPanelTab === "layers";
88
- const layersPanelActive = inspectorTabActive && rightInspectorPanes.layers;
89
- const designPanelActive = inspectorTabActive && rightInspectorPanes.design;
90
- const inspectorPanelActive = layersPanelActive || designPanelActive;
81
+ const inspectorPanelActive = rightPanel === "layers" || rightPanel === "design";
91
82
  return {
92
- layersPanelActive,
93
- designPanelActive,
94
83
  inspectorPanelActive,
95
- inspectorButtonActive: !rightCollapsed && inspectorPanelActive,
96
84
  // Deliberately wider than shouldShowSelectedDomBounds: the on-canvas path
97
85
  // handles ARE the arc-drag affordance, so gating them on an open Inspector
98
86
  // would make keyframe path editing reachable only from a side panel.
@@ -102,18 +90,9 @@ export function useInspectorState(
102
90
  // The Variables tab also works against the canvas selection (bind card),
103
91
  // so the selection outline stays visible there too.
104
92
  shouldShowSelectedDomBounds:
105
- (inspectorPanelActive || rightPanelTab === "variables") &&
106
- !isPlaying &&
107
- !isGestureRecording,
93
+ (inspectorPanelActive || rightPanel === "variables") && !isPlaying && !isGestureRecording,
108
94
  };
109
- }, [
110
- rightPanelTab,
111
- rightInspectorPanes,
112
- rightCollapsed,
113
- isPlaying,
114
- isGestureRecording,
115
- domEditSelection,
116
- ]);
95
+ }, [rightPanel, isPlaying, isGestureRecording, domEditSelection]);
117
96
  }
118
97
 
119
98
  /** Lets an OS file drop reach `onDrop` anywhere in the shell; the timeline shows its own landing preview. */
package/src/index.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  // UI primitives
2
- export { Button, buttonBase, buttonVariants } from "./components/ui/Button";
2
+ export { Button, buttonBase, buttonSizes, buttonVariants } from "./components/ui/Button";
3
3
  export type { ButtonSize, ButtonVariant, PreviewState } from "./components/ui/Button";
4
4
  export { IconButton } from "./components/ui/IconButton";
5
5
  export { Tab, TabPanel, Tabs, TabsList } from "./components/ui/Tabs";
6
6
  export { Tooltip } from "./components/ui/Tooltip";
7
+ export { HyperframesLogo } from "./components/StudioHeader";
7
8
  export { cn } from "./components/ui/cn";
8
9
  export {
9
10
  ContextMenu,
package/src/main.tsx CHANGED
@@ -65,6 +65,10 @@ window.addEventListener("error", (event) => {
65
65
  });
66
66
 
67
67
  let filteredAssetErrorCount = 0;
68
+ let filteredAbortCount = 0;
69
+ // Bounded so a hostile extension cannot grow it; a novel abort message still surfaces once.
70
+ const seenAbortMessages = new Set<string>();
71
+ const MAX_SEEN_ABORT_MESSAGES = 20;
68
72
 
69
73
  // fallow-ignore-next-line complexity
70
74
  window.addEventListener("unhandledrejection", (event) => {
@@ -80,6 +84,28 @@ window.addEventListener("unhandledrejection", (event) => {
80
84
  }
81
85
  return;
82
86
  }
87
+ // An AbortError rejection is a cancellation we asked for, not a failure. Our own
88
+ // fetch chains handle it; the unhandled copies come from browser extensions that
89
+ // wrap window.fetch and derive a promise from each request without a rejection
90
+ // handler, so they reject with our abort reason whenever a thumbnail lease is
91
+ // released mid-fetch. Filtered before the cap so a scroll burst cannot exhaust
92
+ // ERROR_CAP and silence the session's real rejections. Sampled per distinct
93
+ // message, so an abort we have not seen before (e.g. our own "Aborted") is
94
+ // recorded once instead of drowning in the extension flood.
95
+ if (props.error_name === "AbortError") {
96
+ filteredAbortCount++;
97
+ const message = props.error_message.slice(0, 200);
98
+ const novel =
99
+ !seenAbortMessages.has(message) && seenAbortMessages.size < MAX_SEEN_ABORT_MESSAGES;
100
+ if (novel) seenAbortMessages.add(message);
101
+ if (novel || filteredAbortCount % 100 === 0) {
102
+ trackStudioEvent("abort_rejection_filtered", {
103
+ error_message: message,
104
+ total_filtered: filteredAbortCount,
105
+ });
106
+ }
107
+ return;
108
+ }
83
109
 
84
110
  rejectionCount++;
85
111
  if (rejectionCount > ERROR_CAP) {
@@ -448,7 +448,7 @@ export const Timeline = memo(function Timeline({
448
448
  return (
449
449
  <div
450
450
  ref={setContainerRef}
451
- aria-label="Timeline"
451
+ aria-label="Timeline track view"
452
452
  data-timeline-element-count={expandedElements.length}
453
453
  className={`relative border-t select-none h-full overflow-hidden ${assetDrop.isDragOver ? "ring-1 ring-inset ring-studio-accent/60" : ""} ${activeTool === "razor" ? "cursor-crosshair" : shiftHeld ? "cursor-crosshair" : "cursor-default"}`}
454
454
  onMouseMove={updateRazorGuide}
@@ -14,7 +14,7 @@ describe("timeline performance diagnostics", () => {
14
14
 
15
15
  it("reads mounted resources without mutating the timeline", () => {
16
16
  document.body.innerHTML = `
17
- <div aria-label="Timeline" data-timeline-scheduler-queued="3"
17
+ <div aria-label="Timeline track view" data-timeline-scheduler-queued="3"
18
18
  data-timeline-scheduler-active="2" data-timeline-cache-bytes="4096">
19
19
  <div data-timeline-row><div data-clip="true"></div><div data-clip="true"></div></div>
20
20
  <div data-timeline-row><div data-clip="true"></div></div>
@@ -41,7 +41,8 @@ describe("timeline performance diagnostics", () => {
41
41
  });
42
42
 
43
43
  it("returns the zero baseline after unmount or reset removes the DOM", () => {
44
- document.body.innerHTML = '<div aria-label="Timeline"><div data-clip="true"></div></div>';
44
+ document.body.innerHTML =
45
+ '<div aria-label="Timeline track view"><div data-clip="true"></div></div>';
45
46
  expect(readTimelinePerformanceDiagnostics().mountedClipRoots).toBe(1);
46
47
 
47
48
  document.body.replaceChildren();
@@ -69,7 +69,7 @@ function sumDataAttribute(root: ParentNode, selector: string, dataKey: string):
69
69
  export function readTimelinePerformanceDiagnostics(
70
70
  root: ParentNode = document,
71
71
  ): Readonly<TimelinePerformanceDiagnostics> {
72
- const timelineRoots = root.querySelectorAll<HTMLElement>('[aria-label="Timeline"]');
72
+ const timelineRoots = root.querySelectorAll<HTMLElement>('[aria-label="Timeline track view"]');
73
73
  let mountedTimelineDescendants = 0;
74
74
  for (const timelineRoot of timelineRoots) {
75
75
  mountedTimelineDescendants += timelineRoot.querySelectorAll("*").length;
@@ -22,13 +22,6 @@ export type RightPanelTab =
22
22
  | "block-params"
23
23
  | "slideshow"
24
24
  | "variables";
25
- export type RightInspectorPane = "layers" | "design";
26
-
27
- export interface RightInspectorPanes {
28
- layers: boolean;
29
- design: boolean;
30
- }
31
-
32
25
  export interface AgentModalAnchorPoint {
33
26
  x: number;
34
27
  y: number;
@@ -20,7 +20,6 @@ describe("studio UI preferences", () => {
20
20
  const storage = createStorage();
21
21
 
22
22
  writeStudioUiPreferences({ timelineVisible: false }, storage);
23
- writeStudioUiPreferences({ leftWidth: 384, rightWidth: 424 }, storage);
24
23
  writeStudioUiPreferences({ playbackRate: 1.5 }, storage);
25
24
  writeStudioUiPreferences({ audioMuted: true }, storage);
26
25
  writeStudioUiPreferences({ audioVolume: 0.4 }, storage);
@@ -28,8 +27,6 @@ describe("studio UI preferences", () => {
28
27
 
29
28
  expect(readStudioUiPreferences(storage)).toEqual({
30
29
  timelineVisible: false,
31
- leftWidth: 384,
32
- rightWidth: 424,
33
30
  playbackRate: 1.5,
34
31
  audioMuted: true,
35
32
  audioVolume: 0.4,
@@ -57,9 +54,6 @@ describe("studio UI preferences", () => {
57
54
  storage.setItem(
58
55
  "hf-studio-ui-preferences",
59
56
  JSON.stringify({
60
- leftCollapsed: "yes",
61
- leftWidth: "wide",
62
- rightWidth: Number.NaN,
63
57
  timelineVisible: true,
64
58
  playbackRate: Number.NaN,
65
59
  audioMuted: "false",
@@ -119,28 +113,28 @@ describe("thumbnailMode preference", () => {
119
113
  describe("per-project scoping", () => {
120
114
  it("keeps two projects' preferences independent", () => {
121
115
  const storage = createStorage();
122
- writeStudioUiPreferences({ leftWidth: 300 }, storage, "alpha");
123
- writeStudioUiPreferences({ leftWidth: 500 }, storage, "beta");
116
+ writeStudioUiPreferences({ playbackRate: 1.25 }, storage, "alpha");
117
+ writeStudioUiPreferences({ playbackRate: 2 }, storage, "beta");
124
118
 
125
- expect(readStudioUiPreferences(storage, "alpha").leftWidth).toBe(300);
126
- expect(readStudioUiPreferences(storage, "beta").leftWidth).toBe(500);
119
+ expect(readStudioUiPreferences(storage, "alpha").playbackRate).toBe(1.25);
120
+ expect(readStudioUiPreferences(storage, "beta").playbackRate).toBe(2);
127
121
  });
128
122
 
129
123
  it("a project with nothing saved yet inherits the pre-scoping global entry once", () => {
130
124
  const storage = createStorage();
131
- storage.setItem("hf-studio-ui-preferences", JSON.stringify({ leftWidth: 600 }));
125
+ storage.setItem("hf-studio-ui-preferences", JSON.stringify({ playbackRate: 0.5 }));
132
126
 
133
- expect(readStudioUiPreferences(storage, "gamma").leftWidth).toBe(600);
127
+ expect(readStudioUiPreferences(storage, "gamma").playbackRate).toBe(0.5);
134
128
  });
135
129
 
136
130
  it("stops inheriting the global entry once the project has its own write", () => {
137
131
  const storage = createStorage();
138
- storage.setItem("hf-studio-ui-preferences", JSON.stringify({ leftWidth: 600 }));
132
+ storage.setItem("hf-studio-ui-preferences", JSON.stringify({ playbackRate: 0.5 }));
139
133
 
140
- writeStudioUiPreferences({ leftWidth: 250 }, storage, "gamma");
134
+ writeStudioUiPreferences({ playbackRate: 1.75 }, storage, "gamma");
141
135
 
142
- expect(readStudioUiPreferences(storage, "gamma").leftWidth).toBe(250);
143
- expect(readStudioUiPreferences(storage, null).leftWidth).toBe(600);
136
+ expect(readStudioUiPreferences(storage, "gamma").playbackRate).toBe(1.75);
137
+ expect(readStudioUiPreferences(storage, null).playbackRate).toBe(0.5);
144
138
  });
145
139
  });
146
140
 
@@ -10,11 +10,7 @@ export interface StoredPreviewZoomState {
10
10
  export type TimelineTimeDisplayMode = "time" | "frame";
11
11
 
12
12
  export interface StudioUiPreferences {
13
- leftCollapsed?: boolean;
14
- leftWidth?: number;
15
- rightWidth?: number;
16
13
  timelineVisible?: boolean;
17
- timelineHeight?: number;
18
14
  playbackRate?: number;
19
15
  audioMuted?: boolean;
20
16
  audioVolume?: number;
@@ -83,21 +79,9 @@ function readStorage(storage: Storage | null, key: string): StudioUiPreferences
83
79
  if (!isRecord(parsed)) return {};
84
80
 
85
81
  const preferences: StudioUiPreferences = {};
86
- if (typeof parsed.leftCollapsed === "boolean") {
87
- preferences.leftCollapsed = parsed.leftCollapsed;
88
- }
89
- if (typeof parsed.leftWidth === "number" && Number.isFinite(parsed.leftWidth)) {
90
- preferences.leftWidth = parsed.leftWidth;
91
- }
92
- if (typeof parsed.rightWidth === "number" && Number.isFinite(parsed.rightWidth)) {
93
- preferences.rightWidth = parsed.rightWidth;
94
- }
95
82
  if (typeof parsed.timelineVisible === "boolean") {
96
83
  preferences.timelineVisible = parsed.timelineVisible;
97
84
  }
98
- if (typeof parsed.timelineHeight === "number" && Number.isFinite(parsed.timelineHeight)) {
99
- preferences.timelineHeight = parsed.timelineHeight;
100
- }
101
85
  if (typeof parsed.playbackRate === "number" && Number.isFinite(parsed.playbackRate)) {
102
86
  preferences.playbackRate = parsed.playbackRate;
103
87
  }