babylon.quarks-editor 0.27.0 → 0.28.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.
Files changed (138) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +31 -4
  3. package/dist/core/assetTree.d.ts +5 -0
  4. package/dist/core/assets.d.ts +7 -0
  5. package/dist/core/assets.js +11 -7
  6. package/dist/core/binding.d.ts +9 -0
  7. package/dist/core/binding.js +8 -0
  8. package/dist/core/collision.d.ts +4 -0
  9. package/dist/core/collision.js +4 -0
  10. package/dist/core/colors.d.ts +16 -0
  11. package/dist/core/colors.js +8 -0
  12. package/dist/core/defaultEffect.d.ts +3 -2
  13. package/dist/core/defaultEffect.js +1 -1
  14. package/dist/core/editorSession.d.ts +229 -0
  15. package/dist/core/editorSession.js +983 -0
  16. package/dist/core/effectTree.d.ts +5 -0
  17. package/dist/core/galleryPlayback.js +2 -1
  18. package/dist/core/history.d.ts +6 -0
  19. package/dist/core/history.js +4 -0
  20. package/dist/core/indexedDbProjectStore.d.ts +13 -0
  21. package/dist/core/indexedDbProjectStore.js +13 -0
  22. package/dist/core/material.d.ts +12 -2
  23. package/dist/core/playback.d.ts +6 -0
  24. package/dist/core/projectStore.d.ts +26 -0
  25. package/dist/core/projectStore.js +1 -0
  26. package/dist/core/sceneEntries.d.ts +3 -0
  27. package/dist/core/scrub.d.ts +1 -1
  28. package/dist/core/scrub.js +2 -2
  29. package/dist/core/shapes.d.ts +24 -0
  30. package/dist/core/shapes.js +5 -0
  31. package/dist/core/systems.d.ts +2 -0
  32. package/dist/core/systems.js +3 -1
  33. package/dist/core/timeline.d.ts +23 -3
  34. package/dist/core/values.d.ts +21 -4
  35. package/dist/core/values.js +5 -0
  36. package/dist/index.d.ts +5 -1
  37. package/dist/react/AssetsPanel.d.ts +7 -0
  38. package/dist/react/ChoiceDialog.d.ts +7 -0
  39. package/dist/react/ContextMenu.d.ts +10 -1
  40. package/dist/react/EditorWorkspace.d.ts +7 -0
  41. package/dist/react/EditorWorkspace.js +6 -3
  42. package/dist/react/EffectEditor.d.ts +8 -2
  43. package/dist/react/EffectEditorHost.d.ts +32 -28
  44. package/dist/react/EffectEditorHost.js +241 -1665
  45. package/dist/react/EffectTreeView.d.ts +3 -0
  46. package/dist/react/MessageDialog.d.ts +5 -0
  47. package/dist/react/ModuleSection.d.ts +10 -0
  48. package/dist/react/ModuleSection.js +4 -1
  49. package/dist/react/ProjectsPage.d.ts +6 -0
  50. package/dist/react/ProjectsPage.js +27 -11
  51. package/dist/react/PromptDialog.d.ts +7 -0
  52. package/dist/react/ScenePanel.d.ts +7 -0
  53. package/dist/react/ScenePanel.js +7 -5
  54. package/dist/react/TextureSelect.d.ts +5 -5
  55. package/dist/react/TimelinePanel.d.ts +13 -0
  56. package/dist/react/TimelinePanel.js +10 -6
  57. package/dist/react/TimelineTrackRow.d.ts +10 -0
  58. package/dist/react/TransportBar.d.ts +9 -0
  59. package/dist/react/ValueField.d.ts +32 -5
  60. package/dist/react/ValueField.js +20 -9
  61. package/dist/react/behaviorModules.d.ts +16 -5
  62. package/dist/react/behaviorModules.js +16 -1
  63. package/dist/react/fields.d.ts +8 -0
  64. package/dist/react/fields.js +3 -0
  65. package/dist/react/host/BottomDocks.d.ts +36 -0
  66. package/dist/react/host/BottomDocks.js +63 -0
  67. package/dist/react/host/HostDialogs.d.ts +50 -0
  68. package/dist/react/host/HostDialogs.js +113 -0
  69. package/dist/react/host/InspectorSidebar.d.ts +32 -0
  70. package/dist/react/host/InspectorSidebar.js +59 -0
  71. package/dist/react/host/SceneSidebar.d.ts +53 -0
  72. package/dist/react/host/SceneSidebar.js +62 -0
  73. package/dist/react/host/ViewportPane.d.ts +56 -0
  74. package/dist/react/host/ViewportPane.js +91 -0
  75. package/dist/react/host/hostHooks.d.ts +44 -0
  76. package/dist/react/host/hostHooks.js +164 -0
  77. package/dist/react/host/hostStyles.d.ts +21 -0
  78. package/dist/react/host/hostStyles.js +57 -0
  79. package/dist/react/index.d.ts +3 -1
  80. package/dist/react/modules.d.ts +17 -6
  81. package/dist/react/modules.js +38 -20
  82. package/dist/react/theme.d.ts +15 -0
  83. package/dist/react/theme.js +15 -0
  84. package/dist/session.d.ts +9 -0
  85. package/dist/session.js +8 -0
  86. package/package.json +17 -11
  87. package/src/core/assetTree.ts +14 -1
  88. package/src/core/assets.ts +25 -10
  89. package/src/core/binding.ts +9 -0
  90. package/src/core/collision.ts +4 -0
  91. package/src/core/colors.ts +21 -1
  92. package/src/core/defaultEffect.ts +4 -3
  93. package/src/core/editorSession.ts +1299 -0
  94. package/src/core/effectTree.ts +5 -0
  95. package/src/core/galleryPlayback.ts +2 -1
  96. package/src/core/history.ts +6 -0
  97. package/src/core/indexedDbProjectStore.ts +13 -0
  98. package/src/core/loadEffect.ts +1 -1
  99. package/src/core/material.ts +14 -4
  100. package/src/core/playback.ts +6 -0
  101. package/src/core/projectStore.ts +26 -0
  102. package/src/core/sceneEntries.ts +3 -0
  103. package/src/core/scrub.ts +2 -2
  104. package/src/core/shapes.ts +28 -1
  105. package/src/core/systems.ts +9 -2
  106. package/src/core/timeline.ts +40 -15
  107. package/src/core/values.ts +20 -4
  108. package/src/index.ts +5 -1
  109. package/src/react/AssetsPanel.tsx +7 -0
  110. package/src/react/ChoiceDialog.tsx +7 -0
  111. package/src/react/ContextMenu.tsx +10 -1
  112. package/src/react/EditorWorkspace.tsx +11 -5
  113. package/src/react/EffectEditor.tsx +7 -1
  114. package/src/react/EffectEditorHost.tsx +406 -2340
  115. package/src/react/EffectTreeView.tsx +3 -0
  116. package/src/react/MessageDialog.tsx +5 -0
  117. package/src/react/ModuleSection.tsx +11 -0
  118. package/src/react/ProjectsPage.tsx +38 -11
  119. package/src/react/PromptDialog.tsx +7 -0
  120. package/src/react/ScenePanel.tsx +14 -5
  121. package/src/react/TextureSelect.tsx +6 -6
  122. package/src/react/TimelinePanel.tsx +23 -6
  123. package/src/react/TimelineTrackRow.tsx +10 -0
  124. package/src/react/TransportBar.tsx +9 -0
  125. package/src/react/ValueField.tsx +58 -14
  126. package/src/react/behaviorModules.tsx +34 -32
  127. package/src/react/fields.tsx +12 -1
  128. package/src/react/host/BottomDocks.tsx +178 -0
  129. package/src/react/host/HostDialogs.tsx +228 -0
  130. package/src/react/host/InspectorSidebar.tsx +147 -0
  131. package/src/react/host/SceneSidebar.tsx +227 -0
  132. package/src/react/host/ViewportPane.tsx +300 -0
  133. package/src/react/host/hostHooks.ts +198 -0
  134. package/src/react/host/hostStyles.ts +65 -0
  135. package/src/react/index.ts +3 -1
  136. package/src/react/modules.tsx +70 -44
  137. package/src/react/theme.ts +15 -0
  138. package/src/session.ts +17 -0
