@arronqzy/react-view 1.0.13 → 1.0.15
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/styles.css +96 -4
- package/package.json +11 -10
- package/src/panel/ReactViewOnlinePreview.tsx +31 -2
- package/src/panel/ReactViewPanel.tsx +186 -124
- package/src/panel/ai/applyAssistantAction.ts +1 -0
- package/src/panel/components/BusyOverlay.tsx +25 -0
- package/src/panel/components/ElementsLayer.tsx +110 -68
- package/src/panel/components/MaterialSidebar.tsx +15 -0
- package/src/panel/components/MoveableLayer.tsx +8 -0
- package/src/panel/components/PanelCanvas.tsx +3 -0
- package/src/panel/components/PanelConfigSidebar.tsx +140 -91
- package/src/panel/components/SelectLayer.tsx +5 -0
- package/src/panel/components/WorkspaceConfigSidebar.tsx +1 -0
- package/src/panel/components/WorkspaceProjectNav.tsx +27 -4
- package/src/panel/components/config/PanelConfigScene3dSection.tsx +874 -0
- package/src/panel/components/scope-config/ScopeTemplateAutocompleteHost.tsx +1 -2
- package/src/panel/components/table/PanelConfigTableSection.tsx +82 -50
- package/src/panel/components/table/TableNodeContent.tsx +40 -15
- package/src/panel/hooks/usePanelElements.ts +13 -1
- package/src/panel/hooks/useWorkspaceProjects.ts +133 -66
- package/src/panel/library/workspace-project-cache.ts +8 -2
- package/src/panel/library/workspace-project-db.ts +29 -11
- package/src/panel/library/workspace-snapshot.ts +21 -4
- package/src/panel/scope/view-scope-store.ts +8 -0
- package/src/panel/types.ts +4 -3
- package/src/panel/utils/async-work.ts +178 -0
- package/src/panel/utils/gridPlacement.ts +29 -0
- package/src/panel/utils/panelElementDefaults.ts +37 -1
- package/src/panel/utils/scope-autocomplete.ts +0 -114
|
@@ -7,14 +7,13 @@ import {
|
|
|
7
7
|
} from "react";
|
|
8
8
|
import { createPortal } from "react-dom";
|
|
9
9
|
import { cn } from "@arronqzy/ui";
|
|
10
|
-
|
|
11
10
|
import {
|
|
12
11
|
applyScopeAutocompleteSelection,
|
|
13
12
|
buildScopeExpression,
|
|
14
13
|
getScopeAutocompleteSuggestions,
|
|
15
14
|
parseScopeAutocomplete,
|
|
16
15
|
type ScopeAutocompleteState,
|
|
17
|
-
} from "
|
|
16
|
+
} from "@arronqzy/blueprint-dsl";
|
|
18
17
|
|
|
19
18
|
function isAutocompleteTarget(
|
|
20
19
|
target: EventTarget | null
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
generateMockTableRows,
|
|
18
18
|
stringifyMockTableRows,
|
|
19
19
|
type TableMockLocale,
|
|
20
|
+
isCssTransparent,
|
|
20
21
|
} from "@arronqzy/view-table";
|
|
21
22
|
import type { PanelElement } from "../../types";
|
|
22
23
|
import { ConfigHintIcon } from "../ConfigHintIcon";
|
|
@@ -24,6 +25,7 @@ import { ConditionEditor } from "./ConditionEditor";
|
|
|
24
25
|
import { JsonCodeEditor } from "./JsonCodeEditor";
|
|
25
26
|
import { useI18n } from "@arronqzy/i18n/react";
|
|
26
27
|
import React, { useMemo, useRef, useState } from "react";
|
|
28
|
+
import { readFileAsDataUrl, runBusyTask } from "../../utils/async-work";
|
|
27
29
|
import {
|
|
28
30
|
Button,
|
|
29
31
|
Dialog,
|
|
@@ -217,35 +219,34 @@ export function PanelConfigTableSection({
|
|
|
217
219
|
|
|
218
220
|
const uploadImageForColumn = async (index: number, file: File) => {
|
|
219
221
|
try {
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
setImageUploadHint(t("panel.config.uploadServerAndBase64"));
|
|
222
|
+
await runBusyTask(t("common.uploadingFile"), async () => {
|
|
223
|
+
const base64 = await readFileAsDataUrl(file, t("panel.messages.readImageFailed"), "image");
|
|
224
|
+
patchWidgetProps(index, {
|
|
225
|
+
imageUrlMode: "static",
|
|
226
|
+
imageUrl: base64,
|
|
227
|
+
});
|
|
228
|
+
setImageUploadHint(t("panel.config.uploadWrittenBase64"));
|
|
229
|
+
try {
|
|
230
|
+
const form = new FormData();
|
|
231
|
+
form.append("file", file);
|
|
232
|
+
const resp = await fetch("/api/upload", { method: "POST", body: form });
|
|
233
|
+
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
234
|
+
const data = (await resp.json()) as { url?: string };
|
|
235
|
+
if (data.url) {
|
|
236
|
+
patchWidgetProps(index, {
|
|
237
|
+
imageUrlMode: "static",
|
|
238
|
+
imageUrl: data.url,
|
|
239
|
+
});
|
|
240
|
+
setImageUploadHint(t("panel.config.uploadServerAndBase64"));
|
|
241
|
+
}
|
|
242
|
+
} catch {
|
|
243
|
+
setImageUploadHint(t("panel.config.uploadServerFailedKeepBase64"));
|
|
243
244
|
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
245
|
+
});
|
|
246
|
+
} catch (error) {
|
|
247
|
+
setImageUploadHint(
|
|
248
|
+
error instanceof Error ? error.message : t("panel.messages.readImageFailed")
|
|
249
|
+
);
|
|
249
250
|
}
|
|
250
251
|
};
|
|
251
252
|
|
|
@@ -321,27 +322,45 @@ export function PanelConfigTableSection({
|
|
|
321
322
|
value: string,
|
|
322
323
|
onChange: (next: string) => void,
|
|
323
324
|
hintKey?: string,
|
|
324
|
-
placeholder = "#000000"
|
|
325
|
+
placeholder = "#000000",
|
|
326
|
+
allowTransparent = false
|
|
325
327
|
) => {
|
|
326
328
|
const label = t(labelKey);
|
|
329
|
+
const transparent = allowTransparent && isCssTransparent(value);
|
|
330
|
+
const fallbackHex = placeholder.startsWith("#") ? placeholder : "#ffffff";
|
|
327
331
|
return (
|
|
328
332
|
<div className={fieldClass}>
|
|
329
333
|
{fieldLabel(labelKey, hintKey)}
|
|
330
|
-
|
|
331
|
-
<
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
334
|
+
{allowTransparent ? (
|
|
335
|
+
<label className="flex items-center gap-2 text-[11px]">
|
|
336
|
+
<Switch
|
|
337
|
+
checked={transparent}
|
|
338
|
+
onCheckedChange={(checked) => onChange(checked ? "transparent" : fallbackHex)}
|
|
339
|
+
/>
|
|
340
|
+
<span>{t("panel.config.tableBgTransparent")}</span>
|
|
341
|
+
</label>
|
|
342
|
+
) : null}
|
|
343
|
+
{transparent ? (
|
|
344
|
+
<div className="text-[10px] text-muted-foreground">
|
|
345
|
+
{t("panel.config.tableBgTransparentHint")}
|
|
346
|
+
</div>
|
|
347
|
+
) : (
|
|
348
|
+
<div className="flex items-center gap-2">
|
|
349
|
+
<Input
|
|
350
|
+
value={value}
|
|
351
|
+
onChange={(e) => onChange(e.target.value)}
|
|
352
|
+
placeholder={placeholder}
|
|
353
|
+
className={`${inputClass} min-w-0 flex-1`}
|
|
354
|
+
/>
|
|
355
|
+
<Input
|
|
356
|
+
type="color"
|
|
357
|
+
value={toPickerHex(value, fallbackHex)}
|
|
358
|
+
onChange={(e) => onChange(e.target.value)}
|
|
359
|
+
className="h-7 w-10 shrink-0 cursor-pointer p-1"
|
|
360
|
+
aria-label={t("common.colorPickerAria", { label })}
|
|
361
|
+
/>
|
|
362
|
+
</div>
|
|
363
|
+
)}
|
|
345
364
|
</div>
|
|
346
365
|
);
|
|
347
366
|
};
|
|
@@ -705,9 +724,8 @@ export function PanelConfigTableSection({
|
|
|
705
724
|
</ConfigHintIcon>
|
|
706
725
|
</div>
|
|
707
726
|
|
|
708
|
-
{(selectedColumn.widget ?? "text")
|
|
709
|
-
|
|
710
|
-
<div className="grid grid-cols-2 gap-2">
|
|
727
|
+
{(selectedColumn.widget ?? "text") !== "image" ? (
|
|
728
|
+
<div className="grid grid-cols-2 gap-2">
|
|
711
729
|
<label className={fieldClass}>
|
|
712
730
|
{fieldLabel("panel.config.tableTextFontSize")}
|
|
713
731
|
<Input
|
|
@@ -849,6 +867,10 @@ export function PanelConfigTableSection({
|
|
|
849
867
|
</Select>
|
|
850
868
|
</label>
|
|
851
869
|
</div>
|
|
870
|
+
) : null}
|
|
871
|
+
|
|
872
|
+
{(selectedColumn.widget ?? "text") === "text" ? (
|
|
873
|
+
<>
|
|
852
874
|
<label className={fieldClass}>
|
|
853
875
|
{fieldLabel("panel.config.tableActionOnClick", "panel.config.tableActionBlueprintHint")}
|
|
854
876
|
<BlueprintNodeSelect
|
|
@@ -1849,6 +1871,14 @@ export function PanelConfigTableSection({
|
|
|
1849
1871
|
/>
|
|
1850
1872
|
</label>
|
|
1851
1873
|
<div className="grid grid-cols-2 gap-2">
|
|
1874
|
+
{colorField(
|
|
1875
|
+
"panel.config.tableBodyBg",
|
|
1876
|
+
table.tableStyle?.backgroundColor ?? "",
|
|
1877
|
+
(next) => patchTableStyle({ backgroundColor: next || undefined }),
|
|
1878
|
+
"panel.config.tableBodyBgHint",
|
|
1879
|
+
"#ffffff",
|
|
1880
|
+
true
|
|
1881
|
+
)}
|
|
1852
1882
|
{colorField(
|
|
1853
1883
|
"panel.config.tableStyleColor",
|
|
1854
1884
|
table.tableStyle?.color ?? "",
|
|
@@ -1875,8 +1905,9 @@ export function PanelConfigTableSection({
|
|
|
1875
1905
|
"panel.config.tableHeaderBg",
|
|
1876
1906
|
table.headerStyle?.backgroundColor ?? "",
|
|
1877
1907
|
(next) => patchHeaderStyle({ backgroundColor: next || undefined }),
|
|
1878
|
-
|
|
1879
|
-
"#f3f4f6"
|
|
1908
|
+
"panel.config.tableHeaderBgHint",
|
|
1909
|
+
"#f3f4f6",
|
|
1910
|
+
true
|
|
1880
1911
|
)}
|
|
1881
1912
|
<label className={fieldClass}>
|
|
1882
1913
|
{fieldLabel("panel.config.tableHeaderFontSize")}
|
|
@@ -1969,7 +2000,8 @@ export function PanelConfigTableSection({
|
|
|
1969
2000
|
patchTable({ rowStyleRules: next });
|
|
1970
2001
|
},
|
|
1971
2002
|
"panel.config.tableBgColorHint",
|
|
1972
|
-
"#ecfccb"
|
|
2003
|
+
"#ecfccb",
|
|
2004
|
+
true
|
|
1973
2005
|
)}
|
|
1974
2006
|
</div>
|
|
1975
2007
|
))}
|
|
@@ -5,8 +5,11 @@ import {
|
|
|
5
5
|
resolveCellDisplay,
|
|
6
6
|
resolveRowDisplay,
|
|
7
7
|
stylePropsToCss,
|
|
8
|
+
tableTextStyleToCss,
|
|
8
9
|
resolveRawTableInput,
|
|
9
10
|
resolveProgressDisplay,
|
|
11
|
+
resolveTableBodyBackground,
|
|
12
|
+
resolveTableHeaderBackground,
|
|
10
13
|
type PanelTableConfig,
|
|
11
14
|
type NormalizedColumn,
|
|
12
15
|
type NormalizedRow,
|
|
@@ -64,8 +67,12 @@ function TableCellView({
|
|
|
64
67
|
};
|
|
65
68
|
|
|
66
69
|
const style = stylePropsToCss(cell.style);
|
|
70
|
+
const textCss = tableTextStyleToCss(cell.widgetProps?.textStyle);
|
|
67
71
|
const textOverflow = cell.widgetProps?.textStyle?.overflow ?? "ellipsis";
|
|
68
|
-
const isWrap =
|
|
72
|
+
const isWrap =
|
|
73
|
+
textOverflow === "wrap" &&
|
|
74
|
+
cell.widget !== "image" &&
|
|
75
|
+
cell.widget !== "progress";
|
|
69
76
|
const common: React.CSSProperties = {
|
|
70
77
|
...style,
|
|
71
78
|
overflow: "hidden",
|
|
@@ -98,6 +105,7 @@ function TableCellView({
|
|
|
98
105
|
lineHeight: "20px",
|
|
99
106
|
color: "#fff",
|
|
100
107
|
background: bg,
|
|
108
|
+
...textCss,
|
|
101
109
|
}}
|
|
102
110
|
>
|
|
103
111
|
{cell.widget === "badge" ? (
|
|
@@ -126,7 +134,13 @@ function TableCellView({
|
|
|
126
134
|
e.stopPropagation();
|
|
127
135
|
emit("click", cell.widgetProps?.actions?.onClickBlueprintNodeId);
|
|
128
136
|
}}
|
|
129
|
-
style={{
|
|
137
|
+
style={{
|
|
138
|
+
color: "#2563eb",
|
|
139
|
+
textDecoration: "underline",
|
|
140
|
+
overflow: "hidden",
|
|
141
|
+
textOverflow: "ellipsis",
|
|
142
|
+
...textCss,
|
|
143
|
+
}}
|
|
130
144
|
>
|
|
131
145
|
{cell.text || cell.href}
|
|
132
146
|
</a>
|
|
@@ -137,7 +151,14 @@ function TableCellView({
|
|
|
137
151
|
const display = resolveProgressDisplay(cell.widgetProps);
|
|
138
152
|
const trackColor = cell.color ?? "#3b82f6";
|
|
139
153
|
const label = (
|
|
140
|
-
<span
|
|
154
|
+
<span
|
|
155
|
+
style={{
|
|
156
|
+
fontSize: 11,
|
|
157
|
+
color: "rgba(0,0,0,0.55)",
|
|
158
|
+
flex: "0 0 auto",
|
|
159
|
+
...textCss,
|
|
160
|
+
}}
|
|
161
|
+
>
|
|
141
162
|
{cell.text}
|
|
142
163
|
</span>
|
|
143
164
|
);
|
|
@@ -178,8 +199,15 @@ function TableCellView({
|
|
|
178
199
|
y="50%"
|
|
179
200
|
dominantBaseline="central"
|
|
180
201
|
textAnchor="middle"
|
|
181
|
-
fontSize={
|
|
182
|
-
|
|
202
|
+
fontSize={
|
|
203
|
+
typeof textCss.fontSize === "number"
|
|
204
|
+
? textCss.fontSize
|
|
205
|
+
: Math.max(8, Math.round(size * 0.32))
|
|
206
|
+
}
|
|
207
|
+
fill={typeof textCss.color === "string" ? textCss.color : "rgba(0,0,0,0.65)"}
|
|
208
|
+
fontFamily={typeof textCss.fontFamily === "string" ? textCss.fontFamily : undefined}
|
|
209
|
+
fontWeight={textCss.fontWeight}
|
|
210
|
+
fontStyle={typeof textCss.fontStyle === "string" ? textCss.fontStyle : undefined}
|
|
183
211
|
>
|
|
184
212
|
{Math.round(pct)}
|
|
185
213
|
</text>
|
|
@@ -227,7 +255,7 @@ function TableCellView({
|
|
|
227
255
|
draggable={false}
|
|
228
256
|
/>
|
|
229
257
|
) : (
|
|
230
|
-
|
|
258
|
+
<span style={{ fontSize: 11, opacity: 0.5, ...textCss }}>—</span>
|
|
231
259
|
)}
|
|
232
260
|
</div>
|
|
233
261
|
);
|
|
@@ -275,21 +303,18 @@ function TableCellView({
|
|
|
275
303
|
}}
|
|
276
304
|
/>
|
|
277
305
|
</button>
|
|
278
|
-
<span style={{ marginLeft: 6, fontSize: 12 }}>{cell.text}</span>
|
|
306
|
+
<span style={{ marginLeft: 6, fontSize: 12, ...textCss }}>{cell.text}</span>
|
|
279
307
|
</div>
|
|
280
308
|
);
|
|
281
309
|
} else {
|
|
282
|
-
const textStyle = cell.widgetProps?.textStyle;
|
|
283
310
|
const canClickText = Boolean(cell.widgetProps?.actions?.onClickBlueprintNodeId);
|
|
284
311
|
content = (
|
|
285
312
|
<div
|
|
286
313
|
style={{
|
|
287
314
|
...common,
|
|
288
|
-
|
|
289
|
-
fontStyle: textStyle?.fontStyle,
|
|
290
|
-
textDecoration: textStyle?.textDecoration === "none" ? undefined : textStyle?.textDecoration,
|
|
315
|
+
...textCss,
|
|
291
316
|
cursor: canClickText ? "pointer" : undefined,
|
|
292
|
-
color: canClickText && !common.color ? "#2563eb" : common.color,
|
|
317
|
+
color: canClickText && !common.color && !textCss.color ? "#2563eb" : (textCss.color as string | undefined) ?? common.color,
|
|
293
318
|
}}
|
|
294
319
|
onClick={(e) => {
|
|
295
320
|
if (!canClickText) return;
|
|
@@ -431,7 +456,7 @@ export function TableNodeContent({
|
|
|
431
456
|
<div
|
|
432
457
|
className="rv-table-node h-full w-full overflow-hidden"
|
|
433
458
|
style={{
|
|
434
|
-
background: (tableConfig.tableStyle?.backgroundColor
|
|
459
|
+
background: resolveTableBodyBackground(tableConfig.tableStyle?.backgroundColor),
|
|
435
460
|
border:
|
|
436
461
|
tableConfig.tableStyle?.borderWidth != null
|
|
437
462
|
? `${tableConfig.tableStyle.borderWidth}px solid ${tableConfig.tableStyle.borderColor ?? "rgba(0,0,0,0.12)"}`
|
|
@@ -455,7 +480,7 @@ export function TableNodeContent({
|
|
|
455
480
|
position: tableConfig.tableStyle?.stickyHeader === false ? "relative" : "sticky",
|
|
456
481
|
top: 0,
|
|
457
482
|
zIndex: 2,
|
|
458
|
-
background: (tableConfig.headerStyle?.backgroundColor
|
|
483
|
+
background: resolveTableHeaderBackground(tableConfig.headerStyle?.backgroundColor),
|
|
459
484
|
borderBottom: "1px solid rgba(0,0,0,0.1)",
|
|
460
485
|
...headerStyle,
|
|
461
486
|
color: headerTextColor,
|
|
@@ -489,7 +514,7 @@ export function TableNodeContent({
|
|
|
489
514
|
</div>
|
|
490
515
|
) : null}
|
|
491
516
|
|
|
492
|
-
<div ref={parentRef} className="min-h-0 flex-1 overflow-auto">
|
|
517
|
+
<div ref={parentRef} className="min-h-0 flex-1 overflow-auto" style={{ background: "transparent" }}>
|
|
493
518
|
{model.rows.length === 0 ? (
|
|
494
519
|
<div className="flex h-full flex-col items-center justify-center gap-2 px-3 text-center text-xs text-muted-foreground">
|
|
495
520
|
<svg
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
ReferenceCopyMode,
|
|
10
10
|
} from "../types";
|
|
11
11
|
import { getPanelMessages } from "../constants/messages";
|
|
12
|
+
import { removeViewElementScopes } from "../scope/view-scope-store";
|
|
12
13
|
import {
|
|
13
14
|
DEFAULT_LAYER,
|
|
14
15
|
DEFAULT_LAYER_ID,
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
getDefaultGridConfig,
|
|
18
19
|
getDefaultLayer,
|
|
19
20
|
getDefaultNodeName,
|
|
21
|
+
getDefaultScene3dConfigForMaterial,
|
|
20
22
|
getDefaultSizeByMaterial,
|
|
21
23
|
getDefaultTextContent,
|
|
22
24
|
normalizePrimaryLayer,
|
|
@@ -367,6 +369,7 @@ export function usePanelElements() {
|
|
|
367
369
|
});
|
|
368
370
|
const idSet = new Set(unlockedIds);
|
|
369
371
|
if (idSet.size === 0) return;
|
|
372
|
+
const familyIds = new Set<string>();
|
|
370
373
|
store.update(
|
|
371
374
|
(draft) => {
|
|
372
375
|
const deletedSourceIds = new Set<string>();
|
|
@@ -376,7 +379,7 @@ export function usePanelElements() {
|
|
|
376
379
|
const props = n.props as PanelElement;
|
|
377
380
|
deletedSourceIds.add(props.mappingSourceNodeId ?? props.id);
|
|
378
381
|
});
|
|
379
|
-
|
|
382
|
+
familyIds.clear();
|
|
380
383
|
(draft.root.children ?? []).forEach((n) => {
|
|
381
384
|
if (!isPanelElementNode(n) || !n.props) return;
|
|
382
385
|
const props = n.props as PanelElement;
|
|
@@ -389,6 +392,7 @@ export function usePanelElements() {
|
|
|
389
392
|
},
|
|
390
393
|
{ meta: { type: "node.batch-delete", ids: Array.from(idSet) } }
|
|
391
394
|
);
|
|
395
|
+
removeViewElementScopes([...familyIds]);
|
|
392
396
|
}, [byId, layerById]);
|
|
393
397
|
|
|
394
398
|
const bringElementsToFront = useCallback((ids: string[]) => {
|
|
@@ -598,6 +602,7 @@ export function usePanelElements() {
|
|
|
598
602
|
geometryShape: materialType === "geometry" ? "rect" : undefined,
|
|
599
603
|
geometryColor: materialType === "geometry" ? "#3b82f6" : undefined,
|
|
600
604
|
geometryScript: undefined,
|
|
605
|
+
...getDefaultScene3dConfigForMaterial(materialType),
|
|
601
606
|
x: Math.round(x - size.width / 2),
|
|
602
607
|
y: Math.round(y - size.height / 2),
|
|
603
608
|
width: size.width,
|
|
@@ -1059,6 +1064,13 @@ export function usePanelElements() {
|
|
|
1059
1064
|
|
|
1060
1065
|
const exportPanelData = useCallback(() => {
|
|
1061
1066
|
const current = store.getState();
|
|
1067
|
+
if (typeof structuredClone === "function") {
|
|
1068
|
+
try {
|
|
1069
|
+
return structuredClone(current) as State;
|
|
1070
|
+
} catch {
|
|
1071
|
+
/* fall through */
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1062
1074
|
return JSON.parse(JSON.stringify(current)) as State;
|
|
1063
1075
|
}, []);
|
|
1064
1076
|
|