@alpaca-editor/core 1.0.0 → 1.0.3764-editor-mono

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 (223) hide show
  1. package/dist/client-components/api.js +6 -0
  2. package/dist/client-components/index.js +36 -0
  3. package/dist/components/ActionButton.js +9 -0
  4. package/dist/components/Error.js +28 -0
  5. package/dist/config/config.js +654 -0
  6. package/dist/config/types.js +2 -0
  7. package/dist/editor/ComponentInfo.js +31 -0
  8. package/dist/editor/ConfirmationDialog.js +32 -0
  9. package/dist/editor/ContentTree.js +406 -0
  10. package/dist/editor/ContextMenu.js +117 -0
  11. package/dist/editor/Editor.js +55 -0
  12. package/dist/editor/EditorWarning.js +13 -0
  13. package/dist/editor/EditorWarnings.js +24 -0
  14. package/dist/editor/FieldEditorPopup.js +24 -0
  15. package/dist/editor/FieldHistory.js +40 -0
  16. package/dist/editor/FieldList.js +63 -0
  17. package/dist/editor/FieldListField.js +164 -0
  18. package/dist/editor/FieldListFieldWithFallbacks.js +114 -0
  19. package/dist/editor/FloatingToolbar.js +92 -0
  20. package/dist/editor/ImageEditor.js +55 -0
  21. package/dist/editor/InsertMenu.js +164 -0
  22. package/dist/editor/ItemInfo.js +30 -0
  23. package/dist/editor/LinkEditorDialog.js +89 -0
  24. package/dist/editor/MainLayout.js +46 -0
  25. package/dist/editor/NewEditorClient.js +9 -0
  26. package/dist/editor/PictureCropper.js +332 -0
  27. package/dist/editor/PictureEditor.js +104 -0
  28. package/dist/editor/PictureEditorDialog.js +194 -0
  29. package/dist/editor/ScrollingContentTree.js +30 -0
  30. package/dist/editor/Terminal.js +109 -0
  31. package/dist/editor/Titlebar.js +11 -0
  32. package/dist/editor/ai/AiPopup.js +25 -0
  33. package/dist/editor/ai/AiResponseMessage.js +24 -0
  34. package/dist/editor/ai/AiTerminal.js +241 -0
  35. package/dist/editor/ai/AiToolCall.js +18 -0
  36. package/dist/editor/ai/EditorAiTerminal.js +9 -0
  37. package/dist/editor/ai/editorAiContext.js +14 -0
  38. package/dist/editor/client/DialogContext.js +29 -0
  39. package/dist/editor/client/EditorClient.js +1336 -0
  40. package/dist/editor/client/GenericDialog.js +27 -0
  41. package/dist/editor/client/editContext.js +59 -0
  42. package/dist/editor/client/helpers.js +31 -0
  43. package/dist/editor/client/itemsRepository.js +255 -0
  44. package/dist/editor/client/operations.js +398 -0
  45. package/dist/editor/client/pageModelBuilder.js +129 -0
  46. package/dist/editor/commands/commands.js +2 -0
  47. package/dist/editor/commands/componentCommands.js +277 -0
  48. package/dist/editor/commands/createVersionCommand.js +26 -0
  49. package/dist/editor/commands/deleteVersionCommand.js +55 -0
  50. package/dist/editor/commands/itemCommands.js +134 -0
  51. package/dist/editor/commands/localizeItem/LocalizeItemDialog.js +94 -0
  52. package/dist/editor/commands/undo.js +32 -0
  53. package/dist/editor/component-designer/ComponentDesigner.js +58 -0
  54. package/dist/editor/component-designer/ComponentDesignerAiTerminal.js +9 -0
  55. package/dist/editor/component-designer/ComponentDesignerMenu.js +67 -0
  56. package/dist/editor/component-designer/ComponentEditor.js +59 -0
  57. package/dist/editor/component-designer/ComponentRenderingCodeEditor.js +16 -0
  58. package/dist/editor/component-designer/ComponentRenderingEditor.js +71 -0
  59. package/dist/editor/component-designer/ComponentsDropdown.js +22 -0
  60. package/dist/editor/component-designer/PlaceholdersEditor.js +70 -0
  61. package/dist/editor/component-designer/RenderingsDropdown.js +25 -0
  62. package/dist/editor/component-designer/TemplateEditor.js +144 -0
  63. package/dist/editor/component-designer/aiContext.js +18 -0
  64. package/dist/editor/componentTreeHelper.js +97 -0
  65. package/dist/editor/control-center/ControlCenterMenu.js +59 -0
  66. package/dist/editor/control-center/IndexOverview.js +27 -0
  67. package/dist/editor/control-center/IndexSettings.js +106 -0
  68. package/dist/editor/control-center/Status.js +7 -0
  69. package/dist/editor/editor-warnings/ItemLocked.js +40 -0
  70. package/dist/editor/editor-warnings/NoLanguageWriteAccess.js +16 -0
  71. package/dist/editor/editor-warnings/NoWorkflowWriteAccess.js +16 -0
  72. package/dist/editor/editor-warnings/NoWriteAccess.js +14 -0
  73. package/dist/editor/editor-warnings/ValidationErrors.js +27 -0
  74. package/dist/editor/field-types/AttachmentEditor.js +7 -0
  75. package/dist/editor/field-types/CheckboxEditor.js +32 -0
  76. package/dist/editor/field-types/DropLinkEditor.js +51 -0
  77. package/dist/editor/field-types/DropListEditor.js +58 -0
  78. package/dist/editor/field-types/ImageFieldEditor.js +36 -0
  79. package/dist/editor/field-types/InternalLinkFieldEditor.js +64 -0
  80. package/dist/editor/field-types/LinkFieldEditor.js +58 -0
  81. package/dist/editor/field-types/MultiLineText.js +35 -0
  82. package/dist/editor/field-types/PictureFieldEditor.js +59 -0
  83. package/dist/editor/field-types/RawEditor.js +33 -0
  84. package/dist/editor/field-types/ReactQuill.js +366 -0
  85. package/dist/editor/field-types/RichTextEditor.js +46 -0
  86. package/dist/editor/field-types/RichTextEditorComponent.js +72 -0
  87. package/dist/editor/field-types/SingleLineText.js +92 -0
  88. package/dist/editor/field-types/TreeListEditor.js +137 -0
  89. package/dist/editor/fieldTypes.js +2 -0
  90. package/dist/editor/media-selector/AiImageSearch.js +110 -0
  91. package/dist/editor/media-selector/AiImageSearchPrompt.js +58 -0
  92. package/dist/editor/media-selector/MediaSelector.js +11 -0
  93. package/dist/editor/media-selector/Preview.js +9 -0
  94. package/dist/editor/media-selector/Thumbnails.js +11 -0
  95. package/dist/editor/media-selector/TreeSelector.js +171 -0
  96. package/dist/editor/media-selector/UploadZone.js +80 -0
  97. package/dist/editor/menubar/ActionsMenu.js +33 -0
  98. package/dist/editor/menubar/ActiveUsers.js +13 -0
  99. package/dist/editor/menubar/ApproveAndPublish.js +13 -0
  100. package/dist/editor/menubar/BrowseHistory.js +14 -0
  101. package/dist/editor/menubar/ItemLanguageVersion.js +36 -0
  102. package/dist/editor/menubar/LanguageSelector.js +33 -0
  103. package/dist/editor/menubar/Menu.js +65 -0
  104. package/dist/editor/menubar/NavButtons.js +43 -0
  105. package/dist/editor/menubar/PageSelector.js +50 -0
  106. package/dist/editor/menubar/PageViewerControls.js +37 -0
  107. package/dist/editor/menubar/Separator.js +8 -0
  108. package/dist/editor/menubar/SiteInfo.js +26 -0
  109. package/dist/editor/menubar/User.js +18 -0
  110. package/dist/editor/menubar/VersionSelector.js +49 -0
  111. package/dist/editor/page-editor-chrome/CommentHighlighting.js +214 -0
  112. package/dist/editor/page-editor-chrome/CommentHighlightings.js +17 -0
  113. package/dist/editor/page-editor-chrome/FieldActionIndicator.js +27 -0
  114. package/dist/editor/page-editor-chrome/FieldActionIndicators.js +15 -0
  115. package/dist/editor/page-editor-chrome/FieldEditedIndicator.js +27 -0
  116. package/dist/editor/page-editor-chrome/FieldEditedIndicators.js +15 -0
  117. package/dist/editor/page-editor-chrome/FrameMenu.js +178 -0
  118. package/dist/editor/page-editor-chrome/FrameMenus.js +24 -0
  119. package/dist/editor/page-editor-chrome/InlineEditor.js +101 -0
  120. package/dist/editor/page-editor-chrome/LockedFieldIndicator.js +35 -0
  121. package/dist/editor/page-editor-chrome/NoLayout.js +21 -0
  122. package/dist/editor/page-editor-chrome/PageEditorChrome.js +65 -0
  123. package/dist/editor/page-editor-chrome/PictureEditorOverlay.js +109 -0
  124. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +82 -0
  125. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js +147 -0
  126. package/dist/editor/page-viewer/DeviceToolbar.js +21 -0
  127. package/dist/editor/page-viewer/EditorForm.js +130 -0
  128. package/dist/editor/page-viewer/MiniMap.js +257 -0
  129. package/dist/editor/page-viewer/PageViewer.js +64 -0
  130. package/dist/editor/page-viewer/PageViewerFrame.js +696 -0
  131. package/dist/editor/page-viewer/pageViewContext.js +117 -0
  132. package/dist/editor/pageModel.js +2 -0
  133. package/dist/editor/picture-shared.js +28 -0
  134. package/dist/editor/reviews/Comment.js +112 -0
  135. package/dist/editor/reviews/Comments.js +24 -0
  136. package/dist/editor/reviews/PreviewInfo.js +13 -0
  137. package/dist/editor/reviews/Reviews.js +165 -0
  138. package/dist/editor/reviews/reviewCommands.js +44 -0
  139. package/dist/editor/reviews/useReviews.js +48 -0
  140. package/dist/editor/services/aiService.js +99 -0
  141. package/dist/editor/services/componentDesignerService.js +79 -0
  142. package/dist/editor/services/contentService.js +104 -0
  143. package/dist/editor/services/editService.js +322 -0
  144. package/dist/editor/services/indexService.js +25 -0
  145. package/dist/editor/services/reviewsService.js +43 -0
  146. package/dist/editor/services/serviceHelper.js +67 -0
  147. package/dist/editor/services/systemService.js +7 -0
  148. package/dist/editor/services/translationService.js +15 -0
  149. package/dist/editor/services-server/api.js +119 -0
  150. package/dist/editor/services-server/graphQL.js +56 -0
  151. package/dist/editor/sidebar/ComponentPalette.js +55 -0
  152. package/dist/editor/sidebar/ComponentTree.js +362 -0
  153. package/dist/editor/sidebar/Debug.js +60 -0
  154. package/dist/editor/sidebar/DictionaryEditor.js +160 -0
  155. package/dist/editor/sidebar/EditHistory.js +74 -0
  156. package/dist/editor/sidebar/GraphQL.js +115 -0
  157. package/dist/editor/sidebar/Insert.js +24 -0
  158. package/dist/editor/sidebar/MainContentTree.js +52 -0
  159. package/dist/editor/sidebar/Performance.js +34 -0
  160. package/dist/editor/sidebar/Sessions.js +31 -0
  161. package/dist/editor/sidebar/Sidebar.js +15 -0
  162. package/dist/editor/sidebar/SidebarView.js +76 -0
  163. package/dist/editor/sidebar/Translations.js +160 -0
  164. package/dist/editor/sidebar/Validation.js +52 -0
  165. package/dist/editor/sidebar/ViewSelector.js +15 -0
  166. package/dist/editor/sidebar/Workbox.js +80 -0
  167. package/dist/editor/ui/CenteredMessage.js +7 -0
  168. package/dist/editor/ui/CopyToClipboardButton.js +17 -0
  169. package/dist/editor/ui/DialogButtons.js +7 -0
  170. package/dist/editor/ui/Icons.js +75 -0
  171. package/dist/editor/ui/ItemNameDialog.js +45 -0
  172. package/dist/editor/ui/ItemNameDialogNew.js +61 -0
  173. package/dist/editor/ui/ItemSearch.js +93 -0
  174. package/dist/editor/ui/PerfectTree.js +223 -0
  175. package/dist/editor/ui/Section.js +12 -0
  176. package/dist/editor/ui/SimpleIconButton.js +11 -0
  177. package/dist/editor/ui/SimpleMenu.js +9 -0
  178. package/dist/editor/ui/SimpleTable.js +11 -0
  179. package/dist/editor/ui/SimpleTabs.js +21 -0
  180. package/dist/editor/ui/SimpleToolbar.js +7 -0
  181. package/dist/editor/ui/Spinner.js +7 -0
  182. package/dist/editor/ui/Splitter.js +187 -0
  183. package/dist/editor/ui/StackedPanels.js +69 -0
  184. package/dist/editor/ui/Toolbar.js +7 -0
  185. package/dist/editor/utils/id-helper.js +7 -0
  186. package/dist/editor/utils/insertOptions.js +45 -0
  187. package/dist/editor/utils/itemutils.js +25 -0
  188. package/dist/editor/utils/useMemoDebug.js +20 -0
  189. package/dist/editor/utils.js +328 -0
  190. package/dist/editor/views/CompareView.js +147 -0
  191. package/dist/editor/views/EditView.js +17 -0
  192. package/dist/editor/views/ItemEditor.js +24 -0
  193. package/dist/editor/views/SingleEditView.js +25 -0
  194. package/dist/index.js +22 -0
  195. package/dist/page-wizard/PageWizard.js +62 -0
  196. package/dist/page-wizard/SelectWizard.js +43 -0
  197. package/dist/page-wizard/WizardSteps.js +71 -0
  198. package/dist/page-wizard/service.js +26 -0
  199. package/dist/page-wizard/startPageWizardCommand.js +23 -0
  200. package/dist/page-wizard/steps/BuildPageStep.js +138 -0
  201. package/dist/page-wizard/steps/CollectStep.js +124 -0
  202. package/dist/page-wizard/steps/ComponentTypesSelector.js +211 -0
  203. package/dist/page-wizard/steps/Components.js +94 -0
  204. package/dist/page-wizard/steps/CreatePage.js +142 -0
  205. package/dist/page-wizard/steps/CreatePageAndLayoutStep.js +230 -0
  206. package/dist/page-wizard/steps/EditButton.js +7 -0
  207. package/dist/page-wizard/steps/FieldEditor.js +30 -0
  208. package/dist/page-wizard/steps/Generate.js +11 -0
  209. package/dist/page-wizard/steps/ImagesStep.js +159 -0
  210. package/dist/page-wizard/steps/LayoutStep.js +120 -0
  211. package/dist/page-wizard/steps/SelectStep.js +150 -0
  212. package/dist/page-wizard/steps/schema.js +140 -0
  213. package/dist/page-wizard/steps/usePageCreator.js +194 -0
  214. package/dist/splash-screen/NewPage.js +131 -0
  215. package/dist/splash-screen/SectionHeadline.js +9 -0
  216. package/dist/splash-screen/SplashScreen.js +81 -0
  217. package/dist/tour/Tour.js +321 -0
  218. package/dist/tour/default-tour.js +231 -0
  219. package/dist/tour/preview-tour.js +93 -0
  220. package/dist/tsconfig.build.tsbuildinfo +1 -0
  221. package/dist/types.js +2 -0
  222. package/package.json +1 -1
  223. package/src/editor/sidebar/ComponentTree.tsx +512 -512
