mirror of
https://github.com/proelements/proelements.git
synced 2026-05-05 17:15:58 +00:00
v4.0.4.1
This commit is contained in:
+287
@@ -0,0 +1,287 @@
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
Checkbox,
|
||||
Chip,
|
||||
FormControlLabel,
|
||||
Link,
|
||||
Stack,
|
||||
Switch,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from '@elementor/ui';
|
||||
import { AlertTriangleFilledIcon, ExternalLinkIcon } from '@elementor/icons';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import * as PropTypes from 'prop-types';
|
||||
|
||||
const SubSettingRow = ( {
|
||||
label,
|
||||
checked,
|
||||
onChange,
|
||||
disabled = false,
|
||||
limitExceeded = false,
|
||||
overLimitCount = 0,
|
||||
onReviewClick,
|
||||
overrideAll = false,
|
||||
onOverrideAllChange,
|
||||
showOverrideOption = false,
|
||||
notExported = false,
|
||||
} ) => {
|
||||
if ( notExported ) {
|
||||
return (
|
||||
<Box
|
||||
sx={ {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
px: 1.25,
|
||||
} }
|
||||
>
|
||||
<Typography variant="body1" color="text.primary">
|
||||
{ label }
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
{ __( 'Not exported', 'elementor' ) }
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={ {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
px: 1.25,
|
||||
} }
|
||||
>
|
||||
<Stack direction="row" alignItems="center" spacing={ 1 } sx={ { flex: 1 } }>
|
||||
<Typography variant="body1" color="text.primary">
|
||||
{ label }
|
||||
</Typography>
|
||||
|
||||
{ limitExceeded && overLimitCount > 0 && (
|
||||
<Chip
|
||||
label={ `${ overLimitCount } ${ __( 'over limit', 'elementor' ) }` }
|
||||
size="tiny"
|
||||
sx={ {
|
||||
height: 20,
|
||||
borderColor: 'warning.main',
|
||||
color: 'warning.main',
|
||||
backgroundColor: 'transparent',
|
||||
'& .MuiChip-label': {
|
||||
px: 0.75,
|
||||
fontSize: 12,
|
||||
},
|
||||
} }
|
||||
variant="outlined"
|
||||
/>
|
||||
) }
|
||||
|
||||
{ limitExceeded && onReviewClick && (
|
||||
<Link
|
||||
component="button"
|
||||
variant="body2"
|
||||
color="info.main"
|
||||
onClick={ onReviewClick }
|
||||
sx={ {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 0.5,
|
||||
textDecoration: 'none',
|
||||
'&:hover': {
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
} }
|
||||
>
|
||||
{ __( 'Review', 'elementor' ) }
|
||||
<ExternalLinkIcon sx={ { fontSize: 16 } } />
|
||||
</Link>
|
||||
) }
|
||||
</Stack>
|
||||
|
||||
<Stack direction="row" alignItems="center" spacing={ 1 }>
|
||||
{ showOverrideOption && limitExceeded && (
|
||||
<Stack direction="row" alignItems="center" spacing={ 0.5 }>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={ overrideAll }
|
||||
onChange={ ( e ) => onOverrideAllChange?.( e.target.checked ) }
|
||||
color="info"
|
||||
size="small"
|
||||
sx={ { p: 0 } }
|
||||
/>
|
||||
}
|
||||
label={ __( 'Override all', 'elementor' ) }
|
||||
sx={ {
|
||||
gap: 1,
|
||||
mr: 0,
|
||||
'& .MuiFormControlLabel-label': {
|
||||
fontSize: 14,
|
||||
},
|
||||
} }
|
||||
/>
|
||||
<Tooltip
|
||||
title={ __( 'This will delete all existing items and replace them with the imported ones', 'elementor' ) }
|
||||
placement="top"
|
||||
arrow
|
||||
>
|
||||
<AlertTriangleFilledIcon
|
||||
sx={ {
|
||||
fontSize: 16,
|
||||
color: 'warning.main',
|
||||
cursor: 'pointer',
|
||||
} }
|
||||
/>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
) }
|
||||
|
||||
<Switch
|
||||
checked={ checked }
|
||||
onChange={ ( e, isChecked ) => onChange?.( isChecked ) }
|
||||
color="info"
|
||||
size="medium"
|
||||
disabled={ disabled || ( limitExceeded && ! overrideAll ) }
|
||||
/>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
SubSettingRow.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
checked: PropTypes.bool,
|
||||
onChange: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
limitExceeded: PropTypes.bool,
|
||||
overLimitCount: PropTypes.number,
|
||||
onReviewClick: PropTypes.func,
|
||||
overrideAll: PropTypes.bool,
|
||||
onOverrideAllChange: PropTypes.func,
|
||||
showOverrideOption: PropTypes.bool,
|
||||
notExported: PropTypes.bool,
|
||||
};
|
||||
|
||||
export function ClassesVariablesSection( {
|
||||
settings,
|
||||
onSettingChange,
|
||||
isImport = false,
|
||||
classesExported = true,
|
||||
variablesExported = true,
|
||||
classesLimitExceeded = false,
|
||||
variablesLimitExceeded = false,
|
||||
classesOverLimitCount = 0,
|
||||
variablesOverLimitCount = 0,
|
||||
onClassesReviewClick,
|
||||
onVariablesReviewClick,
|
||||
disabled = false,
|
||||
notExported = false,
|
||||
} ) {
|
||||
const [ classesOverrideAll, setClassesOverrideAll ] = useState( settings.classesOverrideAll ?? false );
|
||||
const [ variablesOverrideAll, setVariablesOverrideAll ] = useState( settings.variablesOverrideAll ?? false );
|
||||
|
||||
const hasLimitWarning = isImport && ( classesLimitExceeded || variablesLimitExceeded );
|
||||
const classesNotExported = isImport && ! classesExported;
|
||||
const variablesNotExported = isImport && ! variablesExported;
|
||||
|
||||
return (
|
||||
<Box sx={ { mb: 3, border: 1, borderRadius: 1, borderColor: 'action.focus', p: 2.5 } }>
|
||||
<Stack spacing={ 2.5 }>
|
||||
<Box sx={ { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } }>
|
||||
<Typography variant="h6">
|
||||
{ __( 'Classes & variables', 'elementor' ) }
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{ hasLimitWarning && ! notExported && (
|
||||
<Alert
|
||||
severity="warning"
|
||||
icon={ <AlertTriangleFilledIcon sx={ { color: 'warning.main' } } /> }
|
||||
sx={ {
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'warning.background',
|
||||
'& .MuiAlert-message': {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 0.75,
|
||||
},
|
||||
} }
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
component="span"
|
||||
sx={ { fontWeight: 500 } }
|
||||
color="text.secondary"
|
||||
>
|
||||
{ __( 'Import limit reached.', 'elementor' ) }
|
||||
</Typography>
|
||||
<Typography variant="body2" component="span" color="text.secondary">
|
||||
{ __( 'To resolve this, review existing items or choose to override', 'elementor' ) }
|
||||
</Typography>
|
||||
</Alert>
|
||||
) }
|
||||
|
||||
<Stack spacing={ 1.5 }>
|
||||
<SubSettingRow
|
||||
label={ __( 'Classes', 'elementor' ) }
|
||||
checked={ settings.classes ?? false }
|
||||
onChange={ ( isChecked ) => onSettingChange( 'classes', isChecked ) }
|
||||
disabled={ disabled }
|
||||
limitExceeded={ isImport && classesLimitExceeded && ! classesNotExported }
|
||||
overLimitCount={ classesOverLimitCount }
|
||||
onReviewClick={ onClassesReviewClick }
|
||||
overrideAll={ classesOverrideAll }
|
||||
onOverrideAllChange={ ( checked ) => {
|
||||
setClassesOverrideAll( checked );
|
||||
onSettingChange( 'classesOverrideAll', checked );
|
||||
} }
|
||||
showOverrideOption={ isImport && ! classesNotExported }
|
||||
notExported={ classesNotExported }
|
||||
/>
|
||||
|
||||
<SubSettingRow
|
||||
label={ __( 'Variables', 'elementor' ) }
|
||||
checked={ settings.variables ?? false }
|
||||
onChange={ ( isChecked ) => onSettingChange( 'variables', isChecked ) }
|
||||
disabled={ disabled }
|
||||
limitExceeded={ isImport && variablesLimitExceeded && ! variablesNotExported }
|
||||
overLimitCount={ variablesOverLimitCount }
|
||||
onReviewClick={ onVariablesReviewClick }
|
||||
overrideAll={ variablesOverrideAll }
|
||||
onOverrideAllChange={ ( checked ) => {
|
||||
setVariablesOverrideAll( checked );
|
||||
onSettingChange( 'variablesOverrideAll', checked );
|
||||
} }
|
||||
showOverrideOption={ isImport && ! variablesNotExported }
|
||||
notExported={ variablesNotExported }
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
ClassesVariablesSection.propTypes = {
|
||||
settings: PropTypes.shape( {
|
||||
classes: PropTypes.bool,
|
||||
variables: PropTypes.bool,
|
||||
classesOverrideAll: PropTypes.bool,
|
||||
variablesOverrideAll: PropTypes.bool,
|
||||
} ).isRequired,
|
||||
onSettingChange: PropTypes.func.isRequired,
|
||||
isImport: PropTypes.bool,
|
||||
classesExported: PropTypes.bool,
|
||||
variablesExported: PropTypes.bool,
|
||||
classesLimitExceeded: PropTypes.bool,
|
||||
variablesLimitExceeded: PropTypes.bool,
|
||||
classesOverLimitCount: PropTypes.number,
|
||||
variablesOverLimitCount: PropTypes.number,
|
||||
onClassesReviewClick: PropTypes.func,
|
||||
onVariablesReviewClick: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
notExported: PropTypes.bool,
|
||||
};
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
Stack,
|
||||
Typography,
|
||||
} from '@elementor/ui';
|
||||
import { AlertTriangleFilledIcon, XIcon } from '@elementor/icons';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import * as PropTypes from 'prop-types';
|
||||
|
||||
const getDialogContent = ( type ) => {
|
||||
switch ( type ) {
|
||||
case 'both':
|
||||
return {
|
||||
title: __( 'Override all classes and variables?', 'elementor' ),
|
||||
description: __( 'This will delete all existing classes and variables and replace them with the imported ones. This action cannot be undone.', 'elementor' ),
|
||||
};
|
||||
case 'variables':
|
||||
return {
|
||||
title: __( 'Override all variables?', 'elementor' ),
|
||||
description: __( 'This will delete all existing variables and replace them with the imported ones. This action cannot be undone.', 'elementor' ),
|
||||
};
|
||||
case 'classes':
|
||||
default:
|
||||
return {
|
||||
title: __( 'Override all classes?', 'elementor' ),
|
||||
description: __( 'This will delete all existing classes and replace them with the imported ones. This action cannot be undone.', 'elementor' ),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export function OverrideConfirmationDialog( {
|
||||
open,
|
||||
onClose,
|
||||
onConfirm,
|
||||
type = 'classes',
|
||||
} ) {
|
||||
const { title, description } = getDialogContent( type );
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={ open }
|
||||
onClose={ onClose }
|
||||
maxWidth="xs"
|
||||
fullWidth
|
||||
>
|
||||
<DialogContent sx={ { pt: 2, pb: 1.5 } }>
|
||||
<Stack spacing={ 1.5 }>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="flex-start">
|
||||
<Stack direction="row" spacing={ 1.5 } alignItems="flex-start" sx={ { flex: 1 } }>
|
||||
<AlertTriangleFilledIcon
|
||||
sx={ {
|
||||
color: 'warning.dark',
|
||||
fontSize: 24,
|
||||
flexShrink: 0,
|
||||
} }
|
||||
/>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
sx={ { fontWeight: 500 } }
|
||||
>
|
||||
{ title }
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Button
|
||||
onClick={ onClose }
|
||||
sx={ {
|
||||
minWidth: 'auto',
|
||||
p: 0.5,
|
||||
color: 'text.primary',
|
||||
} }
|
||||
>
|
||||
<XIcon sx={ { fontSize: 20 } } />
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
sx={ { pr: 1 } }
|
||||
>
|
||||
{ description }
|
||||
</Typography>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions sx={ { px: 3, pb: 2 } }>
|
||||
<Button
|
||||
onClick={ onClose }
|
||||
color="secondary"
|
||||
variant="text"
|
||||
>
|
||||
{ __( 'Cancel', 'elementor' ) }
|
||||
</Button>
|
||||
<Button
|
||||
onClick={ onConfirm }
|
||||
variant="contained"
|
||||
sx={ {
|
||||
color: 'white',
|
||||
backgroundColor: 'warning.main',
|
||||
'&:hover': {
|
||||
backgroundColor: 'warning.dark',
|
||||
},
|
||||
} }
|
||||
>
|
||||
{ __( 'Save and override', 'elementor' ) }
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
OverrideConfirmationDialog.propTypes = {
|
||||
open: PropTypes.bool.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
type: PropTypes.oneOf( [ 'classes', 'variables', 'both' ] ),
|
||||
};
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useTabFocus } from './use-tab-focus';
|
||||
|
||||
const DEFAULT_CLASSES_LIMIT = 100;
|
||||
const DEFAULT_VARIABLES_LIMIT = 100;
|
||||
|
||||
function getLimitsFromConfig() {
|
||||
const config = window.elementorAppConfig?.[ 'import-export-customization' ];
|
||||
|
||||
return {
|
||||
classes: config?.limits?.classes ?? DEFAULT_CLASSES_LIMIT,
|
||||
variables: config?.limits?.variables ?? DEFAULT_VARIABLES_LIMIT,
|
||||
};
|
||||
}
|
||||
|
||||
export function useClassesVariablesLimits( { open, isImport } ) {
|
||||
const [ existingClassesCount, setExistingClassesCount ] = useState( 0 );
|
||||
const [ existingVariablesCount, setExistingVariablesCount ] = useState( 0 );
|
||||
const [ isLoading, setIsLoading ] = useState( false );
|
||||
const [ error, setError ] = useState( null );
|
||||
|
||||
const limits = useMemo( () => getLimitsFromConfig(), [] );
|
||||
|
||||
const fetchCounts = useCallback( async () => {
|
||||
if ( ! open || ! isImport ) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading( true );
|
||||
setError( null );
|
||||
|
||||
try {
|
||||
const baseUrl = window.wpApiSettings?.root || '/wp-json/';
|
||||
const nonce = window.wpApiSettings?.nonce || '';
|
||||
|
||||
const [ classesResponse, variablesResponse ] = await Promise.all( [
|
||||
fetch( `${ baseUrl }elementor/v1/global-classes`, {
|
||||
headers: {
|
||||
'X-WP-Nonce': nonce,
|
||||
},
|
||||
} ),
|
||||
fetch( `${ baseUrl }elementor/v1/variables/list`, {
|
||||
headers: {
|
||||
'X-WP-Nonce': nonce,
|
||||
},
|
||||
} ),
|
||||
] );
|
||||
|
||||
if ( classesResponse.ok ) {
|
||||
const classesData = await classesResponse.json();
|
||||
const classesCount = Object.keys( classesData?.data || {} ).length;
|
||||
setExistingClassesCount( classesCount );
|
||||
}
|
||||
|
||||
if ( variablesResponse.ok ) {
|
||||
const variablesData = await variablesResponse.json();
|
||||
const variablesCount = variablesData?.data?.total || 0;
|
||||
setExistingVariablesCount( variablesCount );
|
||||
}
|
||||
} catch ( err ) {
|
||||
setError( err );
|
||||
} finally {
|
||||
setIsLoading( false );
|
||||
}
|
||||
}, [ open, isImport ] );
|
||||
|
||||
useEffect( () => {
|
||||
fetchCounts();
|
||||
}, [ fetchCounts ] );
|
||||
|
||||
useTabFocus( fetchCounts );
|
||||
|
||||
const calculateLimitInfo = useCallback( ( existingCount, importedCount, limit ) => {
|
||||
const totalAfterImport = existingCount + importedCount;
|
||||
const isExceeded = totalAfterImport > limit;
|
||||
const overLimitCount = isExceeded ? totalAfterImport - limit : 0;
|
||||
|
||||
return {
|
||||
isExceeded,
|
||||
overLimitCount,
|
||||
totalAfterImport,
|
||||
};
|
||||
}, [] );
|
||||
|
||||
return {
|
||||
existingClassesCount,
|
||||
existingVariablesCount,
|
||||
classesLimit: limits.classes,
|
||||
variablesLimit: limits.variables,
|
||||
isLoading,
|
||||
error,
|
||||
calculateLimitInfo,
|
||||
refetch: fetchCounts,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export function useTabFocus( callback ) {
|
||||
useEffect( () => {
|
||||
const handleVisibilityChange = () => {
|
||||
if ( 'visible' === document.visibilityState ) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
document.addEventListener( 'visibilitychange', handleVisibilityChange );
|
||||
return () => document.removeEventListener( 'visibilitychange', handleVisibilityChange );
|
||||
}, [ callback ] );
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\App\Modules\ImportExportCustomization;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ElementorPro\License\API;
|
||||
|
||||
class Utils {
|
||||
public static function is_high_tier(): bool {
|
||||
try {
|
||||
$plan_type = API::get_plan_type();
|
||||
return 'expert' === $plan_type || 'agency' === $plan_type;
|
||||
} catch ( \Exception $exception ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user