@@ -41,14 +41,10 @@ import {CheckboxField, NumberField, Row, SelectField} from './fields.js';
41
41
  import {GradientEditor} from './GradientEditor.js';
42
42
  import {iconStyle} from './icons.js';
43
43
  import type {TextureOption} from './modules.js';
44
- import {ModuleSection} from './ModuleSection.js';
44
+ import {ModuleSection, type ModuleProps} from './ModuleSection.js';
45
45
  import {theme} from './theme.js';
46
46
  import {ValueField} from './ValueField.js';
47
47
 
48
- interface ModuleProps {
49
- binding: EffectBinding;
50
- }
51
-
52
48
  function removeBehaviorOfType(behaviors: Behavior[], type: string): void {
53
49
  for (let i = behaviors.length - 1; i >= 0; i--) {
54
50
  if ((behaviors[i] as {type?: string}).type === type) {
@@ -85,6 +81,7 @@ function BehaviorModule<T extends Behavior>(props: {
85
81
  );
86
82
  }
87
83
 
84
+ /** The Speed over Lifetime module (Unity's Speed Modifier): scales speed by a curve over the particle's life. */
88
85
  export function SpeedOverLifeModule({binding}: ModuleProps) {
89
86
  return (
90
87
  <BehaviorModule<SpeedOverLife>
@@ -92,9 +89,7 @@ export function SpeedOverLifeModule({binding}: ModuleProps) {
92
89
  title="Speed over Lifetime"
93
90
  type="SpeedOverLife"
94
91
  create={() =>
95
- new SpeedOverLife(
96
- buildScalar({mode: 'curve', value: 1, min: 0, max: 1, curve: [1, 0.75, 0.5, 0]}) as never
97
- )
92
+ new SpeedOverLife(buildScalar({mode: 'curve', value: 1, min: 0, max: 1, curve: [1, 0.75, 0.5, 0]}))
98
93
  }
99
94
  >
100
95
  {(behavior) => (
@@ -102,7 +97,7 @@ export function SpeedOverLifeModule({binding}: ModuleProps) {
102
97
  <CurveEditor
103
98
  pieces={readPieces(behavior.speed)}
104
99
  maxValue={2}
105
- onChange={(pieces) => binding.apply(() => (behavior.speed = buildCurve(pieces) as never))}
100
+ onChange={(pieces) => binding.apply(() => (behavior.speed = buildCurve(pieces)))}
106
101
  />
107
102
  </div>
108
103
  )}
@@ -110,6 +105,7 @@ export function SpeedOverLifeModule({binding}: ModuleProps) {
110
105
  );
111
106
  }
112
107
 
108
+ /** The Limit Speed over Lifetime module (Unity's Limit Velocity over Lifetime): caps speed by a curve, with dampening. */
113
109
  export function LimitSpeedOverLifeModule({binding}: ModuleProps) {
114
110
  return (
115
111
  <BehaviorModule<LimitSpeedOverLife>
@@ -117,10 +113,7 @@ export function LimitSpeedOverLifeModule({binding}: ModuleProps) {
117
113
  title="Limit Speed over Lifetime"
118
114
  type="LimitSpeedOverLife"
119
115
  create={() =>
120
- new LimitSpeedOverLife(
121
- buildScalar({mode: 'curve', value: 1, min: 0, max: 1, curve: [4, 2, 1, 1]}) as never,
122
- 0.5
123
- )
116
+ new LimitSpeedOverLife(buildScalar({mode: 'curve', value: 1, min: 0, max: 1, curve: [4, 2, 1, 1]}), 0.5)
124
117
  }
125
118
  >
126
119
  {(behavior) => (
@@ -129,7 +122,7 @@ export function LimitSpeedOverLifeModule({binding}: ModuleProps) {
129
122
  <CurveEditor
130
123
  pieces={readPieces(behavior.speed)}
131
124
  maxValue={8}
132
- onChange={(pieces) => binding.apply(() => (behavior.speed = buildCurve(pieces) as never))}
125
+ onChange={(pieces) => binding.apply(() => (behavior.speed = buildCurve(pieces)))}
133
126
  />
134
127
  </div>
135
128
  <Row label="Dampen">
@@ -146,6 +139,7 @@ export function LimitSpeedOverLifeModule({binding}: ModuleProps) {
146
139
  );
147
140
  }
148
141
 
142
+ /** The Force over Lifetime module (Unity's Force over Lifetime): a constant push per axis. */
149
143
  export function ForceOverLifeModule({binding}: ModuleProps) {
150
144
  return (
151
145
  <BehaviorModule<ForceOverLife>
@@ -334,6 +328,7 @@ export function AttractorModule({binding}: ModuleProps) {
334
328
  );
335
329
  }
336
330
 
331
+ /** The Gravity module: pulls particles towards a point. */
337
332
  export function GravityModule({binding}: ModuleProps) {
338
333
  return (
339
334
  <BehaviorModule<ApplyForce>
@@ -367,6 +362,7 @@ export function GravityModule({binding}: ModuleProps) {
367
362
  );
368
363
  }
369
364
 
365
+ /** The Rotation over Lifetime module (Unity's Rotation over Lifetime): spin rate over the particle's life. */
370
366
  export function RotationOverLifeModule({binding}: ModuleProps) {
371
367
  return (
372
368
  <BehaviorModule<RotationOverLife>
@@ -653,6 +649,7 @@ export function SequencesModule({binding, textureOptions}: ModuleProps & {textur
653
649
  );
654
650
  }
655
651
 
652
+ /** The Noise module (Unity's Noise): moves and turns particles by a noise field. */
656
653
  export function NoiseModule({binding}: ModuleProps) {
657
654
  return (
658
655
  <BehaviorModule<Noise>
@@ -753,6 +750,7 @@ function Vector3Row(props: {
753
750
  );
754
751
  }
755
752
 
753
+ /** The Turbulence module: a curl noise force field. */
756
754
  export function TurbulenceModule({binding}: ModuleProps) {
757
755
  return (
758
756
  <BehaviorModule<TurbulenceField>
@@ -822,6 +820,7 @@ export function TurbulenceModule({binding}: ModuleProps) {
822
820
  );
823
821
  }
824
822
 
823
+ /** The Randomize Direction module: tilts each particle's start direction by a random angle. */
825
824
  export function EmitDirectionModule({binding}: ModuleProps) {
826
825
  return (
827
826
  <BehaviorModule<ChangeEmitDirection>
@@ -833,16 +832,17 @@ export function EmitDirectionModule({binding}: ModuleProps) {
833
832
  {(behavior) => (
834
833
  <ValueField
835
834
  label="Angle (rad)"
836
- generator={behavior.angle as never}
835
+ generator={behavior.angle}
836
+ curve={false}
837
837
  min={0}
838
- curveMax={Math.PI}
839
- onChange={(g) => binding.apply(() => (behavior.angle = g as never))}
838
+ onChange={(g) => binding.apply(() => (behavior.angle = g))}
840
839
  />
841
840
  )}
842
841
  </BehaviorModule>
843
842
  );
844
843
  }
845
844
 
845
+ /** The Collision module (Unity's Collision): particles bounce off or die on a ground plane. */
846
846
  export function CollisionModule({binding}: ModuleProps) {
847
847
  // Like Unity: Collision stores bounce only. Colliders are host-provided via
848
848
  // setPhysicsResolver, asked when a particle is tested — the editor never invents a ground plane.
@@ -897,6 +897,7 @@ function SpeedRangeRows({binding, behavior}: {binding: EffectBinding; behavior:
897
897
  );
898
898
  }
899
899
 
900
+ /** The Color by Speed module (Unity's Color by Speed): a gradient over a speed range. */
900
901
  export function ColorBySpeedModule({binding}: ModuleProps) {
901
902
  return (
902
903
  <BehaviorModule<ColorBySpeed>
@@ -924,6 +925,7 @@ export function ColorBySpeedModule({binding}: ModuleProps) {
924
925
  );
925
926
  }
926
927
 
928
+ /** The Size by Speed module (Unity's Size by Speed): size over a speed range. */
927
929
  export function SizeBySpeedModule({binding}: ModuleProps) {
928
930
  return (
929
931
  <BehaviorModule<SizeBySpeed>
@@ -932,7 +934,7 @@ export function SizeBySpeedModule({binding}: ModuleProps) {
932
934
  type="SizeBySpeed"
933
935
  create={() =>
934
936
  new SizeBySpeed(
935
- buildScalar({mode: 'curve', value: 1, min: 0, max: 1, curve: [0.5, 0.8, 1.2, 1.5]}) as never,
937
+ buildScalar({mode: 'curve', value: 1, min: 0, max: 1, curve: [0.5, 0.8, 1.2, 1.5]}),
936
938
  new IntervalValue(0, 5)
937
939
  )
938
940
  }
@@ -941,9 +943,9 @@ export function SizeBySpeedModule({binding}: ModuleProps) {
941
943
  <>
942
944
  <div style={{marginTop: 6}}>
943
945
  <CurveEditor
944
- pieces={readPieces(behavior.size as never)}
946
+ pieces={readPieces(behavior.size)}
945
947
  maxValue={2}
946
- onChange={(pieces) => binding.apply(() => (behavior.size = buildCurve(pieces) as never))}
948
+ onChange={(pieces) => binding.apply(() => (behavior.size = buildCurve(pieces)))}
947
949
  />
948
950
  </div>
949
951
  <SpeedRangeRows binding={binding} behavior={behavior} />
@@ -953,6 +955,7 @@ export function SizeBySpeedModule({binding}: ModuleProps) {
953
955
  );
954
956
  }
955
957
 
958
+ /** The Rotation by Speed module (Unity's Rotation by Speed): spin rate over a speed range. */
956
959
  export function RotationBySpeedModule({binding}: ModuleProps) {
957
960
  return (
958
961
  <BehaviorModule<RotationBySpeed>
@@ -977,6 +980,7 @@ export function RotationBySpeedModule({binding}: ModuleProps) {
977
980
  );
978
981
  }
979
982
 
983
+ /** The Orbit over Lifetime module (Unity's Velocity over Lifetime orbital): turns particles about an axis. */
980
984
  export function OrbitOverLifeModule({binding}: ModuleProps) {
981
985
  return (
982
986
  <BehaviorModule<OrbitOverLife>
@@ -997,6 +1001,7 @@ export function OrbitOverLifeModule({binding}: ModuleProps) {
997
1001
  );
998
1002
  }
999
1003
 
1004
+ /** The Width over Trail module (Unity's Trails width over trail): a trail's width along its length. */
1000
1005
  export function WidthOverTrailModule({binding}: ModuleProps) {
1001
1006
  return (
1002
1007
  <BehaviorModule<WidthOverLength>
@@ -1004,18 +1009,16 @@ export function WidthOverTrailModule({binding}: ModuleProps) {
1004
1009
  title="Width over Trail"
1005
1010
  type="WidthOverLength"
1006
1011
  create={() =>
1007
- new WidthOverLength(
1008
- buildScalar({mode: 'curve', value: 1, min: 0, max: 1, curve: [1, 0.75, 0.4, 0]}) as never
1009
- )
1012
+ new WidthOverLength(buildScalar({mode: 'curve', value: 1, min: 0, max: 1, curve: [1, 0.75, 0.4, 0]}))
1010
1013
  }
1011
1014
  >
1012
1015
  {(behavior) => (
1013
1016
  <>
1014
1017
  <div style={{marginTop: 6}}>
1015
1018
  <CurveEditor
1016
- pieces={readPieces(behavior.width as never)}
1019
+ pieces={readPieces(behavior.width)}
1017
1020
  maxValue={2}
1018
- onChange={(pieces) => binding.apply(() => (behavior.width = buildCurve(pieces) as never))}
1021
+ onChange={(pieces) => binding.apply(() => (behavior.width = buildCurve(pieces)))}
1019
1022
  />
1020
1023
  </div>
1021
1024
  {/* Off: the curve multiplies the particle size. On: it is the width in world units (files from 0.21 and earlier). */}
@@ -1033,10 +1036,11 @@ export function WidthOverTrailModule({binding}: ModuleProps) {
1033
1036
 
1034
1037
  /** How many of the first frames a start-frame generator picks from: its exclusive upper bound. */
1035
1038
  function startFrameCount(startFrame: FrameOverLife['startFrame']): number {
1036
- const state = readScalar(startFrame as never);
1039
+ const state = readScalar(startFrame);
1037
1040
  return Math.round(state.mode === 'random' ? state.max : state.value);
1038
1041
  }
1039
1042
 
1043
+ /** The Texture Sheet Animation module (Unity's Texture Sheet Animation): plays the sheet's frames by life or by frame rate. */
1040
1044
  export function TextureSheetModule({binding}: ModuleProps) {
1041
1045
  const system = binding.system;
1042
1046
  const frameBehavior = findBehavior<FrameOverLife>(system.behaviors, 'FrameOverLife');
@@ -1045,7 +1049,7 @@ export function TextureSheetModule({binding}: ModuleProps) {
1045
1049
  const u = Math.max(1, system.uTileCount);
1046
1050
  const v = Math.max(1, system.vTileCount);
1047
1051
  // Start tile → cell (row-major, top-left origin — matches the shader's tile UV mapping).
1048
- const startIdx = Math.min(Math.max(0, Math.round(readScalar(system.startTileIndex as never).value)), tiles - 1);
1052
+ const startIdx = Math.min(Math.max(0, Math.round(readScalar(system.startTileIndex).value)), tiles - 1);
1049
1053
  const startCol = startIdx % u;
1050
1054
  const startRow = Math.floor(startIdx / u);
1051
1055
  return (
@@ -1134,7 +1138,7 @@ export function TextureSheetModule({binding}: ModuleProps) {
1134
1138
  </Row>
1135
1139
  <Row label="Start tile">
1136
1140
  <NumberField
1137
- value={readScalar(system.startTileIndex as never).value}
1141
+ value={readScalar(system.startTileIndex).value}
1138
1142
  min={0}
1139
1143
  step={1}
1140
1144
  onChange={(v) => binding.apply((s) => (s.startTileIndex = new ConstantValue(Math.round(v))))}
@@ -1158,7 +1162,7 @@ export function TextureSheetModule({binding}: ModuleProps) {
1158
1162
  min: 0,
1159
1163
  max: tiles - 1,
1160
1164
  curve: [0, (tiles - 1) / 3, (2 * (tiles - 1)) / 3, tiles - 1],
1161
- }) as never
1165
+ })
1162
1166
  )
1163
1167
  );
1164
1168
  }
@@ -1264,9 +1268,7 @@ export function TextureSheetModule({binding}: ModuleProps) {
1264
1268
  <CurveEditor
1265
1269
  pieces={readPieces(frameBehavior.frame)}
1266
1270
  maxValue={Math.max(1, tiles - 1)}
1267
- onChange={(pieces) =>
1268
- binding.apply(() => (frameBehavior.frame = buildCurve(pieces) as never))
1269
- }
1271
+ onChange={(pieces) => binding.apply(() => (frameBehavior.frame = buildCurve(pieces)))}
1270
1272
  />
1271
1273
  </div>
1272
1274
  </>
@@ -2,6 +2,7 @@ import React, {useEffect, useState} from 'react';
2
2
  import {InspectorLabel} from './InspectorLabel.js';
3
3
  import {inputStyle, rowStyle} from './theme.js';
4
4
 
5
+ /** An inspector row: a label, with an optional hint, and its controls. */
5
6
  export function Row(props: {label: string; hint?: string; hintKey?: string; children: React.ReactNode}) {
6
7
  return (
7
8
  <div style={rowStyle}>
@@ -53,6 +54,7 @@ export function NumberField(props: {
53
54
  );
54
55
  }
55
56
 
57
+ /** A checkbox bound to a boolean. */
56
58
  export function CheckboxField(props: {value: boolean; onChange: (value: boolean) => void}) {
57
59
  return (
58
60
  <input
@@ -65,9 +67,18 @@ export function CheckboxField(props: {value: boolean; onChange: (value: boolean)
65
67
  );
66
68
  }
67
69
 
70
+ /** A select bound to a value, from a list of options. */
68
71
  export function SelectField<T extends string | number>(props: {
72
+ /** The selected value. */
69
73
  value: T;
70
- options: Array<{value: T; label: string}>;
74
+ /** The options, in menu order. */
75
+ options: Array<{
76
+ /** The option's value. */
77
+ value: T;
78
+ /** Its text. */
79
+ label: string;
80
+ }>;
81
+ /** Receives the picked value, a number when the values are numbers. */
71
82
  onChange: (value: T) => void;
72
83
  }) {
73
84
  const numeric = typeof props.value === 'number';
@@ -0,0 +1,178 @@
1
+ import {ChevronDownIcon, ChevronUpIcon, MapPinIcon, XMarkIcon} from '@heroicons/react/24/solid';
2
+ import React from 'react';
3
+ import {AssetsPanel, type AssetsPanelProps} from '../AssetsPanel.js';
4
+ import {iconStyle} from '../icons.js';
5
+ import {buttonStyle, theme} from '../theme.js';
6
+ import {
7
+ activeSquareButtonStyle,
8
+ dockIconButtonStyle,
9
+ dockTitleStyle,
10
+ DRAWER_HEIGHT,
11
+ TIMELINE_HEIGHT,
12
+ } from './hostStyles.js';
13
+
14
+ /** Props of {@link AssetsDrawer}. */
15
+ export interface AssetsDrawerProps {
16
+ /** The drawer is shown. */
17
+ open: boolean;
18
+ /** The drawer is docked above the timeline instead of floating over the viewport. */
19
+ pinned: boolean;
20
+ /** Pin or unpin it (it stays open). */
21
+ onTogglePin(): void;
22
+ /** Close and unpin it. */
23
+ onClose(): void;
24
+ /** The Assets browser inside. */
25
+ panel: AssetsPanelProps;
26
+ }
27
+
28
+ /** The Assets browser, floating over the viewport or pinned above the timeline. */
29
+ export function AssetsDrawer(props: AssetsDrawerProps): React.ReactElement {
30
+ const {open, pinned} = props;
31
+ return (
32
+ <div
33
+ aria-hidden={!open && !pinned}
34
+ style={{
35
+ height: DRAWER_HEIGHT,
36
+ display: 'flex',
37
+ flexDirection: 'column',
38
+ background: theme.panelBg,
39
+ borderTop: `1px solid ${theme.border}`,
40
+ ...(pinned
41
+ ? {flexShrink: 0}
42
+ : {
43
+ position: 'absolute',
44
+ left: 0,
45
+ right: 0,
46
+ bottom: 0,
47
+ zIndex: 20,
48
+ background: '#0a0f20',
49
+ boxShadow: '0 -10px 30px rgba(0, 0, 0, 0.5)',
50
+ transform: open ? 'none' : 'translateY(100%)',
51
+ opacity: open ? 1 : 0,
52
+ pointerEvents: open ? 'auto' : 'none',
53
+ transition: 'transform 0.18s ease, opacity 0.18s ease',
54
+ }),
55
+ }}
56
+ >
57
+ <div
58
+ style={{
59
+ display: 'flex',
60
+ alignItems: 'center',
61
+ gap: 6,
62
+ padding: '4px 6px 4px 12px',
63
+ borderBottom: `1px solid ${theme.border}`,
64
+ }}
65
+ >
66
+ <span style={dockTitleStyle}>Assets</span>
67
+ <span style={{fontSize: 11, color: theme.textDim}}>Ctrl+Space</span>
68
+ <span style={{flex: 1}} />
69
+ <button
70
+ type="button"
71
+ className="qe-hover"
72
+ title={pinned ? 'Unpin — float over the viewport again' : 'Pin — keep it docked above the timeline'}
73
+ style={{...dockIconButtonStyle, ...(pinned ? activeSquareButtonStyle : {})}}
74
+ onClick={props.onTogglePin}
75
+ >
76
+ <MapPinIcon style={iconStyle(13)} />
77
+ </button>
78
+ <button
79
+ type="button"
80
+ className="qe-hover"
81
+ title="Close (Esc)"
82
+ style={dockIconButtonStyle}
83
+ onClick={props.onClose}
84
+ >
85
+ <XMarkIcon style={iconStyle(13)} />
86
+ </button>
87
+ </div>
88
+ <AssetsPanel {...props.panel} />
89
+ </div>
90
+ );
91
+ }
92
+
93
+ /** Props of {@link TimelineDock}. */
94
+ export interface TimelineDockProps {
95
+ /** The timeline is collapsed to its header. */
96
+ collapsed: boolean;
97
+ /** Collapse or expand it. */
98
+ onToggleCollapsed(): void;
99
+ /** Name of the focused effect, if any. */
100
+ effectName?: string;
101
+ /** Assets in the project, counted on the Assets button. */
102
+ assetCount: number;
103
+ /** The Assets button shows as switched on. */
104
+ assetsActive: boolean;
105
+ /** The Assets button. */
106
+ onToggleAssets(): void;
107
+ /** The timeline, or `null` when nothing is focused. */
108
+ children: React.ReactNode;
109
+ }
110
+
111
+ /** The bottom dock: the focused effect's timeline, and the Assets button. */
112
+ export function TimelineDock(props: TimelineDockProps): React.ReactElement {
113
+ const {collapsed} = props;
114
+ return (
115
+ <div
116
+ style={{
117
+ flexShrink: 0,
118
+ borderTop: `1px solid ${theme.border}`,
119
+ background: theme.panelBg,
120
+ display: 'flex',
121
+ flexDirection: 'column',
122
+ }}
123
+ >
124
+ <div style={{display: 'flex', alignItems: 'center', gap: 6, padding: '3px 6px'}}>
125
+ <button
126
+ type="button"
127
+ className="qe-hover-bg"
128
+ title={collapsed ? 'Expand timeline' : 'Collapse timeline'}
129
+ style={{...dockIconButtonStyle, border: 'none', background: 'transparent'}}
130
+ onClick={props.onToggleCollapsed}
131
+ >
132
+ {collapsed ? <ChevronUpIcon style={iconStyle(13)} /> : <ChevronDownIcon style={iconStyle(13)} />}
133
+ </button>
134
+ <span style={dockTitleStyle}>Timeline</span>
135
+ {props.effectName !== undefined && (
136
+ <span
137
+ style={{
138
+ fontSize: 12,
139
+ color: theme.textDim,
140
+ overflow: 'hidden',
141
+ textOverflow: 'ellipsis',
142
+ whiteSpace: 'nowrap',
143
+ }}
144
+ >
145
+ {props.effectName}
146
+ </span>
147
+ )}
148
+ <span style={{flex: 1}} />
149
+ <button
150
+ type="button"
151
+ className="qe-hover"
152
+ title="Assets (Ctrl+Space)"
153
+ style={{
154
+ ...buttonStyle,
155
+ padding: '3px 10px',
156
+ ...(props.assetsActive ? activeSquareButtonStyle : {}),
157
+ }}
158
+ onClick={props.onToggleAssets}
159
+ >
160
+ Assets{props.assetCount > 0 ? ` · ${props.assetCount}` : ''}
161
+ </button>
162
+ </div>
163
+ <div
164
+ style={{
165
+ height: TIMELINE_HEIGHT,
166
+ display: collapsed ? 'none' : 'flex',
167
+ borderTop: `1px solid ${theme.border}`,
168
+ }}
169
+ >
170
+ {props.children ?? (
171
+ <div style={{padding: '14px', fontSize: 12, color: theme.textDim}}>
172
+ Select an effect in the Scene to see its timeline.
173
+ </div>
174
+ )}
175
+ </div>
176
+ </div>
177
+ );
178
+ }