@griddo/ax 1.65.23 → 1.66.0

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 (57) hide show
  1. package/package.json +2 -2
  2. package/src/GlobalStore.tsx +4 -3
  3. package/src/api/structuredData.tsx +15 -1
  4. package/src/components/Browser/index.tsx +13 -16
  5. package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/Field/index.tsx +12 -0
  6. package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/TemplateManager/index.tsx +11 -0
  7. package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/index.tsx +13 -0
  8. package/src/components/ConfigPanel/Form/index.tsx +3 -1
  9. package/src/components/ConfigPanel/index.tsx +19 -5
  10. package/src/components/ConfigPanel/style.tsx +5 -0
  11. package/src/components/FieldContainer/index.tsx +4 -0
  12. package/src/components/Fields/ComponentArray/MixableComponentArray/PasteModuleButton/index.tsx +49 -0
  13. package/src/components/Fields/ComponentArray/MixableComponentArray/index.tsx +51 -11
  14. package/src/components/Fields/ComponentArray/MixableComponentArray/style.tsx +14 -1
  15. package/src/components/Fields/ComponentContainer/index.tsx +24 -5
  16. package/src/components/Fields/ImageField/index.tsx +18 -5
  17. package/src/components/Image/index.tsx +25 -0
  18. package/src/components/Notification/index.tsx +3 -1
  19. package/src/components/index.tsx +2 -0
  20. package/src/containers/Navigation/Defaults/actions.tsx +27 -9
  21. package/src/containers/PageEditor/actions.tsx +104 -5
  22. package/src/containers/PageEditor/constants.tsx +2 -0
  23. package/src/containers/PageEditor/interfaces.tsx +12 -0
  24. package/src/containers/PageEditor/reducer.tsx +8 -0
  25. package/src/containers/PageEditor/utils.tsx +2 -2
  26. package/src/helpers/index.tsx +6 -0
  27. package/src/helpers/schemas.tsx +36 -7
  28. package/src/modules/App/Routing/index.tsx +1 -1
  29. package/src/modules/Content/OptionTable/index.tsx +44 -43
  30. package/src/modules/Content/OptionTable/store.tsx +1 -1
  31. package/src/modules/Content/OptionTable/style.tsx +27 -12
  32. package/src/modules/Content/PageItem/index.tsx +14 -4
  33. package/src/modules/Content/atoms.tsx +19 -2
  34. package/src/modules/Content/index.tsx +37 -14
  35. package/src/modules/Content/utils.tsx +27 -12
  36. package/src/modules/GlobalEditor/Editor/index.tsx +12 -1
  37. package/src/modules/GlobalEditor/index.tsx +20 -2
  38. package/src/modules/Navigation/Defaults/DefaultsEditor/index.tsx +13 -0
  39. package/src/modules/Navigation/Defaults/atoms.tsx +28 -0
  40. package/src/modules/Navigation/Defaults/index.tsx +30 -4
  41. package/src/modules/Navigation/Defaults/style.tsx +32 -1
  42. package/src/modules/PageEditor/Editor/index.tsx +16 -1
  43. package/src/modules/PageEditor/index.tsx +14 -1
  44. package/src/modules/PublicPreview/index.tsx +15 -18
  45. package/src/modules/Settings/Globals/NavigationModules/SideModal/SideModalOption/index.tsx +35 -0
  46. package/src/modules/Settings/Globals/NavigationModules/SideModal/SideModalOption/style.tsx +22 -0
  47. package/src/modules/Settings/Globals/NavigationModules/SideModal/index.tsx +111 -0
  48. package/src/modules/Settings/Globals/NavigationModules/SideModal/style.tsx +64 -0
  49. package/src/modules/Settings/Globals/NavigationModules/index.tsx +89 -0
  50. package/src/modules/Settings/Globals/NavigationModules/style.tsx +36 -0
  51. package/src/modules/Settings/Globals/index.tsx +38 -1
  52. package/src/modules/Sites/SitesList/SiteItem/index.tsx +7 -5
  53. package/src/modules/StructuredData/StructuredDataList/OptionTable/index.tsx +14 -3
  54. package/src/modules/StructuredData/StructuredDataList/OptionTable/style.tsx +11 -2
  55. package/src/modules/StructuredData/StructuredDataList/atoms.tsx +19 -2
  56. package/src/modules/StructuredData/StructuredDataList/index.tsx +4 -13
  57. 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": "1.65.23",
