@alpaca-editor/core 1.0.3938 → 1.0.3939

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 (75) hide show
  1. package/dist/editor/ContentTree.js +12 -8
  2. package/dist/editor/ContentTree.js.map +1 -1
  3. package/dist/editor/ContextMenu.d.ts +1 -1
  4. package/dist/editor/ContextMenu.js +17 -3
  5. package/dist/editor/ContextMenu.js.map +1 -1
  6. package/dist/editor/FieldActionsOverlay.d.ts +17 -0
  7. package/dist/editor/FieldActionsOverlay.js +148 -0
  8. package/dist/editor/FieldActionsOverlay.js.map +1 -0
  9. package/dist/editor/FieldHistory.d.ts +2 -1
  10. package/dist/editor/FieldHistory.js +11 -8
  11. package/dist/editor/FieldHistory.js.map +1 -1
  12. package/dist/editor/FieldListField.js +14 -17
  13. package/dist/editor/FieldListField.js.map +1 -1
  14. package/dist/editor/PictureEditor.js +28 -2
  15. package/dist/editor/PictureEditor.js.map +1 -1
  16. package/dist/editor/Titlebar.js +18 -9
  17. package/dist/editor/Titlebar.js.map +1 -1
  18. package/dist/editor/ai/AiTerminal.js +27 -41
  19. package/dist/editor/ai/AiTerminal.js.map +1 -1
  20. package/dist/editor/client/EditorClient.js +48 -18
  21. package/dist/editor/client/EditorClient.js.map +1 -1
  22. package/dist/editor/client/editContext.d.ts +1 -1
  23. package/dist/editor/client/editContext.js.map +1 -1
  24. package/dist/editor/client/itemsRepository.js +126 -90
  25. package/dist/editor/client/itemsRepository.js.map +1 -1
  26. package/dist/editor/menubar/BrowseHistory.js +3 -4
  27. package/dist/editor/menubar/BrowseHistory.js.map +1 -1
  28. package/dist/editor/menubar/PageSelector.js +37 -9
  29. package/dist/editor/menubar/PageSelector.js.map +1 -1
  30. package/dist/editor/page-editor-chrome/FieldActionIndicator.js +1 -1
  31. package/dist/editor/page-editor-chrome/FieldActionIndicator.js.map +1 -1
  32. package/dist/editor/page-viewer/PageViewerFrame.js +98 -2
  33. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  34. package/dist/editor/pageModel.d.ts +14 -0
  35. package/dist/editor/reviews/Comment.js +3 -2
  36. package/dist/editor/reviews/Comment.js.map +1 -1
  37. package/dist/editor/services/editService.d.ts +1 -1
  38. package/dist/editor/services/editService.js +2 -1
  39. package/dist/editor/services/editService.js.map +1 -1
  40. package/dist/editor/ui/Icons.js +1 -1
  41. package/dist/editor/ui/Icons.js.map +1 -1
  42. package/dist/editor/ui/ItemList.d.ts +16 -0
  43. package/dist/editor/ui/ItemList.js +19 -0
  44. package/dist/editor/ui/ItemList.js.map +1 -0
  45. package/dist/editor/ui/ItemSearch.js +2 -12
  46. package/dist/editor/ui/ItemSearch.js.map +1 -1
  47. package/dist/editor/ui/SimpleTabs.js +1 -1
  48. package/dist/editor/ui/SimpleTabs.js.map +1 -1
  49. package/dist/revision.d.ts +2 -2
  50. package/dist/revision.js +2 -2
  51. package/dist/styles.css +3 -8
  52. package/package.json +1 -1
  53. package/src/editor/ContentTree.tsx +15 -12
  54. package/src/editor/ContextMenu.tsx +20 -2
  55. package/src/editor/FieldActionsOverlay.tsx +307 -0
  56. package/src/editor/FieldHistory.tsx +9 -8
  57. package/src/editor/FieldListField.tsx +29 -29
  58. package/src/editor/PictureEditor.tsx +66 -1
  59. package/src/editor/Titlebar.tsx +22 -11
  60. package/src/editor/ai/AiTerminal.tsx +42 -53
  61. package/src/editor/client/EditorClient.tsx +62 -18
  62. package/src/editor/client/editContext.ts +5 -1
  63. package/src/editor/client/itemsRepository.ts +151 -115
  64. package/src/editor/menubar/BrowseHistory.tsx +7 -16
  65. package/src/editor/menubar/PageSelector.tsx +91 -66
  66. package/src/editor/page-editor-chrome/FieldActionIndicator.tsx +1 -1
  67. package/src/editor/page-viewer/PageViewerFrame.tsx +143 -0
  68. package/src/editor/pageModel.ts +12 -0
  69. package/src/editor/reviews/Comment.tsx +5 -6
  70. package/src/editor/services/editService.ts +2 -0
  71. package/src/editor/ui/Icons.tsx +1 -0
  72. package/src/editor/ui/ItemList.tsx +76 -0
  73. package/src/editor/ui/ItemSearch.tsx +9 -46
  74. package/src/editor/ui/SimpleTabs.tsx +1 -1
  75. package/src/revision.ts +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"SimpleTabs.js","sourceRoot":"","sources":["../../../src/editor/ui/SimpleTabs.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AASzC,MAAM,UAAU,UAAU,CAAC,EACzB,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,SAAS,GAMV;IACC,IAAI,SAAS,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChC,SAAS,GAAG,CAAC,CAAC;IAChB,CAAC;IAED,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;QAClB,SAAS,GAAG,CAAC,CAAC;IAChB,CAAC;IAED,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEnC,OAAO,CACL,8BACE,cAAK,SAAS,EAAE,OAAO,CAAC,yBAAyB,EAAE,SAAS,CAAC,YAC1D,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,CACxB,iBACE,EAAE,EAAE,GAAG,CAAC,EAAE,EACV,SAAS,EAAE,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,gBAAgB,iBAEnD,GAAG,CAAC,MAAM,EACvB,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,EAClC,KAAK,EAAE;wBACL,UAAU,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ;wBACnD,YAAY,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,MAAM;wBAC9D,YAAY,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK;wBAC/C,KAAK,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM;qBAC9C,YAEA,GAAG,CAAC,KAAK,IAVL,GAAG,CAAC,EAAE,CAWJ,CACV,CAAC,GACE,EACL,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,IACxB,CACJ,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"SimpleTabs.js","sourceRoot":"","sources":["../../../src/editor/ui/SimpleTabs.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AASzC,MAAM,UAAU,UAAU,CAAC,EACzB,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,SAAS,GAMV;IACC,IAAI,SAAS,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChC,SAAS,GAAG,CAAC,CAAC;IAChB,CAAC;IAED,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;QAClB,SAAS,GAAG,CAAC,CAAC;IAChB,CAAC;IAED,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEnC,OAAO,CACL,8BACE,cAAK,SAAS,EAAE,OAAO,CAAC,iBAAiB,EAAE,SAAS,CAAC,YAClD,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,CACxB,iBACE,EAAE,EAAE,GAAG,CAAC,EAAE,EACV,SAAS,EAAE,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,gBAAgB,iBAEnD,GAAG,CAAC,MAAM,EACvB,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,EAClC,KAAK,EAAE;wBACL,UAAU,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ;wBACnD,YAAY,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,MAAM;wBAC9D,YAAY,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK;wBAC/C,KAAK,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM;qBAC9C,YAEA,GAAG,CAAC,KAAK,IAVL,GAAG,CAAC,EAAE,CAWJ,CACV,CAAC,GACE,EACL,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,IACxB,CACJ,CAAC;AACJ,CAAC"}
@@ -1,2 +1,2 @@
1
- export declare const version = "1.0.3938";
2
- export declare const buildDate = "2025-06-06 15:49:56";
1
+ export declare const version = "1.0.3939";
2
+ export declare const buildDate = "2025-06-07 12:23:08";
package/dist/revision.js CHANGED
@@ -1,3 +1,3 @@
1
- export const version = "1.0.3938";
2
- export const buildDate = "2025-06-06 15:49:56";
1
+ export const version = "1.0.3939";
2
+ export const buildDate = "2025-06-07 12:23:08";
3
3
  //# sourceMappingURL=revision.js.map
package/dist/styles.css CHANGED
@@ -675,6 +675,9 @@
675
675
  .min-w-64 {
676
676
  min-width: calc(var(--spacing) * 64);
677
677
  }
678
+ .min-w-96 {
679
+ min-width: calc(var(--spacing) * 96);
680
+ }
678
681
  .min-w-\[8rem\] {
679
682
  min-width: 8rem;
680
683
  }
@@ -1450,9 +1453,6 @@
1450
1453
  .pr-6 {
1451
1454
  padding-right: calc(var(--spacing) * 6);
1452
1455
  }
1453
- .pr-8 {
1454
- padding-right: calc(var(--spacing) * 8);
1455
- }
1456
1456
  .pb-0 {
1457
1457
  padding-bottom: calc(var(--spacing) * 0);
1458
1458
  }
@@ -2523,11 +2523,6 @@
2523
2523
  padding-top: calc(var(--spacing) * 0);
2524
2524
  }
