@griddo/ax 1.66.11 → 1.67.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 (74) hide show
  1. package/package.json +2 -2
  2. package/src/__tests__/components/Fields/ConditionalField/ConditionalField.test.tsx +95 -0
  3. package/src/api/pages.tsx +15 -3
  4. package/src/api/redirects.tsx +4 -2
  5. package/src/api/sites.tsx +12 -4
  6. package/src/components/Browser/index.tsx +9 -22
  7. package/src/components/Browser/style.tsx +1 -6
  8. package/src/components/ErrorCenter/index.tsx +8 -5
  9. package/src/components/ErrorCenter/style.tsx +21 -8
  10. package/src/components/Fields/ComponentArray/MixableComponentArray/AddItemButton/index.tsx +3 -3
  11. package/src/components/Fields/ComponentArray/MixableComponentArray/index.tsx +60 -25
  12. package/src/components/Fields/ComponentContainer/index.tsx +21 -7
  13. package/src/components/Fields/ConditionalField/index.tsx +1 -1
  14. package/src/components/Fields/LinkField/index.tsx +111 -0
  15. package/src/components/Fields/ReferenceField/ItemList/index.tsx +4 -0
  16. package/src/components/Fields/ReferenceField/ManualPanel/index.tsx +12 -2
  17. package/src/components/Fields/ReferenceField/index.tsx +24 -12
  18. package/src/components/Fields/ReferenceField/style.tsx +12 -1
  19. package/src/components/Fields/UrlField/index.tsx +13 -1
  20. package/src/components/Fields/VisualUniqueSelection/utils.tsx +1 -6
  21. package/src/components/Fields/index.tsx +2 -0
  22. package/src/components/FieldsBehavior/index.tsx +14 -1
  23. package/src/components/Icon/components/Copy.js +14 -0
  24. package/src/components/Icon/components/Copy2.js +14 -0
  25. package/src/components/Icon/components/Duplicate.js +3 -5
  26. package/src/components/Icon/components/Page.js +12 -0
  27. package/src/components/Icon/svgs/Copy.svg +3 -0
  28. package/src/components/Icon/svgs/Copy2.svg +3 -0
  29. package/src/components/Icon/svgs/Duplicate.svg +1 -1
  30. package/src/components/Icon/svgs/page.svg +3 -0
  31. package/src/components/MainWrapper/AppBar/index.tsx +21 -10
  32. package/src/components/MainWrapper/AppBar/style.tsx +11 -3
  33. package/src/components/MainWrapper/index.tsx +2 -0
  34. package/src/components/Notification/index.tsx +1 -3
  35. package/src/components/SearchField/index.tsx +37 -4
  36. package/src/components/SearchField/style.tsx +23 -10
  37. package/src/components/index.tsx +2 -0
  38. package/src/containers/Navigation/Defaults/actions.tsx +2 -0
  39. package/src/containers/PageEditor/actions.tsx +92 -17
  40. package/src/containers/PageEditor/utils.tsx +2 -1
  41. package/src/containers/Sites/actions.tsx +53 -24
  42. package/src/containers/Sites/constants.tsx +2 -0
  43. package/src/containers/Sites/interfaces.tsx +12 -5
  44. package/src/containers/Sites/reducer.tsx +8 -0
  45. package/src/containers/StructuredData/actions.tsx +5 -8
  46. package/src/forms/index.tsx +9 -1
  47. package/src/forms/validators.tsx +119 -12
  48. package/src/helpers/index.tsx +2 -0
  49. package/src/helpers/objects.tsx +10 -2
  50. package/src/modules/Categories/CategoriesList/CategoryItem/index.tsx +3 -1
  51. package/src/modules/Categories/CategoriesList/CategoryPanel/index.tsx +15 -9
  52. package/src/modules/Categories/CategoriesList/index.tsx +2 -1
  53. package/src/modules/Content/PageItem/index.tsx +52 -2
  54. package/src/modules/Content/atoms.tsx +41 -3
  55. package/src/modules/Content/index.tsx +44 -2
  56. package/src/modules/Content/style.tsx +8 -1
  57. package/src/modules/FramePreview/index.tsx +85 -0
  58. package/src/modules/FramePreview/style.tsx +18 -0
  59. package/src/modules/GlobalEditor/Editor/index.tsx +3 -1
  60. package/src/modules/GlobalEditor/PageBrowser/index.tsx +3 -0
  61. package/src/modules/GlobalEditor/index.tsx +22 -6
  62. package/src/modules/PageEditor/Editor/index.tsx +5 -1
  63. package/src/modules/PageEditor/PageBrowser/index.tsx +4 -5
  64. package/src/modules/PageEditor/index.tsx +27 -9
  65. package/src/modules/Redirects/index.tsx +40 -10
  66. package/src/modules/Settings/Globals/index.tsx +1 -1
  67. package/src/modules/Sites/index.tsx +2 -2
  68. package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/index.tsx +1 -1
  69. package/src/modules/StructuredData/StructuredDataList/index.tsx +19 -2
  70. package/src/modules/Users/Profile/index.tsx +3 -3
  71. package/src/routes/multisite.tsx +12 -4
  72. package/src/routes/site.tsx +1 -1
  73. package/src/types/index.tsx +13 -4
  74. package/tsconfig.paths.json +2 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@griddo/ax",
