@alpaca-editor/core 1.0.3812 → 1.0.3815

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 (117) hide show
  1. package/dist/components/ui/context-menu.d.ts +25 -0
  2. package/dist/components/ui/context-menu.js +51 -0
  3. package/dist/components/ui/context-menu.js.map +1 -0
  4. package/dist/config/config.js +3 -2
  5. package/dist/config/config.js.map +1 -1
  6. package/dist/config/types.d.ts +4 -2
  7. package/dist/editor/ComponentInfo.js +1 -1
  8. package/dist/editor/ComponentInfo.js.map +1 -1
  9. package/dist/editor/ConfirmationDialog.d.ts +1 -1
  10. package/dist/editor/ContextMenu.d.ts +1 -1
  11. package/dist/editor/ContextMenu.js +24 -9
  12. package/dist/editor/ContextMenu.js.map +1 -1
  13. package/dist/editor/ImageEditor.js +2 -2
  14. package/dist/editor/ImageEditor.js.map +1 -1
  15. package/dist/editor/ItemInfo.js +2 -2
  16. package/dist/editor/ItemInfo.js.map +1 -1
  17. package/dist/editor/MainLayout.js +3 -3
  18. package/dist/editor/MainLayout.js.map +1 -1
  19. package/dist/editor/Titlebar.js +1 -1
  20. package/dist/editor/Titlebar.js.map +1 -1
  21. package/dist/editor/ai/AiTerminal.js +19 -12
  22. package/dist/editor/ai/AiTerminal.js.map +1 -1
  23. package/dist/editor/client/EditorClient.js +19 -4
  24. package/dist/editor/client/EditorClient.js.map +1 -1
  25. package/dist/editor/client/editContext.d.ts +1 -1
  26. package/dist/editor/client/operations.js +15 -14
  27. package/dist/editor/client/operations.js.map +1 -1
  28. package/dist/editor/client/pageModelBuilder.js +8 -5
  29. package/dist/editor/client/pageModelBuilder.js.map +1 -1
  30. package/dist/editor/commands/componentCommands.js +15 -13
  31. package/dist/editor/commands/componentCommands.js.map +1 -1
  32. package/dist/editor/componentTreeHelper.js +3 -3
  33. package/dist/editor/componentTreeHelper.js.map +1 -1
  34. package/dist/editor/control-center/ControlCenterMenu.js +3 -3
  35. package/dist/editor/control-center/ControlCenterMenu.js.map +1 -1
  36. package/dist/editor/field-types/TreeListEditor.js +4 -4
  37. package/dist/editor/field-types/TreeListEditor.js.map +1 -1
  38. package/dist/editor/page-editor-chrome/FrameMenu.js +52 -14
  39. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  40. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +5 -5
  41. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
  42. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js +114 -45
  43. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js.map +1 -1
  44. package/dist/editor/page-viewer/PageViewerFrame.d.ts +0 -1
  45. package/dist/editor/page-viewer/PageViewerFrame.js +119 -215
  46. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  47. package/dist/editor/page-viewer/pageModelBuilder.d.ts +3 -0
  48. package/dist/editor/page-viewer/pageModelBuilder.js +299 -0
  49. package/dist/editor/page-viewer/pageModelBuilder.js.map +1 -0
  50. package/dist/editor/pageModel.d.ts +5 -0
  51. package/dist/editor/sidebar/ComponentPalette.js +30 -30
  52. package/dist/editor/sidebar/ComponentPalette.js.map +1 -1
  53. package/dist/editor/sidebar/ComponentTree.js +7 -6
  54. package/dist/editor/sidebar/ComponentTree.js.map +1 -1
  55. package/dist/editor/sidebar/MainContentTree.js +1 -1
  56. package/dist/editor/sidebar/MainContentTree.js.map +1 -1
  57. package/dist/editor/sidebar/SidebarView.js +3 -3
  58. package/dist/editor/sidebar/SidebarView.js.map +1 -1
  59. package/dist/editor/ui/CopyToClipboardButton.js +2 -1
  60. package/dist/editor/ui/CopyToClipboardButton.js.map +1 -1
  61. package/dist/editor/ui/Icons.d.ts +0 -1
  62. package/dist/editor/ui/Icons.js +0 -3
  63. package/dist/editor/ui/Icons.js.map +1 -1
  64. package/dist/editor/ui/Section.js +1 -1
  65. package/dist/editor/ui/Section.js.map +1 -1
  66. package/dist/editor/ui/SimpleMenu.d.ts +1 -8
  67. package/dist/editor/ui/SimpleMenu.js +1 -1
  68. package/dist/editor/ui/SimpleMenu.js.map +1 -1
  69. package/dist/editor/utils.d.ts +2 -2
  70. package/dist/editor/utils.js +57 -9
  71. package/dist/editor/utils.js.map +1 -1
  72. package/dist/lib/safelist.js +1 -1
  73. package/dist/lib/safelist.js.map +1 -1
  74. package/dist/splash-screen/SplashScreen.js +0 -1
  75. package/dist/splash-screen/SplashScreen.js.map +1 -1
  76. package/dist/styles.css +242 -59
  77. package/dist/types.d.ts +2 -2
  78. package/package.json +3 -2
  79. package/src/components/ui/context-menu.tsx +250 -0
  80. package/src/config/config.tsx +2 -2
  81. package/src/config/types.ts +4 -2
  82. package/src/editor/ComponentInfo.tsx +3 -5
  83. package/src/editor/ConfirmationDialog.tsx +1 -1
  84. package/src/editor/ContextMenu.tsx +68 -19
  85. package/src/editor/ImageEditor.tsx +2 -2
  86. package/src/editor/ItemInfo.tsx +4 -4
  87. package/src/editor/MainLayout.tsx +3 -4
  88. package/src/editor/Titlebar.tsx +1 -1
  89. package/src/editor/ai/AiTerminal.tsx +31 -24
  90. package/src/editor/client/EditorClient.tsx +23 -6
  91. package/src/editor/client/editContext.ts +1 -1
  92. package/src/editor/client/operations.ts +16 -14
  93. package/src/editor/client/pageModelBuilder.ts +26 -18
  94. package/src/editor/commands/componentCommands.tsx +58 -39
  95. package/src/editor/componentTreeHelper.tsx +3 -2
  96. package/src/editor/control-center/ControlCenterMenu.tsx +4 -4
  97. package/src/editor/field-types/TreeListEditor.tsx +11 -11
  98. package/src/editor/page-editor-chrome/FrameMenu.tsx +81 -17
  99. package/src/editor/page-editor-chrome/InlineEditor.tsx +5 -5
  100. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +12 -15
  101. package/src/editor/page-editor-chrome/PlaceholderDropZones.tsx +159 -68
  102. package/src/editor/page-viewer/PageViewerFrame.tsx +131 -300
  103. package/src/editor/page-viewer/pageModelBuilder.ts +412 -0
  104. package/src/editor/pageModel.ts +5 -0
  105. package/src/editor/sidebar/ComponentPalette.tsx +108 -106
  106. package/src/editor/sidebar/ComponentTree.tsx +10 -5
  107. package/src/editor/sidebar/MainContentTree.tsx +1 -1
  108. package/src/editor/sidebar/SidebarView.tsx +9 -9
  109. package/src/editor/ui/CopyToClipboardButton.tsx +2 -1
  110. package/src/editor/ui/Icons.tsx +0 -18
  111. package/src/editor/ui/Section.tsx +4 -4
  112. package/src/editor/ui/SimpleMenu.tsx +5 -13
  113. package/src/editor/utils.ts +74 -17
  114. package/src/lib/safelist.tsx +2 -0
  115. package/src/splash-screen/SplashScreen.tsx +0 -1
  116. package/src/types.ts +2 -4
  117. package/styles.css +58 -17