2525
2525
  }
2526
- .md\:pb-3 {
2527
- @media (width >= 48rem) {
2528
- padding-bottom: calc(var(--spacing) * 3);
2529
- }
2530
- }
2531
2526
  .md\:pb-4 {
2532
2527
  @media (width >= 48rem) {
2533
2528
  padding-bottom: calc(var(--spacing) * 4);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alpaca-editor/core",
3
- "version": "1.0.3938",
3
+ "version": "1.0.3939",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -231,12 +231,6 @@ export default function ContentTree({
231
231
 
232
232
  const refreshNode = useCallback(
233
233
  async (node: TreeNode) => {
234
- const tempNode = { ...node };
235
-
236
- tempNode.children = undefined;
237
-
238
- await doPreloadNodes(expandedKeys || [], [tempNode as CustomTreeNode]);
239
-
240
234
  const item = await editContext?.itemsRepository.getItem(
241
235
  node.data as ItemDescriptor,
242
236
  );
@@ -256,16 +250,25 @@ export default function ContentTree({
256
250
  });
257
251
  customNode.selectable = !selectPagesOnly || item.hasLayout;
258
252
  customNode.data = item;
259
- customNode.hasChildren = item.hasChildren;
260
- // Preserve existing parent relationship - don't update it
261
- }
253
+ // Don't set hasChildren yet - we'll determine it after loading children
262
254
 
263
- node.children = tempNode.children;
255
+ // Always reload children to get the current state, regardless of expand state
256
+ const newChildren = await loadNodeChildren(customNode);
257
+ customNode.children = newChildren;
258
+
259
+ // Update hasChildren based on actual children count
260
+ customNode.hasChildren = newChildren.length > 0;
261
+
262
+ // If node is not expanded, clear children to save memory but keep hasChildren correct
263
+ if (!expandedKeys?.includes(node.key as string)) {
264
+ customNode.children = undefined;
265
+ }
266
+ }
264
267
  },
265
268
  [
266
269
  editContext?.itemsRepository,
267
270
  expandedKeys,
268
- doPreloadNodes,
271
+ loadNodeChildren,
269
272
  isDraggable,
270
273
  selectPagesOnly,
271
274
  ],
@@ -341,7 +344,7 @@ export default function ContentTree({
341
344
  if (hasNewKeys) {
342
345
  setExpandedKeys([...new Set([...expandedKeys, ...newExpandedKeys])]);
343
346
  }
344
- }, [expandIdPath, expandedKeys, rootItemId]);
347
+ }, [expandIdPath, rootItemId]);
345
348
 
346
349
  useEffect(() => {
347
350
  const newNodeDictionary: { [key: string]: CustomTreeNode } = {};
@@ -70,6 +70,12 @@ export async function showComponentContextMenu(
70
70
  editContext: EditContextType,
71
71
  field?: FieldDescriptor,
72
72
  fieldButtons?: FieldButton[],
73
+ onParameterizedAction?: (
74
+ field: FieldDescriptor,
75
+ action: FieldButton,
76
+ allFieldButtons: FieldButton[],
77
+ event: any,
78
+ ) => void,
73
79
  ) {
74
80
  const componentCommandMenuItems = await getComponentMenuItems(
75
81
  editContext,
@@ -89,8 +95,20 @@ export async function showComponentContextMenu(
89
95
  menuItems.push({
90
96
  id: button.id,
91
97
  label: button.label,
92
- command: async () => {
93
- editContext!.triggerFieldAction(field, button);
98
+ command: async (event) => {
99
+ // Check if the button has parameters
100
+ if (button.parameters && button.parameters.length > 0) {
101
+ // If we have a parameterized action handler, use it
102
+ if (onParameterizedAction) {
103
+ onParameterizedAction(field, button, fieldButtons, event);
104
+ } else {
105
+ // Fallback: execute without parameters (might not work well)
106
+ editContext!.triggerFieldAction(field, button);
107
+ }
108
+ } else {
109
+ // For simple actions, execute directly
110
+ editContext!.triggerFieldAction(field, button);
111
+ }
94
112
  },
95
113
  icon: button.icon ? <img src={button.icon} width={16} /> : undefined,
96
114
  });
@@ -0,0 +1,307 @@
1
+ import React, { useState, useRef, useEffect } from "react";
2
+ import { OverlayPanel } from "primereact/overlaypanel";
3
+ import { FieldButton, FieldButtonParameter } from "./pageModel";
4
+
5
+ interface FieldActionsOverlayProps {
6
+ generatorButtons?: FieldButton[];
7
+ onActionClick: (action: FieldButton) => void;
8
+ onParameterizedActionExecute: (
9
+ action: FieldButton,
10
+ parameters: Record<string, string>,
11
+ ) => void;
12
+ currentOverlay?: any;
13
+ fieldId: string;
14
+ setCurrentOverlay: (overlay: any) => void;
15
+ preSelectedAction?: FieldButton; // Auto-select this action when overlay opens
16
+ }
17
+
18
+ const FieldActionsOverlay = React.forwardRef<
19
+ FieldActionsOverlayRef,
20
+ FieldActionsOverlayProps
21
+ >(
22
+ (
23
+ {
24
+ generatorButtons,
25
+ onActionClick,
26
+ onParameterizedActionExecute,
27
+ currentOverlay,
28
+ fieldId,
29
+ setCurrentOverlay,
30
+ preSelectedAction,
31
+ },
32
+ ref,
33
+ ) => {
34
+ const overlayRef = useRef<OverlayPanel>(null);
35
+ const [selectedParameterizedAction, setSelectedParameterizedAction] =
36
+ useState<FieldButton | null>(null);
37
+ const [parameterValues, setParameterValues] = useState<
38
+ Record<string, string>
39
+ >({});
40
+
41
+ const overlayId = fieldId + "_generators";
42
+
43
+ useEffect(() => {
44
+ if (currentOverlay !== overlayId) {
45
+ overlayRef.current?.hide();
46
+ resetParameterizedAction();
47
+ }
48
+ }, [currentOverlay, overlayId]);
49
+
50
+ // Auto-select preSelectedAction if provided (removed from useEffect)
51
+ // Now handled in the show function
52
+
53
+ function resetParameterizedAction(): void {
54
+ setSelectedParameterizedAction(null);
55
+ setParameterValues({});
56
+ }
57
+
58
+ function handleActionClick(action: FieldButton): void {
59
+ if (action.parameters && action.parameters.length > 0) {
60
+ setSelectedParameterizedAction(action);
61
+ // Initialize parameter values with defaults
62
+ const initialValues: Record<string, string> = {};
63
+ action.parameters.forEach((param) => {
64
+ initialValues[param.id] = param.defaultValue || "";
65
+ });
66
+ setParameterValues(initialValues);
67
+ } else {
68
+ onActionClick(action);
69
+ overlayRef.current?.hide();
70
+ }
71
+ }
72
+
73
+ function executeParameterizedAction(): void {
74
+ if (selectedParameterizedAction) {
75
+ onParameterizedActionExecute(
76
+ selectedParameterizedAction,
77
+ parameterValues,
78
+ );
79
+ resetParameterizedAction();
80
+ overlayRef.current?.hide();
81
+ }
82
+ }
83
+
84
+ function goBackToActionList(): void {
85
+ resetParameterizedAction();
86
+ }
87
+
88
+ function cancelParameterizedAction(): void {
89
+ resetParameterizedAction();
90
+ overlayRef.current?.hide();
91
+ }
92
+
93
+ function updateParameterValue(paramId: string, value: string): void {
94
+ setParameterValues((prev) => ({ ...prev, [paramId]: value }));
95
+ }
96
+
97
+ function areRequiredParametersFilled(): boolean {
98
+ if (!selectedParameterizedAction?.parameters) return true;
99
+
100
+ return selectedParameterizedAction.parameters.every((param) => {
101
+ if (!param.required) return true;
102
+ const value = parameterValues[param.id] || "";
103
+ return value.trim().length > 0;
104
+ });
105
+ }
106
+
107
+ function renderParameterInput(
108
+ param: FieldButtonParameter,
109
+ shouldAutoFocus: boolean = false,
110
+ ): React.ReactNode {
111
+ const value = parameterValues[param.id] || "";
112
+ const commonProps = {
113
+ value,
114
+ onChange: (
115
+ e: React.ChangeEvent<
116
+ HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
117
+ >,
118
+ ) => updateParameterValue(param.id, e.target.value),
119
+ onKeyDown: (e: React.KeyboardEvent) => {
120
+ if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
121
+ e.preventDefault();
122
+ if (areRequiredParametersFilled()) {
123
+ executeParameterizedAction();
124
+ }
125
+ } else if (e.key === "Escape") {
126
+ e.preventDefault();
127
+ goBackToActionList();
128
+ }
129
+ },
130
+ className: "w-full rounded border p-2 text-xs",
131
+ placeholder: param.placeholder || "",
132
+ autoFocus: shouldAutoFocus,
133
+ };
134
+
135
+ switch (param.type) {
136
+ case "textarea":
137
+ return (
138
+ <textarea
139
+ {...commonProps}
140
+ rows={param.rows || 3}
141
+ className={commonProps.className + " resize-none"}
142
+ />
143
+ );
144
+ case "number":
145
+ return <input {...commonProps} type="number" />;
146
+ case "select":
147
+ return (
148
+ <select {...commonProps}>
149
+ <option value="">
150
+ {param.placeholder || "Select an option..."}
151
+ </option>
152
+ {param.options?.map((option) => (
153
+ <option key={option.value} value={option.value}>
154
+ {option.label}
155
+ </option>
156
+ ))}
157
+ </select>
158
+ );
159
+ case "text":
160
+ default:
161
+ return <input {...commonProps} type="text" />;
162
+ }
163
+ }
164
+
165
+ function show(event: any, actionToSelect?: FieldButton): void {
166
+ console.log("FieldActionsOverlay show called with:", {
167
+ event,
168
+ actionToSelect,
169
+ preSelectedAction,
170
+ overlayId,
171
+ isTrusted: event?.isTrusted,
172
+ });
173
+
174
+ // Set overlay state first, then show
175
+ setCurrentOverlay(overlayId);
176
+
177
+ // Check if this is a synthetic event (from context menu) or real event (from button click)
178
+ if (
179
+ event &&
180
+ event.isTrusted === false &&
181
+ event.target &&
182
+ event.target.style
183
+ ) {
184
+ // This is likely our synthetic event with a positioned target element
185
+ console.log("Using synthetic event positioning");
186
+ overlayRef.current?.toggle(event, event.target);
187
+ } else {
188
+ // This is a normal event, use standard positioning
189
+ console.log("Using normal event positioning");
190
+ overlayRef.current?.toggle(event);
191
+ }
192
+
193
+ // Use the passed actionToSelect or fall back to preSelectedAction prop
194
+ const actionToAutoSelect = actionToSelect || preSelectedAction;
195
+
196
+ // Auto-select the action after overlay is shown
197
+ if (
198
+ actionToAutoSelect &&
199
+ actionToAutoSelect.parameters &&
200
+ actionToAutoSelect.parameters.length > 0
201
+ ) {
202
+ console.log("Auto-selecting action in 50ms:", actionToAutoSelect);
203
+ // Use longer timeout to ensure overlay is stable
204
+ setTimeout(() => {
205
+ console.log(
206
+ "Calling handleActionClick for action:",
207
+ actionToAutoSelect,
208
+ );
209
+ handleActionClick(actionToAutoSelect);
210
+ }, 50);
211
+ } else {
212
+ console.log(
213
+ "Not auto-selecting - actionToAutoSelect:",
214
+ actionToAutoSelect,
215
+ );
216
+ }
217
+ }
218
+
219
+ function hide(): void {
220
+ overlayRef.current?.hide();
221
+ }
222
+
223
+ React.useImperativeHandle(ref, () => ({
224
+ show,
225
+ hide,
226
+ }));
227
+
228
+ return (
229
+ <OverlayPanel ref={overlayRef} className="p-1">
230
+ {selectedParameterizedAction ? (
231
+ <div className="min-w-64 p-2">
232
+ <div className="mb-2">
233
+ <div className="mb-2 flex items-center">
234
+ <button
235
+ className="mr-2 text-xs hover:text-gray-400"
236
+ onClick={goBackToActionList}
237
+ >
238
+ <i className="pi pi-arrow-left" />
239
+ </button>
240
+ <span className="text-xs font-semibold">
241
+ {selectedParameterizedAction.label}
242
+ </span>
243
+ </div>
244
+ <div className="mb-3 text-xs text-gray-600">
245
+ {selectedParameterizedAction.description}
246
+ </div>
247
+
248
+ {selectedParameterizedAction.parameters?.map((param, index) => (
249
+ <div key={param.id} className="mb-3">
250
+ <label className="mb-1 block text-xs font-medium">
251
+ {param.label}
252
+ {param.required && (
253
+ <span className="ml-1 text-red-500">*</span>
254
+ )}
255
+ </label>
256
+ {renderParameterInput(param, index === 0)}
257
+ </div>
258
+ ))}
259
+
260
+ <div className="mt-1 text-xs text-gray-500">
261
+ Ctrl+Enter to execute, Esc to cancel
262
+ </div>
263
+ </div>
264
+ <div className="flex gap-2">
265
+ <button
266
+ className="rounded bg-blue-500 px-3 py-1 text-xs text-white hover:bg-blue-600 disabled:cursor-not-allowed disabled:opacity-50"
267
+ onClick={executeParameterizedAction}
268
+ disabled={!areRequiredParametersFilled()}
269
+ >
270
+ Execute
271
+ </button>
272
+ <button
273
+ className="rounded bg-gray-300 px-3 py-1 text-xs text-gray-700 hover:bg-gray-400"
274
+ onClick={cancelParameterizedAction}
275
+ >
276
+ Cancel
277
+ </button>
278
+ </div>
279
+ </div>
280
+ ) : (
281
+ <>
282
+ {generatorButtons?.map((x) => (
283
+ <button
284
+ key={x.id}
285
+ className="block p-1 text-xs hover:text-gray-400"
286
+ onClick={() => handleActionClick(x)}
287
+ >
288
+ {x.icon && <i className={x.icon + " mr-2 text-xs"} />}
289
+ {x.label}
290
+ </button>
291
+ ))}
292
+ </>
293
+ )}
294
+ </OverlayPanel>
295
+ );
296
+ },
297
+ );
298
+
299
+ // Export methods to be used by parent component
300
+ export interface FieldActionsOverlayRef {
301
+ show: (event: any, actionToSelect?: FieldButton) => void;
302
+ hide: () => void;
303
+ }
304
+
305
+ FieldActionsOverlay.displayName = "FieldActionsOverlay";
306
+
307
+ export { FieldActionsOverlay };
@@ -12,9 +12,11 @@ import { SimpleIconButton } from "./ui/SimpleIconButton";
12
12
  export function FieldHistory({
13
13
  field,
14
14
  onHover,
15
+ onRevert,
15
16
  }: {
16
17
  field: Field;
17
18
  onHover: (historyEntry: FieldHistoryItem | undefined) => void;
19
+ onRevert?: () => void;
18
20
  }) {
19
21
  const [fieldHistory, setFieldHistory] = useState<FieldHistoryItem[]>([]);
20
22
  const editContext = useEditContext();
@@ -30,13 +32,11 @@ export function FieldHistory({
30
32
  }, [field]);
31
33
 
32
34
  if (fieldHistory.length === 0)
33
- return <div className="p-2 text-gray-500 text-xs">No history</div>;
35
+ return <div className="p-2 text-xs text-gray-500">No history</div>;
34
36
 
35
37
  return (
36
38
  <div>
37
- <div className="text-xs p-2 bg-gray-100 text-gray-500">
38
- Field History
39
- </div>
39
+ <div className="bg-gray-100 p-2 text-xs text-gray-500">Field History</div>
40
40
  <div className="max-h-96 overflow-auto">
41
41
  <SimpleTable
42
42
  items={fieldHistory}
@@ -55,13 +55,14 @@ export function FieldHistory({
55
55
  body: (x) => (
56
56
  <SimpleIconButton
57
57
  icon="pi pi-undo"
58
- onClick={() =>
59
- editContext.operations.editField({
58
+ onClick={async () => {
59
+ await editContext.operations.editField({
60
60
  field: field.descriptor,
61
61
  rawValue: x.rawValue,
62
62
  refresh: "immediate",
63
- })
64
- }
63
+ });
64
+ onRevert?.();
65
+ }}
65
66
  label="Revert"
66
67
  />
67
68
  ),
@@ -12,6 +12,10 @@ import { getSessionWithFieldLock } from "./utils";
12
12
 
13
13
  import { loadFieldButtons } from "./services/editService";
14
14
  import { Field, FieldButton } from "./pageModel";
15
+ import {
16
+ FieldActionsOverlay,
17
+ FieldActionsOverlayRef,
18
+ } from "./FieldActionsOverlay";
15
19
  import { useThrottledCallback } from "use-debounce";
16
20
  import { SimpleIconButton } from "./ui/SimpleIconButton";
17
21
  import { FieldHistory } from "./FieldHistory";
@@ -43,15 +47,21 @@ export default function FieldListField({
43
47
  const fieldItem = field.descriptor.item;
44
48
 
45
49
  const [showRawValue, setShowRawValue] = useState(false);
46
- const generatorsOverlay = useRef<OverlayPanel>(null);
50
+ const generatorsOverlay = useRef<FieldActionsOverlayRef>(null);
47
51
  const fieldHistoryOverlay = useRef<OverlayPanel>(null);
48
52
  const [generatorButtons, setButtons] = useState<FieldButton[]>();
49
53
  const [isModified, setIsModified] = useState(false);
50
54
  const [isSaved, setIsSaved] = useState(false);
51
55
  const [historyEntry, setHistoryEntry] = useState<FieldHistoryItem>();
56
+ async function handleActionClick(action: FieldButton): Promise<void> {
57
+ editContext?.triggerFieldAction(field.descriptor, action);
58
+ }
52
59
 
53
- async function editButtonClicked(x: FieldButton): Promise<void> {
54
- editContext?.triggerFieldAction(field.descriptor, x);
60
+ async function handleParameterizedActionExecute(
61
+ action: FieldButton,
62
+ parameters: Record<string, string>,
63
+ ): Promise<void> {
64
+ editContext?.triggerFieldAction(field.descriptor, action, parameters);
55
65
  }
56
66
 
57
67
  useEffect(() => {
@@ -148,24 +158,16 @@ export default function FieldListField({
148
158
  }
149
159
  }, [isFocusedField]);
150
160
 
151
- useEffect(() => {
152
- if (editContext.currentOverlay !== field.id + "_generators")
153
- generatorsOverlay.current?.hide();
154
- }, [editContext.currentOverlay]);
155
-
156
161
  const renderGeneratorButtons = () => {
157
162
  if (generatorButtons?.length || 0 > 0)
158
163
  return (
159
164
  <>
160
165
  {(!executingAction || executingAction.state !== "running") && (
161
166
  <SimpleIconButton
162
- icon={<WizardIcon className="h-4 w-4" />}
167
+ icon={<WizardIcon className="h-5 w-5" />}
163
168
  label="Generate"
164
169
  onClick={(e: any) => {
165
- generatorsOverlay.current?.toggle(e);
166
- editContext.setCurrentOverlay(field.id + "_generators");
167
- e.preventDefault();
168
- e.stopPropagation();
170
+ generatorsOverlay.current?.show(e);
169
171
  }}
170
172
  ></SimpleIconButton>
171
173
  )}
@@ -175,21 +177,15 @@ export default function FieldListField({
175
177
  {executingAction?.label}
176
178
  </div>
177
179
  )}
178
- <OverlayPanel ref={generatorsOverlay} className="p-1">
179
- {generatorButtons?.map((x) => (
180
- <button
181
- key={x.id}
182
- className="block p-1 text-xs hover:text-gray-400"
183
- onClick={() => {
184
- editButtonClicked(x);
185
- generatorsOverlay.current?.hide();
186
- }}
187
- >
188
- {x.icon && <i className={x.icon + " mr-2 text-xs"} />}
189
- {x.label}
190
- </button>
191
- ))}
192
- </OverlayPanel>
180
+ <FieldActionsOverlay
181
+ ref={generatorsOverlay}
182
+ generatorButtons={generatorButtons}
183
+ onActionClick={handleActionClick}
184
+ onParameterizedActionExecute={handleParameterizedActionExecute}
185
+ currentOverlay={editContext.currentOverlay}
186
+ fieldId={field.id}
187
+ setCurrentOverlay={editContext.setCurrentOverlay}
188
+ />
193
189
  </>
194
190
  );
195
191
  return null;
@@ -245,7 +241,10 @@ export default function FieldListField({
245
241
  {field.isShared && (
246
242
  <span className="text-xs text-gray-400">[Shared]</span>
247
243
  )}
248
- <span className="text-xs">·</span>
244
+ {((!field.isFallback && field.rawValue !== null) ||
245
+ showFallbackButton ||
246
+ (config?.buttons && config.buttons.length > 0) ||
247
+ !simplified) && <span className="text-xs">·</span>}
249
248
  {!field.isFallback && field.rawValue !== null ? (
250
249
  <button
251
250
  className="p-0 text-xs"
@@ -313,6 +312,7 @@ export default function FieldListField({
313
312
  <FieldHistory
314
313
  field={field}
315
314
  onHover={(x) => setHistoryEntry(x)}
315
+ onRevert={() => fieldHistoryOverlay.current?.hide()}
316
316
  />
317
317
  </OverlayPanel>
318
318
  </div>