@griddo/ax 10.4.32 → 10.4.34

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 (24) hide show
  1. package/package.json +2 -2
  2. package/src/__tests__/components/ConfigPanel/Form/ConnectedField/ConnectedField.test.tsx +1 -0
  3. package/src/__tests__/components/ConfigPanel/Form/ConnectedField/NavConnectedField/NavConnectedField.test.tsx +1 -0
  4. package/src/__tests__/components/ConfigPanel/Form/ConnectedField/PageConnectedField/PageConnectedField.test.tsx +5 -0
  5. package/src/__tests__/components/ConfigPanel/Form/ConnectedField/PageConnectedField/TemplateManager/TemplateManager.test.tsx +5 -0
  6. package/src/__tests__/components/ConfigPanel/Form/Form.test.tsx +1 -0
  7. package/src/__tests__/components/ConfigPanel/GlobalPageForm/GlobalPageForm.test.tsx +1 -0
  8. package/src/__tests__/components/ConfigPanel/Header/Header.test.tsx +6 -0
  9. package/src/components/ConfigPanel/Form/ConnectedField/NavConnectedField/index.tsx +6 -2
  10. package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/TemplateManager/index.tsx +10 -7
  11. package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/index.tsx +24 -8
  12. package/src/components/ErrorToast/index.tsx +2 -2
  13. package/src/containers/Settings/DataPacks/actions.tsx +9 -8
  14. package/src/containers/Sites/actions.tsx +17 -2
  15. package/src/containers/Sites/constants.tsx +1 -0
  16. package/src/containers/Sites/interfaces.tsx +7 -1
  17. package/src/containers/Sites/reducer.tsx +5 -1
  18. package/src/helpers/index.tsx +13 -1
  19. package/src/helpers/themes.tsx +82 -3
  20. package/src/modules/Content/OptionTable/index.tsx +45 -36
  21. package/src/modules/Content/OptionTable/store.tsx +4 -3
  22. package/src/modules/Settings/Globals/index.tsx +37 -4
  23. package/src/modules/Settings/Globals/style.tsx +5 -1
  24. package/src/types/index.tsx +23 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@griddo/ax",
3
3
  "description": "Griddo Author Experience",
4
- "version": "10.4.32",
4
+ "version": "10.4.34",
5
5
  "authors": [
6
6
  "Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
7
7
  "Carlos Torres <carlos.torres@secuoyas.com>",
@@ -232,5 +232,5 @@
232
232
  "publishConfig": {
233
233
  "access": "public"
234
234
  },
235
- "gitHead": "151e9f780f11806f31b75c6a7fe425548377feee"
235
+ "gitHead": "21a939f48088c711230a887664d83565dd7caa92"
236
236
  }
@@ -96,6 +96,7 @@ const initialStore = {
96
96
  editorID: 1,
97
97
  },
98
98
  ],
99
+ themeElements: null,
99
100
  },
100
101
  app: {
101
102
  lang: { locale: "es-ES", id: 0 },
@@ -98,6 +98,7 @@ const initialStore = {
98
98
  editorID: 1,
99
99
  },
100
100
  ],
101
+ themeElements: null,
101
102
  },
102
103
  app: {
103
104
  lang: { locale: "es-ES", id: 0 },
@@ -52,6 +52,7 @@ const initialStore = {
52
52
  editorID: 1,
53
53
  },
54
54
  ],
55
+ themeElements: null,
55
56
  },
56
57
  app: {
57
58
  lang: { locale: "es-ES", id: 0 },
@@ -201,6 +202,7 @@ describe("PageConnectedField component rendering", () => {
201
202
  editorID: 1,
202
203
  },
203
204
  ],
205
+ themeElements: null,
204
206
  },
205
207
  app: {
206
208
  lang: { locale: "es-ES", id: 0 },
@@ -254,6 +256,7 @@ describe("PageConnectedField component rendering", () => {
254
256
  editorID: 1,
255
257
  },
256
258
  ],
259
+ themeElements: null,
257
260
  },
258
261
  app: {
259
262
  lang: { locale: "es-ES", id: 0 },
@@ -307,6 +310,7 @@ describe("PageConnectedField component rendering", () => {
307
310
  editorID: 1,
308
311
  },
309
312
  ],
313
+ themeElements: null,
310
314
  },
311
315
  app: {
312
316
  lang: { locale: "es-ES", id: 0 },
@@ -362,6 +366,7 @@ describe("PageConnectedField component rendering", () => {
362
366
  editorID: 1,
363
367
  },
364
368
  ],
369
+ themeElements: null,
365
370
  },
366
371
  app: {
367
372
  lang: { locale: "es-ES", id: 0 },
@@ -72,6 +72,8 @@ const defaultProps = {
72
72
  theme: "griddo-alt-theme",
73
73
  moduleCopy: null,
74
74
  availableDataPacks: [],
75
+ activatedModules: [],
76
+ lang: 4,
75
77
  };
76
78
 
77
79
  const initialStore = {
@@ -112,6 +114,9 @@ const initialStore = {
112
114
  available: [],
113
115
  modules: [],
114
116
  },
117
+ sites: {
118
+ themeElements: null,
119
+ },
115
120
  };
116
121
 
117
122
  const store = mockStore(initialStore);
@@ -98,6 +98,7 @@ const initialStore = {
98
98
  editorID: 1,
99
99
  },
100
100
  ],
101
+ themeElements: null,
101
102
  },
