@twick/studio 0.15.12 → 0.15.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
1
  import { TextPanelState, TextPanelActions } from '../../hooks/use-text-panel';
2
2
 
3
3
  export type TextPanelProps = TextPanelState & TextPanelActions;
4
- export declare function TextPanel({ textContent, fontSize, selectedFont, isBold, isItalic, textColor, strokeColor, applyShadow, shadowColor, strokeWidth, fonts, operation, setTextContent, setFontSize, setSelectedFont, setIsBold, setIsItalic, setTextColor, setStrokeColor, setApplyShadow, setShadowColor, setStrokeWidth, handleApplyChanges, }: TextPanelProps): import("react/jsx-runtime").JSX.Element;
4
+ export declare function TextPanel({ textContent, fontSize, selectedFont, isBold, isItalic, textColor, strokeColor, applyShadow, shadowColor, strokeWidth, applyBackground, backgroundColor, backgroundOpacity, fonts, operation, setTextContent, setFontSize, setSelectedFont, setIsBold, setIsItalic, setTextColor, setStrokeColor, setApplyShadow, setShadowColor, setStrokeWidth, setApplyBackground, setBackgroundColor, setBackgroundOpacity, handleApplyChanges, }: TextPanelProps): import("react/jsx-runtime").JSX.Element;
@@ -27,6 +27,9 @@ export interface TextPanelState {
27
27
  applyShadow: boolean;
28
28
  shadowColor: string;
29
29
  strokeWidth: number;
30
+ applyBackground: boolean;
31
+ backgroundColor: string;
32
+ backgroundOpacity: number;
30
33
  fonts: string[];
31
34
  operation: string;
32
35
  }
@@ -41,6 +44,9 @@ export interface TextPanelActions {
41
44
  setApplyShadow: (shadow: boolean) => void;
42
45
  setShadowColor: (color: string) => void;
43
46
  setStrokeWidth: (width: number) => void;
47
+ setApplyBackground: (apply: boolean) => void;
48
+ setBackgroundColor: (color: string) => void;
49
+ setBackgroundOpacity: (opacity: number) => void;
44
50
  handleApplyChanges: () => void;
45
51
  }
