@ramesesinc/platform-core 0.1.6 → 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/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 +7 -7
- package/dist/core/PageContext.js +17 -7
- package/dist/core/PageViewContext.d.ts +13 -1
- package/dist/core/PageViewContext.js +75 -2
- 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/UpdateData.tsx +0 -49
- package/dist/components/action/UpdateState.tsx +0 -40
- 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
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
import { useRef, useState, useCallback, useEffect } from "react";
|
|
2
|
-
import Editor from "@monaco-editor/react";
|
|
3
|
-
import UIComponent from "../common/UIComponent";
|
|
4
|
-
import useUIInput, { UIInputProps } from "../common/UIInput";
|
|
5
|
-
//import CopyButton from "../ui/CopyButton";
|
|
6
|
-
|
|
7
|
-
type CodeEditorType = "sql" | "json" | "java" | "javascript" | "html" | "css" | "typescript" | "xml" | "yaml" | "markdown" | "plaintext";
|
|
8
|
-
|
|
9
|
-
const languageMap: Record<CodeEditorType, string> = {
|
|
10
|
-
sql: "sql",
|
|
11
|
-
json: "json",
|
|
12
|
-
java: "groovy",
|
|
13
|
-
javascript: "javascript",
|
|
14
|
-
html: "html",
|
|
15
|
-
css: "css",
|
|
16
|
-
typescript: "typescript",
|
|
17
|
-
xml: "xml",
|
|
18
|
-
yaml: "yaml",
|
|
19
|
-
markdown: "markdown",
|
|
20
|
-
plaintext: "plaintext",
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
type CodeEditorProps = UIInputProps & {
|
|
24
|
-
type?: CodeEditorType;
|
|
25
|
-
height?: number | string;
|
|
26
|
-
width?: number | string;
|
|
27
|
-
readOnly?: boolean;
|
|
28
|
-
immediate?: boolean;
|
|
29
|
-
showCopy?: boolean;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const CodeEditor = (props: CodeEditorProps) => {
|
|
33
|
-
const {
|
|
34
|
-
type = "plaintext",
|
|
35
|
-
height = 300,
|
|
36
|
-
width = "100%",
|
|
37
|
-
readOnly = false,
|
|
38
|
-
immediate = true,
|
|
39
|
-
showCopy = true,
|
|
40
|
-
} = props ?? {};
|
|
41
|
-
|
|
42
|
-
const editorRef = useRef<any>(null);
|
|
43
|
-
const valueRef = useRef<string>("");
|
|
44
|
-
const [fontSize, setFontSize] = useState(14);
|
|
45
|
-
const [error, setError] = useState("");
|
|
46
|
-
|
|
47
|
-
const language = languageMap[type] ?? type;
|
|
48
|
-
|
|
49
|
-
const onRefresh = () => {
|
|
50
|
-
setEditorValue(getValue());
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const { initialValue, getValue, setValue } = useUIInput({ ...props, onRefresh });
|
|
54
|
-
valueRef.current = initialValue ?? "";
|
|
55
|
-
const [editorValue, setEditorValue] = useState(valueRef.current);
|
|
56
|
-
|
|
57
|
-
// Font size keyboard shortcuts
|
|
58
|
-
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
|
59
|
-
if (e.ctrlKey && e.key === "=") {
|
|
60
|
-
e.preventDefault();
|
|
61
|
-
setFontSize((prev) => Math.min(prev + 1, 40));
|
|
62
|
-
}
|
|
63
|
-
if (e.ctrlKey && e.key === "-") {
|
|
64
|
-
e.preventDefault();
|
|
65
|
-
setFontSize((prev) => Math.max(prev - 1, 8));
|
|
66
|
-
}
|
|
67
|
-
if (e.ctrlKey && e.key === "0") {
|
|
68
|
-
e.preventDefault();
|
|
69
|
-
setFontSize(14);
|
|
70
|
-
}
|
|
71
|
-
}, []);
|
|
72
|
-
|
|
73
|
-
useEffect(() => {
|
|
74
|
-
window.addEventListener("keydown", handleKeyDown);
|
|
75
|
-
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
76
|
-
}, [handleKeyDown]);
|
|
77
|
-
|
|
78
|
-
// JSON-specific validation
|
|
79
|
-
const validateJson = (value: string) => {
|
|
80
|
-
if (!value.trim()) {
|
|
81
|
-
setError("");
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
try {
|
|
85
|
-
JSON.parse(value);
|
|
86
|
-
setError("");
|
|
87
|
-
} catch (e: any) {
|
|
88
|
-
setError(e.message);
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const handleEditorChange = (value?: string) => {
|
|
93
|
-
const safeValue = value ?? "";
|
|
94
|
-
setEditorValue(safeValue);
|
|
95
|
-
|
|
96
|
-
if (type === "json") {
|
|
97
|
-
validateJson(safeValue);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (immediate) {
|
|
101
|
-
setValue(safeValue);
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const handleEditorDidMount = (editor: any, monaco: any) => {
|
|
106
|
-
editorRef.current = editor;
|
|
107
|
-
|
|
108
|
-
if (type === "json") {
|
|
109
|
-
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
|
|
110
|
-
validate: true,
|
|
111
|
-
allowComments: false,
|
|
112
|
-
schemas: [],
|
|
113
|
-
enableSchemaRequest: true,
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
editor.updateOptions({
|
|
118
|
-
minimap: { enabled: false },
|
|
119
|
-
fontSize,
|
|
120
|
-
lineNumbers: "on",
|
|
121
|
-
renderWhitespace: "selection",
|
|
122
|
-
folding: true,
|
|
123
|
-
bracketPairColorization: { enabled: true },
|
|
124
|
-
formatOnPaste: true,
|
|
125
|
-
formatOnType: true,
|
|
126
|
-
});
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
// Sync font size if editor already mounted
|
|
130
|
-
useEffect(() => {
|
|
131
|
-
editorRef.current?.updateOptions({ fontSize });
|
|
132
|
-
}, [fontSize]);
|
|
133
|
-
|
|
134
|
-
/*
|
|
135
|
-
//<CopyButton
|
|
136
|
-
// item={editorValue}
|
|
137
|
-
// copySize={15}
|
|
138
|
-
// copiedSize={10}
|
|
139
|
-
// classNameCopied="!px-[15px] !py-[10px]"
|
|
140
|
-
///>
|
|
141
|
-
*/
|
|
142
|
-
|
|
143
|
-
return (
|
|
144
|
-
<UIComponent {...(props ?? {})}>
|
|
145
|
-
<div className="relative w-full bg-white">
|
|
146
|
-
{showCopy && (
|
|
147
|
-
<div className="absolute top-2 right-4 z-50">
|
|
148
|
-
</div>
|
|
149
|
-
)}
|
|
150
|
-
<Editor
|
|
151
|
-
height={height}
|
|
152
|
-
width={width}
|
|
153
|
-
language={language}
|
|
154
|
-
value={editorValue}
|
|
155
|
-
onChange={handleEditorChange}
|
|
156
|
-
onMount={handleEditorDidMount}
|
|
157
|
-
theme="vs-dark"
|
|
158
|
-
options={{
|
|
159
|
-
readOnly,
|
|
160
|
-
domReadOnly: readOnly,
|
|
161
|
-
padding: { top: 10 },
|
|
162
|
-
selectOnLineNumbers: true,
|
|
163
|
-
automaticLayout: true,
|
|
164
|
-
minimap: { enabled: false },
|
|
165
|
-
fontSize,
|
|
166
|
-
wordWrap: "off",
|
|
167
|
-
lineNumbers: "on",
|
|
168
|
-
renderWhitespace: "selection",
|
|
169
|
-
folding: true,
|
|
170
|
-
bracketPairColorization: { enabled: true },
|
|
171
|
-
formatOnPaste: true,
|
|
172
|
-
formatOnType: true,
|
|
173
|
-
scrollBeyondLastLine: false,
|
|
174
|
-
smoothScrolling: true,
|
|
175
|
-
cursorBlinking: "smooth",
|
|
176
|
-
renderLineHighlight: "all",
|
|
177
|
-
scrollbar: { vertical: "visible", horizontal: "visible" },
|
|
178
|
-
}}
|
|
179
|
-
/>
|
|
180
|
-
{error && type === "json" && (
|
|
181
|
-
<p className="text-red-500 text-xs mt-1 px-1">{error}</p>
|
|
182
|
-
)}
|
|
183
|
-
</div>
|
|
184
|
-
</UIComponent>
|
|
185
|
-
);
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
export default CodeEditor;
|
|
@@ -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;
|