From dcbd7f443669c61c0e846e334233b505f458b808 Mon Sep 17 00:00:00 2001 From: Alquen Antonio Sarmiento Date: Tue, 3 Sep 2024 22:52:16 +0800 Subject: [PATCH 01/32] feat: allow enabling, disabling and hiding of blocks --- src/blocks.js | 12 +++-- src/disabled-blocks.js | 84 ++++++++++++++++---------------- src/editor-settings.php | 13 +++-- src/util/blocks.js | 9 ++++ src/welcome/admin.js | 104 +++++++++++++++++++++++++--------------- 5 files changed, 134 insertions(+), 88 deletions(-) diff --git a/src/blocks.js b/src/blocks.js index 6bdc8e1ee..909e2bc6d 100644 --- a/src/blocks.js +++ b/src/blocks.js @@ -12,8 +12,12 @@ import './disabled-blocks' /** * External dependencies */ -import { i18n } from 'stackable' -import { addStackableBlockCategory, registerBlockType } from '~stackable/util' +import { i18n, settings as stackableSettings } from 'stackable' +import { + addStackableBlockCategory, + registerBlockType, + BLOCK_STATE, +} from '~stackable/util' import { withVisualGuideContext } from '~stackable/higher-order' /** @@ -45,8 +49,8 @@ const importAllAndRegister = r => { settings.keywords = settings.keywords.map( keyword => __( keyword, i18n ) ) // eslint-disable-line @wordpress/i18n-no-variables } - // Register the block. - if ( ! getBlockType( name ) ) { + // Register the block if it's not already registered and not disabled. + if ( ! getBlockType( name ) && stackableSettings.stackable_disabled_blocks[ name ] !== BLOCK_STATE.DISABLED ) { registerBlockType( name, settings ) } } ) diff --git a/src/disabled-blocks.js b/src/disabled-blocks.js index 039350262..004811cd1 100644 --- a/src/disabled-blocks.js +++ b/src/disabled-blocks.js @@ -1,60 +1,64 @@ /** - * Filter that modified the metadata of the blocks to disable blocks and + * Filter that modified the metadata of the blocks to hide blocks and * variations depending on the settings of the user. */ import { settings } from 'stackable' import { addFilter } from '@wordpress/hooks' +import { BLOCK_STATE } from '~stackable/util' // Disable these blocks when the following variables are disabled. We need this // so that if a variation is disabled, we will no longer be able to add the // relevant block. -const BLOCK_DEPENDENCIES = { - 'stackable/icon-button': 'stackable/button-group|icon-button', - 'stackable/button': 'stackable/button-group|button', -} +// const BLOCK_DEPENDENCIES = { +// 'stackable/icon-button': 'stackable/button-group|icon-button', +// 'stackable/button': 'stackable/button-group|button', +// } -const getDefaultVariation = variations => { - return variations?.find( ( { isDefault } ) => isDefault )?.name -} -const getVariationsToRemove = ( disabledBlocks, blockName ) => { - return disabledBlocks.filter( disabledBlock => disabledBlock.startsWith( `${ blockName }|` ) ) - .map( disabledBlock => disabledBlock.split( '|' )[ 1 ] ) -} +// const getDefaultVariation = variations => { +// return variations?.find( ( { isDefault } ) => isDefault )?.name +// } +// const getVariationsToRemove = ( disabledBlocks, blockName ) => { +// return disabledBlocks.filter( disabledBlock => disabledBlock.startsWith( `${ blockName }|` ) ) +// .map( disabledBlock => disabledBlock.split( '|' )[ 1 ] ) +// } const applySettingsToMeta = metadata => { - let inserter = ! settings.stackable_disabled_blocks.includes( metadata.name ) - - // Check if this block is dependent on another variation being enabled. - if ( BLOCK_DEPENDENCIES[ metadata.name ] ) { - if ( settings.stackable_disabled_blocks.includes( BLOCK_DEPENDENCIES[ metadata.name ] ) ) { - inserter = false - } + let inserter = true + if ( metadata.name in settings.stackable_disabled_blocks ) { + inserter = ! settings.stackable_disabled_blocks[ metadata.name ] === BLOCK_STATE.HIDDEN } - const variationsToRemove = getVariationsToRemove( settings.stackable_disabled_blocks, metadata.name ) - let variations = metadata.variations || [] + // // Check if this block is dependent on another variation being enabled. + // if ( BLOCK_DEPENDENCIES[ metadata.name ] ) { + // if ( settings.stackable_disabled_blocks.includes( BLOCK_DEPENDENCIES[ metadata.name ] ) ) { + // inserter = false + // } + // } - // Remove variations if there are ones disabled. - if ( variationsToRemove.length ) { - const hasDefaultVariation = !! getDefaultVariation( metadata.variations ) - variations = variations.filter( variation => ! variationsToRemove.includes( variation.name ) ) - // If there was a default variation before, ensure we still have a default - // variation if it gets removed. - if ( variations.length && hasDefaultVariation && ! getDefaultVariation( variations ) ) { - variations[ 0 ].isDefault = true - } + // const variationsToRemove = getVariationsToRemove( settings.stackable_disabled_blocks, metadata.name ) + // let variations = metadata.variations || [] - // If no more variations are left, and the main block is hidden, remove the - // main block from the inserter so it won't show up when adding a block. - if ( ! variations.length ) { - if ( metadata[ 'stk-type' ] === 'hidden' ) { - inserter = false - } - } - } + // // Remove variations if there are ones disabled. + // if ( variationsToRemove.length ) { + // const hasDefaultVariation = !! getDefaultVariation( metadata.variations ) + // variations = variations.filter( variation => ! variationsToRemove.includes( variation.name ) ) + // // If there was a default variation before, ensure we still have a default + // // variation if it gets removed. + // if ( variations.length && hasDefaultVariation && ! getDefaultVariation( variations ) ) { + // variations[ 0 ].isDefault = true + // } + + // // If no more variations are left, and the main block is hidden, remove the + // // main block from the inserter so it won't show up when adding a block. + // if ( ! variations.length ) { + // if ( metadata[ 'stk-type' ] === 'hidden' ) { + // inserter = false + // } + // } + // } - // Adjust the metadata. - metadata.variations = variations + // // Adjust the metadata. + // metadata.variations = variations if ( typeof metadata.supports === 'undefined' ) { metadata.supports = {} } diff --git a/src/editor-settings.php b/src/editor-settings.php index 79d4a0db6..32003ca33 100644 --- a/src/editor-settings.php +++ b/src/editor-settings.php @@ -33,16 +33,19 @@ public function register_settings() { register_setting( 'stackable_editor_settings', 'stackable_disabled_blocks', + // Use an object to store the block names as keys and the value that represents if disabled or hidden. + // Enabled blocks are not stored in the object to save memory. array( - 'type' => 'array', + 'type' => 'object', 'description' => __( 'Blocks that should be hidden in the block editor', STACKABLE_I18N ), 'sanitize_callback' => array( $this, 'sanitize_array_setting' ), 'show_in_rest' => array( 'schema' => array( - 'items' => array( - 'type' => 'string', - ) - ) + 'type' => 'object', + 'additionalProperties' => array( + 'type' => 'number', + ), + ), ), 'default' => array(), ) diff --git a/src/util/blocks.js b/src/util/blocks.js index ccdcda278..10e722d72 100644 --- a/src/util/blocks.js +++ b/src/util/blocks.js @@ -33,6 +33,15 @@ import { useMemo } from '@wordpress/element' import { BlockIcon } from '@wordpress/block-editor' import { __ } from '@wordpress/i18n' +/** + * Enum for disabling and hiding blocks. + */ +export const BLOCK_STATE = Object.freeze( { + ENABLED: 0, + DISABLED: 1, + HIDDEN: 2, +} ) + /** * Converts the registered block name into a block name string that can be used in hook names or ids. * diff --git a/src/welcome/admin.js b/src/welcome/admin.js index e124aaaff..9999b1df8 100644 --- a/src/welcome/admin.js +++ b/src/welcome/admin.js @@ -32,6 +32,7 @@ import AdminSelectSetting from '~stackable/components/admin-select-setting' import AdminToggleSetting from '~stackable/components/admin-toggle-setting' import AdminTextSetting from '~stackable/components/admin-text-setting' import { GettingStarted } from './getting-started' +import { BLOCK_STATE } from '~stackable/util/blocks' const FREE_BLOCKS = importBlocks( require.context( '../block', true, /block\.json$/ ) ) export const getAllBlocks = () => applyFilters( 'stackable.settings.blocks', FREE_BLOCKS ) @@ -92,16 +93,18 @@ const BlockList = () => { ) } +// Toggle the block states between enabled, disabled and hidden. +// Enabled blocks are not stored in the settings object. const BlockToggler = () => { const DERIVED_BLOCKS = getAllBlocks() const [ isSaving, setIsSaving ] = useState( false ) - const [ disabledBlocks, setDisabledBlocks ] = useState( [] ) + const [ disabledBlocks, setDisabledBlocks ] = useState( {} ) useEffect( () => { loadPromise.then( () => { const settings = new models.Settings() settings.fetch().then( response => { - setDisabledBlocks( response.stackable_disabled_blocks ) + setDisabledBlocks( response.stackable_disabled_blocks ?? {} ) } ) } ) }, [] ) @@ -112,35 +115,45 @@ const BlockToggler = () => { model.save().then( () => setIsSaving( false ) ) } - const enableAllBlocks = type => () => { - let newDisabledBlocks = [ ...disabledBlocks ] - DERIVED_BLOCKS[ type ].forEach( block => { - newDisabledBlocks = newDisabledBlocks.filter( blockName => blockName !== block.name ) - } ) - setDisabledBlocks( newDisabledBlocks ) - save( newDisabledBlocks, type ) - } - - const disableAllBlocks = type => () => { - const newDisabledBlocks = [ ...disabledBlocks ] - DERIVED_BLOCKS[ type ].forEach( block => { - if ( ! newDisabledBlocks.includes( block.name ) ) { - newDisabledBlocks.push( block.name ) - } - } ) - setDisabledBlocks( newDisabledBlocks ) - save( newDisabledBlocks, type ) - } - - const toggleBlock = useCallback( ( name, type ) => { - let newDisabledBlocks = null - if ( disabledBlocks.includes( name ) ) { - newDisabledBlocks = disabledBlocks.filter( block => block !== name ) + // TODO: Implement enable, disable and hide all blocks + + // const enableAllBlocks = type => () => { + // let newDisabledBlocks = { ...disabledBlocks } + // DERIVED_BLOCKS[ type ].forEach( block => { + // newDisabledBlocks = newDisabledBlocks.filter( blockName => blockName !== block.name ) + // } ) + + // console.log(DERIVED_BLOCKS[ type ] ) + + // Object.entries(disabledBlocks).forEach( ([key, value]) => { + // if (key in DERIVED_BLOCKS[ type ]) { + // delete disabledBlocks[key] + // } + // } ) + + // setDisabledBlocks( disabledBlocks ) + // save( newDisabledBlocks, type ) + // } + + // const disableAllBlocks = type => () => { + // const newDisabledBlocks = [ ...disabledBlocks ] + // DERIVED_BLOCKS[ type ].forEach( block => { + // if ( ! newDisabledBlocks.includes( block.name ) ) { + // newDisabledBlocks.push( block.name ) + // } + // } ) + // setDisabledBlocks( newDisabledBlocks ) + // save( newDisabledBlocks, type ) + // } + + const toggleBlock = useCallback( ( name, type, value ) => { + const valueInt = Number( value ) + let newDisabledBlocks = { ...disabledBlocks } + + if ( valueInt === BLOCK_STATE.ENABLED ) { + delete newDisabledBlocks[ name ] } else { - newDisabledBlocks = [ - ...disabledBlocks, - name, - ] + newDisabledBlocks = { ...disabledBlocks, [ name ]: valueInt } } setDisabledBlocks( newDisabledBlocks ) save( newDisabledBlocks, type ) @@ -163,13 +176,14 @@ const BlockToggler = () => {
{ isSaving === id && } + { /* + */ }
{ DERIVED_BLOCKS[ id ].map( ( block, i ) => { - const isDisabled = disabledBlocks.includes( block.name ) - + const blockState = disabledBlocks[ block.name ] ?? BLOCK_STATE.ENABLED const demoLink = block[ 'stk-demo' ] && ( { return ( - { - toggleBlock( block.name, id ) + value={ blockState } + key={ i } + options={ [ + { + name: 'Enabled', + value: BLOCK_STATE.ENABLED, + }, + { + name: 'Disabled', + value: BLOCK_STATE.DISABLED, + }, + { + name: 'Hidden', + value: BLOCK_STATE.HIDDEN, + }, + ] } + onChange={ value => { + toggleBlock( block.name, id, value ) } } size="small" - disabled={ __( 'Disabled', i18n ) } - enabled={ __( 'Enabled', i18n ) } /> ) } ) } From e03a884a2f7c0f2c3328492cfa65698d7ea453b6 Mon Sep 17 00:00:00 2001 From: Alquen Antonio Sarmiento Date: Tue, 10 Sep 2024 22:37:28 +0800 Subject: [PATCH 02/32] feat: add enable settings to toolbar features --- src/editor-settings.php | 65 ++++++++++++++++++++ src/format-types/highlight/index.js | 26 ++++---- src/plugins/layout-picker-reset/index.js | 6 +- src/plugins/save-block/index.js | 5 +- src/welcome/admin.js | 76 ++++++++++++++++++++++++ 5 files changed, 163 insertions(+), 15 deletions(-) diff --git a/src/editor-settings.php b/src/editor-settings.php index 32003ca33..128a5ce6e 100644 --- a/src/editor-settings.php +++ b/src/editor-settings.php @@ -157,6 +157,66 @@ public function register_settings() { 'default' => true, ) ); + + register_setting( + 'stackable_editor_settings', + 'stackable_enable_text_highlight', + array( + 'type' => 'boolean', + 'description' => __( 'Adds a toolbar button for highlighting text', STACKABLE_I18N ), + 'sanitize_callback' => 'sanitize_text_field', + 'show_in_rest' => true, + 'default' => true, + ) + ); + + register_setting( + 'stackable_editor_settings', + 'stackable_enable_dynamic_content', + array( + 'type' => 'boolean', + 'description' => __( 'Adds a toolbar button for inserting dynamic content', STACKABLE_I18N ), + 'sanitize_callback' => 'sanitize_text_field', + 'show_in_rest' => true, + 'default' => true, + ) + ); + + register_setting( + 'stackable_editor_settings', + 'stackable_enable_copy_paste_styles', + array( + 'type' => 'boolean', + 'description' => __( 'Adds a toolbar button for copying and pasting block styles', STACKABLE_I18N ), + 'sanitize_callback' => 'sanitize_text_field', + 'show_in_rest' => true, + 'default' => true, + ) + ); + + register_setting( + 'stackable_editor_settings', + 'stackable_enable_reset_layout', + array( + 'type' => 'boolean', + 'description' => __( 'Adds a toolbar button for resetting the layout of a block', STACKABLE_I18N ), + 'sanitize_callback' => 'sanitize_text_field', + 'show_in_rest' => true, + 'default' => true, + ) + ); + + register_setting( + 'stackable_editor_settings', + 'stackable_enable_save_as_default_block', + array( + 'type' => 'boolean', + 'description' => __( 'Adds a toolbar button for saving the current block variation as the default block', STACKABLE_I18N ), + 'sanitize_callback' => 'sanitize_text_field', + 'show_in_rest' => true, + 'default' => true, + ) + ); } public function sanitize_array_setting( $input ) { @@ -177,6 +237,11 @@ public function add_settings( $settings ) { $settings['stackable_auto_collapse_panels'] = get_option( 'stackable_auto_collapse_panels' ); $settings['stackable_enable_block_linking'] = get_option( 'stackable_enable_block_linking' ); $settings['stackable_enable_carousel_lazy_loading'] = get_option( 'stackable_enable_carousel_lazy_loading' ); + $settings['stackable_enable_text_highlight'] = get_option( 'stackable_enable_text_highlight' ); + $settings['stackable_enable_dynamic_content'] = get_option( 'stackable_enable_dynamic_content' ); + $settings['stackable_enable_copy_paste_styles'] = get_option( 'stackable_enable_copy_paste_styles' ); + $settings['stackable_enable_reset_layout'] = get_option( 'stackable_enable_reset_layout' ); + $settings['stackable_enable_save_as_default_block'] = get_option( 'stackable_enable_save_as_default_block' ); return $settings; } diff --git a/src/format-types/highlight/index.js b/src/format-types/highlight/index.js index 3106e6988..bd13cbc15 100644 --- a/src/format-types/highlight/index.js +++ b/src/format-types/highlight/index.js @@ -5,7 +5,7 @@ import { ColorPaletteControl, AdvancedToolbarControl, Popover, } from '~stackable/components' import { whiteIfDarkBlackIfLight } from '~stackable/util' -import { i18n } from 'stackable' +import { i18n, settings } from 'stackable' /** * WordPress dependencies @@ -256,17 +256,19 @@ const HighlightButton = props => { ) } -registerFormatType( - 'stk/highlight', { - title: __( 'Highlight Text', i18n ), - tagName: 'span', - className: 'stk-highlight', - edit: HighlightButton, - attributes: { - style: 'style', - }, - } -) +if ( settings.stackable_enable_toolbar_text_highlight ) { + registerFormatType( + 'stk/highlight', { + title: __( 'Highlight Text', i18n ), + tagName: 'span', + className: 'stk-highlight', + edit: HighlightButton, + attributes: { + style: 'style', + }, + } + ) +} // Backward compatibility, ugb/highlight, but this is not visible. registerFormatType( diff --git a/src/plugins/layout-picker-reset/index.js b/src/plugins/layout-picker-reset/index.js index eae9c27e2..236a54baf 100644 --- a/src/plugins/layout-picker-reset/index.js +++ b/src/plugins/layout-picker-reset/index.js @@ -1,7 +1,9 @@ /** * External dependencies */ -import { i18n, isContentOnlyMode } from 'stackable' +import { + i18n, isContentOnlyMode, settings, +} from 'stackable' // import { Button } from '~stackable/components' /** @@ -55,7 +57,7 @@ if ( ! isContentOnlyMode ) { return ( <> - { hasVariations && hasLayoutReset && ( + { settings.stackable_enable_reset_layout && hasVariations && hasLayoutReset && ( ) } diff --git a/src/plugins/save-block/index.js b/src/plugins/save-block/index.js index a73cc3b75..bb8c594de 100644 --- a/src/plugins/save-block/index.js +++ b/src/plugins/save-block/index.js @@ -6,6 +6,7 @@ import './variation-picker' import './custom-block-styles-editor' import SaveMenu from './save-menu' import { useSavedDefaultBlockStyle } from '~stackable/hooks' +import { settings } from 'stackable' /** * WordPress dependencies @@ -25,7 +26,9 @@ const _SaveMenu = withSelect( select => { } } )( SaveMenu ) -registerPlugin( 'stackable-save-block-menu', { render: _SaveMenu } ) +if ( settings.stackable_enable_save_as_default_block ) { + registerPlugin( 'stackable-save-block-menu', { render: _SaveMenu } ) +} /** * Add the block style loader to each Stackable block. diff --git a/src/welcome/admin.js b/src/welcome/admin.js index 9999b1df8..e0d920e2b 100644 --- a/src/welcome/admin.js +++ b/src/welcome/admin.js @@ -290,6 +290,11 @@ const EditorSettings = () => { 'stackable_auto_collapse_panels', 'stackable_enable_block_linking', 'stackable_enable_carousel_lazy_loading', + 'stackable_enable_text_highlight', + 'stackable_enable_dynamic_content', + 'stackable_enable_copy_paste_styles', + 'stackable_enable_reset_layout', + 'stackable_enable_save_as_default_block', ] ) ) } ) } ) @@ -439,6 +444,77 @@ const EditorSettings = () => { } } help={ __( 'Disable this if you encounter layout or spacing issues when using images inside carousel-type blocks because of image lazy loading.', i18n ) } /> + { + setIsBusy( true ) + const model = new models.Settings( { stackable_enable_text_highlight: value } ) // eslint-disable-line camelcase + model.save().then( () => setIsBusy( false ) ) + setSettings( { + ...settings, + stackable_enable_text_highlight: value, // eslint-disable-line camelcase + } ) + } } + help={ __( 'Adds a toolbar button for highlighting text', i18n ) } + /> + { + setIsBusy( true ) + const model = new models.Settings( { stackable_enable_dynamic_content: value } ) // eslint-disable-line camelcase + model.save().then( () => setIsBusy( false ) ) + setSettings( { + ...settings, + stackable_enable_dynamic_content: value, // eslint-disable-line camelcase + } ) + } } + help={ __( 'Adds a toolbar button for inserting dynamic content', i18n ) } + /> + { + setIsBusy( true ) + const model = new models.Settings( { stackable_enable_copy_paste_styles: value } ) // eslint-disable-line camelcase + model.save().then( () => setIsBusy( false ) ) + setSettings( { + ...settings, + stackable_enable_copy_paste_styles: value, // eslint-disable-line camelcase + } ) + } } + help={ __( 'Adds a toolbar button for copying and pasting block styles', i18n ) } + /> + { + setIsBusy( true ) + const model = new models.Settings( { stackable_enable_reset_layout: value } ) // eslint-disable-line camelcase + model.save().then( () => setIsBusy( false ) ) + setSettings( { + ...settings, + stackable_enable_reset_layout: value, // eslint-disable-line camelcase + } ) + } } + help={ __( 'Adds a toolbar button for resetting the layout of a stackble block back to the original', i18n ) } + /> + { + setIsBusy( true ) + const model = new models.Settings( { stackable_enable_save_as_default_block: value } ) // eslint-disable-line camelcase + model.save().then( () => setIsBusy( false ) ) + setSettings( { + ...settings, + stackable_enable_save_as_default_block: value, // eslint-disable-line camelcase + } ) + } } + help={ __( 'Adds a toolbar button for saving a block as the default block', i18n ) } + /> + { isBusy &&
From 4920cb4b3768e56080eae3663f86b9fdc027408b Mon Sep 17 00:00:00 2001 From: Alquen Antonio Sarmiento Date: Mon, 23 Sep 2024 12:27:35 +0800 Subject: [PATCH 03/32] feat: add settings store for tracking changes --- src/welcome/store.js | 62 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/welcome/store.js diff --git a/src/welcome/store.js b/src/welcome/store.js new file mode 100644 index 000000000..21081f48c --- /dev/null +++ b/src/welcome/store.js @@ -0,0 +1,62 @@ +/** + * WordPress dependencies + */ +import { createReduxStore, register } from '@wordpress/data' +import { models } from '@wordpress/api' + +// Default state for the store +const DEFAULT_STATE = { + settings: {}, +} + +// Store actions +const STORE_ACTIONS = { + updateSettings: settings => { + return { + type: 'UPDATE_SETTINGS', + settings, + } + }, + saveSettings: () => { + return { + type: 'SAVE_SETTINGS', + } + }, +} + +// Store selectors +const STORE_SELECTORS = { + getSettings: state => state.settings, +} + +// Store reducer +const STORE_REDUCER = ( state = DEFAULT_STATE, action ) => { + switch ( action.type ) { + case 'UPDATE_SETTINGS': { + return { + ...state, + settings: { + ...state.settings, + ...action.settings, + }, + } + } + case 'SAVE_SETTINGS': { + const model = new models.Settings( state.settings ) // eslint-disable-line camelcase + model.save() + return state + } + default: + return state + } +} + +export const registerSettingsStore = () => { + register( + createReduxStore( 'stackable/settings', { + reducer: STORE_REDUCER, + actions: STORE_ACTIONS, + selectors: STORE_SELECTORS, + } ) + ) +} From 1439e8fd15692f1062f6bee53d1d93a2700b5755 Mon Sep 17 00:00:00 2001 From: Alquen Antonio Sarmiento Date: Mon, 23 Sep 2024 12:28:36 +0800 Subject: [PATCH 04/32] feat: settings content organizaiont --- src/welcome/index.php | 55 ++++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/src/welcome/index.php b/src/welcome/index.php index b2abfec3c..21ee9d605 100644 --- a/src/welcome/index.php +++ b/src/welcome/index.php @@ -174,44 +174,56 @@ public static function print_premium_button() { ?> public function stackable_settings_content() { ?> -
-
+
+
print_header() ?> print_premium_button() ?> print_tabs() ?>

-
-
+
+
+
-
+

-

+

-
-

+
+

', '' ) ?>

-
+
-
+
+

+

+

+
+
+
+

+

+
+
+
-
+ +

can_use_premium_code() ) : ?>

' . __( 'Learn more', STACKABLE_I18N ) . '' ?>

@@ -224,7 +236,7 @@ public function stackable_settings_content() {

-
+

@@ -235,20 +247,19 @@ public function stackable_settings_content() {

-
-

-

- -
+
+

+

+
-
+

-
+
Date: Mon, 23 Sep 2024 12:34:12 +0800 Subject: [PATCH 05/32] feat: style for sidenav --- src/welcome/admin.scss | 91 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 4 deletions(-) diff --git a/src/welcome/admin.scss b/src/welcome/admin.scss index 9586aa79e..ca7605353 100644 --- a/src/welcome/admin.scss +++ b/src/welcome/admin.scss @@ -8,6 +8,11 @@ --stk-welcome-light-border: #d0d5dd; } +// Make scroll smooth with inline navigation. +html { + scroll-behavior: smooth; +} + // Clear out the margins of the admin page. body[class*="page_stackable"], @@ -18,6 +23,10 @@ body[class*="page_stk-"] { #wpbody-content > .wrap:not(#fs_connect) { // stylelint-disable-line selector-id-pattern margin: 0; padding: 0 50px; + + &.wrap-settings { + padding: 0; + } } } @@ -31,6 +40,10 @@ body[class*="page_stk-"] { border-bottom: 1px solid var(--stk-welcome-light-border); font-weight: 300; + &.s-header-settings { + margin: 0; + } + .s-header { padding: 0px; position: absolute; @@ -397,6 +410,9 @@ body.toplevel_page_stk-custom-fields { color: #f34957; } } + &.s-box-hidden { + display: none; + } } .s-absolute-spinner { position: absolute; @@ -476,10 +492,77 @@ body.toplevel_page_stk-custom-fields { margin-bottom: 0 !important; } } - .s-body-container-grid { - display: grid; - grid-template-columns: 1fr 350px; - grid-gap: 30px; + .s-sidenav { + height: 80%; + width: 250px; + left: 0; + position: absolute; + background-color: #fff; + padding-top: 30px; + display: flex; + flex-direction: column; + justify-content: flex-start; + .s-sidenav-item { + color: #444; + font-size: 14px; + padding: 15px 20px; + text-decoration: none; + display: block; + position: relative; + transition: background-color 0.3s ease; + cursor: pointer; + &:hover { + color: var(--stk-welcome-primary) !important; + } + &.s-active { + font-weight: 600; + color: var(--stk-welcome-primary) !important; + transition: color 0.3s ease; + &::after { + content: ""; + position: absolute; + right: 0; + top: 0; + bottom: 0; + width: 2px; + background-color: var(--stk-welcome-primary); + transition: width 0.3s ease; + } + } + &.s-sidenav-item-highlight { + font-weight: bold; + } + } + // This is a button, render this in the bottom of the side bar + // The side bar is a flex container, so this will be at the bottom. + .s-save-changes { + background: linear-gradient(135deg, #b300be, #f00069); + transition: all 0.1s ease-in-out; + text-decoration: none; + border: none; + color: #fff; + padding: 12px 20px; + text-transform: uppercase; + letter-spacing: 1px; + font-size: 14px; + cursor: pointer; + box-sizing: border-box !important; + display: inline-block; + margin: 120px 20px 0; + &:hover { + opacity: 0.85; + box-shadow: none !important; + } + } + } + .s-sidenav-fixed { + position: fixed; + height: 100%; + top: 32px; + left: 160px; + } + .s-body-container-with-sidenav { + margin-left: 250px; } .s-side { h2, From a494402bda1c0d1c4a2e743f02e4fb6b38bb3906 Mon Sep 17 00:00:00 2001 From: Alquen Antonio Sarmiento Date: Mon, 23 Sep 2024 12:49:49 +0800 Subject: [PATCH 06/32] feat: sidenav, organization, disabling blocks, and save changes --- src/welcome/admin.js | 752 +++++++++++++++++++++++-------------------- 1 file changed, 409 insertions(+), 343 deletions(-) diff --git a/src/welcome/admin.js b/src/welcome/admin.js index e0d920e2b..8ec649941 100644 --- a/src/welcome/admin.js +++ b/src/welcome/admin.js @@ -17,6 +17,7 @@ import domReady from '@wordpress/dom-ready' import { Spinner, CheckboxControl } from '@wordpress/components' import { loadPromise, models } from '@wordpress/api' import { applyFilters } from '@wordpress/hooks' +import { dispatch } from '@wordpress/data' /** * External dependencies @@ -31,10 +32,15 @@ import { createRoot } from '~stackable/util/element' import AdminSelectSetting from '~stackable/components/admin-select-setting' import AdminToggleSetting from '~stackable/components/admin-toggle-setting' import AdminTextSetting from '~stackable/components/admin-text-setting' +import AdvancedToolbarControl from '~stackable/components/advanced-toolbar-control' import { GettingStarted } from './getting-started' import { BLOCK_STATE } from '~stackable/util/blocks' +import { registerSettingsStore } from './store' + +registerSettingsStore() const FREE_BLOCKS = importBlocks( require.context( '../block', true, /block\.json$/ ) ) + export const getAllBlocks = () => applyFilters( 'stackable.settings.blocks', FREE_BLOCKS ) export const BLOCK_CATEROGIES = [ @@ -58,6 +64,17 @@ export const BLOCK_CATEROGIES = [ }, ] +// Implement pick without using lodash, because themes and plugins might remove +// lodash from the admin. +const pick = ( obj, keys ) => { + return keys.reduce( ( acc, key ) => { + if ( obj && obj.hasOwnProperty( key ) ) { + acc[ key ] = obj[ key ] + } + return acc + }, {} ) +} + const BlockList = () => { const DERIVED_BLOCKS = getAllBlocks() return ( @@ -93,158 +110,6 @@ const BlockList = () => { ) } -// Toggle the block states between enabled, disabled and hidden. -// Enabled blocks are not stored in the settings object. -const BlockToggler = () => { - const DERIVED_BLOCKS = getAllBlocks() - const [ isSaving, setIsSaving ] = useState( false ) - const [ disabledBlocks, setDisabledBlocks ] = useState( {} ) - - useEffect( () => { - loadPromise.then( () => { - const settings = new models.Settings() - settings.fetch().then( response => { - setDisabledBlocks( response.stackable_disabled_blocks ?? {} ) - } ) - } ) - }, [] ) - - const save = ( disabledBlocks, type ) => { - setIsSaving( type ) - const model = new models.Settings( { stackable_disabled_blocks: disabledBlocks } ) // eslint-disable-line camelcase - model.save().then( () => setIsSaving( false ) ) - } - - // TODO: Implement enable, disable and hide all blocks - - // const enableAllBlocks = type => () => { - // let newDisabledBlocks = { ...disabledBlocks } - // DERIVED_BLOCKS[ type ].forEach( block => { - // newDisabledBlocks = newDisabledBlocks.filter( blockName => blockName !== block.name ) - // } ) - - // console.log(DERIVED_BLOCKS[ type ] ) - - // Object.entries(disabledBlocks).forEach( ([key, value]) => { - // if (key in DERIVED_BLOCKS[ type ]) { - // delete disabledBlocks[key] - // } - // } ) - - // setDisabledBlocks( disabledBlocks ) - // save( newDisabledBlocks, type ) - // } - - // const disableAllBlocks = type => () => { - // const newDisabledBlocks = [ ...disabledBlocks ] - // DERIVED_BLOCKS[ type ].forEach( block => { - // if ( ! newDisabledBlocks.includes( block.name ) ) { - // newDisabledBlocks.push( block.name ) - // } - // } ) - // setDisabledBlocks( newDisabledBlocks ) - // save( newDisabledBlocks, type ) - // } - - const toggleBlock = useCallback( ( name, type, value ) => { - const valueInt = Number( value ) - let newDisabledBlocks = { ...disabledBlocks } - - if ( valueInt === BLOCK_STATE.ENABLED ) { - delete newDisabledBlocks[ name ] - } else { - newDisabledBlocks = { ...disabledBlocks, [ name ]: valueInt } - } - setDisabledBlocks( newDisabledBlocks ) - save( newDisabledBlocks, type ) - }, [ setDisabledBlocks, disabledBlocks ] ) - - return ( - <> - { BLOCK_CATEROGIES.map( ( { - id, label, Icon, - } ) => { - const classes = classnames( [ - 's-box-block__title', - `s-box-block__title--${ id }`, - ] ) - return ( -
-

- { Icon && } - { label } -

-
- { isSaving === id && } - { /* - - - */ } -
-
- { DERIVED_BLOCKS[ id ].map( ( block, i ) => { - const blockState = disabledBlocks[ block.name ] ?? BLOCK_STATE.ENABLED - const demoLink = block[ 'stk-demo' ] && ( - ev.stopPropagation() } - > - { __( 'view demo', i18n ) } - - ) - - const title = <> - { __( block.title, i18n ) } { /* eslint-disable-line @wordpress/i18n-no-variables */ } - { demoLink } - - - return ( - { - toggleBlock( block.name, id, value ) - } } - size="small" - /> - ) - } ) } -
-
- ) - } ) } - - ) -} - -// Implement pick without using lodash, because themes and plugins might remove -// lodash from the admin. -const pick = ( obj, keys ) => { - return keys.reduce( ( acc, key ) => { - if ( obj && obj.hasOwnProperty( key ) ) { - acc[ key ] = obj[ key ] - } - return acc - }, {} ) -} - // Create an admin notice if there's an error fetching the settings. const SettingsNotice = () => { const [ error, setError ] = useState( null ) @@ -272,189 +137,176 @@ const SettingsNotice = () => { ) } +const SaveChangeButton = () => { + return ( + + ) +} + +const Sidenav = () => { + const tabList = [ + { id: 'editor-settings', label: __( 'Editor Settings', i18n ) }, + { id: 'responsiveness', label: __( 'Responsiveness', i18n ) }, + { id: 'blocks', label: __( 'Block', i18n ) }, + { id: 'optimizations', label: __( 'Optimization', i18n ) }, + { id: 'global-settings', label: __( 'Global Settings', i18n ) }, + { id: 'role-manager', label: __( 'Role Manager', i18n ) }, + { id: 'custom-fields-settings', label: __( 'Custom Fields', i18n ) }, + { id: 'integrations', label: __( 'Integration', i18n ) }, + { id: 'other-settings', label: __( 'Miscellaneous ', i18n ) }, + ] + const [ activeId, setActiveId ] = useState( 'editor-settings' ) + + const handleTabClick = id => { + const previousComponent = document.querySelector( `#${ activeId }` ) + if ( previousComponent ) { + previousComponent.classList.add( 's-box-hidden' ) + } + const selectedComponent = document.querySelector( `#${ id }` ) + if ( selectedComponent ) { + selectedComponent.classList.remove( 's-box-hidden' ) + } + setActiveId( id ) + } + + useEffect( () => { + const handleScroll = () => { + const header = document.querySelector( '.s-header-settings' ) + const sidenav = document.querySelector( '.s-sidenav' ) + + if ( header ) { + // If the header is scrolled out of view, make the sidebar fixed + if ( header.getBoundingClientRect().bottom <= 32 ) { + sidenav.classList.add( 's-sidenav-fixed' ) + } else { + sidenav.classList.remove( 's-sidenav-fixed' ) + } + } + } + window.addEventListener( 'scroll', handleScroll ) + return () => { + window.removeEventListener( 'scroll', handleScroll ) + } + }, [] ) + + return ( + <> +
+ { tabList.map( ( { id, label } ) => { + return ( + ) + } ) } +
+ + + ) +} + const EditorSettings = () => { const [ settings, setSettings ] = useState( {} ) - const [ isBusy, setIsBusy ] = useState( false ) - const [ saveTimeout, setSaveTimeout ] = useState( null ) useEffect( () => { loadPromise.then( () => { const settings = new models.Settings() settings.fetch().then( response => { setSettings( pick( response, [ - 'stackable_google_maps_api_key', - 'stackable_enable_design_library', - 'stackable_optimize_inline_css', 'stackable_block_default_width', 'stackable_block_wide_width', - 'stackable_auto_collapse_panels', + 'stackable_enable_design_library', 'stackable_enable_block_linking', - 'stackable_enable_carousel_lazy_loading', 'stackable_enable_text_highlight', 'stackable_enable_dynamic_content', 'stackable_enable_copy_paste_styles', 'stackable_enable_reset_layout', 'stackable_enable_save_as_default_block', + 'stackable_auto_collapse_panels', ] ) ) } ) } ) }, [] ) - return <> - { - setIsBusy( true ) - const model = new models.Settings( { stackable_enable_design_library: value } ) // eslint-disable-line camelcase - model.save().then( () => setIsBusy( false ) ) - setSettings( { - ...settings, - stackable_enable_design_library: value, // eslint-disable-line camelcase - } ) - } } - help={ __( 'Adds a button on the top of the editor which gives access to a collection of pre-made block designs.', i18n ) } - /> - { - setIsBusy( true ) - const model = new models.Settings( { stackable_optimize_inline_css: value } ) // eslint-disable-line camelcase - model.save().then( () => setIsBusy( false ) ) - setSettings( { - ...settings, - stackable_optimize_inline_css: value, // eslint-disable-line camelcase - } ) - } } - help={ __( 'Optimize inlined CSS styles. If this is enabled, similar selectors will be combined together, helpful if you changed Block Defaults.', i18n ) } - /> - { - setIsBusy( true ) - const model = new models.Settings( { stackable_auto_collapse_panels: value } ) // eslint-disable-line camelcase - model.save().then( () => setIsBusy( false ) ) - setSettings( { - ...settings, - stackable_auto_collapse_panels: value, // eslint-disable-line camelcase - } ) - } } - help={ __( 'Collapse other inspector panels when opening another, keeping only one open at a time.', i18n ) } - /> - { - setIsBusy( true ) - const model = new models.Settings( { stackable_enable_block_linking: value } ) // eslint-disable-line camelcase - model.save().then( () => setIsBusy( false ) ) - setSettings( { - ...settings, - stackable_enable_block_linking: value, // eslint-disable-line camelcase - } ) - } } - help={ - <> - { __( 'Gives you the ability to link columns. Any changes you make on one column will automatically get applied on the other columns.', i18n ) } -   - { __( 'Learn more', i18n ) } - - } - /> +

{ __( 'Blocks', i18n ) }

{ - clearTimeout( saveTimeout ) setSettings( { ...settings, stackable_block_default_width: value, // eslint-disable-line camelcase } ) - setSaveTimeout( setTimeout( () => { - setIsBusy( true ) - const model = new models.Settings( { stackable_block_default_width: value } ) // eslint-disable-line camelcase - model.save().then( () => setIsBusy( false ) ) - }, 400 ) ) + dispatch( 'stackable/settings' ).updateSettings( { stackable_block_default_width: value } ) // eslint-disable-line camelcase } } help={ __( 'The width used when a Columns block has its Content Width set to center. This is automatically detected from your theme. You can adjust it if your blocks are not aligned correctly. In px, you can also use other units or use a calc() formula.', i18n ) } - > + /> { - clearTimeout( saveTimeout ) setSettings( { ...settings, stackable_block_wide_width: value, // eslint-disable-line camelcase } ) - setSaveTimeout( setTimeout( () => { - setIsBusy( true ) - const model = new models.Settings( { stackable_block_wide_width: value } ) // eslint-disable-line camelcase - model.save().then( () => setIsBusy( false ) ) - }, 400 ) ) + dispatch( 'stackable/settings' ).updateSettings( { stackable_block_wide_width: value } ) // eslint-disable-line camelcase } } help={ __( 'The width used when a Columns block has its Content Width set to wide. This is automatically detected from your theme. You can adjust it if your blocks are not aligned correctly. In px, you can also use other units or use a calc() formula.', i18n ) } /> - { __( 'Editor', i18n ) } + { - clearTimeout( saveTimeout ) setSettings( { ...settings, - stackable_google_maps_api_key: value, // eslint-disable-line camelcase + stackable_enable_design_library: value, // eslint-disable-line camelcase } ) - setSaveTimeout( - setTimeout( () => { - setIsBusy( true ) - const model = new models.Settings( { - stackable_google_maps_api_key: value, // eslint-disable-line camelcase - } ) - model.save().then( () => setIsBusy( false ) ) - }, 400 ) - ) + dispatch( 'stackable/settings' ).updateSettings( { stackable_enable_design_library: value } ) // eslint-disable-line camelcase } } - help={ - <> - { __( - 'Adding a Google API Key enables additional features of the Stackable Map Block.', - i18n - ) } -   - { __( 'Learn more', i18n ) } - - } + help={ __( 'Adds a button on the top of the editor which gives access to a collection of pre-made block designs.', i18n ) } /> { - setIsBusy( true ) - const model = new models.Settings( { stackable_enable_carousel_lazy_loading: value } ) // eslint-disable-line camelcase - model.save().then( () => setIsBusy( false ) ) setSettings( { ...settings, - stackable_enable_carousel_lazy_loading: value, // eslint-disable-line camelcase + stackable_enable_block_linking: value, // eslint-disable-line camelcase } ) + dispatch( 'stackable/settings' ).updateSettings( { stackable_enable_block_linking: value } ) // eslint-disable-line camelcase } } - help={ __( 'Disable this if you encounter layout or spacing issues when using images inside carousel-type blocks because of image lazy loading.', i18n ) } + help={ + <> + { __( 'Gives you the ability to link columns. Any changes you make on one column will automatically get applied on the other columns.', i18n ) } +   + { __( 'Learn more', i18n ) } + + } /> +

{ __( 'Toolbar', i18n ) }

{ - setIsBusy( true ) - const model = new models.Settings( { stackable_enable_text_highlight: value } ) // eslint-disable-line camelcase - model.save().then( () => setIsBusy( false ) ) setSettings( { ...settings, stackable_enable_text_highlight: value, // eslint-disable-line camelcase } ) + dispatch( 'stackable/settings' ).updateSettings( { stackable_enable_text_highlight: value } ) // eslint-disable-line camelcase } } help={ __( 'Adds a toolbar button for highlighting text', i18n ) } /> @@ -462,13 +314,11 @@ const EditorSettings = () => { label={ __( 'Toolbar Dynamic Content', i18n ) } value={ settings.stackable_enable_dynamic_content } onChange={ value => { - setIsBusy( true ) - const model = new models.Settings( { stackable_enable_dynamic_content: value } ) // eslint-disable-line camelcase - model.save().then( () => setIsBusy( false ) ) setSettings( { ...settings, stackable_enable_dynamic_content: value, // eslint-disable-line camelcase } ) + dispatch( 'stackable/settings' ).updateSettings( { stackable_enable_dynamic_content: value } ) // eslint-disable-line camelcase } } help={ __( 'Adds a toolbar button for inserting dynamic content', i18n ) } /> @@ -476,13 +326,11 @@ const EditorSettings = () => { label={ __( 'Copy & Paste Styles', i18n ) } value={ settings.stackable_enable_copy_paste_styles } onChange={ value => { - setIsBusy( true ) - const model = new models.Settings( { stackable_enable_copy_paste_styles: value } ) // eslint-disable-line camelcase - model.save().then( () => setIsBusy( false ) ) setSettings( { ...settings, stackable_enable_copy_paste_styles: value, // eslint-disable-line camelcase } ) + dispatch( 'stackable/settings' ).updateSettings( { stackable_enable_copy_paste_styles: value } ) // eslint-disable-line camelcase } } help={ __( 'Adds a toolbar button for copying and pasting block styles', i18n ) } /> @@ -490,13 +338,11 @@ const EditorSettings = () => { label={ __( 'Reset Layout', i18n ) } value={ settings.stackable_enable_reset_layout } onChange={ value => { - setIsBusy( true ) - const model = new models.Settings( { stackable_enable_reset_layout: value } ) // eslint-disable-line camelcase - model.save().then( () => setIsBusy( false ) ) setSettings( { ...settings, stackable_enable_reset_layout: value, // eslint-disable-line camelcase } ) + dispatch( 'stackable/settings' ).updateSettings( { stackable_enable_reset_layout: value } ) // eslint-disable-line camelcase } } help={ __( 'Adds a toolbar button for resetting the layout of a stackble block back to the original', i18n ) } /> @@ -504,33 +350,36 @@ const EditorSettings = () => { label={ __( 'Save as Default Block', i18n ) } value={ settings.stackable_enable_save_as_default_block } onChange={ value => { - setIsBusy( true ) - const model = new models.Settings( { stackable_enable_save_as_default_block: value } ) // eslint-disable-line camelcase - model.save().then( () => setIsBusy( false ) ) setSettings( { ...settings, stackable_enable_save_as_default_block: value, // eslint-disable-line camelcase } ) + dispatch( 'stackable/settings' ).updateSettings( { stackable_enable_save_as_default_block: value } ) // eslint-disable-line camelcase } } help={ __( 'Adds a toolbar button for saving a block as the default block', i18n ) } /> - - { isBusy && -
- -
- } +

{ __( 'Blocks', i18n ) }

+ { + setSettings( { + ...settings, + stackable_auto_collapse_panels: value, // eslint-disable-line camelcase + } ) + dispatch( 'stackable/settings' ).updateSettings( { stackable_auto_collapse_panels: value } ) // eslint-disable-line camelcase + } } + help={ __( 'Collapse other inspector panels when opening another, keeping only one open at a time.', i18n ) } + /> } -const DynamicBreakpointsSettings = () => { +const Responsiveness = () => { const [ tabletBreakpoint, setTabletBreakpoint ] = useState( '' ) const [ mobileBreakpoint, setMobileBreakpoint ] = useState( '' ) const [ isReady, setIsReady ] = useState( false ) - const [ isBusy, setIsBusy ] = useState( false ) useEffect( () => { - setIsBusy( true ) loadPromise.then( () => { const settings = new models.Settings() settings.fetch().then( response => { @@ -540,24 +389,15 @@ const DynamicBreakpointsSettings = () => { setMobileBreakpoint( breakpoints.mobile || '' ) } setIsReady( true ) - setIsBusy( false ) } ) } ) }, [] ) useEffect( () => { if ( isReady ) { - const t = setTimeout( () => { - setIsBusy( true ) - const model = new models.Settings( { - stackable_dynamic_breakpoints: { // eslint-disable-line camelcase - tablet: tabletBreakpoint, - mobile: mobileBreakpoint, - }, - } ) - model.save().then( () => setIsBusy( false ) ) - }, 400 ) - return () => clearTimeout( t ) + dispatch( 'stackable/settings' ).updateSettings( { + stackable_dynamic_breakpoints: { tablet: tabletBreakpoint, mobile: mobileBreakpoint }, // eslint-disable-line camelcase + } ) } }, [ tabletBreakpoint, mobileBreakpoint, isReady ] ) @@ -578,14 +418,197 @@ const DynamicBreakpointsSettings = () => { placeholder="768" > px
- { isBusy && -
- -
- } } +// Toggle the block states between enabled, disabled and hidden. +// Enabled blocks are not stored in the settings object. +const Blocks = () => { + const DERIVED_BLOCKS = getAllBlocks() + const [ isSaving, setIsSaving ] = useState( false ) + const [ disabledBlocks, setDisabledBlocks ] = useState( {} ) + + useEffect( () => { + loadPromise.then( () => { + const settings = new models.Settings() + settings.fetch().then( response => { + setDisabledBlocks( response.stackable_disabled_blocks ?? {} ) + } ) + } ) + }, [] ) + + const save = ( disabledBlocks, type ) => { + setIsSaving( type ) + dispatch( 'stackable/settings' ).updateSettings( { stackable_disabled_blocks: disabledBlocks } ) // eslint-disable-line camelcase + .then( () => setIsSaving( false ) ) + } + + // TODO: Implement enable, disable and hide all blocks + + // const enableAllBlocks = type => () => { + // let newDisabledBlocks = { ...disabledBlocks } + // DERIVED_BLOCKS[ type ].forEach( block => { + // newDisabledBlocks = newDisabledBlocks.filter( blockName => blockName !== block.name ) + // } ) + + // console.log(DERIVED_BLOCKS[ type ] ) + + // Object.entries(disabledBlocks).forEach( ([key, value]) => { + // if (key in DERIVED_BLOCKS[ type ]) { + // delete disabledBlocks[key] + // } + // } ) + + // setDisabledBlocks( disabledBlocks ) + // save( newDisabledBlocks, type ) + // } + + // const disableAllBlocks = type => () => { + // const newDisabledBlocks = [ ...disabledBlocks ] + // DERIVED_BLOCKS[ type ].forEach( block => { + // if ( ! newDisabledBlocks.includes( block.name ) ) { + // newDisabledBlocks.push( block.name ) + // } + // } ) + // setDisabledBlocks( newDisabledBlocks ) + // save( newDisabledBlocks, type ) + // } + + const toggleBlock = useCallback( ( name, type, value ) => { + const valueInt = Number( value ) + let newDisabledBlocks = { ...disabledBlocks } + + if ( valueInt === BLOCK_STATE.ENABLED ) { + delete newDisabledBlocks[ name ] + } else { + newDisabledBlocks = { ...disabledBlocks, [ name ]: valueInt } + } + setDisabledBlocks( newDisabledBlocks ) + save( newDisabledBlocks, type ) + }, [ setDisabledBlocks, disabledBlocks ] ) + + return ( + <> + { BLOCK_CATEROGIES.map( ( { + id, label, Icon, + } ) => { + const classes = classnames( [ + 's-box-block__title', + `s-box-block__title--${ id }`, + ] ) + return ( +
+

+ { Icon && } + { label } +

+
+ { isSaving === id && } + { /* + + + */ } +
+
+ { DERIVED_BLOCKS[ id ].map( ( block, i ) => { + const blockState = disabledBlocks[ block.name ] ?? BLOCK_STATE.ENABLED + const demoLink = block[ 'stk-demo' ] && ( + ev.stopPropagation() } + > + { __( 'view demo', i18n ) } + + ) + + const title = <> + { __( block.title, i18n ) } { /* eslint-disable-line @wordpress/i18n-no-variables */ } + { demoLink } + + + return ( + <> + { title } + { + toggleBlock( block.name, id, value ) + } } + isSmall={ true } + /> + + ) + } ) } +
+
+ ) + } ) } + + ) +} + +const Optimizations = () => { + const [ settings, setSettings ] = useState( {} ) + + useEffect( () => { + loadPromise.then( () => { + const settings = new models.Settings() + settings.fetch().then( response => { + setSettings( pick( response, [ + 'stackable_optimize_inline_css', + 'stackable_enable_carousel_lazy_loading', + ] ) ) + } ) + } ) + }, [] ) + + return <> + { + setSettings( { + ...settings, + stackable_optimize_inline_css: value, // eslint-disable-line camelcase + } ) + dispatch( 'stackable/settings' ).updateSettings( { stackable_optimize_inline_css: value } ) // eslint-disable-line camelcase + } } + help={ __( 'Optimize inlined CSS styles. If this is enabled, similar selectors will be combined together, helpful if you changed Block Defaults.', i18n ) } + /> + { + setSettings( { + ...settings, + stackable_enable_carousel_lazy_loading: value, // eslint-disable-line camelcase + } ) + dispatch( 'stackable/settings' ).updateSettings( { stackable_enable_carousel_lazy_loading: value } ) // eslint-disable-line camelcase + } } + help={ __( 'Disable this if you encounter layout or spacing issues when using images inside carousel-type blocks because of image lazy loading.', i18n ) } + /> + +} + const GlobalSettings = () => { const [ forceTypography, setForceTypography ] = useState( false ) @@ -599,8 +622,7 @@ const GlobalSettings = () => { }, [] ) const updateForceTypography = value => { - const model = new models.Settings( { stackable_global_force_typography: value } ) // eslint-disable-line camelcase - model.save() + dispatch( 'stackable/settings' ).updateSettings( { stackable_global_force_typography: value } ) // eslint-disable-line camelcase setForceTypography( value ) } @@ -615,25 +637,48 @@ const GlobalSettings = () => { } -const IconSettings = () => { - const [ faVersion, setFaVersion ] = useState( '' ) +const Integrations = () => { + const [ settings, setSettings ] = useState( {} ) useEffect( () => { loadPromise.then( () => { const settings = new models.Settings() settings.fetch().then( response => { - setFaVersion( response.stackable_icons_fa_free_version || '6.5.1' ) + const picked = pick( response, [ + 'stackable_google_maps_api_key', + 'stackable_icons_fa_free_version', + ] ) + if ( ! picked.stackable_icons_fa_free_version ) { + picked.stackable_icons_fa_free_version = '6.5.1' // eslint-disable-line camelcase + } + setSettings( picked ) } ) } ) }, [] ) - const updateFaVersion = value => { - const model = new models.Settings( { stackable_icons_fa_free_version: value } ) // eslint-disable-line camelcase - model.save() - setFaVersion( value ) - } - - return + return <> + { + setSettings( { + ...settings, + stackable_google_maps_api_key: value, // eslint-disable-line camelcase + } ) + dispatch( 'stackable/settings' ).updateSettings( { stackable_google_maps_api_key: value } ) // eslint-disable-line camelcase + } } + help={ + <> + { __( + 'Adding a Google API Key enables additional features of the Stackable Map Block.', + i18n + ) } +   + { __( 'Learn more', i18n ) } + + } + />
Date: Mon, 30 Sep 2024 13:45:36 +0800 Subject: [PATCH 10/32] feat: use react approach for cleaner and more efficient code --- src/welcome/admin.js | 534 ++++++++++++++++++------------------------- src/welcome/store.js | 62 ----- 2 files changed, 226 insertions(+), 370 deletions(-) delete mode 100644 src/welcome/store.js diff --git a/src/welcome/admin.js b/src/welcome/admin.js index 8ec649941..e0938b4ed 100644 --- a/src/welcome/admin.js +++ b/src/welcome/admin.js @@ -11,13 +11,12 @@ import SVGSectionIcon from './images/settings-icon-section.svg' */ import { __ } from '@wordpress/i18n' import { - useEffect, useState, Fragment, useCallback, + useEffect, useState, Fragment, useCallback, useRef, useMemo, lazy, Suspense, } from '@wordpress/element' import domReady from '@wordpress/dom-ready' import { Spinner, CheckboxControl } from '@wordpress/components' import { loadPromise, models } from '@wordpress/api' import { applyFilters } from '@wordpress/hooks' -import { dispatch } from '@wordpress/data' /** * External dependencies @@ -25,6 +24,7 @@ import { dispatch } from '@wordpress/data' import { i18n, showProNoticesOption, + isPro, } from 'stackable' import classnames from 'classnames' import { importBlocks } from '~stackable/util/admin' @@ -35,9 +35,6 @@ import AdminTextSetting from '~stackable/components/admin-text-setting' import AdvancedToolbarControl from '~stackable/components/advanced-toolbar-control' import { GettingStarted } from './getting-started' import { BLOCK_STATE } from '~stackable/util/blocks' -import { registerSettingsStore } from './store' - -registerSettingsStore() const FREE_BLOCKS = importBlocks( require.context( '../block', true, /block\.json$/ ) ) @@ -137,87 +134,86 @@ const SettingsNotice = () => { ) } -const SaveChangeButton = () => { - return ( - - ) -} - -const Sidenav = () => { - const tabList = [ +const Sidenav = ( { + currentTab, + onTabChange, + handleSettingsSave, +} ) => { + const tabList = useMemo( () => [ { id: 'editor-settings', label: __( 'Editor Settings', i18n ) }, { id: 'responsiveness', label: __( 'Responsiveness', i18n ) }, - { id: 'blocks', label: __( 'Block', i18n ) }, + { id: 'blocks', label: __( 'Blocks', i18n ) }, { id: 'optimizations', label: __( 'Optimization', i18n ) }, { id: 'global-settings', label: __( 'Global Settings', i18n ) }, { id: 'role-manager', label: __( 'Role Manager', i18n ) }, { id: 'custom-fields-settings', label: __( 'Custom Fields', i18n ) }, { id: 'integrations', label: __( 'Integration', i18n ) }, { id: 'other-settings', label: __( 'Miscellaneous ', i18n ) }, - ] - const [ activeId, setActiveId ] = useState( 'editor-settings' ) + ], [] ) - const handleTabClick = id => { - const previousComponent = document.querySelector( `#${ activeId }` ) - if ( previousComponent ) { - previousComponent.classList.add( 's-box-hidden' ) - } - const selectedComponent = document.querySelector( `#${ id }` ) - if ( selectedComponent ) { - selectedComponent.classList.remove( 's-box-hidden' ) - } - setActiveId( id ) - } + const sidenavRef = useRef( null ) useEffect( () => { const handleScroll = () => { const header = document.querySelector( '.s-header-settings' ) - const sidenav = document.querySelector( '.s-sidenav' ) if ( header ) { // If the header is scrolled out of view, make the sidebar fixed if ( header.getBoundingClientRect().bottom <= 32 ) { - sidenav.classList.add( 's-sidenav-fixed' ) + sidenavRef.current.classList.add( 's-sidenav-fixed' ) } else { - sidenav.classList.remove( 's-sidenav-fixed' ) + sidenavRef.current.classList.remove( 's-sidenav-fixed' ) } } } window.addEventListener( 'scroll', handleScroll ) - return () => { - window.removeEventListener( 'scroll', handleScroll ) - } + return () => window.removeEventListener( 'scroll', handleScroll ) }, [] ) return ( <> -
- { tabList.map( ( { id, label } ) => { - return ( - ) - } ) } -
- + ) } -const EditorSettings = () => { +// Main settings component +const Settings = () => { const [ settings, setSettings ] = useState( {} ) + const [ unsavedChanges, setUnsavedChanges ] = useState( {} ) + const [ currentTab, setCurrentTab ] = useState( 'editor-settings' ) + + const handleSettingsChange = useCallback( newSettings => { + setSettings( prev => ( { ...prev, ...newSettings } ) ) + setUnsavedChanges( prev => ( { ...prev, ...newSettings } ) ) + }, [] ) + + const handleSettingsSave = useCallback( () => { + // TODO: Save settings + setUnsavedChanges( {} ) + }, [ unsavedChanges ] ) useEffect( () => { loadPromise.then( () => { @@ -234,22 +230,39 @@ const EditorSettings = () => { 'stackable_enable_reset_layout', 'stackable_enable_save_as_default_block', 'stackable_auto_collapse_panels', + 'stackable_dynamic_breakpoints', + 'stackable_disabled_blocks', ] ) ) } ) } ) }, [] ) + + return <> + +
+ { currentTab === 'editor-settings' && } + { currentTab === 'responsiveness' && } + { currentTab === 'blocks' && } + { currentTab === 'optimizations' && } + { currentTab === 'global-settings' && } + { currentTab === 'role-manager' && } + { currentTab === 'custom-fields-settings' && } + { currentTab === 'integrations' && } + { currentTab === 'other-settings' && } +
+ +} + +const EditorSettings = ( { settings, handleSettingsChange } ) => { return <>

{ __( 'Blocks', i18n ) }

+

{ __( 'You can customize the default width of your blocks here.', i18n ) }

{ - setSettings( { - ...settings, - stackable_block_default_width: value, // eslint-disable-line camelcase - } ) - dispatch( 'stackable/settings' ).updateSettings( { stackable_block_default_width: value } ) // eslint-disable-line camelcase + handleSettingsChange( { stackable_block_default_width: value } ) // eslint-disable-line camelcase } } help={ __( 'The width used when a Columns block has its Content Width set to center. This is automatically detected from your theme. You can adjust it if your blocks are not aligned correctly. In px, you can also use other units or use a calc() formula.', i18n ) } /> @@ -258,24 +271,18 @@ const EditorSettings = () => { value={ settings.stackable_block_wide_width } type="text" onChange={ value => { - setSettings( { - ...settings, - stackable_block_wide_width: value, // eslint-disable-line camelcase - } ) - dispatch( 'stackable/settings' ).updateSettings( { stackable_block_wide_width: value } ) // eslint-disable-line camelcase + handleSettingsChange( { stackable_block_wide_width: value } ) // eslint-disable-line camelcase } } help={ __( 'The width used when a Columns block has its Content Width set to wide. This is automatically detected from your theme. You can adjust it if your blocks are not aligned correctly. In px, you can also use other units or use a calc() formula.', i18n ) } /> +

{ __( 'Editor', i18n ) }

+

{ __( 'You can customize some of the features and behavior of Stackable in the editor here.' ) }

{ - setSettings( { - ...settings, - stackable_enable_design_library: value, // eslint-disable-line camelcase - } ) - dispatch( 'stackable/settings' ).updateSettings( { stackable_enable_design_library: value } ) // eslint-disable-line camelcase + handleSettingsChange( { stackable_enable_design_library: value } ) // eslint-disable-line camelcase } } help={ __( 'Adds a button on the top of the editor which gives access to a collection of pre-made block designs.', i18n ) } /> @@ -283,11 +290,7 @@ const EditorSettings = () => { label={ __( 'Block Linking (Beta)', i18n ) } value={ settings.stackable_enable_block_linking } onChange={ value => { - setSettings( { - ...settings, - stackable_enable_block_linking: value, // eslint-disable-line camelcase - } ) - dispatch( 'stackable/settings' ).updateSettings( { stackable_enable_block_linking: value } ) // eslint-disable-line camelcase + handleSettingsChange( { stackable_enable_block_linking: value } ) // eslint-disable-line camelcase } } help={ <> @@ -297,16 +300,14 @@ const EditorSettings = () => { } /> +

{ __( 'Toolbar', i18n ) }

+

{ __( 'You can disable some toolbar features here.', i18n ) }

{ - setSettings( { - ...settings, - stackable_enable_text_highlight: value, // eslint-disable-line camelcase - } ) - dispatch( 'stackable/settings' ).updateSettings( { stackable_enable_text_highlight: value } ) // eslint-disable-line camelcase + handleSettingsChange( { stackable_enable_text_highlight: value } ) // eslint-disable-line camelcase } } help={ __( 'Adds a toolbar button for highlighting text', i18n ) } /> @@ -314,11 +315,7 @@ const EditorSettings = () => { label={ __( 'Toolbar Dynamic Content', i18n ) } value={ settings.stackable_enable_dynamic_content } onChange={ value => { - setSettings( { - ...settings, - stackable_enable_dynamic_content: value, // eslint-disable-line camelcase - } ) - dispatch( 'stackable/settings' ).updateSettings( { stackable_enable_dynamic_content: value } ) // eslint-disable-line camelcase + handleSettingsChange( { stackable_enable_dynamic_content: value } ) // eslint-disable-line camelcase } } help={ __( 'Adds a toolbar button for inserting dynamic content', i18n ) } /> @@ -326,11 +323,7 @@ const EditorSettings = () => { label={ __( 'Copy & Paste Styles', i18n ) } value={ settings.stackable_enable_copy_paste_styles } onChange={ value => { - setSettings( { - ...settings, - stackable_enable_copy_paste_styles: value, // eslint-disable-line camelcase - } ) - dispatch( 'stackable/settings' ).updateSettings( { stackable_enable_copy_paste_styles: value } ) // eslint-disable-line camelcase + handleSettingsChange( { stackable_enable_copy_paste_styles: value } ) // eslint-disable-line camelcase } } help={ __( 'Adds a toolbar button for copying and pasting block styles', i18n ) } /> @@ -338,11 +331,7 @@ const EditorSettings = () => { label={ __( 'Reset Layout', i18n ) } value={ settings.stackable_enable_reset_layout } onChange={ value => { - setSettings( { - ...settings, - stackable_enable_reset_layout: value, // eslint-disable-line camelcase - } ) - dispatch( 'stackable/settings' ).updateSettings( { stackable_enable_reset_layout: value } ) // eslint-disable-line camelcase + handleSettingsChange( { stackable_enable_reset_layout: value } ) // eslint-disable-line camelcase } } help={ __( 'Adds a toolbar button for resetting the layout of a stackble block back to the original', i18n ) } /> @@ -350,98 +339,69 @@ const EditorSettings = () => { label={ __( 'Save as Default Block', i18n ) } value={ settings.stackable_enable_save_as_default_block } onChange={ value => { - setSettings( { - ...settings, - stackable_enable_save_as_default_block: value, // eslint-disable-line camelcase - } ) - dispatch( 'stackable/settings' ).updateSettings( { stackable_enable_save_as_default_block: value } ) // eslint-disable-line camelcase + handleSettingsChange( { stackable_enable_save_as_default_block: value } ) // eslint-disable-line } } help={ __( 'Adds a toolbar button for saving a block as the default block', i18n ) } /> -

{ __( 'Blocks', i18n ) }

+ +

{ __( 'Inspector', i18n ) }

+

{ __( 'You can customize some of the features and behavior of Stackable in the inspector here.' ) }

{ - setSettings( { - ...settings, - stackable_auto_collapse_panels: value, // eslint-disable-line camelcase - } ) - dispatch( 'stackable/settings' ).updateSettings( { stackable_auto_collapse_panels: value } ) // eslint-disable-line camelcase + handleSettingsChange( { stackable_auto_collapse_panels: value } ) // eslint-disable-line camelcase } } help={ __( 'Collapse other inspector panels when opening another, keeping only one open at a time.', i18n ) } /> } -const Responsiveness = () => { - const [ tabletBreakpoint, setTabletBreakpoint ] = useState( '' ) - const [ mobileBreakpoint, setMobileBreakpoint ] = useState( '' ) - const [ isReady, setIsReady ] = useState( false ) - - useEffect( () => { - loadPromise.then( () => { - const settings = new models.Settings() - settings.fetch().then( response => { - const breakpoints = response.stackable_dynamic_breakpoints - if ( breakpoints ) { - setTabletBreakpoint( breakpoints.tablet || '' ) - setMobileBreakpoint( breakpoints.mobile || '' ) - } - setIsReady( true ) - } ) - } ) - }, [] ) - - useEffect( () => { - if ( isReady ) { - dispatch( 'stackable/settings' ).updateSettings( { - stackable_dynamic_breakpoints: { tablet: tabletBreakpoint, mobile: mobileBreakpoint }, // eslint-disable-line camelcase - } ) - } - }, [ tabletBreakpoint, mobileBreakpoint, isReady ] ) - - return -
- setTabletBreakpoint( value ) } - placeholder="1024" - > px - setMobileBreakpoint( value ) } - placeholder="768" - > px -
-
+const Responsiveness = ( { settings, handleSettingsChange } ) => { + return <> +

{ __( 'Dynamic Breakpoints', i18n ) }

+

+ { __( 'Blocks can be styles differently for tablet and mobile screens, and some styles adjust to make them fit better in smaller screens. You can change the widths when tablet and mobile views are triggered. ', i18n ) } + + { __( 'Learn more', i18n ) } + +

+ { + handleSettingsChange( { + stackable_dynamic_breakpoints: { // eslint-disable-line camelcase + tablet: value, + mobile: settings.stackable_dynamic_breakpoints.mobile || '', // eslint-disable-line camelcase + }, + } ) + } } + placeholder="1024" + > px + { + handleSettingsChange( { + stackable_dynamic_breakpoints: { // eslint-disable-line camelcase + tablet: settings.stackable_dynamic_breakpoints.tablet || '', // eslint-disable-line camelcase + mobile: value, + }, + } ) + } } + placeholder="768" + > px + } // Toggle the block states between enabled, disabled and hidden. // Enabled blocks are not stored in the settings object. -const Blocks = () => { +const Blocks = ( { settings, handleSettingsChange } ) => { const DERIVED_BLOCKS = getAllBlocks() - const [ isSaving, setIsSaving ] = useState( false ) - const [ disabledBlocks, setDisabledBlocks ] = useState( {} ) - - useEffect( () => { - loadPromise.then( () => { - const settings = new models.Settings() - settings.fetch().then( response => { - setDisabledBlocks( response.stackable_disabled_blocks ?? {} ) - } ) - } ) - }, [] ) - - const save = ( disabledBlocks, type ) => { - setIsSaving( type ) - dispatch( 'stackable/settings' ).updateSettings( { stackable_disabled_blocks: disabledBlocks } ) // eslint-disable-line camelcase - .then( () => setIsSaving( false ) ) - } + const disabledBlocks = settings.stackable_disabled_blocks ?? {} // eslint-disable-line camelcase // TODO: Implement enable, disable and hide all blocks @@ -474,7 +434,7 @@ const Blocks = () => { // save( newDisabledBlocks, type ) // } - const toggleBlock = useCallback( ( name, type, value ) => { + const toggleBlock = ( name, type, value ) => { const valueInt = Number( value ) let newDisabledBlocks = { ...disabledBlocks } @@ -483,12 +443,12 @@ const Blocks = () => { } else { newDisabledBlocks = { ...disabledBlocks, [ name ]: valueInt } } - setDisabledBlocks( newDisabledBlocks ) - save( newDisabledBlocks, type ) - }, [ setDisabledBlocks, disabledBlocks ] ) - + handleSettingsChange( { stackable_disabled_blocks: newDisabledBlocks } ) // eslint-disable-line camelcase + } return ( <> +

{ __( 'Blocks', i18n ) }

+

{ __( 'You can enable, hide and disable Stackable blocks. Hiding the blocks hides them from the editor. Disabling the blocks prevent them from being loaded for faster performance.', i18n ) }

{ BLOCK_CATEROGIES.map( ( { id, label, Icon, } ) => { @@ -503,7 +463,6 @@ const Blocks = () => { { label }
- { isSaving === id && } { /* @@ -566,31 +525,14 @@ const Blocks = () => { ) } -const Optimizations = () => { - const [ settings, setSettings ] = useState( {} ) - - useEffect( () => { - loadPromise.then( () => { - const settings = new models.Settings() - settings.fetch().then( response => { - setSettings( pick( response, [ - 'stackable_optimize_inline_css', - 'stackable_enable_carousel_lazy_loading', - ] ) ) - } ) - } ) - }, [] ) - +const Optimizations = ( { settings, handleSettingsChange } ) => { return <> +

{ __( 'Optimizations', i18n ) }

{ - setSettings( { - ...settings, - stackable_optimize_inline_css: value, // eslint-disable-line camelcase - } ) - dispatch( 'stackable/settings' ).updateSettings( { stackable_optimize_inline_css: value } ) // eslint-disable-line camelcase + handleSettingsChange( { stackable_optimize_inline_css: value } ) // eslint-disable-line camelcase } } help={ __( 'Optimize inlined CSS styles. If this is enabled, similar selectors will be combined together, helpful if you changed Block Defaults.', i18n ) } /> @@ -598,75 +540,104 @@ const Optimizations = () => { label={ __( 'Lazy Load Images within Carousels', i18n ) } value={ settings.stackable_enable_carousel_lazy_loading } onChange={ value => { - setSettings( { - ...settings, - stackable_enable_carousel_lazy_loading: value, // eslint-disable-line camelcase - } ) - dispatch( 'stackable/settings' ).updateSettings( { stackable_enable_carousel_lazy_loading: value } ) // eslint-disable-line camelcase + handleSettingsChange( { stackable_enable_carousel_lazy_loading: value } ) // eslint-disable-line camelcase } } help={ __( 'Disable this if you encounter layout or spacing issues when using images inside carousel-type blocks because of image lazy loading.', i18n ) } /> } -const GlobalSettings = () => { - const [ forceTypography, setForceTypography ] = useState( false ) - - useEffect( () => { - loadPromise.then( () => { - const settings = new models.Settings() - settings.fetch().then( response => { - setForceTypography( !! response.stackable_global_force_typography ) - } ) - } ) - }, [] ) - - const updateForceTypography = value => { - dispatch( 'stackable/settings' ).updateSettings( { stackable_global_force_typography: value } ) // eslint-disable-line camelcase - setForceTypography( value ) - } - - return +const GlobalSettings = ( { settings, handleSettingsChange } ) => { + return <> { + handleSettingsChange( { stackable_global_force_typography: value } ) // eslint-disable-line camelcase + } } disabled={ __( 'Not forced', i18n ) } enabled={ __( 'Force styles', i18n ) } /> - + } -const Integrations = () => { - const [ settings, setSettings ] = useState( {} ) +const EditorModeSettings = lazy( () => import( '../../pro__premium_only/src/welcome/editor-mode' ) ) - useEffect( () => { - loadPromise.then( () => { - const settings = new models.Settings() - settings.fetch().then( response => { - const picked = pick( response, [ - 'stackable_google_maps_api_key', - 'stackable_icons_fa_free_version', - ] ) - if ( ! picked.stackable_icons_fa_free_version ) { - picked.stackable_icons_fa_free_version = '6.5.1' // eslint-disable-line camelcase +const RoleManager = () => { + return <> +

{ __( '📰 Role Manager', i18n ) }

+

+ { __( 'Lock the Block Editor\'s inspector for different user roles, and give clients edit access to only images and content. Content Editing Mode affects all blocks. ', i18n ) } + + { __( 'Learn more', i18n ) } + +

+ { isPro + ? }> +
+ +
+
+ :

+ { __( 'This is only available in Stackable Premium.', i18n ) } + + { __( 'Go Premium', i18n ) } + +

+ } + +} + +const CustomFieldsEnableSettings = lazy( () => import( '../../pro__premium_only/src/welcome/custom-fields-toggle' ) ) +const CustomFieldsManagerSettings = lazy( () => import( '../../pro__premium_only/src/welcome/custom-fields-roles' ) ) + +const CustomFields = () => { + return <> +
+

{ __( '📋 Custom Fields', i18n ) }

+ }> +
+ +
+
+
+

+ { __( 'Create Custom Fields that you can reference across your entire site. You can assign which roles can manage your Custom Fields. ', i18n ) } + + { __( 'Learn more', i18n ) } + +

+ { isPro + ? }> +
+ +
+
+ :

+ { __( 'This is only available in Stackable Premium.', i18n ) } + + { __( 'Go Premium', i18n ) } + +

+ } + +} +const Integrations = ( { settings, handleSettingsChange } ) => { return <> +

{ __( 'Integrations', i18n ) }

{ - setSettings( { - ...settings, - stackable_google_maps_api_key: value, // eslint-disable-line camelcase - } ) - dispatch( 'stackable/settings' ).updateSettings( { stackable_google_maps_api_key: value } ) // eslint-disable-line camelcase + handleSettingsChange( { stackable_google_maps_api_key: value } ) // eslint-disable-line camelcase } } help={ <> @@ -704,11 +675,7 @@ const Integrations = () => { }, ] } onChange={ value => { - setSettings( { - ...settings, - stackable_icons_fa_free_version: value, // eslint-disable-line camelcase - } ) - dispatch( 'stackable/settings' ).updateSettings( { stackable_icons_fa_free_version: value } ) // eslint-disable-line camelcase + handleSettingsChange( { stackable_icons_fa_free_version: value } ) // eslint-disable-line camelcase } } />
@@ -748,6 +715,7 @@ const AdditionalOptions = props => { return (
+

{ __( '🔩 Other Settings', i18n ) }

{ props.showProNoticesOption && { + if ( document.querySelector( '.s-getting-started__body' ) ) { + createRoot( + document.querySelector( '.s-getting-started__body' ) + ).render( + + ) + } + // This is for the getting started block list. if ( document.querySelector( '.s-getting-started__block-list' ) ) { createRoot( @@ -856,69 +832,11 @@ domReady( () => { ) } - if ( document.querySelector( '.s-editor-settings' ) ) { - createRoot( - document.querySelector( '.s-editor-settings' ) - ).render( - - ) - } - - if ( document.querySelector( '.s-responsiveness' ) ) { - createRoot( - document.querySelector( '.s-responsiveness' ) - ).render( - - ) - } - - if ( document.querySelector( '.s-blocks' ) ) { + if ( document.querySelector( '.s-content' ) ) { createRoot( - document.querySelector( '.s-blocks' ) + document.querySelector( '.s-content' ) ).render( - - ) - } - - if ( document.querySelector( '.s-optimizations' ) ) { - createRoot( - document.querySelector( '.s-optimizations' ) - ).render( - - ) - } - - if ( document.querySelector( '.s-global-settings' ) ) { - createRoot( - document.querySelector( '.s-global-settings' ) - ).render( - - ) - } - - if ( document.querySelector( '.s-integrations' ) ) { - createRoot( - document.querySelector( '.s-integrations' ) - ).render( - - ) - } - - if ( document.querySelector( '.s-other-options-wrapper' ) ) { - createRoot( - document.querySelector( '.s-other-options-wrapper' ) - ).render( - - ) - } - - if ( document.querySelector( '.s-getting-started__body' ) ) { - createRoot( - document.querySelector( '.s-getting-started__body' ) - ).render( - + ) } } ) diff --git a/src/welcome/store.js b/src/welcome/store.js deleted file mode 100644 index 21081f48c..000000000 --- a/src/welcome/store.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * WordPress dependencies - */ -import { createReduxStore, register } from '@wordpress/data' -import { models } from '@wordpress/api' - -// Default state for the store -const DEFAULT_STATE = { - settings: {}, -} - -// Store actions -const STORE_ACTIONS = { - updateSettings: settings => { - return { - type: 'UPDATE_SETTINGS', - settings, - } - }, - saveSettings: () => { - return { - type: 'SAVE_SETTINGS', - } - }, -} - -// Store selectors -const STORE_SELECTORS = { - getSettings: state => state.settings, -} - -// Store reducer -const STORE_REDUCER = ( state = DEFAULT_STATE, action ) => { - switch ( action.type ) { - case 'UPDATE_SETTINGS': { - return { - ...state, - settings: { - ...state.settings, - ...action.settings, - }, - } - } - case 'SAVE_SETTINGS': { - const model = new models.Settings( state.settings ) // eslint-disable-line camelcase - model.save() - return state - } - default: - return state - } -} - -export const registerSettingsStore = () => { - register( - createReduxStore( 'stackable/settings', { - reducer: STORE_REDUCER, - actions: STORE_ACTIONS, - selectors: STORE_SELECTORS, - } ) - ) -} From 3e8b25db3f6b364d559f01e42a953ae9b1b5e1bb Mon Sep 17 00:00:00 2001 From: Alquen Antonio Sarmiento Date: Tue, 1 Oct 2024 19:17:23 +0800 Subject: [PATCH 11/32] feat: admin notice when settings are saved --- src/welcome/admin.js | 73 ++++++++++++++++++++++++++---------------- src/welcome/admin.scss | 18 ++++++++--- src/welcome/index.php | 8 +++-- 3 files changed, 63 insertions(+), 36 deletions(-) diff --git a/src/welcome/admin.js b/src/welcome/admin.js index e0938b4ed..ef70bdff3 100644 --- a/src/welcome/admin.js +++ b/src/welcome/admin.js @@ -11,7 +11,7 @@ import SVGSectionIcon from './images/settings-icon-section.svg' */ import { __ } from '@wordpress/i18n' import { - useEffect, useState, Fragment, useCallback, useRef, useMemo, lazy, Suspense, + useEffect, useState, useCallback, useRef, useMemo, lazy, Suspense, } from '@wordpress/element' import domReady from '@wordpress/dom-ready' import { Spinner, CheckboxControl } from '@wordpress/components' @@ -63,14 +63,14 @@ export const BLOCK_CATEROGIES = [ // Implement pick without using lodash, because themes and plugins might remove // lodash from the admin. -const pick = ( obj, keys ) => { - return keys.reduce( ( acc, key ) => { - if ( obj && obj.hasOwnProperty( key ) ) { - acc[ key ] = obj[ key ] - } - return acc - }, {} ) -} +// const pick = ( obj, keys ) => { +// return keys.reduce( ( acc, key ) => { +// if ( obj && obj.hasOwnProperty( key ) ) { +// acc[ key ] = obj[ key ] +// } +// return acc +// }, {} ) +// } const BlockList = () => { const DERIVED_BLOCKS = getAllBlocks() @@ -108,7 +108,7 @@ const BlockList = () => { } // Create an admin notice if there's an error fetching the settings. -const SettingsNotice = () => { +const RestSettingsNotice = () => { const [ error, setError ] = useState( null ) useEffect( () => { @@ -134,6 +134,23 @@ const SettingsNotice = () => { ) } +const SaveSettingsNotice = ( ) => { + const [ isDismissed, setIsDismissed ] = useState( false ) + + if ( isDismissed ) { + return null + } + + return ( +
+

{ __( 'Settings saved.', i18n ) }

+ +
+ ) +} + const Sidenav = ( { currentTab, onTabChange, @@ -211,7 +228,20 @@ const Settings = () => { }, [] ) const handleSettingsSave = useCallback( () => { - // TODO: Save settings + if ( Object.keys( unsavedChanges ).length === 0 ) { + return + } + const model = new models.Settings( unsavedChanges ) + model.save().then( () => { + if ( document.querySelector( '.s-save-settings-notice' ) ) { + createRoot( + document.querySelector( '.s-save-settings-notice' ) + ).render( + + ) + window.scrollTo( { top: 0, behavior: 'smooth' } ) + } + } ) setUnsavedChanges( {} ) }, [ unsavedChanges ] ) @@ -219,20 +249,7 @@ const Settings = () => { loadPromise.then( () => { const settings = new models.Settings() settings.fetch().then( response => { - setSettings( pick( response, [ - 'stackable_block_default_width', - 'stackable_block_wide_width', - 'stackable_enable_design_library', - 'stackable_enable_block_linking', - 'stackable_enable_text_highlight', - 'stackable_enable_dynamic_content', - 'stackable_enable_copy_paste_styles', - 'stackable_enable_reset_layout', - 'stackable_enable_save_as_default_block', - 'stackable_auto_collapse_panels', - 'stackable_dynamic_breakpoints', - 'stackable_disabled_blocks', - ] ) ) + setSettings( response ) } ) } ) }, [] ) @@ -824,11 +841,11 @@ domReady( () => { ) } - if ( document.querySelector( '.s-settings-notice' ) ) { + if ( document.querySelector( '.s-rest-settings-notice' ) ) { createRoot( - document.querySelector( '.s-settings-notice' ) + document.querySelector( '.s-rest-settings-notice' ) ).render( - + ) } diff --git a/src/welcome/admin.scss b/src/welcome/admin.scss index 47d3186a9..c38889887 100644 --- a/src/welcome/admin.scss +++ b/src/welcome/admin.scss @@ -475,6 +475,9 @@ body.toplevel_page_stk-custom-fields { display: flex; flex-direction: column; } + &.s-body-container-with-sidenav { + margin-left: 250px; + } p, li { line-height: 1.6; @@ -498,7 +501,7 @@ body.toplevel_page_stk-custom-fields { left: 0; position: absolute; background-color: #fff; - padding-top: 30px; + padding-top: 20px; display: flex; flex-direction: column; justify-content: flex-start; @@ -509,6 +512,10 @@ body.toplevel_page_stk-custom-fields { text-decoration: none; display: block; position: relative; + width: 100%; + text-align: left; + background: none; + border: none; transition: background-color 0.3s ease; cursor: pointer; &:hover { @@ -531,6 +538,7 @@ body.toplevel_page_stk-custom-fields { } &.s-sidenav-item-highlight { font-weight: bold; + text-decoration: underline; } } // This is a button, render this in the bottom of the side bar @@ -548,7 +556,7 @@ body.toplevel_page_stk-custom-fields { cursor: pointer; box-sizing: border-box !important; display: inline-block; - margin: 120px 20px 0; + margin: 20px 20px 0; &:hover { opacity: 0.85; box-shadow: none !important; @@ -560,9 +568,9 @@ body.toplevel_page_stk-custom-fields { height: 100%; top: 32px; left: 160px; - } - .s-body-container-with-sidenav { - margin-left: 250px; + .s-save-changes { + margin: 120px 20px 0; + } } .s-side { h2, diff --git a/src/welcome/index.php b/src/welcome/index.php index 35da44de1..923f35426 100644 --- a/src/welcome/index.php +++ b/src/welcome/index.php @@ -181,13 +181,15 @@ public function stackable_settings_content() { print_tabs() ?>

+
+
+
+
-
-
-
+
From 8e76a0a75c5cdf2114a0fee3ff5aea9bf73270d4 Mon Sep 17 00:00:00 2001 From: Alquen Antonio Sarmiento Date: Tue, 1 Oct 2024 20:48:48 +0800 Subject: [PATCH 12/32] feat: integrate pro-related settings --- src/welcome/admin.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/welcome/admin.js b/src/welcome/admin.js index ef70bdff3..9ab20a5ef 100644 --- a/src/welcome/admin.js +++ b/src/welcome/admin.js @@ -228,6 +228,7 @@ const Settings = () => { }, [] ) const handleSettingsSave = useCallback( () => { + console.log( settings ) // eslint-disable-line no-console if ( Object.keys( unsavedChanges ).length === 0 ) { return } @@ -243,7 +244,7 @@ const Settings = () => { } } ) setUnsavedChanges( {} ) - }, [ unsavedChanges ] ) + }, [ unsavedChanges, settings ] ) useEffect( () => { loadPromise.then( () => { @@ -262,8 +263,8 @@ const Settings = () => { { currentTab === 'blocks' && } { currentTab === 'optimizations' && } { currentTab === 'global-settings' && } - { currentTab === 'role-manager' && } - { currentTab === 'custom-fields-settings' && } + { currentTab === 'role-manager' && } + { currentTab === 'custom-fields-settings' && } { currentTab === 'integrations' && } { currentTab === 'other-settings' && } @@ -580,7 +581,7 @@ const GlobalSettings = ( { settings, handleSettingsChange } ) => { const EditorModeSettings = lazy( () => import( '../../pro__premium_only/src/welcome/editor-mode' ) ) -const RoleManager = () => { +const RoleManager = ( { settings, handleSettingsChange } ) => { return <>

{ __( '📰 Role Manager', i18n ) }

@@ -598,7 +599,7 @@ const RoleManager = () => { { isPro ? }>

- +
:

@@ -614,13 +615,13 @@ const RoleManager = () => { const CustomFieldsEnableSettings = lazy( () => import( '../../pro__premium_only/src/welcome/custom-fields-toggle' ) ) const CustomFieldsManagerSettings = lazy( () => import( '../../pro__premium_only/src/welcome/custom-fields-roles' ) ) -const CustomFields = () => { +const CustomFields = ( { settings, handleSettingsChange } ) => { return <>

{ __( '📋 Custom Fields', i18n ) }

}>
- +
@@ -633,7 +634,7 @@ const CustomFields = () => { { isPro ? }>
- +
:

From 233e05f5995f723665750db97e3afdcf4502b815 Mon Sep 17 00:00:00 2001 From: Alquen Antonio Sarmiento Date: Thu, 3 Oct 2024 10:00:13 +0800 Subject: [PATCH 13/32] fix: conditional lazy loading of pro settings --- src/welcome/admin.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/welcome/admin.js b/src/welcome/admin.js index 9ab20a5ef..e532746d6 100644 --- a/src/welcome/admin.js +++ b/src/welcome/admin.js @@ -567,6 +567,7 @@ const Optimizations = ( { settings, handleSettingsChange } ) => { const GlobalSettings = ( { settings, handleSettingsChange } ) => { return <> +

{ __( 'Global Settings', i18n ) }

{
:

- { __( 'This is only available in Stackable Premium.', i18n ) } + { __( 'This is only available in Stackable Premium. ', i18n ) } { __( 'Go Premium', i18n ) } @@ -619,11 +620,13 @@ const CustomFields = ( { settings, handleSettingsChange } ) => { return <>

{ __( '📋 Custom Fields', i18n ) }

- }> -
- -
-
+ { isPro && + }> +
+ +
+
+ }

{ __( 'Create Custom Fields that you can reference across your entire site. You can assign which roles can manage your Custom Fields. ', i18n ) } @@ -638,7 +641,7 @@ const CustomFields = ( { settings, handleSettingsChange } ) => {

:

- { __( 'This is only available in Stackable Premium.', i18n ) } + { __( 'This is only available in Stackable Premium. ', i18n ) } { __( 'Go Premium', i18n ) } From c8b74a7951cd7c1abe1ac07d568c56e441bc4bc9 Mon Sep 17 00:00:00 2001 From: Alquen Antonio Sarmiento Date: Sat, 5 Oct 2024 16:41:32 +0800 Subject: [PATCH 14/32] feat: integrate missing settings such as icon, pro notice, tooltip --- src/welcome/admin.js | 541 +++++++++++++++++++++---------------------- 1 file changed, 268 insertions(+), 273 deletions(-) diff --git a/src/welcome/admin.js b/src/welcome/admin.js index e532746d6..ec58c5f3d 100644 --- a/src/welcome/admin.js +++ b/src/welcome/admin.js @@ -228,7 +228,7 @@ const Settings = () => { }, [] ) const handleSettingsSave = useCallback( () => { - console.log( settings ) // eslint-disable-line no-console + console.log( unsavedChanges ) // eslint-disable-line no-console if ( Object.keys( unsavedChanges ).length === 0 ) { return } @@ -266,153 +266,165 @@ const Settings = () => { { currentTab === 'role-manager' && } { currentTab === 'custom-fields-settings' && } { currentTab === 'integrations' && } - { currentTab === 'other-settings' && } + { currentTab === 'other-settings' && } } const EditorSettings = ( { settings, handleSettingsChange } ) => { - return <> -

{ __( 'Blocks', i18n ) }

-

{ __( 'You can customize the default width of your blocks here.', i18n ) }

- { - handleSettingsChange( { stackable_block_default_width: value } ) // eslint-disable-line camelcase - } } - help={ __( 'The width used when a Columns block has its Content Width set to center. This is automatically detected from your theme. You can adjust it if your blocks are not aligned correctly. In px, you can also use other units or use a calc() formula.', i18n ) } - /> - { - handleSettingsChange( { stackable_block_wide_width: value } ) // eslint-disable-line camelcase - } } - help={ __( 'The width used when a Columns block has its Content Width set to wide. This is automatically detected from your theme. You can adjust it if your blocks are not aligned correctly. In px, you can also use other units or use a calc() formula.', i18n ) } - /> + return ( +
+

{ __( 'Blocks', i18n ) }

+

{ __( 'You can customize the default width of your blocks here.', i18n ) }

+ { + handleSettingsChange( { stackable_block_default_width: value } ) // eslint-disable-line camelcase + } } + help={ __( 'The width used when a Columns block has its Content Width set to center. This is automatically detected from your theme. You can adjust it if your blocks are not aligned correctly. In px, you can also use other units or use a calc() formula.', i18n ) } + /> + { + handleSettingsChange( { stackable_block_wide_width: value } ) // eslint-disable-line camelcase + } } + help={ __( 'The width used when a Columns block has its Content Width set to wide. This is automatically detected from your theme. You can adjust it if your blocks are not aligned correctly. In px, you can also use other units or use a calc() formula.', i18n ) } + /> -

{ __( 'Editor', i18n ) }

-

{ __( 'You can customize some of the features and behavior of Stackable in the editor here.' ) }

- { - handleSettingsChange( { stackable_enable_design_library: value } ) // eslint-disable-line camelcase - } } - help={ __( 'Adds a button on the top of the editor which gives access to a collection of pre-made block designs.', i18n ) } - /> - { - handleSettingsChange( { stackable_enable_block_linking: value } ) // eslint-disable-line camelcase - } } - help={ - <> - { __( 'Gives you the ability to link columns. Any changes you make on one column will automatically get applied on the other columns.', i18n ) } -   - { __( 'Learn more', i18n ) } - - } - /> +

{ __( 'Editor', i18n ) }

+

{ __( 'You can customize some of the features and behavior of Stackable in the editor here.' ) }

+ { + handleSettingsChange( { stackable_enable_design_library: value } ) // eslint-disable-line camelcase + } } + help={ __( 'Adds a button on the top of the editor which gives access to a collection of pre-made block designs.', i18n ) } + /> + { + handleSettingsChange( { stackable_enable_block_linking: value } ) // eslint-disable-line camelcase + } } + help={ + <> + { __( 'Gives you the ability to link columns. Any changes you make on one column will automatically get applied on the other columns.', i18n ) } +   + { __( 'Learn more', i18n ) } + + } + /> -

{ __( 'Toolbar', i18n ) }

-

{ __( 'You can disable some toolbar features here.', i18n ) }

- { - handleSettingsChange( { stackable_enable_text_highlight: value } ) // eslint-disable-line camelcase - } } - help={ __( 'Adds a toolbar button for highlighting text', i18n ) } - /> - { - handleSettingsChange( { stackable_enable_dynamic_content: value } ) // eslint-disable-line camelcase - } } - help={ __( 'Adds a toolbar button for inserting dynamic content', i18n ) } - /> - { - handleSettingsChange( { stackable_enable_copy_paste_styles: value } ) // eslint-disable-line camelcase - } } - help={ __( 'Adds a toolbar button for copying and pasting block styles', i18n ) } - /> - { - handleSettingsChange( { stackable_enable_reset_layout: value } ) // eslint-disable-line camelcase - } } - help={ __( 'Adds a toolbar button for resetting the layout of a stackble block back to the original', i18n ) } - /> - { - handleSettingsChange( { stackable_enable_save_as_default_block: value } ) // eslint-disable-line - } } - help={ __( 'Adds a toolbar button for saving a block as the default block', i18n ) } - /> +

{ __( 'Toolbar', i18n ) }

+

{ __( 'You can disable some toolbar features here.', i18n ) }

+ { + handleSettingsChange( { stackable_enable_text_highlight: value } ) // eslint-disable-line camelcase + } } + help={ __( 'Adds a toolbar button for highlighting text', i18n ) } + /> + { + handleSettingsChange( { stackable_enable_dynamic_content: value } ) // eslint-disable-line camelcase + } } + help={ __( 'Adds a toolbar button for inserting dynamic content', i18n ) } + /> + { + handleSettingsChange( { stackable_enable_copy_paste_styles: value } ) // eslint-disable-line camelcase + } } + help={ __( 'Adds a toolbar button for copying and pasting block styles', i18n ) } + /> + { + handleSettingsChange( { stackable_enable_reset_layout: value } ) // eslint-disable-line camelcase + } } + help={ __( 'Adds a toolbar button for resetting the layout of a stackble block back to the original', i18n ) } + /> + { + handleSettingsChange( { stackable_enable_save_as_default_block: value } ) // eslint-disable-line + } } + help={ __( 'Adds a toolbar button for saving a block as the default block', i18n ) } + /> -

{ __( 'Inspector', i18n ) }

-

{ __( 'You can customize some of the features and behavior of Stackable in the inspector here.' ) }

- { - handleSettingsChange( { stackable_auto_collapse_panels: value } ) // eslint-disable-line camelcase - } } - help={ __( 'Collapse other inspector panels when opening another, keeping only one open at a time.', i18n ) } - /> - +

{ __( 'Inspector', i18n ) }

+

{ __( 'You can customize some of the features and behavior of Stackable in the inspector here.' ) }

+ { + handleSettingsChange( { stackable_help_tooltip_disabled: value ? '1' : '' } ) // eslint-disable-line camelcase + } } + help={ __( 'Disables the help video tooltips that appear in the inspector.', i18n ) } + /> + { + handleSettingsChange( { stackable_auto_collapse_panels: value } ) // eslint-disable-line camelcase + } } + help={ __( 'Collapse other inspector panels when opening another, keeping only one open at a time.', i18n ) } + /> +
+ ) } const Responsiveness = ( { settings, handleSettingsChange } ) => { - return <> -

{ __( 'Dynamic Breakpoints', i18n ) }

-

- { __( 'Blocks can be styles differently for tablet and mobile screens, and some styles adjust to make them fit better in smaller screens. You can change the widths when tablet and mobile views are triggered. ', i18n ) } - - { __( 'Learn more', i18n ) } - -

- { - handleSettingsChange( { - stackable_dynamic_breakpoints: { // eslint-disable-line camelcase - tablet: value, - mobile: settings.stackable_dynamic_breakpoints.mobile || '', // eslint-disable-line camelcase - }, - } ) - } } - placeholder="1024" - > px - { - handleSettingsChange( { - stackable_dynamic_breakpoints: { // eslint-disable-line camelcase - tablet: settings.stackable_dynamic_breakpoints.tablet || '', // eslint-disable-line camelcase - mobile: value, - }, - } ) - } } - placeholder="768" - > px - + return ( +
+

{ __( 'Dynamic Breakpoints', i18n ) }

+

+ { __( 'Blocks can be styles differently for tablet and mobile screens, and some styles adjust to make them fit better in smaller screens. You can change the widths when tablet and mobile views are triggered. ', i18n ) } + + { __( 'Learn more', i18n ) } + +

+ { + handleSettingsChange( { + stackable_dynamic_breakpoints: { // eslint-disable-line camelcase + tablet: value, + mobile: settings.stackable_dynamic_breakpoints.mobile || '', // eslint-disable-line camelcase + }, + } ) + } } + placeholder="1024" + > px + { + handleSettingsChange( { + stackable_dynamic_breakpoints: { // eslint-disable-line camelcase + tablet: settings.stackable_dynamic_breakpoints.tablet || '', // eslint-disable-line camelcase + mobile: value, + }, + } ) + } } + placeholder="768" + > px +
+ ) } // Toggle the block states between enabled, disabled and hidden. @@ -464,7 +476,7 @@ const Blocks = ( { settings, handleSettingsChange } ) => { handleSettingsChange( { stackable_disabled_blocks: newDisabledBlocks } ) // eslint-disable-line camelcase } return ( - <> +

{ __( 'Blocks', i18n ) }

{ __( 'You can enable, hide and disable Stackable blocks. Hiding the blocks hides them from the editor. Disabling the blocks prevent them from being loaded for faster performance.', i18n ) }

{ BLOCK_CATEROGIES.map( ( { @@ -539,30 +551,32 @@ const Blocks = ( { settings, handleSettingsChange } ) => {
) } ) } - +
) } const Optimizations = ( { settings, handleSettingsChange } ) => { - return <> -

{ __( 'Optimizations', i18n ) }

- { - handleSettingsChange( { stackable_optimize_inline_css: value } ) // eslint-disable-line camelcase - } } - help={ __( 'Optimize inlined CSS styles. If this is enabled, similar selectors will be combined together, helpful if you changed Block Defaults.', i18n ) } - /> - { - handleSettingsChange( { stackable_enable_carousel_lazy_loading: value } ) // eslint-disable-line camelcase - } } - help={ __( 'Disable this if you encounter layout or spacing issues when using images inside carousel-type blocks because of image lazy loading.', i18n ) } - /> - + return ( +
+

{ __( 'Optimizations', i18n ) }

+ { + handleSettingsChange( { stackable_optimize_inline_css: value } ) // eslint-disable-line camelcase + } } + help={ __( 'Optimize inlined CSS styles. If this is enabled, similar selectors will be combined together, helpful if you changed Block Defaults.', i18n ) } + /> + { + handleSettingsChange( { stackable_enable_carousel_lazy_loading: value } ) // eslint-disable-line camelcase + } } + help={ __( 'Disable this if you encounter layout or spacing issues when using images inside carousel-type blocks because of image lazy loading.', i18n ) } + /> +
+ ) } const GlobalSettings = ( { settings, handleSettingsChange } ) => { @@ -650,118 +664,107 @@ const CustomFields = ( { settings, handleSettingsChange } ) => { } +const IconSettings = lazy( () => import( '../../pro__premium_only/src/welcome/icons.js' ) ) + const Integrations = ( { settings, handleSettingsChange } ) => { - return <> -

{ __( 'Integrations', i18n ) }

- { - handleSettingsChange( { stackable_google_maps_api_key: value } ) // eslint-disable-line camelcase - } } - help={ - <> - { __( - 'Adding a Google API Key enables additional features of the Stackable Map Block.', - i18n - ) } -   - { __( 'Learn more', i18n ) } - - } - /> -
- -
-
- +

{ __( 'Integrations', i18n ) }

+ { - handleSettingsChange( { stackable_icons_fa_free_version: value } ) // eslint-disable-line camelcase + handleSettingsChange( { stackable_google_maps_api_key: value } ) // eslint-disable-line camelcase } } + help={ + <> + { __( + 'Adding a Google API Key enables additional features of the Stackable Map Block.', + i18n + ) } +   + { __( 'Learn more', i18n ) } + + } /> + { isPro + ? }> +
+ +
+
+ : <> +
+ +
+
+

+ { __( 'FontAwesome Pro Kit', i18n ) } + { __( 'This is only available in Stackable Premium. ', i18n ) } + + { __( 'Go Premium', i18n ) } + +

+
+ + } +
+
+ +
+
+ { + handleSettingsChange( { stackable_icons_fa_free_version: value } ) // eslint-disable-line camelcase + } } + /> +
+
- + ) } -const AdditionalOptions = props => { - const [ helpTooltipsDisabled, setHelpTooltipsDisabled ] = useState( false ) - const [ generateNativeGlobalColors, setGenerateNativeGlobalColors ] = useState( false ) - const [ v2EditorBackwardCompatibility, setV2EditorBackwardCompatibility ] = useState( false ) - const [ v2EditorBackwardCompatibilityUsage, setV2EditorBackwardCompatibilityUsage ] = useState( false ) - const [ v2FrontendBackwardCompatibility, setV2FrontendBackwardCompatibility ] = useState( false ) - const [ showPremiumNotices, setShowPremiumNotices ] = useState( false ) - const [ isBusy, setIsBusy ] = useState( false ) - - useEffect( () => { - setIsBusy( true ) - loadPromise.then( () => { - const settings = new models.Settings() - settings.fetch().then( response => { - setHelpTooltipsDisabled( response.stackable_help_tooltip_disabled === '1' ) - setGenerateNativeGlobalColors( !! response.stackable_global_colors_native_compatibility ) - setV2EditorBackwardCompatibility( response.stackable_v2_editor_compatibility === '1' ) - setV2EditorBackwardCompatibilityUsage( response.stackable_v2_editor_compatibility_usage === '1' ) - setV2FrontendBackwardCompatibility( response.stackable_v2_frontend_compatibility === '1' ) - setShowPremiumNotices( response.stackable_show_pro_notices === '1' ) - setIsBusy( false ) - } ) - } ) - }, [] ) - - const updateSetting = settings => { - setIsBusy( true ) - const model = new models.Settings( settings ) - model.save().then( () => setIsBusy( false ) ) - } - +const AdditionalOptions = ( { settings, handleSettingsChange } ) => { return ( -
-

{ __( '🔩 Other Settings', i18n ) }

- { props.showProNoticesOption && +
+

{ __( '🔩 Miscellaneous', i18n ) }

+ { showProNoticesOption && { - updateSetting( { stackable_show_pro_notices: checked ? '1' : '' } ) // eslint-disable-line camelcase - setShowPremiumNotices( checked ) + handleSettingsChange( { stackable_show_pro_notices: checked ? '1' : '' } ) // eslint-disable-line camelcase } } /> } - { - updateSetting( { stackable_help_tooltip_disabled: checked ? '1' : '' } ) // eslint-disable-line camelcase - setHelpTooltipsDisabled( checked ) - } } - /> { - updateSetting( { stackable_global_colors_native_compatibility: checked } ) // eslint-disable-line camelcase - setGenerateNativeGlobalColors( checked ) + handleSettingsChange( { stackable_global_colors_native_compatibility: checked } ) // eslint-disable-line camelcase } } />

{ __( '🏠 Migration Settings', i18n ) }

@@ -772,44 +775,36 @@ const AdditionalOptions = props => {

{ - const settings = { stackable_v2_editor_compatibility: checked ? '1' : '' } // eslint-disable-line camelcase if ( checked ) { - settings.stackable_v2_editor_compatibility_usage = '' // eslint-disable-line camelcase - setV2EditorBackwardCompatibilityUsage( false ) + handleSettingsChange( { stackable_v2_editor_compatibility_usage: '' } ) // eslint-disable-line camelcase } - updateSetting( settings ) - setV2EditorBackwardCompatibility( checked ) + handleSettingsChange( { stackable_v2_editor_compatibility: checked ? '1' : '' } ) // eslint-disable-line camelcase } } /> { - const settings = { stackable_v2_editor_compatibility_usage: checked ? '1' : '' } // eslint-disable-line camelcase if ( checked ) { - settings.stackable_v2_editor_compatibility = '' // eslint-disable-line camelcase - setV2EditorBackwardCompatibility( false ) + handleSettingsChange( { stackable_v2_editor_compatibility: '' } ) // eslint-disable-line camelcase } - updateSetting( settings ) - setV2EditorBackwardCompatibilityUsage( checked ) + handleSettingsChange( { stackable_v2_editor_compatibility_usage: checked ? '1' : '' } ) // eslint-disable-line camelcase } } /> { - updateSetting( { stackable_v2_frontend_compatibility: checked ? '1' : '' } ) // eslint-disable-line camelcase - setV2FrontendBackwardCompatibility( checked ) + handleSettingsChange( { stackable_v2_frontend_compatibility: checked ? '1' : '' } ) // eslint-disable-line camelcase } } /> - { isBusy && -
- -
- }
) } From c3adc2376426e1eb831ce39b26e409e83d43b7e8 Mon Sep 17 00:00:00 2001 From: Alquen Antonio Sarmiento Date: Sat, 5 Oct 2024 16:56:30 +0800 Subject: [PATCH 15/32] feat: add stackable global settings --- src/editor-settings.php | 13 +++++++++++++ src/welcome/admin.js | 8 ++++++++ 2 files changed, 21 insertions(+) diff --git a/src/editor-settings.php b/src/editor-settings.php index 128a5ce6e..f59656328 100644 --- a/src/editor-settings.php +++ b/src/editor-settings.php @@ -123,6 +123,18 @@ public function register_settings() { ) ); + register_setting( + 'stackable_editor_settings', + 'stackable_enable_global_settings', + array( + 'type' => 'boolean', + 'description' => __( 'Allow the configuration of global settings such as color palette, typography, and block defaults', STACKABLE_I18N ), + 'sanitize_callback' => 'sanitize_text_field', + 'show_in_rest' => true, + 'default' => true, + ) + ); + register_setting( 'stackable_editor_settings', 'stackable_enable_block_linking', @@ -235,6 +247,7 @@ public function add_settings( $settings ) { $settings['stackable_enable_design_library'] = get_option( 'stackable_enable_design_library' ); $settings['stackable_optimize_inline_css'] = get_option( 'stackable_optimize_inline_css' ); $settings['stackable_auto_collapse_panels'] = get_option( 'stackable_auto_collapse_panels' ); + $settings['stackable_enable_global_settings'] = get_option( 'stackable_enable_global_settings' ); $settings['stackable_enable_block_linking'] = get_option( 'stackable_enable_block_linking' ); $settings['stackable_enable_carousel_lazy_loading'] = get_option( 'stackable_enable_carousel_lazy_loading' ); $settings['stackable_enable_text_highlight'] = get_option( 'stackable_enable_text_highlight' ); diff --git a/src/welcome/admin.js b/src/welcome/admin.js index ec58c5f3d..0e0eb1296 100644 --- a/src/welcome/admin.js +++ b/src/welcome/admin.js @@ -305,6 +305,14 @@ const EditorSettings = ( { settings, handleSettingsChange } ) => { } } help={ __( 'Adds a button on the top of the editor which gives access to a collection of pre-made block designs.', i18n ) } /> + { + handleSettingsChange( { stackable_enable_global_settings: value } ) // eslint-disable-line camelcase + } } + help={ __( 'Adds a button on the top of the editor which gives access to Stackable settings.', i18n ) } + /> Date: Sat, 5 Oct 2024 17:13:28 +0800 Subject: [PATCH 16/32] fix: indentation from merge conflict --- src/welcome/admin.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/welcome/admin.js b/src/welcome/admin.js index ccd1817b2..8eb828a38 100644 --- a/src/welcome/admin.js +++ b/src/welcome/admin.js @@ -294,14 +294,14 @@ const EditorSettings = ( { settings, handleSettingsChange } ) => { } } help={ __( 'The width used when a Columns block has its Content Width set to wide. This is automatically detected from your theme. You can adjust it if your blocks are not aligned correctly. In px, you can also use other units or use a calc() formula.', i18n ) } /> - { + { handleSettingsChange( { stackable_enable_text_default_block: value } ) // eslint-disable-line camelcase } } - help={ __( 'If enabled, Stackable Text blocks will be added by default instead of the native Paragraph Block.', i18n ) } - /> + help={ __( 'If enabled, Stackable Text blocks will be added by default instead of the native Paragraph Block.', i18n ) } + />

{ __( 'Editor', i18n ) }

{ __( 'You can customize some of the features and behavior of Stackable in the editor here.' ) }

From 14c6d7fcd3e8ba8f09fddeb5e0ae1b0c58e1c6cf Mon Sep 17 00:00:00 2001 From: Alquen Antonio Sarmiento Date: Sun, 6 Oct 2024 15:49:08 +0800 Subject: [PATCH 17/32] feat: apply global settings by conditional registering --- src/editor-settings.php | 2 ++ src/plugins/global-settings/index.js | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/editor-settings.php b/src/editor-settings.php index 389516da0..fd89194af 100644 --- a/src/editor-settings.php +++ b/src/editor-settings.php @@ -227,6 +227,8 @@ public function register_settings() { 'sanitize_callback' => 'sanitize_text_field', 'show_in_rest' => true, 'default' => true, + ) + ); register_setting( 'stackable_editor_settings', diff --git a/src/plugins/global-settings/index.js b/src/plugins/global-settings/index.js index 733d335f7..015ee9e6f 100644 --- a/src/plugins/global-settings/index.js +++ b/src/plugins/global-settings/index.js @@ -8,7 +8,11 @@ import './block-defaults' * External dependencies */ import { SVGStackableIcon } from '~stackable/icons' -import { i18n, isContentOnlyMode } from 'stackable' +import { + i18n, + isContentOnlyMode, + settings, +} from 'stackable' /** WordPress dependencies */ @@ -59,7 +63,7 @@ const GlobalSettings = () => { ) } -if ( ! isContentOnlyMode ) { +if ( ! isContentOnlyMode && settings.stackable_enable_global_settings ) { registerPlugin( 'stackable-global-settings', { render: GlobalSettings, } ) From 7f9bf2ff1e7b2342534e1e67999455c1bae26e59 Mon Sep 17 00:00:00 2001 From: Alquen Antonio Sarmiento Date: Mon, 14 Oct 2024 09:00:52 +0800 Subject: [PATCH 18/32] feat: search functionality integration --- src/welcome/admin.js | 268 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 225 insertions(+), 43 deletions(-) diff --git a/src/welcome/admin.js b/src/welcome/admin.js index 8eb828a38..f306aa146 100644 --- a/src/welcome/admin.js +++ b/src/welcome/admin.js @@ -134,7 +134,7 @@ const RestSettingsNotice = () => { ) } -const SaveSettingsNotice = ( ) => { +const SaveSettingsNotice = () => { const [ isDismissed, setIsDismissed ] = useState( false ) if ( isDismissed ) { @@ -151,21 +151,105 @@ const SaveSettingsNotice = ( ) => { ) } +// TODO: Proper tab nesting +// Implement other highlight without admin base const Sidenav = ( { currentTab, - onTabChange, + handleTabChange, handleSettingsSave, + currentSearch, } ) => { const tabList = useMemo( () => [ - { id: 'editor-settings', label: __( 'Editor Settings', i18n ) }, - { id: 'responsiveness', label: __( 'Responsiveness', i18n ) }, - { id: 'blocks', label: __( 'Blocks', i18n ) }, - { id: 'optimizations', label: __( 'Optimization', i18n ) }, - { id: 'global-settings', label: __( 'Global Settings', i18n ) }, - { id: 'role-manager', label: __( 'Role Manager', i18n ) }, - { id: 'custom-fields-settings', label: __( 'Custom Fields', i18n ) }, - { id: 'integrations', label: __( 'Integration', i18n ) }, - { id: 'other-settings', label: __( 'Miscellaneous ', i18n ) }, + { + id: 'editor-settings', + label: __( 'Editor Settings', i18n ), + settings: [ + 'Nested Block Width', + 'Nested Wide Block Width', + 'Design Library', + 'Block Linking (Beta)', + 'Toolbar Text Highlight', + 'Toolbar Dynamic Content', + 'Copy & Paste Styles', + 'Reset Layout', + 'Save as Default Block', + 'Dont show help video tooltips', + 'Auto-Collapse Panels', + ], + }, + { + id: 'responsiveness', + label: __( 'Responsiveness', i18n ), + settings: [ + 'Tablet Breakpoint', + 'Mobile Breakpoint', + ], + }, + { + id: 'blocks', + label: __( 'Blocks', i18n ), + settings: [], + }, + { + id: 'optimizations', + label: __( 'Optimization', i18n ), + settings: [ + 'Optimize Inline CSS', + 'Lazy Load Images within Carousels', + ], + }, + { + id: 'global-settings', + label: __( 'Global Settings', i18n ), + settings: [ + 'Force Typography Styles', + ], + }, + { + id: 'role-manager', + label: __( 'Role Manager', i18n ), + settings: [ + 'Role Manager', + 'Administrator', + 'Editor', + 'Author', + 'Contributor', + 'Subscriber', + ], + }, + { + id: 'custom-fields-settings', + label: __( 'Custom Fields', i18n ), + settings: [ + 'Custom Fields', + 'Administrator', + 'Editor', + 'Author', + 'Contributor', + 'Subscriber', + ], + }, + { + id: 'integrations', + label: __( 'Integration', i18n ), + settings: [ + 'Google Maps API Key', + 'FontAwesome Pro Kit', + 'FontAwesome Icon Library Version', + ], + }, + { + id: 'other-settings', + label: __( 'Miscellaneous ', i18n ), + settings: [ + 'Migration', + 'Show Go premium notices', + 'Generate Global Colors for native blocks', + 'Load version 2 blocks in the editor', + 'Load version 2 blocks in the editor only when the page was using version 2 blocks', + 'Load version 2 frontend block stylesheet and scripts for backward compatibility', + ], + }, ], [] ) const sidenavRef = useRef( null ) @@ -191,12 +275,23 @@ const Sidenav = ( { <> @@ -436,6 +419,7 @@ const Settings = () => { const [ unsavedChanges, setUnsavedChanges ] = useState( {} ) const [ currentTab, setCurrentTab ] = useState( 'editor-settings' ) const [ currentSearch, setCurrentSearch ] = useState( '' ) + const [ isSaving, setIsSaving ] = useState( false ) const handleSettingsChange = useCallback( newSettings => { setSettings( prev => ( { ...prev, ...newSettings } ) ) @@ -443,20 +427,16 @@ const Settings = () => { }, [] ) const handleSettingsSave = useCallback( () => { - console.log( unsavedChanges ) // eslint-disable-line no-console if ( Object.keys( unsavedChanges ).length === 0 ) { return } + setIsSaving( true ) const model = new models.Settings( unsavedChanges ) model.save().then( () => { - if ( document.querySelector( '.s-save-settings-notice' ) ) { - createRoot( - document.querySelector( '.s-save-settings-notice' ) - ).render( - - ) - window.scrollTo( { top: 0, behavior: 'smooth' } ) - } + // Add a little more time for the spinner for better feedback + setTimeout( () => { + setIsSaving( false ) + }, 500 ) } ) setUnsavedChanges( {} ) }, [ unsavedChanges, settings ] ) @@ -482,6 +462,7 @@ const Settings = () => { handleTabChange={ setCurrentTab } handleSettingsSave={ handleSettingsSave } currentSearch={ currentSearch } + isSaving={ isSaving } />
diff --git a/src/welcome/admin.scss b/src/welcome/admin.scss index 0577900f2..3995354c9 100644 --- a/src/welcome/admin.scss +++ b/src/welcome/admin.scss @@ -398,6 +398,7 @@ body.toplevel_page_stk-custom-fields { margin-bottom: 30px; transition: all 0.3s ease; position: relative; + min-height: 70vh; &.s-box-spaced { padding-left: 4vw; padding-right: 4vw; @@ -873,11 +874,15 @@ body.toplevel_page_stk-custom-fields { grid-template-columns: 1fr 1fr 1fr; } -// Collapse to a single column for mobile. @media screen and (max-width: 960px) { + // Collapse to a single column for mobile. .s-body-container { grid-template-columns: 1fr !important; } + // Matched the width of wordpress admin menu + .s-sidenav-fixed { + left: 36px !important; + } } // Save spinner for the additional options.