46
52
  export declare const useTextPanel: ({ selectedElement, addElement, updateElement, }: {
package/dist/index.js CHANGED
@@ -113,49 +113,49 @@ const createLucideIcon = (iconName, iconNode) => {
113
113
  * This source code is licensed under the ISC license.
114
114
  * See the LICENSE file in the root directory of this source tree.
115
115
  */
116
- const __iconNode$s = [
116
+ const __iconNode$u = [
117
117
  ["rect", { width: "18", height: "14", x: "3", y: "5", rx: "2", ry: "2", key: "12ruh7" }],
118
118
  ["path", { d: "M7 15h4M15 15h2M7 11h2M13 11h4", key: "1ueiar" }]
119
119
  ];
120
- const Captions = createLucideIcon("captions", __iconNode$s);
120
+ const Captions = createLucideIcon("captions", __iconNode$u);
121
121
  /**
122
122
  * @license lucide-react v0.511.0 - ISC
123
123
  *
124
124
  * This source code is licensed under the ISC license.
125
125
  * See the LICENSE file in the root directory of this source tree.
126
126
  */
127
- const __iconNode$r = [
127
+ const __iconNode$t = [
128
128
  ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
129
129
  ["path", { d: "m9 12 2 2 4-4", key: "dzmm74" }]
130
130
  ];
131
- const CircleCheck = createLucideIcon("circle-check", __iconNode$r);
131
+ const CircleCheck = createLucideIcon("circle-check", __iconNode$t);
132
132
  /**
133
133
  * @license lucide-react v0.511.0 - ISC
134
134
  *
135
135
  * This source code is licensed under the ISC license.
136
136
  * See the LICENSE file in the root directory of this source tree.
137
137
  */
138
- const __iconNode$q = [
138
+ const __iconNode$s = [
139
139
  ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
140
140
  ["path", { d: "m15 9-6 6", key: "1uzhvr" }],
141
141
  ["path", { d: "m9 9 6 6", key: "z0biqf" }]
142
142
  ];
143
- const CircleX = createLucideIcon("circle-x", __iconNode$q);
143
+ const CircleX = createLucideIcon("circle-x", __iconNode$s);
144
144
  /**
145
145
  * @license lucide-react v0.511.0 - ISC
146
146
  *
147
147
  * This source code is licensed under the ISC license.
148
148
  * See the LICENSE file in the root directory of this source tree.
149
149
  */
150
- const __iconNode$p = [["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]];
151
- const Circle = createLucideIcon("circle", __iconNode$p);
150
+ const __iconNode$r = [["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]];
151
+ const Circle = createLucideIcon("circle", __iconNode$r);
152
152
  /**
153
153
  * @license lucide-react v0.511.0 - ISC
154
154
  *
155
155
  * This source code is licensed under the ISC license.
156
156
  * See the LICENSE file in the root directory of this source tree.
157
157
  */
158
- const __iconNode$o = [
158
+ const __iconNode$q = [
159
159
  [
160
160
  "path",
161
161
  { d: "M20.2 6 3 11l-.9-2.4c-.3-1.1.3-2.2 1.3-2.5l13.5-4c1.1-.3 2.2.3 2.5 1.3Z", key: "1tn4o7" }
@@ -164,112 +164,132 @@ const __iconNode$o = [
164
164
  ["path", { d: "m12.4 3.4 3.1 4", key: "6hsd6n" }],
165
165
  ["path", { d: "M3 11h18v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z", key: "ltgou9" }]
166
166
  ];
167
- const Clapperboard = createLucideIcon("clapperboard", __iconNode$o);
167
+ const Clapperboard = createLucideIcon("clapperboard", __iconNode$q);
168
168
  /**
169
169
  * @license lucide-react v0.511.0 - ISC
170
170
  *
171
171
  * This source code is licensed under the ISC license.
172
172
  * See the LICENSE file in the root directory of this source tree.
173
173
  */
174
- const __iconNode$n = [
174
+ const __iconNode$p = [
175
175
  ["path", { d: "M12 15V3", key: "m9g1x1" }],
176
176
  ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }],
177
177
  ["path", { d: "m7 10 5 5 5-5", key: "brsn70" }]
178
178
  ];
179
- const Download = createLucideIcon("download", __iconNode$n);
179
+ const Download = createLucideIcon("download", __iconNode$p);
180
180
  /**
181
181
  * @license lucide-react v0.511.0 - ISC
182
182
  *
183
183
  * This source code is licensed under the ISC license.
184
184
  * See the LICENSE file in the root directory of this source tree.
185
185
  */
186
- const __iconNode$m = [
186
+ const __iconNode$o = [
187
187
  ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", key: "1rqfz7" }],
188
188
  ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4", key: "tnqrlb" }]
189
189
  ];
190
- const File = createLucideIcon("file", __iconNode$m);
190
+ const File = createLucideIcon("file", __iconNode$o);
191
191
  /**
192
192
  * @license lucide-react v0.511.0 - ISC
193
193
  *
194
194
  * This source code is licensed under the ISC license.
195
195
  * See the LICENSE file in the root directory of this source tree.
196
196
  */
197
- const __iconNode$l = [
197
+ const __iconNode$n = [
198
198
  ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2", key: "1m3agn" }],
199
199
  ["circle", { cx: "9", cy: "9", r: "2", key: "af1f0g" }],
200
200
  ["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21", key: "1xmnt7" }]
201
201
  ];
202
- const Image = createLucideIcon("image", __iconNode$l);
202
+ const Image = createLucideIcon("image", __iconNode$n);
203
203
  /**
204
204
  * @license lucide-react v0.511.0 - ISC
205
205
  *
206
206
  * This source code is licensed under the ISC license.
207
207
  * See the LICENSE file in the root directory of this source tree.
208
208
  */
209
- const __iconNode$k = [
209
+ const __iconNode$m = [
210
210
  ["path", { d: "M6 16c5 0 7-8 12-8a4 4 0 0 1 0 8c-5 0-7-8-12-8a4 4 0 1 0 0 8", key: "18ogeb" }]
211
211
  ];
212
- const Infinity = createLucideIcon("infinity", __iconNode$k);
212
+ const Infinity = createLucideIcon("infinity", __iconNode$m);
213
213
  /**
214
214
  * @license lucide-react v0.511.0 - ISC
215
215
  *
216
216
  * This source code is licensed under the ISC license.
217
217
  * See the LICENSE file in the root directory of this source tree.
218
218
  */
219
- const __iconNode$j = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
220
- const LoaderCircle = createLucideIcon("loader-circle", __iconNode$j);
219
+ const __iconNode$l = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
220
+ const LoaderCircle = createLucideIcon("loader-circle", __iconNode$l);
221
221
  /**
222
222
  * @license lucide-react v0.511.0 - ISC
223
223
  *
224
224
  * This source code is licensed under the ISC license.
225
225
  * See the LICENSE file in the root directory of this source tree.
226
226
  */
227
- const __iconNode$i = [
227
+ const __iconNode$k = [
228
228
  ["path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z", key: "1lielz" }]
229
229
  ];
230
- const MessageSquare = createLucideIcon("message-square", __iconNode$i);
230
+ const MessageSquare = createLucideIcon("message-square", __iconNode$k);
231
231
  /**
232
232
  * @license lucide-react v0.511.0 - ISC
233
233
  *
234
234
  * This source code is licensed under the ISC license.
235
235
  * See the LICENSE file in the root directory of this source tree.
236
236
  */
237
- const __iconNode$h = [
237
+ const __iconNode$j = [
238
238
  ["path", { d: "M9 18V5l12-2v13", key: "1jmyc2" }],
239
239
  ["circle", { cx: "6", cy: "18", r: "3", key: "fqmcym" }],
240
240
  ["circle", { cx: "18", cy: "16", r: "3", key: "1hluhg" }]
241
241
  ];
242
- const Music = createLucideIcon("music", __iconNode$h);
242
+ const Music = createLucideIcon("music", __iconNode$j);
243
243
  /**
244
244
  * @license lucide-react v0.511.0 - ISC
245
245
  *
246
246
  * This source code is licensed under the ISC license.
247
247
  * See the LICENSE file in the root directory of this source tree.
248
248
  */
249
- const __iconNode$g = [
249
+ const __iconNode$i = [
250
250
  ["rect", { x: "14", y: "4", width: "4", height: "16", rx: "1", key: "zuxfzm" }],
251
251
  ["rect", { x: "6", y: "4", width: "4", height: "16", rx: "1", key: "1okwgv" }]
252
252
  ];
253
- const Pause = createLucideIcon("pause", __iconNode$g);
253
+ const Pause = createLucideIcon("pause", __iconNode$i);
254
254
  /**
255
255
  * @license lucide-react v0.511.0 - ISC
256
256
  *
257
257
  * This source code is licensed under the ISC license.
258
258
  * See the LICENSE file in the root directory of this source tree.
259
259
  */
260
- const __iconNode$f = [["polygon", { points: "6 3 20 12 6 21 6 3", key: "1oa8hb" }]];
261
- const Play = createLucideIcon("play", __iconNode$f);
260
+ const __iconNode$h = [["polygon", { points: "6 3 20 12 6 21 6 3", key: "1oa8hb" }]];
261
+ const Play = createLucideIcon("play", __iconNode$h);
262
262
  /**
263
263
  * @license lucide-react v0.511.0 - ISC
264
264
  *
265
265
  * This source code is licensed under the ISC license.
266
266
  * See the LICENSE file in the root directory of this source tree.
267
267
  */
268
- const __iconNode$e = [
268
+ const __iconNode$g = [
269
269
  ["path", { d: "M5 12h14", key: "1ays0h" }],
270
270
  ["path", { d: "M12 5v14", key: "s699le" }]
271
271
  ];
272
- const Plus = createLucideIcon("plus", __iconNode$e);
272
+ const Plus = createLucideIcon("plus", __iconNode$g);
273
+ /**
274
+ * @license lucide-react v0.511.0 - ISC
275
+ *
276
+ * This source code is licensed under the ISC license.
277
+ * See the LICENSE file in the root directory of this source tree.
278
+ */
279
+ const __iconNode$f = [
280
+ ["rect", { width: "20", height: "12", x: "2", y: "6", rx: "2", key: "9lu3g6" }]
281
+ ];
282
+ const RectangleHorizontal = createLucideIcon("rectangle-horizontal", __iconNode$f);
283
+ /**
284
+ * @license lucide-react v0.511.0 - ISC
285
+ *
286
+ * This source code is licensed under the ISC license.
287
+ * See the LICENSE file in the root directory of this source tree.
288
+ */
289
+ const __iconNode$e = [
290
+ ["rect", { width: "12", height: "20", x: "6", y: "2", rx: "2", key: "1oxtiu" }]
291
+ ];
292
+ const RectangleVertical = createLucideIcon("rectangle-vertical", __iconNode$e);
273
293
  /**
274
294
  * @license lucide-react v0.511.0 - ISC
275
295
  *
@@ -566,6 +586,15 @@ const StudioHeader = ({
566
586
  setOrientation(orientation2);
567
587
  }
568
588
  }, []);
589
+ const handleOrientationChange = (nextOrientation) => {
590
+ if (nextOrientation === orientation) return;
591
+ const confirmMessage = "Changing orientation will create a new project with the new resolution. Do you want to continue?";
592
+ if (!window.confirm(confirmMessage)) {
593
+ return;
594
+ }
595
+ onNewProject();
596
+ setOrientation(nextOrientation);
597
+ };
569
598
  react.useEffect(() => {
570
599
  if (orientation === "horizontal") {
571
600
  localStorage.setItem("orientation", "horizontal");
@@ -576,10 +605,31 @@ const StudioHeader = ({
576
605
  }
577
606
  }, [orientation]);
578
607
  return /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "header", children: [
579
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-container", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-container", children: [
608
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-container", children: [
580
609
  /* @__PURE__ */ jsxRuntime.jsx(Clapperboard, { className: "icon-lg accent-purple" }),
581
610
  /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-gradient", children: "Twick Studio" })
582
- ] }) }),
611
+ ] }),
612
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-container", style: { gap: "0.5rem" }, children: [
613
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm opacity-80", children: "Orientation" }),
614
+ /* @__PURE__ */ jsxRuntime.jsx(
615
+ "button",
616
+ {
617
+ className: `btn-ghost ${orientation === "vertical" ? "btn-primary" : ""}`,
618
+ title: "Portrait (720×1280)",
619
+ onClick: () => handleOrientationChange("vertical"),
620
+ children: /* @__PURE__ */ jsxRuntime.jsx(RectangleVertical, { className: "icon-sm" })
621
+ }
622
+ ),
623
+ /* @__PURE__ */ jsxRuntime.jsx(
624
+ "button",
625
+ {
626
+ className: `btn-ghost ${orientation === "horizontal" ? "btn-primary" : ""}`,
627
+ title: "Landscape (1280×720)",
628
+ onClick: () => handleOrientationChange("horizontal"),
629
+ children: /* @__PURE__ */ jsxRuntime.jsx(RectangleHorizontal, { className: "icon-sm" })
630
+ }
631
+ )
632
+ ] }),
583
633
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-container", children: [
584
634
  /* @__PURE__ */ jsxRuntime.jsxs(
585
635
  "button",
@@ -1476,6 +1526,9 @@ function TextPanel({
1476
1526
  applyShadow,
1477
1527
  shadowColor,
1478
1528
  strokeWidth,
1529
+ applyBackground,
1530
+ backgroundColor,
1531
+ backgroundOpacity,
1479
1532
  fonts,
1480
1533
  operation,
1481
1534
  setTextContent,
@@ -1488,6 +1541,9 @@ function TextPanel({
1488
1541
  setApplyShadow,
1489
1542
  setShadowColor,
1490
1543
  setStrokeWidth,
1544
+ setApplyBackground,
1545
+ setBackgroundColor,
1546
+ setBackgroundOpacity,
1491
1547
  handleApplyChanges
1492
1548
  }) {
1493
1549
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
@@ -1656,6 +1712,69 @@ function TextPanel({
1656
1712
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "slider-value", children: strokeWidth })
1657
1713
  ] })
1658
1714
  ] }),
1715
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-section", children: [
1716
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-dark", children: "Background" }),
1717
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "color-section", children: [
1718
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "checkbox-control", children: /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "checkbox-label", children: [
1719
+ /* @__PURE__ */ jsxRuntime.jsx(
1720
+ "input",
1721
+ {
1722
+ type: "checkbox",
1723
+ checked: applyBackground,
1724
+ onChange: (e) => setApplyBackground(e.target.checked),
1725
+ className: "checkbox-purple"
1726
+ }
1727
+ ),
1728
+ "Apply Background"
1729
+ ] }) }),
1730
+ applyBackground && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1731
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "color-control", children: [
1732
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-small", children: "Background Color" }),
1733
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "color-inputs", children: [
1734
+ /* @__PURE__ */ jsxRuntime.jsx(
1735
+ "input",
1736
+ {
1737
+ type: "color",
1738
+ value: backgroundColor,
1739
+ onChange: (e) => setBackgroundColor(e.target.value),
1740
+ className: "color-picker"
1741
+ }
1742
+ ),
1743
+ /* @__PURE__ */ jsxRuntime.jsx(
1744
+ "input",
1745
+ {
1746
+ type: "text",
1747
+ value: backgroundColor,
1748
+ onChange: (e) => setBackgroundColor(e.target.value),
1749
+ className: "color-text"
1750
+ }
1751
+ )
1752
+ ] })
1753
+ ] }),
1754
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-section", children: [
1755
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-small", children: "Background Opacity" }),
1756
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "slider-container", children: [
1757
+ /* @__PURE__ */ jsxRuntime.jsx(
1758
+ "input",
1759
+ {
1760
+ type: "range",
1761
+ min: "0",
1762
+ max: "1",
1763
+ step: 0.1,
1764
+ value: backgroundOpacity,
1765
+ onChange: (e) => setBackgroundOpacity(Number(e.target.value)),
1766
+ className: "slider-purple"
1767
+ }
1768
+ ),
1769
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "slider-value", children: [
1770
+ Math.round(backgroundOpacity * 100),
1771
+ "%"
1772
+ ] })
1773
+ ] })
1774
+ ] })
1775
+ ] })
1776
+ ] })
1777
+ ] }),
1659
1778
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: handleApplyChanges, className: "btn-primary w-full", children: operation }) })
1660
1779
  ] });
