@elementor/editor-editing-panel 4.1.0-838 → 4.1.0-beta2
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 +155 -87
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +107 -38
- package/dist/index.mjs.map +1 -1
- package/package.json +21 -24
- package/src/apply-unapply-actions.ts +17 -1
- package/src/components/creatable-autocomplete/use-filter-options.ts +10 -2
- package/src/components/css-classes/css-class-selector.tsx +99 -28
- package/src/components/design-system-import/components/conflict-options.tsx +0 -67
- package/src/components/design-system-import/components/trigger-button.tsx +0 -33
- package/src/components/design-system-import/hooks/use-dialog-state.ts +0 -24
- package/src/components/design-system-import/hooks/use-import-request.ts +0 -38
- package/src/components/design-system-import/import-design-system-dialog.tsx +0 -89
- package/src/components/design-system-import/import-notifications.tsx +0 -57
- package/src/components/design-system-import/types.ts +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-editing-panel",
|
|
3
|
-
"version": "4.1.0-
|
|
3
|
+
"version": "4.1.0-beta2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "Elementor Team",
|
|
6
6
|
"homepage": "https://elementor.com/",
|
|
@@ -39,31 +39,28 @@
|
|
|
39
39
|
"dev": "tsup --config=../../tsup.dev.ts"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@elementor/editor": "4.1.0-
|
|
43
|
-
"@elementor/editor-canvas": "4.1.0-
|
|
44
|
-
"@elementor/editor-controls": "4.1.0-
|
|
45
|
-
"@elementor/editor-documents": "4.1.0-
|
|
46
|
-
"@elementor/editor-elements": "4.1.0-
|
|
47
|
-
"@elementor/editor-interactions": "4.1.0-
|
|
48
|
-
"@elementor/editor-
|
|
49
|
-
"@elementor/editor-
|
|
50
|
-
"@elementor/editor-
|
|
51
|
-
"@elementor/editor-
|
|
52
|
-
"@elementor/editor-styles": "4.1.0-
|
|
53
|
-
"@elementor/editor-
|
|
54
|
-
"@elementor/editor-
|
|
55
|
-
"@elementor/editor-v1-adapters": "4.1.0-838",
|
|
56
|
-
"@elementor/http-client": "4.1.0-838",
|
|
42
|
+
"@elementor/editor": "4.1.0-beta2",
|
|
43
|
+
"@elementor/editor-canvas": "4.1.0-beta2",
|
|
44
|
+
"@elementor/editor-controls": "4.1.0-beta2",
|
|
45
|
+
"@elementor/editor-documents": "4.1.0-beta2",
|
|
46
|
+
"@elementor/editor-elements": "4.1.0-beta2",
|
|
47
|
+
"@elementor/editor-interactions": "4.1.0-beta2",
|
|
48
|
+
"@elementor/editor-panels": "4.1.0-beta2",
|
|
49
|
+
"@elementor/editor-props": "4.1.0-beta2",
|
|
50
|
+
"@elementor/editor-responsive": "4.1.0-beta2",
|
|
51
|
+
"@elementor/editor-styles": "4.1.0-beta2",
|
|
52
|
+
"@elementor/editor-styles-repository": "4.1.0-beta2",
|
|
53
|
+
"@elementor/editor-ui": "4.1.0-beta2",
|
|
54
|
+
"@elementor/editor-v1-adapters": "4.1.0-beta2",
|
|
57
55
|
"@elementor/icons": "~1.75.1",
|
|
58
|
-
"@elementor/editor-variables": "4.1.0-
|
|
59
|
-
"@elementor/locations": "4.1.0-
|
|
60
|
-
"@elementor/menus": "4.1.0-
|
|
61
|
-
"@elementor/
|
|
62
|
-
"@elementor/
|
|
63
|
-
"@elementor/session": "4.1.0-838",
|
|
56
|
+
"@elementor/editor-variables": "4.1.0-beta2",
|
|
57
|
+
"@elementor/locations": "4.1.0-beta2",
|
|
58
|
+
"@elementor/menus": "4.1.0-beta2",
|
|
59
|
+
"@elementor/schema": "4.1.0-beta2",
|
|
60
|
+
"@elementor/session": "4.1.0-beta2",
|
|
64
61
|
"@elementor/ui": "1.37.5",
|
|
65
|
-
"@elementor/utils": "4.1.0-
|
|
66
|
-
"@elementor/wp-media": "4.1.0-
|
|
62
|
+
"@elementor/utils": "4.1.0-beta2",
|
|
63
|
+
"@elementor/wp-media": "4.1.0-beta2",
|
|
67
64
|
"@wordpress/i18n": "^5.13.0"
|
|
68
65
|
},
|
|
69
66
|
"peerDependencies": {
|
|
@@ -2,6 +2,7 @@ import { setDocumentModifiedStatus } from '@elementor/editor-documents';
|
|
|
2
2
|
import { getElementSetting, updateElementSettings } from '@elementor/editor-elements';
|
|
3
3
|
import { classesPropTypeUtil, type ClassesPropValue } from '@elementor/editor-props';
|
|
4
4
|
import { type StyleDefinitionID } from '@elementor/editor-styles';
|
|
5
|
+
import { stylesRepository } from '@elementor/editor-styles-repository';
|
|
5
6
|
|
|
6
7
|
// Externalized for use outside of Hooks
|
|
7
8
|
|
|
@@ -16,6 +17,21 @@ export function doApplyClasses( elementId: string, classIds: StyleDefinitionID[]
|
|
|
16
17
|
withHistory: false,
|
|
17
18
|
} );
|
|
18
19
|
setDocumentModifiedStatus( true );
|
|
20
|
+
|
|
21
|
+
ensureClassesAreLoaded( classIds );
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function ensureClassesAreLoaded( classIds: StyleDefinitionID[] ) {
|
|
25
|
+
const providers = stylesRepository.getProviders();
|
|
26
|
+
|
|
27
|
+
classIds.forEach( ( classId ) => {
|
|
28
|
+
stylesRepository.getProviderByKey( classId )?.actions.get( classId );
|
|
29
|
+
const owningProvider = providers.find( ( provider ) =>
|
|
30
|
+
provider.actions.all().some( ( style ) => style.id === classId )
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
owningProvider?.actions.get( classId );
|
|
34
|
+
} );
|
|
19
35
|
}
|
|
20
36
|
|
|
21
37
|
export function doUnapplyClass( elementId: string, classId: StyleDefinitionID, classesPropType = 'classes' ) {
|
|
@@ -24,7 +40,7 @@ export function doUnapplyClass( elementId: string, classId: StyleDefinitionID, c
|
|
|
24
40
|
return false;
|
|
25
41
|
}
|
|
26
42
|
|
|
27
|
-
const updatedClassIds = appliedClasses.filter( ( id ) => id !== classId );
|
|
43
|
+
const updatedClassIds = appliedClasses.filter( ( id: StyleDefinitionID ) => id !== classId );
|
|
28
44
|
doApplyClasses( elementId, updatedClassIds, classesPropType );
|
|
29
45
|
return true;
|
|
30
46
|
}
|
|
@@ -2,6 +2,12 @@ import { createFilterOptions } from '@elementor/ui';
|
|
|
2
2
|
|
|
3
3
|
import { type InternalOption, type Option } from './types';
|
|
4
4
|
|
|
5
|
+
const STRIP_NON_CLASS_CHARS = /[^a-zA-Z0-9_-]/g;
|
|
6
|
+
|
|
7
|
+
function normalizeClassSearch( value: string ) {
|
|
8
|
+
return value.replace( STRIP_NON_CLASS_CHARS, '' ).toLowerCase();
|
|
9
|
+
}
|
|
10
|
+
|
|
5
11
|
export function useFilterOptions< TOption extends Option >( parameters: {
|
|
6
12
|
options: TOption[];
|
|
7
13
|
selected: TOption[];
|
|
@@ -10,7 +16,9 @@ export function useFilterOptions< TOption extends Option >( parameters: {
|
|
|
10
16
|
} ) {
|
|
11
17
|
const { options, selected, onCreate, entityName } = parameters;
|
|
12
18
|
|
|
13
|
-
const filter = createFilterOptions< InternalOption< TOption > >(
|
|
19
|
+
const filter = createFilterOptions< InternalOption< TOption > >( {
|
|
20
|
+
matchFrom: 'any',
|
|
21
|
+
} );
|
|
14
22
|
|
|
15
23
|
const filterOptions = (
|
|
16
24
|
optionList: InternalOption< TOption >[],
|
|
@@ -23,7 +31,7 @@ export function useFilterOptions< TOption extends Option >( parameters: {
|
|
|
23
31
|
|
|
24
32
|
const filteredOptions = filter(
|
|
25
33
|
optionList.filter( ( option ) => ! selectedValues.includes( option.value ) ),
|
|
26
|
-
params
|
|
34
|
+
{ ...params, inputValue: normalizeClassSearch( params.inputValue ) }
|
|
27
35
|
);
|
|
28
36
|
|
|
29
37
|
const isExisting = options.some( ( option ) => params.inputValue === option.label );
|
|
@@ -11,11 +11,13 @@ import {
|
|
|
11
11
|
validateStyleLabel,
|
|
12
12
|
} from '@elementor/editor-styles-repository';
|
|
13
13
|
import { InfoAlert, WarningInfotip } from '@elementor/editor-ui';
|
|
14
|
+
import { isExperimentActive } from '@elementor/editor-v1-adapters';
|
|
14
15
|
import { ColorSwatchIcon, MapPinIcon } from '@elementor/icons';
|
|
15
16
|
import { createLocation } from '@elementor/locations';
|
|
16
17
|
import {
|
|
17
18
|
type AutocompleteChangeReason,
|
|
18
19
|
Box,
|
|
20
|
+
Button,
|
|
19
21
|
Chip,
|
|
20
22
|
type ChipOwnProps,
|
|
21
23
|
FormLabel,
|
|
@@ -43,6 +45,22 @@ import { useApplyClass, useCreateAndApplyClass, useUnapplyClass } from './use-ap
|
|
|
43
45
|
const ID = 'elementor-css-class-selector';
|
|
44
46
|
const TAGS_LIMIT = 50;
|
|
45
47
|
|
|
48
|
+
const EVENT_OPEN_GLOBAL_CLASSES_MANAGER = 'elementor/open-global-classes-manager';
|
|
49
|
+
const EVENT_TOGGLE_DESIGN_SYSTEM = 'elementor/toggle-design-system';
|
|
50
|
+
|
|
51
|
+
function openClassManagerPanel() {
|
|
52
|
+
if ( isExperimentActive( 'e_editor_design_system_panel' ) ) {
|
|
53
|
+
window.dispatchEvent(
|
|
54
|
+
new CustomEvent( EVENT_TOGGLE_DESIGN_SYSTEM, {
|
|
55
|
+
detail: { tab: 'classes' as const },
|
|
56
|
+
} )
|
|
57
|
+
);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
window.dispatchEvent( new CustomEvent( EVENT_OPEN_GLOBAL_CLASSES_MANAGER ) );
|
|
62
|
+
}
|
|
63
|
+
|
|
46
64
|
type StyleDefOption = Option & {
|
|
47
65
|
color: ChipOwnProps[ 'color' ];
|
|
48
66
|
icon: ReactElement | null;
|
|
@@ -74,7 +92,7 @@ export function CssClassSelector() {
|
|
|
74
92
|
const [ renameError, setRenameError ] = useState< string | null >( null );
|
|
75
93
|
|
|
76
94
|
const handleSelect = useHandleSelect();
|
|
77
|
-
const { create, validate, entityName } = useCreateAction();
|
|
95
|
+
const { create, validate, entityName, isAtLimit, limitCount } = useCreateAction();
|
|
78
96
|
|
|
79
97
|
const appliedOptions = useAppliedOptions( options );
|
|
80
98
|
const active = appliedOptions.find( ( option ) => option.value === activeId ) ?? EMPTY_OPTION;
|
|
@@ -114,7 +132,13 @@ export function CssClassSelector() {
|
|
|
114
132
|
onCreate={ create ?? undefined }
|
|
115
133
|
validate={ validate ?? undefined }
|
|
116
134
|
limitTags={ TAGS_LIMIT }
|
|
117
|
-
renderEmptyState={
|
|
135
|
+
renderEmptyState={
|
|
136
|
+
isAtLimit && typeof limitCount === 'number'
|
|
137
|
+
? ( props ) => (
|
|
138
|
+
<LimitReachedEmptyState limitCount={ limitCount } onClear={ props.onClear } />
|
|
139
|
+
)
|
|
140
|
+
: EmptyState
|
|
141
|
+
}
|
|
118
142
|
getLimitTagsText={ ( more ) => (
|
|
119
143
|
<Chip size="tiny" variant="standard" label={ `+${ more }` } clickable />
|
|
120
144
|
) }
|
|
@@ -166,7 +190,9 @@ export function CssClassSelector() {
|
|
|
166
190
|
);
|
|
167
191
|
}
|
|
168
192
|
|
|
169
|
-
|
|
193
|
+
type EmptyStateProps = { searchValue: string; onClear: () => void };
|
|
194
|
+
|
|
195
|
+
const EmptyStateLayout = ( { searchValue, onClear, children }: EmptyStateProps & { children: React.ReactNode } ) => (
|
|
170
196
|
<Box sx={ { py: 4 } }>
|
|
171
197
|
<Stack
|
|
172
198
|
gap={ 1 }
|
|
@@ -181,11 +207,7 @@ const EmptyState = ( { searchValue, onClear }: { searchValue: string; onClear: (
|
|
|
181
207
|
<br />
|
|
182
208
|
“{ searchValue }”.
|
|
183
209
|
</Typography>
|
|
184
|
-
|
|
185
|
-
{ __( 'With your current role,', 'elementor' ) }
|
|
186
|
-
<br />
|
|
187
|
-
{ __( 'you can only use existing classes.', 'elementor' ) }
|
|
188
|
-
</Typography>
|
|
210
|
+
{ children }
|
|
189
211
|
<Link color="text.secondary" variant="caption" component="button" onClick={ onClear }>
|
|
190
212
|
{ __( 'Clear & try again', 'elementor' ) }
|
|
191
213
|
</Link>
|
|
@@ -193,6 +215,62 @@ const EmptyState = ( { searchValue, onClear }: { searchValue: string; onClear: (
|
|
|
193
215
|
</Box>
|
|
194
216
|
);
|
|
195
217
|
|
|
218
|
+
const EmptyState = ( props: EmptyStateProps ) => (
|
|
219
|
+
<EmptyStateLayout { ...props }>
|
|
220
|
+
<Typography align="center" variant="caption" sx={ { mb: 2 } }>
|
|
221
|
+
{ __( 'With your current role,', 'elementor' ) }
|
|
222
|
+
<br />
|
|
223
|
+
{ __( 'you can only use existing classes.', 'elementor' ) }
|
|
224
|
+
</Typography>
|
|
225
|
+
</EmptyStateLayout>
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const LimitReachedEmptyState = ( {
|
|
229
|
+
limitCount,
|
|
230
|
+
onClear,
|
|
231
|
+
}: Pick< EmptyStateProps, 'onClear' > & { limitCount: number } ) => (
|
|
232
|
+
<Box sx={ { py: 4 } }>
|
|
233
|
+
<Stack
|
|
234
|
+
gap={ 1 }
|
|
235
|
+
alignItems="center"
|
|
236
|
+
color="text.secondary"
|
|
237
|
+
justifyContent="center"
|
|
238
|
+
sx={ { px: 1, m: 'auto', maxWidth: '260px' } }
|
|
239
|
+
>
|
|
240
|
+
<ColorSwatchIcon sx={ { transform: 'rotate(90deg)' } } fontSize="large" />
|
|
241
|
+
<Typography align="center" variant="subtitle2">
|
|
242
|
+
{
|
|
243
|
+
/* translators: %s is the maximum number of classes */
|
|
244
|
+
__( 'Limit of %s classes reached', 'elementor' ).replace( '%s', String( limitCount ) )
|
|
245
|
+
}
|
|
246
|
+
</Typography>
|
|
247
|
+
<Typography align="center" variant="caption" component="div">
|
|
248
|
+
{ __( 'Remove a class to create a new one.', 'elementor' ) }{ ' ' }
|
|
249
|
+
<Link
|
|
250
|
+
color="inherit"
|
|
251
|
+
variant="caption"
|
|
252
|
+
component="button"
|
|
253
|
+
onClick={ onClear }
|
|
254
|
+
sx={ { verticalAlign: 'baseline' } }
|
|
255
|
+
>
|
|
256
|
+
{ __( 'Clear', 'elementor' ) }
|
|
257
|
+
</Link>
|
|
258
|
+
</Typography>
|
|
259
|
+
<Button
|
|
260
|
+
variant="outlined"
|
|
261
|
+
color="secondary"
|
|
262
|
+
size="small"
|
|
263
|
+
onClick={ () => {
|
|
264
|
+
openClassManagerPanel();
|
|
265
|
+
onClear();
|
|
266
|
+
} }
|
|
267
|
+
>
|
|
268
|
+
{ __( 'Class Manager', 'elementor' ) }
|
|
269
|
+
</Button>
|
|
270
|
+
</Stack>
|
|
271
|
+
</Box>
|
|
272
|
+
);
|
|
273
|
+
|
|
196
274
|
const updateClassByProvider = ( provider: string | null, data: UpdateActionPayload ) => {
|
|
197
275
|
if ( ! provider ) {
|
|
198
276
|
return;
|
|
@@ -250,6 +328,18 @@ function useCreateAction() {
|
|
|
250
328
|
return {};
|
|
251
329
|
}
|
|
252
330
|
|
|
331
|
+
const entityName =
|
|
332
|
+
provider.labels.singular && provider.labels.plural
|
|
333
|
+
? ( provider.labels as CreatableAutocompleteProps< StyleDefOption >[ 'entityName' ] )
|
|
334
|
+
: undefined;
|
|
335
|
+
|
|
336
|
+
const validate = ( newClassLabel: string, event: ValidationEvent ): ValidationResult =>
|
|
337
|
+
validateStyleLabel( newClassLabel, event );
|
|
338
|
+
|
|
339
|
+
if ( hasReachedLimit( provider ) ) {
|
|
340
|
+
return { entityName, isAtLimit: true as const, limitCount: provider.limit, validate };
|
|
341
|
+
}
|
|
342
|
+
|
|
253
343
|
const create = ( classLabel: string ) => {
|
|
254
344
|
const { createdId } = createAction( { classLabel } );
|
|
255
345
|
trackStyles( provider.getKey() ?? '', 'classCreated', {
|
|
@@ -259,26 +349,7 @@ function useCreateAction() {
|
|
|
259
349
|
} );
|
|
260
350
|
};
|
|
261
351
|
|
|
262
|
-
|
|
263
|
-
if ( hasReachedLimit( provider ) ) {
|
|
264
|
-
return {
|
|
265
|
-
isValid: false,
|
|
266
|
-
/* translators: %s is the maximum number of classes */
|
|
267
|
-
errorMessage: __(
|
|
268
|
-
'You’ve reached the limit of %s classes. Please remove an existing one to create a new class.',
|
|
269
|
-
'elementor'
|
|
270
|
-
).replace( '%s', provider.limit.toString() ),
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
return validateStyleLabel( newClassLabel, event );
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
const entityName =
|
|
277
|
-
provider.labels.singular && provider.labels.plural
|
|
278
|
-
? ( provider.labels as CreatableAutocompleteProps< StyleDefOption >[ 'entityName' ] )
|
|
279
|
-
: undefined;
|
|
280
|
-
|
|
281
|
-
return { create, validate, entityName };
|
|
352
|
+
return { create, validate, entityName, isAtLimit: false as const };
|
|
282
353
|
}
|
|
283
354
|
|
|
284
355
|
function hasReachedLimit( provider: StylesProvider ) {
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { Card, CardActionArea, Radio, RadioGroup, Stack, Typography } from '@elementor/ui';
|
|
3
|
-
import { __ } from '@wordpress/i18n';
|
|
4
|
-
|
|
5
|
-
import { type ConflictStrategy } from '../types';
|
|
6
|
-
|
|
7
|
-
type Option = {
|
|
8
|
-
value: ConflictStrategy;
|
|
9
|
-
title: string;
|
|
10
|
-
description: string;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const getOptions = (): Option[] => [
|
|
14
|
-
{
|
|
15
|
-
value: 'replace',
|
|
16
|
-
title: __( 'Replace existing values', 'elementor' ),
|
|
17
|
-
description: __( 'Imported design system values will overwrite existing variables and classes.', 'elementor' ),
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
value: 'keep',
|
|
21
|
-
title: __( 'Keep existing values', 'elementor' ),
|
|
22
|
-
description: __( 'Existing variables and classes will not change.', 'elementor' ),
|
|
23
|
-
},
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
type Props = {
|
|
27
|
-
value: ConflictStrategy | null;
|
|
28
|
-
onChange: ( value: ConflictStrategy ) => void;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export const ConflictOptions = ( { value, onChange }: Props ) => {
|
|
32
|
-
const options = getOptions();
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<Stack spacing={ 1 }>
|
|
36
|
-
<Typography variant="body1">
|
|
37
|
-
{ __( 'How to handle conflicts with existing variables or classes?', 'elementor' ) }
|
|
38
|
-
</Typography>
|
|
39
|
-
<RadioGroup
|
|
40
|
-
value={ value ?? '' }
|
|
41
|
-
onChange={ ( _: unknown, next: string ) => onChange( next as ConflictStrategy ) }
|
|
42
|
-
>
|
|
43
|
-
<Stack spacing={ 1 }>
|
|
44
|
-
{ options.map( ( option ) => (
|
|
45
|
-
<Card key={ option.value } variant="outlined">
|
|
46
|
-
<CardActionArea onClick={ () => onChange( option.value ) }>
|
|
47
|
-
<Stack direction="row" alignItems="center" spacing={ 2 } padding={ 2 }>
|
|
48
|
-
<Radio
|
|
49
|
-
value={ option.value }
|
|
50
|
-
checked={ value === option.value }
|
|
51
|
-
inputProps={ { 'aria-label': option.title } }
|
|
52
|
-
/>
|
|
53
|
-
<Stack direction="column" spacing={ 0.5 }>
|
|
54
|
-
<Typography variant="subtitle2">{ option.title }</Typography>
|
|
55
|
-
<Typography variant="caption" color="text.secondary">
|
|
56
|
-
{ option.description }
|
|
57
|
-
</Typography>
|
|
58
|
-
</Stack>
|
|
59
|
-
</Stack>
|
|
60
|
-
</CardActionArea>
|
|
61
|
-
</Card>
|
|
62
|
-
) ) }
|
|
63
|
-
</Stack>
|
|
64
|
-
</RadioGroup>
|
|
65
|
-
</Stack>
|
|
66
|
-
);
|
|
67
|
-
};
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { closeDialog, openDialog } from '@elementor/editor-ui';
|
|
3
|
-
import { UploadIcon } from '@elementor/icons';
|
|
4
|
-
import { useIsMutating } from '@elementor/query';
|
|
5
|
-
import { IconButton, Tooltip } from '@elementor/ui';
|
|
6
|
-
import { __ } from '@wordpress/i18n';
|
|
7
|
-
|
|
8
|
-
import { IMPORT_DESIGN_SYSTEM_MUTATION_KEY } from '../hooks/use-import-request';
|
|
9
|
-
import { ImportDesignSystemDialog } from '../import-design-system-dialog';
|
|
10
|
-
|
|
11
|
-
export const TriggerButton = () => {
|
|
12
|
-
const isImporting = useIsMutating( { mutationKey: [ ...IMPORT_DESIGN_SYSTEM_MUTATION_KEY ] } ) > 0;
|
|
13
|
-
|
|
14
|
-
const label = isImporting
|
|
15
|
-
? __( 'Importing design system…', 'elementor' )
|
|
16
|
-
: __( 'Import Design System', 'elementor' );
|
|
17
|
-
|
|
18
|
-
const handleClick = () => {
|
|
19
|
-
openDialog( {
|
|
20
|
-
component: <ImportDesignSystemDialog onClose={ closeDialog } />,
|
|
21
|
-
} );
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
return (
|
|
25
|
-
<Tooltip title={ label } placement="top">
|
|
26
|
-
<span>
|
|
27
|
-
<IconButton size="tiny" disabled={ isImporting } aria-label={ label } onClick={ handleClick }>
|
|
28
|
-
<UploadIcon fontSize="tiny" />
|
|
29
|
-
</IconButton>
|
|
30
|
-
</span>
|
|
31
|
-
</Tooltip>
|
|
32
|
-
);
|
|
33
|
-
};
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
|
|
3
|
-
import { type ConflictStrategy } from '../types';
|
|
4
|
-
|
|
5
|
-
export type DialogState = {
|
|
6
|
-
file: File | null;
|
|
7
|
-
conflictStrategy: ConflictStrategy | null;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
const initialState: DialogState = {
|
|
11
|
-
file: null,
|
|
12
|
-
conflictStrategy: null,
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export const useDialogState = () => {
|
|
16
|
-
const [ state, setState ] = useState< DialogState >( initialState );
|
|
17
|
-
|
|
18
|
-
const setFile = ( file: File | null ) => setState( ( prev ) => ( { ...prev, file } ) );
|
|
19
|
-
|
|
20
|
-
const setConflictStrategy = ( conflictStrategy: ConflictStrategy ) =>
|
|
21
|
-
setState( ( prev ) => ( { ...prev, conflictStrategy } ) );
|
|
22
|
-
|
|
23
|
-
return { ...state, setFile, setConflictStrategy };
|
|
24
|
-
};
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { GLOBAL_STYLES_IMPORTED_EVENT, type ImportedGlobalStylesPayload } from '@elementor/editor-canvas';
|
|
2
|
-
import { reloadCurrentDocument } from '@elementor/editor-documents';
|
|
3
|
-
import { httpService } from '@elementor/http-client';
|
|
4
|
-
import { useMutation } from '@elementor/query';
|
|
5
|
-
|
|
6
|
-
import { type ConflictStrategy } from '../types';
|
|
7
|
-
|
|
8
|
-
const IMPORT_ENDPOINT = '/design-system/import';
|
|
9
|
-
|
|
10
|
-
export const IMPORT_DESIGN_SYSTEM_MUTATION_KEY = [ 'design-system-import' ] as const;
|
|
11
|
-
|
|
12
|
-
type ImportRequestArgs = {
|
|
13
|
-
file: File;
|
|
14
|
-
conflictStrategy: ConflictStrategy;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const useImportRequest = () => {
|
|
18
|
-
return useMutation( {
|
|
19
|
-
mutationKey: [ ...IMPORT_DESIGN_SYSTEM_MUTATION_KEY ],
|
|
20
|
-
mutationFn: async ( { file, conflictStrategy }: ImportRequestArgs ): Promise< void > => {
|
|
21
|
-
const formData = new FormData();
|
|
22
|
-
formData.append( 'file', file );
|
|
23
|
-
formData.append( 'conflict_strategy', conflictStrategy );
|
|
24
|
-
|
|
25
|
-
const response = await httpService().post< ImportedGlobalStylesPayload >( IMPORT_ENDPOINT, formData, {
|
|
26
|
-
headers: { 'Content-Type': 'multipart/form-data' },
|
|
27
|
-
} );
|
|
28
|
-
|
|
29
|
-
window.dispatchEvent(
|
|
30
|
-
new CustomEvent< ImportedGlobalStylesPayload >( GLOBAL_STYLES_IMPORTED_EVENT, {
|
|
31
|
-
detail: response?.data,
|
|
32
|
-
} )
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
await reloadCurrentDocument();
|
|
36
|
-
},
|
|
37
|
-
} );
|
|
38
|
-
};
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { closeDialog, FileUploadDropzone, FileUploadRow, openDialog } from '@elementor/editor-ui';
|
|
3
|
-
import { Button, DialogActions, DialogContent, DialogHeader, DialogTitle, Stack } from '@elementor/ui';
|
|
4
|
-
import { __, sprintf } from '@wordpress/i18n';
|
|
5
|
-
|
|
6
|
-
import { ConflictOptions } from './components/conflict-options';
|
|
7
|
-
import { useDialogState } from './hooks/use-dialog-state';
|
|
8
|
-
import { useImportRequest } from './hooks/use-import-request';
|
|
9
|
-
import { notifyImportFailure, notifyImportInProgress, notifyImportSuccess } from './import-notifications';
|
|
10
|
-
|
|
11
|
-
const ALLOWED_FILE_TYPES: `${ string }/${ string }`[] = [ 'application/zip' ];
|
|
12
|
-
const FILE_INPUT_ACCEPT = 'application/zip,.zip';
|
|
13
|
-
// TODO: Replace with the actual server-enforced limit once finalized.
|
|
14
|
-
const MAX_FILE_SIZE_MB = 3;
|
|
15
|
-
|
|
16
|
-
type Props = {
|
|
17
|
-
onClose: () => void;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const reopenSelf = () => {
|
|
21
|
-
openDialog( {
|
|
22
|
-
component: <ImportDesignSystemDialog onClose={ closeDialog } />,
|
|
23
|
-
} );
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export const ImportDesignSystemDialog = ( { onClose }: Props ) => {
|
|
27
|
-
const { file, conflictStrategy, setFile, setConflictStrategy } = useDialogState();
|
|
28
|
-
const importMutation = useImportRequest();
|
|
29
|
-
|
|
30
|
-
const isImportEnabled = Boolean( file && conflictStrategy );
|
|
31
|
-
|
|
32
|
-
const handleImport = async () => {
|
|
33
|
-
if ( ! file || ! conflictStrategy ) {
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
notifyImportInProgress();
|
|
38
|
-
onClose();
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
await importMutation.mutateAsync( { file, conflictStrategy } );
|
|
42
|
-
notifyImportSuccess();
|
|
43
|
-
} catch {
|
|
44
|
-
notifyImportFailure( reopenSelf );
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<>
|
|
50
|
-
<DialogHeader logo={ false }>
|
|
51
|
-
<DialogTitle>{ __( 'Import Design System', 'elementor' ) }</DialogTitle>
|
|
52
|
-
</DialogHeader>
|
|
53
|
-
<DialogContent>
|
|
54
|
-
<Stack spacing={ 3 }>
|
|
55
|
-
{ file ? (
|
|
56
|
-
<FileUploadRow file={ file } onRemove={ () => setFile( null ) } />
|
|
57
|
-
) : (
|
|
58
|
-
<FileUploadDropzone
|
|
59
|
-
onFileSelected={ setFile }
|
|
60
|
-
allowedFileTypes={ ALLOWED_FILE_TYPES }
|
|
61
|
-
accept={ FILE_INPUT_ACCEPT }
|
|
62
|
-
regionLabel={ __( 'Design system file dropzone', 'elementor' ) }
|
|
63
|
-
helperText={ sprintf(
|
|
64
|
-
// translators: %d is the maximum file size in megabytes.
|
|
65
|
-
__( 'zip (max. %dMB)', 'elementor' ),
|
|
66
|
-
MAX_FILE_SIZE_MB
|
|
67
|
-
) }
|
|
68
|
-
/>
|
|
69
|
-
) }
|
|
70
|
-
<ConflictOptions value={ conflictStrategy } onChange={ setConflictStrategy } />
|
|
71
|
-
</Stack>
|
|
72
|
-
</DialogContent>
|
|
73
|
-
<DialogActions>
|
|
74
|
-
<Button size="medium" color="secondary" onClick={ onClose }>
|
|
75
|
-
{ __( 'Cancel', 'elementor' ) }
|
|
76
|
-
</Button>
|
|
77
|
-
<Button
|
|
78
|
-
size="medium"
|
|
79
|
-
variant="contained"
|
|
80
|
-
color="primary"
|
|
81
|
-
disabled={ ! isImportEnabled }
|
|
82
|
-
onClick={ handleImport }
|
|
83
|
-
>
|
|
84
|
-
{ __( 'Import', 'elementor' ) }
|
|
85
|
-
</Button>
|
|
86
|
-
</DialogActions>
|
|
87
|
-
</>
|
|
88
|
-
);
|
|
89
|
-
};
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { dismissNotification, notify } from '@elementor/editor-notifications';
|
|
2
|
-
import { getQueryClient } from '@elementor/query';
|
|
3
|
-
import { __ } from '@wordpress/i18n';
|
|
4
|
-
|
|
5
|
-
import { IMPORT_DESIGN_SYSTEM_MUTATION_KEY } from './hooks/use-import-request';
|
|
6
|
-
|
|
7
|
-
const IMPORT_STARTED_NOTIFICATION_ID = 'design-system-import-started';
|
|
8
|
-
const SUCCESS_NOTIFICATION_ID = 'design-system-import-succeeded';
|
|
9
|
-
const FAILURE_NOTIFICATION_ID = 'design-system-import-failed';
|
|
10
|
-
|
|
11
|
-
export const notifyImportInProgress = () => {
|
|
12
|
-
notify( {
|
|
13
|
-
id: IMPORT_STARTED_NOTIFICATION_ID,
|
|
14
|
-
type: 'info',
|
|
15
|
-
message: __( 'Import in Progress. You will be notified when the import is complete.', 'elementor' ),
|
|
16
|
-
} );
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export const notifyImportSuccess = () => {
|
|
20
|
-
dismissNotification( IMPORT_STARTED_NOTIFICATION_ID );
|
|
21
|
-
|
|
22
|
-
notify( {
|
|
23
|
-
id: SUCCESS_NOTIFICATION_ID,
|
|
24
|
-
type: 'success',
|
|
25
|
-
message: __( 'Design system imported', 'elementor' ),
|
|
26
|
-
} );
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export const notifyImportFailure = ( onRetry: () => void ) => {
|
|
30
|
-
dismissNotification( IMPORT_STARTED_NOTIFICATION_ID );
|
|
31
|
-
|
|
32
|
-
notify( {
|
|
33
|
-
id: FAILURE_NOTIFICATION_ID,
|
|
34
|
-
type: 'error',
|
|
35
|
-
message: __( 'Your design system import failed', 'elementor' ),
|
|
36
|
-
additionalActionProps: [
|
|
37
|
-
{
|
|
38
|
-
size: 'small',
|
|
39
|
-
variant: 'outlined',
|
|
40
|
-
color: 'error',
|
|
41
|
-
children: __( 'Try again', 'elementor' ),
|
|
42
|
-
onClick: () => {
|
|
43
|
-
dismissNotification( FAILURE_NOTIFICATION_ID );
|
|
44
|
-
|
|
45
|
-
const isImporting =
|
|
46
|
-
getQueryClient().isMutating( { mutationKey: [ ...IMPORT_DESIGN_SYSTEM_MUTATION_KEY ] } ) > 0;
|
|
47
|
-
|
|
48
|
-
if ( isImporting ) {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
onRetry();
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
],
|
|
56
|
-
} );
|
|
57
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type ConflictStrategy = 'replace' | 'keep';
|