@arronqzy/react-view 1.0.3 → 1.0.7

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 (43) hide show
  1. package/dist/styles.css +555 -15
  2. package/package.json +14 -7
  3. package/readme.md +74 -5
  4. package/src/core/template.tsx +12 -2
  5. package/src/panel/ReactViewOnlinePreview.tsx +18 -8
  6. package/src/panel/ReactViewPanel.tsx +403 -177
  7. package/src/panel/ai/AssistantChatPanel.tsx +895 -0
  8. package/src/panel/ai/AssistantMarkdown.tsx +256 -0
  9. package/src/panel/ai/applyAssistantAction.ts +652 -0
  10. package/src/panel/ai/revealConfigField.ts +137 -0
  11. package/src/panel/components/ConfigHintIcon.tsx +50 -0
  12. package/src/panel/components/ElementsLayer.tsx +39 -22
  13. package/src/panel/components/MaterialSidebar.tsx +157 -109
  14. package/src/panel/components/MoveableLayer.tsx +75 -61
  15. package/src/panel/components/PanelCanvas.tsx +81 -64
  16. package/src/panel/components/PanelConfigSidebar.tsx +471 -448
  17. package/src/panel/components/SelectLayer.tsx +1 -0
  18. package/src/panel/components/ViewElementScopePanel.tsx +13 -12
  19. package/src/panel/components/WorkspaceConfigSidebar.tsx +5 -2
  20. package/src/panel/components/WorkspaceProjectNav.tsx +25 -14
  21. package/src/panel/components/WorkspaceStageSplit.tsx +3 -1
  22. package/src/panel/components/scope-config/ScopeConfigContext.tsx +9 -0
  23. package/src/panel/components/scope-config/ScopeTemplatePreviewHost.tsx +242 -0
  24. package/src/panel/components/scope-config/ScopeTemplateUsageHint.tsx +12 -0
  25. package/src/panel/components/scope-config/ScopeTemplateWarningsPanel.tsx +6 -3
  26. package/src/panel/components/table/ConditionEditor.tsx +205 -0
  27. package/src/panel/components/table/JsonCodeEditor.tsx +115 -0
  28. package/src/panel/components/table/PanelConfigTableSection.tsx +2192 -0
  29. package/src/panel/components/table/TableNodeContent.tsx +544 -0
  30. package/src/panel/constants/messages.ts +76 -34
  31. package/src/panel/hooks/usePanelElements.ts +64 -52
  32. package/src/panel/hooks/useRafThrottledScroll.ts +30 -0
  33. package/src/panel/hooks/useWorkspaceProjects.ts +11 -8
  34. package/src/panel/types.ts +6 -0
  35. package/src/panel/utils/chartOptionBuilder.ts +12 -1
  36. package/src/panel/utils/panelElementDefaults.ts +69 -39
  37. package/src/panel/utils/scope-field-labels.ts +51 -37
  38. package/src/panel/utils/scope-template-chart.ts +92 -0
  39. package/src/panel/utils/scope-template-preview.ts +128 -0
  40. package/src/panel/utils/scope-template-spread.ts +252 -0
  41. package/src/panel/utils/scope-template-warnings.ts +131 -11
  42. package/src/panel/utils/scope-template.ts +105 -3
  43. package/src/tailwind.css +36 -15