3
3
  "description": "Griddo Author Experience",
4
- "version": "1.66.11",
4
+ "version": "1.67.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": "32040fc708a7f4a7d0e1b941924c395f49aead12"
224
+ "gitHead": "d44876fdcc7e83f8715701a96c1a9b222666925a"
225
225
  }
@@ -0,0 +1,95 @@
1
+ import React from "react";
2
+ import ConditionalField from "@ax/components/Fields/ConditionalField";
3
+ import { ThemeProvider } from "styled-components";
4
+ import { parseTheme } from "@griddo/core";
5
+ import globalTheme from "@ax/themes/theme.json";
6
+ import { mock } from "jest-mock-extended";
7
+ import { render, screen, cleanup } from "@testing-library/react";
8
+ import FieldsBehavior from "@ax/components/FieldsBehavior";
9
+
10
+ afterEach(cleanup);
11
+
12
+ const defaultProps = mock<IConditionalFieldProps>();
13
+
14
+ describe("ConditionalField component rendering", () => {
15
+ it("should render the component", () => {
16
+ render(
17
+ <ThemeProvider theme={parseTheme(globalTheme)}>
18
+ <ConditionalField {...defaultProps} />
19
+ </ThemeProvider>
20
+ );
21
+
22
+ const conditionalFieldContent = screen.getByTestId("conditionalFieldContent");
23
+
24
+ expect(conditionalFieldContent).toBeTruthy();
25
+ });
26
+
27
+ it("should render innerFields", () => {
28
+ const fieldProps = {
29
+ objKey: "subjDocumentUrl",
30
+ fieldType: "TextField",
31
+ innerFields: [],
32
+ field: {
33
+ title: "Document URL",
34
+ type: "TextField",
35
+ key: "subjDocumentUrl",
36
+ placeholder: "https://",
37
+ condition: "url",
38
+ },
39
+ title: "Cool title",
40
+ type: "TextField",
41
+ mandatory: "true",
42
+ isTitle: true,
43
+ theme: "default-theme",
44
+ };
45
+
46
+ defaultProps.innerFields = [<FieldsBehavior {...fieldProps} key={1} />];
47
+ defaultProps.value = "url";
48
+
49
+ render(
50
+ <ThemeProvider theme={parseTheme(globalTheme)}>
51
+ <ConditionalField {...defaultProps} />
52
+ </ThemeProvider>
53
+ );
54
+
55
+ expect(screen.getByText("Cool title"));
56
+ });
57
+
58
+ it("should not render innerFields", () => {
59
+ const fieldProps = {
60
+ objKey: "subjDocumentUrl",
61
+ fieldType: "TextField",
62
+ innerFields: [],
63
+ field: {
64
+ title: "Document URL",
65
+ type: "TextField",
66
+ key: "subjDocumentUrl",
67
+ placeholder: "https://",
68
+ condition: "url",
69
+ },
70
+ title: "Cool title",
71
+ type: "TextField",
72
+ mandatory: "true",
73
+ isTitle: true,
74
+ theme: "default-theme",
75
+ };
76
+
77
+ defaultProps.innerFields = [<FieldsBehavior {...fieldProps} key={1} />];
78
+ defaultProps.value = "video";
79
+
80
+ render(
81
+ <ThemeProvider theme={parseTheme(globalTheme)}>
82
+ <ConditionalField {...defaultProps} />
83
+ </ThemeProvider>
84
+ );
85
+
86
+ expect(screen.queryByText("Cool title")).not.toBeTruthy();
87
+ });
88
+ });
89
+
90
+ interface IConditionalFieldProps {
91
+ value: string | boolean;
92
+ options: any[];
93
+ innerFields: any;
94
+ onChange: (value: any) => void;
95
+ }
package/src/api/pages.tsx CHANGED
@@ -75,6 +75,11 @@ const SERVICES: { [key: string]: IServiceConfig } = {
75
75
  endpoint: ["/page/", "/preview/"],
76
76
  method: "GET",
77
77
  },