102
103
  app: {
103
104
  lang: { locale: "es-ES", id: 0 },
@@ -52,6 +52,7 @@ const initialStore = {
52
52
  currentSiteInfo: {
53
53
  id: 1,
54
54
  },
55
+ themeElements: null,
55
56
  },
56
57
  app: {
57
58
  lang: { locale: "es-ES", id: 0 },
@@ -10,10 +10,16 @@ import { parseTheme } from "@ax/helpers";
10
10
  import Header, { IHeaderProps } from "@ax/components/ConfigPanel/Header";
11
11
  import globalTheme from "@ax/themes/theme.json";
12
12
 
13
+ window.scrollTo = jest.fn();
14
+
13
15
  beforeEach(() => {
14
16
  cleanup();
15
17
  });
16
18
 
19
+ afterAll(() => {
20
+ jest.clearAllMocks();
21
+ });
22
+
17
23
  const middlewares: any = [];
18
24
  const mockStore = configureStore(middlewares);
19
25
  const updateEditorContentMock = jest.fn();
@@ -5,7 +5,7 @@ import { FieldContainer } from "@ax/components";
5
5
  import { navigationActions } from "@ax/containers/Navigation";
6
6
  import { getInnerFields } from "@ax/forms";
7
7
  import { IRootState } from "@ax/types";
8
- import { areEqual } from "@ax/helpers";
8
+ import { areEqual, filterThemeElements } from "@ax/helpers";
9
9
 
10
10
  const NavConnectedField = (props: any) => {
11
11
  const {
@@ -26,6 +26,7 @@ const NavConnectedField = (props: any) => {
26
26
  theme,
27
27
  disabled,
28
28
  moduleCopy,
29
+ themeElements,
29
30
  } = props;
30
31
 
31
32
  const updateValue = (key: string, value: any) => {
@@ -68,9 +69,11 @@ const NavConnectedField = (props: any) => {
68
69
  );
69
70
  }
70
71
 
72
+ const filteredWhiteList = whiteList ? filterThemeElements(themeElements, whiteList, "modules") : whiteList;
73
+
71
74
  return (
72
75
  <FieldContainer
73
- whiteList={whiteList}
76
+ whiteList={filteredWhiteList}
74
77
  key={objKey}
75
78
  objKey={objKey}
76
79
  field={field}
@@ -99,6 +102,7 @@ const mapStateToProps = (state: IRootState) => ({
99
102
  activatedTemplates: state.dataPacks.templates,
100
103
  menus: state.menu.savedMenus,
101
104
  moduleCopy: state.navigation.moduleCopy,
105
+ themeElements: state.sites.themeElements,
102
106
  });
103
107
 
104
108
  const mapDispatchToProps = {
@@ -1,8 +1,8 @@
1
1
  import React from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
- import { IDataPack, IErrorItem, IRootState, ISchemaField, ISite } from "@ax/types";
5
- import { getModuleCategories } from "@ax/helpers";
4
+ import { IDataPack, IErrorItem, IRootState, ISchemaField, ISite, IThemeElements } from "@ax/types";
5
+ import { filterThemeElements, getModuleCategories } from "@ax/helpers";
6
6
  import Field from "../Field";
7
7
 
8
8
  import * as S from "./style";
@@ -29,6 +29,7 @@ export const TemplateManager = (props: IProps): JSX.Element => {
29
29
  availableDataPacks,
30
30
  setHistoryPush,
31
31
  lang,
32
+ themeElements,
32
33
  } = props;
33
34
 
34
35
  const isConfig = selectedTab === "config";
@@ -60,10 +61,11 @@ export const TemplateManager = (props: IProps): JSX.Element => {
60
61
  }, []);
61
62
 
62
63
  const mappedWhiteList: string[] = whiteList ? [...whiteList, ...addedModules].sort() : [...addedModules.sort()];
63
- const categories = getModuleCategories(mappedWhiteList);
64
+ const filteredWhiteList = filterThemeElements(themeElements, mappedWhiteList, "modules");
65
+ const categories = getModuleCategories(filteredWhiteList);
64
66
 
65
67
  return {
66
- whiteList: mappedWhiteList,
68
+ whiteList: filteredWhiteList,
67
69
  categories,
68
70
  key,
69
71
  fieldObjKey,
@@ -93,7 +95,7 @@ export const TemplateManager = (props: IProps): JSX.Element => {
93
95
  handleUpdate,
94
96
  error,
95
97
  readonly,
96
- disabledField
98
+ disabledField,
97
99
  } = getFieldProps(templateField);
98
100
 
99
101
  return (
@@ -141,7 +143,7 @@ interface IProps {
141
143
  site?: ISite;
142
144
  activatedPacks: IDataPack[];
143
145
  disabled?: boolean;
144
- activatedModules?: string[];
146
+ activatedModules: string[];
145
147
  isTemplateActivated: boolean;
146
148
  deleteError(error: IErrorItem): void;
147
149
  errors: IErrorItem[];
@@ -150,12 +152,13 @@ interface IProps {
150
152
  availableDataPacks: Record<string, any>[];
151
153
  setHistoryPush?: (path: string, isEditor: boolean) => void;
152
154
  lang: number;
155
+ themeElements: IThemeElements | null;
153
156
  }
154
157
 
155
158
  const mapStateToProps = (state: IRootState) => ({
156
159
  activatedPacks: state.dataPacks.activated,
157
- activatedModules: state.dataPacks.modules,
158
160
  availableDataPacks: state.dataPacks.available,
161
+ themeElements: state.sites.themeElements,
159
162
  });
160
163
 
161
164
  export default connect(mapStateToProps)(TemplateManager);
@@ -1,7 +1,14 @@
1
1
  import React, { memo, useEffect } from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
- import { getTemplate, isModuleDisabled, slugify, areEqual } from "@ax/helpers";
4
+ import {
5
+ getTemplate,
6
+ isModuleDisabled,
7
+ slugify,
8
+ areEqual,
9
+ filterThemeElements,
10
+ isTemplateExcludedFromTheme,
11
+ } from "@ax/helpers";
5
12
  import { IRootState } from "@ax/types";
6
13
  import { pageEditorActions } from "@ax/containers/PageEditor";
7
14
 
@@ -39,6 +46,7 @@ const PageConnectedField = (props: any) => {
39
46
  setHistoryPush,
40
47
  languages,
41
48
  isReadOnly,
49
+ themeElements,
42
50
  } = props;
43
51
 
44
52
  const isTemplate = field.type === "template";
@@ -51,9 +59,11 @@ const PageConnectedField = (props: any) => {
51
59
 
52
60
  let isTemplateActivated = true;
53
61
  if (selectedContent.template && !isGlobal) {
54
- isTemplateActivated = activatedTemplates.find((temp: any) => temp.id === selectedContent.template.templateType)
55
- ? true
56
- : false;
62
+ isTemplateActivated =
63
+ activatedTemplates.find((temp: any) => temp.id === selectedContent.template.templateType) &&
64
+ !isTemplateExcludedFromTheme(themeElements, selectedContent.template.templateType)
65
+ ? true
66
+ : false;
57
67
  }
58
68
 
59
69
  const parentIsReadOnly =
@@ -72,10 +82,14 @@ const PageConnectedField = (props: any) => {
72
82
 
73
83
  const isFieldReadOnly = (["parent", "slug"].includes(objKey) && isPageHome) || parentIsReadOnly || field.readonly;
74
84
 
85
+ const filteredActivatedModules = filterThemeElements(themeElements, activatedModules, "modules");
86
+ const filteredWhiteList = whiteList ? filterThemeElements(themeElements, whiteList, "modules") : whiteList;
87
+
75
88
  const isDisabled =
76
89
  (!isGlobal &&
77
- (isModuleDisabled(selectedContent.component, componentType, activatedModules) || !isTemplateActivated)) ||
78
- isFieldReadOnly || field.disabled;
90
+ (isModuleDisabled(selectedContent.component, componentType, filteredActivatedModules) || !isTemplateActivated)) ||
91
+ isFieldReadOnly ||
92
+ field.disabled;
79
93
 
80
94
  const isMetaTitleModified = selectedContent.metaTitle && selectedContent.metaTitle !== selectedContent.title;
81
95
 
@@ -160,13 +174,14 @@ const PageConnectedField = (props: any) => {
160
174
  moduleCopy={moduleCopy}
161
175
  setHistoryPush={setHistoryPush}
162
176
  lang={lang.id}
177
+ activatedModules={filteredActivatedModules}
163
178
  />
164
179
  );
165
180
  }
166
181
 
167
182
  return (
168
183
  <Field
169
- whiteList={whiteList}
184
+ whiteList={filteredWhiteList}
170
185
  objKey={objKey}
171
186
  field={field}
172
187
  selectedContent={selectedContent}
@@ -178,7 +193,7 @@ const PageConnectedField = (props: any) => {
178
193
  lang={lang.id}
179
194
  disabled={isDisabled}
180
195
  readonly={isFieldReadOnly}
181
- activatedModules={activatedModules}
196
+ activatedModules={filteredActivatedModules}
182
197
  isTemplateActivated={isTemplateActivated}
183
198
  error={error}
184
199
  deleteError={deleteError}
@@ -207,6 +222,7 @@ const mapStateToProps = (state: IRootState) => ({
207
222
  availableDataPacks: state.dataPacks.available,
208
223
  template: state.pageEditor.template,
209
224
  languages: state.sites.currentSiteLanguages,
225
+ themeElements: state.sites.themeElements,
210
226
  });
211
227
 
212
228
  const mapDispatchToProps = {
@@ -2,8 +2,8 @@ import React, { forwardRef } from "react";
2
2
 
3
3
  import * as S from "./style";
4
4
 
5
- const ErrorToast = (props: IProps) => {
6
- return <S.ErrorWrapper id="error" data-testid="error-wrapper" {...props} />;
5
+ const ErrorToast = (props: IProps, ref: any) => {
6
+ return <S.ErrorWrapper id="error" data-testid="error-wrapper" {...props} ref={ref} />;
7
7
  };
8
8
 
9
9
  export interface IProps {
@@ -1,6 +1,6 @@
1
1
  import { Dispatch } from "redux";
2
2
 
3
- import { handleRequest, sortBy } from "@ax/helpers";
3
+ import { filterThemeDatapacks, handleRequest, sortBy } from "@ax/helpers";
4
4
  import { dataPack } from "@ax/api";
5
5
  import { appActions } from "@ax/containers/App";
6
6
  import { structuredDataActions } from "@ax/containers/StructuredData";
@@ -25,7 +25,7 @@ import {
25
25
  ISetModules,
26
26
  ISetTemplates,
27
27
  } from "./interfaces";
28
- import { IDataPack, IDataPackCategory } from "@ax/types";
28
+ import { IDataPack, IDataPackCategory, IRootState } from "@ax/types";
29
29
  import { dataPacksInitialState } from "./reducer";
30
30
 
31
31
  function setActivated(activated: any): ISetActivated {
@@ -258,19 +258,20 @@ function resetForm(): (dispatch: Dispatch) => void {
258
258
 
259
259
  function getAvailableSiteDataPacks(
260
260
  queryParams: string | null,
261
- loading = true,
261
+ loading = true
262
262
  ): (dispatch: Dispatch, getState: any) => Promise<void> {
263
263
  return async (dispatch, getState) => {
264
264
  try {
265
265
  const {
266
- sites: { currentSiteInfo },
267
- } = getState();
268
- const currentSiteID = currentSiteInfo && currentSiteInfo.id;
266
+ sites: { currentSiteInfo, themeElements },
267
+ }: IRootState = getState();
268
+
269
269
  const query = queryParams ? `?status=deactivated${queryParams}` : `?status=deactivated`;
270
270
 
271
271
  const handleSuccess = (data: any) => {
272
272
  const { items } = data;
273
- dispatch(setAvailable(items));
273
+ const filteredDatapacks = filterThemeDatapacks(themeElements, items);
274
+ dispatch(setAvailable(filteredDatapacks));
274
275
  };
275
276
 
276
277
  const responseActions = {
@@ -278,7 +279,7 @@ function getAvailableSiteDataPacks(
278
279
  handleError: () => console.log("Error en getAvailableSiteDataPacks"),
279
280
  };
280
281
 
281
- const callback = async () => dataPack.getSiteDataPack(currentSiteID, query);
282
+ const callback = async () => currentSiteInfo && dataPack.getSiteDataPack(currentSiteInfo.id, query);
282
283
 
283
284
  const setLoading = loading ? [appActions.setIsLoading] : [];
284
285
 
@@ -15,6 +15,7 @@ import {
15
15
  SET_CONTENT_FILTERS,
16
16
  SET_CONFIG,
17
17
  SET_CURRENT_SEARCH,
18
+ SET_THEME_ELEMENTS,
18
19
  } from "./constants";
19
20
 
20
21
  import {
@@ -32,6 +33,7 @@ import {
32
33
  ISetSitesTotalItems,
33
34
  ISetConfig,
34
35
  ISetCurrentSearch,
36
+ ISetThemeElements,
35
37
  } from "./interfaces";
36
38
 
37
39
  import {
@@ -43,6 +45,7 @@ import {
43
45
  IGetSitesParams,
44
46
  ISiteListConfig,
45
47
  IQueryValue,
48
+ IThemeElements,
46
49
  } from "@ax/types";
47
50
  import { sites, languages, dataPack, social, structuredData, analytics, pages } from "@ax/api";
48
51
  import { appActions } from "@ax/containers/App";
@@ -53,7 +56,7 @@ import { analyticsActions } from "@ax/containers/Analytics";
53
56
  import { dataPacksActions } from "@ax/containers/Settings/DataPacks";
54
57
  import { socialActions } from "@ax/containers/Settings/Social";
55
58
  import { integrationsActions } from "@ax/containers/Integrations";
56
- import { getDefaultTheme, handleRequest, isReqOk, sortBy } from "@ax/helpers";
59
+ import { getDefaultTheme, getThemeElements, handleRequest, isReqOk, sortBy } from "@ax/helpers";
57
60
  import { usersActions } from "@ax/containers/Users";
58
61
 
59
62
  const { setIsLoading, setIsSaving, setLanguage } = appActions;
@@ -119,6 +122,10 @@ function setCurrentSearch(currentSearch: string): ISetCurrentSearch {
119
122
  return { type: SET_CURRENT_SEARCH, payload: { currentSearch } };
120
123
  }
121
124
 
125
+ function setThemeElements(themeElements: IThemeElements | null): ISetThemeElements {
126
+ return { type: SET_THEME_ELEMENTS, payload: { themeElements } };
127
+ }
128
+
122
129
  // TODO: hay que controlar que cuando da error la API borrar los sites ya guardados y sacar el error (ver los siguientes FIXME)
123
130
  function getSites(params: IGetSitesParams = { recentSitesNumber: 7 }): (dispatch: Dispatch) => Promise<void> {
124
131
  return async (dispatch) => {
@@ -192,7 +199,11 @@ function saveSettings(form: ISettingsForm): (dispatch: Dispatch, getState: any)
192
199
  const theme = getDefaultTheme();
193
200
 
194
201
  const responseActions = {
195
- handleSuccess: (response: any) => setSiteInfo(response)(dispatch, getState),
202
+ handleSuccess: (response: ISite) => {
203
+ setSiteInfo(response)(dispatch, getState);
204
+ const themeElements = getThemeElements(response.theme) || null;
205
+ dispatch(setThemeElements(themeElements));
206
+ },
196
207
  handleError: (response: any) => appActions.handleError(response)(dispatch),
197
208
  };
198
209
 
@@ -245,6 +256,9 @@ function setSiteInfo(currentSiteInfo: ISite): (dispatch: any, getState: any) =>
245
256
  dispatch(setCurrentSiteInfo(currentSiteInfo));
246
257
  await getRoles({ siteId: currentSiteInfo.id }, undefined, false)(dispatch);
247
258
 
259
+ const themeElements = getThemeElements(currentSiteInfo.theme) || null;
260
+ dispatch(setThemeElements(themeElements));
261
+
248
262
  // get site languages
249
263
  const response: any = await languages.getSiteLanguages(currentSiteInfo.id);
250
264
  if (isReqOk(response.status)) {
@@ -486,6 +500,7 @@ function resetSiteValues(siteID: number): (dispatch: Dispatch) => void {
486
500
  dispatch(setTotalItems(0));
487
501
  getAnalytics(siteID)(dispatch);
488
502
  dispatch(setCurrentSearch(""));
503
+ dispatch(setThemeElements(null));
489
504
  };
490
505
  }
491
506
 
@@ -15,6 +15,7 @@ export const SET_CURRENT_SITE_ERROR_PAGES = `${NAME}/SET_CURRENT_SITE_ERROR_PAGE
15
15
  export const SET_CONTENT_FILTERS = `${NAME}/SET_CONTENT_FILTERS`;
16
16
  export const SET_CONFIG = `${NAME}/SET_CONFIG`;
17
17
  export const SET_CURRENT_SEARCH = `${NAME}/SET_CURRENT_SEARCH`;
18
+ export const SET_THEME_ELEMENTS = `${NAME}/SET_THEME_ELEMENTS`;
18
19
 
19
20
  export const ITEMS_PER_PAGE = 50;
20
21
 
@@ -14,8 +14,9 @@ import {
14
14
  SET_SITES_TOTAL_ITEMS,
15
15
  SET_CONFIG,
16
16
  SET_CURRENT_SEARCH,
17
+ SET_THEME_ELEMENTS,
17
18
  } from "./constants";
18
- import { IQueryValue, ISite, ISiteListConfig } from "@ax/types";
19
+ import { IQueryValue, ISite, ISiteListConfig, IThemeElements } from "@ax/types";
19
20
 
20
21
  export interface ISetFilter {
21
22
  type: typeof SET_FILTER;
@@ -92,4 +93,9 @@ export interface ISetCurrentSearch {
92
93
  payload: { currentSearch: string };
93
94
  }
94
95
 
96
+ export interface ISetThemeElements {
97
+ type: typeof SET_THEME_ELEMENTS;
98
+ payload: { themeElements: IThemeElements | null };
99
+ }
100
+
95
101
  export type SitesActionsCreators = ISetSitesAction & ISetCurrentSiteInfoAction;
@@ -14,9 +14,10 @@ import {
14
14
  SET_CONTENT_FILTERS,
15
15
  SET_CONFIG,
16
16
  SET_CURRENT_SEARCH,
17
+ SET_THEME_ELEMENTS,
17
18
  } from "./constants";
18
19
 
19
- import { ISite, IPage, ILanguage, ISiteListConfig, IQueryValue } from "@ax/types";
20
+ import { ISite, IPage, ILanguage, ISiteListConfig, IQueryValue, IThemeElements } from "@ax/types";
20
21
 
21
22
  import { SitesActionsCreators } from "./interfaces";
22
23
 
@@ -36,6 +37,7 @@ export interface ISitesState {
36
37
  contentFilters: Record<string, IQueryValue[]> | null;
37
38
  config: ISiteListConfig;
38
39
  currentSearch: string;
40
+ themeElements: IThemeElements | null;
39
41
  }
40
42
 
41
43
  const config = {
@@ -67,6 +69,7 @@ export const initialState = {
67
69
  contentFilters: null,
68
70
  config,
69
71
  currentSearch: "",
72
+ themeElements: null,
70
73
  };
71
74
 
72
75
  export function reducer(state = initialState, action: any): ISitesState {
@@ -86,6 +89,7 @@ export function reducer(state = initialState, action: any): ISitesState {
86
89
  case SET_CONTENT_FILTERS:
87
90
  case SET_CONFIG:
88
91
  case SET_CURRENT_SEARCH:
92
+ case SET_THEME_ELEMENTS:
89
93
  return { ...state, ...action.payload };
90
94
  default:
91
95
  return state;
@@ -100,7 +100,14 @@ import { getActivatedDataPacksIds, isModuleDisabled, getDeactivatedModules } fro
100
100
 
101
101
  import { isDevelopment } from "./environment";
102
102
 
103
- import { getDefaultTheme } from "./themes";
103
+ import {
104
+ getDefaultTheme,
105
+ getThemeElements,
106
+ filterThemeElements,
107
+ filterThemeTemplates,
108
+ filterThemeDatapacks,
109
+ isTemplateExcludedFromTheme,
110
+ } from "./themes";
104
111
 
105
112
  import { parseTheme } from "./parseTheme";
106
113
 
@@ -183,6 +190,11 @@ export {
183
190
  splitAndJoin,
184
191
  splitAndTrim,
185
192
  getDefaultTheme,
193
+ getThemeElements,
194
+ filterThemeElements,
195
+ filterThemeTemplates,
196
+ filterThemeDatapacks,
197
+ isTemplateExcludedFromTheme,
186
198
  copyTextToClipboard,
187
199
  getNavigationModules,
188
200
  getDefaultNavigationModules,
@@ -1,9 +1,88 @@
1
+ import { IDataPack, IGriddoTheme, ITemplateOption, IThemeElements } from "@ax/types";
1
2
  import { themes } from "components";
2
3
 
3
4
  const getDefaultTheme = (): string => {
4
- const defaultTheme = themes.find((theme: any) => theme.default);
5
+ const defaultTheme = (themes as IGriddoTheme[]).find((theme) => theme.default);
5
6
  const theme = defaultTheme ? defaultTheme.value : themes[0].value;
6
7
  return theme;
7
- }
8
+ };
8
9
 
9
- export { getDefaultTheme }
10
+ const getThemeElements = (theme: string): IThemeElements | undefined => {
11
+ return (themes as IGriddoTheme[]).find((griddoTheme) => griddoTheme.value === theme)?.elements;
12
+ };
13
+
14
+ const filterThemeElements = (
15
+ themeElements: IThemeElements | null,
16
+ elements: string[],
17
+ type: "modules" | "templates" | "datapacks"
18
+ ): string[] => {
19
+ if (themeElements === null) return elements;
20
+
21
+ const { exclude, include } = themeElements;
22
+
23
+ let filteredElements: string[] = elements;
24
+ if (exclude && Array.isArray(exclude[type])) {
25
+ filteredElements = elements.filter((element: string) => !exclude[type]?.includes(element));
26
+ } else if (include && Array.isArray(include[type])) {
27
+ filteredElements = elements.filter((element: string) => include[type]?.includes(element));
28
+ }
29
+
30
+ return filteredElements;
31
+ };
32
+
33
+ const filterThemeTemplates = (
34
+ themeElements: IThemeElements | null,
35
+ templates: ITemplateOption[]
36
+ ): ITemplateOption[] => {
37
+ if (themeElements === null) return templates;
38
+
39
+ const { exclude, include } = themeElements;
40
+
41
+ let filteredElements: ITemplateOption[] = templates;
42
+ if (exclude && Array.isArray(exclude.templates)) {
43
+ filteredElements = templates.filter((template) => !exclude.templates?.includes(template.value));
44
+ } else if (include && Array.isArray(include.templates)) {
45
+ filteredElements = templates.filter((template) => include.templates?.includes(template.value));
46
+ }
47
+
48
+ return filteredElements;
49
+ };
50
+
51
+ const filterThemeDatapacks = (themeElements: IThemeElements | null, datapacks: IDataPack[]): IDataPack[] => {
52
+ if (themeElements === null) return datapacks;
53
+
54
+ const { exclude, include } = themeElements;
55
+
56
+ let filteredElements: IDataPack[] = datapacks;
57
+ if (exclude && Array.isArray(exclude.datapacks)) {
58
+ filteredElements = datapacks.filter((datapack) => !exclude.datapacks?.includes(datapack.id));
59
+ } else if (include && Array.isArray(include.datapacks)) {
60
+ filteredElements = datapacks.filter((datapack) => include.datapacks?.includes(datapack.id));
61
+ }
62
+
63
+ return filteredElements;
64
+ };
65
+
66
+ const isTemplateExcludedFromTheme = (themeElements: IThemeElements | null, template: string): boolean => {
67
+ if (themeElements === null) return false;
68
+
69
+ const { exclude, include } = themeElements;
70
+
71
+ if (
72
+ (exclude && Array.isArray(exclude.templates) && exclude.templates.includes(template)) ||
73
+ (include && Array.isArray(include.templates) && !include.templates.includes(template))
74
+ ) {
75
+ return true;
76
+ }
77
+
78
+ return false;
79
+ };
80
+
81
+ export {
82
+ getDefaultTheme,
83
+ getThemeElements,
84
+ filterThemeElements,
85
+ filterThemeTemplates,
86
+ filterThemeDatapacks,
87
+ isTemplateExcludedFromTheme,
88
+ };
@@ -1,13 +1,13 @@
1
1
  import React, { useReducer, useEffect, useLayoutEffect } from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
- import { IRootState, IStructuredData } from "@ax/types";
5
- import { getThumbnailProps } from "@ax/helpers";
4
+ import { IRootState, IStructuredData, ITemplateOption, IThemeElements } from "@ax/types";
5
+ import { filterThemeTemplates, getThumbnailProps } from "@ax/helpers";
6
6
  import { MenuItem, RadioGroup } from "@ax/components";
7
7
  import { structuredDataActions } from "@ax/containers/StructuredData";
8
8
  import { SecondaryActionButton, MainActionButton } from "./../atoms";
9
9
 
10
- import { reducer, IOptionTableStore, setColumnValues, setShowThumbnail, setSelectedType, setOption } from "./store";
10
+ import { reducer, IOptionTableStore, setColumnValues, setSelectedType, setOption } from "./store";
11
11
 
12
12
  import * as S from "./style";
13
13
 
@@ -23,12 +23,16 @@ const OptionTable = (props: IOptionTableProps): JSX.Element => {
23
23
  mainAction,
24
24
  secondaryAction,
25
25
  structuredData,
26
+ themeElements,
26
27
  } = props;
27
28
 
28
- const filterOptions = (value: string, objKey: string) => values.filter((item: any) => item[objKey] === value);
29
+ const filteredValues = filterThemeTemplates(themeElements, values);
30
+
31
+ const filterOptions = (value: string, objKey: "value" | "type") =>
32
+ filteredValues.filter((item) => item[objKey] === value);
29
33
 
30
34
  const filterOptionsByDataPack = (value: string) => {
31
- return values.filter((item: any) => {
35
+ return filteredValues.filter((item) => {
32
36
  return (
33
37
  item.editable !== false && (item.dataPacks ? item.dataPacks.includes(value.toUpperCase()) : item.type === value)
34
38
  );
@@ -47,11 +51,11 @@ const OptionTable = (props: IOptionTableProps): JSX.Element => {
47
51
 
48
52
  const [state, dispatch] = useReducer(reducer, initialState);
49
53
 
50
- const selectOption = (option: any, currentOptions?: any) => {
54
+ const selectOption = (option: string, currentOptions?: ITemplateOption[]) => {
51
55
  const { columnValues } = state;
52
56
  const availableOptions = currentOptions ? currentOptions : columnValues;
53
57
 
54
- const optionObj = availableOptions.find((item: any) => item.value === option);
58
+ const optionObj = availableOptions.find((item) => item.value === option);
55
59
  setSelectedOption(option);
56
60
  if (optionObj) {
57
61
  const { isData } = optionObj;
@@ -59,10 +63,10 @@ const OptionTable = (props: IOptionTableProps): JSX.Element => {
59
63
  }
60
64
  };
61
65
 
62
- let filteredOptionsByDataPack: any = filterOptionsByDataPack(state.selectedType);
66
+ let filteredOptionsByDataPack = filterOptionsByDataPack(state.selectedType);
63
67
 
64
68
  useLayoutEffect(() => {
65
- displayOptions({ value: state.selectedType });
69
+ displayOptions({ value: state.selectedType, label: state.selectedType });
66
70
  // eslint-disable-next-line react-hooks/exhaustive-deps
67
71
  }, []);
68
72
 
@@ -77,12 +81,11 @@ const OptionTable = (props: IOptionTableProps): JSX.Element => {
77
81
  isStructuredData ? selectData(value) : selectPage(value);
78
82
  };
79
83
 
80
- const showThumbnail = (isDisplayed: boolean) => dispatch(setShowThumbnail(isDisplayed));
81
84
  const setType = (type: string) => dispatch(setSelectedType(type));
82
- const setValues = (columns: any) => dispatch(setColumnValues(columns));
83
- const setSelectedOption = (selectedOption: any) => dispatch(setOption({ selectedOption }));
85
+ const setValues = (columns: ITemplateOption[]) => dispatch(setColumnValues(columns));
86
+ const setSelectedOption = (selectedOption: string) => dispatch(setOption({ selectedOption }));
84
87
 
85
- const isOptionInType = (column: any) => {
88
+ const isOptionInType = (column: ITemplateOption[]) => {
86
89
  const optionValues = filterOptions(state.selectedOption, "value");
87
90
  return column.includes(optionValues[0]);
88
91
  };
@@ -92,20 +95,19 @@ const OptionTable = (props: IOptionTableProps): JSX.Element => {
92
95
  isOptionInType(filteredOptionsByDataPack) &&
93
96
  getThumbnailProps(state.selectedOption, true, theme);
94
97
 
95
- const displayOptions = (item: any) => {
98
+ const displayOptions = (item: IOptionFilter) => {
96
99
  const { value } = item;
100
+
97
101
  setType(value);
98
102
  filteredOptionsByDataPack = filterOptionsByDataPack(value);
99
-
100
- filteredOptionsByDataPack.forEach((option: any) => {
103
+ filteredOptionsByDataPack.forEach((option) => {
101
104
  const { name } = option;
102
105
 
103
- const currentGlobalStructuredData = structuredData.global.find((data: any) =>
104
- data.schema.templates?.includes(name)
105
- );
106
- const currentLocalStructuredData = structuredData.site.find((data: any) => data.schema.templates?.includes(name));
106
+ const currentGlobalStructuredData = structuredData.global.find((data) => data.schema.templates?.includes(name));
107
+ const currentLocalStructuredData = structuredData.site.find((data) => data.schema.templates?.includes(name));
108
+
107
109
  if (currentGlobalStructuredData || currentLocalStructuredData) {
108
- filteredOptionsByDataPack = filteredOptionsByDataPack.map((option: any) => {
110
+ filteredOptionsByDataPack = filteredOptionsByDataPack.map((option) => {
109
111
  if (option.mode !== "detail") return option;
110
112
  const optionType = option.type;
111
113
  const currentStructuredDataId = currentGlobalStructuredData?.id || currentLocalStructuredData?.id;
@@ -113,17 +115,17 @@ const OptionTable = (props: IOptionTableProps): JSX.Element => {
113
115
  const title = currentGlobalStructuredData
114
116
  ? `Get ${currentGlobalStructuredData.title} from Global`
115
117
  : currentLocalStructuredData?.title;
116
- return { ...option, title };
118
+ return { ...option, title: title || "" };
117
119
  }
118
120
  return option;
119
121
  });
120
- const globalOptionIdx = filteredOptionsByDataPack.findIndex((option: any) => option.mode === "detail");
122
+ const globalOptionIdx = filteredOptionsByDataPack.findIndex((option) => option.mode === "detail");
121
123
  const globalOption = filteredOptionsByDataPack.splice(globalOptionIdx, 1);
122
124
  filteredOptionsByDataPack.push(...globalOption);
123
125
  }
124
126
  });
125
127
 
126
- filteredOptionsByDataPack.sort((ele: any, comp: any) => {
128
+ filteredOptionsByDataPack.sort((ele, comp) => {
127
129
  // Use localeCompare to compare strings alphabetically
128
130
  return ele.title.localeCompare(comp.title);
129
131
  });
@@ -131,24 +133,21 @@ const OptionTable = (props: IOptionTableProps): JSX.Element => {
131
133
  setValues(filteredOptionsByDataPack);
132
134
  const firstOption = filteredOptionsByDataPack[0] && filteredOptionsByDataPack[0].value;
133
135
  selectOption(firstOption, filteredOptionsByDataPack);
134
-
135
- const optionList = filterOptions(state.selectedOption, "value");
136
- const displayThumbnail = optionList.type !== value;
137
-
138
- showThumbnail(displayThumbnail);
139
136
  };
140
137
 
138
+ const handleChange = (value: string | boolean) => typeof value === "string" && selectOption(value);
139
+
141
140
  const hasThumbnail = state.showThumbnail && thumbnailProps && isOptionInType(filteredOptionsByDataPack);
142
141
 
143
142
  return (
144
143
  <S.Table>
145
144
  <S.LeftColumn>
146
- {filters.map((item: any, i: number) => {
147
- const displayFilteredOptions = () => state.selectedType !== item.type && displayOptions(item);
145
+ {filters.map((item, i: number) => {
146
+ const displayFilteredOptions = () => state.selectedType !== item.value && displayOptions(item);
148
147
  const isSelected = item.value === state.selectedType;
149
148
  const selectedClass = isSelected ? "selected" : "";
150
149
  return (
151
- <MenuItem key={`${item.type}${i}`}>
150
+ <MenuItem key={`${item.value}${i}`}>
152
151
  <S.NavLink onClick={displayFilteredOptions} className={selectedClass}>
153
152
  <S.Link active={isSelected}>{item.label}</S.Link>
154
153
  </S.NavLink>
@@ -157,7 +156,7 @@ const OptionTable = (props: IOptionTableProps): JSX.Element => {
157
156
  })}
158
157
  </S.LeftColumn>
159
158
  <S.Column>
160
- <RadioGroup options={state.columnValues} onChange={selectOption} value={state.selectedOption} name="" />
159
+ <RadioGroup options={state.columnValues} onChange={handleChange} value={state.selectedOption} name="" />
161
160
  </S.Column>
162
161
  <S.Column>
163
162
  {hasThumbnail && (
@@ -176,24 +175,34 @@ const OptionTable = (props: IOptionTableProps): JSX.Element => {
176
175
 
177
176
  interface IAction {
178
177
  title: string;
179
- onClick: any;
178
+ onClick: () => void;
179
+ }
180
+
181
+ interface IOptionFilter {
182
+ label: string;
183
+ value: string;
184
+ isData?: boolean;
185
+ special?: string;
186
+ mode?: string;
180
187
  }
181
188
 
182
189
  interface IOptionTableProps {
183
190
  selectData: (value: string) => void;
184
191
  selectPage: (value: string) => void;
185
192
  setIsStructuredData: (isActive: boolean) => void;
186
- filters: any;
187
- values: any;
193
+ filters: IOptionFilter[];
194
+ values: ITemplateOption[];
188
195
  selectedValue: string;
189
196
  theme: string;
190
197
  mainAction: IAction;
191
198
  secondaryAction: IAction;
192
199
  structuredData: { global: IStructuredData[]; site: IStructuredData[] };
200
+ themeElements: IThemeElements | null;
193
201
  }
194
202
 
195
203
  const mapStateToProps = (state: IRootState) => ({
196
204
  structuredData: state.structuredData.structuredData,
205
+ themeElements: state.sites.themeElements,
197
206
  });
198
207
 
199
208
  const mapDispatchToProps = {
@@ -1,3 +1,4 @@
1
+ import { ITemplateOption } from "@ax/types";
1
2
 
2
3
  const SET_COLUMN_VALUES = "SET_COLUMN_VALUES";
3
4
  const SET_SHOW_THUMBNAIL = "SET_SHOW_THUMBNAIL";
@@ -39,8 +40,8 @@ export function setOption(data: any) {
39
40
  }
40
41
 
41
42
  export interface IOptionTableStore {
42
- columnValues: any;
43
+ columnValues: ITemplateOption[];
43
44
  showThumbnail: boolean;
44
45
  selectedOption: string;
45
- selectedType: any;
46
- }
46
+ selectedType: string;
47
+ }
@@ -2,11 +2,11 @@ import React, { useState } from "react";
2
2
  import { connect } from "react-redux";
3
3
  import { themes } from "components";
4
4
 
5
- import { IImage, INavItem, IRootState, ISettingsForm } from "@ax/types";
5
+ import { IGriddoTheme, IImage, INavItem, IRootState, ISettingsForm } from "@ax/types";
6
6
  import { RouteLeavingGuard } from "@ax/guards";
7
- import { useShouldBeSaved } from "@ax/hooks";
7
+ import { useModal, useShouldBeSaved } from "@ax/hooks";
8
8
  import { appActions } from "@ax/containers/App";
9
- import { FieldsBehavior, FieldGroup, MainWrapper, ErrorToast, Nav, Notification } from "@ax/components";
9
+ import { FieldsBehavior, FieldGroup, MainWrapper, ErrorToast, Nav, Notification, Modal } from "@ax/components";
10
10
  import { sitesActions } from "@ax/containers/Sites";
11
11
  import { getNavigationModules } from "@ax/helpers";
12
12
 
@@ -18,6 +18,8 @@ const Globals = (props: IProps): JSX.Element => {
18
18
  const { isSaving, currentSiteInfo, saveSettings, setHistoryPush, navItems, currentNavItem } = props;
19
19
 
20
20
  const [isNavigationNotificationOpen, setIsNavigationNotificationOpen] = useState(false);
21
+ const [tempTheme, setTempTheme] = useState<string | undefined>();
22
+ const { isOpen, toggleModal } = useModal();
21
23
 
22
24
  const title = "General settings";
23
25
 
@@ -38,13 +40,26 @@ const Globals = (props: IProps): JSX.Element => {
38
40
 
39
41
  const setNameValue = (value: string) => setValue({ name: value });
40
42
  const setTimezoneValue = (value: string) => setValue({ timezone: value });
41
- const setThemeValue = (value: string) => setValue({ theme: value });
43
+ const setThemeValue = (value: string) => {
44
+ const theme = (themes as IGriddoTheme[]).find((th) => th.value === value);
45
+ if (theme && theme.elements) {
46
+ setTempTheme(value);
47
+ toggleModal();
48
+ } else {
49
+ setValue({ theme: value });
50
+ }
51
+ };
42
52
  const setFaviconValue = (value: IImage) => setValue({ favicon: value.url ? value.url : null });
43
53
  const setSmallAvatarValue = (value: IImage) => setValue({ smallAvatar: value.url ? value.url : null });
44
54
  const setBigAvatarValue = (value: IImage) => setValue({ bigAvatar: value.url ? value.url : null });
45
55
  const setThumbnailValue = (value: IImage) => setValue({ thumbnail: value.url ? value.url : null });
46
56
  const setNavigationModulesValue = (value: any) => setValue({ navigationModules: value });
47
57
 
58
+ const handleChangeTheme = () => {
59
+ tempTheme && setValue({ theme: tempTheme });
60
+ toggleModal();
61
+ };
62
+
48
63
  const saveForm = async () => {
49
64
  const isSaved = await saveSettings(form);
50
65
  const isNavigationModulesChanging =
@@ -73,6 +88,9 @@ const Globals = (props: IProps): JSX.Element => {
73
88
  const closeNavigationNotification = () => setIsNavigationNotificationOpen(false);
74
89
  const goToNavigationModules = () => setHistoryPush("/sites/navigations/modules");
75
90
 
91
+ const mainDeleteAction = { title: "Understood", onClick: handleChangeTheme };
92
+ const secondaryDeleteAction = { title: "Cancel", onClick: toggleModal };
93
+
76
94
  return (
77
95
  <>
78
96
  <RouteLeavingGuard when={isDirty} action={setRoute} text={modalText} />
@@ -163,6 +181,21 @@ const Globals = (props: IProps): JSX.Element => {
163
181
  </S.FormWrapper>
164
182
  </S.ContentWrapper>
165
183
  </S.Wrapper>
184
+ <Modal
185
+ isOpen={isOpen}
186
+ hide={toggleModal}
187
+ title="Theme Change Warning"
188
+ secondaryAction={secondaryDeleteAction}
189
+ mainAction={mainDeleteAction}
190
+ size="S"
191
+ >
192
+ <S.ModalContent>
193
+ <p>
194
+ The selected theme has some restrictions. By switching to this theme,{" "}
195
+ <strong>you may lose access to some modules or templates.</strong>
196
+ </p>
197
+ </S.ModalContent>
198
+ </Modal>
166
199
  </MainWrapper>
167
200
  </>
168
201
  );
@@ -17,4 +17,8 @@ const FormWrapper = styled.div`
17
17
  margin: ${(p) => `${p.theme.spacing.m} 0 0 ${p.theme.spacing.m}`};
18
18
  `;
19
19
 
20
- export { Wrapper, ContentWrapper, FormWrapper };
20
+ const ModalContent = styled.div`
21
+ padding: ${(p) => p.theme.spacing.m};
22
+ `;
23
+
24
+ export { Wrapper, ContentWrapper, FormWrapper, ModalContent };
@@ -983,6 +983,29 @@ export interface IBulkAction {
983
983
  action: () => void;
984
984
  }
985
985
 
986
+ export interface IGriddoTheme {
987
+ label: string;
988
+ value: string;
989
+ default: boolean;
990
+ elements?: IThemeElements;
991
+ }
992
+
993
+ export interface IThemeElements {
994
+ include?: { datapacks?: string[]; templates?: string[]; modules?: string[] };
995
+ exclude?: { datapacks?: string[]; templates?: string[]; modules?: string[] };
996
+ }
997
+
998
+ export interface ITemplateOption {
999
+ dataPacks?: string[];
1000
+ value: string;
1001
+ name: string;
1002
+ title: string;
1003
+ type: string;
1004
+ mode?: string;
1005
+ editable?: boolean;
1006
+ isData?: boolean;
1007
+ }
1008
+
986
1009
  export type Field =
987
1010
  | "AsyncCheckGroup"
988
1011
  | "AsyncSelect"