@elementor/editor-global-classes 0.17.4 → 0.17.6

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.
@@ -1,27 +1,63 @@
1
1
  import { __dispatch as dispatch, __getState as getState } from '@elementor/store';
2
2
 
3
- import { apiClient, type UpdateContext } from './api';
4
- import { type GlobalClassesState, slice } from './store';
3
+ import { apiClient, type ApiContext } from './api';
4
+ import { type GlobalClasses, selectData, selectFrontendInitialData, selectPreviewInitialData, slice } from './store';
5
5
 
6
6
  type Options = {
7
- context: UpdateContext;
7
+ context: ApiContext;
8
8
  };
9
9
 
10
10
  export async function saveGlobalClasses( { context }: Options ) {
11
- const state: GlobalClassesState = getState().globalClasses;
12
-
13
- const data = {
14
- items: state.items,
15
- order: state.order,
16
- };
11
+ const state = selectData( getState() );
17
12
 
18
13
  if ( context === 'preview' ) {
19
- await apiClient.saveDraft( data );
20
-
21
- return;
14
+ await apiClient.saveDraft( {
15
+ items: state.items,
16
+ order: state.order,
17
+ changes: calculateChanges( state, selectPreviewInitialData( getState() ) ),
18
+ } );
19
+ } else {
20
+ await apiClient.publish( {
21
+ items: state.items,
22
+ order: state.order,
23
+ changes: calculateChanges( state, selectFrontendInitialData( getState() ) ),
24
+ } );
22
25
  }
23
26
 
24
- await apiClient.publish( data );
27
+ dispatch( slice.actions.reset( { context } ) );
28
+ }
29
+
30
+ function calculateChanges( state: GlobalClasses, initialData: GlobalClasses ) {
31
+ const stateIds = Object.keys( state.items );
32
+ const initialDataIds = Object.keys( initialData.items );
33
+
34
+ return {
35
+ added: stateIds.filter( ( id ) => ! initialDataIds.includes( id ) ),
36
+ deleted: initialDataIds.filter( ( id ) => ! stateIds.includes( id ) ),
37
+ modified: stateIds.filter( ( id ) => {
38
+ return id in initialData.items && hash( state.items[ id ] ) !== hash( initialData.items[ id ] );
39
+ } ),
40
+ };
41
+ }
42
+
43
+ type UnknownObject = Record< string, unknown >;
44
+
45
+ // Inspired by:
46
+ // https://github.com/TanStack/query/blob/66ea5f2fc/packages/query-core/src/utils.ts#L212
47
+ function hash( obj: UnknownObject ): string {
48
+ return JSON.stringify( obj, ( _, value ) =>
49
+ isPlainObject( value )
50
+ ? Object.keys( value )
51
+ .sort()
52
+ .reduce< UnknownObject >( ( result, key ) => {
53
+ result[ key ] = value[ key ];
54
+
55
+ return result;
56
+ }, {} )
57
+ : value
58
+ );
59
+ }
25
60
 
26
- dispatch( slice.actions.setPristine() );
61
+ function isPlainObject( value: unknown ): value is UnknownObject {
62
+ return !! value && typeof value === 'object' && ! Array.isArray( value );
27
63
  }
package/src/store.ts CHANGED
@@ -13,17 +13,29 @@ import {
13
13
  type SliceState,
14
14
  } from '@elementor/store';
15
15
 
16
+ import type { ApiContext } from './api';
16
17
  import { GlobalClassNotFoundError } from './errors';
17
18
 
