@griddo/ax 1.66.13 → 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.
- package/package.json +2 -2
- package/src/__tests__/components/Fields/ConditionalField/ConditionalField.test.tsx +95 -0
- package/src/api/pages.tsx +15 -3
- package/src/api/redirects.tsx +4 -2
- package/src/api/sites.tsx +12 -4
- package/src/components/Browser/index.tsx +9 -22
- package/src/components/Browser/style.tsx +1 -6
- package/src/components/ErrorCenter/index.tsx +8 -5
- package/src/components/ErrorCenter/style.tsx +21 -8
- package/src/components/Fields/ComponentArray/MixableComponentArray/AddItemButton/index.tsx +3 -3
- package/src/components/Fields/ComponentArray/MixableComponentArray/index.tsx +60 -25
- package/src/components/Fields/ComponentContainer/index.tsx +21 -7
- package/src/components/Fields/ConditionalField/index.tsx +1 -1
- package/src/components/Fields/LinkField/index.tsx +111 -0
- package/src/components/Fields/ReferenceField/ItemList/index.tsx +4 -0
- package/src/components/Fields/ReferenceField/ManualPanel/index.tsx +12 -2
- package/src/components/Fields/ReferenceField/index.tsx +24 -12
- package/src/components/Fields/ReferenceField/style.tsx +12 -1
- package/src/components/Fields/UrlField/index.tsx +13 -1
- package/src/components/Fields/VisualUniqueSelection/utils.tsx +1 -6
- package/src/components/Fields/index.tsx +2 -0
- package/src/components/FieldsBehavior/index.tsx +14 -1
- package/src/components/Icon/components/Copy.js +14 -0
- package/src/components/Icon/components/Copy2.js +14 -0
- package/src/components/Icon/components/Duplicate.js +3 -5
- package/src/components/Icon/components/Page.js +12 -0
- package/src/components/Icon/svgs/Copy.svg +3 -0
- package/src/components/Icon/svgs/Copy2.svg +3 -0
- package/src/components/Icon/svgs/Duplicate.svg +1 -1
- package/src/components/Icon/svgs/page.svg +3 -0
- package/src/components/MainWrapper/AppBar/index.tsx +21 -10
- package/src/components/MainWrapper/AppBar/style.tsx +11 -3
- package/src/components/MainWrapper/index.tsx +2 -0
- package/src/components/Notification/index.tsx +1 -3
- package/src/components/SearchField/index.tsx +37 -4
- package/src/components/SearchField/style.tsx +23 -10
- package/src/components/index.tsx +2 -0
- package/src/containers/Navigation/Defaults/actions.tsx +2 -0
- package/src/containers/PageEditor/actions.tsx +92 -17
- package/src/containers/PageEditor/utils.tsx +2 -1
- package/src/containers/Sites/actions.tsx +53 -24
- package/src/containers/Sites/constants.tsx +2 -0
- package/src/containers/Sites/interfaces.tsx +12 -5
- package/src/containers/Sites/reducer.tsx +8 -0
- package/src/containers/StructuredData/actions.tsx +5 -8
- package/src/forms/index.tsx +9 -1
- package/src/forms/validators.tsx +119 -12
- package/src/helpers/index.tsx +2 -0
- package/src/helpers/objects.tsx +10 -2
- package/src/modules/Categories/CategoriesList/CategoryItem/index.tsx +3 -1
- package/src/modules/Categories/CategoriesList/CategoryPanel/index.tsx +15 -9
- package/src/modules/Categories/CategoriesList/index.tsx +2 -1
- package/src/modules/Content/PageItem/index.tsx +52 -2
- package/src/modules/Content/atoms.tsx +41 -3
- package/src/modules/Content/index.tsx +44 -2
- package/src/modules/Content/style.tsx +8 -1
- package/src/modules/FramePreview/index.tsx +85 -0
- package/src/modules/FramePreview/style.tsx +18 -0
- package/src/modules/GlobalEditor/Editor/index.tsx +3 -1
- package/src/modules/GlobalEditor/PageBrowser/index.tsx +3 -0
- package/src/modules/GlobalEditor/index.tsx +22 -6
- package/src/modules/PageEditor/Editor/index.tsx +5 -1
- package/src/modules/PageEditor/PageBrowser/index.tsx +4 -5
- package/src/modules/PageEditor/index.tsx +27 -9
- package/src/modules/Redirects/index.tsx +40 -10
- package/src/modules/Settings/Globals/index.tsx +1 -1
- package/src/modules/Sites/index.tsx +2 -2
- package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/index.tsx +1 -1
- package/src/modules/StructuredData/StructuredDataList/index.tsx +19 -2
- package/src/modules/Users/Profile/index.tsx +3 -3
- package/src/routes/multisite.tsx +12 -4
- package/src/routes/site.tsx +1 -1
- package/src/types/index.tsx +13 -4
- 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.
|
|
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": "
|
|
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
|
|
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 =
|
|
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
|
};
|
package/src/api/redirects.tsx
CHANGED
|
@@ -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 (
|
|
105
|
-
|
|
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}
|
|
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 {
|
|
84
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
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=
|
|
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
|
|
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}
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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: "
|
|
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
|
|
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
|
|
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
|
-
|
|
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;
|