@@ -1,512 +1,512 @@
1
- "use client";
2
-
3
- import { useEditContext, useEditContextRef } from "../client/editContext";
4
-
5
- import { useEffect, useRef, useState } from "react";
6
-
7
- import { confirmDialog } from "primereact/confirmdialog";
8
- import { getComponentCommands } from "../commands/componentCommands";
9
- import { MenuItem, MenuItemCommandEvent } from "primereact/menuitem";
10
-
11
- import { Component, Placeholder } from "../pageModel";
12
- import { PerfectTree, TreeNode } from "../ui/PerfectTree";
13
-
14
- import { isValidPlaceholder } from "../componentTreeHelper";
15
-
16
- type CustomTreeNode = TreeNode<Component | Placeholder> & {
17
- parent?: CustomTreeNode;
18
- componentId?: string;
19
- className?: string;
20
- type: string;
21
- tags?: {
22
- value: string;
23
- icon: string;
24
- severity: "success" | "info" | "warning" | "danger" | null | undefined;
25
- onClick?: () => void;
26
- color?: string;
27
- tooltip?: string;
28
- id: string;
29
- }[];
30
- };
31
-
32
- export function ComponentTree({}) {
33
- const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
34
- const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
35
-
36
- const [rootNodes, setRootNodes] = useState<TreeNode[]>([]);
37
- const [nodeDictionary, setNodeDictionary] = useState<{
38
- [key: string]: CustomTreeNode;
39
- }>({});
40
-
41
- let editContextRef = useEditContextRef();
42
- const editContext = useEditContext();
43
-
44
- const treeRef = useRef<HTMLDivElement>(null);
45
-
46
- useEffect(() => {
47
- if (treeRef.current) {
48
- const selectedNodeKey = Object.keys(selectedKeys || {})[0];
49
-
50
- const selectedNodeElement = treeRef.current.querySelector(
51
- `[data-id="${selectedNodeKey}"]`,
52
- );
53
-
54
- if (selectedNodeElement) {
55
- selectedNodeElement.scrollIntoView({
56
- behavior: "smooth",
57
- block: "nearest",
58
- });
59
- }
60
- }
61
- }, [selectedKeys]);
62
-
63
- useEffect(() => {
64
- if (editContext?.selection) {
65
- setSelectedKeys(editContext.selection);
66
- }
67
- }, [editContext?.selection]);
68
-
69
- const selection = editContext?.selection || [];
70
- const page = editContext?.page;
71
-
72
- useEffect(() => {
73
- const newExpandedKeys = [...expandedKeys];
74
-
75
- for (const id of selection) {
76
- if (nodeDictionary[id]?.parent) {
77
- expandParents(nodeDictionary[id].parent, newExpandedKeys);
78
- }
79
- }
80
-
81
- if (
82
- newExpandedKeys.length !== expandedKeys.length ||
83
- !newExpandedKeys.every((key, i) => key === expandedKeys[i])
84
- ) {
85
- setExpandedKeys(newExpandedKeys);
86
- }
87
- }, [selection, nodeDictionary]);
88
-
89
- function mapPlaceholderNode(
90
- p: Placeholder,
91
- parent: CustomTreeNode,
92
- ): CustomTreeNode {
93
- const node: CustomTreeNode = {
94
- key: p.key,
95
- componentId: p.key,
96
- label: p.name,
97
- icon: "pi pi-folder",
98
- data: p,
99
- parent: parent,
100
- type: "placeholder",
101
- };
102
- node.children = p.components.map((c) => mapComponentNode(c, node));
103
- return node;
104
- }
105
-
106
- function mapPlaceholders(
107
- c: Component,
108
- parent: CustomTreeNode,
109
- ): CustomTreeNode[] {
110
- if (!c?.placeholders) return [];
111
- if (c.placeholders.length > 1)
112
- return c.placeholders.map((x) => mapPlaceholderNode(x, parent));
113
- else if (c.placeholders.length > 0) {
114
- return c.placeholders[0]!.components.map((c) =>
115
- mapComponentNode(c, parent),
116
- );
117
- }
118
- return [];
119
- }
120
-
121
- function mapComponentNode(
122
- c: Component,
123
- parent: CustomTreeNode,
124
- ): CustomTreeNode {
125
- const node: CustomTreeNode = {
126
- key: c.id,
127
- componentId: c.id,
128
- label: c.name,
129
- icon: "pi pi-stop",
130
- data: c,
131
- parent: parent,
132
- tags: [],
133
- className: c.isRemovedFromMasterLanguage ? "text-gray-400" : "",
134
- type: "component",
135
- };
136
-
137
- if (c.isInheritedFromMasterLanguage) {
138
- node.tags!.push({
139
- id: "inherited",
140
- severity: "info",
141
- value:
142
- "Inherited " + (c.isRemovedFromMasterLanguage ? " / removed" : ""),
143
- tooltip: c.isRemovedFromMasterLanguage
144
- ? "Component was removed from master language"
145
- : "Component is inherited from master language",
146
- icon: "pi pi-caret-right",
147
- });
148
- }
149
-
150
- if (c.layoutId) {
151
- node.tags!.push({
152
- id: "layout",
153
- severity: "warning",
154
- value: "Layout",
155
- icon: "",
156
- color: "rgb(192 132 252)",
157
- tooltip: "Component is inherited from layout. Click to edit layout.",
158
- onClick: async () => {
159
- if (!editContext || !c.layoutId) return;
160
- const hostItem = await editContext.itemsRepository.getItem({
161
- id: c.layoutId,
162
- language: page!.item.language,
163
- version: 0,
164
- });
165
- if (!hostItem) return;
166
- confirmDialog({
167
- header: "Edit layout",
168
- message: "Edit layout " + hostItem.path + "?",
169
- accept: () =>
170
- editContext.loadItem({
171
- id: c.layoutId!,
172
- language: page!.item.language,
173
- version: 0,
174
- }),
175
- });
176
- },
177
- });
178
- }
179
-
180
- if (c.datasourceItem && c.datasourceItem?.id !== c.id) {
181
- node.tags!.push({
182
- id: "linked",
183
- severity: "warning",
184
- value: "Shared",
185
- icon: "",
186
- tooltip: "Component is linked to " + c.datasourceItem?.id,
187
- color: "rgb(251, 146, 60)",
188
- });
189
- }
190
-
191
- node.children = mapPlaceholders(c, node);
192
-
193
- if (c.items.length > 0 && c.items[0]!.id !== c.datasourceItem?.id) {
194
- const itemsNode: CustomTreeNode = {
195
- key: "items" + c.id,
196
- label: "items",
197
- icon: "pi pi-circle",
198
- parent: node,
199
- type: "items",
200
- };
201
-
202
- itemsNode.children = c.items.map((x) => {
203
- const itemNode: CustomTreeNode = {
204
- key: x.id,
205
- label: x.name,
206
- icon: "pi pi-file",
207
- parent: itemsNode,
208
- type: "item",
209
- };
210
- return itemNode;
211
- });
212
-
213
- node.children.push(itemsNode);
214
- }
215
-
216
- return node;
217
- }
218
-
219
- useEffect(() => {
220
- if (!page) return;
221
-
222
- const treeNodes = mapPlaceholders(page.rootComponent, {
223
- key: "root",
224
- label: "Root",
225
- icon: "pi pi-home",
226
- children: [],
227
- type: "placeholder",
228
- });
229
- const dict: { [key: string]: CustomTreeNode } = {};
230
- createMap(treeNodes, dict);
231
-
232
- const otherNode: CustomTreeNode = {
233
- key: "other",
234
- label: "Other",
235
- icon: "pi pi-file",
236
- children: [],
237
- type: "other",
238
- };
239
-
240
- if (otherNode.children?.length) treeNodes.push(otherNode);
241
-
242
- const pageNode: CustomTreeNode = {
243
- key: "page",
244
- label: "Page",
245
- icon: "pi pi-home",
246
- parent: undefined,
247
- componentId: page.item.id,
248
- type: "page",
249
- };
250
-
251
- setRootNodes([pageNode, ...treeNodes]);
252
- setNodeDictionary(dict);
253
- }, [page]);
254
-
255
- const handleTreeSelection = (key: string, event: React.MouseEvent) => {
256
- let newKeys = [key];
257
- if (event.ctrlKey) {
258
- if (selectedKeys.includes(key)) {
259
- newKeys = selectedKeys.filter((x) => x !== key);
260
- } else {
261
- newKeys = [...selectedKeys, key];
262
- }
263
- }
264
-
265
- const selectedComponentIds = newKeys
266
- .filter((key) => nodeDictionary[key])
267
- .map((key) => nodeDictionary[key]!.componentId!);
268
-
269
- editContextRef.current?.select(selectedComponentIds);
270
-
271
- if (selectedComponentIds.length === 1) {
272
- editContextRef.current?.setScrollIntoView(selectedComponentIds[0]);
273
- }
274
- };
275
-
276
- // Handle tree context menu
277
- const handleTreeContextMenu = (item: any, event: React.MouseEvent) => {
278
- const componentId = nodeDictionary[item.id]?.componentId;
279
-
280
- let selectedComponentIds = editContext?.selection || [];
281
-
282
- if (componentId) {
283
- if (selectedComponentIds.indexOf(componentId) < 0) {
284
- editContextRef.current?.select([componentId]);
285
- selectedComponentIds = [componentId];
286
- }
287
- }
288
-
289
- function isDefinedEntity(
290
- entity: Component | Placeholder | undefined,
291
- ): entity is Component | Placeholder {
292
- return entity !== undefined;
293
- }
294
-
295
- let selectedEntities = selectedComponentIds
296
- .filter((key) => nodeDictionary[key])
297
- .map((key) => nodeDictionary[key]!.data)
298
- .filter(isDefinedEntity);
299
-
300
- if (selectedEntities.length === 0 && item.data) {
301
- selectedEntities = [item.data];
302
- }
303
-
304
- let commands = getComponentCommands(
305
- selectedEntities,
306
- editContextRef.current!,
307
- );
308
-
309
- const menu: MenuItem[] = commands.map((x) => ({
310
- label: x.label,
311
- icon: x.icon,
312
- disabled: editContextRef.current?.isCommandDisabled({
313
- command: x,
314
- }),
315
- command: (ev: MenuItemCommandEvent) => {
316
- editContextRef.current?.executeCommand({
317
- command: x,
318
- data: { components: selectedEntities as Component[] },
319
- event: ev.originalEvent,
320
- });
321
- },
322
- }));
323
-
324
- editContextRef.current?.showContextMenu(event, menu);
325
- };
326
-
327
- // Convert expanded keys for PerfectTree
328
- const expandedItems = expandedKeys;
329
-
330
- // Convert selected keys for PerfectTree
331
- const selectedItems = selectedKeys;
332
-
333
- // Handle expanding nodes
334
- const handleToggle = (key: string) => {
335
- if (expandedKeys.includes(key)) {
336
- handleCollapseItem(key);
337
- } else {
338
- const newExpandedKeys = [...expandedKeys];
339
- newExpandedKeys.push(key);
340
- setExpandedKeys(newExpandedKeys);
341
- }
342
- };
343
-
344
- // Handle collapsing nodes
345
- const handleCollapseItem = (key: string) => {
346
- // First, get the node we're collapsing
347
- const nodeToCollapse = nodeDictionary[key];
348
-
349
- // Create a new array for expanded keys
350
- const newExpandedKeys = [...expandedKeys].filter((x) => x !== key);
351
-
352
- setExpandedKeys(newExpandedKeys);
353
- };
354
-
355
- const getPlaceholder = (node: CustomTreeNode | null): Placeholder | null => {
356
- if (!node) return null;
357
- if (node.type === "placeholder") return node.data as Placeholder;
358
- if (node.type === "component" && (node.data as Component).placeholders) {
359
- if ((node.data as Component).placeholders.length === 1)
360
- return (node.data as Component).placeholders[0]!;
361
- }
362
- return null;
363
- };
364
-
365
- // Handle drag over node
366
- const handleDragOverZone = (
367
- dragOverNode: CustomTreeNode | null,
368
- index: number,
369
- event: React.DragEvent,
370
- ): boolean => {
371
- if (!editContext?.dragObject) return false;
372
- const placeholder = getPlaceholder(dragOverNode);
373
- if (!placeholder) return false;
374
- var isValid = isValidPlaceholder(placeholder, editContext.dragObject);
375
- return isValid;
376
- };
377
-
378
- // Handle drop on node
379
- const handleDropZone = (
380
- droppedOnNode: CustomTreeNode | null,
381
- index: number,
382
- event: React.DragEvent,
383
- ): void => {
384
- const placeholder = getPlaceholder(droppedOnNode);
385
- if (!placeholder) return;
386
-
387
- editContext!.droppedInPlaceholder(placeholder.key, index);
388
- };
389
-
390
- if (!page)
391
- return (
392
- <div className="flex h-full items-center justify-center text-sm text-gray-500"></div>
393
- );
394
-
395
- // Use the PerfectTree component
396
- return (
397
- <div className="p-2" ref={treeRef}>
398
- <PerfectTree
399
- nodes={rootNodes}
400
- isDragging={!!editContext?.dragObject}
401
- selectedKeys={selectedItems}
402
- expandedKeys={expandedItems}
403
- onToggleExpand={handleToggle}
404
- onSelect={handleTreeSelection}
405
- enableDragAndDrop={true}
406
- onDragOverZone={(parent, index, event) =>
407
- handleDragOverZone(parent as CustomTreeNode | null, index, event)
408
- }
409
- onStartDrag={(data) => {
410
- const component = data.node.data as Component;
411
-
412
- // Initialize drag data to make sure dataTransfer is set
413
- data.event.dataTransfer.setData("text/plain", data.node.key);
414
-
415
- // Only create drag object for components with datasourceItem
416
- if (!component?.datasourceItem) return;
417
-
418
- editContext!.dragStart({
419
- type: "component",
420
- typeId: component.typeId,
421
- templateId: component.datasourceItem?.templateId,
422
- name: component.name,
423
- component: {
424
- id: component.id,
425
- language: editContext!.page!.item.language,
426
- version: editContext!.page!.item.version,
427
- },
428
- });
429
- }}
430
- onDragEnd={(event) => {
431
- editContext!.dragEnd();
432
- }}
433
- onDrop={(parent, index, event) =>
434
- handleDropZone(parent as CustomTreeNode | null, index, event)
435
- }
436
- renderNode={(node) => renderNode(node as CustomTreeNode)}
437
- />
438
- </div>
439
- );
440
- }
441
-
442
- function renderNode(node: CustomTreeNode) {
443
- return (
444
- <div>
445
- <div className="font-geist-sans flex items-center gap-2 p-0.5 text-[12px] text-gray-600">
446
- <i className={node.icon}></i>
447
- <span className={node.className}>{node.label}</span>
448
- {node.tags?.map((x: any) => (
449
- <div key={`tag-${node.key}-${x.id}`}>
450
- <span
451
- id={`id-${node.key}-${x.id}`}
452
- className="ml-2 rounded px-1 py-0.5 text-xs"
453
- style={{
454
- backgroundColor: x.color,
455
- marginLeft: "8px",
456
- fontSize: "10px",
457
- padding: "1px 4px 2px 2px",
458
- }}
459
- title={x.tooltip}
460
- onClick={
461
- x.onClick
462
- ? (e) => {
463
- e.stopPropagation();
464
- x.onClick();
465
- }
466
- : undefined
467
- }
468
- >
469
- {x.icon && <i className={x.icon}></i>}
470
- {x.value}
471
- </span>
472
- </div>
473
- ))}
474
- </div>
475
- </div>
476
- );
477
- }
478
-
479
- function createMap(
480
- treeNodes: CustomTreeNode[],
481
- nodeDictionary: { [key: string]: CustomTreeNode },
482
- ) {
483
- treeNodes.forEach((n) => {
484
- if (n.key) nodeDictionary[n.key] = n;
485
- if (n.children) createMap(n.children as CustomTreeNode[], nodeDictionary);
486
- });
487
- }
488
-
489
- function expandParents(node: CustomTreeNode, expandedKeysList: string[]) {
490
- if (!node) return;
491
-
492
- if (node.key && !expandedKeysList.includes(node.key)) {
493
- expandedKeysList.push(node.key);
494
- }
495
-
496
- if (node.parent) {
497
- expandParents(node.parent, expandedKeysList);
498
- }
499
- }
500
-
501
- function hasSameProps(obj1: any, obj2: any) {
502
- var obj1Props = Object.keys(obj1),
503
- obj2Props = Object.keys(obj2);
504
-
505
- if (obj1Props.length == obj2Props.length) {
506
- return obj1Props.every(function (prop) {
507
- return obj2Props.indexOf(prop) >= 0;
508
- });
509
- }
510
-
511
- return false;
512
- }
1
+ "use client";
2
+
3
+ import { useEditContext, useEditContextRef } from "../client/editContext";
4
+
5
+ import { useEffect, useRef, useState } from "react";
6
+
7
+ import { confirmDialog } from "primereact/confirmdialog";
8
+ import { getComponentCommands } from "../commands/componentCommands";
9
+ import { MenuItem, MenuItemCommandEvent } from "primereact/menuitem";
10
+
11
+ import { Component, Placeholder } from "../pageModel";
12
+ import { PerfectTree, TreeNode } from "../ui/PerfectTree";
13
+
14
+ import { isValidPlaceholder } from "../componentTreeHelper";
15
+
16
+ type CustomTreeNode = TreeNode<Component | Placeholder> & {
17
+ parent?: CustomTreeNode;
18
+ componentId?: string;
19
+ className?: string;
20
+ type: string;
21
+ tags?: {
22
+ value: string;
23
+ icon: string;
24
+ severity: "success" | "info" | "warning" | "danger" | null | undefined;
25
+ onClick?: () => void;
26
+ color?: string;
27
+ tooltip?: string;
28
+ id: string;
29
+ }[];
30
+ };
31
+
32
+ export function ComponentTree({}) {
33
+ const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
34
+ const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
35
+
36
+ const [rootNodes, setRootNodes] = useState<TreeNode[]>([]);
37
+ const [nodeDictionary, setNodeDictionary] = useState<{
38
+ [key: string]: CustomTreeNode;
39
+ }>({});
40
+
41
+ let editContextRef = useEditContextRef();
42
+ const editContext = useEditContext();
43
+
44
+ const treeRef = useRef<HTMLDivElement>(null);
45
+
46
+ useEffect(() => {
47
+ if (treeRef.current) {
48
+ const selectedNodeKey = Object.keys(selectedKeys || {})[0];
49
+
50
+ const selectedNodeElement = treeRef.current.querySelector(
51
+ `[data-id="${selectedNodeKey}"]`,
52
+ );
53
+
54
+ if (selectedNodeElement) {
55
+ selectedNodeElement.scrollIntoView({
56
+ behavior: "smooth",
57
+ block: "nearest",
58
+ });
59
+ }
60
+ }
61
+ }, [selectedKeys]);
62
+
63
+ useEffect(() => {
64
+ if (editContext?.selection) {
65
+ setSelectedKeys(editContext.selection);
66
+ }
67
+ }, [editContext?.selection]);
68
+
69
+ const selection = editContext?.selection || [];
70
+ const page = editContext?.page;
71
+
72
+ useEffect(() => {
73
+ const newExpandedKeys = [...expandedKeys];
74
+
75
+ for (const id of selection) {
76
+ if (nodeDictionary[id]?.parent) {
77
+ expandParents(nodeDictionary[id].parent, newExpandedKeys);
78
+ }
79
+ }
80
+
81
+ if (
82
+ newExpandedKeys.length !== expandedKeys.length ||
83
+ !newExpandedKeys.every((key, i) => key === expandedKeys[i])
84
+ ) {
85
+ setExpandedKeys(newExpandedKeys);
86
+ }
87
+ }, [selection, nodeDictionary]);
88
+
89
+ function mapPlaceholderNode(
90
+ p: Placeholder,
91
+ parent: CustomTreeNode,
92
+ ): CustomTreeNode {
93
+ const node: CustomTreeNode = {
94
+ key: p.key,
95
+ componentId: p.key,
96
+ label: p.name,
97
+ icon: "pi pi-folder",
98
+ data: p,
99
+ parent: parent,
100
+ type: "placeholder",
101
+ };
102
+ node.children = p.components.map((c) => mapComponentNode(c, node));
103
+ return node;
104
+ }
105
+
106
+ function mapPlaceholders(
107
+ c: Component,
108
+ parent: CustomTreeNode,
109
+ ): CustomTreeNode[] {
110
+ if (!c?.placeholders) return [];
111
+ if (c.placeholders.length > 1)
112
+ return c.placeholders.map((x) => mapPlaceholderNode(x, parent));
113
+ else if (c.placeholders.length > 0) {
114
+ return c.placeholders[0]!.components.map((c) =>
115
+ mapComponentNode(c, parent),
116
+ );
117
+ }
118
+ return [];
119
+ }
120
+
121
+ function mapComponentNode(
122
+ c: Component,
123
+ parent: CustomTreeNode,
124
+ ): CustomTreeNode {
125
+ const node: CustomTreeNode = {
126
+ key: c.id,
127
+ componentId: c.id,
128
+ label: c.name,
129
+ icon: "pi pi-stop",
130
+ data: c,
131
+ parent: parent,
132
+ tags: [],
133
+ className: c.isRemovedFromMasterLanguage ? "text-gray-400" : "",
134
+ type: "component",
135
+ };
136
+
137
+ if (c.isInheritedFromMasterLanguage) {
138
+ node.tags!.push({
139
+ id: "inherited",
140
+ severity: "info",
141
+ value:
142
+ "Inherited " + (c.isRemovedFromMasterLanguage ? " / removed" : ""),
143
+ tooltip: c.isRemovedFromMasterLanguage
144
+ ? "Component was removed from master language"
145
+ : "Component is inherited from master language",
146
+ icon: "pi pi-caret-right",
147
+ });
148
+ }
149
+
150
+ if (c.layoutId) {
151
+ node.tags!.push({
152
+ id: "layout",
153
+ severity: "warning",
154
+ value: "Layout",
155
+ icon: "",
156
+ color: "rgb(192 132 252)",
157
+ tooltip: "Component is inherited from layout. Click to edit layout.",
158
+ onClick: async () => {
159
+ if (!editContext || !c.layoutId) return;
160
+ const hostItem = await editContext.itemsRepository.getItem({
161
+ id: c.layoutId,
162
+ language: page!.item.language,
163
+ version: 0,
164
+ });
165
+ if (!hostItem) return;
166
+ confirmDialog({
167
+ header: "Edit layout",
168
+ message: "Edit layout " + hostItem.path + "?",
169
+ accept: () =>
170
+ editContext.loadItem({
171
+ id: c.layoutId!,
172
+ language: page!.item.language,
173
+ version: 0,
174
+ }),
175
+ });
176
+ },
177
+ });
178
+ }
179
+
180
+ if (c.datasourceItem && c.datasourceItem?.id !== c.id) {
181
+ node.tags!.push({
182
+ id: "linked",
183
+ severity: "warning",
184
+ value: "Shared",
185
+ icon: "",
186
+ tooltip: "Component is linked to " + c.datasourceItem?.id,
187
+ color: "rgb(251, 146, 60)",
188
+ });
189
+ }
190
+
191
+ node.children = mapPlaceholders(c, node);
192
+
193
+ if (c.items.length > 0 && c.items[0]!.id !== c.datasourceItem?.id) {
194
+ const itemsNode: CustomTreeNode = {
195
+ key: "items" + c.id,
196
+ label: "items",
197
+ icon: "pi pi-circle",
198
+ parent: node,
199
+ type: "items",
200
+ };
201
+
202
+ itemsNode.children = c.items.map((x) => {
203
+ const itemNode: CustomTreeNode = {
204
+ key: x.id,
205
+ label: x.name,
206
+ icon: "pi pi-file",
207
+ parent: itemsNode,
208
+ type: "item",
209
+ };
210
+ return itemNode;
211
+ });
212
+
213
+ node.children.push(itemsNode);
214
+ }
215
+
216
+ return node;
217
+ }
218
+
219
+ useEffect(() => {
220
+ if (!page) return;
221
+
222
+ const treeNodes = mapPlaceholders(page.rootComponent, {
223
+ key: "root",
224
+ label: "Root",
225
+ icon: "pi pi-home",
226
+ children: [],
227
+ type: "placeholder",
228
+ });
229
+ const dict: { [key: string]: CustomTreeNode } = {};
230
+ createMap(treeNodes, dict);
231
+
232
+ const otherNode: CustomTreeNode = {
233
+ key: "other",
234
+ label: "Other",
235
+ icon: "pi pi-file",
236
+ children: [],
237
+ type: "other",
238
+ };
239
+
240
+ if (otherNode.children?.length) treeNodes.push(otherNode);
241
+
242
+ const pageNode: CustomTreeNode = {
243
+ key: "page",
244
+ label: "Page",
245
+ icon: "pi pi-home",
246
+ parent: undefined,
247
+ componentId: page.item.id,
248
+ type: "page",
249
+ };
250
+
251
+ setRootNodes([pageNode, ...treeNodes]);
252
+ setNodeDictionary(dict);
253
+ }, [page]);
254
+
255
+ const handleTreeSelection = (key: string, event: React.MouseEvent) => {
256
+ let newKeys = [key];
257
+ if (event.ctrlKey) {
258
+ if (selectedKeys.includes(key)) {
259
+ newKeys = selectedKeys.filter((x) => x !== key);
260
+ } else {
261
+ newKeys = [...selectedKeys, key];
262
+ }
263
+ }
264
+
265
+ const selectedComponentIds = newKeys
266
+ .filter((key) => nodeDictionary[key])
267
+ .map((key) => nodeDictionary[key]!.componentId!);
268
+
269
+ editContextRef.current?.select(selectedComponentIds);
270
+
271
+ if (selectedComponentIds.length === 1) {
272
+ editContextRef.current?.setScrollIntoView(selectedComponentIds[0]);
273
+ }
274
+ };
275
+
276
+ // Handle tree context menu
277
+ const handleTreeContextMenu = (item: any, event: React.MouseEvent) => {
278
+ const componentId = nodeDictionary[item.id]?.componentId;
279
+
280
+ let selectedComponentIds = editContext?.selection || [];
281
+
282
+ if (componentId) {
283
+ if (selectedComponentIds.indexOf(componentId) < 0) {
284
+ editContextRef.current?.select([componentId]);
285
+ selectedComponentIds = [componentId];
286
+ }
287
+ }
288
+
289
+ function isDefinedEntity(
290
+ entity: Component | Placeholder | undefined,
291
+ ): entity is Component | Placeholder {
292
+ return entity !== undefined;
293
+ }
294
+
295
+ let selectedEntities = selectedComponentIds
296
+ .filter((key) => nodeDictionary[key])
297
+ .map((key) => nodeDictionary[key]!.data)
298
+ .filter(isDefinedEntity);
299
+
300
+ if (selectedEntities.length === 0 && item.data) {
301
+ selectedEntities = [item.data];
302
+ }
303
+
304
+ let commands = getComponentCommands(
305
+ selectedEntities,
306
+ editContextRef.current!,
307
+ );
308
+
309
+ const menu: MenuItem[] = commands.map((x) => ({
310
+ label: x.label,
311
+ icon: x.icon,
312
+ disabled: editContextRef.current?.isCommandDisabled({
313
+ command: x,
314
+ }),
315
+ command: (ev: MenuItemCommandEvent) => {
316
+ editContextRef.current?.executeCommand({
317
+ command: x,
318
+ data: { components: selectedEntities as Component[] },
319
+ event: ev.originalEvent,
320
+ });
321
+ },
322
+ }));
323
+
324
+ editContextRef.current?.showContextMenu(event, menu);
325
+ };
326
+
327
+ // Convert expanded keys for PerfectTree
328
+ const expandedItems = expandedKeys;
329
+
330
+ // Convert selected keys for PerfectTree
331
+ const selectedItems = selectedKeys;
332
+
333
+ // Handle expanding nodes
334
+ const handleToggle = (key: string) => {
335
+ if (expandedKeys.includes(key)) {
336
+ handleCollapseItem(key);
337
+ } else {
338
+ const newExpandedKeys = [...expandedKeys];
339
+ newExpandedKeys.push(key);
340
+ setExpandedKeys(newExpandedKeys);
341
+ }
342
+ };
343
+
344
+ // Handle collapsing nodes
345
+ const handleCollapseItem = (key: string) => {
346
+ // First, get the node we're collapsing
347
+ const nodeToCollapse = nodeDictionary[key];
348
+
349
+ // Create a new array for expanded keys
350
+ const newExpandedKeys = [...expandedKeys].filter((x) => x !== key);
351
+
352
+ setExpandedKeys(newExpandedKeys);
353
+ };
354
+
355
+ const getPlaceholder = (node: CustomTreeNode | null): Placeholder | null => {
356
+ if (!node) return null;
357
+ if (node.type === "placeholder") return node.data as Placeholder;
358
+ if (node.type === "component" && (node.data as Component).placeholders) {
359
+ if ((node.data as Component).placeholders.length === 1)
360
+ return (node.data as Component).placeholders[0]!;
361
+ }
362
+ return null;
363
+ };
364
+
365
+ // Handle drag over node
366
+ const handleDragOverZone = (
367
+ dragOverNode: CustomTreeNode | null,
368
+ index: number,
369
+ event: React.DragEvent,
370
+ ): boolean => {
371
+ if (!editContext?.dragObject) return false;
372
+ const placeholder = getPlaceholder(dragOverNode);
373
+ if (!placeholder) return false;
374
+ var isValid = isValidPlaceholder(placeholder, editContext.dragObject);
375
+ return isValid;
376
+ };
377
+
378
+ // Handle drop on node
379
+ const handleDropZone = (
380
+ droppedOnNode: CustomTreeNode | null,
381
+ index: number,
382
+ event: React.DragEvent,
383
+ ): void => {
384
+ const placeholder = getPlaceholder(droppedOnNode);
385
+ if (!placeholder) return;
386
+
387
+ editContext!.droppedInPlaceholder(placeholder.key, index);
388
+ };
389
+
390
+ if (!page)
391
+ return (
392
+ <div className="flex h-full items-center justify-center text-sm text-gray-500"></div>
393
+ );
394
+
395
+ // Use the PerfectTree component
396
+ return (
397
+ <div className="p-2" ref={treeRef}>
398
+ <PerfectTree
399
+ nodes={rootNodes}
400
+ isDragging={!!editContext?.dragObject}
401
+ selectedKeys={selectedItems}
402
+ expandedKeys={expandedItems}
403
+ onToggleExpand={handleToggle}
404
+ onSelect={handleTreeSelection}
405
+ enableDragAndDrop={true}
406
+ onDragOverZone={(parent, index, event) =>
407
+ handleDragOverZone(parent as CustomTreeNode | null, index, event)
408
+ }
409
+ onStartDrag={(data) => {
410
+ const component = data.node.data as Component;
411
+
412
+ // Initialize drag data to make sure dataTransfer is set
413
+ data.event.dataTransfer.setData("text/plain", data.node.key);
414
+
415
+ // Only create drag object for components with datasourceItem
416
+ if (!component?.datasourceItem) return;
417
+
418
+ editContext!.dragStart({
419
+ type: "component",
420
+ typeId: component.typeId,
421
+ templateId: component.datasourceItem?.templateId,
422
+ name: component.name,
423
+ component: {
424
+ id: component.id,
425
+ language: editContext!.page!.item.language,
426
+ version: editContext!.page!.item.version,
427
+ },
428
+ });
429
+ }}
430
+ onDragEnd={(event) => {
431
+ editContext!.dragEnd();
432
+ }}
433
+ onDrop={(parent, index, event) =>
434
+ handleDropZone(parent as CustomTreeNode | null, index, event)
435
+ }
436
+ renderNode={(node) => renderNode(node as CustomTreeNode)}
437
+ />
438
+ </div>
439
+ );
440
+ }
441
+
442
+ function renderNode(node: CustomTreeNode) {
443
+ return (
444
+ <div>
445
+ <div className="font-geist-sans flex items-center gap-2 p-0.5 text-[12px] text-gray-600">
446
+ <i className={node.icon}></i>
447
+ <span className={node.className}>{node.label}</span>
448
+ {node.tags?.map((x: any) => (
449
+ <div key={`tag-${node.key}-${x.id}`}>
450
+ <span
451
+ id={`id-${node.key}-${x.id}`}
452
+ className="ml-2 rounded px-1 py-0.5 text-xs"
453
+ style={{
454
+ backgroundColor: x.color,
455
+ marginLeft: "8px",
456
+ fontSize: "10px",
457
+ padding: "1px 4px 2px 2px",
458
+ }}
459
+ title={x.tooltip}
460
+ onClick={
461
+ x.onClick
462
+ ? (e) => {
463
+ e.stopPropagation();
464
+ x.onClick();
465
+ }
466
+ : undefined
467
+ }
468
+ >
469
+ {x.icon && <i className={x.icon}></i>}
470
+ {x.value}
471
+ </span>
472
+ </div>
473
+ ))}
474
+ </div>
475
+ </div>
476
+ );
477
+ }
478
+
479
+ function createMap(
480
+ treeNodes: CustomTreeNode[],
481
+ nodeDictionary: { [key: string]: CustomTreeNode },
482
+ ) {
483
+ treeNodes.forEach((n) => {
484
+ if (n.key) nodeDictionary[n.key] = n;
485
+ if (n.children) createMap(n.children as CustomTreeNode[], nodeDictionary);
486
+ });
487
+ }
488
+
489
+ function expandParents(node: CustomTreeNode, expandedKeysList: string[]) {
490
+ if (!node) return;
491
+
492
+ if (node.key && !expandedKeysList.includes(node.key)) {
493
+ expandedKeysList.push(node.key);
494
+ }
495
+
496
+ if (node.parent) {
497
+ expandParents(node.parent, expandedKeysList);
498
+ }
499
+ }
500
+
501
+ function hasSameProps(obj1: any, obj2: any) {
502
+ var obj1Props = Object.keys(obj1),
503
+ obj2Props = Object.keys(obj2);
504
+
505
+ if (obj1Props.length == obj2Props.length) {
506
+ return obj1Props.every(function (prop) {
507
+ return obj2Props.indexOf(prop) >= 0;
508
+ });
509
+ }
510
+
511
+ return false;
512
+ }