18
- export type GlobalClassesState = {
19
+ export type GlobalClasses = {
19
20
  items: Record< StyleDefinitionID, StyleDefinition >;
20
21
  order: StyleDefinitionID[];
22
+ };
23
+
24
+ type GlobalClassesState = {
25
+ data: GlobalClasses;
26
+ initialData: {
27
+ frontend: GlobalClasses;
28
+ preview: GlobalClasses;
29
+ };
21
30
  isDirty: boolean;
22
31
  };
23
32
 
24
33
  const initialState: GlobalClassesState = {
25
- items: {},
26
- order: [],
34
+ data: { items: {}, order: [] },
35
+ initialData: {
36
+ frontend: { items: {}, order: [] },
37
+ preview: { items: {}, order: [] },
38
+ },
27
39
  isDirty: false,
28
40
  };
29
41
 
@@ -36,49 +48,65 @@ export const slice = createSlice( {
36
48
  name: SLICE_NAME,
37
49
  initialState,
38
50
  reducers: {
39
- init( state, { payload }: PayloadAction< Pick< GlobalClassesState, 'items' | 'order' > > ) {
40
- state.items = payload.items;
41
- state.order = payload.order;
51
+ load(
52
+ state,
53
+ {
54
+ payload: { frontend, preview },
55
+ }: PayloadAction< {
56
+ frontend: GlobalClasses;
57
+ preview: GlobalClasses;
58
+ } >
59
+ ) {
60
+ state.initialData.frontend = frontend;
61
+ state.initialData.preview = preview;
62
+ state.data = preview;
42
63
 
43
64
  state.isDirty = false;
44
65
  },
66
+
45
67
  add( state, { payload }: PayloadAction< StyleDefinition > ) {
46
- state.items[ payload.id ] = payload;
47
- state.order.unshift( payload.id );
68
+ state.data.items[ payload.id ] = payload;
69
+ state.data.order.unshift( payload.id );
48
70
 
49
71
  state.isDirty = true;
50
72
  },
73
+
51
74
  delete( state, { payload }: PayloadAction< StyleDefinitionID > ) {
52
- state.items = Object.fromEntries( Object.entries( state.items ).filter( ( [ id ] ) => id !== payload ) );
75
+ state.data.items = Object.fromEntries(
76
+ Object.entries( state.data.items ).filter( ( [ id ] ) => id !== payload )
77
+ );
53
78
 
54
- state.order = state.order.filter( ( id ) => id !== payload );
79
+ state.data.order = state.data.order.filter( ( id ) => id !== payload );
55
80
 
56
81
  state.isDirty = true;
57
82
  },
83
+
58
84
  setOrder( state, { payload }: PayloadAction< StyleDefinitionID[] > ) {
59
- state.order = payload;
85
+ state.data.order = payload;
60
86
 
61
87
  state.isDirty = true;
62
88
  },
89
+
63
90
  update( state, { payload }: PayloadAction< { style: UpdateActionPayload } > ) {
64
- const style = state.items[ payload.style.id ];
91
+ const style = state.data.items[ payload.style.id ];
65
92
 
66
93
  const mergedData = {
67
94
  ...style,
68
95
  ...payload.style,
69
96
  };
70
97
 
71
- state.items[ payload.style.id ] = mergedData;
98
+ state.data.items[ payload.style.id ] = mergedData;
72
99
 
73
100
  state.isDirty = true;
74
101
  },
102
+
75
103
  updateProps(
76
104
  state,
77
105
  {
78
106
  payload,
79
107
  }: PayloadAction< { id: StyleDefinitionID; meta: StyleDefinitionVariant[ 'meta' ]; props: Props } >
80
108
  ) {
81
- const style = state.items[ payload.id ];
109
+ const style = state.data.items[ payload.id ];
82
110
 
83
111
  if ( ! style ) {
84
112
  throw new GlobalClassNotFoundError( { context: { styleId: payload.id } } );
@@ -95,22 +123,36 @@ export const slice = createSlice( {
95
123
  state.isDirty = true;
96
124
  },
97
125
 
98
- setPristine( state ) {
99
- state.isDirty = false;
126
+ reset( state, { payload: { context } }: PayloadAction< { context: ApiContext } > ) {
127
+ if ( context === 'frontend' ) {
128
+ state.initialData.frontend = state.data;
129
+
130
+ state.isDirty = false;
131
+ }
132
+
133
+ state.initialData.preview = state.data;
100
134
  },
101
135
  },
102
136
  } );
103
137
 
104
138
  // Selectors
105
- export const selectOrder = ( state: SliceState< typeof slice > ) => state[ SLICE_NAME ].order;
139
+ export const selectData = ( state: SliceState< typeof slice > ) => state[ SLICE_NAME ].data;
106
140
 
107
- export const selectGlobalClasses = ( state: SliceState< typeof slice > ) => state[ SLICE_NAME ].items;
141
+ export const selectFrontendInitialData = ( state: SliceState< typeof slice > ) =>
142
+ state[ SLICE_NAME ].initialData.frontend;
143
+
144
+ export const selectPreviewInitialData = ( state: SliceState< typeof slice > ) =>
145
+ state[ SLICE_NAME ].initialData.preview;
146
+
147
+ export const selectOrder = createSelector( selectData, ( { order } ) => order );
148
+
149
+ export const selectGlobalClasses = createSelector( selectData, ( { items } ) => items );
150
+
151
+ export const selectIsDirty = ( state: SliceState< typeof slice > ) => state[ SLICE_NAME ].isDirty;
108
152
 
109
153
  export const selectOrderedClasses = createSelector( selectGlobalClasses, selectOrder, ( items, order ) =>
110
154
  order.map( ( id ) => items[ id ] )
111
155
  );
112
156
 
113
157
  export const selectClass = ( state: SliceState< typeof slice >, id: StyleDefinitionID ) =>
114
- state[ SLICE_NAME ].items[ id ] ?? null;
115
-
116
- export const selectIsDirty = ( state: SliceState< typeof slice > ) => state.globalClasses.isDirty;
158
+ state[ SLICE_NAME ].data.items[ id ] ?? null;