@griddo/ax 11.10.36 → 11.10.37

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 (28) hide show
  1. package/package.json +2 -2
  2. package/src/__tests__/components/Fields/ComponentArray/MixableComponentArray/PasteModuleButton/PasteModuleButton.test.tsx +1 -3
  3. package/src/components/Browser/index.tsx +20 -18
  4. package/src/components/BrowserContent/index.tsx +8 -6
  5. package/src/components/Fields/ComponentArray/BulkHeader/index.tsx +1 -2
  6. package/src/components/Fields/ComponentArray/MixableComponentArray/AddItemButton/index.tsx +1 -1
  7. package/src/components/Fields/ComponentArray/MixableComponentArray/index.tsx +29 -43
  8. package/src/components/Fields/ComponentArray/{MixableComponentArray/PasteModuleButton → PasteModuleButton}/index.tsx +6 -6
  9. package/src/components/Fields/ComponentArray/SameComponentArray/AddItemButton/index.tsx +1 -1
  10. package/src/components/Fields/ComponentArray/SameComponentArray/index.tsx +103 -61
  11. package/src/components/Fields/ComponentArray/SameComponentArray/style.tsx +7 -1
  12. package/src/components/Fields/ComponentArray/helpers.tsx +2 -2
  13. package/src/components/Fields/ComponentArray/index.tsx +4 -4
  14. package/src/components/Fields/ComponentContainer/EmptyContainer/index.tsx +7 -8
  15. package/src/components/Fields/ComponentContainer/atoms.tsx +0 -2
  16. package/src/components/Fields/ComponentContainer/index.tsx +26 -30
  17. package/src/components/SideModal/SideModalOption/index.tsx +14 -11
  18. package/src/components/SideModal/index.tsx +16 -19
  19. package/src/containers/PageEditor/actions.tsx +17 -20
  20. package/src/helpers/containerEvaluations.tsx +1 -12
  21. package/src/hooks/iframe.ts +13 -9
  22. package/src/modules/Forms/FormEditor/PageBrowser/index.tsx +1 -15
  23. package/src/modules/FramePreview/index.tsx +17 -4
  24. package/src/modules/GlobalEditor/PageBrowser/index.tsx +5 -2
  25. package/src/modules/PageEditor/Editor/index.tsx +1 -2
  26. package/src/modules/PageEditor/PageBrowser/index.tsx +5 -2
  27. package/src/modules/PageEditor/index.tsx +0 -1
  28. package/src/types/index.tsx +11 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@griddo/ax",
3
3
  "description": "Griddo Author Experience",
4
- "version": "11.10.36",
4
+ "version": "11.10.37",
5
5
  "authors": [
6
6
  "Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
7
7
  "Diego M. Béjar <diego.bejar@secuoyas.com>",
@@ -217,5 +217,5 @@
217
217
  "publishConfig": {
218
218
  "access": "public"
219
219
  },
220
- "gitHead": "add7dbd9c6603c224517fa238d8540ce94edafff"
220
+ "gitHead": "44575bb6e91aca2e9213a7bbf8de78a05481557c"
221
221
  }
@@ -1,7 +1,5 @@
1
1
  import * as React from "react";
2
- import PasteModuleButton, {
3
- IPasteModuleProps,
4
- } from "@ax/components/Fields/ComponentArray/MixableComponentArray/PasteModuleButton";
2
+ import PasteModuleButton, { IPasteModuleProps } from "@ax/components/Fields/ComponentArray/PasteModuleButton";
5
3
  import { ThemeProvider } from "styled-components";
6
4
  import { parseTheme } from "@ax/helpers";
7
5
  import "@testing-library/jest-dom";
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useState } from "react";
1
+ import { useEffect, useState } from "react";
2
2
 
3
3
  import { findByEditorID } from "@ax/forms";
4
4
  import { copyTextToClipboard } from "@ax/helpers";