78
+ PAGE_CHECK: {
79
+ ...template,
80
+ endpoint: "/page/check",
81
+ method: "POST",
82
+ },
78
83
  };
79
84
 
80
85
  const getPageInfo = async (pageID: number) => {
@@ -154,15 +159,17 @@ const getPageBreadcrumb = async (pageID: number) => {
154
159
  return sendRequest(SERVICES.GET_PAGE_BREADCRUMB);
155
160
  };
156
161
 
157
- const duplicatePage = async (pageID: number, data: any) => {
162
+ const duplicatePage = async (pageID: number, data?: any, siteID?: number) => {
158
163
  const {
159
164
  host,
160
165
  endpoint: [prefix, suffix],
161
166
  } = SERVICES.DUPLICATE_PAGE;
162
167
 
163
- SERVICES.DUPLICATE_PAGE.dynamicUrl = `${host}${prefix}${pageID}${suffix}`;
168
+ SERVICES.DUPLICATE_PAGE.dynamicUrl = siteID
169
+ ? `${host}${prefix}${pageID}${suffix}/${siteID}`
170
+ : `${host}${prefix}${pageID}${suffix}`;
164
171
 
165
- return sendRequest(SERVICES.DUPLICATE_PAGE, { ...data });
172
+ return siteID ? sendRequest(SERVICES.DUPLICATE_PAGE) : sendRequest(SERVICES.DUPLICATE_PAGE, { ...data });
166
173
  };
167
174
 
168
175
  const bulkDelete = async (ids: any) => sendRequest(SERVICES.DELETE_BULK, { ids });
@@ -199,6 +206,10 @@ const getPublicPage = async (pageID: number, entity: string) => {
199
206
  return sendRequest(SERVICES.GET_PUPLIC_PAGE);
200
207
  };
201
208
 
209
+ const pageCheck = async (data: any) => {
210
+ return sendRequest(SERVICES.PAGE_CHECK, { ...data });
211
+ };
212
+
202
213
  export default {
203
214
  getPageInfo,
204
215
  updatePage,
@@ -214,4 +225,5 @@ export default {
214
225
  bulkRestore,
215
226
  sendPagePing,
216
227
  getPublicPage,
228
+ pageCheck,
217
229
  };
@@ -40,13 +40,15 @@ const SERVICES: { [key: string]: IServiceConfig } = {
40
40
  };
41
41
 
42
42
  const getRedirects = async (params: any, filters?: string) => {
43
- const { page, itemsPerPage, pagination } = params;
43
+ const { page, itemsPerPage, pagination, query, filterBy } = params;
44
44
 
45
45
  const { host, endpoint } = SERVICES.GET_REDIRECTS;
46
46
 
47
47
  const filterString = filters || "";
48
+ const searchQuery = query ? `&query=${query}` : "";
49
+ const filterQuery = filterBy ? `&filterBy=${filterBy}` : "";
48
50
 
49
- SERVICES.GET_REDIRECTS.dynamicUrl = `${host}${endpoint}?page=${page}&itemsPerPage=${itemsPerPage}&pagination=${pagination}${filterString}`;
51
+ SERVICES.GET_REDIRECTS.dynamicUrl = `${host}${endpoint}?page=${page}&itemsPerPage=${itemsPerPage}&pagination=${pagination}${filterString}${searchQuery}${filterQuery}`;
50
52
 
51
53
  return sendRequest(SERVICES.GET_REDIRECTS);
52
54
  };
package/src/api/sites.tsx CHANGED
@@ -101,8 +101,10 @@ const SERVICES: { [key: string]: IServiceConfig } = {
101
101
  },
102
102
  };
103
103
 
104
- const getAllSites = async (token: string) => {
105
- return sendInitialRequest(SERVICES.GET_ALL_SITES, token);
104
+ const getAllSites = async (language?: number) => {
105
+ const { host, endpoint } = SERVICES.GET_ALL_SITES;
106
+ SERVICES.GET_ALL_SITES.dynamicUrl = language ? `${host}${endpoint}?language=${language}` : "";
107
+ return sendRequest(SERVICES.GET_ALL_SITES);
106
108
  };
107
109
 
108
110
  const getSiteInfo = async (siteID: number) => {
@@ -138,17 +140,23 @@ const getSitePages = async (params: IGetSitePagesParams, filterQuery?: string):
138
140
  endpoint: [prefix, suffix],
139
141
  } = SERVICES.GET_SITE_PAGES;
140
142
 
141
- const { siteID, deleted, page, itemsPerPage, query, filterStructuredData, lang, format } = params;
143
+ const { siteID, deleted, page, itemsPerPage, query, filterStructuredData, lang, format, filterPages } = params;
142
144
 
143
145
  const filters = filterQuery ? `${filterQuery}&` : "?";
144
146
 
145
- SERVICES.GET_SITE_PAGES.dynamicUrl = `${host}${prefix}${siteID}${suffix}${filters}deleted=${deleted}&page=${page}&itemsPerPage=${itemsPerPage}`;
147
+ SERVICES.GET_SITE_PAGES.dynamicUrl = `${host}${prefix}${siteID}${suffix}${filters}deleted=${deleted}`;
148
+
149
+ if (page && itemsPerPage)
150
+ SERVICES.GET_SITE_PAGES.dynamicUrl =
151
+ SERVICES.GET_SITE_PAGES.dynamicUrl + `&page=${page}&itemsPerPage=${itemsPerPage}`;
146
152
  if (query && query.trim() !== "")
147
153
  SERVICES.GET_SITE_PAGES.dynamicUrl = SERVICES.GET_SITE_PAGES.dynamicUrl + `&query=${query}`;
148
154
  if (filterStructuredData)
149
155
  SERVICES.GET_SITE_PAGES.dynamicUrl =
150
156
  SERVICES.GET_SITE_PAGES.dynamicUrl + `&filterStructuredData=${filterStructuredData}`;
151
157
  if (format) SERVICES.GET_SITE_PAGES.dynamicUrl = SERVICES.GET_SITE_PAGES.dynamicUrl + `&format=${format}`;
158
+ if (filterPages)
159
+ SERVICES.GET_SITE_PAGES.dynamicUrl = SERVICES.GET_SITE_PAGES.dynamicUrl + `&filterPages=${filterPages.join(",")}`;
152
160
 
153
161
  const dataHeader = {
154
162
  ...(lang && { lang }),
@@ -1,6 +1,4 @@
1
1
  import React, { useEffect, useState, useCallback } from "react";
2
- import { FrameContextConsumer } from "react-frame-component";
3
- import { StyleSheetManager } from "styled-components";
4
2
 
5
3
  import * as components from "components";
6
4
  import { SiteProvider, builderSSR, ssrHelpers } from "components";
@@ -29,10 +27,14 @@ const Browser = (props: IBrowserProps): JSX.Element => {
29
27
  siteID,
30
28
  isPreview,
31
29
  setSelectedContent,
30
+ browserRef,
32
31
  } = props;
33
32
 
34
33
  const API_URL = process.env.REACT_APP_API_ENDPOINT;
35
34
  const PUBLIC_API_URL = process.env.REACT_APP_PUBLIC_API_ENDPOINT;
35
+ const { id, entity } = content;
36
+ const domain = window.location.origin;
37
+ const urlPreview = `${domain}/editor/page-preview`;
36
38
 
37
39
  const [resolution, setResolution] = useState("desktop");
38
40
  const { isVisible, toggleToast, setIsVisible } = useToast();
@@ -80,10 +82,8 @@ const Browser = (props: IBrowserProps): JSX.Element => {
80
82
  };
81
83
 
82
84
  const copyUrl = () => {
83
- const { id, entity } = content;
84
- const domain = window.location.origin;
85
- const url = `${domain}/page-preview/${id}/${entity}`;
86
- copyTextToClipboard(url).then(
85
+ const sharedUrl = `${domain}/page-preview/${id}/${entity}`;
86
+ copyTextToClipboard(sharedUrl).then(
87
87
  function () {
88
88
  toggleToast();
89
89
  },
@@ -125,16 +125,8 @@ const Browser = (props: IBrowserProps): JSX.Element => {
125
125
  </SiteProvider>
126
126
  );
127
127
 
128
- const getStylesFromHeader = () => {
129
- const header = document.head.innerHTML;
130
-
131
- return header.replace(/<style data-styled="active" \b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, "");
132
- };
133
-
134
- const initialContent = `<!DOCTYPE html><html><head>${getStylesFromHeader()}</head><body><div></div></body></html>`;
135
-
136
128
  return (
137
- <S.BrowserWrapper>
129
+ <S.BrowserWrapper ref={browserRef}>
138
130
  <S.NavBar>
139
131
  <S.NavUrl>{url}</S.NavUrl>
140
132
  {isPreview && (
@@ -164,13 +156,7 @@ const Browser = (props: IBrowserProps): JSX.Element => {
164
156
  </S.NavBar>
165
157
  {isPreview ? (
166
158
  <S.FrameWrapper>
167
- <S.StyledFrame width={getWidth(resolution)} height="100%" initialContent={initialContent}>
168
- <FrameContextConsumer>
169
- {(frameContext: any) => (
170
- <StyleSheetManager target={frameContext.document.head}>{Providers}</StyleSheetManager>
171
- )}
172
- </FrameContextConsumer>
173
- </S.StyledFrame>
159
+ <iframe title="Preview" width={getWidth(resolution)} height="100%" src={urlPreview} allow-scripts />
174
160
  </S.FrameWrapper>
175
161
  ) : (
176
162
  <>{Providers}</>
@@ -195,6 +181,7 @@ interface IBrowserProps {
195
181
  disabled?: boolean;
196
182
  siteID?: number;
197
183
  isPreview?: boolean;
184
+ browserRef?: any;
198
185
  }
199
186
 
200
187
  export default Browser;
@@ -1,5 +1,4 @@
1
1
  import styled from "styled-components";
2
- import Frame from "react-frame-component";
3
2
 
4
3
  const BrowserWrapper = styled.div`
5
4
  background-color: ${(p) => p.theme.color.uiBackground01};
@@ -67,8 +66,4 @@ const FrameWrapper = styled.div`
67
66
  height: 100%;
68
67
  `;
69
68
 
70
- const StyledFrame = styled(Frame)`
71
- box-shadow: ${(p) => p.theme.shadow.shadowL};
72
- `;
73
-
74
- export { BrowserWrapper, Wrapper, NavBar, NavUrl, NavActions, IconWrapper, FrameWrapper, StyledFrame };
69
+ export { BrowserWrapper, Wrapper, NavBar, NavUrl, NavActions, IconWrapper, FrameWrapper };
@@ -20,15 +20,18 @@ const ErrorCenter = (props: IProps): JSX.Element => {
20
20
  goToElement(item.key);
21
21
  };
22
22
 
23
+ const icon = item.type === "warning" ? "warning" : "alert";
24
+
23
25
  return (
24
- <S.Wrapper key={`${item.editorID}${item.key}`} onClick={handleClick}>
25
- <S.Header>
26
- <Icon name="alert" size="16" />
27
- {item.type}
26
+ <S.Wrapper key={`${item.editorID}${item.key}`} clickable={!!item.editorID} onClick={handleClick}>
27
+ <S.Header type={item.type}>
28
+ <Icon name={icon} size="16" />
29
+ <S.Type>{item.type}</S.Type>
28
30
  </S.Header>
29
31
  <S.Content>
30
32
  <S.Title>{item.message}</S.Title>
31
33
  <S.Subtitle>{item.name}</S.Subtitle>
34
+ {item.editorID && <S.Link>Go to field</S.Link>}
32
35
  </S.Content>
33
36
  </S.Wrapper>
34
37
  );
@@ -45,7 +48,7 @@ const ErrorCenter = (props: IProps): JSX.Element => {
45
48
  interface IProps {
46
49
  errors: IErrorItem[];
47
50
  actions?: {
48
- goToError(editorID: number, tab: string, template: boolean): void;
51
+ goToError(editorID: number | null, tab: string, template: boolean): void;
49
52
  };
50
53
  }
51
54
 
@@ -1,7 +1,6 @@
1
1
  import styled from "styled-components";
2
2
 
3
3
  const ActionMenu = styled.div`
4
- padding: 0 ${(p) => p.theme.spacing.s};
5
4
  width: ${(p) => `calc(4 * ${p.theme.spacing.l})`};
6
5
  max-height: 380px;
7
6
  overflow: auto;
@@ -10,16 +9,20 @@ const ActionMenu = styled.div`
10
9
  const MenuHeader = styled.div`
11
10
  ${(p) => p.theme.textStyle.headingXXS};
12
11
  color: ${(p) => p.theme.color.textLowEmphasis};
13
- padding-top: ${(p) => p.theme.spacing.xs};
12
+ padding: ${(p) => `${p.theme.spacing.xs} ${p.theme.spacing.s}`};
14
13
  `;
15
14
 
16
- const Wrapper = styled.div`
17
- padding: ${(p) => p.theme.spacing.s} 0;
15
+ const Wrapper = styled.div<{ clickable: boolean }>`
16
+ padding: ${(p) => p.theme.spacing.xs} ${(p) => p.theme.spacing.s};
18
17
  border-bottom: 1px solid ${(p) => p.theme.color.uiLine};
19
- cursor: pointer;
18
+ pointer-events: ${(p) => (p.clickable ? "auto" : "none")};
19
+ cursor: ${(p) => (p.clickable ? "pointer" : "default")};
20
+ :hover {
21
+ background-color: ${(p) => (p.clickable ? p.theme.color.overlayHoverPrimary : "transparent")};
22
+ }
20
23
  `;
21
24
 
22
- const Header = styled.div`
25
+ const Header = styled.div<{ type: string }>`
23
26
  ${(p) => p.theme.textStyle.uiXS};
24
27
  color: ${(p) => p.theme.color.textLowEmphasis};
25
28
  display: flex;
@@ -28,7 +31,7 @@ const Header = styled.div`
28
31
  svg {
29
32
  margin-right: ${(p) => p.theme.spacing.xxs};
30
33
  path {
31
- fill: ${(p) => p.theme.color.error};
34
+ fill: ${(p) => (p.type === "warning" ? p.theme.color.interactive02 : p.theme.color.error)};
32
35
  }
33
36
  }
34
37
  `;
@@ -49,4 +52,14 @@ const Subtitle = styled.div`
49
52
  color: ${(p) => p.theme.color.textLowEmphasis};
50
53
  `;
51
54
 
52
- export { ActionMenu, MenuHeader, Wrapper, Header, Content, Title, Subtitle };
55
+ const Type = styled.span`
56
+ text-transform: capitalize;
57
+ `;
58
+
59
+ const Link = styled.div`
60
+ ${(p) => p.theme.textStyle.uiS};
61
+ color: ${(p) => p.theme.color.interactive01};
62
+ margin-top: ${(p) => p.theme.spacing.xxs};
63
+ `;
64
+
65
+ export { ActionMenu, MenuHeader, Wrapper, Header, Content, Title, Subtitle, Type, Link };
@@ -1,10 +1,8 @@
1
1
  import React, { memo } from "react";
2
- import { useModal } from "@ax/hooks";
3
2
  import { IconAction, SideModal, Tooltip } from "@ax/components";
4
3
 
5
4
  const AddItemButton = (props: IProps) => {
6
- const { handleClick, whiteList, isModuleArr, categories, theme } = props;
7
- const { isOpen, toggleModal } = useModal();
5
+ const { handleClick, whiteList, isModuleArr, categories, theme, isOpen, toggleModal } = props;
8
6
  const optionsType = isModuleArr ? "modules" : "components";
9
7
  const addAction = whiteList.length <= 1 ? () => handleClick(whiteList[0]) : toggleModal;
10
8
 
@@ -35,6 +33,8 @@ interface IProps {
35
33
  isModuleArr: boolean;
36
34
  categories?: any;
37
35
  theme: string;
36
+ toggleModal: () => void;
37
+ isOpen: boolean;
38
38
  }
39
39
 
40
40
  export default memo(AddItemButton);
@@ -1,8 +1,9 @@
1
1
  import React from "react";
2
- import differenceInSeconds from 'date-fns/differenceInSeconds';
2
+ import differenceInSeconds from "date-fns/differenceInSeconds";
3
3
 
4
4
  import { IModule } from "@ax/types";
5
- import { ComponentContainer } from "@ax/components";
5
+ import { ComponentContainer, SideModal } from "@ax/components";
6
+ import { useModal } from "@ax/hooks";
6
7
 
7
8
  import { getComponentProps, containerToComponentArray, getTypefromKey } from "../helpers";
8
9
  import AddItemButton from "./AddItemButton";
@@ -45,18 +46,23 @@ const MixableComponentArray = (props: IMixableComponentArrayProps): JSX.Element
45
46
 
46
47
  const type = getTypefromKey(objKey);
47
48
  const { contentType = type } = field;
49
+ const { isOpen, toggleModal } = useModal();
48
50
 
49
51
  let addModuleAction: any;
50
52
  let addComponentAction: any;
51
-
52
- // fix for old not array values
53
- const fixedValue = Array.isArray(value) ? value : containerToComponentArray(value);
53
+ let deleteModuleAction: any;
54
+ let replaceElementsInCollectionAction: any;
54
55
 
55
56
  if (actions) {
56
57
  addModuleAction = actions.addModuleAction;
57
58
  addComponentAction = actions.addComponentAction;
59
+ deleteModuleAction = actions.deleteModuleAction;
60
+ replaceElementsInCollectionAction = actions.replaceElementsInCollectionAction;
58
61
  }
59
62
 
63
+ // fix for old not array values
64
+ const fixedValue = Array.isArray(value) ? value : containerToComponentArray(value);
65
+
60
66
  const getText = (name: string, index: number) => {
61
67
  return fixedValue.length > 1 ? `#${index + 1} ${name}` : name;
62
68
  };
@@ -70,6 +76,17 @@ const MixableComponentArray = (props: IMixableComponentArrayProps): JSX.Element
70
76
 
71
77
  const handleAdd = isModuleArr ? handleAddModule : handleAddComponent;
72
78
 
79
+ const handleModuleReplace = (moduleType: string) => {
80
+ const { modules } = selectedContent;
81
+ if (isModuleArr) {
82
+ const currentModule = modules[0];
83
+ deleteModuleAction(currentModule?.editorID, contentType);
84
+ handleAddModule(moduleType);
85
+ } else {
86
+ replaceElementsInCollectionAction(moduleType);
87
+ }
88
+ };
89
+
73
90
  const showAddItemButton = (!maxItems || fixedValue.length < maxItems) && !disabled;
74
91
 
75
92
  const timeSinceModuleCopy = !!moduleCopy && differenceInSeconds(new Date(), new Date(moduleCopy.date));
@@ -81,6 +98,10 @@ const MixableComponentArray = (props: IMixableComponentArrayProps): JSX.Element
81
98
  timeSinceModuleCopy < eightHoursInSeconds &&
82
99
  (whiteList.includes(moduleCopyComponent) || isModuleCopyUnavailable);
83
100
 
101
+ const canReplace = maxItems === 1 && whiteList.length > 1;
102
+ const displayReplaceSideModal = value.length > 0 && canReplace;
103
+ const optionsType = isModuleArr ? "modules" : "components";
104
+
84
105
  const Asterisk = () => (mandatory ? <S.Asterisk>*</S.Asterisk> : null);
85
106
 
86
107
  return (
@@ -90,26 +111,26 @@ const MixableComponentArray = (props: IMixableComponentArrayProps): JSX.Element
90
111
  </S.Title>
91
112
  <S.ItemRow>
92
113
  <S.Subtitle>{fixedValue && fixedValue.length} items</S.Subtitle>
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>
114
+ {showPasteModuleButton && (
115
+ <PasteModuleButton
116
+ editorID={editorID}
117
+ isModuleCopyUnavailable={isModuleCopyUnavailable}
118
+ pasteModule={actions.pasteModuleAction}
119
+ setNotification={actions.setNotificationAction}
120
+ setHistoryPush={setHistoryPush}
121
+ />
122
+ )}
123
+ {showAddItemButton && !disabled && (
124
+ <AddItemButton
125
+ isOpen={isOpen}
126
+ toggleModal={toggleModal}
127
+ whiteList={whiteList}
128
+ categories={categories}
129
+ handleClick={handleAdd}
130
+ isModuleArr={isModuleArr}
131
+ theme={theme}
132
+ />
133
+ )}
113
134
  </S.ItemRow>
114
135
  {fixedValue &&
115
136
  fixedValue.map((element: any, i: number) => {
@@ -122,6 +143,8 @@ const MixableComponentArray = (props: IMixableComponentArrayProps): JSX.Element
122
143
  const text = getText(componentTitle || displayName, i);
123
144
  return (
124
145
  <ComponentContainer
146
+ actionReplace={toggleModal}
147
+ canReplace={canReplace}
125
148
  isArray={true}
126
149
  arrayLength={fixedValue.length}
127
150
  index={i}
@@ -141,6 +164,18 @@ const MixableComponentArray = (props: IMixableComponentArrayProps): JSX.Element
141
164
  />
142
165
  );
143
166
  })}
167
+ {displayReplaceSideModal && isOpen && (
168
+ <SideModal
169
+ optionsType={optionsType}
170
+ whiteList={whiteList}
171
+ categories={categories}
172
+ toggleModal={toggleModal}
173
+ isOpen={isOpen}
174
+ handleClick={handleModuleReplace}
175
+ theme={theme}
176
+ showSearch
177
+ />
178
+ )}
144
179
  </S.Wrapper>
145
180
  );
146
181
  };
@@ -28,6 +28,8 @@ const ComponentContainer = (props: IComponentContainerProps): JSX.Element => {
28
28
  canDuplicate,
29
29
  parentKey,
30
30
  theme,
31
+ canReplace,
32
+ actionReplace,
31
33
  } = props;
32
34
 
33
35
  const { isVisible, toggleToast, setIsVisible } = useToast();
@@ -46,7 +48,9 @@ const ComponentContainer = (props: IComponentContainerProps): JSX.Element => {
46
48
 
47
49
  const whiteListFirstItem: any = whiteList && whiteList[0];
48
50
  const hasMultipleOptions: boolean | undefined = whiteList && whiteList.length > 1;
49
- const containerOptions: any = objKey && selectedContent[objKey];
51
+
52
+ const containerOptions: any =
53
+ parentKey && objKey ? selectedContent[parentKey][objKey] : objKey && selectedContent[objKey];
50
54
 
51
55
  let containerText: string = text ? text : getDisplayName(whiteListFirstItem);
52
56
  let componentID: number = editorID ? editorID : containerOptions.editorID;
@@ -76,11 +80,11 @@ const ComponentContainer = (props: IComponentContainerProps): JSX.Element => {
76
80
  const copyItem = () => {
77
81
  const isCopied = copyModuleAction(editorID);
78
82
  isCopied && toggleToast();
79
- }
83
+ };
80
84
 
81
85
  const copyOpt = {
82
86
  label: "copy",
83
- icon: "duplicate",
87
+ icon: "copy",
84
88
  action: copyItem,
85
89
  };
86
90
 
@@ -96,10 +100,17 @@ const ComponentContainer = (props: IComponentContainerProps): JSX.Element => {
96
100
  action: removeItem,
97
101
  };
98
102
 
99
- const actionArrayMenuOptions = [
103
+ const replaceOpt = {
104
+ label: "replace",
105
+ icon: "change",
106
+ action: () => actionReplace && actionReplace(),
107
+ };
108
+
109
+ const actionArrayMenuOptions = [
100
110
  copyOpt,
101
111
  ...(canDuplicate ? [duplicateOpt] : []),
102
- deleteOpt
112
+ deleteOpt,
113
+ ...(canReplace ? [replaceOpt] : []),
103
114
  ];
104
115
 
105
116
  const actionMenuOptions = [
@@ -124,7 +135,8 @@ const ComponentContainer = (props: IComponentContainerProps): JSX.Element => {
124
135
 
125
136
  const handleOptionClick = (option: any) => actions.addComponentAction(option);
126
137
 
127
- const handleReplace = (option: any) => actions.replaceModuleAction(option, selectedContent, objKey);
138
+ const compoundKey = parentKey ? `${parentKey}.${objKey}` : objKey;
139
+ const handleReplace = (option: any) => actions.replaceModuleAction(option, selectedContent, compoundKey);
128
140
 
129
141
  const arrayContainerButtonsProps = {
130
142
  handleDownClick,
@@ -178,7 +190,7 @@ const ComponentContainer = (props: IComponentContainerProps): JSX.Element => {
178
190
  theme={theme}
179
191
  />
180
192
  )}
181
- {isVisible && <Toast message="1 module copied to clipboard" setIsVisible={setIsVisible} />}
193
+ {isVisible && <Toast message="1 module copied to clipboard" setIsVisible={setIsVisible} />}
182
194
  </>
183
195
  );
184
196
  };
@@ -202,6 +214,8 @@ interface IComponentContainerProps {
202
214
  canDuplicate?: boolean;
203
215
  parentKey?: string;
204
216
  theme: string;
217
+ canReplace?: boolean;
218
+ actionReplace?: () => void;
205
219
  }
206
220
 
207
221
  export default ComponentContainer;
@@ -11,7 +11,7 @@ const ConditionalField = (props: IConditionalFieldProps) => {
11
11
  return (
12
12
  <S.Wrapper>
13
13
  <RadioGroup name="radio" value={value} options={options} onChange={handleChange} />
14
- <S.Content>
14
+ <S.Content data-testid="conditionalFieldContent">
15
15
  {innerFields &&
16
16
  innerFields.map((item: any) => {
17
17
  return value === item.props.field.condition && item;