From 6a02237fc954c9cc29a1227e0e84561a6731ea0a Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Tue, 27 Jun 2023 17:19:15 +0300 Subject: [PATCH] [Command center]: Add preferences and keyboard shortcuts commands (#51862) * [Command center]: Add preferences and keyboard shortcuts commands * update labels --- .../data/data-core-edit-post.md | 6 +++ .../header/preferences-menu-item/index.js | 7 +-- .../keyboard-shortcut-help-modal/index.js | 15 ++++-- .../src/components/preferences-modal/index.js | 10 ++-- .../src/hooks/commands/use-common-commands.js | 22 ++++++++ .../index.js | 7 +-- packages/edit-post/src/store/actions.js | 31 +++++++---- packages/edit-post/src/store/reducer.js | 20 ------- packages/edit-post/src/store/selectors.js | 14 +++-- packages/edit-post/src/store/test/reducer.js | 25 --------- .../edit-post/src/store/test/selectors.js | 27 ---------- .../header-edit-mode/more-menu/index.js | 53 +++++++++---------- .../keyboard-shortcut-help-modal/index.js | 28 +++++++--- .../src/components/preferences-modal/index.js | 20 ++++--- .../hooks/commands/use-edit-mode-commands.js | 26 ++++++++- packages/interface/src/store/actions.js | 25 +++++++++ packages/interface/src/store/reducer.js | 20 +++++++ packages/interface/src/store/selectors.js | 12 +++++ packages/interface/src/store/test/reducer.js | 30 +++++++++++ .../interface/src/store/test/selectors.js | 32 +++++++++++ 20 files changed, 288 insertions(+), 142 deletions(-) create mode 100644 packages/interface/src/store/test/reducer.js create mode 100644 packages/interface/src/store/test/selectors.js diff --git a/docs/reference-guides/data/data-core-edit-post.md b/docs/reference-guides/data/data-core-edit-post.md index aee88ce40bc73..b579e7e658007 100644 --- a/docs/reference-guides/data/data-core-edit-post.md +++ b/docs/reference-guides/data/data-core-edit-post.md @@ -264,6 +264,8 @@ _Returns_ ### isModalActive +> **Deprecated** since WP 6.3 use `core/interface` store's selector with the same name instead. + Returns true if a modal is active, or false otherwise. _Parameters_ @@ -336,6 +338,8 @@ Returns an action object signalling that the user closed the sidebar. ### closeModal +> **Deprecated** since WP 6.3 use `core/interface` store's action with the same name instead. + Returns an action object signalling that the user closed a modal. _Returns_ @@ -388,6 +392,8 @@ _Parameters_ ### openModal +> **Deprecated** since WP 6.3 use `core/interface` store's action with the same name instead. + Returns an action object used in signalling that the user opened a modal. _Parameters_ diff --git a/packages/edit-post/src/components/header/preferences-menu-item/index.js b/packages/edit-post/src/components/header/preferences-menu-item/index.js index bc747ca0afde8..037a896fc7f07 100644 --- a/packages/edit-post/src/components/header/preferences-menu-item/index.js +++ b/packages/edit-post/src/components/header/preferences-menu-item/index.js @@ -4,18 +4,19 @@ import { useDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { MenuItem } from '@wordpress/components'; +import { store as interfaceStore } from '@wordpress/interface'; /** * Internal dependencies */ -import { store as editPostStore } from '../../../store'; +import { PREFERENCES_MODAL_NAME } from '../../../components/preferences-modal'; export default function PreferencesMenuItem() { - const { openModal } = useDispatch( editPostStore ); + const { openModal } = useDispatch( interfaceStore ); return ( { - openModal( 'edit-post/preferences' ); + openModal( PREFERENCES_MODAL_NAME ); } } > { __( 'Preferences' ) } diff --git a/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js b/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js index 54565bb5dcd5b..9a7ce46704d47 100644 --- a/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js +++ b/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js @@ -14,6 +14,7 @@ import { } from '@wordpress/keyboard-shortcuts'; import { withSelect, withDispatch, useSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; +import { store as interfaceStore } from '@wordpress/interface'; /** * Internal dependencies @@ -21,9 +22,9 @@ import { compose } from '@wordpress/compose'; import { textFormattingShortcuts } from './config'; import Shortcut from './shortcut'; import DynamicShortcut from './dynamic-shortcut'; -import { store as editPostStore } from '../../store'; -const MODAL_NAME = 'edit-post/keyboard-shortcut-help'; +export const KEYBOARD_SHORTCUT_HELP_MODAL_NAME = + 'edit-post/keyboard-shortcut-help'; const ShortcutList = ( { shortcuts } ) => ( /* @@ -141,14 +142,18 @@ export function KeyboardShortcutHelpModal( { isModalActive, toggleModal } ) { export default compose( [ withSelect( ( select ) => ( { - isModalActive: select( editPostStore ).isModalActive( MODAL_NAME ), + isModalActive: select( interfaceStore ).isModalActive( + KEYBOARD_SHORTCUT_HELP_MODAL_NAME + ), } ) ), withDispatch( ( dispatch, { isModalActive } ) => { - const { openModal, closeModal } = dispatch( editPostStore ); + const { openModal, closeModal } = dispatch( interfaceStore ); return { toggleModal: () => - isModalActive ? closeModal() : openModal( MODAL_NAME ), + isModalActive + ? closeModal() + : openModal( KEYBOARD_SHORTCUT_HELP_MODAL_NAME ), }; } ), ] )( KeyboardShortcutHelpModal ); diff --git a/packages/edit-post/src/components/preferences-modal/index.js b/packages/edit-post/src/components/preferences-modal/index.js index 77c6383b13f32..bb0c8b5cd1271 100644 --- a/packages/edit-post/src/components/preferences-modal/index.js +++ b/packages/edit-post/src/components/preferences-modal/index.js @@ -18,6 +18,7 @@ import { PreferencesModal, PreferencesModalTabs, PreferencesModalSection, + store as interfaceStore, } from '@wordpress/interface'; import { store as preferencesStore } from '@wordpress/preferences'; @@ -35,17 +36,18 @@ import MetaBoxesSection from './meta-boxes-section'; import { store as editPostStore } from '../../store'; import BlockManager from '../block-manager'; -const MODAL_NAME = 'edit-post/preferences'; +export const PREFERENCES_MODAL_NAME = 'edit-post/preferences'; export default function EditPostPreferencesModal() { const isLargeViewport = useViewportMatch( 'medium' ); - const { closeModal } = useDispatch( editPostStore ); + const { closeModal } = useDispatch( interfaceStore ); const [ isModalActive, showBlockBreadcrumbsOption ] = useSelect( ( select ) => { const { getEditorSettings } = select( editorStore ); const { getEditorMode, isFeatureActive } = select( editPostStore ); - const modalActive = - select( editPostStore ).isModalActive( MODAL_NAME ); + const modalActive = select( interfaceStore ).isModalActive( + PREFERENCES_MODAL_NAME + ); const mode = getEditorMode(); const isRichEditingEnabled = getEditorSettings().richEditingEnabled; const isDistractionFreeEnabled = diff --git a/packages/edit-post/src/hooks/commands/use-common-commands.js b/packages/edit-post/src/hooks/commands/use-common-commands.js index 796e0665fc2fa..0366e78179985 100644 --- a/packages/edit-post/src/hooks/commands/use-common-commands.js +++ b/packages/edit-post/src/hooks/commands/use-common-commands.js @@ -9,6 +9,7 @@ import { drawerLeft, drawerRight, blockDefault, + keyboardClose, } from '@wordpress/icons'; import { useCommand } from '@wordpress/commands'; import { store as preferencesStore } from '@wordpress/preferences'; @@ -17,11 +18,14 @@ import { store as interfaceStore } from '@wordpress/interface'; /** * Internal dependencies */ +import { KEYBOARD_SHORTCUT_HELP_MODAL_NAME } from '../../components/keyboard-shortcut-help-modal'; +import { PREFERENCES_MODAL_NAME } from '../../components/preferences-modal'; import { store as editPostStore } from '../../store'; export default function useCommonCommands() { const { openGeneralSidebar, closeGeneralSidebar, switchEditorMode } = useDispatch( editPostStore ); + const { openModal } = useDispatch( interfaceStore ); const { editorMode, activeSidebar } = useSelect( ( select ) => ( { activeSidebar: select( interfaceStore ).getActiveComplementaryArea( @@ -100,4 +104,22 @@ export default function useCommonCommands() { close(); }, } ); + + useCommand( { + name: 'core/open-preferences', + label: __( 'Open editor preferences' ), + icon: cog, + callback: () => { + openModal( PREFERENCES_MODAL_NAME ); + }, + } ); + + useCommand( { + name: 'core/open-shortcut-help', + label: __( 'Open keyboard shortcuts' ), + icon: keyboardClose, + callback: () => { + openModal( KEYBOARD_SHORTCUT_HELP_MODAL_NAME ); + }, + } ); } diff --git a/packages/edit-post/src/plugins/keyboard-shortcuts-help-menu-item/index.js b/packages/edit-post/src/plugins/keyboard-shortcuts-help-menu-item/index.js index 69f7b35c3f116..930f420a24129 100644 --- a/packages/edit-post/src/plugins/keyboard-shortcuts-help-menu-item/index.js +++ b/packages/edit-post/src/plugins/keyboard-shortcuts-help-menu-item/index.js @@ -5,17 +5,18 @@ import { MenuItem } from '@wordpress/components'; import { withDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { displayShortcut } from '@wordpress/keycodes'; +import { store as interfaceStore } from '@wordpress/interface'; /** * Internal dependencies */ -import { store as editPostStore } from '../../store'; +import { KEYBOARD_SHORTCUT_HELP_MODAL_NAME } from '../../components/keyboard-shortcut-help-modal'; export function KeyboardShortcutsHelpMenuItem( { openModal } ) { return ( { - openModal( 'edit-post/keyboard-shortcut-help' ); + openModal( KEYBOARD_SHORTCUT_HELP_MODAL_NAME ); } } shortcut={ displayShortcut.access( 'h' ) } > @@ -25,7 +26,7 @@ export function KeyboardShortcutsHelpMenuItem( { openModal } ) { } export default withDispatch( ( dispatch ) => { - const { openModal } = dispatch( editPostStore ); + const { openModal } = dispatch( interfaceStore ); return { openModal, diff --git a/packages/edit-post/src/store/actions.js b/packages/edit-post/src/store/actions.js index 90f29f37b145c..f938a9837516e 100644 --- a/packages/edit-post/src/store/actions.js +++ b/packages/edit-post/src/store/actions.js @@ -10,6 +10,7 @@ import { store as noticesStore } from '@wordpress/notices'; import { store as coreStore } from '@wordpress/core-data'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { store as editorStore } from '@wordpress/editor'; +import deprecated from '@wordpress/deprecated'; /** * Internal dependencies @@ -42,27 +43,39 @@ export const closeGeneralSidebar = /** * Returns an action object used in signalling that the user opened a modal. * + * @deprecated since WP 6.3 use `core/interface` store's action with the same name instead. + * + * * @param {string} name A string that uniquely identifies the modal. * * @return {Object} Action object. */ -export function openModal( name ) { - return { - type: 'OPEN_MODAL', - name, +export const openModal = + ( name ) => + ( { registry } ) => { + deprecated( "select( 'core/edit-post' ).openModal( name )", { + since: '6.3', + alternative: "select( 'core/interface').openModal( name )", + } ); + return registry.dispatch( interfaceStore ).openModal( name ); }; -} /** * Returns an action object signalling that the user closed a modal. * + * @deprecated since WP 6.3 use `core/interface` store's action with the same name instead. + * * @return {Object} Action object. */ -export function closeModal() { - return { - type: 'CLOSE_MODAL', +export const closeModal = + () => + ( { registry } ) => { + deprecated( "select( 'core/edit-post' ).closeModal()", { + since: '6.3', + alternative: "select( 'core/interface').closeModal()", + } ); + return registry.dispatch( interfaceStore ).closeModal(); }; -} /** * Returns an action object used in signalling that the user opened the publish diff --git a/packages/edit-post/src/store/reducer.js b/packages/edit-post/src/store/reducer.js index 8a4031ecf9b09..622b2e2667f7f 100644 --- a/packages/edit-post/src/store/reducer.js +++ b/packages/edit-post/src/store/reducer.js @@ -22,25 +22,6 @@ export function removedPanels( state = [], action ) { return state; } -/** - * Reducer for storing the name of the open modal, or null if no modal is open. - * - * @param {Object} state Previous state. - * @param {Object} action Action object containing the `name` of the modal - * - * @return {Object} Updated state - */ -export function activeModal( state = null, action ) { - switch ( action.type ) { - case 'OPEN_MODAL': - return action.name; - case 'CLOSE_MODAL': - return null; - } - - return state; -} - export function publishSidebarActive( state = false, action ) { switch ( action.type ) { case 'OPEN_PUBLISH_SIDEBAR': @@ -209,7 +190,6 @@ const metaBoxes = combineReducers( { } ); export default combineReducers( { - activeModal, metaBoxes, publishSidebarActive, removedPanels, diff --git a/packages/edit-post/src/store/selectors.js b/packages/edit-post/src/store/selectors.js index b84e2b6887431..5ae7d6b4437b7 100644 --- a/packages/edit-post/src/store/selectors.js +++ b/packages/edit-post/src/store/selectors.js @@ -298,14 +298,22 @@ export const isEditorPanelOpened = createRegistrySelector( /** * Returns true if a modal is active, or false otherwise. * + * @deprecated since WP 6.3 use `core/interface` store's selector with the same name instead. + * * @param {Object} state Global application state. * @param {string} modalName A string that uniquely identifies the modal. * * @return {boolean} Whether the modal is active. */ -export function isModalActive( state, modalName ) { - return state.activeModal === modalName; -} +export const isModalActive = createRegistrySelector( + ( select ) => ( state, modalName ) => { + deprecated( `select( 'core/edit-post' ).isModalActive`, { + since: '6.3', + alternative: `select( 'core/interface' ).isModalActive`, + } ); + return !! select( interfaceStore ).isModalActive( modalName ); + } +); /** * Returns whether the given feature is enabled or not. diff --git a/packages/edit-post/src/store/test/reducer.js b/packages/edit-post/src/store/test/reducer.js index 4be7c7cb3ffd8..a083de9c67286 100644 --- a/packages/edit-post/src/store/test/reducer.js +++ b/packages/edit-post/src/store/test/reducer.js @@ -7,7 +7,6 @@ import deepFreeze from 'deep-freeze'; * Internal dependencies */ import { - activeModal, isSavingMetaBoxes, metaBoxLocations, removedPanels, @@ -18,30 +17,6 @@ import { import { setIsInserterOpened, setIsListViewOpened } from '../actions'; describe( 'state', () => { - describe( 'activeModal', () => { - it( 'should default to null', () => { - const state = activeModal( undefined, {} ); - expect( state ).toBeNull(); - } ); - - it( 'should set the activeModal to the provided name', () => { - const state = activeModal( null, { - type: 'OPEN_MODAL', - name: 'test-modal', - } ); - - expect( state ).toEqual( 'test-modal' ); - } ); - - it( 'should set the activeModal to null', () => { - const state = activeModal( 'test-modal', { - type: 'CLOSE_MODAL', - } ); - - expect( state ).toBeNull(); - } ); - } ); - describe( 'isSavingMetaBoxes', () => { it( 'should return default state', () => { const actual = isSavingMetaBoxes( undefined, {} ); diff --git a/packages/edit-post/src/store/test/selectors.js b/packages/edit-post/src/store/test/selectors.js index 5df8456dd8534..34c66ed7cf8e6 100644 --- a/packages/edit-post/src/store/test/selectors.js +++ b/packages/edit-post/src/store/test/selectors.js @@ -7,7 +7,6 @@ import deepFreeze from 'deep-freeze'; * Internal dependencies */ import { - isModalActive, hasMetaBoxes, isSavingMetaBoxes, getActiveMetaBoxLocations, @@ -18,32 +17,6 @@ import { } from '../selectors'; describe( 'selectors', () => { - describe( 'isModalActive', () => { - it( 'returns true if the provided name matches the value in the preferences activeModal property', () => { - const state = { - activeModal: 'test-modal', - }; - - expect( isModalActive( state, 'test-modal' ) ).toBe( true ); - } ); - - it( 'returns false if the provided name does not match the preferences activeModal property', () => { - const state = { - activeModal: 'something-else', - }; - - expect( isModalActive( state, 'test-modal' ) ).toBe( false ); - } ); - - it( 'returns false if the preferences activeModal property is null', () => { - const state = { - activeModal: null, - }; - - expect( isModalActive( state, 'test-modal' ) ).toBe( false ); - } ); - } ); - describe( 'isEditorPanelRemoved', () => { it( 'should return false by default', () => { const state = deepFreeze( { diff --git a/packages/edit-site/src/components/header-edit-mode/more-menu/index.js b/packages/edit-site/src/components/header-edit-mode/more-menu/index.js index 7fe63a343960a..4b9d6c40fc6ee 100644 --- a/packages/edit-site/src/components/header-edit-mode/more-menu/index.js +++ b/packages/edit-site/src/components/header-edit-mode/more-menu/index.js @@ -2,13 +2,15 @@ * WordPress dependencies */ import { __, _x } from '@wordpress/i18n'; -import { useReducer } from '@wordpress/element'; import { useSelect, useDispatch, useRegistry } from '@wordpress/data'; -import { useShortcut } from '@wordpress/keyboard-shortcuts'; import { displayShortcut } from '@wordpress/keycodes'; import { external } from '@wordpress/icons'; import { MenuGroup, MenuItem, VisuallyHidden } from '@wordpress/components'; -import { ActionItem, MoreMenuDropdown } from '@wordpress/interface'; +import { + ActionItem, + MoreMenuDropdown, + store as interfaceStore, +} from '@wordpress/interface'; import { PreferenceToggleMenuItem, store as preferencesStore, @@ -17,8 +19,14 @@ import { /** * Internal dependencies */ -import KeyboardShortcutHelpModal from '../../keyboard-shortcut-help-modal'; -import EditSitePreferencesModal from '../../preferences-modal'; +import { + KEYBOARD_SHORTCUT_HELP_MODAL_NAME, + default as KeyboardShortcutHelpModal, +} from '../../keyboard-shortcut-help-modal'; +import { + PREFERENCES_MODAL_NAME, + default as EditSitePreferencesModal, +} from '../../preferences-modal'; import ToolsMoreMenuGroup from '../tools-more-menu-group'; import SiteExport from './site-export'; import WelcomeGuideMenuItem from './welcome-guide-menu-item'; @@ -27,16 +35,6 @@ import ModeSwitcher from '../mode-switcher'; import { store as siteEditorStore } from '../../../store'; export default function MoreMenu( { showIconLabels } ) { - const [ isModalActive, toggleModal ] = useReducer( - ( isActive ) => ! isActive, - false - ); - - const [ isPreferencesModalActive, togglePreferencesModal ] = useReducer( - ( isActive ) => ! isActive, - false - ); - const registry = useRegistry(); const isDistractionFree = useSelect( ( select ) => @@ -49,6 +47,7 @@ export default function MoreMenu( { showIconLabels } ) { const { setIsInserterOpened, setIsListViewOpened, closeGeneralSidebar } = useDispatch( siteEditorStore ); + const { openModal } = useDispatch( interfaceStore ); const { set: setPreference } = useDispatch( preferencesStore ); const toggleDistractionFree = () => { @@ -60,8 +59,6 @@ export default function MoreMenu( { showIconLabels } ) { } ); }; - useShortcut( 'core/edit-site/keyboard-shortcuts', toggleModal ); - return ( <> + openModal( + KEYBOARD_SHORTCUT_HELP_MODAL_NAME + ) + } shortcut={ displayShortcut.access( 'h' ) } > { __( 'Keyboard shortcuts' ) } @@ -156,21 +157,19 @@ export default function MoreMenu( { showIconLabels } ) { /> - + + openModal( PREFERENCES_MODAL_NAME ) + } + > { __( 'Preferences' ) } ) } - - + + ); } diff --git a/packages/edit-site/src/components/keyboard-shortcut-help-modal/index.js b/packages/edit-site/src/components/keyboard-shortcut-help-modal/index.js index aac95775cf1d0..1da14a7cccbe7 100644 --- a/packages/edit-site/src/components/keyboard-shortcut-help-modal/index.js +++ b/packages/edit-site/src/components/keyboard-shortcut-help-modal/index.js @@ -8,8 +8,12 @@ import classnames from 'classnames'; */ import { Modal } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; -import { useSelect } from '@wordpress/data'; +import { + useShortcut, + store as keyboardShortcutsStore, +} from '@wordpress/keyboard-shortcuts'; +import { store as interfaceStore } from '@wordpress/interface'; +import { useSelect, useDispatch } from '@wordpress/data'; /** * Internal dependencies @@ -18,6 +22,9 @@ import { textFormattingShortcuts } from './config'; import Shortcut from './shortcut'; import DynamicShortcut from './dynamic-shortcut'; +export const KEYBOARD_SHORTCUT_HELP_MODAL_NAME = + 'edit-site/keyboard-shortcut-help'; + const ShortcutList = ( { shortcuts } ) => ( /* * Disable reason: The `list` ARIA role is redundant but @@ -82,14 +89,21 @@ const ShortcutCategorySection = ( { ); }; -export default function KeyboardShortcutHelpModal( { - isModalActive, - toggleModal, -} ) { +export default function KeyboardShortcutHelpModal() { + const isModalActive = useSelect( ( select ) => + select( interfaceStore ).isModalActive( + KEYBOARD_SHORTCUT_HELP_MODAL_NAME + ) + ); + const { closeModal, openModal } = useDispatch( interfaceStore ); + const toggleModal = () => + isModalActive + ? closeModal() + : openModal( KEYBOARD_SHORTCUT_HELP_MODAL_NAME ); + useShortcut( 'core/edit-site/keyboard-shortcuts', toggleModal ); if ( ! isModalActive ) { return null; } - return ( + select( interfaceStore ).isModalActive( PREFERENCES_MODAL_NAME ) + ); + const { closeModal, openModal } = useDispatch( interfaceStore ); + const toggleModal = () => + isModalActive ? closeModal() : openModal( PREFERENCES_MODAL_NAME ); const registry = useRegistry(); const { closeGeneralSidebar, setIsListViewOpened, setIsInserterOpened } = - useDispatch( siteEditorStore ); + useDispatch( editSiteStore ); const { set: setPreference } = useDispatch( preferencesStore ); const toggleDistractionFree = () => { diff --git a/packages/edit-site/src/hooks/commands/use-edit-mode-commands.js b/packages/edit-site/src/hooks/commands/use-edit-mode-commands.js index 2466bb2a706c4..92ccee08592cd 100644 --- a/packages/edit-site/src/hooks/commands/use-edit-mode-commands.js +++ b/packages/edit-site/src/hooks/commands/use-edit-mode-commands.js @@ -4,8 +4,6 @@ import { useSelect, useDispatch } from '@wordpress/data'; import { __, isRTL } from '@wordpress/i18n'; import { - code, - cog, trash, backup, layout, @@ -13,6 +11,9 @@ import { drawerLeft, drawerRight, blockDefault, + cog, + code, + keyboardClose, } from '@wordpress/icons'; import { useCommandLoader } from '@wordpress/commands'; import { privateApis as routerPrivateApis } from '@wordpress/router'; @@ -26,6 +27,8 @@ import { store as editSiteStore } from '../../store'; import useEditedEntityRecord from '../../components/use-edited-entity-record'; import isTemplateRemovable from '../../utils/is-template-removable'; import isTemplateRevertable from '../../utils/is-template-revertable'; +import { KEYBOARD_SHORTCUT_HELP_MODAL_NAME } from '../../components/keyboard-shortcut-help-modal'; +import { PREFERENCES_MODAL_NAME } from '../../components/preferences-modal'; import { unlock } from '../../lock-unlock'; const { useHistory } = unlock( routerPrivateApis ); @@ -142,6 +145,7 @@ function useEditUICommands() { } ), [] ); + const { openModal } = useDispatch( interfaceStore ); const { toggle } = useDispatch( preferencesStore ); if ( canvasMode !== 'edit' ) { @@ -208,6 +212,24 @@ function useEditUICommands() { }, } ); + commands.push( { + name: 'core/open-preferences', + label: __( 'Open editor preferences' ), + icon: cog, + callback: () => { + openModal( PREFERENCES_MODAL_NAME ); + }, + } ); + + commands.push( { + name: 'core/open-shortcut-help', + label: __( 'Open keyboard shortcuts' ), + icon: keyboardClose, + callback: () => { + openModal( KEYBOARD_SHORTCUT_HELP_MODAL_NAME ); + }, + } ); + return { isLoading: false, commands, diff --git a/packages/interface/src/store/actions.js b/packages/interface/src/store/actions.js index bc51d4fca7220..b7a9935e888b5 100644 --- a/packages/interface/src/store/actions.js +++ b/packages/interface/src/store/actions.js @@ -181,3 +181,28 @@ export function setFeatureDefaults( scope, defaults ) { registry.dispatch( preferencesStore ).setDefaults( scope, defaults ); }; } + +/** + * Returns an action object used in signalling that the user opened a modal. + * + * @param {string} name A string that uniquely identifies the modal. + * + * @return {Object} Action object. + */ +export function openModal( name ) { + return { + type: 'OPEN_MODAL', + name, + }; +} + +/** + * Returns an action object signalling that the user closed a modal. + * + * @return {Object} Action object. + */ +export function closeModal() { + return { + type: 'CLOSE_MODAL', + }; +} diff --git a/packages/interface/src/store/reducer.js b/packages/interface/src/store/reducer.js index 433f71d15bcc5..8317e53cec95b 100644 --- a/packages/interface/src/store/reducer.js +++ b/packages/interface/src/store/reducer.js @@ -30,6 +30,26 @@ export function complementaryAreas( state = {}, action ) { return state; } +/** + * Reducer for storing the name of the open modal, or null if no modal is open. + * + * @param {Object} state Previous state. + * @param {Object} action Action object containing the `name` of the modal + * + * @return {Object} Updated state + */ +export function activeModal( state = null, action ) { + switch ( action.type ) { + case 'OPEN_MODAL': + return action.name; + case 'CLOSE_MODAL': + return null; + } + + return state; +} + export default combineReducers( { complementaryAreas, + activeModal, } ); diff --git a/packages/interface/src/store/selectors.js b/packages/interface/src/store/selectors.js index c92e45bbd3c59..548cb2f70346a 100644 --- a/packages/interface/src/store/selectors.js +++ b/packages/interface/src/store/selectors.js @@ -90,3 +90,15 @@ export const isFeatureActive = createRegistrySelector( return !! select( preferencesStore ).get( scope, featureName ); } ); + +/** + * Returns true if a modal is active, or false otherwise. + * + * @param {Object} state Global application state. + * @param {string} modalName A string that uniquely identifies the modal. + * + * @return {boolean} Whether the modal is active. + */ +export function isModalActive( state, modalName ) { + return state.activeModal === modalName; +} diff --git a/packages/interface/src/store/test/reducer.js b/packages/interface/src/store/test/reducer.js new file mode 100644 index 0000000000000..eb9424637bca0 --- /dev/null +++ b/packages/interface/src/store/test/reducer.js @@ -0,0 +1,30 @@ +/** + * Internal dependencies + */ +import { activeModal } from '../reducer'; + +describe( 'state', () => { + describe( 'activeModal', () => { + it( 'should default to null', () => { + const state = activeModal( undefined, {} ); + expect( state ).toBeNull(); + } ); + + it( 'should set the activeModal to the provided name', () => { + const state = activeModal( null, { + type: 'OPEN_MODAL', + name: 'test-modal', + } ); + + expect( state ).toEqual( 'test-modal' ); + } ); + + it( 'should set the activeModal to null', () => { + const state = activeModal( 'test-modal', { + type: 'CLOSE_MODAL', + } ); + + expect( state ).toBeNull(); + } ); + } ); +} ); diff --git a/packages/interface/src/store/test/selectors.js b/packages/interface/src/store/test/selectors.js new file mode 100644 index 0000000000000..a3bea882043d2 --- /dev/null +++ b/packages/interface/src/store/test/selectors.js @@ -0,0 +1,32 @@ +/** + * Internal dependencies + */ +import { isModalActive } from '../selectors'; + +describe( 'selectors', () => { + describe( 'isModalActive', () => { + it( 'returns true if the provided name matches the value in the preferences activeModal property', () => { + const state = { + activeModal: 'test-modal', + }; + + expect( isModalActive( state, 'test-modal' ) ).toBe( true ); + } ); + + it( 'returns false if the provided name does not match the preferences activeModal property', () => { + const state = { + activeModal: 'something-else', + }; + + expect( isModalActive( state, 'test-modal' ) ).toBe( false ); + } ); + + it( 'returns false if the preferences activeModal property is null', () => { + const state = { + activeModal: null, + }; + + expect( isModalActive( state, 'test-modal' ) ).toBe( false ); + } ); + } ); +} );