@@ -0,0 +1,205 @@
1
+ import type { Condition, ConditionLeaf, ConditionOp } from "@arronqzy/view-table";
2
+ import {
3
+ addConditionItem,
4
+ getConditionLogic,
5
+ isConditionLeaf,
6
+ listConditionItems,
7
+ removeConditionItem,
8
+ setConditionLogic,
9
+ updateConditionLeaf,
10
+ type ConditionLogic,
11
+ } from "@arronqzy/view-table";
12
+ import { useI18n } from "@arronqzy/i18n/react";
13
+ import {
14
+ Button,
15
+ Input,
16
+ Select,
17
+ SelectContent,
18
+ SelectItem,
19
+ SelectTrigger,
20
+ SelectValue,
21
+ } from "@arronqzy/ui";
22
+ import React from "react";
23
+
24
+ const CONDITION_OPS: ConditionOp[] = [
25
+ "eq",
26
+ "neq",
27
+ "gt",
28
+ "gte",
29
+ "lt",
30
+ "lte",
31
+ "contains",
32
+ "startsWith",
33
+ "endsWith",
34
+ "in",
35
+ "empty",
36
+ "notEmpty",
37
+ "regex",
38
+ "truthy",
39
+ "falsy",
40
+ ];
41
+
42
+ function leafValueText(value: unknown): string {
43
+ if (value == null) return "";
44
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
45
+ return String(value);
46
+ }
47
+ try {
48
+ return JSON.stringify(value);
49
+ } catch {
50
+ return "";
51
+ }
52
+ }
53
+
54
+ function parseLeafValue(text: string): unknown {
55
+ const trimmed = text.trim();
56
+ if (trimmed === "") return "";
57
+ if (trimmed === "true") return true;
58
+ if (trimmed === "false") return false;
59
+ if (/^-?\d+(\.\d+)?$/.test(trimmed)) return Number(trimmed);
60
+ return text;
61
+ }
62
+
63
+ export function ConditionEditor({
64
+ value,
65
+ onChange,
66
+ showField = false,
67
+ fieldClass = "space-y-1",
68
+ inputClass = "h-8 text-[11px]",
69
+ }: {
70
+ value: Condition | undefined;
71
+ onChange: (next: Condition) => void;
72
+ showField?: boolean;
73
+ fieldClass?: string;
74
+ inputClass?: string;
75
+ }) {
76
+ const { t } = useI18n();
77
+ const logic = getConditionLogic(value);
78
+ const items = listConditionItems(value);
79
+
80
+ const opLabel = (op: ConditionOp) => t(`panel.config.tableOp.${op}`);
81
+
82
+ const logicLabel =
83
+ logic === "or"
84
+ ? t("panel.config.tableConditionLogicOr")
85
+ : logic === "not"
86
+ ? t("panel.config.tableConditionLogicNot")
87
+ : t("panel.config.tableConditionLogicAnd");
88
+
89
+ return (
90
+ <div className="space-y-2">
91
+ <div className="flex items-center justify-between gap-2">
92
+ <label className={`${fieldClass} min-w-0 flex-1`}>
93
+ <div className="text-[11px] text-muted-foreground">{t("panel.config.tableConditionLogic")}</div>
94
+ <Select
95
+ value={logic}
96
+ onValueChange={(next) => onChange(setConditionLogic(value, next as ConditionLogic))}
97
+ >
98
+ <SelectTrigger className={inputClass}>
99
+ <SelectValue>{logicLabel}</SelectValue>
100
+ </SelectTrigger>
101
+ <SelectContent>
102
+ <SelectItem value="and">{t("panel.config.tableConditionLogicAnd")}</SelectItem>
103
+ <SelectItem value="or">{t("panel.config.tableConditionLogicOr")}</SelectItem>
104
+ <SelectItem value="not">{t("panel.config.tableConditionLogicNot")}</SelectItem>
105
+ </SelectContent>
106
+ </Select>
107
+ </label>
108
+ <Button
109
+ type="button"
110
+ variant="outline"
111
+ size="sm"
112
+ className="mt-4 h-7 shrink-0 px-2 text-[11px]"
113
+ onClick={() => onChange(addConditionItem(value))}
114
+ >
115
+ {t("panel.config.tableConditionAddItem")}
116
+ </Button>
117
+ </div>
118
+ <div className="text-[10px] text-muted-foreground">{t("panel.config.tableConditionLogicHint")}</div>
119
+ {items.map((item, index) => {
120
+ const leaf: ConditionLeaf = isConditionLeaf(item)
121
+ ? item
122
+ : { op: "eq", value: "" };
123
+ const needsValue = !["empty", "notEmpty", "truthy", "falsy"].includes(leaf.op);
124
+ return (
125
+ <div
126
+ key={`cond-${index}`}
127
+ className="space-y-1.5 rounded-md border border-border/50 bg-background/70 p-2"
128
+ >
129
+ <div className="flex items-center justify-between gap-2">
130
+ <div className="text-[10px] font-medium text-muted-foreground">
131
+ {t("panel.config.tableConditionItem", { n: index + 1 })}
132
+ </div>
133
+ <Button
134
+ type="button"
135
+ variant="ghost"
136
+ size="sm"
137
+ className="h-6 px-1.5 text-[11px] text-destructive"
138
+ disabled={items.length <= 1}
139
+ onClick={() => onChange(removeConditionItem(value, index))}
140
+ >
141
+ ×
142
+ </Button>
143
+ </div>
144
+ <div className={`grid grid-cols-1 gap-1.5 ${showField ? "sm:grid-cols-3" : "sm:grid-cols-2"}`}>
145
+ <label className={fieldClass}>
146
+ <div className="text-[11px] text-muted-foreground">{t("panel.config.tableConditionOp")}</div>
147
+ <Select
148
+ value={leaf.op}
149
+ onValueChange={(next) =>
150
+ onChange(updateConditionLeaf(value, index, { op: next as ConditionOp }))
151
+ }
152
+ >
153
+ <SelectTrigger className={inputClass}>
154
+ <SelectValue>{opLabel(leaf.op)}</SelectValue>
155
+ </SelectTrigger>
156
+ <SelectContent>
157
+ {CONDITION_OPS.map((op) => (
158
+ <SelectItem key={op} value={op}>
159
+ {opLabel(op)}
160
+ </SelectItem>
161
+ ))}
162
+ </SelectContent>
163
+ </Select>
164
+ </label>
165
+ {showField ? (
166
+ <label className={fieldClass}>
167
+ <div className="text-[11px] text-muted-foreground">{t("panel.config.tableConditionField")}</div>
168
+ <Input
169
+ value={leaf.field ?? ""}
170
+ onChange={(e) =>
171
+ onChange(
172
+ updateConditionLeaf(value, index, {
173
+ field: e.target.value || undefined,
174
+ })
175
+ )
176
+ }
177
+ className={inputClass}
178
+ placeholder={t("panel.config.tableConditionFieldPlaceholder")}
179
+ />
180
+ </label>
181
+ ) : null}
182
+ {needsValue ? (
183
+ <label className={fieldClass}>
184
+ <div className="text-[11px] text-muted-foreground">{t("panel.config.tableConditionValue")}</div>
185
+ <Input
186
+ value={leafValueText(leaf.value)}
187
+ onChange={(e) =>
188
+ onChange(
189
+ updateConditionLeaf(value, index, {
190
+ value: parseLeafValue(e.target.value),
191
+ })
192
+ )
193
+ }
194
+ className={inputClass}
195
+ placeholder={t("panel.config.tableConditionValuePlaceholder")}
196
+ />
197
+ </label>
198
+ ) : null}
199
+ </div>
200
+ </div>
201
+ );
202
+ })}
203
+ </div>
204
+ );
205
+ }
@@ -0,0 +1,115 @@
1
+ import React, { useMemo } from "react";
2
+ import { Button } from "@arronqzy/ui";
3
+ import { useI18n } from "@arronqzy/i18n/react";
4
+ import CodeMirror from "@uiw/react-codemirror";
5
+ import { json } from "@codemirror/lang-json";
6
+ import { oneDark } from "@codemirror/theme-one-dark";
7
+ import { EditorView } from "@codemirror/view";
8
+
9
+ export function JsonCodeEditor({
10
+ value,
11
+ onChange,
12
+ className,
13
+ }: {
14
+ value: string;
15
+ onChange: (next: string) => void;
16
+ className?: string;
17
+ }) {
18
+ const { t } = useI18n();
19
+ const extensions = useMemo(
20
+ () => [
21
+ json(),
22
+ EditorView.lineWrapping,
23
+ EditorView.theme({
24
+ "&": {
25
+ height: "100%",
26
+ maxHeight: "100%",
27
+ fontSize: "13px",
28
+ backgroundColor: "transparent",
29
+ },
30
+ "&.cm-editor": {
31
+ height: "100%",
32
+ maxHeight: "100%",
33
+ backgroundColor: "transparent",
34
+ },
35
+ ".cm-scroller": {
36
+ overflow: "auto",
37
+ fontFamily:
38
+ "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
39
+ lineHeight: "1.55",
40
+ },
41
+ ".cm-content": { padding: "8px 0", caretColor: "#e2e8f0" },
42
+ ".cm-gutters": {
43
+ backgroundColor: "transparent",
44
+ borderRight: "1px solid rgba(148,163,184,0.12)",
45
+ color: "#64748b",
46
+ },
47
+ ".cm-activeLineGutter": { backgroundColor: "rgba(148,163,184,0.08)" },
48
+ ".cm-activeLine": { backgroundColor: "rgba(148,163,184,0.06)" },
49
+ }),
50
+ ],
51
+ []
52
+ );
53
+
54
+ const formatJson = () => {
55
+ const trimmed = value.trim();
56
+ if (!trimmed) return;
57
+ try {
58
+ onChange(JSON.stringify(JSON.parse(trimmed), null, 2));
59
+ } catch {
60
+ // keep as-is; parent validates on save
61
+ }
62
+ };
63
+
64
+ return (
65
+ <div
66
+ className={[
67
+ "flex h-full min-h-0 max-h-full flex-col overflow-hidden rounded-xl border border-slate-700/60 bg-[#0c1220] shadow-[inset_0_1px_0_rgba(255,255,255,0.04)]",
68
+ className,
69
+ ]
70
+ .filter(Boolean)
71
+ .join(" ")}
72
+ >
73
+ <div className="flex shrink-0 items-center justify-between gap-2 border-b border-white/5 px-2.5 py-1.5">
74
+ <div className="flex min-w-0 items-center gap-2">
75
+ <span className="rounded-md bg-sky-500/15 px-1.5 py-0.5 font-mono text-[10px] font-medium tracking-wide text-sky-300">
76
+ JSON
77
+ </span>
78
+ <span className="truncate text-[11px] text-slate-400">
79
+ {t("panel.config.tableRowsEditEditorHint")}
80
+ </span>
81
+ </div>
82
+ <Button
83
+ type="button"
84
+ variant="ghost"
85
+ size="sm"
86
+ className="h-6 shrink-0 rounded-md px-2 text-[11px] text-slate-300 hover:bg-white/10 hover:text-white"
87
+ onClick={formatJson}
88
+ >
89
+ {t("panel.config.tableRowsEditFormat")}
90
+ </Button>
91
+ </div>
92
+ <div className="relative min-h-0 flex-1 overflow-hidden">
93
+ <div className="absolute inset-0 overflow-hidden">
94
+ <CodeMirror
95
+ value={value}
96
+ height="100%"
97
+ maxHeight="100%"
98
+ theme={oneDark}
99
+ extensions={extensions}
100
+ basicSetup={{
101
+ lineNumbers: true,
102
+ foldGutter: true,
103
+ highlightActiveLine: true,
104
+ bracketMatching: true,
105
+ autocompletion: true,
106
+ }}
107
+ onChange={(next) => onChange(next)}
108
+ className="h-full max-h-full [&_.cm-editor]:h-full [&_.cm-editor]:max-h-full [&_.cm-editor]:outline-none"
109
+ aria-label={t("panel.config.tableRowsEditTitle")}
110
+ />
111
+ </div>
112
+ </div>
113
+ </div>
114
+ );
115
+ }