4
+ "version": "1.66.0",
5
5
  "authors": [
6
6
  "Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
7
7
  "Carlos Torres <carlos.torres@secuoyas.com>",
@@ -221,5 +221,5 @@
221
221
  "publishConfig": {
222
222
  "access": "public"
223
223
  },
224
- "gitHead": "7c4d164534c6b253cbd0b50a6292d3ea993fb670"
224
+ "gitHead": "48f0e2027ddcce4188e6bed411ecaab485ddb7e5"
225
225
  }
@@ -19,6 +19,7 @@ import { galleryReducer, galleryInitialState } from "./containers/Gallery/reduce
19
19
  import { domainsReducer, domainsInitialState } from "./containers/Domains/reducer";
20
20
  import { redirectsReducer, redirectsInitialState } from "./containers/Redirects/reducer";
21
21
  import { analyticsReducer, analyticsInitialState } from "./containers/Analytics/reducer";
22
+ import { LOGOUT } from "./containers/App/constants";
22
23
 
23
24
  import { IRootState } from "@ax/types";
24
25
 
@@ -54,10 +55,10 @@ export class GlobalStore {
54
55
  });
55
56
 
56
57
  const rootReducer = (state: IRootState | undefined, action: any) => {
57
- if (action.type === "RESET") {
58
+ if (action.type === LOGOUT) {
58
59
  state = {
59
- router: undefined,
60
- app: appInitialState,
60
+ router: state?.router,
61
+ app: state?.app || appInitialState,
61
62
  sites: sitesInitialState,
62
63
  pageEditor: pageEditorInitialState,
63
64
  menu: menuInitialState,
@@ -34,6 +34,11 @@ const SERVICES: { [key: string]: IServiceConfig } = {
34
34
  endpoint: "/structured_data_content/",
35
35
  method: "DELETE",
36
36
  },
37
+ GET_DATA_CONTENT_BULK: {
38
+ ...template,
39
+ endpoint: "/structured_data_content/bulk/",
40
+ method: "GET",
41
+ },
37
42
  DELETE_DATA_CONTENT_BULK: {
38
43
  ...template,
39
44
  endpoint: "/structured_data_content/bulk",
@@ -73,7 +78,7 @@ const SERVICES: { [key: string]: IServiceConfig } = {
73
78
  ...template,
74
79
  endpoint: ["/site/", "/structured_data"],
75
80
  method: "GET",
76
- }
81
+ },
77
82
  };
78
83
 
79
84
  const getData = (token: string | null, siteID?: number | null) => {
@@ -145,6 +150,14 @@ const deleteDataContent = async (dataContentId: number) => {
145
150
  return sendRequest(SERVICES.DELETE_DATA_CONTENT);
146
151
  };
147
152
 
153
+ const getDataContentBulk = (dataContentIDs: number[], siteID?: number) => {
154
+ const { host, endpoint } = SERVICES.GET_DATA_CONTENT_BULK;
155
+ const url = siteID ? `${host}/site/${siteID}${endpoint}` : `${host}${endpoint}`;
156
+ const ids = dataContentIDs.join(",");
157
+ SERVICES.GET_DATA_CONTENT_BULK.dynamicUrl = `${url}${ids}`;
158
+ return sendRequest(SERVICES.GET_DATA_CONTENT_BULK);
159
+ };
160
+
148
161
  const deleteDataContentBulk = (dataContentID: number[]) => {
149
162
  return sendRequest(SERVICES.DELETE_DATA_CONTENT_BULK, { ids: dataContentID });
150
163
  };
@@ -226,4 +239,5 @@ export default {
226
239
  setDataStatus,
227
240
  setDataStatusBulk,
228
241
  getContentTypes,
242
+ getDataContentBulk,
229
243
  };
@@ -3,7 +3,7 @@ import { FrameContextConsumer } from "react-frame-component";
3
3
  import { StyleSheetManager } from "styled-components";
4
4
 
5
5
  import * as components from "components";
6
- import { providers, builderSSR, ssrHelpers } from "components";
6
+ import { SiteProvider, builderSSR, ssrHelpers } from "components";
7
7
  import { Preview } from "@griddo/core";
8
8
  import { findByEditorID } from "@ax/forms";
9
9
  import { copyTextToClipboard } from "@ax/helpers";
@@ -33,7 +33,6 @@ const Browser = (props: IBrowserProps): JSX.Element => {
33
33
 
34
34
  const API_URL = process.env.REACT_APP_API_ENDPOINT;
35
35
  const PUBLIC_API_URL = process.env.REACT_APP_PUBLIC_API_ENDPOINT;
36
- const { SiteProvider, AnimationProvider } = providers;
37
36
 
38
37
  const [resolution, setResolution] = useState("desktop");
39
38
  const { isVisible, toggleToast, setIsVisible } = useToast();
@@ -111,20 +110,18 @@ const Browser = (props: IBrowserProps): JSX.Element => {
111
110
  publicApiUrl={PUBLIC_API_URL}
112
111
  siteId={siteID}
113
112
  >
114
- <AnimationProvider showOnScroll={{ active: false }}>
115
- <S.Wrapper ref={(ref: any) => ((window as any).browserRef = ref)}>
116
- <Preview
117
- isPage={isPage}
118
- apiUrl={API_URL}
119
- library={components}
120
- content={content}
121
- header={header}
122
- footer={footer}
123
- languageId={content.language}
124
- pageLanguages={content.pageLanguages}
125
- />
126
- </S.Wrapper>
127
- </AnimationProvider>
113
+ <S.Wrapper ref={(ref: any) => ((window as any).browserRef = ref)}>
114
+ <Preview
115
+ isPage={isPage}
116
+ apiUrl={API_URL}
117
+ library={components}
118
+ content={content}
119
+ header={header}
120
+ footer={footer}
121
+ languageId={content.language}
122
+ pageLanguages={content.pageLanguages}
123
+ />
124
+ </S.Wrapper>
128
125
  </SiteProvider>
129
126
  );
130
127
 
@@ -25,6 +25,10 @@ const Field = (props: IProps): JSX.Element => {
25
25
  deleteError,
26
26
  errors,
27
27
  theme,
28
+ moduleCopy,
29
+ availableDataPacks,
30
+ template,
31
+ setHistoryPush,
28
32
  } = props;
29
33
 
30
34
  const isGroup = field.type === "FieldGroup";
@@ -72,6 +76,10 @@ const Field = (props: IProps): JSX.Element => {
72
76
  error={error}
73
77
  deleteError={deleteError}
74
78
  theme={theme}
79
+ moduleCopy={moduleCopy}
80
+ availableDataPacks={availableDataPacks}
81
+ template={template}
82
+ setHistoryPush={setHistoryPush}
75
83
  />
76
84
  );
77
85
  };
@@ -97,6 +105,10 @@ interface IProps {
97
105
  errors: IErrorItem[];
98
106
  collapsed?: boolean;
99
107
  theme: string;
108
+ moduleCopy: { date: string; element: Record<string, unknown> } | null;
109
+ availableDataPacks: Record<string, any>[];
110
+ template: any;
111
+ setHistoryPush?: (path: string, isEditor: boolean) => void;
100
112
  }
101
113
 
102
114
  export default Field;
@@ -25,6 +25,9 @@ export const TemplateManager = (props: IProps): JSX.Element => {
25
25
  deleteError,
26
26
  errors,
27
27
  theme,
28
+ moduleCopy,
29
+ availableDataPacks,
30
+ setHistoryPush,
28
31
  } = props;
29
32
 
30
33
  const isConfig = selectedTab === "config";
@@ -92,6 +95,10 @@ export const TemplateManager = (props: IProps): JSX.Element => {
92
95
  deleteError={deleteError}
93
96
  errors={errors}
94
97
  theme={theme}
98
+ moduleCopy={moduleCopy}
99
+ availableDataPacks={availableDataPacks}
100
+ template={template}
101
+ setHistoryPush={setHistoryPush}
95
102
  />
96
103
  );
97
104
  })}
@@ -116,11 +123,15 @@ interface IProps {
116
123
  deleteError(error: IErrorItem): void;
117
124
  errors: IErrorItem[];
118
125
  theme: string;
126
+ moduleCopy: { date: string; element: Record<string, unknown> } | null;
127
+ availableDataPacks: Record<string, any>[];
128
+ setHistoryPush?: (path: string, isEditor: boolean) => void;
119
129
  }
120
130
 
121
131
  const mapStateToProps = (state: IRootState) => ({
122
132
  activatedPacks: state.dataPacks.activated,
123
133
  activatedModules: state.dataPacks.modules,
134
+ availableDataPacks: state.dataPacks.available,
124
135
  });
125
136
 
126
137
  export default connect(mapStateToProps)(TemplateManager);
@@ -34,6 +34,10 @@ const PageConnectedField = (props: any) => {
34
34
  deleteError,
35
35
  isGlobal,
36
36
  theme,
37
+ moduleCopy,
38
+ availableDataPacks,
39
+ template,
40
+ setHistoryPush,
37
41
  } = props;
38
42
 
39
43
  const isTemplate = field.type === "template";
@@ -155,6 +159,8 @@ const PageConnectedField = (props: any) => {
155
159
  deleteError={deleteError}
156
160
  errors={errors}
157
161
  theme={theme}
162
+ moduleCopy={moduleCopy}
163
+ setHistoryPush={setHistoryPush}
158
164
  />
159
165
  );
160
166
  }
