@griddo/ax 1.75.158 → 1.75.159
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/public/manifest.json +2 -2
- package/src/__tests__/components/ConfigPanel/ConfigPanel.test.tsx +5 -0
- package/src/__tests__/components/ConfigPanel/NavigationForm/Field/Field.test.tsx +111 -11
- package/src/__tests__/components/ConfigPanel/NavigationForm/NavigationForm.test.tsx +8 -1
- package/src/__tests__/components/SideModal/SideModal.test.tsx +0 -17
- package/src/components/Browser/index.tsx +1 -1
- package/src/components/ConfigPanel/NavigationForm/Field/index.tsx +76 -23
- package/src/components/SideModal/SideModalOption/index.tsx +10 -1
- package/src/components/SideModal/SideModalOption/style.tsx +18 -12
- package/src/components/SideModal/index.tsx +2 -15
- package/src/components/SideModal/style.tsx +0 -5
- package/src/containers/Navigation/Defaults/actions.tsx +28 -6
- package/src/containers/Navigation/Defaults/index.tsx +2 -5
- package/src/containers/Navigation/Defaults/utils.tsx +43 -1
- package/src/containers/PageEditor/actions.tsx +21 -5
- package/src/containers/PageEditor/utils.tsx +12 -5
- package/src/containers/Settings/DataPacks/actions.tsx +3 -2
- package/src/forms/errors.tsx +1 -1
- package/src/forms/validators.tsx +5 -0
- package/src/hooks/forms.tsx +2 -2
- package/src/modules/Navigation/Defaults/DefaultsEditor/Editor/DefaultsBrowser/index.tsx +1 -1
- package/src/modules/Navigation/Defaults/DefaultsEditor/Editor/index.tsx +1 -1
- package/src/modules/Navigation/Defaults/DefaultsEditor/index.tsx +5 -8
- package/src/modules/PageEditor/index.tsx +9 -1
- package/src/modules/Settings/ContentTypes/DataPacks/Config/Form/TemplateConfig/TemplateEditor/Editor/ConfigPanel/Field/index.tsx +24 -14
- package/src/modules/Settings/ContentTypes/DataPacks/Config/Form/TemplateConfig/TemplateEditor/Editor/index.tsx +2 -15
- package/src/modules/Settings/ContentTypes/DataPacks/Config/Form/TemplateConfig/TemplateEditor/index.tsx +6 -4
- package/src/modules/Navigation/Defaults/DefaultsEditor/utils.tsx +0 -37
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@griddo/ax",
|
|
3
3
|
"description": "Griddo Author Experience",
|
|
4
|
-
"version": "1.75.
|
|
4
|
+
"version": "1.75.159",
|
|
5
5
|
"authors": [
|
|
6
6
|
"Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
|
|
7
7
|
"Carlos Torres <carlos.torres@secuoyas.com>",
|
|
@@ -230,5 +230,5 @@
|
|
|
230
230
|
"publishConfig": {
|
|
231
231
|
"access": "public"
|
|
232
232
|
},
|
|
233
|
-
"gitHead": "
|
|
233
|
+
"gitHead": "6063ff6bcdbd5673c1eba467d6f0898538069ae4"
|
|
234
234
|
}
|
package/public/manifest.json
CHANGED
|
@@ -22,12 +22,11 @@ const initialStore = {
|
|
|
22
22
|
pageEditor: {
|
|
23
23
|
selectedContent: {
|
|
24
24
|
editorID: 0,
|
|
25
|
-
title: "
|
|
26
|
-
id:
|
|
27
|
-
component: "
|
|
25
|
+
title: "Header name azul",
|
|
26
|
+
id: 144,
|
|
27
|
+
component: "header",
|
|
28
28
|
},
|
|
29
29
|
editorContent: {
|
|
30
|
-
module: 0,
|
|
31
30
|
editorID: 0,
|
|
32
31
|
parentEditorID: null,
|
|
33
32
|
setAsHome: false,
|
|
@@ -38,11 +37,89 @@ const initialStore = {
|
|
|
38
37
|
navigation: {
|
|
39
38
|
currentDefaultsContent: [
|
|
40
39
|
{
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
component: "Header",
|
|
41
|
+
type: "header",
|
|
42
|
+
title: "Header name azul",
|
|
43
|
+
setAsDefault: true,
|
|
44
|
+
id: 144,
|
|
45
|
+
isDefault: true,
|
|
46
|
+
thumbnail: {
|
|
47
|
+
id: 1255,
|
|
48
|
+
name: "navigation-thumbnail.png",
|
|
49
|
+
title: "Thumbnail for Header name azul",
|
|
50
|
+
description: "",
|
|
51
|
+
alt: "",
|
|
52
|
+
tags: [],
|
|
53
|
+
url: "https://images.dev.griddo.io/navigation-thumbnail_186",
|
|
54
|
+
thumb: "https://images.dev.griddo.io/w/215/h/161/navigation-thumbnail_186",
|
|
55
|
+
publicId: "thesaurus-dev/navigation-thumbnail_f1622a50-f703-41ae-b5ed-6a5b0212bb4b",
|
|
56
|
+
damId: "navigation-thumbnail_186",
|
|
57
|
+
published: "2023-01-10T10:10:55.732Z",
|
|
58
|
+
size: 4365,
|
|
59
|
+
width: 526,
|
|
60
|
+
height: 120,
|
|
61
|
+
orientation: "L",
|
|
62
|
+
site: 81,
|
|
63
|
+
},
|
|
64
|
+
site: 81,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
component: "Header",
|
|
68
|
+
type: "header",
|
|
69
|
+
title: "Secondary Header",
|
|
70
|
+
setAsDefault: false,
|
|
71
|
+
id: 165,
|
|
72
|
+
isDefault: false,
|
|
73
|
+
thumbnail: {
|
|
74
|
+
id: 922,
|
|
75
|
+
name: ".png",
|
|
76
|
+
title: "Thumbnail for Secondary Header",
|
|
77
|
+
description: "",
|
|
78
|
+
alt: "",
|
|
79
|
+
tags: [],
|
|
80
|
+
url: "https://images.dev.griddo.io/navigation-thumbnail_28",
|
|
81
|
+
thumb: "https://images.dev.griddo.io/w/215/h/161/navigation-thumbnail_28",
|
|
82
|
+
publicId: "thesaurus-dev/navigation-thumbnail_f73c78ec-af02-47fb-82aa-0727c44e47e3",
|
|
83
|
+
damId: "navigation-thumbnail_28",
|
|
84
|
+
published: "2022-06-08T14:07:11.022Z",
|
|
85
|
+
size: 3150,
|
|
86
|
+
width: 528,
|
|
87
|
+
height: 92,
|
|
88
|
+
orientation: "L",
|
|
89
|
+
site: 81,
|
|
90
|
+
},
|
|
91
|
+
site: 81,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
component: "Footer",
|
|
95
|
+
type: "footer",
|
|
96
|
+
title: "Footer name",
|
|
97
|
+
setAsDefault: true,
|
|
98
|
+
id: 65,
|
|
99
|
+
isDefault: true,
|
|
100
|
+
site: 81,
|
|
101
|
+
thumbnail: null,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
component: "Footer",
|
|
105
|
+
type: "footer",
|
|
106
|
+
title: "Footer name 2",
|
|
107
|
+
setAsDefault: false,
|
|
108
|
+
id: 83,
|
|
109
|
+
site: 81,
|
|
110
|
+
thumbnail: null,
|
|
111
|
+
isDefault: false,
|
|
43
112
|
},
|
|
44
113
|
],
|
|
45
114
|
},
|
|
115
|
+
dataPacks: {
|
|
116
|
+
configFormData: {
|
|
117
|
+
defaultParent: null,
|
|
118
|
+
indexDefault: true,
|
|
119
|
+
modifiableOnPage: false,
|
|
120
|
+
templates: {},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
46
123
|
};
|
|
47
124
|
|
|
48
125
|
const store = mockStore(initialStore);
|
|
@@ -51,12 +128,11 @@ const defaultFieldProps = mock<IField>();
|
|
|
51
128
|
const defaultStateProps = mock<IStateProps>();
|
|
52
129
|
const defaultDispatchProps = mock<IDispatchProps>();
|
|
53
130
|
|
|
54
|
-
defaultFieldProps.type = "module";
|
|
55
|
-
|
|
56
131
|
const defaultProps = { ...defaultFieldProps, ...defaultStateProps, ...defaultDispatchProps };
|
|
57
132
|
|
|
58
133
|
describe("Field component rendering", () => {
|
|
59
134
|
it("should render component", () => {
|
|
135
|
+
defaultProps.type = "header";
|
|
60
136
|
render(
|
|
61
137
|
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
62
138
|
<Field {...defaultProps} />
|
|
@@ -64,11 +140,32 @@ describe("Field component rendering", () => {
|
|
|
64
140
|
{ store }
|
|
65
141
|
);
|
|
66
142
|
|
|
67
|
-
const componentWrapper = screen.getByText(/
|
|
143
|
+
const componentWrapper = screen.getByText(/Header name azul/i);
|
|
68
144
|
expect(componentWrapper).toBeInTheDocument();
|
|
69
145
|
});
|
|
70
146
|
|
|
71
|
-
it("should render SideModal because
|
|
147
|
+
it("should render SideModal with 1 header options because 1 is selected", () => {
|
|
148
|
+
defaultProps.type = "header";
|
|
149
|
+
render(
|
|
150
|
+
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
151
|
+
<Field {...defaultProps} />
|
|
152
|
+
</ThemeProvider>,
|
|
153
|
+
{ store }
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const moreInfo = screen.getByTestId("more-info-button");
|
|
157
|
+
fireEvent.click(moreInfo);
|
|
158
|
+
const listItem = screen.getAllByTestId("action-menu-item");
|
|
159
|
+
expect(listItem).toHaveLength(2);
|
|
160
|
+
fireEvent.click(listItem[0]);
|
|
161
|
+
const sideModalWrapper = screen.getByTestId("side-modal");
|
|
162
|
+
expect(sideModalWrapper).toBeTruthy();
|
|
163
|
+
const sideModalOptions = screen.getAllByTestId("side-modal-option");
|
|
164
|
+
expect(sideModalOptions).toHaveLength(1);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should render SideModal with 2 footer option because none is selected", () => {
|
|
168
|
+
defaultProps.type = "footer";
|
|
72
169
|
render(
|
|
73
170
|
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
74
171
|
<Field {...defaultProps} />
|
|
@@ -83,9 +180,12 @@ describe("Field component rendering", () => {
|
|
|
83
180
|
fireEvent.click(listItem[0]);
|
|
84
181
|
const sideModalWrapper = screen.getByTestId("side-modal");
|
|
85
182
|
expect(sideModalWrapper).toBeTruthy();
|
|
183
|
+
const sideModalOptions = screen.getAllByTestId("side-modal-option");
|
|
184
|
+
expect(sideModalOptions).toHaveLength(2);
|
|
86
185
|
});
|
|
87
186
|
|
|
88
187
|
it("should trigger the handleRemove action", () => {
|
|
188
|
+
defaultProps.type = "header";
|
|
89
189
|
render(
|
|
90
190
|
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
91
191
|
<Field {...defaultProps} />
|
|
@@ -99,7 +199,7 @@ describe("Field component rendering", () => {
|
|
|
99
199
|
expect(listItem).toHaveLength(2);
|
|
100
200
|
fireEvent.click(listItem[1]);
|
|
101
201
|
expect(store.getActions()).toContainEqual({
|
|
102
|
-
payload: { editorContent: initialStore.pageEditor.editorContent },
|
|
202
|
+
payload: { editorContent: { ...initialStore.pageEditor.editorContent, header: 0 } },
|
|
103
203
|
type: "pageEditor/SET_EDITOR_CONTENT",
|
|
104
204
|
});
|
|
105
205
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
|
|
3
3
|
import { ThemeProvider } from "styled-components";
|
|
4
|
-
import { render, cleanup, screen
|
|
4
|
+
import { render, cleanup, screen } from "../../../../../config/jest/test-utils";
|
|
5
5
|
import { mock } from "jest-mock-extended";
|
|
6
6
|
import configureStore from "redux-mock-store";
|
|
7
7
|
import "@testing-library/jest-dom";
|
|
@@ -43,6 +43,13 @@ const initialStore = {
|
|
|
43
43
|
},
|
|
44
44
|
],
|
|
45
45
|
},
|
|
46
|
+
dataPacks: {
|
|
47
|
+
configFormData: {
|
|
48
|
+
defaultParent: null,
|
|
49
|
+
indexDefault: true,
|
|
50
|
+
modifiableOnPage: false,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
46
53
|
};
|
|
47
54
|
|
|
48
55
|
const store = mockStore(initialStore);
|
|
@@ -85,24 +85,7 @@ describe("SideModal component rendering", () => {
|
|
|
85
85
|
expect(closeButton).toBeTruthy();
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
-
it("should render default check if setDefault prop exists", () => {
|
|
89
|
-
defaultProps.setDefault = {
|
|
90
|
-
action: jest.fn(),
|
|
91
|
-
checked: false,
|
|
92
|
-
title: "Set the default header",
|
|
93
|
-
};
|
|
94
|
-
render(
|
|
95
|
-
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
96
|
-
<SideModal {...defaultProps} />
|
|
97
|
-
</ThemeProvider>
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
const checkfield = screen.getByTestId("check-field-wrapper");
|
|
101
|
-
expect(checkfield).toBeTruthy();
|
|
102
|
-
});
|
|
103
|
-
|
|
104
88
|
it("should render options", () => {
|
|
105
|
-
defaultProps.setDefault = undefined;
|
|
106
89
|
render(
|
|
107
90
|
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
108
91
|
<SideModal {...defaultProps} />
|
|
@@ -188,7 +188,7 @@ export interface IBrowserProps {
|
|
|
188
188
|
siteID?: number;
|
|
189
189
|
isPreview?: boolean;
|
|
190
190
|
showIframe?: boolean;
|
|
191
|
-
browserRef?:
|
|
191
|
+
browserRef?: React.RefObject<HTMLDivElement>;
|
|
192
192
|
actions?: {
|
|
193
193
|
setSelectedContentAction: any;
|
|
194
194
|
deleteModuleAction(editorID: number): void;
|
|
@@ -8,10 +8,31 @@ import { IRootState } from "@ax/types";
|
|
|
8
8
|
import * as S from "./style";
|
|
9
9
|
|
|
10
10
|
const Field = (props: IProps) => {
|
|
11
|
-
const {
|
|
11
|
+
const {
|
|
12
|
+
type,
|
|
13
|
+
defaults,
|
|
14
|
+
updateEditorContent,
|
|
15
|
+
selectedContent,
|
|
16
|
+
theme,
|
|
17
|
+
removeNavigationFromPage,
|
|
18
|
+
template,
|
|
19
|
+
configFormData,
|
|
20
|
+
} = props;
|
|
12
21
|
|
|
13
22
|
const { isOpen, toggleModal } = useModal();
|
|
14
|
-
|
|
23
|
+
|
|
24
|
+
const defaultSiteHeader = defaults.find(
|
|
25
|
+
(component: any) => component.setAsDefault && component.type === "header"
|
|
26
|
+
)?.id;
|
|
27
|
+
const defaultSiteFooter = defaults.find(
|
|
28
|
+
(component: any) => component.setAsDefault && component.type === "footer"
|
|
29
|
+
)?.id;
|
|
30
|
+
|
|
31
|
+
const { defaultHeader: defaultTemplateHeader, defaultFooter: defaultTemplateFooter } =
|
|
32
|
+
(configFormData.templates && configFormData.templates[template]) || {};
|
|
33
|
+
|
|
34
|
+
const defaultOption =
|
|
35
|
+
type === "header" ? defaultTemplateHeader || defaultSiteHeader : defaultTemplateFooter || defaultSiteFooter;
|
|
15
36
|
|
|
16
37
|
const options = defaults.filter((component: any) => {
|
|
17
38
|
const isCurrentType = component.type === type;
|
|
@@ -19,34 +40,63 @@ const Field = (props: IProps) => {
|
|
|
19
40
|
return isCurrentType && isNotSelected;
|
|
20
41
|
});
|
|
21
42
|
|
|
22
|
-
const
|
|
43
|
+
const templateDefaultOption = options
|
|
44
|
+
.filter(
|
|
45
|
+
(option: any) =>
|
|
46
|
+
(type === "header" && option.id === defaultTemplateHeader) ||
|
|
47
|
+
(type === "footer" && option.id === defaultTemplateFooter)
|
|
48
|
+
)
|
|
49
|
+
.map((option: any) =>
|
|
50
|
+
option.id === defaultOption
|
|
51
|
+
? { ...option, isDefault: true, tag: "template default" }
|
|
52
|
+
: { ...option, isDefault: false, tag: "template default" }
|
|
53
|
+
)?.[0];
|
|
54
|
+
|
|
55
|
+
const siteDefaultOption = options
|
|
56
|
+
.filter(
|
|
57
|
+
(option: any) =>
|
|
58
|
+
(type === "header" && option.id === defaultSiteHeader) || (type === "footer" && option.id === defaultSiteFooter)
|
|
59
|
+
)
|
|
60
|
+
.map((option: any) => {
|
|
61
|
+
const templateHasDefaultSiteHeader = type === "header" && defaultTemplateHeader === null;
|
|
62
|
+
const templateHasDefaultSiteFooter = type === "footer" && defaultTemplateFooter === null;
|
|
63
|
+
|
|
64
|
+
const tag = templateHasDefaultSiteHeader || templateHasDefaultSiteFooter ? "template default" : "site default";
|
|
65
|
+
|
|
66
|
+
const hasDefaultTemplateHeader = type === "header" && defaultTemplateHeader;
|
|
67
|
+
const hasDefaultTemplateFooter = type === "footer" && defaultTemplateFooter;
|
|
68
|
+
|
|
69
|
+
return option.id === defaultOption
|
|
70
|
+
? {
|
|
71
|
+
...option,
|
|
72
|
+
isDefault: true,
|
|
73
|
+
tag: hasDefaultTemplateHeader || hasDefaultTemplateFooter ? "" : tag,
|
|
74
|
+
}
|
|
75
|
+
: {
|
|
76
|
+
...option,
|
|
77
|
+
isDefault: false,
|
|
78
|
+
tag: hasDefaultTemplateHeader || hasDefaultTemplateFooter ? "" : tag,
|
|
79
|
+
};
|
|
80
|
+
})?.[0];
|
|
81
|
+
|
|
82
|
+
const otherOptions = options.filter(
|
|
83
|
+
(option: any) => option.id !== templateDefaultOption?.id && option.id !== siteDefaultOption?.id
|
|
84
|
+
);
|
|
23
85
|
|
|
24
|
-
let
|
|
25
|
-
|
|
26
|
-
filteredByDefaultOptions = options.filter((component: any) => !component.setAsDefault);
|
|
27
|
-
}
|
|
86
|
+
let orderedOptions = siteDefaultOption ? [siteDefaultOption, ...otherOptions] : otherOptions;
|
|
87
|
+
orderedOptions = templateDefaultOption ? [templateDefaultOption, ...orderedOptions] : orderedOptions;
|
|
28
88
|
|
|
89
|
+
const hasMultipleOptions: boolean | undefined = options && options.length > 0;
|
|
29
90
|
const optionsType = `${type}s`;
|
|
30
91
|
const actionMenuIcon = "more";
|
|
31
92
|
const pageEditorID = 0;
|
|
32
93
|
|
|
33
94
|
const handleReplace = (option: any) => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const handleRemove = () => {
|
|
38
|
-
removeNavigationFromPage(type);
|
|
95
|
+
const optionID = option.isDefault ? null : option.id;
|
|
96
|
+
updateEditorContent(pageEditorID, type, optionID);
|
|
39
97
|
};
|
|
40
98
|
|
|
41
|
-
const
|
|
42
|
-
title: `Set the default ${type}`,
|
|
43
|
-
action: () => {
|
|
44
|
-
const option = defaults.find((component: any) => component.type === type && component.setAsDefault);
|
|
45
|
-
handleReplace(option);
|
|
46
|
-
toggleModal();
|
|
47
|
-
},
|
|
48
|
-
checked: selectedContent.setAsDefault,
|
|
49
|
-
};
|
|
99
|
+
const handleRemove = () => removeNavigationFromPage(type);
|
|
50
100
|
|
|
51
101
|
const actionMenuOptions = [
|
|
52
102
|
{
|
|
@@ -75,11 +125,10 @@ const Field = (props: IProps) => {
|
|
|
75
125
|
{hasMultipleOptions && (
|
|
76
126
|
<SideModal
|
|
77
127
|
optionsType={optionsType}
|
|
78
|
-
whiteList={
|
|
128
|
+
whiteList={orderedOptions}
|
|
79
129
|
handleClick={handleReplace}
|
|
80
130
|
toggleModal={toggleModal}
|
|
81
131
|
isOpen={isOpen}
|
|
82
|
-
setDefault={setDefault}
|
|
83
132
|
theme={theme}
|
|
84
133
|
/>
|
|
85
134
|
)}
|
|
@@ -95,6 +144,8 @@ export interface IField {
|
|
|
95
144
|
export interface IStateProps {
|
|
96
145
|
defaults: any;
|
|
97
146
|
selectedContent: any;
|
|
147
|
+
template: string;
|
|
148
|
+
configFormData: any;
|
|
98
149
|
}
|
|
99
150
|
|
|
100
151
|
export interface IDispatchProps {
|
|
@@ -107,6 +158,8 @@ type IProps = IField & IStateProps & IDispatchProps;
|
|
|
107
158
|
const mapStateToProps = (state: IRootState) => ({
|
|
108
159
|
defaults: state.navigation.currentDefaultsContent,
|
|
109
160
|
selectedContent: state.pageEditor.selectedContent,
|
|
161
|
+
template: state.pageEditor.template,
|
|
162
|
+
configFormData: state.dataPacks.configFormData,
|
|
110
163
|
});
|
|
111
164
|
|
|
112
165
|
const mapDispatchToProps = {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React, { memo } from "react";
|
|
2
2
|
|
|
3
|
-
import * as S from "./style";
|
|
4
3
|
import { getDisplayName, getThumbnailProps, filterImageText } from "@ax/helpers";
|
|
4
|
+
import { Tag } from "@ax/components";
|
|
5
|
+
|
|
6
|
+
import * as S from "./style";
|
|
5
7
|
|
|
6
8
|
const getThumbnailData = (option: any, theme: string) => {
|
|
7
9
|
option = filterImageText(option.component ? option.component : option);
|
|
@@ -26,10 +28,17 @@ const SideModalOption = (props: IProps) => {
|
|
|
26
28
|
toggleModal();
|
|
27
29
|
};
|
|
28
30
|
|
|
31
|
+
const defaultTag = option.tag ? (
|
|
32
|
+
<S.TagWrapper data-testid="side-modal-option-tag">
|
|
33
|
+
<Tag text={option.tag} type="square" />
|
|
34
|
+
</S.TagWrapper>
|
|
35
|
+
) : null;
|
|
36
|
+
|
|
29
37
|
return (
|
|
30
38
|
<S.Item onClick={setOption} data-testid="side-modal-option">
|
|
31
39
|
<S.Thumbnail data-testid="side-modal-option-img" {...thumbnailProps} />
|
|
32
40
|
{label}
|
|
41
|
+
{defaultTag}
|
|
33
42
|
</S.Item>
|
|
34
43
|
);
|
|
35
44
|
};
|
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
import styled from "styled-components";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const Item = styled.li`
|
|
4
4
|
cursor: pointer;
|
|
5
|
-
padding: ${p => p.theme.spacing.xs};
|
|
6
|
-
padding-bottom: ${p => p.theme.spacing.s};
|
|
7
|
-
margin-bottom: ${p => p.theme.spacing.s};
|
|
8
|
-
box-shadow: ${p => p.theme.shadow.shadowS};
|
|
9
|
-
border-radius: ${p => p.theme.radii.s};
|
|
10
|
-
${p => p.theme.textStyle.uiS};
|
|
11
|
-
background-color: ${p => p.theme.color.interactiveBackground};
|
|
5
|
+
padding: ${(p) => p.theme.spacing.xs};
|
|
6
|
+
padding-bottom: ${(p) => p.theme.spacing.s};
|
|
7
|
+
margin-bottom: ${(p) => p.theme.spacing.s};
|
|
8
|
+
box-shadow: ${(p) => p.theme.shadow.shadowS};
|
|
9
|
+
border-radius: ${(p) => p.theme.radii.s};
|
|
10
|
+
${(p) => p.theme.textStyle.uiS};
|
|
11
|
+
background-color: ${(p) => p.theme.color.interactiveBackground};
|
|
12
12
|
&:hover {
|
|
13
|
-
background: ${p => p.theme.color.overlayHoverPrimary};
|
|
13
|
+
background: ${(p) => p.theme.color.overlayHoverPrimary};
|
|
14
14
|
}
|
|
15
15
|
&:focus {
|
|
16
|
-
background-color: ${p => p.theme.color.overlayFocusPrimary};
|
|
16
|
+
background-color: ${(p) => p.theme.color.overlayFocusPrimary};
|
|
17
17
|
}
|
|
18
18
|
`;
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
const Thumbnail = styled.img`
|
|
21
21
|
cursor: pointer;
|
|
22
|
-
padding-bottom: ${p => p.theme.spacing.s};
|
|
22
|
+
padding-bottom: ${(p) => p.theme.spacing.s};
|
|
23
23
|
`;
|
|
24
|
+
|
|
25
|
+
const TagWrapper = styled.div`
|
|
26
|
+
margin-top: ${(p) => p.theme.spacing.xs};
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
export { Item, Thumbnail, TagWrapper };
|
|
@@ -4,7 +4,7 @@ import { createPortal } from "react-dom";
|
|
|
4
4
|
import { useHandleClickOutside } from "@ax/hooks";
|
|
5
5
|
import { getDisplayName, filterByCategory } from "@ax/helpers";
|
|
6
6
|
import SideModalOption from "@ax/components/SideModal/SideModalOption";
|
|
7
|
-
import {
|
|
7
|
+
import { MenuItem, SearchField, IconAction } from "@ax/components";
|
|
8
8
|
import { ModuleCategoryInfo } from "@ax/types";
|
|
9
9
|
|
|
10
10
|
import * as S from "./style";
|
|
@@ -19,7 +19,6 @@ const SideModal = (props: ISideModalProps): JSX.Element | null => {
|
|
|
19
19
|
categories,
|
|
20
20
|
handleClick,
|
|
21
21
|
current,
|
|
22
|
-
setDefault,
|
|
23
22
|
showSearch,
|
|
24
23
|
theme,
|
|
25
24
|
} = props;
|
|
@@ -158,17 +157,6 @@ const SideModal = (props: ISideModalProps): JSX.Element | null => {
|
|
|
158
157
|
</S.ButtonWrapper>
|
|
159
158
|
)}
|
|
160
159
|
</S.Header>
|
|
161
|
-
{setDefault && !setDefault.checked && (
|
|
162
|
-
<S.CheckFieldWrapper data-testid="check-field-wrapper">
|
|
163
|
-
<CheckField
|
|
164
|
-
name="setDefault"
|
|
165
|
-
value="setDefault"
|
|
166
|
-
title={setDefault.title}
|
|
167
|
-
onChange={setDefault.action}
|
|
168
|
-
checked={setDefault.checked}
|
|
169
|
-
/>
|
|
170
|
-
</S.CheckFieldWrapper>
|
|
171
|
-
)}
|
|
172
160
|
<S.ColumnsWrapper>
|
|
173
161
|
{(filters || featuredFilters) && (
|
|
174
162
|
<S.Content>
|
|
@@ -186,14 +174,13 @@ const SideModal = (props: ISideModalProps): JSX.Element | null => {
|
|
|
186
174
|
|
|
187
175
|
export interface ISideModalProps {
|
|
188
176
|
isOpen: boolean;
|
|
189
|
-
whiteList
|
|
177
|
+
whiteList?: string[];
|
|
190
178
|
categories?: ModuleCategoryInfo[];
|
|
191
179
|
optionsType: string;
|
|
192
180
|
toggleModal: () => void;
|
|
193
181
|
handleClick?: (moduleType: string) => void;
|
|
194
182
|
componentOptions?: any;
|
|
195
183
|
current?: any;
|
|
196
|
-
setDefault?: any;
|
|
197
184
|
showSearch?: boolean;
|
|
198
185
|
theme: string;
|
|
199
186
|
}
|
|
@@ -52,10 +52,6 @@ const Content = styled.div`
|
|
|
52
52
|
|
|
53
53
|
const NavLink = styled.a``;
|
|
54
54
|
|
|
55
|
-
const CheckFieldWrapper = styled.div`
|
|
56
|
-
padding: ${(p) => p.theme.spacing.m} ${(p) => p.theme.spacing.m} 0;
|
|
57
|
-
`;
|
|
58
|
-
|
|
59
55
|
const FeaturedWrapper = styled.div`
|
|
60
56
|
border-bottom: 1px solid ${(p) => p.theme.colors.uiLine};
|
|
61
57
|
padding-bottom: ${(p) => p.theme.spacing.xs};
|
|
@@ -79,7 +75,6 @@ export {
|
|
|
79
75
|
Title,
|
|
80
76
|
ColumnsWrapper,
|
|
81
77
|
NavLink,
|
|
82
|
-
CheckFieldWrapper,
|
|
83
78
|
FeaturedWrapper,
|
|
84
79
|
SearchWrapper,
|
|
85
80
|
Link,
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
|
|
25
25
|
import { appActions } from "@ax/containers/App";
|
|
26
26
|
|
|
27
|
-
import { getFormData, getStateValues } from "./utils";
|
|
27
|
+
import { getFormData, getStateValues, getImage } from "./utils";
|
|
28
28
|
|
|
29
29
|
import {
|
|
30
30
|
SET_EDITOR_CONTENT,
|
|
@@ -221,9 +221,11 @@ function getNavigationByType(type: string): (dispatch: Dispatch, getState: any)
|
|
|
221
221
|
};
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
function createNavigation(
|
|
224
|
+
function createNavigation(navHtml?: HTMLDivElement | null): (dispatch: Dispatch, getState: any) => Promise<boolean> {
|
|
225
225
|
return async (dispatch, getState) => {
|
|
226
226
|
try {
|
|
227
|
+
dispatch(appActions.setIsSaving(true));
|
|
228
|
+
|
|
227
229
|
const {
|
|
228
230
|
navigation: { editorContent, isNewTranslation },
|
|
229
231
|
sites: { currentSiteInfo },
|
|
@@ -234,6 +236,11 @@ function createNavigation(image: File | null): (dispatch: Dispatch, getState: an
|
|
|
234
236
|
delete editorContent.id;
|
|
235
237
|
}
|
|
236
238
|
|
|
239
|
+
let image = null;
|
|
240
|
+
if (navHtml) {
|
|
241
|
+
image = await getImage(navHtml);
|
|
242
|
+
}
|
|
243
|
+
|
|
237
244
|
const navigationValues = { ...editorContent, site: currentSiteInfo.id, language: lang.id };
|
|
238
245
|
const cleanValues = removeEditorIds(navigationValues);
|
|
239
246
|
|
|
@@ -243,16 +250,20 @@ function createNavigation(image: File | null): (dispatch: Dispatch, getState: an
|
|
|
243
250
|
const updatedContent = { ...editorContent, ...response };
|
|
244
251
|
generateContent(updatedContent, dispatch, getState);
|
|
245
252
|
dispatch(setIsNewTranslation(false));
|
|
253
|
+
dispatch(appActions.setIsSaving(false));
|
|
246
254
|
};
|
|
247
255
|
|
|
248
256
|
const responseActions = {
|
|
249
257
|
handleSuccess: (response: any) => successAction(response),
|
|
250
|
-
handleError: (response: any) =>
|
|
258
|
+
handleError: (response: any) => {
|
|
259
|
+
appActions.handleError(response)(dispatch);
|
|
260
|
+
dispatch(appActions.setIsSaving(false));
|
|
261
|
+
},
|
|
251
262
|
};
|
|
252
263
|
|
|
253
264
|
const callback = async () => navigation.createNavigation(form);
|
|
254
265
|
|
|
255
|
-
return await handleRequest(callback, responseActions, [
|
|
266
|
+
return await handleRequest(callback, responseActions, [])(dispatch);
|
|
256
267
|
} catch (e) {
|
|
257
268
|
console.log(e);
|
|
258
269
|
return false;
|
|
@@ -264,15 +275,22 @@ function updateNavigation(
|
|
|
264
275
|
navID: number,
|
|
265
276
|
data: any,
|
|
266
277
|
fromEditor?: boolean,
|
|
267
|
-
|
|
278
|
+
navHtml?: HTMLDivElement | null
|
|
268
279
|
): (dispatch: Dispatch, getState: any) => Promise<boolean> {
|
|
269
280
|
return async (dispatch, getState) => {
|
|
270
281
|
try {
|
|
282
|
+
dispatch(appActions.setIsSaving(true));
|
|
283
|
+
|
|
271
284
|
const { isNewTranslation } = getStateValues(getState);
|
|
272
285
|
if (isNewTranslation) {
|
|
273
286
|
delete data.id;
|
|
274
287
|
}
|
|
275
288
|
|
|
289
|
+
let image = null;
|
|
290
|
+
if (navHtml) {
|
|
291
|
+
image = await getImage(navHtml);
|
|
292
|
+
}
|
|
293
|
+
|
|
276
294
|
const cleanValues = removeEditorIds(data);
|
|
277
295
|
|
|
278
296
|
const form = getFormData(cleanValues, image);
|
|
@@ -283,10 +301,14 @@ function updateNavigation(
|
|
|
283
301
|
} else {
|
|
284
302
|
getNavigationByType(data.type)(dispatch, getState);
|
|
285
303
|
}
|
|
304
|
+
dispatch(appActions.setIsSaving(false));
|
|
286
305
|
};
|
|
287
306
|
const responseActions = {
|
|
288
307
|
handleSuccess: (response: any) => successAction(response),
|
|
289
|
-
handleError: (response: any) =>
|
|
308
|
+
handleError: (response: any) => {
|
|
309
|
+
appActions.handleError(response)(dispatch);
|
|
310
|
+
dispatch(appActions.setIsSaving(false));
|
|
311
|
+
},
|
|
290
312
|
};
|
|
291
313
|
|
|
292
314
|
const callback = async () => navigation.updateNavigation(navID, form);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { toBlob } from "html-to-image";
|
|
2
|
+
|
|
1
3
|
const getStateValues = (getState: any) => {
|
|
2
4
|
const {
|
|
3
5
|
sites: { currentSiteInfo },
|
|
@@ -42,4 +44,44 @@ const getFormData = (values: any, image?: File | null): FormData => {
|
|
|
42
44
|
return form;
|
|
43
45
|
};
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
const getImage = async (navHtml: HTMLDivElement): Promise<File | null> => {
|
|
48
|
+
const browserContent = navHtml.querySelector<HTMLElement>(".browser-content");
|
|
49
|
+
|
|
50
|
+
if (!browserContent) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const { height, overflow } = getComputedStyle(browserContent);
|
|
55
|
+
browserContent.style.height = "auto";
|
|
56
|
+
browserContent.style.overflow = "visible";
|
|
57
|
+
const originalHeight = browserContent.clientHeight;
|
|
58
|
+
|
|
59
|
+
const elChildren = browserContent.querySelectorAll("*");
|
|
60
|
+
let maxAbsoluteElementHeight = 0;
|
|
61
|
+
[].forEach.call(elChildren, function (element: any) {
|
|
62
|
+
const isAbsolutePosition = getComputedStyle(element).position === "absolute";
|
|
63
|
+
const isMaxAbsolutePositionHeight = element.clientHeight > maxAbsoluteElementHeight;
|
|
64
|
+
if (isAbsolutePosition && isMaxAbsolutePositionHeight) {
|
|
65
|
+
maxAbsoluteElementHeight = element.clientHeight;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
if (maxAbsoluteElementHeight) {
|
|
69
|
+
const actualHeight = originalHeight + maxAbsoluteElementHeight;
|
|
70
|
+
browserContent.style.height = `${actualHeight}px`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return toBlob(browserContent, { quality: 0.95, pixelRatio: 0.5 })
|
|
74
|
+
.then((imageBlob) => {
|
|
75
|
+
return imageBlob && new File([imageBlob], "navigation-thumbnail.png", { type: "image/png" });
|
|
76
|
+
})
|
|
77
|
+
.finally(() => {
|
|
78
|
+
browserContent.style.height = height;
|
|
79
|
+
browserContent.style.overflow = overflow;
|
|
80
|
+
})
|
|
81
|
+
.catch((err) => {
|
|
82
|
+
//console.log(err);
|
|
83
|
+
return null;
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export { getStateValues, getFormData, getImage };
|
|
@@ -197,9 +197,7 @@ function setTranslatedParent(): (dispatch: Dispatch, getState: any) => void {
|
|
|
197
197
|
app: { lang },
|
|
198
198
|
pageEditor: {
|
|
199
199
|
selectedEditorID,
|
|
200
|
-
editorContent: {
|
|
201
|
-
editorContent: { parent, site, entity, language },
|
|
202
|
-
},
|
|
200
|
+
editorContent: { parent, site, entity, language },
|
|
203
201
|
},
|
|
204
202
|
} = getState();
|
|
205
203
|
|
|
@@ -227,9 +225,15 @@ function setTranslatedParent(): (dispatch: Dispatch, getState: any) => void {
|
|
|
227
225
|
function createNewTranslation(isNewTranslation: boolean): (dispatch: Dispatch, getState: any) => void {
|
|
228
226
|
return async (dispatch, getState) => {
|
|
229
227
|
try {
|
|
228
|
+
const {
|
|
229
|
+
sites: { currentSiteInfo },
|
|
230
|
+
} = getState();
|
|
231
|
+
|
|
230
232
|
dispatch(setIsNewTranslation(isNewTranslation));
|
|
231
233
|
dispatch(setCurrentPageStatus("offline"));
|
|
232
|
-
|
|
234
|
+
if (currentSiteInfo) {
|
|
235
|
+
await getSiteDefaults()(dispatch, getState);
|
|
236
|
+
}
|
|
233
237
|
} catch (e) {
|
|
234
238
|
console.log(e); // TODO: capturar error bien
|
|
235
239
|
}
|
|
@@ -385,7 +389,7 @@ function savePage(
|
|
|
385
389
|
pageEditor: {
|
|
386
390
|
selectedEditorID,
|
|
387
391
|
isNewTranslation,
|
|
388
|
-
editorContent: { header, footer },
|
|
392
|
+
editorContent: { header, footer, templateConfig, template },
|
|
389
393
|
},
|
|
390
394
|
sites: { currentSiteInfo },
|
|
391
395
|
app: { lang },
|
|
@@ -413,6 +417,18 @@ function savePage(
|
|
|
413
417
|
delete values.id;
|
|
414
418
|
}
|
|
415
419
|
|
|
420
|
+
/* remove header and footer if page should get the default */
|
|
421
|
+
const { defaultHeader, defaultFooter } =
|
|
422
|
+
(templateConfig && templateConfig.templates && templateConfig.templates[template.templateType]) || {};
|
|
423
|
+
|
|
424
|
+
if (header && ((header.setAsDefault && !defaultHeader) || header.id === defaultHeader)) {
|
|
425
|
+
values["header"] = null;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (footer && ((footer.setAsDefault && !defaultFooter) || footer.id === defaultFooter)) {
|
|
429
|
+
values["footer"] = null;
|
|
430
|
+
}
|
|
431
|
+
|
|
416
432
|
const saveResponse =
|
|
417
433
|
isNewPage || isNewTranslation || createDraft
|
|
418
434
|
? await pages.createPage(values)
|
|
@@ -70,19 +70,26 @@ const getPageNavigation = (
|
|
|
70
70
|
const headers: any = defaultsContent.filter((content: any) => content.type === "header");
|
|
71
71
|
const footers: any = defaultsContent.filter((content: any) => content.type === "footer");
|
|
72
72
|
|
|
73
|
+
const headerDefault = headers.find((content: any) => content.setAsDefault);
|
|
74
|
+
const footerDefault = footers.find((content: any) => content.setAsDefault);
|
|
75
|
+
|
|
73
76
|
const header =
|
|
74
77
|
headerID === 0
|
|
75
78
|
? headerID
|
|
76
79
|
: headerID === undefined || headerID === null
|
|
77
|
-
?
|
|
78
|
-
: headers.find((content: any) =>
|
|
80
|
+
? headerDefault
|
|
81
|
+
: headers.find((content: any) =>
|
|
82
|
+
content.navigationLanguages.some((navLang: any) => navLang.navigationId === headerID)
|
|
83
|
+
) || headerDefault;
|
|
79
84
|
|
|
80
85
|
const footer =
|
|
81
86
|
footerID === 0
|
|
82
|
-
?
|
|
87
|
+
? footerID
|
|
83
88
|
: footerID === undefined || footerID === null
|
|
84
|
-
?
|
|
85
|
-
: footers.find((content: any) =>
|
|
89
|
+
? footerDefault
|
|
90
|
+
: footers.find((content: any) =>
|
|
91
|
+
content.navigationLanguages.some((navLang: any) => navLang.navigationId === footerID)
|
|
92
|
+
) || footerDefault;
|
|
86
93
|
|
|
87
94
|
return { header, footer };
|
|
88
95
|
};
|
|
@@ -203,7 +203,7 @@ function deleteSiteDataPack(
|
|
|
203
203
|
};
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
function updateDataPack(dataPackID: string): (dispatch: Dispatch, getState: any) => Promise<
|
|
206
|
+
function updateDataPack(dataPackID: string): (dispatch: Dispatch, getState: any) => Promise<boolean> {
|
|
207
207
|
return async (dispatch, getState) => {
|
|
208
208
|
try {
|
|
209
209
|
const {
|
|
@@ -231,9 +231,10 @@ function updateDataPack(dataPackID: string): (dispatch: Dispatch, getState: any)
|
|
|
231
231
|
|
|
232
232
|
const callback = async () => dataPack.updateDataPack(currentSiteID, dataPackID, configFormData);
|
|
233
233
|
|
|
234
|
-
await handleRequest(callback, responseActions, [appActions.setIsSaving])(dispatch);
|
|
234
|
+
return await handleRequest(callback, responseActions, [appActions.setIsSaving])(dispatch);
|
|
235
235
|
} catch (e) {
|
|
236
236
|
console.log(e); // TODO: capturar error bien
|
|
237
|
+
return false;
|
|
237
238
|
}
|
|
238
239
|
};
|
|
239
240
|
}
|
package/src/forms/errors.tsx
CHANGED
|
@@ -17,7 +17,7 @@ const ERRORS: Record<string, string> = {
|
|
|
17
17
|
ERR016: "This field is still filled with mockup data.",
|
|
18
18
|
ERR017: "The URL is already in use.",
|
|
19
19
|
ERR018: "No H1 in this page.",
|
|
20
|
-
ERR019: "Not enough entries
|
|
20
|
+
ERR019: "Not enough entries published. {value} items required",
|
|
21
21
|
ERR020: "Sorry, email or password are not correct. ",
|
|
22
22
|
ERR021: "Sorry, we need some info to let you in. Check the fields below.",
|
|
23
23
|
ERR022: "Sorry, this page has not been published. We are working to solve it.",
|
package/src/forms/validators.tsx
CHANGED
|
@@ -128,6 +128,10 @@ const VALIDATORS = {
|
|
|
128
128
|
const isValid = pass1 === pass2;
|
|
129
129
|
return { isValid, errorCode: "ERR041" };
|
|
130
130
|
},
|
|
131
|
+
minItems: (val: any, len: number): IError => {
|
|
132
|
+
const isValid = val.mode === "auto" || (val.fixed && val.fixed.length >= len);
|
|
133
|
+
return { isValid, errorCode: "ERR019" };
|
|
134
|
+
},
|
|
131
135
|
};
|
|
132
136
|
|
|
133
137
|
const getErrorMessage = (key: string, val: number | string | null): string => {
|
|
@@ -269,6 +273,7 @@ const getValidationErrors = (
|
|
|
269
273
|
|
|
270
274
|
let fieldValidators: Record<string, unknown> = field.maxValue ? { maxValue: field.maxValue } : {};
|
|
271
275
|
fieldValidators = field.minValue ? { ...fieldValidators, minValue: field.minValue } : fieldValidators;
|
|
276
|
+
fieldValidators = field.minItems ? { ...fieldValidators, minItems: field.minItems } : fieldValidators;
|
|
272
277
|
|
|
273
278
|
if (hasProps(field, ["validators"]) || Object.keys(fieldValidators).length) {
|
|
274
279
|
const allValidators = { ...field.validators, ...fieldValidators };
|
package/src/hooks/forms.tsx
CHANGED
|
@@ -77,10 +77,10 @@ const useIsDirty = (
|
|
|
77
77
|
return false;
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
-
const resetDirty = () => {
|
|
80
|
+
const resetDirty = (reseting = true) => {
|
|
81
81
|
setIsDirty(false);
|
|
82
82
|
setIsSaved(true);
|
|
83
|
-
setIsResetting(true);
|
|
83
|
+
reseting && setIsResetting(true);
|
|
84
84
|
};
|
|
85
85
|
|
|
86
86
|
useEffect(() => {
|
|
@@ -79,7 +79,7 @@ interface IPageBrowserDispatchProps {
|
|
|
79
79
|
replaceModule(module: any, parent: any, objKey: string): void;
|
|
80
80
|
replaceElementsInCollection(newValue: string, reference: string): void;
|
|
81
81
|
moveModule(moduleID: number, selectedContent: any, newIndex: number, key: string): void;
|
|
82
|
-
browserRef?:
|
|
82
|
+
browserRef?: React.RefObject<HTMLDivElement>;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
type IProps = IEditorStateProps & IPageBrowserDispatchProps;
|
|
@@ -11,7 +11,6 @@ import { ErrorToast, Loading, MainWrapper, Modal } from "@ax/components";
|
|
|
11
11
|
import Editor from "./Editor";
|
|
12
12
|
|
|
13
13
|
import * as S from "./style";
|
|
14
|
-
import { getImage } from "./utils";
|
|
15
14
|
|
|
16
15
|
const DefaultsEditor = (props: IProps) => {
|
|
17
16
|
const {
|
|
@@ -41,7 +40,7 @@ const DefaultsEditor = (props: IProps) => {
|
|
|
41
40
|
const isNew = !editorContent?.id;
|
|
42
41
|
|
|
43
42
|
const isSetAsDefault = editorContent && editorContent.setAsDefault;
|
|
44
|
-
const browserRef = useRef<
|
|
43
|
+
const browserRef = useRef<HTMLDivElement>(null);
|
|
45
44
|
|
|
46
45
|
useEffect(() => {
|
|
47
46
|
getValues();
|
|
@@ -67,12 +66,10 @@ const DefaultsEditor = (props: IProps) => {
|
|
|
67
66
|
}, [currentSiteInfo, editorContent?.id]);
|
|
68
67
|
|
|
69
68
|
const save = async () => {
|
|
70
|
-
const image = await getImage(browserRef);
|
|
71
|
-
|
|
72
69
|
const isSaved =
|
|
73
70
|
isNew || isNewTranslation
|
|
74
|
-
? await createNavigation(
|
|
75
|
-
: await updateNavigation(editorContent.id, editorContent, true,
|
|
71
|
+
? await createNavigation(browserRef?.current)
|
|
72
|
+
: await updateNavigation(editorContent.id, editorContent, true, browserRef?.current);
|
|
76
73
|
if (isSaved) resetDirty();
|
|
77
74
|
};
|
|
78
75
|
|
|
@@ -204,8 +201,8 @@ interface IDispatchProps {
|
|
|
204
201
|
setHistoryPush(path: string, isEditor?: boolean): void;
|
|
205
202
|
setLanguage?(lang: { locale: string; id: number | null }): void;
|
|
206
203
|
getValues(): void;
|
|
207
|
-
createNavigation(
|
|
208
|
-
updateNavigation(navID: number, data: any, fromEditor?: boolean,
|
|
204
|
+
createNavigation(navHtml?: HTMLDivElement | null): Promise<boolean>;
|
|
205
|
+
updateNavigation(navID: number, data: any, fromEditor?: boolean, navHtml?: HTMLDivElement | null): Promise<boolean>;
|
|
209
206
|
createTranslation(isNewTranslation: boolean): void;
|
|
210
207
|
setHeader(id: number | null): void;
|
|
211
208
|
setFooter(id: number | null): void;
|
|
@@ -126,6 +126,7 @@ const PageEditor = (props: IProps) => {
|
|
|
126
126
|
|
|
127
127
|
const allPageVersions = pageLanguages.map((lang: IPageLanguage) => lang.pageId);
|
|
128
128
|
const isDeleted = deleteAllVersions ? await deleteBulk(allPageVersions) : await deletePage();
|
|
129
|
+
|
|
129
130
|
toggleDeleteModal();
|
|
130
131
|
if (isDeleted) {
|
|
131
132
|
setRoute(path);
|
|
@@ -149,7 +150,14 @@ const PageEditor = (props: IProps) => {
|
|
|
149
150
|
}
|
|
150
151
|
};
|
|
151
152
|
|
|
152
|
-
|
|
153
|
+
const handleUpdatePageStatus = async () => {
|
|
154
|
+
const isUpdated = await updatePageStatus([pageID], pageStatus.UPLOAD_PENDING);
|
|
155
|
+
if (isUpdated) {
|
|
156
|
+
resetDirty();
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
pageID ? await handleUpdatePageStatus() : await handleSavePage();
|
|
153
161
|
} else {
|
|
154
162
|
setNotification({ text: errorNotificationText, type: "error" });
|
|
155
163
|
}
|
|
@@ -19,11 +19,27 @@ const Field = (props: IField): JSX.Element => {
|
|
|
19
19
|
theme,
|
|
20
20
|
} = props;
|
|
21
21
|
const { isOpen, toggleModal } = useModal();
|
|
22
|
+
|
|
23
|
+
const optionsType = `${type}s`;
|
|
24
|
+
|
|
25
|
+
const defaultSiteHeader = defaults.find(
|
|
26
|
+
(component: any) => component.setAsDefault && component.type === "header"
|
|
27
|
+
)?.id;
|
|
28
|
+
const defaultSiteFooter = defaults.find(
|
|
29
|
+
(component: any) => component.setAsDefault && component.type === "footer"
|
|
30
|
+
)?.id;
|
|
31
|
+
|
|
32
|
+
const defaultOption = type === "header" ? defaultSiteHeader : defaultSiteFooter;
|
|
33
|
+
|
|
22
34
|
const options = defaults.filter(
|
|
23
|
-
(component: any) =>
|
|
24
|
-
|
|
35
|
+
(component: any) => component.type === type && content[component.type]?.id !== component.id
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const mappedOptions = options.map((option: any) =>
|
|
39
|
+
option.id === defaultOption
|
|
40
|
+
? { ...option, isDefault: true, tag: "site default" }
|
|
41
|
+
: { ...option, isDefault: false, tag: null }
|
|
25
42
|
);
|
|
26
|
-
const optionsType = `${type}s`;
|
|
27
43
|
|
|
28
44
|
const actionMenuOptions = [
|
|
29
45
|
{
|
|
@@ -36,19 +52,20 @@ const Field = (props: IField): JSX.Element => {
|
|
|
36
52
|
const actionMenuIcon = "more";
|
|
37
53
|
|
|
38
54
|
const handleReplace = (option: any) => {
|
|
55
|
+
const optionID = option.isDefault ? null : option.id;
|
|
39
56
|
const configFormData = {
|
|
40
57
|
...dataPackConfigFormData,
|
|
41
58
|
templates: {
|
|
42
59
|
...dataPackConfigFormData.templates,
|
|
43
60
|
[template]: {
|
|
44
61
|
...(!!dataPackConfigFormData.templates && dataPackConfigFormData.templates[template]),
|
|
45
|
-
...(type === "header" && { defaultHeader:
|
|
46
|
-
...(type === "footer" && { defaultFooter:
|
|
62
|
+
...(type === "header" && { defaultHeader: optionID }),
|
|
63
|
+
...(type === "footer" && { defaultFooter: optionID }),
|
|
47
64
|
},
|
|
48
65
|
},
|
|
49
66
|
};
|
|
50
67
|
updateDataPackFormValue(configFormData);
|
|
51
|
-
updateEditorContent(pageEditorID, type,
|
|
68
|
+
updateEditorContent(pageEditorID, type, optionID ? option : null);
|
|
52
69
|
toggleModal();
|
|
53
70
|
};
|
|
54
71
|
|
|
@@ -61,12 +78,6 @@ const Field = (props: IField): JSX.Element => {
|
|
|
61
78
|
return !!siteDefaultNavigation?.isDefault;
|
|
62
79
|
};
|
|
63
80
|
|
|
64
|
-
const setDefault = {
|
|
65
|
-
title: `Put the default ${type}`,
|
|
66
|
-
action: () => handleReplace({ id: null }),
|
|
67
|
-
checked: isDefaultNavigation(),
|
|
68
|
-
};
|
|
69
|
-
|
|
70
81
|
const hasOptions: boolean | undefined = options?.length > 0 || !isDefaultNavigation();
|
|
71
82
|
|
|
72
83
|
if (!content[type]) return <></>;
|
|
@@ -82,11 +93,10 @@ const Field = (props: IField): JSX.Element => {
|
|
|
82
93
|
{hasOptions && (
|
|
83
94
|
<SideModal
|
|
84
95
|
optionsType={optionsType}
|
|
85
|
-
whiteList={
|
|
96
|
+
whiteList={mappedOptions}
|
|
86
97
|
handleClick={handleReplace}
|
|
87
98
|
toggleModal={toggleModal}
|
|
88
99
|
isOpen={isOpen}
|
|
89
|
-
setDefault={setDefault}
|
|
90
100
|
theme={theme}
|
|
91
101
|
/>
|
|
92
102
|
)}
|
|
@@ -9,28 +9,15 @@ import TemplateBrowser from "./TemplateBrowser";
|
|
|
9
9
|
import ConfigPanel from "./ConfigPanel";
|
|
10
10
|
|
|
11
11
|
const Editor = (props: IProps) => {
|
|
12
|
-
const { isLoading, template } = props;
|
|
13
|
-
|
|
14
12
|
const theme = getDefaultTheme();
|
|
15
13
|
|
|
16
|
-
return
|
|
17
|
-
<ResizePanel
|
|
18
|
-
leftPanel={<TemplateBrowser />}
|
|
19
|
-
rightPanel={
|
|
20
|
-
<ConfigPanel
|
|
21
|
-
template={template}
|
|
22
|
-
isLoading={isLoading}
|
|
23
|
-
theme={theme}
|
|
24
|
-
/>
|
|
25
|
-
}
|
|
26
|
-
/>
|
|
27
|
-
);
|
|
14
|
+
return <ResizePanel leftPanel={<TemplateBrowser />} rightPanel={<ConfigPanel theme={theme} {...props} />} />;
|
|
28
15
|
};
|
|
29
16
|
|
|
30
17
|
type IProps = {
|
|
31
18
|
isLoading: boolean;
|
|
32
19
|
template: string;
|
|
33
|
-
}
|
|
20
|
+
};
|
|
34
21
|
|
|
35
22
|
const mapStateToProps = (state: IRootState): IProps => ({
|
|
36
23
|
isLoading: state.app.isLoading,
|
|
@@ -14,14 +14,16 @@ import * as S from "./style";
|
|
|
14
14
|
const TemplateEditor = (props: IProps) => {
|
|
15
15
|
const { isLoading, isSaving, editorContent, setHistoryPush, template, updateDataPack, dataPackSelected } = props;
|
|
16
16
|
|
|
17
|
-
const { isDirty,
|
|
17
|
+
const { isDirty, resetDirty } = useIsDirty(editorContent);
|
|
18
18
|
|
|
19
19
|
const rightButtonProps = {
|
|
20
20
|
label: !isDirty ? "Saved" : isSaving ? "Saving" : "Save",
|
|
21
21
|
disabled: isSaving || !isDirty,
|
|
22
22
|
action: async () => {
|
|
23
|
-
await updateDataPack(dataPackSelected.id);
|
|
24
|
-
|
|
23
|
+
const isSaved = await updateDataPack(dataPackSelected.id);
|
|
24
|
+
if (isSaved) {
|
|
25
|
+
resetDirty(false);
|
|
26
|
+
}
|
|
25
27
|
},
|
|
26
28
|
};
|
|
27
29
|
|
|
@@ -71,7 +73,7 @@ const mapDispatchToProps = {
|
|
|
71
73
|
|
|
72
74
|
interface IDispatchProps {
|
|
73
75
|
setHistoryPush(path: string, isEditor?: boolean): void;
|
|
74
|
-
updateDataPack: (dataPack?: any) => Promise<
|
|
76
|
+
updateDataPack: (dataPack?: any) => Promise<boolean>;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
type IProps = IStateProps & IDispatchProps;
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { toBlob } from "html-to-image";
|
|
2
|
-
|
|
3
|
-
const getImage = async (browserRef: any): Promise<File | null> => {
|
|
4
|
-
if (browserRef?.current) {
|
|
5
|
-
const browserContent = browserRef.current.querySelector(".browser-content");
|
|
6
|
-
|
|
7
|
-
const { height, overflow } = getComputedStyle(browserContent);
|
|
8
|
-
browserContent.style.height = "auto";
|
|
9
|
-
browserContent.style.overflow = "visible";
|
|
10
|
-
const originalHeight = browserContent.clientHeight;
|
|
11
|
-
|
|
12
|
-
const elChildren = browserContent.querySelectorAll("*");
|
|
13
|
-
let maxAbsoluteElementHeight = 0;
|
|
14
|
-
[].forEach.call(elChildren, function (element: any) {
|
|
15
|
-
const isAbsolutePosition = getComputedStyle(element).position === "absolute";
|
|
16
|
-
const isMaxAbsolutePositionHeight = element.clientHeight > maxAbsoluteElementHeight;
|
|
17
|
-
if (isAbsolutePosition && isMaxAbsolutePositionHeight) {
|
|
18
|
-
maxAbsoluteElementHeight = element.clientHeight;
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
if (maxAbsoluteElementHeight) {
|
|
22
|
-
const actualHeight = originalHeight + maxAbsoluteElementHeight;
|
|
23
|
-
browserContent.style.height = `${actualHeight}px`;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const imageBlob = await toBlob(browserContent, { quality: 0.95, pixelRatio: 0.5 });
|
|
27
|
-
const imageFile = imageBlob && new File([imageBlob], "navigation-thumbnail.png", { type: "image/png" });
|
|
28
|
-
|
|
29
|
-
browserContent.style.height = height;
|
|
30
|
-
browserContent.style.overflow = overflow;
|
|
31
|
-
|
|
32
|
-
return imageFile;
|
|
33
|
-
}
|
|
34
|
-
return null;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export { getImage };
|