From 89664283eca2e669682ae21f846ee21661912b3f Mon Sep 17 00:00:00 2001 From: Mikhaela Tapia Date: Tue, 14 Oct 2025 16:14:24 +0800 Subject: [PATCH 1/7] init --- src/block/design-library/edit.js | 16 +- .../design-library-list-item.js | 23 ++- .../design-library-list/design-preview.js | 3 +- .../design-library-list/editor.scss | 33 +++- src/components/design-library-list/index.js | 43 ++-- .../use-preview-renderer.js | 14 +- src/components/design-library-list/util.js | 6 +- .../modal-design-library/editor.scss | 6 + .../modal-design-library/header-actions.js | 30 +-- src/components/modal-design-library/modal.js | 38 ++-- src/components/pro-control/index.js | 8 + src/design-library/index.js | 9 +- src/design-library/init.php | 38 ++++ src/welcome/admin.js | 113 ++++++++++- src/welcome/admin.scss | 1 + src/welcome/import-export/context.js | 8 + src/welcome/import-export/design-library.js | 60 ++++++ src/welcome/import-export/import-export.scss | 93 +++++++++ src/welcome/import-export/index.js | 183 ++++++++++++++++++ 19 files changed, 654 insertions(+), 71 deletions(-) create mode 100644 src/welcome/import-export/context.js create mode 100644 src/welcome/import-export/design-library.js create mode 100644 src/welcome/import-export/import-export.scss create mode 100644 src/welcome/import-export/index.js diff --git a/src/block/design-library/edit.js b/src/block/design-library/edit.js index 2f4643b07c..71a300db50 100644 --- a/src/block/design-library/edit.js +++ b/src/block/design-library/edit.js @@ -116,7 +116,7 @@ const Edit = props => { const spacingSize = ! presetMarks || ! Array.isArray( presetMarks ) ? 120 : presetMarks[ presetMarks.length - 2 ].value // Replaces the current block with a block made out of attributes. - const createBlockWithAttributes = async ( category, blockName, attributes, innerBlocks, substituteBlocks, parentClientId ) => { + const createBlockWithAttributes = async ( category, blockName, attributes, innerBlocks, substituteBlocks, parentClientId, type ) => { const disabledBlocks = settings.stackable_block_states || {} // eslint-disable-line camelcase // Recursively substitute core blocks to disabled Stackable blocks @@ -207,7 +207,7 @@ const Edit = props => { innerBlocks = block[ 0 ].innerBlocks const isDesignLibraryDevMode = devMode && localStorage.getItem( 'stk__design_library__dev_mode' ) === '1' - if ( ! isDesignLibraryDevMode ) { + if ( ! isDesignLibraryDevMode && type !== 'saved' ) { if ( category !== 'Header' ) { if ( ! parentClientId && attributes.hasBackground ) { attributes.blockMargin = { @@ -255,14 +255,16 @@ const Edit = props => { const blocks = [] for ( const blockDesign of designs ) { - const { designData, category } = blockDesign + const { + designData, category, type, + } = blockDesign for ( const patterns of designData ) { const { name, attributes, innerBlocks, } = patterns if ( name && attributes ) { - const block = await createBlockWithAttributes( category, name, applyFilters( 'stackable.design-library.attributes', attributes ), innerBlocks || [], substituteBlocks, parentClientId ) + const block = await createBlockWithAttributes( category, name, applyFilters( 'stackable.design-library.attributes', attributes ), innerBlocks || [], substituteBlocks, parentClientId, type ) blocks.push( block ) } else { console.error( 'Design library selection failed: No block data found' ) // eslint-disable-line no-console @@ -336,14 +338,16 @@ const Edit = props => { _designs.forEach( design => { const { - designData, blocksForSubstitution, category, + designData, blocksForSubstitution, category, type: designType, } = design if ( blocksForSubstitution.size ) { disabledBlocks = disabledBlocks.union( blocksForSubstitution ) } - designs.push( { designData, category } ) + designs.push( { + designData, category, type: designType, + } ) } ) designsRef.current = designs diff --git a/src/components/design-library-list/design-library-list-item.js b/src/components/design-library-list/design-library-list-item.js index 89e8a010b9..8b848d2031 100644 --- a/src/components/design-library-list/design-library-list-item.js +++ b/src/components/design-library-list/design-library-list-item.js @@ -24,6 +24,7 @@ import { } from '@wordpress/element' import { Dashicon, Spinner } from '@wordpress/components' import { __ } from '@wordpress/i18n' +import { applyFilters } from '@wordpress/hooks' const DesignLibraryListItem = memo( props => { const { @@ -86,20 +87,23 @@ const DesignLibraryListItem = memo( props => { const onClickHost = e => { e.stopPropagation() - if ( selectedTab === 'pages' ) { - return - } onClickDesign() } + const buttonAttributes = { + tabIndex: 0, + role: 'button', + onClick: onClickHost, + } + return ( - // eslint-disable-next-line jsx-a11y/mouse-events-have-key-events - + ) } ) diff --git a/src/components/design-library-list/design-preview.js b/src/components/design-library-list/design-preview.js index 0d97058f18..fb5d7aae05 100644 --- a/src/components/design-library-list/design-preview.js +++ b/src/components/design-library-list/design-preview.js @@ -55,7 +55,7 @@ export const DesignPreview = ( { useEffect( () => { const container = ref.current - if ( ! container || selectedTab === 'patterns' ) { + if ( ! container || selectedTab !== 'pages' ) { return } @@ -121,6 +121,7 @@ export const DesignPreview = ( { >
diff --git a/src/components/design-library-list/editor.scss b/src/components/design-library-list/editor.scss index 6c5118ff02..61c9d61070 100644 --- a/src/components/design-library-list/editor.scss +++ b/src/components/design-library-list/editor.scss @@ -54,13 +54,38 @@ opacity: 1; } } + .stk-block-design__edit-btn-container { + opacity: 0; + z-index: 2; + transition: opacity 0.4s cubic-bezier(0.2, 0.6, 0.4, 1); + position: absolute; + padding: 60px 30px 30px; + background: linear-gradient(180deg, transparent, #fffe 40px, #fff) !important; + top: auto; + bottom: -1px; + left: 0; + right: 0; + display: flex; + gap: 24px; + + .ugb-button-component { + width: 100%; + justify-content: center; + } + } &:hover { // box-shadow: rgba(0, 0, 0, 0.25) 0px 25px 50px -12px; box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px; .ugb-button-component { opacity: 1; } + + .stk-block-design__edit-btn-container { + opacity: 1; + transition-delay: 1s; + } } + &.ugb--is-hidden { opacity: 0; pointer-events: none; @@ -236,7 +261,6 @@ } } - .ugb-design-library-items { .stk-spinner-container { height: 100%; @@ -257,3 +281,10 @@ } } } + +.stk-design-library__item-saved .ugb-design-library-item { + cursor: default; + footer { + cursor: pointer; + } +} diff --git a/src/components/design-library-list/index.js b/src/components/design-library-list/index.js index 4e0b2ac579..e599869c30 100644 --- a/src/components/design-library-list/index.js +++ b/src/components/design-library-list/index.js @@ -6,7 +6,7 @@ import DesignLibraryListItem from './design-library-list-item' /** * External dependencies */ -import { i18n } from 'stackable' +import { i18n, isPro } from 'stackable' import classnames from 'classnames' /** @@ -19,12 +19,14 @@ import { } from '@wordpress/element' import { usePresetControls } from '~stackable/hooks' import { useDesignLibraryContext } from '../modal-design-library/modal' +import ProControl from '../pro-control' const DesignLibraryList = memo( props => { const { className = '', designs, isBusy, + selectedTab, } = props const containerRef = useRef( null ) @@ -41,24 +43,27 @@ const DesignLibraryList = memo( props => { className="ugb-modal-design-library__designs" ref={ containerRef } > - { isBusy && } - { ! isBusy && <> -
- { ( designs || [] ).map( ( design, i ) => { - return ( - - ) - } ) } - - { ! ( designs || [] ).length && -

{ __( 'No designs found', i18n ) }

- } -
- } + { selectedTab === 'saved' && ! isPro + ? + : <> + { isBusy && } + { ! isBusy &&
+ { ( designs || [] ).map( ( design, i ) => { + return ( + + ) + } ) } + + { ! ( designs || [] ).length && +

{ __( 'No designs found', i18n ) }

+ } +
} + + }
} ) diff --git a/src/components/design-library-list/use-preview-renderer.js b/src/components/design-library-list/use-preview-renderer.js index ae152caad5..e64a59694a 100644 --- a/src/components/design-library-list/use-preview-renderer.js +++ b/src/components/design-library-list/use-preview-renderer.js @@ -70,7 +70,7 @@ export const usePreviewRenderer = ( const siteTitle = useSelect( select => select( 'core' ).getEntityRecord( 'root', 'site' )?.title || 'InnovateCo', [] ) const isDesignLibraryDevMode = devMode && localStorage.getItem( 'stk__design_library__dev_mode' ) === '1' - const addHasBackground = selectedTab === 'patterns' + const addHasBackground = selectedTab !== 'pages' const updateShadowBodySize = _shadowBody => { const shadowBody = _shadowBody || shadowRoot?.querySelector( 'body' ) @@ -117,7 +117,7 @@ export const usePreviewRenderer = ( const scaleFactor = cardWidth > 0 ? cardWidth / 1300 : 1 // Divide by 1300, which is the width of preview in the shadow DOM let _bodyHeight = 1200 - if ( selectedTab === 'patterns' ) { + if ( selectedTab !== 'pages' ) { _bodyHeight = shadowBody.offsetHeight } @@ -224,13 +224,13 @@ export const usePreviewRenderer = ( let _parsedBlocksForInsertion = null const initialize = async () => { const _content = template - if ( selectedTab === 'patterns' ) { + if ( selectedTab !== 'pages' ) { const categorySlug = getCategorySlug( designId ) // For preview: always replace placeholders (ignore dev mode) - const _contentForPreview = replacePlaceholders( _content, categorySlug, false ) + const _contentForPreview = replacePlaceholders( _content, categorySlug, false, selectedTab ) // For insertion: only create separate content if dev mode is enabled - const _contentForInsertion = isDesignLibraryDevMode ? replacePlaceholders( _content, categorySlug, true ) : _contentForPreview + const _contentForInsertion = isDesignLibraryDevMode ? replacePlaceholders( _content, categorySlug, true, selectedTab ) : _contentForPreview categoriesRef.current.push( categorySlug ) @@ -248,12 +248,12 @@ export const usePreviewRenderer = ( // For preview: always replace placeholders (ignore dev mode) const designsContentForPreview = designs.map( ( design, i ) => - replacePlaceholders( design.template || design.content, categorySlugs[ i ], false ) + replacePlaceholders( design.template || design.content, categorySlugs[ i ], false, selectedTab ) ).join( '\n' ) // For insertion: only create separate content if dev mode is enabled const designsContentForInsertion = isDesignLibraryDevMode ? designs.map( ( design, i ) => - replacePlaceholders( design.template || design.content, categorySlugs[ i ], true ) + replacePlaceholders( design.template || design.content, categorySlugs[ i ], true, selectedTab ) ).join( '\n' ) : designsContentForPreview diff --git a/src/components/design-library-list/util.js b/src/components/design-library-list/util.js index 8e64be7759..9469385602 100644 --- a/src/components/design-library-list/util.js +++ b/src/components/design-library-list/util.js @@ -180,7 +180,11 @@ export const adjustPatternSpacing = ( attributes, category, spacingSize, isDesig } } -export const replacePlaceholders = ( designContent, designCategory, isDesignLibraryDevMode ) => { +export const replacePlaceholders = ( designContent, designCategory, isDesignLibraryDevMode, type ) => { + if ( type === 'saved' ) { + return designContent + } + const defaultValues = DEFAULT_CONTENT[ designCategory ] if ( defaultValues && ! isDesignLibraryDevMode ) { diff --git a/src/components/modal-design-library/editor.scss b/src/components/modal-design-library/editor.scss index 3fadaa780e..ab14f24f91 100644 --- a/src/components/modal-design-library/editor.scss +++ b/src/components/modal-design-library/editor.scss @@ -464,3 +464,9 @@ div.ugb-modal-design-library__enable-background { min-width: unset; } } + +body:has(.stk-design-library__item-saved) { + .components-snackbar-list { + z-index: 100001; + } +} diff --git a/src/components/modal-design-library/header-actions.js b/src/components/modal-design-library/header-actions.js index 1ba1798c8d..8afee0004f 100644 --- a/src/components/modal-design-library/header-actions.js +++ b/src/components/modal-design-library/header-actions.js @@ -5,7 +5,7 @@ import Button from '../button' * External deprendencies */ import { - i18n, isPro, devMode, + i18n, isPro, devMode, showProNotice, } from 'stackable' /** @@ -30,21 +30,29 @@ export const HeaderActions = props => { setDoReset, onClose, } = props + + let controls = [ + { + value: 'patterns', + title: __( 'Patterns', i18n ), + }, + { + value: 'pages', + title: __( 'Pages', i18n ), + }, + ] + + controls = ! isPro && ! showProNotice ? controls : [ ...controls, { + value: 'saved', + title: __( 'Saved', i18n ), + } ] + return <> { /* DEV NOTE: hide for now */ } { const [ selectedContainerScheme, setSelectedContainerScheme ] = useState( '' ) const [ selectedBackgroundScheme, setSelectedBackgroundScheme ] = useState( '' ) + const savedPatterns = applyFilters( 'stackable.design-library.patterns', selectedTab ) + // For version 4, the default tab is now 'patterns' and for category, we use '' instead of 'All'. // So we need to update the local storage values here. useEffect( () => { @@ -90,6 +93,14 @@ export const ModalDesignLibrary = props => { // Update the designs on the sidebar. (this will trigger the display designs update next) useEffect( () => { + if ( selectedTab === 'saved' ) { + setSidebarDesigns( savedPatterns ) + setSelectedCategory( '' ) + setDoReset( false ) + setIsBusy( false ) + return + } + setIsBusy( true ) setSidebarDesigns( [] ) @@ -103,7 +114,7 @@ export const ModalDesignLibrary = props => { setDoReset( false ) setIsBusy( false ) } ) - }, [ doReset, selectedTab ] ) + }, [ doReset, selectedTab, savedPatterns ] ) // This updates the displayed designs the user can pick. useEffect( () => { @@ -111,6 +122,7 @@ export const ModalDesignLibrary = props => { library: sidebarDesigns, category: selectedCategory, plan: selectedPlan.key, + type: selectedTab, } ).then( designs => { setDisplayDesigns( designs ) } ) @@ -137,7 +149,7 @@ export const ModalDesignLibrary = props => { const onSelectDesign = useCallback( ( designId, category, parsedBlocks, blocksForSubstitution, selectedPreviewSize ) => { if ( selectedTab === 'pages' ) { const selectedDesign = [ { - designId, category, designData: parsedBlocks, blocksForSubstitution, selectedPreviewSize, + designId, category, designData: parsedBlocks, blocksForSubstitution, selectedPreviewSize, type: selectedTab, } ] addDesign( selectedDesign ) @@ -167,6 +179,7 @@ export const ModalDesignLibrary = props => { } else { newSelectedDesignData.push( { designId, category, designData: parsedBlocks, blocksForSubstitution, selectedPreviewSize, + type: selectedTab, } ) } @@ -245,7 +258,7 @@ export const ModalDesignLibrary = props => { - { selectedTab === 'patterns' && { className={ `stk-design-library__item-${ selectedTab }` } isBusy={ isBusy } designs={ displayDesigns } + selectedTab={ selectedTab } /> - { selectedTab === 'patterns' &&