@@ -10,6 +10,19 @@ import {
10
10
  } from "../../types";
11
11
 
12
12
  import { Component, ItemDescriptor, Placeholder } from "../pageModel";
13
+ import {
14
+ Check,
15
+ Copy,
16
+ MessageCircleMore,
17
+ Palette,
18
+ Plus,
19
+ RefreshCcw,
20
+ Sparkles,
21
+ Square,
22
+ StopCircle,
23
+ Trash2,
24
+ TriangleAlert,
25
+ } from "lucide-react";
13
26
 
14
27
  export type ComponentCommandData = CommandData & {
15
28
  components: Component[];
@@ -37,11 +50,11 @@ export function getSelectedComponentCommands(editContext: EditContextType) {
37
50
 
38
51
  const componentCommands = getComponentCommands(
39
52
  selectedComponents,
40
- editContext
53
+ editContext,
41
54
  );
42
55
 
43
56
  const componentCommandMenuItems = componentCommands.filter(
44
- (x) => x.visibilityScopes.indexOf("menu") >= 0
57
+ (x) => x.visibilityScopes.indexOf("menu") >= 0,
45
58
  );
46
59
 
47
60
  return componentCommandMenuItems;
@@ -49,14 +62,14 @@ export function getSelectedComponentCommands(editContext: EditContextType) {
49
62
 
50
63
  export function getComponentCommands(
51
64
  entities: (Placeholder | Component)[],
52
- editContext: EditContextType
65
+ editContext: EditContextType,
53
66
  ): ComponentCommand[] {
54
67
  const components = entities.filter(
55
- (x) => x && !isPlaceholder(x)
68
+ (x) => x && !isPlaceholder(x),
56
69
  ) as Component[];
57
70
 
58
71
  const commands = [
59
- getCreateCommentCommand(),
72
+ getCreateCommentCommand(components),
60
73
  getInsertCommand(components, editContext),
61
74
  getDeleteCommand(components, editContext),
62
75
  getDuplicateCommand(components, editContext),
@@ -72,7 +85,7 @@ export function getComponentCommands(
72
85
 
73
86
  function getInsertCommand(
74
87
  components: Component[],
75
- editContext: EditContextType
88
+ editContext: EditContextType,
76
89
  ): ComponentCommand | null {
77
90
  if (components.length !== 1 || isPlaceholder(components[0])) return null;
78
91
  const item = components[0];
@@ -80,7 +93,7 @@ function getInsertCommand(
80
93
  if (!item.placeholders || item.placeholders.length === 0) return null;
81
94
  return {
82
95
  id: "insert",
83
- icon: "pi pi-plus",
96
+ icon: <Plus size={14} />,
84
97
  label: "Insert component",
85
98
  disabled: (context) => !context.editContext.page?.item.canWriteItem,
86
99
  visibilityScopes: ["editFrame", "contextMenu"],
@@ -92,13 +105,13 @@ function getInsertCommand(
92
105
 
93
106
  function getDuplicateCommand(
94
107
  components: Component[],
95
- editContext: EditContextType
108
+ editContext: EditContextType,
96
109
  ): ComponentCommand | null {
97
110
  if (components.length !== 1) return null;
98
111
 
99
112
  return {
100
113
  id: "duplicate",
101
- icon: "pi pi-copy",
114
+ icon: <Copy size={14} />,
102
115
  label: "Duplicate",
103
116
  visibilityScopes: ["contextMenu", "menu"],
104
117
  disabled: (c) => !(c.editContext.page?.item.canWriteItem || false),
@@ -118,7 +131,7 @@ function getDuplicateCommand(
118
131
  function getAiCommand(editContext: EditContextType): ComponentCommand {
119
132
  return {
120
133
  id: "ai",
121
- icon: "pi pi-sparkles",
134
+ icon: <Sparkles size={14} />,
122
135
  label: "AI",
123
136
  disabled: () => false,
124
137
 
@@ -134,7 +147,7 @@ function getAiCommand(editContext: EditContextType): ComponentCommand {
134
147
 
135
148
  function getDesignCommand(
136
149
  components: Component[],
137
- editContext: EditContextType
150
+ editContext: EditContextType,
138
151
  ): ComponentCommand | null {
139
152
  if (components.length !== 1 || isPlaceholder(components[0])) return null;
140
153
  const item = components[0];
@@ -142,21 +155,21 @@ function getDesignCommand(
142
155
  if (!item.datasourceItem?.fields) return null;
143
156
  if (
144
157
  !Object.values(item.datasourceItem.fields).find(
145
- (x) => x.section === "Design"
158
+ (x) => x.section === "Design",
146
159
  )
147
160
  )
148
161
  return null;
149
162
 
150
163
  return {
151
164
  id: "design",
152
- icon: "pi pi-palette",
165
+ icon: <Palette size={14} />,
153
166
  label: "Design",
154
167
  disabled: () => false,
155
168
  execute: async (context: CommandContext<any>) => {
156
169
  editContext.showFieldEditorPopup(
157
170
  item.datasourceItem?.fields || [],
158
171
  ["Design", "Rendering"],
159
- context.event!
172
+ context.event!,
160
173
  );
161
174
  },
162
175
  visibilityScopes: ["editFrame", "contextMenu"],
@@ -165,7 +178,7 @@ function getDesignCommand(
165
178
 
166
179
  function getLinkToMasterCommand(
167
180
  components: Component[],
168
- editContext: EditContextType
181
+ editContext: EditContextType,
169
182
  ): ComponentCommand | null {
170
183
  if (!components.length) return null;
171
184
  if (editContext.page?.item?.masterLanguages?.length === 0) return null;
@@ -174,7 +187,7 @@ function getLinkToMasterCommand(
174
187
  components,
175
188
  editContext,
176
189
  "componentLinkedToMasterLanguage",
177
- "Linked To Master Language"
190
+ "Linked To Master Language",
178
191
  );
179
192
  }
180
193
 
@@ -182,30 +195,34 @@ function getCheckboxCommand(
182
195
  components: Component[],
183
196
  editContext: EditContextType,
184
197
  fieldName: string,
185
- label: string
198
+ label: string,
186
199
  ): ComponentCommand | null {
187
200
  const someLinked = components.find(
188
201
  (x) =>
189
202
  x.datasourceItem?.fields.find((x) => x.name === fieldName)?.rawValue ===
190
- "1"
203
+ "1",
191
204
  );
192
205
  const allLinked = !components.find(
193
206
  (x) =>
194
207
  x.datasourceItem?.fields.find((x) => x.name === fieldName)?.rawValue !==
195
- "1"
208
+ "1",
196
209
  );
197
210
 
198
211
  return {
199
212
  id: fieldName,
200
- icon:
201
- "pi pi-" +
202
- (allLinked ? "check-square" : someLinked ? "stop-circle" : "stop"),
213
+ icon: allLinked ? (
214
+ <Check size={14} />
215
+ ) : someLinked ? (
216
+ <StopCircle size={14} />
217
+ ) : (
218
+ <Square size={14} />
219
+ ),
203
220
  label,
204
221
  disabled: () => false,
205
222
  execute: async () => {
206
223
  components.forEach((c) => {
207
224
  const field = c.datasourceItem?.fields.find(
208
- (x) => x.name === fieldName
225
+ (x) => x.name === fieldName,
209
226
  );
210
227
  if (!field) return;
211
228
  editContext.operations.editField({
@@ -221,7 +238,7 @@ function getCheckboxCommand(
221
238
 
222
239
  function getInheritChildrenFromMasterCommand(
223
240
  components: Component[],
224
- editContext: EditContextType
241
+ editContext: EditContextType,
225
242
  ): ComponentCommand | null {
226
243
  if (!components.length) return null;
227
244
  if (editContext.page?.item?.masterLanguages?.length === 0) return null;
@@ -232,7 +249,7 @@ function getInheritChildrenFromMasterCommand(
232
249
  components,
233
250
  editContext,
234
251
  "inheritChildrenFromMasterLanguage",
235
- "Inherit Children From Master"
252
+ "Inherit Children From Master",
236
253
  );
237
254
  }
238
255
 
@@ -243,7 +260,7 @@ function isLocked(c: Component, editContext: EditContextType): boolean {
243
260
  x.fieldLock?.item.id === c.id &&
244
261
  x.fieldLock.item.language == c.datasourceItem?.language &&
245
262
  x.fieldLock.item.version === c.datasourceItem?.version &&
246
- x.sessionId !== editContext.sessionId
263
+ x.sessionId !== editContext.sessionId,
247
264
  ) !== undefined
248
265
  ) {
249
266
  return true;
@@ -252,21 +269,21 @@ function isLocked(c: Component, editContext: EditContextType): boolean {
252
269
  c.placeholders?.find(
253
270
  (x) =>
254
271
  x.components.find((child) => isLocked(child, editContext)) !==
255
- undefined
272
+ undefined,
256
273
  ) !== undefined
257
274
  );
258
275
  }
259
276
 
260
277
  function getDeleteCommand(
261
278
  components: Component[],
262
- editContext: EditContextType
279
+ editContext: EditContextType,
263
280
  ): ComponentCommand | null {
264
281
  const applicableComponents = components.filter(
265
282
  (c) =>
266
283
  ((!isPlaceholder(c) && !c.layoutId) ||
267
284
  c.layoutId === editContext.page?.item.id) &&
268
285
  !isLocked(c, editContext) &&
269
- editContext.page?.item.canWriteItem
286
+ editContext.page?.item.canWriteItem,
270
287
  );
271
288
 
272
289
  if (applicableComponents.length === 0) return null;
@@ -274,7 +291,7 @@ function getDeleteCommand(
274
291
  return {
275
292
  id: "delete",
276
293
  label: "Delete",
277
- icon: "pi pi-fw pi-trash",
294
+ icon: <Trash2 size={14} />,
278
295
  execute: async (context: CommandContext<any>) =>
279
296
  deleteComponents(applicableComponents, context.editContext),
280
297
  disabled: () =>
@@ -295,7 +312,7 @@ function canSynchronize(placeholderData: Placeholder) {
295
312
 
296
313
  function getSyncCommand(
297
314
  components: (Placeholder | Component)[],
298
- editContext: EditContextType
315
+ editContext: EditContextType,
299
316
  ): ComponentCommand {
300
317
  const placeholders: PlaceholderToSynchronize[] = [];
301
318
 
@@ -321,13 +338,13 @@ function getSyncCommand(
321
338
  placeholders.length > 0 ||
322
339
  (components.length === 0 &&
323
340
  editContext.page?.rootComponent.placeholders.find((x) =>
324
- canSynchronize(x)
341
+ canSynchronize(x),
325
342
  ));
326
343
 
327
344
  return {
328
345
  id: "synchronize",
329
346
  label: "Synchronize",
330
- icon: "pi pi-fw pi-sync",
347
+ icon: <RefreshCcw size={14} />,
331
348
  disabled: () => !canSync,
332
349
  visibilityScopes: ["menu", "editFrame", "contextMenu"],
333
350
  execute: async () => {
@@ -348,7 +365,7 @@ function getSyncCommand(
348
365
 
349
366
  function deleteComponents(
350
367
  components: Component[],
351
- editContext: EditContextType
368
+ editContext: EditContextType,
352
369
  ) {
353
370
  if (!components.length) return;
354
371
 
@@ -360,7 +377,7 @@ function deleteComponents(
360
377
  : "Are you sure you want to remove this component? ") +
361
378
  components.map((x) => x.name).join(", "),
362
379
  header: components.length > 1 ? "Remove components" : "Remove component",
363
- icon: "pi pi-exclamation-triangle",
380
+ icon: <TriangleAlert size={14} />,
364
381
  accept: () => {
365
382
  if (!editContext.page?.item.descriptor) return;
366
383
  console.log("Remove components", components);
@@ -378,13 +395,15 @@ function deleteComponents(
378
395
  });
379
396
  }
380
397
 
381
- function getCreateCommentCommand(): ComponentCommand | null {
382
- // if (components.length !== 1 || isPlaceholder(components[0])) return null;
398
+ function getCreateCommentCommand(
399
+ components: Component[],
400
+ ): ComponentCommand | null {
401
+ if (components.length !== 1 || isPlaceholder(components[0])) return null;
383
402
 
384
403
  return {
385
404
  id: "addComment",
386
405
  label: "Add Comment",
387
- icon: "pi pi-comment",
406
+ icon: <MessageCircleMore size={14} />,
388
407
  disabled: () => false,
389
408
  visibilityScopes: ["contextMenu"],
390
409
  execute: async (context: CommandContext<any>) => {
@@ -396,7 +415,7 @@ function getCreateCommentCommand(): ComponentCommand | null {
396
415
 
397
416
  function getItemDescriptor(
398
417
  component: Component,
399
- editContext: EditContextType
418
+ editContext: EditContextType,
400
419
  ): ItemDescriptor | null {
401
420
  return {
402
421
  id: component.id,
@@ -104,11 +104,12 @@ export const isValidPlaceholder = (
104
104
  (y) =>
105
105
  y.typeId === dragObject.typeId ||
106
106
  y.typeId === dragObject?.templateId ||
107
- y.compatibleTemplates?.find((z) => z == dragObject.typeId) !==
107
+ y.compatibleTypeIds?.find((z) => z == dragObject.typeId) !==
108
108
  undefined ||
109
109
  y.typeId === dragObject.templateId ||
110
- y.compatibleTemplates?.find((z) => z == dragObject.templateId) !==
110
+ y.compatibleTypeIds?.find((z) => z == dragObject.templateId) !==
111
111
  undefined,
112
+ undefined,
112
113
  ) !== undefined
113
114
  );
114
115
  };
@@ -9,7 +9,7 @@ export function ControlCenterMenu() {
9
9
  const searchParams = useSearchParams();
10
10
  const urlActiveItemKey = searchParams.get("ccpanel");
11
11
  const [activeItemKey, setActiveItemKey] = useState<string | null>(
12
- urlActiveItemKey
12
+ urlActiveItemKey,
13
13
  );
14
14
 
15
15
  const router = useRouter();
@@ -34,12 +34,12 @@ export function ControlCenterMenu() {
34
34
 
35
35
  const items = config?.controlCenter.groups.map((group) => {
36
36
  return {
37
- key: group.title,
37
+ id: group.title,
38
38
  label: group.title,
39
39
  icon: group.icon,
40
40
  items:
41
41
  group.panels.map((panel) => ({
42
- key: panel.id,
42
+ id: panel.id,
43
43
  label: panel.title,
44
44
  })) || [],
45
45
  };
@@ -64,7 +64,7 @@ export function ControlCenterMenu() {
64
64
  items={items}
65
65
  activeItemKey={activeItemKey}
66
66
  onItemClick={(item) => {
67
- setActiveItemKey(item.key);
67
+ setActiveItemKey(item.id);
68
68
  }}
69
69
  />
70
70
  );
@@ -33,7 +33,7 @@ export default function TreeListEditor({
33
33
  ItemData[]
34
34
  >([]);
35
35
  const [selectedFromList, setSelectedFromList] = useState<ReferencedItem[]>(
36
- []
36
+ [],
37
37
  );
38
38
  const overlayPanelRef = useRef<OverlayPanel>(null);
39
39
  const [currentItemId, setCurrentItemId] = useState<string>("");
@@ -109,7 +109,7 @@ export default function TreeListEditor({
109
109
  idPath?: string;
110
110
  name: string;
111
111
  icon?: string;
112
- }[]
112
+ }[],
113
113
  ) {
114
114
  setSelectedItemNodesInTree([]);
115
115
 
@@ -138,8 +138,8 @@ export default function TreeListEditor({
138
138
  }
139
139
 
140
140
  return (
141
- <div className="border focus-shadow border-gray-200">
142
- <div className="bg-white mb-1 p-2 border-b border-gray-200">
141
+ <div className="focus-shadow border border-gray-200">
142
+ <div className="mb-1 border-b border-gray-200 bg-white p-2">
143
143
  <ItemSearch
144
144
  rootItemIds={rootItemIds.map((x) => normalizeGuid(x))}
145
145
  itemSelected={async (item) => {
@@ -181,9 +181,9 @@ export default function TreeListEditor({
181
181
  </div>
182
182
  </SplitterPanel>
183
183
  <SplitterPanel>
184
- <div className="h-full w-full relative">
184
+ <div className="relative h-full w-full">
185
185
  <div className="absolute inset-0 flex">
186
- <div className="bg-gray-100 flex flex-col justify-center gap-1 p-1">
186
+ <div className="flex flex-col justify-center gap-1 bg-gray-100 p-1">
187
187
  <SimpleIconButton
188
188
  label="Add"
189
189
  icon="pi pi-angle-right"
@@ -197,7 +197,7 @@ export default function TreeListEditor({
197
197
  disabled={readOnly}
198
198
  />
199
199
  </div>
200
- <div className="flex-1 h-full relative">
200
+ <div className="relative h-full flex-1">
201
201
  <ListBox
202
202
  className="absolute inset-0 overflow-auto text-xs"
203
203
  options={values}
@@ -209,7 +209,7 @@ export default function TreeListEditor({
209
209
  onChange={(e) => setSelectedFromList(e.value)}
210
210
  itemTemplate={(option) => (
211
211
  <div
212
- className="select-none hover:bg-gray-50 relative group flex items-center gap-1"
212
+ className="group relative flex items-center gap-1.5 select-none hover:bg-gray-50"
213
213
  onMouseEnter={async () => {
214
214
  setHoveredItemId(option.id);
215
215
  // setExpandIdPath(option.idPath || "");
@@ -219,10 +219,10 @@ export default function TreeListEditor({
219
219
  if (!readOnly) removeFromList([option]);
220
220
  }}
221
221
  >
222
- <img src={option.icon} className="w-4 h-4 mr-2" />
222
+ <img src={option.icon} className="h-4 w-4" />
223
223
  {trimPath(option.path)}{" "}
224
224
  <button
225
- className="opacity-0 group-hover:opacity-100 transition-opacity"
225
+ className="opacity-0 transition-opacity group-hover:opacity-100"
226
226
  onClick={(e) => {
227
227
  e.stopPropagation();
228
228
  setCurrentItemId(option.id);
@@ -245,7 +245,7 @@ export default function TreeListEditor({
245
245
  <div className="p-3">
246
246
  <div className="flex justify-end gap-2">
247
247
  <Button
248
- className="px-3 py-1 bg-blue-500 text-white rounded text-sm"
248
+ className="rounded bg-blue-500 px-3 py-1 text-sm text-white"
249
249
  onClick={() => {
250
250
  editContext.loadItem(currentItemId);
251
251
  overlayPanelRef.current?.hide();
@@ -5,7 +5,8 @@ import { Rect, findComponentRect } from "../utils";
5
5
  import { useThrottledCallback } from "use-debounce";
6
6
  import { Component } from "../pageModel";
7
7
  import { PageViewContext } from "../page-viewer/pageViewContext";
8
-
8
+ import { ArrowUpFromDot } from "lucide-react";
9
+ import { cn } from "../../lib/utils";
9
10
  export function FrameMenu({
10
11
  component,
11
12
  mode,
@@ -18,8 +19,12 @@ export function FrameMenu({
18
19
  const editContext = useEditContext();
19
20
  const resizeObserverRef = useRef<ResizeObserver | null>(null);
20
21
  const mutationObserverRef = useRef<MutationObserver | null>(null);
22
+ const headerRef = useRef<HTMLDivElement>(null);
21
23
 
22
24
  const [componentRect, setComponentRect] = useState<Rect>();
25
+ const [isHeaderWiderThanComponent, setIsHeaderWiderThanComponent] =
26
+ useState(false);
27
+ const [isHovered, setIsHovered] = useState(false);
23
28
 
24
29
  const updatePosition = () => {
25
30
  if (!component || !editContext || !pageViewContext) return;
@@ -32,7 +37,7 @@ export function FrameMenu({
32
37
  setComponentRect(undefined);
33
38
  };
34
39
 
35
- const componentRect = findComponentRect(iframe, component.id)?.rect;
40
+ const componentRect = findComponentRect(iframe, component)?.rect;
36
41
 
37
42
  if (!componentRect) {
38
43
  resetSelection();
@@ -74,7 +79,7 @@ export function FrameMenu({
74
79
  scrollContainer?.addEventListener("scroll", updatePositionThrottled);
75
80
  iframe.contentWindow?.addEventListener("scroll", updatePositionThrottled);
76
81
 
77
- const componentRect = findComponentRect(iframe, component.id);
82
+ const componentRect = findComponentRect(iframe, component);
78
83
 
79
84
  if (!componentRect) {
80
85
  updatePosition();
@@ -98,6 +103,24 @@ export function FrameMenu({
98
103
  };
99
104
  }, [component]);
100
105
 
106
+ // Effect to measure header width and compare with componentRect
107
+ useEffect(() => {
108
+ if (!headerRef.current || !componentRect) return;
109
+
110
+ const headerResizeObserver = new ResizeObserver((entries) => {
111
+ for (const entry of entries) {
112
+ const headerWidth = entry.contentRect.width;
113
+ setIsHeaderWiderThanComponent(headerWidth > componentRect.width);
114
+ }
115
+ });
116
+
117
+ headerResizeObserver.observe(headerRef.current);
118
+
119
+ return () => {
120
+ headerResizeObserver.disconnect();
121
+ };
122
+ }, [componentRect]);
123
+
101
124
  useEffect(() => {
102
125
  setTimeout(() => {
103
126
  updatePosition();
@@ -114,6 +137,7 @@ export function FrameMenu({
114
137
 
115
138
  const commands = editContext.getComponentCommands([component]);
116
139
  const isDraggable =
140
+ component.canBeMoved &&
117
141
  mode === "edit" &&
118
142
  !component.layoutId &&
119
143
  pageViewContext.page?.item.canWriteItem;
@@ -140,9 +164,8 @@ export function FrameMenu({
140
164
  }));
141
165
 
142
166
  const customButtons: EditButton[] = []; // TODO
143
- let buttons = [...customButtons];
144
167
 
145
- buttons = [...buttons, ...commandButtons];
168
+ const buttons = [...customButtons, ...commandButtons];
146
169
 
147
170
  function handleDragStart(event: React.DragEvent<HTMLDivElement>): void {
148
171
  if (!component?.datasourceItem) return;
@@ -169,7 +192,7 @@ export function FrameMenu({
169
192
  editContext!.dragEnd();
170
193
  }
171
194
 
172
- const isShared = component.datasourceItem?.id !== component.id;
195
+ const isShared = component.isShared;
173
196
  const isReadonly = mode === "compare" || mode === "view";
174
197
  const isLayout = component.layoutId;
175
198
 
@@ -179,29 +202,50 @@ export function FrameMenu({
179
202
  ? "shared"
180
203
  : isLayout
181
204
  ? "layout"
182
- : "default";
205
+ : component.canBeMoved
206
+ ? "default"
207
+ : "nonMovable";
183
208
 
184
209
  const colorVariants = {
185
210
  shared: "border-orange-400",
186
- readonly: "border-purple-400",
211
+ readonly: "border-gray-400",
187
212
  layout: "border-purple-400",
188
213
  default: "border-sky-400",
214
+ nonMovable: "border-red-400",
189
215
  };
190
216
 
191
217
  const bgColorVariants = {
192
218
  shared: "bg-orange-400",
193
- readonly: "bg-purple-400",
219
+ readonly: "bg-gray-400",
194
220
  layout: "bg-purple-400",
195
221
  default: "bg-sky-400",
222
+ nonMovable: "bg-red-400",
196
223
  };
197
- if (!componentRect) return null;
224
+
225
+ // Calculate initial estimation for the header width
226
+ const estimatedHeaderWidth = buttons.length * 30 + component.name.length * 8; // Approximate width calculation
227
+ const initialIsHeaderWiderThanComponent =
228
+ estimatedHeaderWidth > (componentRect?.width || 0);
229
+
230
+ useEffect(() => {
231
+ setIsHeaderWiderThanComponent(initialIsHeaderWiderThanComponent);
232
+ }, [initialIsHeaderWiderThanComponent]);
198
233
 
199
234
  const isMultiSelected = editContext.selection.length > 1;
235
+ if (!componentRect) return null;
200
236
 
201
237
  return (
202
238
  <>
203
239
  <div
204
- className={`pointer-events-none absolute inset-0 border-2 ${colorVariants[color]} tour-frame-menu opacity-50 hover:opacity-100`}
240
+ className={cn(
241
+ "pointer-events-none absolute inset-0 rounded-b-sm border-2",
242
+ colorVariants[color],
243
+ "tour-frame-menu opacity-50 hover:opacity-100",
244
+ isHovered && "opacity-100",
245
+ !isMultiSelected && isHeaderWiderThanComponent && "border-t-0",
246
+ !isMultiSelected && !isHeaderWiderThanComponent && "rounded-tl-sm",
247
+ isMultiSelected && "rounded-t-sm",
248
+ )}
205
249
  style={{
206
250
  left: componentRect.x,
207
251
  top: componentRect.y,
@@ -213,9 +257,11 @@ export function FrameMenu({
213
257
  >
214
258
  {!isMultiSelected && (
215
259
  <div
260
+ ref={headerRef}
216
261
  className={
217
262
  `editframe-menu pointer-events-auto absolute z-1000 flex items-center pr-4 text-base text-white ${bgColorVariants[color]} ` +
218
- (componentRect.y - 36 < 0 ? "rounded-bl-lg" : "rounded-t-lg")
263
+ (componentRect.y - 36 < 0 ? "rounded-bl-lg" : "rounded-t-lg") +
264
+ (isHeaderWiderThanComponent ? " rounded-bl-lg" : "")
219
265
  }
220
266
  style={{
221
267
  right: "-2px",
@@ -232,20 +278,38 @@ export function FrameMenu({
232
278
  draggable={isDraggable}
233
279
  onDragStart={handleDragStart}
234
280
  onDragEnd={handleDragEnd}
235
- className={`px-4 ${
281
+ className={`group flex items-center gap-1 pr-2 pl-3 text-sm text-nowrap ${
236
282
  isDraggable ? "cursor-move" : "cursor-default"
237
283
  } `}
238
284
  >
239
285
  {component.name}
286
+ {component.parentPlaceholder?.parentComponent && (
287
+ <span
288
+ className="cursor-pointer opacity-0 transition-opacity group-hover:opacity-100"
289
+ onClick={() => {
290
+ editContext.select([
291
+ component.parentPlaceholder?.parentComponent.id || "",
292
+ ]);
293
+ }}
294
+ >
295
+ <ArrowUpFromDot height={14} width={14} />
296
+ </span>
297
+ )}
240
298
  </div>
241
299
  {mode === "edit" && (
242
- <div className="flex items-center gap-2">
300
+ <div className="flex items-center gap-2 text-sm">
243
301
  {buttons.map((b, i) => (
244
- <div className="cursor-pointer p-1" key={i}>
302
+ <div
303
+ className="cursor-pointer hover:text-gray-200"
304
+ key={i}
305
+ onClick={(ev) => {
306
+ ev.stopPropagation();
307
+ b.onClick(ev);
308
+ }}
309
+ >
245
310
  {typeof b.icon === "string" ? (
246
311
  <i
247
- className={b.icon + " cursor-pointer"}
248
- onClick={b.onClick}
312
+ className={b.icon + " cursor-pointer text-sm"}
249
313
  title={b.label}
250
314
  />
251
315
  ) : (
@@ -28,7 +28,7 @@ export function InlineEditor({
28
28
  x.fieldId === fieldId &&
29
29
  x.item.id === itemId &&
30
30
  x.item.language === language &&
31
- x.item.version === version
31
+ x.item.version === version,
32
32
  )?.value;
33
33
 
34
34
  if (modifiedFieldValue === value) return;
@@ -47,7 +47,7 @@ export function InlineEditor({
47
47
  value: value,
48
48
  });
49
49
  },
50
- context.configuration.debounceFieldEditsInterval
50
+ context.configuration.debounceFieldEditsInterval,
51
51
  );
52
52
 
53
53
  useEffect(() => {
@@ -56,7 +56,7 @@ export function InlineEditor({
56
56
 
57
57
  const editableElements =
58
58
  pageViewContext.editorIframeRef.current?.contentWindow?.document.querySelectorAll(
59
- '[contenteditable="true"]'
59
+ '[contenteditable="true"]',
60
60
  );
61
61
 
62
62
  editableElements?.forEach((element) => {
@@ -90,7 +90,7 @@ export function InlineEditor({
90
90
  fieldName,
91
91
  itemId,
92
92
  language,
93
- version
93
+ version,
94
94
  );
95
95
  });
96
96
 
@@ -116,7 +116,7 @@ export function InlineEditor({
116
116
  modifiedFieldsContext?.modifiedFields.forEach((field) => {
117
117
  const elements =
118
118
  pageViewContext.editorIframeRef.current!.contentWindow?.document.querySelectorAll(
119
- `[data-fieldid="${field.fieldId}"][data-itemid="${field.item.id}"][data-language="${field.item.language}"][data-version="${field.item.version}"`
119
+ `[data-fieldid="${field.fieldId}"][data-itemid="${field.item.id}"][data-language="${field.item.language}"][data-version="${field.item.version}"`,
120
120
  );
121
121
 
122
122
  elements?.forEach(async (element) => {