@ramesesinc/platform-core 0.1.5 → 0.1.8
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/components/action/LookupPage.js +9 -31
- package/dist/components/action/ViewPage.d.ts +2 -0
- package/dist/components/action/ViewPage.js +25 -31
- package/dist/components/common/UIComponent.js +4 -3
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/table/DataList.js +2 -2
- package/dist/components/view/PopupView.d.ts +13 -0
- package/dist/components/view/PopupView.js +25 -20
- package/dist/core/DataContext.d.ts +7 -4
- package/dist/core/DataContext.js +16 -4
- package/dist/core/Page.js +25 -26
- package/dist/core/PageCache.js +16 -3
- package/dist/core/PageContext.js +90 -18
- package/dist/core/PageViewContext.d.ts +13 -1
- package/dist/core/PageViewContext.js +89 -5
- package/dist/core/PopupContext.d.ts +49 -0
- package/dist/core/PopupContext.js +380 -0
- package/dist/core/RowContext.js +1 -1
- package/dist/core/WindowContext.d.ts +15 -0
- package/dist/core/WindowContext.js +28 -0
- package/dist/core/index.d.ts +16 -0
- package/dist/index.css +25 -7
- package/dist/lib/utils/BeanUtils.js +7 -7
- package/dist/templates/DataListTemplate.js +7 -2
- package/dist/templates/ExplorerTemplate.js +1 -1
- package/package.json +5 -5
- package/dist/components/action/AlertMessage.tsx +0 -38
- package/dist/components/action/Button.tsx +0 -230
- package/dist/components/action/CancelEdit.tsx +0 -40
- package/dist/components/action/DeleteData.tsx +0 -73
- package/dist/components/action/Edit.tsx +0 -40
- package/dist/components/action/LookupPage.tsx +0 -113
- package/dist/components/action/ProcessRunner.tsx +0 -337
- package/dist/components/action/Refresh.tsx +0 -35
- package/dist/components/action/SaveData.tsx +0 -74
- package/dist/components/action/SelectData.tsx +0 -47
- package/dist/components/action/Undo.tsx +0 -50
- package/dist/components/action/UpdateContext.tsx +0 -40
- package/dist/components/action/UpdateData.tsx +0 -49
- package/dist/components/action/ViewBackPage.tsx +0 -46
- package/dist/components/action/ViewPage.tsx +0 -141
- package/dist/components/common/UIComponent.tsx +0 -86
- package/dist/components/common/UIInput.tsx +0 -49
- package/dist/components/common/UIMenu.tsx +0 -91
- package/dist/components/index.ts +0 -51
- package/dist/components/input/CodeEditor.tsx +0 -188
- package/dist/components/input/DateField.tsx +0 -274
- package/dist/components/input/DayPicker.tsx +0 -5
- package/dist/components/input/HtmlCode.tsx +0 -203
- package/dist/components/input/JsonCode.tsx +0 -205
- package/dist/components/input/MonthPicker.tsx +0 -5
- package/dist/components/input/ScriptCode.tsx +0 -195
- package/dist/components/input/Select.tsx +0 -78
- package/dist/components/input/SqlCode.tsx +0 -162
- package/dist/components/input/StringDecision.tsx +0 -64
- package/dist/components/input/Text.tsx +0 -57
- package/dist/components/input/YearPicker.tsx +0 -81
- package/dist/components/list/IconMenu.tsx +0 -115
- package/dist/components/list/TabMenu.tsx +0 -127
- package/dist/components/list/TreeMenu.tsx +0 -279
- package/dist/components/list/TxnTaskList.tsx +0 -198
- package/dist/components/output/Label.tsx +0 -50
- package/dist/components/table/DataList.tsx +0 -820
- package/dist/components/table/DataTable.tsx +0 -572
- package/dist/components/table/ListHandler.ts +0 -276
- package/dist/components/table/TableContext.tsx +0 -122
- package/dist/components/view/ComponentView.tsx +0 -102
- package/dist/components/view/FilterView.tsx +0 -21
- package/dist/components/view/HtmlForm.tsx +0 -176
- package/dist/components/view/HtmlView.tsx +0 -98
- package/dist/components/view/IFrameView.tsx +0 -48
- package/dist/components/view/Modal.tsx +0 -72
- package/dist/components/view/PageView.tsx +0 -131
- package/dist/components/view/PopupView.tsx +0 -160
- package/dist/components/view/RootView.tsx +0 -109
- package/dist/components/view/WizardView.tsx +0 -48
- package/dist/lib/layouts/BorderLayout.tsx +0 -31
- package/dist/lib/layouts/CardLayout.tsx +0 -73
- package/dist/lib/layouts/CenterLayout.tsx +0 -20
- package/dist/lib/layouts/GridLayout.tsx +0 -20
- package/dist/lib/layouts/HPanel.tsx +0 -31
- package/dist/lib/layouts/HorizontalLayout.tsx +0 -29
- package/dist/lib/layouts/MainLayout.tsx +0 -16
- package/dist/lib/layouts/PageLayout.tsx +0 -29
- package/dist/lib/layouts/VPanel.tsx +0 -27
- package/dist/lib/layouts/XLayout.tsx +0 -29
- package/dist/lib/layouts/YLayout.tsx +0 -29
- package/dist/lib/layouts/index.ts +0 -13
- /package/dist/components/action/{UpdateContext.d.ts → UpdateState.d.ts} +0 -0
- /package/dist/components/action/{UpdateContext.js → UpdateState.js} +0 -0
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
import { useRef, useState } from "react";
|
|
2
|
-
import UIComponent from "../common/UIComponent";
|
|
3
|
-
import useUIInput, { UIInputProps } from "../common/UIInput";
|
|
4
|
-
|
|
5
|
-
type DateProps = UIInputProps & {
|
|
6
|
-
required?: boolean;
|
|
7
|
-
immediate?: boolean;
|
|
8
|
-
min?: string; // ISO date string
|
|
9
|
-
max?: string; // ISO date string
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
const DateField = (props: DateProps) => {
|
|
13
|
-
const { name, immediate = true, min, max } = props ?? {};
|
|
14
|
-
const [focused, setFocused] = useState(false);
|
|
15
|
-
const [showCalendar, setShowCalendar] = useState(false);
|
|
16
|
-
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
17
|
-
const calendarRef = useRef<HTMLDivElement | null>(null);
|
|
18
|
-
const valueRef = useRef<string>("");
|
|
19
|
-
const className = "border rounded px-2 py-1 w-full";
|
|
20
|
-
|
|
21
|
-
const handleFocus = () => {
|
|
22
|
-
setFocused(true);
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const handleBlur = () => {
|
|
26
|
-
if (!immediate) {
|
|
27
|
-
setValue(inputValue);
|
|
28
|
-
}
|
|
29
|
-
setFocused(false);
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const onRefresh = () => {
|
|
33
|
-
setInputValue(getValue());
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const { initialValue, getValue, setValue, binding } = useUIInput({
|
|
37
|
-
...props,
|
|
38
|
-
onRefresh
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
valueRef.current = initialValue ?? "";
|
|
42
|
-
const [inputValue, setInputValue] = useState(valueRef.current);
|
|
43
|
-
|
|
44
|
-
// Get current date for calendar navigation - use useMemo or direct calculation
|
|
45
|
-
const getCurrentDate = () => {
|
|
46
|
-
if (inputValue && inputValue.match(/^\d{4}-\d{2}-\d{2}$/)) {
|
|
47
|
-
// Parse ISO date string as local date to avoid timezone issues
|
|
48
|
-
const parts = inputValue.split('-');
|
|
49
|
-
const year = parseInt(parts[0], 10);
|
|
50
|
-
const month = parseInt(parts[1], 10) - 1; // Month is 0-indexed
|
|
51
|
-
const day = parseInt(parts[2], 10);
|
|
52
|
-
return new globalThis.Date(year, month, day);
|
|
53
|
-
}
|
|
54
|
-
return new globalThis.Date();
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const [calendarDate, setCalendarDate] = useState(() => getCurrentDate());
|
|
58
|
-
|
|
59
|
-
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
60
|
-
const text = e.target.value ?? "";
|
|
61
|
-
|
|
62
|
-
if (text !== inputValue) {
|
|
63
|
-
valueRef.current = text;
|
|
64
|
-
setInputValue(text);
|
|
65
|
-
|
|
66
|
-
// Update calendar date when user types a valid date
|
|
67
|
-
if (text && text.match(/^\d{4}-\d{2}-\d{2}$/)) {
|
|
68
|
-
const parts = text.split('-');
|
|
69
|
-
const year = parseInt(parts[0], 10);
|
|
70
|
-
const month = parseInt(parts[1], 10) - 1;
|
|
71
|
-
const day = parseInt(parts[2], 10);
|
|
72
|
-
setCalendarDate(new globalThis.Date(year, month, day));
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (immediate) {
|
|
77
|
-
setValue(text);
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const toggleCalendar = () => {
|
|
82
|
-
setShowCalendar(!showCalendar);
|
|
83
|
-
if (!showCalendar) {
|
|
84
|
-
setCalendarDate(getCurrentDate());
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const selectDate = (date: globalThis.Date) => {
|
|
89
|
-
const dateString = date.toISOString().split("T")[0];
|
|
90
|
-
valueRef.current = dateString;
|
|
91
|
-
setInputValue(dateString);
|
|
92
|
-
setValue(dateString);
|
|
93
|
-
setShowCalendar(false);
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const previousMonth = () => {
|
|
97
|
-
const newDate = new globalThis.Date(calendarDate);
|
|
98
|
-
newDate.setMonth(newDate.getMonth() - 1);
|
|
99
|
-
setCalendarDate(newDate);
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const nextMonth = () => {
|
|
103
|
-
const newDate = new globalThis.Date(calendarDate);
|
|
104
|
-
newDate.setMonth(newDate.getMonth() + 1);
|
|
105
|
-
setCalendarDate(newDate);
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
// Generate calendar days
|
|
109
|
-
const generateCalendarDays = () => {
|
|
110
|
-
const year = calendarDate.getFullYear();
|
|
111
|
-
const month = calendarDate.getMonth();
|
|
112
|
-
|
|
113
|
-
const firstDay = new globalThis.Date(year, month, 1);
|
|
114
|
-
const lastDay = new globalThis.Date(year, month + 1, 0);
|
|
115
|
-
const startingDayOfWeek = firstDay.getDay();
|
|
116
|
-
const daysInMonth = lastDay.getDate();
|
|
117
|
-
|
|
118
|
-
const days: (globalThis.Date | null)[] = [];
|
|
119
|
-
|
|
120
|
-
// Add empty cells for days before month starts
|
|
121
|
-
for (let i = 0; i < startingDayOfWeek; i++) {
|
|
122
|
-
days.push(null);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Add days of month
|
|
126
|
-
for (let day = 1; day <= daysInMonth; day++) {
|
|
127
|
-
days.push(new globalThis.Date(year, month, day));
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return days;
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
const isSelectedDate = (date: globalThis.Date) => {
|
|
134
|
-
if (!inputValue || !inputValue.match(/^\d{4}-\d{2}-\d{2}$/)) return false;
|
|
135
|
-
// Parse ISO date string as local date
|
|
136
|
-
const parts = inputValue.split('-');
|
|
137
|
-
const selectedYear = parseInt(parts[0], 10);
|
|
138
|
-
const selectedMonth = parseInt(parts[1], 10) - 1;
|
|
139
|
-
const selectedDay = parseInt(parts[2], 10);
|
|
140
|
-
|
|
141
|
-
return (
|
|
142
|
-
date.getDate() === selectedDay &&
|
|
143
|
-
date.getMonth() === selectedMonth &&
|
|
144
|
-
date.getFullYear() === selectedYear
|
|
145
|
-
);
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
const isToday = (date: globalThis.Date) => {
|
|
149
|
-
const today = new globalThis.Date();
|
|
150
|
-
return (
|
|
151
|
-
date.getDate() === today.getDate() &&
|
|
152
|
-
date.getMonth() === today.getMonth() &&
|
|
153
|
-
date.getFullYear() === today.getFullYear()
|
|
154
|
-
);
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
const monthNames = [
|
|
158
|
-
"January", "February", "March", "April", "May", "June",
|
|
159
|
-
"July", "August", "September", "October", "November", "December"
|
|
160
|
-
];
|
|
161
|
-
|
|
162
|
-
const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
163
|
-
|
|
164
|
-
const calendarDays = generateCalendarDays();
|
|
165
|
-
|
|
166
|
-
return (
|
|
167
|
-
<UIComponent {...(props ?? {})}>
|
|
168
|
-
<div className="relative">
|
|
169
|
-
<div className="flex gap-1">
|
|
170
|
-
<input
|
|
171
|
-
type="date"
|
|
172
|
-
ref={inputRef}
|
|
173
|
-
onChange={onChange}
|
|
174
|
-
value={inputValue}
|
|
175
|
-
onFocus={handleFocus}
|
|
176
|
-
onBlur={handleBlur}
|
|
177
|
-
className={className}
|
|
178
|
-
min={min}
|
|
179
|
-
max={max}
|
|
180
|
-
/>
|
|
181
|
-
<button
|
|
182
|
-
type="button"
|
|
183
|
-
onClick={toggleCalendar}
|
|
184
|
-
className="border rounded px-3 py-1 bg-white hover:bg-gray-50"
|
|
185
|
-
>
|
|
186
|
-
📅
|
|
187
|
-
</button>
|
|
188
|
-
</div>
|
|
189
|
-
|
|
190
|
-
{showCalendar && (
|
|
191
|
-
<div
|
|
192
|
-
ref={calendarRef}
|
|
193
|
-
className="absolute z-10 mt-1 bg-white border rounded shadow-lg p-4"
|
|
194
|
-
style={{ minWidth: "280px" }}
|
|
195
|
-
>
|
|
196
|
-
{/* Calendar Header */}
|
|
197
|
-
<div className="flex justify-between items-center mb-4">
|
|
198
|
-
<button
|
|
199
|
-
type="button"
|
|
200
|
-
onClick={previousMonth}
|
|
201
|
-
className="px-2 py-1 hover:bg-gray-100 rounded"
|
|
202
|
-
>
|
|
203
|
-
◀
|
|
204
|
-
</button>
|
|
205
|
-
<div className="font-semibold">
|
|
206
|
-
{monthNames[calendarDate.getMonth()]} {calendarDate.getFullYear()}
|
|
207
|
-
</div>
|
|
208
|
-
<button
|
|
209
|
-
type="button"
|
|
210
|
-
onClick={nextMonth}
|
|
211
|
-
className="px-2 py-1 hover:bg-gray-100 rounded"
|
|
212
|
-
>
|
|
213
|
-
▶
|
|
214
|
-
</button>
|
|
215
|
-
</div>
|
|
216
|
-
|
|
217
|
-
{/* Day Names */}
|
|
218
|
-
<div className="grid grid-cols-7 gap-1 mb-2">
|
|
219
|
-
{dayNames.map((day) => (
|
|
220
|
-
<div
|
|
221
|
-
key={day}
|
|
222
|
-
className="text-center text-xs font-medium text-gray-600 py-1"
|
|
223
|
-
>
|
|
224
|
-
{day}
|
|
225
|
-
</div>
|
|
226
|
-
))}
|
|
227
|
-
</div>
|
|
228
|
-
|
|
229
|
-
{/* Calendar Days */}
|
|
230
|
-
<div className="grid grid-cols-7 gap-1">
|
|
231
|
-
{calendarDays.map((date, index) => {
|
|
232
|
-
if (!date) {
|
|
233
|
-
return <div key={index} className="aspect-square" />;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const selected = isSelectedDate(date);
|
|
237
|
-
const today = isToday(date);
|
|
238
|
-
|
|
239
|
-
return (
|
|
240
|
-
<button
|
|
241
|
-
key={index}
|
|
242
|
-
type="button"
|
|
243
|
-
onClick={() => selectDate(date)}
|
|
244
|
-
className={`
|
|
245
|
-
aspect-square flex items-center justify-center rounded text-sm
|
|
246
|
-
${selected ? "bg-blue-500 text-white font-semibold" : ""}
|
|
247
|
-
${today && !selected ? "border-2 border-blue-500" : ""}
|
|
248
|
-
${!selected && !today ? "hover:bg-gray-100" : ""}
|
|
249
|
-
`}
|
|
250
|
-
>
|
|
251
|
-
{date.getDate()}
|
|
252
|
-
</button>
|
|
253
|
-
);
|
|
254
|
-
})}
|
|
255
|
-
</div>
|
|
256
|
-
|
|
257
|
-
{/* Close Button */}
|
|
258
|
-
<div className="mt-4 flex justify-end">
|
|
259
|
-
<button
|
|
260
|
-
type="button"
|
|
261
|
-
onClick={() => setShowCalendar(false)}
|
|
262
|
-
className="px-3 py-1 text-sm border rounded hover:bg-gray-50"
|
|
263
|
-
>
|
|
264
|
-
Close
|
|
265
|
-
</button>
|
|
266
|
-
</div>
|
|
267
|
-
</div>
|
|
268
|
-
)}
|
|
269
|
-
</div>
|
|
270
|
-
</UIComponent>
|
|
271
|
-
);
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
export default DateField;
|
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import Editor from "@monaco-editor/react";
|
|
2
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
-
import { usePageContext } from "../../core/PageContext";
|
|
4
|
-
import CopyButton from "../../lib/components/CopyButton";
|
|
5
|
-
import UIComponent from "../common/UIComponent";
|
|
6
|
-
import useUIInput, { UIInputProps } from "../common/UIInput";
|
|
7
|
-
|
|
8
|
-
type HtmlCodeProps = UIInputProps & {
|
|
9
|
-
height?: number | string;
|
|
10
|
-
width?: number | string;
|
|
11
|
-
showCopy?: boolean;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const HtmlCode = (props: HtmlCodeProps) => {
|
|
15
|
-
const { height = 300, width = "100%", showCopy = true } = props ?? {};
|
|
16
|
-
|
|
17
|
-
const pageContext = usePageContext();
|
|
18
|
-
const editorRef = useRef<any>(null);
|
|
19
|
-
const [fontSize, setFontSize] = useState(14);
|
|
20
|
-
const [error, setError] = useState("");
|
|
21
|
-
const [readOnly, setReadOnly] = useState(true);
|
|
22
|
-
|
|
23
|
-
const onRefresh = () => {
|
|
24
|
-
const val = getValue();
|
|
25
|
-
setEditorValue(val ?? "");
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const { initialValue, getValue, setValue } = useUIInput({ ...props, onRefresh });
|
|
29
|
-
|
|
30
|
-
const [editorValue, setEditorValue] = useState(() => initialValue ?? "");
|
|
31
|
-
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
const unsubscribe = pageContext?.dependsTo("editable", (val: any) => {
|
|
34
|
-
const isEditable = val === true || val === "true";
|
|
35
|
-
setReadOnly(!isEditable);
|
|
36
|
-
|
|
37
|
-
if (!isEditable) {
|
|
38
|
-
setEditorValue(getValue() ?? "");
|
|
39
|
-
setError("");
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
return unsubscribe;
|
|
43
|
-
}, []);
|
|
44
|
-
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
return () => {
|
|
47
|
-
editorRef.current = null;
|
|
48
|
-
};
|
|
49
|
-
}, []);
|
|
50
|
-
|
|
51
|
-
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
|
52
|
-
if (e.ctrlKey && e.key === "=") {
|
|
53
|
-
e.preventDefault();
|
|
54
|
-
setFontSize((prev) => Math.min(prev + 1, 40));
|
|
55
|
-
}
|
|
56
|
-
if (e.ctrlKey && e.key === "-") {
|
|
57
|
-
e.preventDefault();
|
|
58
|
-
setFontSize((prev) => Math.max(prev - 1, 8));
|
|
59
|
-
}
|
|
60
|
-
if (e.ctrlKey && e.key === "0") {
|
|
61
|
-
e.preventDefault();
|
|
62
|
-
setFontSize(14);
|
|
63
|
-
}
|
|
64
|
-
}, []);
|
|
65
|
-
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
window.addEventListener("keydown", handleKeyDown);
|
|
68
|
-
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
69
|
-
}, [handleKeyDown]);
|
|
70
|
-
|
|
71
|
-
useEffect(() => {
|
|
72
|
-
editorRef.current?.updateOptions({ fontSize });
|
|
73
|
-
}, [fontSize]);
|
|
74
|
-
|
|
75
|
-
useEffect(() => {
|
|
76
|
-
editorRef.current?.updateOptions({ readOnly, domReadOnly: readOnly });
|
|
77
|
-
}, [readOnly]);
|
|
78
|
-
|
|
79
|
-
const validateHtml = (code: string) => {
|
|
80
|
-
if (!code.trim()) {
|
|
81
|
-
setError("");
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const errors: string[] = [];
|
|
86
|
-
const lines = code.split("\n");
|
|
87
|
-
const tagStack: { tagName: string; line: number }[] = [];
|
|
88
|
-
const tagRegex = /<\/?([a-zA-Z0-9-]+)(\s[^>]*)?>/g;
|
|
89
|
-
|
|
90
|
-
for (let i = 0; i < lines.length; i++) {
|
|
91
|
-
const line = lines[i];
|
|
92
|
-
const matches = [...line.matchAll(tagRegex)];
|
|
93
|
-
|
|
94
|
-
for (const match of matches) {
|
|
95
|
-
const tagName = match[1].toLowerCase();
|
|
96
|
-
const isClosing = match[0].startsWith("</");
|
|
97
|
-
const selfClosing = /\/>$/.test(match[0]) || ["br", "hr", "img", "input", "meta", "link"].includes(tagName);
|
|
98
|
-
|
|
99
|
-
if (!isClosing && !selfClosing) {
|
|
100
|
-
tagStack.push({ tagName, line: i + 1 });
|
|
101
|
-
} else if (isClosing) {
|
|
102
|
-
const lastTag = tagStack.pop();
|
|
103
|
-
if (!lastTag || lastTag.tagName !== tagName) {
|
|
104
|
-
errors.push(`Line ${i + 1}: Unmatched closing tag </${tagName}>`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (i === 0 && !line.toLowerCase().includes("<!doctype")) {
|
|
110
|
-
if (code.includes("<html")) {
|
|
111
|
-
errors.push("Missing <!DOCTYPE html> declaration at the top");
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (i === lines.length - 1 && code.includes("<html")) {
|
|
116
|
-
if (!code.includes("<head")) errors.push("Missing <head> tag");
|
|
117
|
-
if (!code.includes("<body")) errors.push("Missing <body> tag");
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (tagStack.length > 0) {
|
|
122
|
-
const unclosed = tagStack.map((t) => `<${t.tagName}> (opened on line ${t.line})`).join(", ");
|
|
123
|
-
errors.push(`Unclosed tag(s): ${unclosed}`);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
setError(errors.length > 0 ? errors.join("\n• ") : "");
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
const handleEditorChange = (value?: string) => {
|
|
130
|
-
if (readOnly) return;
|
|
131
|
-
const safeValue = value ?? "";
|
|
132
|
-
setEditorValue(safeValue);
|
|
133
|
-
setValue(safeValue); // write through immediately — HTML is always a string
|
|
134
|
-
validateHtml(safeValue);
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
const handleEditorDidMount = (editor: any, monaco: any) => {
|
|
138
|
-
editorRef.current = editor;
|
|
139
|
-
|
|
140
|
-
editor.updateOptions({
|
|
141
|
-
minimap: { enabled: false },
|
|
142
|
-
fontSize,
|
|
143
|
-
lineNumbers: "on",
|
|
144
|
-
renderWhitespace: "selection",
|
|
145
|
-
folding: true,
|
|
146
|
-
bracketPairColorization: { enabled: true },
|
|
147
|
-
formatOnPaste: true,
|
|
148
|
-
formatOnType: true,
|
|
149
|
-
wordWrap: "on",
|
|
150
|
-
autoClosingBrackets: "always",
|
|
151
|
-
autoClosingQuotes: "always",
|
|
152
|
-
suggestOnTriggerCharacters: true,
|
|
153
|
-
});
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
return (
|
|
157
|
-
<UIComponent {...(props ?? {})}>
|
|
158
|
-
<div className="relative w-full bg-white">
|
|
159
|
-
{showCopy && (
|
|
160
|
-
<div className="absolute top-2 right-4 z-50">
|
|
161
|
-
<CopyButton item={editorValue} copySize={15} copiedSize={10} classNameCopied="!px-[15px] !py-[10px]" />
|
|
162
|
-
</div>
|
|
163
|
-
)}
|
|
164
|
-
<Editor
|
|
165
|
-
key="html-code-editor"
|
|
166
|
-
height={height}
|
|
167
|
-
width={width}
|
|
168
|
-
language="html"
|
|
169
|
-
value={editorValue}
|
|
170
|
-
onChange={handleEditorChange}
|
|
171
|
-
onMount={handleEditorDidMount}
|
|
172
|
-
theme="vs-dark"
|
|
173
|
-
options={{
|
|
174
|
-
readOnly,
|
|
175
|
-
domReadOnly: readOnly,
|
|
176
|
-
padding: { top: 10 },
|
|
177
|
-
selectOnLineNumbers: true,
|
|
178
|
-
automaticLayout: true,
|
|
179
|
-
minimap: { enabled: false },
|
|
180
|
-
fontSize,
|
|
181
|
-
wordWrap: "on",
|
|
182
|
-
lineNumbers: "on",
|
|
183
|
-
folding: true,
|
|
184
|
-
bracketPairColorization: { enabled: true },
|
|
185
|
-
formatOnPaste: true,
|
|
186
|
-
formatOnType: true,
|
|
187
|
-
scrollBeyondLastLine: false,
|
|
188
|
-
smoothScrolling: true,
|
|
189
|
-
cursorBlinking: "smooth",
|
|
190
|
-
renderLineHighlight: "all",
|
|
191
|
-
autoClosingBrackets: "always",
|
|
192
|
-
autoClosingQuotes: "always",
|
|
193
|
-
suggestOnTriggerCharacters: true,
|
|
194
|
-
scrollbar: { vertical: "visible", horizontal: "visible" },
|
|
195
|
-
}}
|
|
196
|
-
/>
|
|
197
|
-
{error && !readOnly && <p className="text-red-500 text-xs mt-1 px-1 whitespace-pre-line">{error}</p>}
|
|
198
|
-
</div>
|
|
199
|
-
</UIComponent>
|
|
200
|
-
);
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
export default HtmlCode;
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import Editor from "@monaco-editor/react";
|
|
2
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
-
import { usePageContext } from "../../core/PageContext";
|
|
4
|
-
import CopyButton from "../../lib/components/CopyButton";
|
|
5
|
-
import UIComponent from "../common/UIComponent";
|
|
6
|
-
import useUIInput, { UIInputProps } from "../common/UIInput";
|
|
7
|
-
|
|
8
|
-
type JsonCodeProps = UIInputProps & {
|
|
9
|
-
height?: number | string;
|
|
10
|
-
width?: number | string;
|
|
11
|
-
showCopy?: boolean;
|
|
12
|
-
onParsed?: (value: any) => void;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const JsonCode = (props: JsonCodeProps) => {
|
|
16
|
-
const { height = 300, width = "100%", showCopy = true, onParsed } = props ?? {};
|
|
17
|
-
|
|
18
|
-
const pageContext = usePageContext();
|
|
19
|
-
const editorRef = useRef<any>(null);
|
|
20
|
-
const [fontSize, setFontSize] = useState(14);
|
|
21
|
-
const [error, setError] = useState("");
|
|
22
|
-
const [readOnly, setReadOnly] = useState(true);
|
|
23
|
-
|
|
24
|
-
const stringify = (val: any): string => {
|
|
25
|
-
if (val == null) return "";
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
return typeof val === "string" ? val : JSON.stringify(val, null, 2);
|
|
29
|
-
} catch (error) {
|
|
30
|
-
console.error("Error stringifying value:", error);
|
|
31
|
-
return "";
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const onRefresh = () => {
|
|
36
|
-
const val = getValue();
|
|
37
|
-
updateValue(val);
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
// Initialize once from pageContext — no buffering
|
|
41
|
-
const { initialValue, getValue, setValue } = useUIInput({ ...props, onRefresh });
|
|
42
|
-
|
|
43
|
-
const [editorValue, setEditorValue] = useState<string | null>(stringify(initialValue));
|
|
44
|
-
|
|
45
|
-
const updateValue = (value: any) => {
|
|
46
|
-
if (error != null && error !== "") {
|
|
47
|
-
// no updating of editor value if there's an error
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const strValue = stringify(value);
|
|
52
|
-
let strEditorValue = "";
|
|
53
|
-
try {
|
|
54
|
-
const parsedEditorValue = JSON.parse(editorValue ?? "");
|
|
55
|
-
strEditorValue = stringify(parsedEditorValue);
|
|
56
|
-
} catch (e) {}
|
|
57
|
-
|
|
58
|
-
const matched = strValue === strEditorValue;
|
|
59
|
-
// console.log("updateValue ", { value, strValue, strEditorValue, matched });
|
|
60
|
-
|
|
61
|
-
if (!matched) {
|
|
62
|
-
setEditorValue(strValue);
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
const unsubscribe = pageContext?.dependsTo("editable", (val: any) => {
|
|
68
|
-
const isEditable = val === true || val === "true";
|
|
69
|
-
setReadOnly(!isEditable);
|
|
70
|
-
setError("");
|
|
71
|
-
|
|
72
|
-
if (!isEditable) {
|
|
73
|
-
const val = getValue();
|
|
74
|
-
updateValue(val);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
return unsubscribe;
|
|
78
|
-
}, []);
|
|
79
|
-
|
|
80
|
-
useEffect(() => {
|
|
81
|
-
return () => {
|
|
82
|
-
// console.log("unmount");
|
|
83
|
-
editorRef.current = null;
|
|
84
|
-
};
|
|
85
|
-
}, []);
|
|
86
|
-
|
|
87
|
-
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
|
88
|
-
if (e.ctrlKey && e.key === "=") {
|
|
89
|
-
e.preventDefault();
|
|
90
|
-
setFontSize((prev) => Math.min(prev + 1, 40));
|
|
91
|
-
}
|
|
92
|
-
if (e.ctrlKey && e.key === "-") {
|
|
93
|
-
e.preventDefault();
|
|
94
|
-
setFontSize((prev) => Math.max(prev - 1, 8));
|
|
95
|
-
}
|
|
96
|
-
if (e.ctrlKey && e.key === "0") {
|
|
97
|
-
e.preventDefault();
|
|
98
|
-
setFontSize(14);
|
|
99
|
-
}
|
|
100
|
-
}, []);
|
|
101
|
-
|
|
102
|
-
useEffect(() => {
|
|
103
|
-
window.addEventListener("keydown", handleKeyDown);
|
|
104
|
-
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
105
|
-
}, [handleKeyDown]);
|
|
106
|
-
|
|
107
|
-
useEffect(() => {
|
|
108
|
-
editorRef.current?.updateOptions({ fontSize });
|
|
109
|
-
}, [fontSize]);
|
|
110
|
-
|
|
111
|
-
useEffect(() => {
|
|
112
|
-
editorRef.current?.updateOptions({ readOnly, domReadOnly: readOnly });
|
|
113
|
-
}, [readOnly]);
|
|
114
|
-
|
|
115
|
-
const handleEditorChange = (value?: string) => {
|
|
116
|
-
if (readOnly) return;
|
|
117
|
-
const safeValue = value ?? "";
|
|
118
|
-
setEditorValue(safeValue);
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
const parsed = JSON.parse(safeValue);
|
|
122
|
-
setValue(parsed); // write through to pageContext immediately
|
|
123
|
-
setError("");
|
|
124
|
-
onParsed?.(parsed);
|
|
125
|
-
} catch (e: any) {
|
|
126
|
-
setError(e.message);
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const handleEditorDidMount = (editor: any, monaco: any) => {
|
|
131
|
-
editorRef.current = editor;
|
|
132
|
-
|
|
133
|
-
try {
|
|
134
|
-
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
|
|
135
|
-
validate: true,
|
|
136
|
-
allowComments: false,
|
|
137
|
-
schemas: [],
|
|
138
|
-
enableSchemaRequest: true,
|
|
139
|
-
});
|
|
140
|
-
} catch (e) {
|
|
141
|
-
console.warn("Monaco JSON diagnostics setup failed:", e);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
editor.updateOptions({
|
|
145
|
-
minimap: { enabled: false },
|
|
146
|
-
fontSize,
|
|
147
|
-
lineNumbers: "on",
|
|
148
|
-
renderWhitespace: "selection",
|
|
149
|
-
folding: false,
|
|
150
|
-
bracketPairColorization: { enabled: true },
|
|
151
|
-
formatOnPaste: false,
|
|
152
|
-
formatOnType: false,
|
|
153
|
-
});
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
// useEffect(() => {
|
|
157
|
-
// console.log("editor value changed", editorValue, getValue());
|
|
158
|
-
// }, [editorValue]);
|
|
159
|
-
|
|
160
|
-
return (
|
|
161
|
-
<UIComponent {...(props ?? {})}>
|
|
162
|
-
<div className="relative w-full bg-white">
|
|
163
|
-
{showCopy && (
|
|
164
|
-
<div className="absolute top-2 right-4 z-50">
|
|
165
|
-
<CopyButton item={editorValue ?? ""} copySize={15} copiedSize={10} classNameCopied="!px-[15px] !py-[10px]" />
|
|
166
|
-
</div>
|
|
167
|
-
)}
|
|
168
|
-
<Editor
|
|
169
|
-
key="json-code-viewer"
|
|
170
|
-
height={height}
|
|
171
|
-
width={width}
|
|
172
|
-
language="json"
|
|
173
|
-
value={editorValue ?? ""}
|
|
174
|
-
onChange={handleEditorChange}
|
|
175
|
-
onMount={handleEditorDidMount}
|
|
176
|
-
theme="vs-dark"
|
|
177
|
-
options={{
|
|
178
|
-
readOnly,
|
|
179
|
-
domReadOnly: readOnly,
|
|
180
|
-
padding: { top: 10 },
|
|
181
|
-
selectOnLineNumbers: true,
|
|
182
|
-
automaticLayout: true,
|
|
183
|
-
minimap: { enabled: false },
|
|
184
|
-
fontSize,
|
|
185
|
-
wordWrap: "off",
|
|
186
|
-
lineNumbers: "on",
|
|
187
|
-
renderWhitespace: "selection",
|
|
188
|
-
folding: false,
|
|
189
|
-
bracketPairColorization: { enabled: true },
|
|
190
|
-
formatOnType: false,
|
|
191
|
-
formatOnPaste: false,
|
|
192
|
-
scrollBeyondLastLine: false,
|
|
193
|
-
smoothScrolling: true,
|
|
194
|
-
cursorBlinking: "smooth",
|
|
195
|
-
renderLineHighlight: "all",
|
|
196
|
-
scrollbar: { vertical: "visible", horizontal: "visible" },
|
|
197
|
-
}}
|
|
198
|
-
/>
|
|
199
|
-
{error && !readOnly && <p className="text-red-500 text-xs mt-1 px-1">{error}</p>}
|
|
200
|
-
</div>
|
|
201
|
-
</UIComponent>
|
|
202
|
-
);
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
export default JsonCode;
|