From 0401825b1af1ca76ab59f8f5342efb29be5082c2 Mon Sep 17 00:00:00 2001 From: mxkae Date: Tue, 17 Sep 2024 22:00:47 +0800 Subject: [PATCH 1/9] add icon library in global settings --- src/plugins/global-settings/icon-library.js | 33 +++++++++++++++++++++ src/plugins/global-settings/index.js | 1 + 2 files changed, 34 insertions(+) create mode 100644 src/plugins/global-settings/icon-library.js diff --git a/src/plugins/global-settings/icon-library.js b/src/plugins/global-settings/icon-library.js new file mode 100644 index 000000000..251a775cf --- /dev/null +++ b/src/plugins/global-settings/icon-library.js @@ -0,0 +1,33 @@ +/** + * External dependencies + */ +import { i18n, isPro } from 'stackable' +import { + PanelAdvancedSettings, + ProControl, +} from '~stackable/components' + +import { addFilter, applyFilters } from '@wordpress/hooks' +import { Fragment } from '@wordpress/element' +import { __ } from '@wordpress/i18n' + +addFilter( 'stackable.global-settings.inspector', 'stackable/icon-library', output => { + return ( + + { output } + + + { ! isPro && } + { isPro && + applyFilters( 'stackable.global-settings.inspector.icon-library.control', null ) + } + + + + ) +} ) + diff --git a/src/plugins/global-settings/index.js b/src/plugins/global-settings/index.js index 733d335f7..2f04f7a3b 100644 --- a/src/plugins/global-settings/index.js +++ b/src/plugins/global-settings/index.js @@ -3,6 +3,7 @@ */ import './editor-loader' import './block-defaults' +import './icon-library' /** * External dependencies From 1097e0454c0f8fe2bbfc11063115758ae1d5c249 Mon Sep 17 00:00:00 2001 From: mxkae Date: Wed, 18 Sep 2024 13:18:02 +0800 Subject: [PATCH 2/9] change ProControl type --- src/components/pro-control/index.js | 5 +++++ src/plugins/global-settings/icon-library.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/pro-control/index.js b/src/components/pro-control/index.js index 79f29f52b..d95427d37 100644 --- a/src/components/pro-control/index.js +++ b/src/components/pro-control/index.js @@ -109,6 +109,11 @@ const LABELS = {
  • { __( 'Hide the current post - great for synced patterns', i18n ) }
  • , }, + 'icon-library': { + description: , + }, } const ProControl = props => { diff --git a/src/plugins/global-settings/icon-library.js b/src/plugins/global-settings/icon-library.js index 251a775cf..509621646 100644 --- a/src/plugins/global-settings/icon-library.js +++ b/src/plugins/global-settings/icon-library.js @@ -21,7 +21,7 @@ addFilter( 'stackable.global-settings.inspector', 'stackable/icon-library', outp id="icon-library-settings" isPremiumPanel={ ! isPro } > - { ! isPro && } + { ! isPro && } { isPro && applyFilters( 'stackable.global-settings.inspector.icon-library.control', null ) } From 0dfe241b48d1506e56ad19717b71e53860443415 Mon Sep 17 00:00:00 2001 From: mxkae Date: Wed, 18 Sep 2024 21:48:25 +0800 Subject: [PATCH 3/9] create reusable component --- src/components/index.js | 1 + src/components/sortable-picker/index.js | 225 ++++++++++++++++++ .../global-settings/colors/color-picker.js | 191 ++------------- 3 files changed, 241 insertions(+), 176 deletions(-) create mode 100644 src/components/sortable-picker/index.js diff --git a/src/components/index.js b/src/components/index.js index 6207dbbb6..f72f57316 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -112,3 +112,4 @@ export { default as ColumnsWidthMultiControl } from './columns-width-multi-contr export { default as Popover } from './popover' export { default as HelpTooltip } from './help-tooltip' export { default as RichText } from './rich-text' +export { default as SortablePicker } from './sortable-picker' diff --git a/src/components/sortable-picker/index.js b/src/components/sortable-picker/index.js new file mode 100644 index 000000000..8023f5af4 --- /dev/null +++ b/src/components/sortable-picker/index.js @@ -0,0 +1,225 @@ +/** + * External dependencies + */ +import classnames from 'classnames' +import { Button } from '~stackable/components' +import { + sortableContainer, sortableElement, sortableHandle, +} from 'react-sortable-hoc' + +/** + * WordPress dependencies + */ +import { + ColorPicker, + BaseControl, + ColorIndicator, + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis + __experimentalHStack as HStack, + Dashicon, + Dropdown, +} from '@wordpress/components' +import { useState } from '@wordpress/element' +import { __ } from '@wordpress/i18n' +import { applyFilters } from '@wordpress/hooks' + +const popoverProps = { + placement: 'left-start', + offset: 36, + shift: true, +} + +// We need to define these because 13 (return key) is not included in the +// default keyCodes to initiate a drag. +const DRAG_KEYCODES = { + lift: [ 32, 13 ], + drop: [ 32, 13 ], + cancel: [ 27 ], + up: [ 38, 37 ], + down: [ 40, 39 ], +} + +const SortablePicker = props => { + const { + items, + itemType, + dropdownOnAdd = false, + onChangeItem, + onDeleteItem, + handleAddItem, + onSortEnd, + AddItemPopover = null, + ref, + } = props + const [ isSorting, setIsSorting ] = useState( false ) + + const classNames = classnames( + 'ugb-global-settings-color-picker', + 'components-circular-option-picker', + 'editor-color-palette-control__color-palette', + props.className + ) + + return ( + + ( + + ) + } } + renderContent={ () => ( +
    + { itemType === 'color' && + onChange( { + ...item, + color: value, + } ) } + color={ item.color } + enableAlpha={ true } + /> + } + { itemType === 'icon' && <> +

    hello

    + { applyFilters( 'stackable.global-settings.inspector.icon-library.icon-picker', null ) } + } +
    + ) } + /> + - ) - } } - renderContent={ () => ( -
    - onChange( { - ...color, - color: value, - } ) } - color={ color.color } - enableAlpha={ true } - /> -
    - ) } - /> - } ) } - { ! isBusy && ! results.length && + { ! isBusy && ! results.faIcons.length && ! results.iconLibrary.length &&

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

    } @@ -309,6 +326,7 @@ IconSearchPopover.defaultProps = { onClose: noop, returnSVGValue: true, // If true, the value provided in onChange will be the SVG markup of the icon. If false, the value will be a prefix-iconName value. allowReset: true, + showPrompt: true, __deprecatedAnchorRef: undefined, __deprecatedPosition: 'center', __deprecatedOnClickOutside: noop, diff --git a/src/components/icon-search-popover/search.js b/src/components/icon-search-popover/search.js index 11aabc4d6..d3b7ea84d 100644 --- a/src/components/icon-search-popover/search.js +++ b/src/components/icon-search-popover/search.js @@ -1,3 +1,4 @@ +import { applyFilters } from '@wordpress/hooks' import { fontAwesomeSearchProIcons, iconsFaKit, iconsFaProKitVersion, iconsFaFreeKitVersion, } from 'stackable' @@ -23,13 +24,17 @@ export const searchFontAwesomeIconName = async ( name = 'icon', isPro = fontAwes } ) .then( r => r.json() ) - return data.data.search.reduce( ( iconResults, iconData ) => { + const faIcons = data.data.search.reduce( ( iconResults, iconData ) => { convertFontAwesomeToIcon( iconData, isPro ).forEach( icon => { iconResults.push( icon ) } ) return iconResults }, [] ) + + const iconLibrary = applyFilters( 'stackable.global-settings.inspector.icon-library.search-icons', null, name ) + + return { faIcons, iconLibrary } } /** diff --git a/src/components/sortable-picker/index.js b/src/components/sortable-picker/index.js index 8023f5af4..ab75d8955 100644 --- a/src/components/sortable-picker/index.js +++ b/src/components/sortable-picker/index.js @@ -11,9 +11,7 @@ import { * WordPress dependencies */ import { - ColorPicker, BaseControl, - ColorIndicator, // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalHStack as HStack, Dashicon, @@ -21,7 +19,6 @@ import { } from '@wordpress/components' import { useState } from '@wordpress/element' import { __ } from '@wordpress/i18n' -import { applyFilters } from '@wordpress/hooks' const popoverProps = { placement: 'left-start', @@ -42,7 +39,6 @@ const DRAG_KEYCODES = { const SortablePicker = props => { const { items, - itemType, dropdownOnAdd = false, onChangeItem, onDeleteItem, @@ -99,7 +95,8 @@ const SortablePicker = props => { item={ item } onDelete={ () => onDeleteItem( item ) } onChange={ item => onChangeItem( item ) } - itemType={ itemType } + ItemPreview={ props.ItemPreview } + ItemPicker={ props.ItemPicker } /> ) ) } @@ -132,7 +129,9 @@ const LabeledItemIndicator = props => { item, onDelete, onChange, - itemType, + ItemPreview = null, + ItemPicker = null, + } = props const [ isFocused, setIsFocused ] = useState( false ) @@ -155,12 +154,7 @@ const LabeledItemIndicator = props => { isPressed={ isOpen } > - { itemType === 'color' && - } - { itemType === 'icon' &&

    Icon

    } + { } } renderContent={ () => (
    - { itemType === 'color' && - onChange( { - ...item, - color: value, - } ) } - color={ item.color } - enableAlpha={ true } - /> - } - { itemType === 'icon' && <> -

    hello

    - { applyFilters( 'stackable.global-settings.inspector.icon-library.icon-picker', null ) } - } +
    ) } /> diff --git a/src/global-settings.php b/src/global-settings.php index a88e0d4a7..9949ab46b 100644 --- a/src/global-settings.php +++ b/src/global-settings.php @@ -281,6 +281,35 @@ public function register_global_settings() { 'default' => '', ) ); + + register_setting( + 'stackable_global_settings', + 'stackable_icon_library', + array( + 'type' => 'array', + 'description' => __( 'Stackable Icon Library', STACKABLE_I18N ), + 'sanitize_callback' => array( $this, 'sanitize_array_setting' ), + 'show_in_rest' => array( + 'schema' => array( + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'name' => array( + 'type' => 'string', + ), + 'key' => array( + 'type' => 'string', + ), + 'icon' => array( + 'type' => 'string', + ), + ) + ) + ) + ), + 'default' => '', + ) + ); } /** diff --git a/src/plugins/global-settings/colors/color-picker.js b/src/plugins/global-settings/colors/color-picker.js index a25fb5165..21ab393fa 100644 --- a/src/plugins/global-settings/colors/color-picker.js +++ b/src/plugins/global-settings/colors/color-picker.js @@ -21,6 +21,7 @@ import { } from '@wordpress/data' import { models } from '@wordpress/api' import { __, sprintf } from '@wordpress/i18n' +import { ColorIndicator, ColorPicker } from '@wordpress/components' let saveTimeout = null @@ -126,6 +127,24 @@ const ColorPickers = props => { setIsSorting( false ) } + const ItemPreview = ( { item } ) => { + return + } + + const ItemPicker = ( { item, onChange } ) => { + return onChange( { + ...item, + color: value, + } ) } + color={ item.color } + enableAlpha={ true } + /> + } + return ( { onDeleteItem={ onColorDelete } handleAddItem={ handleAddIcon } onSortEnd={ onSortEnd } + ItemPreview={ ItemPreview } + ItemPicker={ ItemPicker } { ...props } /> ) From 84331ed6575d3a07e9b949b251f37bc81afdf72e Mon Sep 17 00:00:00 2001 From: Benjamin Intal Date: Tue, 8 Oct 2024 13:06:55 +0800 Subject: [PATCH 5/9] improve code readability --- src/components/font-awesome-icon/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/font-awesome-icon/index.js b/src/components/font-awesome-icon/index.js index 3f48aa894..499b4d3da 100644 --- a/src/components/font-awesome-icon/index.js +++ b/src/components/font-awesome-icon/index.js @@ -80,7 +80,10 @@ const addSVGAttributes = ( svgHTML, attributesToAdd = {}, attributesToRemove = [ } const FontAwesomeIcon = memo( props => { - const { svgAttrsToAdd = { width: '32', height: '32' }, svgAttrsToRemove = [ 'id', 'data-name' ] } = props + const { + svgAttrsToAdd = { width: '32', height: '32' }, + svgAttrsToRemove = [ 'id', 'data-name' ], + } = props const [ forceUpdateCount, setForceUpdateCount ] = useState( 0 ) const forceUpdate = () => { setForceUpdateCount( forceUpdateCount + 1 ) From 357db74777089e4ea6ee4d82752ba2dde33e5f4a Mon Sep 17 00:00:00 2001 From: "bfintal@gmail.com" <> Date: Wed, 9 Oct 2024 10:49:56 +0800 Subject: [PATCH 6/9] updated premium notice text --- src/components/pro-control/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/pro-control/index.js b/src/components/pro-control/index.js index d95427d37..7f7512880 100644 --- a/src/components/pro-control/index.js +++ b/src/components/pro-control/index.js @@ -110,8 +110,11 @@ const LABELS = { , }, 'icon-library': { + title: __( 'Unlock Your Icon Library', i18n ), description:
      -
    • { __( 'Icon Library', i18n ) }
    • +
    • { __( 'Add your custom SVG icons', i18n ) }
    • +
    • { __( 'Easily access your custom icons in the icon picker', i18n ) }
    • +
    • { __( 'Organize your custom icons in your library', i18n ) }
    , }, } From 6e3a005c8a886c51d4aa3474035dc30287c3b6c4 Mon Sep 17 00:00:00 2001 From: mxkae Date: Mon, 28 Oct 2024 11:07:22 +0800 Subject: [PATCH 7/9] update popover --- src/components/icon-search-popover/index.js | 21 +++++++++++-------- src/components/sortable-picker/index.js | 11 +++++++--- .../global-settings/colors/color-picker.js | 18 +++++++++------- src/plugins/global-settings/editor.scss | 2 +- 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/components/icon-search-popover/index.js b/src/components/icon-search-popover/index.js index f54834a46..32409178f 100644 --- a/src/components/icon-search-popover/index.js +++ b/src/components/icon-search-popover/index.js @@ -11,7 +11,9 @@ import { PanelBody, TextControl, Spinner, } from '@wordpress/components' import { __ } from '@wordpress/i18n' -import { useState, useEffect } from '@wordpress/element' +import { + useState, useEffect, Fragment, +} from '@wordpress/element' /** * External dependencies @@ -25,7 +27,7 @@ import { import { faGetIcon, faFetchIcon } from '~stackable/util' import { FileDrop } from 'react-file-drop' import classnames from 'classnames' -import { applyFilters } from '@wordpress/hooks' +import { applyFilters, doAction } from '@wordpress/hooks' let searchTimeout = null let tempMediaUpload = null @@ -152,9 +154,7 @@ const IconSearchPopover = props => { setIsDropping( false ) const svgString = cleanSvgString( addCustomIconClass( e.target.result ) ) - if ( isPro && props.showPrompt ) { - applyFilters( 'stackable.global-settings.inspector.icon-library.prompt', null, svgString ) - } + doAction( 'stackable.icon-search-popover.svg-upload', svgString ) props.onChange( svgString ) props.onClose() @@ -173,6 +173,8 @@ const IconSearchPopover = props => { 'ugb-icon--has-reset': props.allowReset, } ) + const IconLibraryIcons = applyFilters( 'stackable.global-settings.inspector.icon-library.icons', Fragment ) + const content = (
    {
    { isBusy && } - { ! isBusy && applyFilters( 'stackable.global-settings.inspector.icon-library.icons', null, { - icons: results.iconLibrary, onChange: props.onChange, onClose: props.onClose, - } ) } + { ! isBusy && } { ! isBusy && results.faIcons.map( ( { prefix, iconName }, i ) => { const iconValue = `${ prefix }-${ iconName }` return
    @@ -138,30 +139,18 @@ const LabeledItemIndicator = props => { onChange, ItemPreview = null, ItemPicker = null, - enableDebounce = true, // If false, onChange will be called immediately. + updateOnBlur = false, // If true, onChange will be called only when the input is blurred. } = props const [ isFocused, setIsFocused ] = useState( false ) - const [ debouncedText, setDebouncedText ] = useState( item.name ) + // Updates the debounced text when the items get resorted. useEffect( () => { - setDebouncedText( item.name ) - }, [ item.name ] ) - - useEffect( () => { - let timeout - if ( item.name !== debouncedText && enableDebounce ) { - timeout = setTimeout( () => { - onChange( { - ...item, - name: debouncedText, - } ) - }, 300 ) + if ( item.name !== debouncedText ) { + setDebouncedText( item.name ) } - - return () => clearTimeout( timeout ) - }, [ debouncedText, onChange ] ) + }, [ item.name ] ) return ( @@ -184,11 +173,12 @@ const LabeledItemIndicator = props => { { - if ( enableDebounce ) { - setDebouncedText( ev.target.value ) - } else { + setDebouncedText( ev.target.value ) + + // If we're not updating on blur, update the value immediately. + if ( ! updateOnBlur ) { onChange( { ...item, name: ev.target.value, @@ -197,7 +187,18 @@ const LabeledItemIndicator = props => { } } onFocus={ () => setIsFocused( true ) } onBlur={ ev => { - setIsFocused( false ) + if ( updateOnBlur ) { + onChange( { + ...item, + name: debouncedText, + } ) + } + + // Update isFocused after a delay to ensure onChange has finished if updating on blur. + setTimeout( () => { + setIsFocused( false ) + }, 100 ) + if ( isOpen && ! ev.relatedTarget?.closest( '.components-popover' ) ) { onToggle() }