1661
1780
  }
@@ -1690,6 +1809,9 @@ const useTextPanel = ({
1690
1809
  const [applyShadow, setApplyShadow] = react.useState(DEFAULT_TEXT_PROPS.applyShadow);
1691
1810
  const [shadowColor, setShadowColor] = react.useState(DEFAULT_TEXT_PROPS.shadowColor);
1692
1811
  const [strokeWidth, setStrokeWidth] = react.useState(DEFAULT_TEXT_PROPS.strokeWidth);
1812
+ const [applyBackground, setApplyBackground] = react.useState(false);
1813
+ const [backgroundColor, setBackgroundColor] = react.useState("#FACC15");
1814
+ const [backgroundOpacity, setBackgroundOpacity] = react.useState(1);
1693
1815
  const fonts = Object.values(VideoEditor.AVAILABLE_TEXT_FONTS);
1694
1816
  const handleApplyChanges = async () => {
1695
1817
  let textElement;
@@ -1704,35 +1826,41 @@ const useTextPanel = ({
1704
1826
  textElement.setStrokeColor(strokeColor);
1705
1827
  textElement.setLineWidth(strokeWidth);
1706
1828
  textElement.setTextAlign(DEFAULT_TEXT_PROPS.textAlign);
1829
+ const nextProps = { ...textElement.getProps() };
1707
1830
  if (applyShadow) {
1708
- textElement.setProps({
1709
- ...textElement.getProps(),
1710
- shadowColor,
1711
- shadowOffset: DEFAULT_TEXT_PROPS.shadowOffset,
1712
- shadowBlur: DEFAULT_TEXT_PROPS.shadowBlur,
1713
- shadowOpacity: DEFAULT_TEXT_PROPS.shadowOpacity
1714
- });
1831
+ nextProps.shadowColor = shadowColor;
1832
+ nextProps.shadowOffset = DEFAULT_TEXT_PROPS.shadowOffset;
1833
+ nextProps.shadowBlur = DEFAULT_TEXT_PROPS.shadowBlur;
1834
+ nextProps.shadowOpacity = DEFAULT_TEXT_PROPS.shadowOpacity;
1715
1835
  } else {
1716
- textElement.setProps({
1717
- ...textElement.getProps(),
1718
- shadowColor: void 0,
1719
- shadowOffset: void 0,
1720
- shadowBlur: void 0,
1721
- shadowOpacity: void 0
1722
- });
1836
+ nextProps.shadowColor = void 0;
1837
+ nextProps.shadowOffset = void 0;
1838
+ nextProps.shadowBlur = void 0;
1839
+ nextProps.shadowOpacity = void 0;
1840
+ }
1841
+ if (applyBackground) {
1842
+ nextProps.backgroundColor = backgroundColor;
1843
+ nextProps.backgroundOpacity = backgroundOpacity;
1844
+ } else {
1845
+ nextProps.backgroundColor = void 0;
1846
+ nextProps.backgroundOpacity = void 0;
1723
1847
  }
1848
+ textElement.setProps(nextProps);
1724
1849
  updateElement(textElement);
1725
1850
  } else {
1726
1851
  textElement = new timeline.TextElement(textContent).setFontSize(fontSize).setFontFamily(selectedFont).setFontWeight(isBold ? 700 : 400).setFontStyle(isItalic ? "italic" : "normal").setFill(textColor).setStrokeColor(strokeColor).setLineWidth(strokeWidth).setTextAlign("center");
1852
+ const nextProps = { ...textElement.getProps() };
1727
1853
  if (applyShadow) {
1728
- textElement.setProps({
1729
- ...textElement.getProps(),
1730
- shadowColor,
1731
- shadowOffset: DEFAULT_TEXT_PROPS.shadowOffset,
1732
- shadowBlur: DEFAULT_TEXT_PROPS.shadowBlur,
1733
- shadowOpacity: DEFAULT_TEXT_PROPS.shadowOpacity
1734
- });
1854
+ nextProps.shadowColor = shadowColor;
1855
+ nextProps.shadowOffset = DEFAULT_TEXT_PROPS.shadowOffset;
1856
+ nextProps.shadowBlur = DEFAULT_TEXT_PROPS.shadowBlur;
1857
+ nextProps.shadowOpacity = DEFAULT_TEXT_PROPS.shadowOpacity;
1858
+ }
1859
+ if (applyBackground) {
1860
+ nextProps.backgroundColor = backgroundColor;
1861
+ nextProps.backgroundOpacity = backgroundOpacity;
1735
1862
  }
1863
+ textElement.setProps(nextProps);
1736
1864
  await addElement(textElement);
1737
1865
  }
1738
1866
  };
@@ -1752,6 +1880,12 @@ const useTextPanel = ({
1752
1880
  if (hasShadow) {
1753
1881
  setShadowColor(textProps.shadowColor ?? DEFAULT_TEXT_PROPS.shadowColor);
1754
1882
  }
1883
+ const hasBackground = textProps.backgroundColor != null && textProps.backgroundColor !== "";
1884
+ setApplyBackground(hasBackground);
1885
+ if (hasBackground) {
1886
+ setBackgroundColor(textProps.backgroundColor ?? "#FACC15");
1887
+ setBackgroundOpacity(textProps.backgroundOpacity ?? 1);
1888
+ }
1755
1889
  } else {
1756
1890
  setTextContent(DEFAULT_TEXT_PROPS.text);
1757
1891
  setFontSize(DEFAULT_TEXT_PROPS.fontSize);
@@ -1763,6 +1897,9 @@ const useTextPanel = ({
1763
1897
  setStrokeWidth(DEFAULT_TEXT_PROPS.strokeWidth);
1764
1898
  setApplyShadow(DEFAULT_TEXT_PROPS.applyShadow);
1765
1899
  setShadowColor(DEFAULT_TEXT_PROPS.shadowColor);
1900
+ setApplyBackground(false);
1901
+ setBackgroundColor("#FACC15");
1902
+ setBackgroundOpacity(1);
1766
1903
  }
1767
1904
  }, [selectedElement]);
1768
1905
  return {
@@ -1788,6 +1925,12 @@ const useTextPanel = ({
1788
1925
  setApplyShadow,
1789
1926
  setShadowColor,
1790
1927
  setStrokeWidth,
1928
+ applyBackground,
1929
+ backgroundColor,
1930
+ backgroundOpacity,
1931
+ setApplyBackground,
1932
+ setBackgroundColor,
1933
+ setBackgroundOpacity,
1791
1934
  handleApplyChanges
1792
1935
  };
1793
1936
  };
@@ -2498,7 +2641,7 @@ const useSubtitlesPanel = () => {
2498
2641
  const subtitlesTrack = react.useRef(null);
2499
2642
  const { editor } = timeline.useTimelineContext();
2500
2643
  const fetchSubtitles = async () => {
2501
- const editorSubtitlesTrack = editor.getSubtiltesTrack();
2644
+ const editorSubtitlesTrack = editor.getSubtitlesTrack();
2502
2645
  if (editorSubtitlesTrack) {
2503
2646
  subtitlesTrack.current = editorSubtitlesTrack;
2504
2647
  setSubtitles(
@@ -3525,7 +3668,7 @@ function PropertiesPanelContainer({
3525
3668
  ] });
3526
3669
  }
3527
3670
  const useStudioOperation = (studioConfig) => {
3528
- const { editor, present } = timeline.useTimelineContext();
3671
+ const { editor, present, videoResolution } = timeline.useTimelineContext();
3529
3672
  const { setSeekTime, setPlayerState } = livePlayer.useLivePlayerContext();
3530
3673
  const [projectName, setProjectName] = react.useState("");
3531
3674
  const onNewProject = () => {
@@ -3573,14 +3716,13 @@ const useStudioOperation = (studioConfig) => {
3573
3716
  }
3574
3717
  };
3575
3718
  const onExportVideo = async () => {
3576
- var _a, _b;
3577
3719
  if ((studioConfig == null ? void 0 : studioConfig.exportVideo) && present) {
3578
3720
  await studioConfig.exportVideo(present, {
3579
3721
  outFile: "output.mp4",
3580
3722
  fps: 30,
3581
3723
  resolution: {
3582
- width: (_a = studioConfig.videoProps) == null ? void 0 : _a.width,
3583
- height: (_b = studioConfig.videoProps) == null ? void 0 : _b.height
3724
+ width: videoResolution.width,
3725
+ height: videoResolution.height
3584
3726
  }
3585
3727
  });
3586
3728
  } else {