@elementor/editor-components 3.33.0-99 → 3.34.2
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/dist/index.js +1860 -123
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1863 -110
- package/dist/index.mjs.map +1 -1
- package/package.json +21 -11
- package/src/api.ts +57 -11
- package/src/component-instance-transformer.ts +24 -0
- package/src/component-overridable-transformer.ts +28 -0
- package/src/components/components-tab/component-search.tsx +32 -0
- package/src/components/components-tab/components-item.tsx +67 -0
- package/src/components/components-tab/components-list.tsx +141 -0
- package/src/components/components-tab/components.tsx +17 -0
- package/src/components/components-tab/loading-components.tsx +43 -0
- package/src/components/components-tab/search-provider.tsx +38 -0
- package/src/components/consts.ts +1 -0
- package/src/components/create-component-form/create-component-form.tsx +109 -100
- package/src/components/create-component-form/utils/get-component-event-data.ts +54 -0
- package/src/components/create-component-form/utils/replace-element-with-component.ts +28 -10
- package/src/components/edit-component/component-modal.tsx +134 -0
- package/src/components/edit-component/edit-component.tsx +134 -0
- package/src/components/in-edit-mode.tsx +43 -0
- package/src/components/overridable-props/indicator.tsx +81 -0
- package/src/components/overridable-props/overridable-prop-form.tsx +98 -0
- package/src/components/overridable-props/overridable-prop-indicator.tsx +128 -0
- package/src/components/overridable-props/utils/get-overridable-prop.ts +20 -0
- package/src/create-component-type.ts +194 -0
- package/src/hooks/use-canvas-document.ts +6 -0
- package/src/hooks/use-components.ts +6 -9
- package/src/hooks/use-element-rect.ts +81 -0
- package/src/init.ts +82 -3
- package/src/mcp/index.ts +14 -0
- package/src/mcp/save-as-component-tool.ts +92 -0
- package/src/populate-store.ts +12 -0
- package/src/prop-types/component-overridable-prop-type.ts +17 -0
- package/src/store/actions.ts +21 -0
- package/src/store/components-styles-provider.ts +24 -0
- package/src/store/create-unpublished-component.ts +40 -0
- package/src/store/load-components-assets.ts +26 -0
- package/src/store/load-components-styles.ts +44 -0
- package/src/store/remove-component-styles.ts +9 -0
- package/src/store/set-overridable-prop.ts +161 -0
- package/src/store/store.ts +168 -0
- package/src/store/thunks.ts +10 -0
- package/src/sync/before-save.ts +15 -0
- package/src/sync/create-components-before-save.ts +108 -0
- package/src/sync/update-components-before-save.ts +36 -0
- package/src/types.ts +91 -0
- package/src/utils/component-document-data.ts +19 -0
- package/src/utils/get-component-ids.ts +36 -0
- package/src/utils/get-container-for-new-element.ts +49 -0
- package/src/utils/tracking.ts +47 -0
- package/src/components/components-tab.tsx +0 -6
- package/src/hooks/use-create-component.ts +0 -13
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type V1Document } from '@elementor/editor-documents';
|
|
2
|
+
import { __getStore as getStore } from '@elementor/store';
|
|
3
|
+
|
|
4
|
+
import { type ComponentsPathItem, slice } from './store';
|
|
5
|
+
|
|
6
|
+
export function updateCurrentComponent( {
|
|
7
|
+
path,
|
|
8
|
+
currentComponentId,
|
|
9
|
+
}: {
|
|
10
|
+
path: ComponentsPathItem[];
|
|
11
|
+
currentComponentId: V1Document[ 'id' ] | null;
|
|
12
|
+
} ) {
|
|
13
|
+
const dispatch = getStore()?.dispatch;
|
|
14
|
+
|
|
15
|
+
if ( ! dispatch ) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
dispatch( slice.actions.setPath( path ) );
|
|
20
|
+
dispatch( slice.actions.setCurrentComponentId( currentComponentId ) );
|
|
21
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createStylesProvider } from '@elementor/editor-styles-repository';
|
|
2
|
+
import { __getState as getState, __subscribeWithSelector as subscribeWithSelector } from '@elementor/store';
|
|
3
|
+
|
|
4
|
+
import { selectFlatStyles, SLICE_NAME } from './store';
|
|
5
|
+
|
|
6
|
+
export const componentsStylesProvider = createStylesProvider( {
|
|
7
|
+
key: 'components-styles',
|
|
8
|
+
priority: 100,
|
|
9
|
+
subscribe: ( cb ) =>
|
|
10
|
+
subscribeWithSelector(
|
|
11
|
+
( state ) => state[ SLICE_NAME ],
|
|
12
|
+
() => {
|
|
13
|
+
cb();
|
|
14
|
+
}
|
|
15
|
+
),
|
|
16
|
+
actions: {
|
|
17
|
+
all: () => {
|
|
18
|
+
return selectFlatStyles( getState() );
|
|
19
|
+
},
|
|
20
|
+
get: ( id ) => {
|
|
21
|
+
return selectFlatStyles( getState() ).find( ( style ) => style.id === id ) ?? null;
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
} );
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type V1ElementData } from '@elementor/editor-elements';
|
|
2
|
+
import { __privateRunCommand as runCommand } from '@elementor/editor-v1-adapters';
|
|
3
|
+
import { __dispatch as dispatch } from '@elementor/store';
|
|
4
|
+
import { generateUniqueId } from '@elementor/utils';
|
|
5
|
+
|
|
6
|
+
import { type ComponentEventData } from '../components/create-component-form/utils/get-component-event-data';
|
|
7
|
+
import { replaceElementWithComponent } from '../components/create-component-form/utils/replace-element-with-component';
|
|
8
|
+
import { trackComponentEvent } from '../utils/tracking';
|
|
9
|
+
import { slice } from './store';
|
|
10
|
+
|
|
11
|
+
export function createUnpublishedComponent(
|
|
12
|
+
name: string,
|
|
13
|
+
element: V1ElementData,
|
|
14
|
+
eventData: ComponentEventData | null
|
|
15
|
+
) {
|
|
16
|
+
const uid = generateUniqueId( 'component' );
|
|
17
|
+
const componentBase = { uid, name };
|
|
18
|
+
|
|
19
|
+
dispatch(
|
|
20
|
+
slice.actions.addUnpublished( {
|
|
21
|
+
...componentBase,
|
|
22
|
+
elements: [ element ],
|
|
23
|
+
} )
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
dispatch( slice.actions.addCreatedThisSession( uid ) );
|
|
27
|
+
|
|
28
|
+
replaceElementWithComponent( element, componentBase );
|
|
29
|
+
|
|
30
|
+
trackComponentEvent( {
|
|
31
|
+
action: 'created',
|
|
32
|
+
component_uid: uid,
|
|
33
|
+
component_name: name,
|
|
34
|
+
...eventData,
|
|
35
|
+
} );
|
|
36
|
+
|
|
37
|
+
runCommand( 'document/save/auto' );
|
|
38
|
+
|
|
39
|
+
return uid;
|
|
40
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { isDocumentDirty, setDocumentModifiedStatus } from '@elementor/editor-documents';
|
|
2
|
+
import { type V1ElementData } from '@elementor/editor-elements';
|
|
3
|
+
|
|
4
|
+
import { getComponentDocumentData } from '../utils/component-document-data';
|
|
5
|
+
import { getComponentIds } from '../utils/get-component-ids';
|
|
6
|
+
import { loadComponentsStyles } from './load-components-styles';
|
|
7
|
+
|
|
8
|
+
export async function loadComponentsAssets( elements: V1ElementData[] ) {
|
|
9
|
+
const componentIds = await getComponentIds( elements );
|
|
10
|
+
|
|
11
|
+
updateDocumentState( componentIds );
|
|
12
|
+
|
|
13
|
+
return loadComponentsStyles( componentIds );
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function updateDocumentState( componentIds: number[] ) {
|
|
17
|
+
const components = ( await Promise.all( componentIds.map( getComponentDocumentData ) ) ).filter(
|
|
18
|
+
( document ) => !! document
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const isDrafted = components.some( isDocumentDirty );
|
|
22
|
+
|
|
23
|
+
if ( isDrafted ) {
|
|
24
|
+
setDocumentModifiedStatus( true );
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type V1ElementData } from '@elementor/editor-elements';
|
|
2
|
+
import { type StyleDefinition } from '@elementor/editor-styles';
|
|
3
|
+
import { __dispatch as dispatch, __getState as getState } from '@elementor/store';
|
|
4
|
+
|
|
5
|
+
import { apiClient } from '../api';
|
|
6
|
+
import { type ComponentId } from '../types';
|
|
7
|
+
import { selectStyles, slice } from './store';
|
|
8
|
+
|
|
9
|
+
export async function loadComponentsStyles( componentIds: number[] ) {
|
|
10
|
+
if ( ! componentIds.length ) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const knownComponents = selectStyles( getState() );
|
|
15
|
+
const unknownComponentIds = componentIds.filter( ( id ) => ! knownComponents[ id ] );
|
|
16
|
+
|
|
17
|
+
if ( ! unknownComponentIds.length ) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
addComponentStyles( unknownComponentIds );
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function addComponentStyles( ids: ComponentId[] ) {
|
|
25
|
+
const newComponents = await loadStyles( ids );
|
|
26
|
+
|
|
27
|
+
addStyles( newComponents );
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function loadStyles( ids: number[] ): Promise< [ number, V1ElementData ][] > {
|
|
31
|
+
return Promise.all( ids.map( async ( id ) => [ id, await apiClient.getComponentConfig( id ) ] ) );
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function addStyles( data: ( readonly [ ComponentId, V1ElementData ] )[] ) {
|
|
35
|
+
const styles = Object.fromEntries(
|
|
36
|
+
data.map( ( [ componentId, componentData ] ) => [ componentId, extractStyles( componentData ) ] )
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
dispatch( slice.actions.addStyles( styles ) );
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function extractStyles( element: V1ElementData ): Array< StyleDefinition > {
|
|
43
|
+
return [ ...Object.values( element.styles ?? {} ), ...( element.elements ?? [] ).flatMap( extractStyles ) ];
|
|
44
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { __dispatch as dispatch } from '@elementor/store';
|
|
2
|
+
|
|
3
|
+
import { apiClient } from '../api';
|
|
4
|
+
import { slice } from './store';
|
|
5
|
+
|
|
6
|
+
export function removeComponentStyles( id: number ) {
|
|
7
|
+
apiClient.invalidateComponentConfigCache( id );
|
|
8
|
+
dispatch( slice.actions.removeStyles( { id } ) );
|
|
9
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { type PropValue } from '@elementor/editor-props';
|
|
2
|
+
import { __dispatch as dispatch, __getState as getState } from '@elementor/store';
|
|
3
|
+
import { generateUniqueId } from '@elementor/utils';
|
|
4
|
+
import { __ } from '@wordpress/i18n';
|
|
5
|
+
|
|
6
|
+
import { type OverridableProp, type OverridableProps, type OverridablePropsGroup } from '../types';
|
|
7
|
+
import { selectOverridableProps, slice } from './store';
|
|
8
|
+
|
|
9
|
+
type Props = {
|
|
10
|
+
componentId: number;
|
|
11
|
+
overrideKey: string | null;
|
|
12
|
+
elementId: string;
|
|
13
|
+
label: string;
|
|
14
|
+
groupId: string | null;
|
|
15
|
+
propKey: string;
|
|
16
|
+
elType: string;
|
|
17
|
+
widgetType: string;
|
|
18
|
+
originValue: PropValue;
|
|
19
|
+
};
|
|
20
|
+
export function setOverridableProp( {
|
|
21
|
+
componentId,
|
|
22
|
+
overrideKey,
|
|
23
|
+
elementId,
|
|
24
|
+
label,
|
|
25
|
+
groupId,
|
|
26
|
+
propKey,
|
|
27
|
+
elType,
|
|
28
|
+
widgetType,
|
|
29
|
+
originValue,
|
|
30
|
+
}: Props ): OverridableProp | undefined {
|
|
31
|
+
const overridableProps = selectOverridableProps( getState(), componentId );
|
|
32
|
+
|
|
33
|
+
if ( ! overridableProps ) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const existingOverridableProp = overrideKey ? overridableProps.props[ overrideKey ] : null;
|
|
38
|
+
|
|
39
|
+
const { props: existingProps, groups: existingGroups } = { ...overridableProps };
|
|
40
|
+
|
|
41
|
+
const { groups: updatedGroups, currentGroupId } = getUpdatedGroups(
|
|
42
|
+
existingGroups,
|
|
43
|
+
groupId || existingOverridableProp?.groupId
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const overridableProp = {
|
|
47
|
+
overrideKey: existingOverridableProp?.overrideKey || generateUniqueId( 'prop' ),
|
|
48
|
+
label,
|
|
49
|
+
elementId,
|
|
50
|
+
propKey,
|
|
51
|
+
widgetType,
|
|
52
|
+
elType,
|
|
53
|
+
originValue,
|
|
54
|
+
groupId: currentGroupId,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const props = {
|
|
58
|
+
...existingProps,
|
|
59
|
+
[ overridableProp.overrideKey ]: overridableProp,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const groups = {
|
|
63
|
+
items: {
|
|
64
|
+
...updatedGroups.items,
|
|
65
|
+
[ currentGroupId ]: getGroupWithProp( updatedGroups, currentGroupId, overridableProp ),
|
|
66
|
+
},
|
|
67
|
+
order: updatedGroups.order.includes( currentGroupId )
|
|
68
|
+
? updatedGroups.order
|
|
69
|
+
: [ ...updatedGroups.order, currentGroupId ],
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const isChangingGroups = existingOverridableProp && existingOverridableProp.groupId !== currentGroupId;
|
|
73
|
+
|
|
74
|
+
if ( isChangingGroups ) {
|
|
75
|
+
groups.items[ existingOverridableProp.groupId ] = getGroupWithoutProp(
|
|
76
|
+
updatedGroups,
|
|
77
|
+
existingOverridableProp.groupId,
|
|
78
|
+
overridableProp
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
dispatch(
|
|
83
|
+
slice.actions.setOverridableProps( {
|
|
84
|
+
componentId,
|
|
85
|
+
overridableProps: {
|
|
86
|
+
props,
|
|
87
|
+
groups,
|
|
88
|
+
},
|
|
89
|
+
} )
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
return overridableProp;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
type UpdatedGroups = { groups: OverridableProps[ 'groups' ]; currentGroupId: string };
|
|
96
|
+
|
|
97
|
+
function getUpdatedGroups( groups: OverridableProps[ 'groups' ], groupId: string | undefined ): UpdatedGroups {
|
|
98
|
+
if ( ! groupId ) {
|
|
99
|
+
// use first existing group
|
|
100
|
+
if ( groups.order.length > 0 ) {
|
|
101
|
+
return { groups, currentGroupId: groups.order[ 0 ] };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// create the first group (default)
|
|
105
|
+
return addNewGroup( groups );
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if ( ! groups.items[ groupId ] ) {
|
|
109
|
+
// fallback - if for any reason there's no such group - create it
|
|
110
|
+
return addNewGroup( groups, groupId );
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// use the existing group
|
|
114
|
+
return { groups, currentGroupId: groupId };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function addNewGroup( groups: OverridableProps[ 'groups' ], groupId?: string | undefined ): UpdatedGroups {
|
|
118
|
+
const currentGroupId = groupId || generateUniqueId( 'group' );
|
|
119
|
+
const updatedGroups = {
|
|
120
|
+
...groups,
|
|
121
|
+
items: {
|
|
122
|
+
...groups.items,
|
|
123
|
+
[ currentGroupId ]: {
|
|
124
|
+
id: currentGroupId,
|
|
125
|
+
label: __( 'Default', 'elementor' ),
|
|
126
|
+
props: [],
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
order: [ ...groups.order, currentGroupId ],
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return { groups: updatedGroups, currentGroupId };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function getGroupWithProp(
|
|
136
|
+
groups: OverridableProps[ 'groups' ],
|
|
137
|
+
groupId: string,
|
|
138
|
+
overridableProp: OverridableProp
|
|
139
|
+
): OverridablePropsGroup {
|
|
140
|
+
const group: OverridablePropsGroup = { ...groups.items[ groupId ] };
|
|
141
|
+
|
|
142
|
+
if ( ! group.props.includes( overridableProp.overrideKey ) ) {
|
|
143
|
+
group.props = [ ...group.props, overridableProp.overrideKey ];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return group;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function getGroupWithoutProp(
|
|
150
|
+
groups: OverridableProps[ 'groups' ],
|
|
151
|
+
groupId: string,
|
|
152
|
+
overridableProp: OverridableProp
|
|
153
|
+
): OverridablePropsGroup {
|
|
154
|
+
const group = { ...groups.items[ groupId ] };
|
|
155
|
+
|
|
156
|
+
if ( group ) {
|
|
157
|
+
group.props = group.props.filter( ( key ) => key !== overridableProp.overrideKey );
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return group;
|
|
161
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { type V1Document } from '@elementor/editor-documents';
|
|
2
|
+
import {
|
|
3
|
+
__createSelector as createSelector,
|
|
4
|
+
__createSlice as createSlice,
|
|
5
|
+
type PayloadAction,
|
|
6
|
+
type SliceState,
|
|
7
|
+
} from '@elementor/store';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
type Component,
|
|
11
|
+
type ComponentId,
|
|
12
|
+
type OverridableProps,
|
|
13
|
+
type PublishedComponent,
|
|
14
|
+
type StylesDefinition,
|
|
15
|
+
type UnpublishedComponent,
|
|
16
|
+
} from '../types';
|
|
17
|
+
import { loadComponents } from './thunks';
|
|
18
|
+
|
|
19
|
+
type GetComponentResponse = PublishedComponent[];
|
|
20
|
+
|
|
21
|
+
type Status = 'idle' | 'pending' | 'error';
|
|
22
|
+
|
|
23
|
+
type ComponentsState = {
|
|
24
|
+
data: PublishedComponent[];
|
|
25
|
+
unpublishedData: UnpublishedComponent[];
|
|
26
|
+
loadStatus: Status;
|
|
27
|
+
styles: StylesDefinition;
|
|
28
|
+
createdThisSession: Component[ 'uid' ][];
|
|
29
|
+
path: ComponentsPathItem[];
|
|
30
|
+
currentComponentId: V1Document[ 'id' ] | null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type ComponentsSlice = SliceState< typeof slice >;
|
|
34
|
+
|
|
35
|
+
export type ComponentsPathItem = {
|
|
36
|
+
instanceId?: string;
|
|
37
|
+
componentId: V1Document[ 'id' ];
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const initialState: ComponentsState = {
|
|
41
|
+
data: [],
|
|
42
|
+
unpublishedData: [],
|
|
43
|
+
loadStatus: 'idle',
|
|
44
|
+
styles: {},
|
|
45
|
+
createdThisSession: [],
|
|
46
|
+
path: [],
|
|
47
|
+
currentComponentId: null,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const SLICE_NAME = 'components';
|
|
51
|
+
|
|
52
|
+
export const slice = createSlice( {
|
|
53
|
+
name: SLICE_NAME,
|
|
54
|
+
initialState,
|
|
55
|
+
reducers: {
|
|
56
|
+
add: ( state, { payload }: PayloadAction< PublishedComponent | PublishedComponent[] > ) => {
|
|
57
|
+
if ( Array.isArray( payload ) ) {
|
|
58
|
+
state.data = [ ...state.data, ...payload ];
|
|
59
|
+
} else {
|
|
60
|
+
state.data.unshift( payload );
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
load: ( state, { payload }: PayloadAction< PublishedComponent[] > ) => {
|
|
64
|
+
state.data = payload;
|
|
65
|
+
},
|
|
66
|
+
addUnpublished: ( state, { payload }: PayloadAction< UnpublishedComponent > ) => {
|
|
67
|
+
state.unpublishedData.unshift( payload );
|
|
68
|
+
},
|
|
69
|
+
resetUnpublished: ( state ) => {
|
|
70
|
+
state.unpublishedData = [];
|
|
71
|
+
},
|
|
72
|
+
removeStyles( state, { payload }: PayloadAction< { id: ComponentId } > ) {
|
|
73
|
+
const { [ payload.id ]: _, ...rest } = state.styles;
|
|
74
|
+
|
|
75
|
+
state.styles = rest;
|
|
76
|
+
},
|
|
77
|
+
addStyles: ( state, { payload } ) => {
|
|
78
|
+
state.styles = { ...state.styles, ...payload };
|
|
79
|
+
},
|
|
80
|
+
addCreatedThisSession: ( state, { payload }: PayloadAction< string > ) => {
|
|
81
|
+
state.createdThisSession.push( payload );
|
|
82
|
+
},
|
|
83
|
+
setCurrentComponentId: ( state, { payload }: PayloadAction< V1Document[ 'id' ] | null > ) => {
|
|
84
|
+
state.currentComponentId = payload;
|
|
85
|
+
},
|
|
86
|
+
setPath: ( state, { payload }: PayloadAction< ComponentsPathItem[] > ) => {
|
|
87
|
+
state.path = payload;
|
|
88
|
+
},
|
|
89
|
+
setOverridableProps: (
|
|
90
|
+
state,
|
|
91
|
+
{ payload }: PayloadAction< { componentId: ComponentId; overridableProps: OverridableProps } >
|
|
92
|
+
) => {
|
|
93
|
+
const component = state.data.find( ( comp ) => comp.id === payload.componentId );
|
|
94
|
+
|
|
95
|
+
if ( ! component ) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
component.overridableProps = payload.overridableProps;
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
extraReducers: ( builder ) => {
|
|
103
|
+
builder.addCase( loadComponents.fulfilled, ( state, { payload }: PayloadAction< GetComponentResponse > ) => {
|
|
104
|
+
state.data = payload;
|
|
105
|
+
state.loadStatus = 'idle';
|
|
106
|
+
} );
|
|
107
|
+
builder.addCase( loadComponents.pending, ( state ) => {
|
|
108
|
+
state.loadStatus = 'pending';
|
|
109
|
+
} );
|
|
110
|
+
builder.addCase( loadComponents.rejected, ( state ) => {
|
|
111
|
+
state.loadStatus = 'error';
|
|
112
|
+
} );
|
|
113
|
+
},
|
|
114
|
+
} );
|
|
115
|
+
|
|
116
|
+
const selectData = ( state: ComponentsSlice ) => state[ SLICE_NAME ].data;
|
|
117
|
+
const selectLoadStatus = ( state: ComponentsSlice ) => state[ SLICE_NAME ].loadStatus;
|
|
118
|
+
const selectStylesDefinitions = ( state: ComponentsSlice ) => state[ SLICE_NAME ].styles ?? {};
|
|
119
|
+
const selectUnpublishedData = ( state: ComponentsSlice ) => state[ SLICE_NAME ].unpublishedData;
|
|
120
|
+
const getCreatedThisSession = ( state: ComponentsSlice ) => state[ SLICE_NAME ].createdThisSession;
|
|
121
|
+
const getPath = ( state: ComponentsSlice ) => state[ SLICE_NAME ].path;
|
|
122
|
+
const getCurrentComponentId = ( state: ComponentsSlice ) => state[ SLICE_NAME ].currentComponentId;
|
|
123
|
+
const selectComponent = ( state: ComponentsSlice, componentId: ComponentId ) =>
|
|
124
|
+
state[ SLICE_NAME ].data.find( ( component ) => component.id === componentId );
|
|
125
|
+
|
|
126
|
+
export const selectComponents = createSelector(
|
|
127
|
+
selectData,
|
|
128
|
+
selectUnpublishedData,
|
|
129
|
+
( data: PublishedComponent[], unpublishedData: UnpublishedComponent[] ) => [
|
|
130
|
+
...unpublishedData.map( ( item ) => ( { uid: item.uid, name: item.name } ) ),
|
|
131
|
+
...data,
|
|
132
|
+
]
|
|
133
|
+
);
|
|
134
|
+
export const selectUnpublishedComponents = createSelector(
|
|
135
|
+
selectUnpublishedData,
|
|
136
|
+
( unpublishedData: UnpublishedComponent[] ) => unpublishedData
|
|
137
|
+
);
|
|
138
|
+
export const selectLoadIsPending = createSelector( selectLoadStatus, ( status ) => status === 'pending' );
|
|
139
|
+
export const selectLoadIsError = createSelector( selectLoadStatus, ( status ) => status === 'error' );
|
|
140
|
+
export const selectStyles = ( state: ComponentsSlice ) => state[ SLICE_NAME ].styles ?? {};
|
|
141
|
+
export const selectFlatStyles = createSelector( selectStylesDefinitions, ( data ) => Object.values( data ).flat() );
|
|
142
|
+
export const selectCreatedThisSession = createSelector(
|
|
143
|
+
getCreatedThisSession,
|
|
144
|
+
( createdThisSession ) => createdThisSession
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const DEFAULT_OVERRIDABLE_PROPS: OverridableProps = {
|
|
148
|
+
props: {},
|
|
149
|
+
groups: {
|
|
150
|
+
items: {},
|
|
151
|
+
order: [],
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export const selectOverridableProps = createSelector(
|
|
156
|
+
selectComponent,
|
|
157
|
+
( component: PublishedComponent | undefined ) => {
|
|
158
|
+
if ( ! component ) {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
return component.overridableProps ?? DEFAULT_OVERRIDABLE_PROPS;
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
export const selectPath = createSelector( getPath, ( path ) => path );
|
|
165
|
+
export const selectCurrentComponentId = createSelector(
|
|
166
|
+
getCurrentComponentId,
|
|
167
|
+
( currentComponentId ) => currentComponentId
|
|
168
|
+
);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { __createAsyncThunk as createAsyncThunk } from '@elementor/store';
|
|
2
|
+
|
|
3
|
+
import { apiClient } from '../api';
|
|
4
|
+
|
|
5
|
+
const loadComponents = createAsyncThunk( 'components/load', async () => {
|
|
6
|
+
const response = await apiClient.get();
|
|
7
|
+
return response;
|
|
8
|
+
} );
|
|
9
|
+
|
|
10
|
+
export { loadComponents };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type Container, type DocumentSaveStatus } from '../types';
|
|
2
|
+
import { createComponentsBeforeSave } from './create-components-before-save';
|
|
3
|
+
import { updateComponentsBeforeSave } from './update-components-before-save';
|
|
4
|
+
|
|
5
|
+
type Options = {
|
|
6
|
+
container: Container;
|
|
7
|
+
status: DocumentSaveStatus;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const beforeSave = ( { container, status }: Options ) => {
|
|
11
|
+
return Promise.all( [
|
|
12
|
+
createComponentsBeforeSave( { container, status } ),
|
|
13
|
+
updateComponentsBeforeSave( { container, status } ),
|
|
14
|
+
] );
|
|
15
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { updateElementSettings, type V1ElementData } from '@elementor/editor-elements';
|
|
2
|
+
import { __dispatch as dispatch, __getState as getState } from '@elementor/store';
|
|
3
|
+
|
|
4
|
+
import { apiClient } from '../api';
|
|
5
|
+
import { selectUnpublishedComponents, slice } from '../store/store';
|
|
6
|
+
import {
|
|
7
|
+
type ComponentInstancePropValue,
|
|
8
|
+
type Container,
|
|
9
|
+
type DocumentSaveStatus,
|
|
10
|
+
type UnpublishedComponent,
|
|
11
|
+
} from '../types';
|
|
12
|
+
|
|
13
|
+
export async function createComponentsBeforeSave( {
|
|
14
|
+
container,
|
|
15
|
+
status,
|
|
16
|
+
}: {
|
|
17
|
+
container: Container;
|
|
18
|
+
status: DocumentSaveStatus;
|
|
19
|
+
} ) {
|
|
20
|
+
const unpublishedComponents = selectUnpublishedComponents( getState() );
|
|
21
|
+
|
|
22
|
+
if ( ! unpublishedComponents.length ) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const uidToComponentId = await createComponents( unpublishedComponents, status );
|
|
28
|
+
|
|
29
|
+
const elements = container.model.get( 'elements' ).toJSON();
|
|
30
|
+
updateComponentInstances( elements, uidToComponentId );
|
|
31
|
+
|
|
32
|
+
dispatch(
|
|
33
|
+
slice.actions.add(
|
|
34
|
+
unpublishedComponents.map( ( component ) => ( {
|
|
35
|
+
id: uidToComponentId.get( component.uid ) as number,
|
|
36
|
+
name: component.name,
|
|
37
|
+
uid: component.uid,
|
|
38
|
+
} ) )
|
|
39
|
+
)
|
|
40
|
+
);
|
|
41
|
+
dispatch( slice.actions.resetUnpublished() );
|
|
42
|
+
} catch ( error ) {
|
|
43
|
+
throw new Error( `Failed to publish components and update component instances: ${ error }` );
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function createComponents(
|
|
48
|
+
components: UnpublishedComponent[],
|
|
49
|
+
status: DocumentSaveStatus
|
|
50
|
+
): Promise< Map< string, number > > {
|
|
51
|
+
const response = await apiClient.create( {
|
|
52
|
+
status,
|
|
53
|
+
items: components.map( ( component ) => ( {
|
|
54
|
+
uid: component.uid,
|
|
55
|
+
title: component.name,
|
|
56
|
+
elements: component.elements,
|
|
57
|
+
} ) ),
|
|
58
|
+
} );
|
|
59
|
+
|
|
60
|
+
const map = new Map< string, number >();
|
|
61
|
+
|
|
62
|
+
Object.entries( response ).forEach( ( [ key, value ] ) => {
|
|
63
|
+
map.set( key, value );
|
|
64
|
+
} );
|
|
65
|
+
|
|
66
|
+
return map;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function updateComponentInstances( elements: V1ElementData[], uidToComponentId: Map< string, number > ): void {
|
|
70
|
+
elements.forEach( ( element ) => {
|
|
71
|
+
const { shouldUpdate, newComponentId } = shouldUpdateElement( element, uidToComponentId );
|
|
72
|
+
if ( shouldUpdate ) {
|
|
73
|
+
updateElementComponentId( element.id, newComponentId );
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if ( element.elements ) {
|
|
77
|
+
updateComponentInstances( element.elements, uidToComponentId );
|
|
78
|
+
}
|
|
79
|
+
} );
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function shouldUpdateElement(
|
|
83
|
+
element: V1ElementData,
|
|
84
|
+
uidToComponentId: Map< string, number >
|
|
85
|
+
): { shouldUpdate: true; newComponentId: number } | { shouldUpdate: false; newComponentId: null } {
|
|
86
|
+
if ( element.widgetType === 'e-component' ) {
|
|
87
|
+
const currentComponentId = ( element.settings?.component_instance as ComponentInstancePropValue< string > )
|
|
88
|
+
?.value?.component_id;
|
|
89
|
+
|
|
90
|
+
if ( currentComponentId && uidToComponentId.has( currentComponentId ) ) {
|
|
91
|
+
return { shouldUpdate: true, newComponentId: uidToComponentId.get( currentComponentId ) as number };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return { shouldUpdate: false, newComponentId: null };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function updateElementComponentId( elementId: string, componentId: number ): void {
|
|
98
|
+
updateElementSettings( {
|
|
99
|
+
id: elementId,
|
|
100
|
+
props: {
|
|
101
|
+
component_instance: {
|
|
102
|
+
$$type: 'component-instance',
|
|
103
|
+
value: { component_id: componentId },
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
withHistory: false,
|
|
107
|
+
} );
|
|
108
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { isDocumentDirty } from '@elementor/editor-documents';
|
|
2
|
+
|
|
3
|
+
import { apiClient } from '../api';
|
|
4
|
+
import { type Container, type DocumentSaveStatus } from '../types';
|
|
5
|
+
import { getComponentDocumentData, invalidateComponentDocumentData } from '../utils/component-document-data';
|
|
6
|
+
import { getComponentIds } from '../utils/get-component-ids';
|
|
7
|
+
|
|
8
|
+
type Options = {
|
|
9
|
+
status: DocumentSaveStatus;
|
|
10
|
+
container: Container;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export async function updateComponentsBeforeSave( { status, container }: Options ) {
|
|
14
|
+
if ( status !== 'publish' ) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const elements = container.model.get( 'elements' ).toJSON();
|
|
19
|
+
|
|
20
|
+
const componentIds = await getComponentIds( elements );
|
|
21
|
+
|
|
22
|
+
const componentDocumentData = await Promise.all( componentIds.map( getComponentDocumentData ) );
|
|
23
|
+
|
|
24
|
+
const draftIds = componentDocumentData
|
|
25
|
+
.filter( ( document ) => !! document )
|
|
26
|
+
.filter( isDocumentDirty )
|
|
27
|
+
.map( ( document ) => document.id );
|
|
28
|
+
|
|
29
|
+
if ( draftIds.length === 0 ) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
await apiClient.updateStatuses( draftIds, 'publish' );
|
|
34
|
+
|
|
35
|
+
draftIds.forEach( ( id ) => invalidateComponentDocumentData( id ) );
|
|
36
|
+
}
|