@@ -179,6 +185,10 @@ const PageConnectedField = (props: any) => {
179
185
  deleteError={deleteError}
180
186
  errors={errors}
181
187
  theme={theme}
188
+ moduleCopy={moduleCopy}
189
+ availableDataPacks={availableDataPacks}
190
+ template={template}
191
+ setHistoryPush={setHistoryPush}
182
192
  />
183
193
  );
184
194
  };
@@ -193,6 +203,9 @@ const mapStateToProps = (state: IRootState) => ({
193
203
  templateConfig: state.pageEditor.templateConfig,
194
204
  activatedModules: state.dataPacks.modules,
195
205
  errors: state.pageEditor.errors,
206
+ moduleCopy: state.pageEditor.moduleCopy,
207
+ availableDataPacks: state.dataPacks.available,
208
+ template: state.pageEditor.template,
196
209
  });
197
210
 
198
211
  const mapDispatchToProps = {
@@ -6,7 +6,7 @@ import { Tabs } from "@ax/components";
6
6
  import ConnectedField from "./ConnectedField";
7
7
 
8
8
  export const Form = (props: IProps): JSX.Element => {
9
- const { schema, selectedTab, setSelectedTab, actions, isPage, isGlobal, theme } = props;
9
+ const { schema, selectedTab, setSelectedTab, actions, isPage, isGlobal, theme, setHistoryPush } = props;
10
10
  const tabContent = schema.configTabs.find((tab: ISchemaTab) => tab.title === selectedTab);
11
11
  const setTab = (tab: string) => setSelectedTab(tab);
12
12
 
@@ -29,6 +29,7 @@ export const Form = (props: IProps): JSX.Element => {
29
29
  isPage={isPage}
30
30
  isGlobal={isGlobal}
31
31
  theme={theme}
32
+ setHistoryPush={setHistoryPush}
32
33
  />
33
34
  );
34
35
  };
@@ -69,6 +70,7 @@ interface IProps {
69
70
  isPage: boolean;
70
71
  isGlobal?: boolean;
71
72
  theme: string;
73
+ setHistoryPush?: (path: string, isEditor: boolean) => void;
72
74
  }
73
75
 
74
76
  export default Form;
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React, { useEffect, useRef } from "react";
2
2
  import { isEmptyObj } from "@ax/helpers";
3
3
 
4
4
  import { Loading } from "@ax/components";
@@ -10,7 +10,9 @@ import NavigationForm from "./NavigationForm";
10
10
  import GlobalPageForm from "./GlobalPageForm";
11
11
  import PreviewForm from "./PreviewForm";
12
12
 
13
- const NAV_COMPONENTS = ["Header", "Footer"];
13
+ import * as S from "./style";
14
+
15
+ const navigationModulesTypes = ["header", "footer"];
14
16
 
15
17
  const ConfigPanel = (props: IStateProps): JSX.Element => {
16
18
  const {
@@ -31,13 +33,23 @@ const ConfigPanel = (props: IStateProps): JSX.Element => {
31
33
  isReadOnly,
32
34
  userEditing,
33
35
  theme,
36
+ lastElementAddedId,
34
37
  } = props;
35
38
 
39
+ const wrapperRef = useRef<HTMLDivElement>(null);
40
+
41
+ useEffect(() => {
42
+ if (lastElementAddedId && wrapperRef.current) {
43
+ const element = document.querySelector(`.editorId-${lastElementAddedId}`);
44
+ element && element.scrollIntoView();
45
+ }
46
+ }, [lastElementAddedId]);
47
+
36
48
  if (isLoading || isEmptyObj(schema)) {
37
49
  return <Loading />;
38
50
  }
39
51
 
40
- const showNavigationForm = NAV_COMPONENTS.includes(schema.component) && isPage;
52
+ const showNavigationForm = navigationModulesTypes.includes(schema.type) && isPage;
41
53
  const isGlobalPageNotEditable = isGlobal && isPage && !isEditable;
42
54
 
43
55
  const getForm = () => {
@@ -67,13 +79,14 @@ const ConfigPanel = (props: IStateProps): JSX.Element => {
67
79
  isPage={isPage}
68
80
  isGlobal={isGlobal}
69
81
  theme={theme}
82
+ setHistoryPush={setHistoryPush}
70
83
  />
71
84
  );
72
85
  }
73
86
  };
74
87
 
75
88
  return (
76
- <>
89
+ <S.Wrapper ref={wrapperRef}>
77
90
  <Header
78
91
  schema={schema}
79
92
  actions={actions}
@@ -83,7 +96,7 @@ const ConfigPanel = (props: IStateProps): JSX.Element => {
83
96
  setSelectedContent={setSelectedContent}
84
97
  />
85
98
  {getForm()}
86
- </>
99
+ </S.Wrapper>
87
100
  );
88
101
  };
89
102
 
@@ -105,6 +118,7 @@ interface IStateProps {
105
118
  isReadOnly?: boolean;
106
119
  userEditing?: IUserEditing | null;
107
120
  theme: string;
121
+ lastElementAddedId?: null | number;
108
122
  }
109
123
 
110
124
  export default ConfigPanel;
@@ -0,0 +1,5 @@
1
+ import styled from "styled-components";
2
+
3
+ const Wrapper = styled.div``;
4
+
5
+ export { Wrapper };
@@ -77,4 +77,8 @@ interface IProps {
77
77
  error?: any;
78
78
  deleteError?(error: any): void;
79
79
  theme: string;
80
+ moduleCopy?: { date: string; element: Record<string, unknown> } | null;
81
+ availableDataPacks?: Record<string, unknown>[];
82
+ template?: any;
83
+ setHistoryPush?: (path: string, isEditor: boolean) => void;
80
84
  }
@@ -0,0 +1,49 @@
1
+ import React, { memo } from "react";
2
+
3
+ import { useToast } from "@ax/hooks";
4
+ import { Tooltip, IconAction, Toast } from "@ax/components";
5
+ import { INotification } from "@ax/types";
6
+
7
+ const PasteModuleButton = (props: IProps): JSX.Element => {
8
+ const { pasteModule, setNotification, setHistoryPush, editorID, isModuleCopyUnavailable } = props;
9
+
10
+ const { isVisible, toggleToast, setIsVisible } = useToast();
11
+
12
+ const handlePasteModule = async () => {
13
+ if (!isModuleCopyUnavailable) {
14
+ const pasteResult = await pasteModule(editorID);
15
+ if (pasteResult.error) {
16
+ const { type, text } = pasteResult.error;
17
+ setNotification && setNotification({ type, text });
18
+ }
19
+ toggleToast();
20
+ } else {
21
+ const notification: INotification = {
22
+ type: "error",
23
+ 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.",
24
+ btnText: "Activate package",
25
+ onClick: () => setHistoryPush && setHistoryPush("/sites/settings/content-types", false),
26
+ };
27
+ setNotification && setNotification(notification);
28
+ }
29
+ };
30
+
31
+ return (
32
+ <>
33
+ <Tooltip content="Paste from clipboard">
34
+ <IconAction icon="paste" onClick={handlePasteModule} />
35
+ </Tooltip>
36
+ {isVisible && <Toast message="Module pasted from clipboard" setIsVisible={setIsVisible} />}
37
+ </>
38
+ );
39
+ };
40
+
41
+ interface IProps {
42
+ pasteModule: (editorID: number) => Promise<{ error?: INotification }>;
43
+ setNotification?: (notification: INotification) => void;
44
+ setHistoryPush?: (path: string, isEditor: boolean) => void;
45
+ editorID: number;
46
+ isModuleCopyUnavailable: boolean;
47
+ }
48
+
49
+ export default memo(PasteModuleButton);
@@ -1,10 +1,12 @@
1
1
  import React from "react";
2
+ import differenceInSeconds from 'date-fns/differenceInSeconds';
2
3
 
3
4
  import { IModule } from "@ax/types";
4
5
  import { ComponentContainer } from "@ax/components";
5
6
 
6
- import AddItemButton from "./AddItemButton";
7
7
  import { getComponentProps, containerToComponentArray, getTypefromKey } from "../helpers";
8
+ import AddItemButton from "./AddItemButton";
9
+ import PasteModuleButton from "./PasteModuleButton";
8
10
 
9
11
  import * as S from "./style";
10
12
 
@@ -25,8 +27,22 @@ const MixableComponentArray = (props: IMixableComponentArrayProps): JSX.Element
25
27
  field,
26
28
  mandatory,
27
29
  theme,
30
+ moduleCopy,
31
+ availableDataPacks,
32
+ template,
33
+ setHistoryPush,
28
34
  } = props;
29
35
 
36
+ const moduleCopyComponent = moduleCopy?.element.component;
37
+ const availableDataPackModule = availableDataPacks?.reduce((prev: any, curr: any) => {
38
+ const packModule = curr.modules.find((module: any) => module.id === moduleCopyComponent);
39
+ return prev || packModule;
40
+ }, null);
41
+ const isModuleCopyUnavailable =
42
+ availableDataPackModule &&
43
+ !whiteList.includes(moduleCopyComponent) &&
44
+ !!availableDataPackModule?.sectionList[template.component]?.includes(field.key);
45
+
30
46
  const type = getTypefromKey(objKey);
31
47
  const { contentType = type } = field;
32
48
 
@@ -54,7 +70,16 @@ const MixableComponentArray = (props: IMixableComponentArrayProps): JSX.Element
54
70
 
55
71
  const handleAdd = isModuleArr ? handleAddModule : handleAddComponent;
56
72
 
57
- const showAddItemButton = !maxItems || fixedValue.length < maxItems;
73
+ const showAddItemButton = (!maxItems || fixedValue.length < maxItems) && !disabled;
74
+
75
+ const timeSinceModuleCopy = !!moduleCopy && differenceInSeconds(new Date(), new Date(moduleCopy.date));
76
+ const eightHoursInSeconds = 8 * 60 * 60;
77
+ const showPasteModuleButton =
78
+ showAddItemButton &&
79
+ isModuleArr &&
80
+ !!moduleCopy &&
81
+ timeSinceModuleCopy < eightHoursInSeconds &&
82
+ (whiteList.includes(moduleCopyComponent) || isModuleCopyUnavailable);
58
83
 
59
84
  const Asterisk = () => (mandatory ? <S.Asterisk>*</S.Asterisk> : null);
60
85
 
@@ -65,15 +90,26 @@ const MixableComponentArray = (props: IMixableComponentArrayProps): JSX.Element
65
90
  </S.Title>
66
91
  <S.ItemRow>
67
92
  <S.Subtitle>{fixedValue && fixedValue.length} items</S.Subtitle>
68
- {showAddItemButton && !disabled && (
69
- <AddItemButton
70
- whiteList={whiteList}
71
- categories={categories}
72
- handleClick={handleAdd}
73
- isModuleArr={isModuleArr}
74
- theme={theme}
75
- />
76
- )}
93
+ <S.ActionsWrapper>
94
+ {showPasteModuleButton && (
95
+ <PasteModuleButton
96
+ editorID={editorID}
97
+ isModuleCopyUnavailable={isModuleCopyUnavailable}
98
+ pasteModule={actions.pasteModuleAction}
99
+ setNotification={actions.setNotificationAction}
100
+ setHistoryPush={setHistoryPush}
101
+ />
102
+ )}
103
+ {showAddItemButton && (
104
+ <AddItemButton
105
+ whiteList={whiteList}
106
+ categories={categories}
107
+ handleClick={handleAdd}
108
+ isModuleArr={isModuleArr}
109
+ theme={theme}
110
+ />
111
+ )}
112
+ </S.ActionsWrapper>
77
113
  </S.ItemRow>
78
114
  {fixedValue &&
79
115
  fixedValue.map((element: any, i: number) => {
@@ -125,6 +161,10 @@ export interface IMixableComponentArrayProps {
125
161
  field: any;
126
162
  mandatory?: boolean;
127
163
  theme: string;
164
+ moduleCopy: { date: string; element: Record<string, any> } | null;
165
+ availableDataPacks: Record<string, any>[];
166
+ template: any;
167
+ setHistoryPush?: (path: string, isEditor: boolean) => void;
128
168
  }
129
169
 
130
170
  export default MixableComponentArray;
@@ -52,4 +52,17 @@ const Asterisk = styled.span`
52
52
  color: ${(p) => p.theme.color.error};
53
53
  `;
54
54
 
55
- export { Wrapper, Title, Component, ItemRow, Subtitle, Asterisk };
55
+ const ActionsWrapper = styled.div`
56
+ display: flex;
57
+ gap: ${(p) => p.theme.spacing.xxs};
58
+ `;
59
+
60
+ export {
61
+ Wrapper,
62
+ Title,
63
+ Component,
64
+ ItemRow,
65
+ Subtitle,
66
+ Asterisk,
67
+ ActionsWrapper
68
+ };
@@ -1,8 +1,8 @@
1
1
  import React from "react";
2
2
 
3
- import { useModal } from "@ax/hooks";
3
+ import { useModal, useToast } from "@ax/hooks";
4
4
  import { isEmptyContainer, getDisplayName, trimText } from "@ax/helpers";
5
- import { Icon, SideModal } from "@ax/components";
5
+ import { Icon, SideModal, Toast } from "@ax/components";
6
6
  import EmptyContainer from "./EmptyContainer";
7
7
  import { ArrayContainerButtons, ContainerButtons } from "./atoms";
8
8
 
@@ -27,17 +27,21 @@ const ComponentContainer = (props: IComponentContainerProps): JSX.Element => {
27
27
  disabled,
28
28
  canDuplicate,
29
29
  parentKey,
30
- theme
30
+ theme,
31
31
  } = props;
32
32
 
33
+ const { isVisible, toggleToast, setIsVisible } = useToast();
34
+
33
35
  let deleteModuleAction: any;
34
36
  let moveModuleAction: any;
35
37
  let duplicateModuleAction: any;
38
+ let copyModuleAction: any;
36
39
 
37
40
  if (actions) {
38
41
  deleteModuleAction = actions.deleteModuleAction;
39
42
  moveModuleAction = actions.moveModuleAction;
40
43
  duplicateModuleAction = actions.duplicateModuleAction;
44
+ copyModuleAction = actions.copyModuleAction;
41
45
  }
42
46
 
43
47
  const whiteListFirstItem: any = whiteList && whiteList[0];
@@ -69,6 +73,16 @@ const ComponentContainer = (props: IComponentContainerProps): JSX.Element => {
69
73
 
70
74
  const removeItem = () => deleteModuleAction(editorID, parentKey);
71
75
  const duplicateItem = () => parentKey && duplicateModuleAction(editorID, parentKey);
76
+ const copyItem = () => {
77
+ const isCopied = copyModuleAction(editorID);
78
+ isCopied && toggleToast();
79
+ }
80
+
81
+ const copyOpt = {
82
+ label: "copy",
83
+ icon: "duplicate",
84
+ action: copyItem,
85
+ };
72
86
 
73
87
  const duplicateOpt = {
74
88
  label: "duplicate",
@@ -82,7 +96,11 @@ const ComponentContainer = (props: IComponentContainerProps): JSX.Element => {
82
96
  action: removeItem,
83
97
  };
84
98
 
85
- const actionArrayMenuOptions = !canDuplicate ? [deleteOpt] : [duplicateOpt, deleteOpt];
99
+ const actionArrayMenuOptions = [
100
+ copyOpt,
101
+ ...(canDuplicate ? [duplicateOpt] : []),
102
+ deleteOpt
103
+ ];
86
104
 
87
105
  const actionMenuOptions = [
88
106
  {
@@ -130,7 +148,7 @@ const ComponentContainer = (props: IComponentContainerProps): JSX.Element => {
130
148
  />
131
149
  ) : (
132
150
  <>
133
- <S.Component isArray={isArray} onClick={handleClick} disabled={disabled}>
151
+ <S.Component isArray={isArray} onClick={handleClick} disabled={disabled} className={`editorId-${editorID}`}>
134
152
  {containerInfo && !disabled && (
135
153
  <S.IconWrapper>
136
154
  <Icon name={containerText} />
@@ -160,6 +178,7 @@ const ComponentContainer = (props: IComponentContainerProps): JSX.Element => {
160
178
  theme={theme}
161
179
  />
162
180
  )}
181
+ {isVisible && <Toast message="1 module copied to clipboard" setIsVisible={setIsVisible} />}
163
182
  </>
164
183
  );
165
184
  };
@@ -1,5 +1,5 @@
1
1
  import React, { memo, useEffect, useState } from "react";
2
- import { Icon, IconAction, Gallery, Modal } from "@ax/components";
2
+ import { Icon, IconAction, Gallery, Modal, Image } from "@ax/components";
3
3
 
4
4
  import { formatBytes, getFormattedDateWithTimezone } from "@ax/helpers";
5
5
  import { IImage, ISite } from "@ax/types";
@@ -8,8 +8,18 @@ import { useModal } from "@ax/hooks";
8
8
  import * as S from "./style";
9
9
 
10
10
  const ImageField = (props: IImageFieldProps) => {
11
- const { value, error, onChange, selectedContent, disabled, handleValidation, validators, site, setIsGalleryOpened } =
12
- props;
11
+ const {
12
+ value,
13
+ error,
14
+ onChange,
15
+ selectedContent,
16
+ disabled,
17
+ handleValidation,
18
+ validators,
19
+ site,
20
+ setIsGalleryOpened,
21
+ cropPreview = false,
22
+ } = props;
13
23
 
14
24
  const isLinkableImage = selectedContent && selectedContent.component === "LinkableImage";
15
25
  const hasImage = value && Object.prototype.hasOwnProperty.call(value, "url");
@@ -54,6 +64,8 @@ const ImageField = (props: IImageFieldProps) => {
54
64
  }
55
65
  };
56
66
 
67
+ const previewHeight = cropPreview ? { height: 190 } : {};
68
+
57
69
  return (
58
70
  <>
59
71
  <S.FieldWrapper error={error} preview={!!previewSrc} disabled={disabled} onClick={handleClick}>
@@ -73,7 +85,7 @@ const ImageField = (props: IImageFieldProps) => {
73
85
  </S.ImageDataWrapper>
74
86
  )}
75
87
  <S.Preview preview={!!previewSrc}>
76
- <img src={previewSrc} alt="Preview" />
88
+ {previewSrc && <Image url={previewSrc} width={320} {...previewHeight} />}
77
89
  <S.PreviewActions>
78
90
  <IconAction icon="change" onClick={handleChange} />
79
91
  <IconAction icon="delete" onClick={handleDelete} />
@@ -96,6 +108,7 @@ interface IImageFieldProps {
96
108
  handleValidation?: (value: string, validators?: Record<string, unknown>) => void;
97
109
  validators?: Record<string, unknown>;
98
110
  site: ISite;
111
+ cropPreview?: boolean;
99
112
  }
100
113
 
101
- export default memo(ImageField);
114
+ export default memo(ImageField);