@@ -38,7 +38,7 @@ const Browser = (props: IBrowserProps): JSX.Element => {
38
38
  const isFormEditor = editorType === "form";
39
39
 
40
40
  const [resolution, setResolution] = useState("desktop");
41
- const { isVisible, toggleToast, setIsVisible } = useToast();
41
+ const { isVisible, toggleToast, setIsVisible, state: toastState } = useToast();
42
42
 
43
43
  useOnMessageReceivedFromIframe(actions);
44
44
 
@@ -78,33 +78,34 @@ const Browser = (props: IBrowserProps): JSX.Element => {
78
78
  const copyUrl = () => {
79
79
  const sharedUrl = `${domain}/page-preview/${id}/${entity}`;
80
80
  copyTextToClipboard(sharedUrl).then(
81
- function () {
82
- toggleToast();
81
+ () => {
82
+ toggleToast("URL Copied");
83
83
  },
84
- function (err) {
84
+ (err) => {
85
85
  console.error("Could not copy text: ", err);
86
86
  },
87
87
  );
88
88
  };
89
89
 
90
- const toastProps = {
91
- setIsVisible,
92
- message: "URL Copied",
93
- };
94
-
95
90
  const deleteModuleSelected = (editorID: number) => {
96
91
  actions?.setSelectedContentAction(0);
97
- actions?.deleteModuleAction([editorID]);
92
+ actions?.deleteModuleAction?.([editorID]);
98
93
  };
99
94
 
100
95
  const duplicateModuleSelected = (editorID: number) => {
101
- const duplicatedEditorID = actions?.duplicateModuleAction([editorID]);
102
- actions?.setSelectedContentAction(duplicatedEditorID);
96
+ const duplicatedEditorID = actions?.duplicateModuleAction?.([editorID]);
97
+ duplicatedEditorID && actions?.setSelectedContentAction(duplicatedEditorID);
98
+ };
99
+
100
+ const copyModuleSelected = (editorID: number) => {
101
+ const isCopied = actions?.copyModuleAction?.([editorID]);
102
+ isCopied && toggleToast("1 module copied to clipboard");
103
103
  };
104
104
 
105
105
  const moduleActions = {
106
106
  deleteModuleAction: deleteModuleSelected,
107
107
  duplicateModuleAction: duplicateModuleSelected,
108
+ copyModuleAction: copyModuleSelected,
108
109
  };
109
110
 
110
111
  return (
@@ -181,7 +182,7 @@ const Browser = (props: IBrowserProps): JSX.Element => {
181
182
  />
182
183
  </S.Wrapper>
183
184
  )}
184
- {isVisible && <Toast {...toastProps} />}
185
+ {isVisible && <Toast message={toastState} setIsVisible={setIsVisible} />}
185
186
  </S.BrowserWrapper>
186
187
  );
187
188
  };
@@ -203,10 +204,11 @@ export interface IBrowserProps {
203
204
  browserRef?: React.RefObject<HTMLDivElement>;
204
205
  editorType?: "form" | "page";
205
206
  actions?: {
206
- setSelectedContentAction: any;
207
- deleteModuleAction(editorID: number[]): void;
208
- duplicateModuleAction(editorID: number[]): number;
209
- setScrollEditorIDAction(editorID: number | null): void;
207
+ setSelectedContentAction(editorID: number): void;
208
+ deleteModuleAction?(editorID: number[]): void;
209
+ duplicateModuleAction?(editorID: number[]): number;
210
+ copyModuleAction?(editorID: number[]): number | boolean;
211
+ setScrollEditorIDAction?(editorID: number | null): void;
210
212
  };
211
213
  }
212
214
 
@@ -1,8 +1,8 @@
1
- import React, { useCallback, useEffect } from "react";
1
+ import { useCallback, useEffect } from "react";
2
2
  import * as components from "components";
3
3
  import { builderSSR, ssrHelpers, SiteProvider } from "components";
4
- import { Core, Preview } from "@griddo/core";
5
- import { ILanguage, ISocialState } from "@ax/types";
4
+ import { type Core, Preview } from "@griddo/core";
5
+ import type { ILanguage, ISocialState } from "@ax/types";
6
6
 
7
7
  const BrowserContent = (props: IProps) => {
8
8
  const {
@@ -28,11 +28,12 @@ const BrowserContent = (props: IProps) => {
28
28
  const INSTANCE = process.env.REACT_APP_INSTANCE || process.env.GRIDDO_REACT_APP_INSTANCE;
29
29
 
30
30
  const useInstanceExternalAssets = useCallback(() => {
31
- if (builderSSR && builderSSR.onRenderBody) {
31
+ if (builderSSR?.onRenderBody) {
32
32
  builderSSR.onRenderBody(ssrHelpers);
33
33
  }
34
34
  }, []);
35
35
 
36
+ // biome-ignore lint/correctness/useExhaustiveDependencies: fix
36
37
  useEffect(useInstanceExternalAssets, [useInstanceExternalAssets]);
37
38
 
38
39
  return (
@@ -84,8 +85,9 @@ interface IProps {
84
85
  ): void;
85
86
  selectHoverEditorID?(editorID: number, parentEditorID: number): void;
86
87
  moduleActions?: {
87
- deleteModuleAction(editorID: number): void;
88
- duplicateModuleAction(editorID: number): void;
88
+ deleteModuleAction?(editorID: number): void;
89
+ duplicateModuleAction?(editorID: number): void;
90
+ copyModuleAction?(editorID: number): void;
89
91
  };
90
92
  }
91
93
 
@@ -1,6 +1,5 @@
1
- import React from "react";
2
1
  import { CheckField, Icon, IconAction } from "@ax/components";
3
- import { IBulkAction } from "@ax/types";
2
+ import type { IBulkAction } from "@ax/types";
4
3
 
5
4
  import * as S from "./style";
6
5
 
@@ -1,4 +1,4 @@
1
- import React, { memo } from "react";
1
+ import { memo } from "react";
2
2
  import { IconAction, SideModal, Tooltip } from "@ax/components";
3
3
 
4
4
  const AddItemButton = (props: IProps) => {
@@ -1,22 +1,15 @@
1
- import React, { useEffect, useState } from "react";
2
- import {
3
- closestCenter,
4
- DndContext,
5
- DragEndEvent,
6
- DragStartEvent,
7
- PointerSensor,
8
- useSensor,
9
- useSensors,
10
- } from "@dnd-kit/core";
1
+ import { useEffect, useState, memo } from "react";
2
+ import { closestCenter, DndContext, useSensor, useSensors, PointerSensor } from "@dnd-kit/core";
3
+ import type { DragEndEvent, DragStartEvent } from "@dnd-kit/core";
11
4
  import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
12
5
 
13
- import { IModule, INotification, ISchemaField } from "@ax/types";
6
+ import type { IComponent, IModule, INotification, ISchemaField, ITemplate, ModuleCategoryInfo } from "@ax/types";
14
7
  import { ComponentContainer, SideModal, Toast } from "@ax/components";
15
8
  import { useBulkSelection, useModal, useToast } from "@ax/hooks";
16
9
 
17
10
  import { getComponentProps, containerToComponentArray, getTypefromKey, getModulesToPaste } from "../helpers";
18
11
  import AddItemButton from "./AddItemButton";
19
- import PasteModuleButton from "./PasteModuleButton";
12
+ import PasteModuleButton from "../PasteModuleButton";
20
13
  import BulkHeader from "../BulkHeader";
21
14
 
22
15
  import * as S from "./style";
@@ -72,11 +65,19 @@ const MixableComponentArray = (props: IMixableComponentArrayProps): JSX.Element
72
65
  field,
73
66
  );
74
67
 
75
- const selectedID = parseInt(localStorage.getItem("selectedID") || "0");
68
+ const selectedID = parseInt(localStorage.getItem("selectedID") || "0", 10);
76
69
  const isComponentModule = contentType === "components";
77
70
  const isModuleArr = contentType === "modules" || contentType === "fields";
78
71
  const isFormArr = contentType === "fields";
79
72
 
73
+ const isAbletoAdd = (!maxItems || fixedValue.length < maxItems) && !disabled && whiteList.length > 0;
74
+ const showAddItemButton = isAbletoAdd && (!isFormArr || (isFormArr && !!setSelectedFormFieldAction));
75
+ const showPasteModuleButton = isAbletoAdd && modulesToPaste.length > 0;
76
+
77
+ const canReplace = maxItems === 1 && whiteList.length > 1;
78
+ const displayReplaceSideModal = fixedValue.length > 0 && canReplace;
79
+ const optionsType = isModuleArr ? "modules" : "components";
80
+
80
81
  const { isOpen, toggleModal } = useModal(false, false);
81
82
  const [isBulkOpen, setIsBulkOpen] = useState(false);
82
83
  const [draggingId, setDraggingId] = useState<number | null>(null);
@@ -103,21 +104,21 @@ const MixableComponentArray = (props: IMixableComponentArrayProps): JSX.Element
103
104
  return fixedValue.length > 1 ? `#${index + 1} ${name}` : name;
104
105
  };
105
106
 
106
- const handleAddModule = (moduleType: string) =>
107
- addModuleAction && addModuleAction(moduleType, objKey, editorID, isComponentModule);
107
+ const handleAddModule = (moduleType: string) => addModuleAction?.(moduleType, objKey, editorID, isComponentModule);
108
108
 
109
- const handleAddComponent = (componentType: string) => addComponentAction && addComponentAction(componentType, objKey);
109
+ const handleAddComponent = (componentType: string) => addComponentAction?.(componentType, objKey);
110
110
 
111
111
  const handleAdd = isModuleArr ? handleAddModule : handleAddComponent;
112
112
 
113
- const handleModuleReplace = (moduleType: string) => {
113
+ const handleModuleReplace = (moduleType: string | IComponent) => {
114
114
  const { modules } = selectedContent;
115
+ if (typeof moduleType !== "string") return;
115
116
  if (isModuleArr && deleteModuleAction && modules?.length > 0) {
116
117
  const currentModule: IModule = modules[0];
117
118
  deleteModuleAction([currentModule.editorID], objKey);
118
119
  handleAddModule(moduleType);
119
120
  } else {
120
- replaceElementsInCollectionAction && replaceElementsInCollectionAction(moduleType);
121
+ replaceElementsInCollectionAction?.(moduleType);
121
122
  }
122
123
  };
123
124
 
@@ -126,29 +127,15 @@ const MixableComponentArray = (props: IMixableComponentArrayProps): JSX.Element
126
127
  const handleToggleModal = () =>
127
128
  isFormArr && setSelectedFormFieldAction ? setSelectedFormFieldAction(field) : toggleModal();
128
129
 
129
- const isAbletoAdd = (!maxItems || fixedValue.length < maxItems) && !disabled && whiteList.length > 0;
130
-
131
- const showAddItemButton = isAbletoAdd && (!isFormArr || (isFormArr && !!setSelectedFormFieldAction));
132
-
133
- const showPasteModuleButton =
134
- isAbletoAdd &&
135
- (isModuleArr || objKey === "componentModules") &&
136
- (modulesToPaste.length > 0 || unavailableModules.length > 0);
137
-
138
- const canReplace = maxItems === 1 && whiteList.length > 1;
139
- const displayReplaceSideModal = fixedValue.length > 0 && canReplace;
140
- const optionsType = isModuleArr ? "modules" : "components";
141
-
142
130
  const Asterisk = () => (mandatory ? <S.Asterisk>*</S.Asterisk> : null);
143
131
 
144
- const ComponentList = React.memo(function ComponentList({ components }: { components: IModule[] }): JSX.Element {
132
+ const ComponentList = memo(function ComponentList({ components }: { components: IModule[] }): JSX.Element {
145
133
  return (
146
134
  <>
147
135
  {components.map((element: IModule, i: number) => {
148
136
  const { editorID, fixed } = element;
149
137
  const componentProps = getComponentProps(element, activatedModules, isModuleArr);
150
- const { moduleTitle, isModuleDeactivated, componentTitle, displayName, isModule, isModuleDisabled } =
151
- componentProps;
138
+ const { moduleTitle, isModuleDeactivated, componentTitle, displayName, isModuleDisabled } = componentProps;
152
139
  const text = getText(componentTitle || displayName, i);
153
140
  const isItemSelected = isSelected(editorID);
154
141
  const isDraggingSelected = selectedItems.all.includes(draggingId);
@@ -177,7 +164,6 @@ const MixableComponentArray = (props: IMixableComponentArrayProps): JSX.Element
177
164
  parentKey={objKey}
178
165
  theme={theme}
179
166
  arrayLength={components.length}
180
- isModule={isModule}
181
167
  isSelected={isItemSelected}
182
168
  onChange={addToBulkSelection}
183
169
  hasMenu={selectedItems.all.length > 0}
@@ -216,7 +202,7 @@ const MixableComponentArray = (props: IMixableComponentArrayProps): JSX.Element
216
202
  };
217
203
 
218
204
  const handleCopy = () => {
219
- const modulesCopied = copyModuleAction && copyModuleAction(selectedItems.all);
205
+ const modulesCopied = copyModuleAction?.(selectedItems.all);
220
206
  modulesCopied &&
221
207
  toggleToast(`${modulesCopied} module${selectedItems.all.length > 1 ? "s" : ""} copied to clipboard`);
222
208
  };
@@ -227,9 +213,9 @@ const MixableComponentArray = (props: IMixableComponentArrayProps): JSX.Element
227
213
  type: "error",
228
214
  text: "Unable to duplicate modules: The destination area has a limit on the number of modules allowed. Please adjust the selection accordingly.",
229
215
  };
230
- setNotificationAction && setNotificationAction(notification);
216
+ setNotificationAction?.(notification);
231
217
  } else {
232
- duplicateModuleAction && duplicateModuleAction(selectedItems.all, objKey);
218
+ duplicateModuleAction?.(selectedItems.all, objKey);
233
219
  resetBulkSelection();
234
220
  }
235
221
  };
@@ -238,7 +224,7 @@ const MixableComponentArray = (props: IMixableComponentArrayProps): JSX.Element
238
224
  const modulesToDelete = fixedValue
239
225
  .filter((element) => selectedItems.all.includes(element.editorID) && (!isFormArr || !element.fixed))
240
226
  .map((element) => element.editorID);
241
- deleteModuleAction && deleteModuleAction(modulesToDelete, objKey);
227
+ deleteModuleAction?.(modulesToDelete, objKey);
242
228
  resetBulkSelection();
243
229
  };
244
230
 
@@ -346,7 +332,7 @@ export interface IMixableComponentArrayProps {
346
332
  editorID: number;
347
333
  goTo: (editorID: number) => void;
348
334
  actions?: {
349
- addComponentAction: (componentType: any, key?: string) => void;
335
+ addComponentAction: (componentType: IComponent | string, key?: string) => void;
350
336
  addModuleAction: (moduleType: string, key: string, selectedID: number, isComponentModule?: boolean) => void;
351
337
  deleteModuleAction: (editorID: number[], key?: string) => void;
352
338
  duplicateModuleAction: (editorID: number[], key?: string) => number;
@@ -355,10 +341,10 @@ export interface IMixableComponentArrayProps {
355
341
  moveModuleAction: (moduleID: number[], selectedContent: any, newIndex: number, key: string) => void;
356
342
  pasteModuleAction: (editorID: number, key: string, modulesToPaste: IModule[]) => Promise<{ error?: INotification }>;
357
343
  setNotificationAction: (notification: INotification) => void;
358
- replaceModuleAction: (module: any, parent: any, objKey: string) => void;
344
+ replaceModuleAction: (module: IComponent, parent: any, objKey: string) => void;
359
345
  setSelectedFormFieldAction: (field: ISchemaField | null) => void;
360
346
  };
361
- categories?: any[];
347
+ categories?: ModuleCategoryInfo[];
362
348
  disabled?: boolean;
363
349
  activatedModules: string[];
364
350
  objKey: string;
@@ -367,7 +353,7 @@ export interface IMixableComponentArrayProps {
367
353
  theme: string;
368
354
  moduleCopy: { date: Date; elements: IModule[] } | null;
369
355
  availableDataPacks: Record<string, any>[];
370
- template: any;
356
+ template: ITemplate;
371
357
  setHistoryPush?: (path: string, isEditor: boolean) => void;
372
358
  scrollEditorID: number | null;
373
359
  }
@@ -1,8 +1,8 @@
1
- import React, { memo } from "react";
1
+ import { memo } from "react";
2
2
 
3
3
  import { useToast } from "@ax/hooks";
4
4
  import { Tooltip, IconAction, Toast } from "@ax/components";
5
- import { IModule, INotification } from "@ax/types";
5
+ import type { IModule, INotification } from "@ax/types";
6
6
 
7
7
  const PasteModuleButton = (props: IPasteModuleProps): JSX.Element => {
8
8
  const {
@@ -24,9 +24,9 @@ const PasteModuleButton = (props: IPasteModuleProps): JSX.Element => {
24
24
  type: "error",
25
25
  text: "You are trying to paste a module that is part of a disabled content type package. To copy it, you must first activate it.",
26
26
  btnText: "Activate package",
27
- onClick: () => setHistoryPush && setHistoryPush("/sites/settings/content-types", false),
27
+ onClick: () => setHistoryPush?.("/sites/settings/content-types", false),
28
28
  };
29
- setNotification && setNotification(notification);
29
+ setNotification?.(notification);
30
30
  }
31
31
 
32
32
  if (slots && modulesToPaste.length > slots) {
@@ -34,12 +34,12 @@ const PasteModuleButton = (props: IPasteModuleProps): JSX.Element => {
34
34
  type: "error",
35
35
  text: "Unable to paste modules: The destination area has a limit on the number of modules allowed. Please adjust the selection accordingly.",
36
36
  };
37
- setNotification && setNotification(notification);
37
+ setNotification?.(notification);
38
38
  } else {
39
39
  const pasteResult = await pasteModule(editorID, arrayKey, modulesToPaste);
40
40
  if (pasteResult?.error) {
41
41
  const { type, text } = pasteResult.error;
42
- setNotification && setNotification({ type, text });
42
+ setNotification?.({ type, text });
43
43
  } else {
44
44
  toggleToast(`${modulesToPaste.length} module${modulesToPaste.length > 1 ? "s" : ""} pasted from clipboard`);
45
45
  }
@@ -1,4 +1,4 @@
1
- import React, { memo } from "react";
1
+ import { memo } from "react";
2
2
  import { IconAction, Tooltip } from "@ax/components";
3
3
 
4
4
  const AddItemButton = (props: IProps) => {
@@ -1,22 +1,16 @@
1
- import React, { useEffect, useState } from "react";
2
- import {
3
- closestCenter,
4
- DndContext,
5
- DragEndEvent,
6
- DragStartEvent,
7
- PointerSensor,
8
- useSensor,
9
- useSensors,
10
- } from "@dnd-kit/core";
1
+ import { useEffect, useState, memo } from "react";
2
+ import { closestCenter, DndContext, PointerSensor, useSensor, useSensors } from "@dnd-kit/core";
3
+ import type { DragEndEvent, DragStartEvent } from "@dnd-kit/core";
11
4
  import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
12
5
 
13
- import { IModule, INotification, ISchemaField } from "@ax/types";
14
- import { ComponentContainer } from "@ax/components";
15
- import { useBulkSelection } from "@ax/hooks";
6
+ import type { IComponent, IModule, INotification, ISchemaField, ITemplate, ModuleCategoryInfo } from "@ax/types";
7
+ import { ComponentContainer, Toast } from "@ax/components";
8
+ import { useBulkSelection, useToast } from "@ax/hooks";
16
9
 
17
10
  import AddItemButton from "./AddItemButton";
18
- import { getComponentProps, getTypefromKey } from "../helpers";
11
+ import { getComponentProps, getModulesToPaste, getTypefromKey } from "../helpers";
19
12
  import BulkHeader from "../BulkHeader";
13
+ import PasteModuleButton from "../PasteModuleButton";
20
14
 
21
15
  import * as S from "./style";
22
16
 
@@ -37,19 +31,32 @@ const SameComponentArray = (props: ISameComponentArrayProps): JSX.Element => {
37
31
  field,
38
32
  mandatory,
39
33
  theme,
34
+ moduleCopy,
35
+ availableDataPacks,
36
+ template,
37
+ setHistoryPush,
40
38
  } = props;
41
39
 
42
40
  const type = getTypefromKey(objKey);
43
41
  const { contentType = type } = field;
44
- const componentIDs: number[] = value && value.length ? value.map((element) => element.editorID) : [];
42
+ const componentIDs: number[] = value?.length ? value.map((element) => element.editorID) : [];
45
43
 
46
- const { addModuleAction, addComponentAction, setNotificationAction, duplicateModuleAction, deleteModuleAction } =
47
- actions || {};
44
+ const {
45
+ addModuleAction,
46
+ addComponentAction,
47
+ setNotificationAction,
48
+ duplicateModuleAction,
49
+ deleteModuleAction,
50
+ pasteModuleAction,
51
+ copyModuleAction,
52
+ } = actions || {};
48
53
 
49
54
  const [isBulkOpen, setIsBulkOpen] = useState(false);
50
55
  const [draggingId, setDraggingId] = useState<number | null>(null);
51
56
  const { resetBulkSelection, selectedItems, isSelected, checkState, addToBulkSelection, selectAllItems } =
52
57
  useBulkSelection(componentIDs);
58
+ const { isVisible, toggleToast, setIsVisible, state: toastState } = useToast();
59
+
53
60
 
54
61
  const sensors = useSensors(
55
62
  useSensor(PointerSensor, {
@@ -65,23 +72,31 @@ const SameComponentArray = (props: ISameComponentArrayProps): JSX.Element => {
65
72
  }
66
73
  }, [selectedItems.all]);
67
74
 
75
+ const { modulesToPaste, unavailableModules } = getModulesToPaste(
76
+ moduleCopy,
77
+ whiteList,
78
+ availableDataPacks,
79
+ template,
80
+ field,
81
+ );
82
+
68
83
  const getText = (name: string, index: number) => {
69
84
  return value && value.length > 1 ? `#${index + 1} ${name}` : name;
70
85
  };
71
86
 
72
- const componentType = field.reference ? selectedContent[field.reference] : selectedContent["kind"];
87
+ const componentType = field.reference ? selectedContent[field.reference] : selectedContent.kind;
73
88
 
74
89
  const isModuleArr = contentType === "modules";
75
90
  const isComponentModule = contentType === "components";
91
+ const showAddItemButton = !maxItems || (value && value.length < maxItems);
92
+ const showPasteModuleButton = showAddItemButton && modulesToPaste.length > 0;
76
93
 
77
94
  const handleAddModule = (moduleType: string) => addModuleAction(moduleType, objKey, editorID, isComponentModule);
78
95
 
79
- const handleAddComponent = () => addComponentAction && addComponentAction(componentType, objKey);
96
+ const handleAddComponent = () => addComponentAction?.(componentType, objKey);
80
97
 
81
98
  const handleAdd = isModuleArr ? handleAddModule : handleAddComponent;
82
99
 
83
- const showAddItemButton = !maxItems || (value && value.length < maxItems);
84
-
85
100
  const Asterisk = () => (mandatory ? <S.Asterisk>*</S.Asterisk> : null);
86
101
 
87
102
  const handleDragStart = (event: DragStartEvent) => {
@@ -106,15 +121,14 @@ const SameComponentArray = (props: ISameComponentArrayProps): JSX.Element => {
106
121
  setDraggingId(null);
107
122
  };
108
123
 
109
- const ComponentList = React.memo(function ComponentList({ components }: { components: IModule[] }): JSX.Element {
124
+ const ComponentList = memo(function ComponentList({ components }: { components: IModule[] }): JSX.Element {
110
125
  return (
111
126
  <>
112
127
  {components.map((element, i: number) => {
113
128
  const { editorID } = element;
114
129
  const componentProps = getComponentProps(element, activatedModules, isModuleArr);
115
- if (!componentProps) return <></>;
116
- const { moduleTitle, isModuleDeactivated, componentTitle, displayName, isModule, isModuleDisabled } =
117
- componentProps;
130
+ if (!componentProps) return null;
131
+ const { moduleTitle, isModuleDeactivated, componentTitle, displayName, isModuleDisabled } = componentProps;
118
132
  const text = getText(componentTitle || displayName, i);
119
133
  const isItemSelected = isSelected(editorID);
120
134
  const isDraggingSelected = selectedItems.all.includes(draggingId);
@@ -122,33 +136,31 @@ const SameComponentArray = (props: ISameComponentArrayProps): JSX.Element => {
122
136
  const isMultiDragging = selectedItems.all.length > 1 && draggingId === editorID;
123
137
 
124
138
  return (
125
- <>
126
- <ComponentContainer
127
- isArray={true}
128
- key={editorID}
129
- editorID={editorID}
130
- goTo={goTo}
131
- text={text}
132
- moduleTitle={moduleTitle}
133
- whiteList={whiteList}
134
- categories={categories}
135
- actions={actions}
136
- selectedContent={selectedContent}
137
- disabled={disabled || isModuleDisabled}
138
- canDuplicate={showAddItemButton && !isModuleDeactivated}
139
- canDelete={true}
140
- parentKey={objKey}
141
- theme={theme}
142
- arrayLength={components.length}
143
- isModule={isModule}
144
- isSelected={isItemSelected}
145
- onChange={addToBulkSelection}
146
- hasMenu={selectedItems.all.length > 0}
147
- isMultiDragging={isMultiDragging}
148
- draggingCount={selectedItems.all.length}
149
- className={`${isGhosting ? "ghosting" : ""} ${isMultiDragging ? "dragging" : ""}`}
150
- />
151
- </>
139
+ <ComponentContainer
140
+ isArray={true}
141
+ key={editorID}
142
+ editorID={editorID}
143
+ goTo={goTo}
144
+ text={text}
145
+ moduleTitle={moduleTitle}
146
+ whiteList={whiteList}
147
+ categories={categories}
148
+ actions={actions}
149
+ selectedContent={selectedContent}
150
+ disabled={disabled || isModuleDisabled}
151
+ canDuplicate={showAddItemButton && !isModuleDeactivated}
152
+ canDelete={true}
153
+ parentKey={objKey}
154
+ theme={theme}
155
+ arrayLength={components.length}
156
+ isSelected={isItemSelected}
157
+ onChange={addToBulkSelection}
158
+ hasMenu={selectedItems.all.length > 0}
159
+ isMultiDragging={isMultiDragging}
160
+ draggingCount={selectedItems.all.length}
161
+ className={`${isGhosting ? "ghosting" : ""} ${isMultiDragging ? "dragging" : ""}`}
162
+ toggleToast={toggleToast}
163
+ />
152
164
  );
153
165
  })}
154
166
  </>
@@ -161,19 +173,30 @@ const SameComponentArray = (props: ISameComponentArrayProps): JSX.Element => {
161
173
  type: "error",
162
174
  text: "Unable to duplicate modules: The destination area has a limit on the number of modules allowed. Please adjust the selection accordingly.",
163
175
  };
164
- setNotificationAction && setNotificationAction(notification);
176
+ setNotificationAction?.(notification);
165
177
  } else {
166
- duplicateModuleAction && duplicateModuleAction(selectedItems.all, objKey);
178
+ duplicateModuleAction?.(selectedItems.all, objKey);
167
179
  resetBulkSelection();
168
180
  }
169
181
  };
170
182
 
171
183
  const handleDelete = () => {
172
- deleteModuleAction && deleteModuleAction(selectedItems.all, objKey);
184
+ deleteModuleAction?.(selectedItems.all, objKey);
173
185
  resetBulkSelection();
174
186
  };
175
187
 
188
+ const handleCopy = () => {
189
+ const modulesCopied = copyModuleAction?.(selectedItems.all);
190
+ modulesCopied &&
191
+ toggleToast(`${modulesCopied} module${selectedItems.all.length > 1 ? "s" : ""} copied to clipboard`);
192
+ };
193
+
176
194
  const bulkActions = [
195
+ {
196
+ icon: "copy",
197
+ text: "copy",
198
+ action: handleCopy,
199
+ },
177
200
  {
178
201
  icon: "duplicate",
179
202
  text: "duplicate",
@@ -210,9 +233,23 @@ const SameComponentArray = (props: ISameComponentArrayProps): JSX.Element => {
210
233
  ) : (
211
234
  <S.Subtitle>{value?.length || 0} items</S.Subtitle>
212
235
  )}
213
- {showAddItemButton && !disabled && (
214
- <AddItemButton handleClick={handleAdd} tooltipText={isModuleArr ? "Add module" : "Add component"} />
215
- )}
236
+ <S.ActionsWrapper data-testid="mixableComponentWrapper">
237
+ {showPasteModuleButton && pasteModuleAction && (
238
+ <PasteModuleButton
239
+ editorID={editorID}
240
+ isModuleCopyUnavailable={unavailableModules.length > 0}
241
+ pasteModule={pasteModuleAction}
242
+ setNotification={setNotificationAction}
243
+ setHistoryPush={setHistoryPush}
244
+ arrayKey={objKey}
245
+ modulesToPaste={modulesToPaste}
246
+ slots={maxItems ? maxItems - value.length : null}
247
+ />
248
+ )}
249
+ {showAddItemButton && !disabled && (
250
+ <AddItemButton handleClick={handleAdd} tooltipText={isModuleArr ? "Add module" : "Add component"} />
251
+ )}
252
+ </S.ActionsWrapper>
216
253
  </S.ItemRow>
217
254
  {value && Array.isArray(value) && (
218
255
  <DndContext
@@ -226,6 +263,7 @@ const SameComponentArray = (props: ISameComponentArrayProps): JSX.Element => {
226
263
  </SortableContext>
227
264
  </DndContext>
228
265
  )}
266
+ {isVisible && <Toast message={toastState} setIsVisible={setIsVisible} />}
229
267
  </S.Wrapper>
230
268
  );
231
269
  };
@@ -240,7 +278,7 @@ export interface ISameComponentArrayProps {
240
278
  editorID: number;
241
279
  goTo: (editorID: number) => void;
242
280
  actions: {
243
- addComponentAction: (componentType: any, key?: string) => void;
281
+ addComponentAction: (componentType: IComponent | string, key?: string) => void;
244
282
  addModuleAction: (moduleType: string, key: string, selectedID: number, isComponentModule?: boolean) => void;
245
283
  deleteModuleAction: (editorID: number[], key?: string) => void;
246
284
  duplicateModuleAction: (editorID: number[], key?: string) => number;
@@ -249,15 +287,19 @@ export interface ISameComponentArrayProps {
249
287
  moveModuleAction: (moduleID: number[], selectedContent: any, newIndex: number, key: string) => void;
250
288
  pasteModuleAction: (editorID: number, key: string, modulesToPaste: IModule[]) => Promise<{ error?: INotification }>;
251
289
  setNotificationAction: (notification: INotification) => void;
252
- replaceModuleAction: (module: any, parent: any, objKey: string) => void;
290
+ replaceModuleAction: (module: IComponent, parent: any, objKey: string) => void;
253
291
  };
254
- categories?: any;
292
+ categories?: ModuleCategoryInfo[];
255
293
  disabled?: boolean;
256
294
  activatedModules: string[];
257
295
  objKey: string;
258
296
  field: ISchemaField;
259
297
  mandatory?: boolean;
260
298
  theme: string;
299
+ moduleCopy: { date: Date; elements: IModule[] } | null;
300
+ availableDataPacks: Record<string, any>[];
301
+ template: ITemplate;
302
+ setHistoryPush?: (path: string, isEditor: boolean) => void;
261
303
  }
262
304
 
263
305
  export default SameComponentArray;