@wordpress/patterns 1.2.1-next.5a1d1283.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/components/category-selector.js +73 -0
  3. package/build/components/category-selector.js.map +1 -0
  4. package/build/components/create-pattern-modal.js +64 -21
  5. package/build/components/create-pattern-modal.js.map +1 -1
  6. package/build/components/pattern-convert-button.js +20 -1
  7. package/build/components/pattern-convert-button.js.map +1 -1
  8. package/build/components/patterns-manage-button.js +7 -3
  9. package/build/components/patterns-manage-button.js.map +1 -1
  10. package/build/constants.js +23 -0
  11. package/build/constants.js.map +1 -0
  12. package/build/index.js +11 -1
  13. package/build/index.js.map +1 -1
  14. package/build/private-apis.js +7 -1
  15. package/build/private-apis.js.map +1 -1
  16. package/build/store/actions.js +45 -23
  17. package/build/store/actions.js.map +1 -1
  18. package/build/store/index.js +4 -3
  19. package/build/store/index.js.map +1 -1
  20. package/build/store/selectors.js +2 -2
  21. package/build/store/selectors.js.map +1 -1
  22. package/build-module/components/category-selector.js +65 -0
  23. package/build-module/components/category-selector.js.map +1 -0
  24. package/build-module/components/create-pattern-modal.js +64 -20
  25. package/build-module/components/create-pattern-modal.js.map +1 -1
  26. package/build-module/components/pattern-convert-button.js +22 -3
  27. package/build-module/components/pattern-convert-button.js.map +1 -1
  28. package/build-module/components/patterns-manage-button.js +8 -4
  29. package/build-module/components/patterns-manage-button.js.map +1 -1
  30. package/build-module/constants.js +12 -0
  31. package/build-module/constants.js.map +1 -0
  32. package/build-module/index.js +1 -1
  33. package/build-module/index.js.map +1 -1
  34. package/build-module/private-apis.js +7 -1
  35. package/build-module/private-apis.js.map +1 -1
  36. package/build-module/store/actions.js +41 -20
  37. package/build-module/store/actions.js.map +1 -1
  38. package/build-module/store/index.js +4 -3
  39. package/build-module/store/index.js.map +1 -1
  40. package/build-module/store/selectors.js +1 -1
  41. package/build-module/store/selectors.js.map +1 -1
  42. package/build-style/style-rtl.css +7 -0
  43. package/build-style/style.css +7 -0
  44. package/package.json +16 -14
  45. package/src/components/category-selector.js +80 -0
  46. package/src/components/create-pattern-modal.js +89 -40
  47. package/src/components/pattern-convert-button.js +26 -3
  48. package/src/components/patterns-manage-button.js +10 -4
  49. package/src/components/style.scss +7 -0
  50. package/src/constants.js +17 -0
  51. package/src/index.js +1 -2
  52. package/src/private-apis.js +12 -0
  53. package/src/store/actions.js +55 -27
  54. package/src/store/index.js +3 -2
  55. package/src/store/selectors.js +1 -1
@@ -10,56 +10,97 @@ import {
10
10
  ToggleControl,
11
11
  } from '@wordpress/components';
12
12
  import { __ } from '@wordpress/i18n';
13
- import { useState, useCallback } from '@wordpress/element';
13
+ import { useState } from '@wordpress/element';
14
14
  import { useDispatch } from '@wordpress/data';
15
15
  import { store as noticesStore } from '@wordpress/notices';
16
+ import { store as coreStore } from '@wordpress/core-data';
16
17
 
17
- export const USER_PATTERN_CATEGORY = 'my-patterns';
18
-
19
- export const SYNC_TYPES = {
20
- full: undefined,
21
- unsynced: 'unsynced',
22
- };
18
+ /**
19
+ * Internal dependencies
20
+ */
21
+ import { PATTERN_DEFAULT_CATEGORY, PATTERN_SYNC_TYPES } from '../constants';
23
22
 
24
23
  /**
25
24
  * Internal dependencies
26
25
  */
27
- import { store } from '../store';
26
+ import { store as patternsStore } from '../store';
27
+ import CategorySelector, { CATEGORY_SLUG } from './category-selector';
28
+ import { unlock } from '../lock-unlock';
28
29
 
29
30
  export default function CreatePatternModal( {
30
31
  onSuccess,
31
32
  onError,
32
- clientIds,
33
+ content,
33
34
  onClose,
34
35
  className = 'patterns-menu-items__convert-modal',
35
36
  } ) {
36
- const [ syncType, setSyncType ] = useState( SYNC_TYPES.full );
37
+ const [ syncType, setSyncType ] = useState( PATTERN_SYNC_TYPES.full );
38
+ const [ categoryTerms, setCategoryTerms ] = useState( [] );
37
39
  const [ title, setTitle ] = useState( '' );
38
- const { __experimentalCreatePattern: createPattern } = useDispatch( store );
39
-
40
+ const [ isSaving, setIsSaving ] = useState( false );
41
+ const { createPattern } = unlock( useDispatch( patternsStore ) );
42
+ const { saveEntityRecord, invalidateResolution } = useDispatch( coreStore );
40
43
  const { createErrorNotice } = useDispatch( noticesStore );
41
- const onCreate = useCallback(
42
- async function ( patternTitle, sync ) {
43
- try {
44
- const newPattern = await createPattern(
45
- patternTitle,
46
- sync,
47
- clientIds
48
- );
49
- onSuccess( {
50
- pattern: newPattern,
51
- categoryId: USER_PATTERN_CATEGORY,
52
- } );
53
- } catch ( error ) {
54
- createErrorNotice( error.message, {
55
- type: 'snackbar',
56
- id: 'convert-to-pattern-error',
57
- } );
58
- onError();
44
+
45
+ async function onCreate( patternTitle, sync ) {
46
+ if ( ! title || isSaving ) {
47
+ return;
48
+ }
49
+
50
+ try {
51
+ setIsSaving( true );
52
+ const categories = await Promise.all(
53
+ categoryTerms.map( ( termName ) =>
54
+ findOrCreateTerm( termName )
55
+ )
56
+ );
57
+
58
+ const newPattern = await createPattern(
59
+ patternTitle,
60
+ sync,
61
+ typeof content === 'function' ? content() : content,
62
+ categories
63
+ );
64
+ onSuccess( {
65
+ pattern: newPattern,
66
+ categoryId: PATTERN_DEFAULT_CATEGORY,
67
+ } );
68
+ } catch ( error ) {
69
+ createErrorNotice( error.message, {
70
+ type: 'snackbar',
71
+ id: 'convert-to-pattern-error',
72
+ } );
73
+ onError();
74
+ } finally {
75
+ setIsSaving( false );
76
+ setCategoryTerms( [] );
77
+ setTitle( '' );
78
+ }
79
+ }
80
+
81
+ /**
82
+ * @param {string} term
83
+ * @return {Promise<number>} The pattern category id.
84
+ */
85
+ async function findOrCreateTerm( term ) {
86
+ try {
87
+ const newTerm = await saveEntityRecord(
88
+ 'taxonomy',
89
+ CATEGORY_SLUG,
90
+ { name: term },
91
+ { throwOnError: true }
92
+ );
93
+ invalidateResolution( 'getUserPatternCategories' );
94
+ return newTerm.id;
95
+ } catch ( error ) {
96
+ if ( error.code !== 'term_exists' ) {
97
+ throw error;
59
98
  }
60
- },
61
- [ createPattern, clientIds, onSuccess, createErrorNotice, onError ]
62
- );
99
+
100
+ return error.data.term_id;
101
+ }
102
+ }
103
+
63
104
  return (
64
105
  <Modal
65
106
  title={ __( 'Create pattern' ) }
@@ -73,7 +114,6 @@ export default function CreatePatternModal( {
73
114
  onSubmit={ ( event ) => {
74
115
  event.preventDefault();
75
116
  onCreate( title, syncType );
76
- setTitle( '' );
77
117
  } }
78
118
  >
79
119
  <VStack spacing="5">
@@ -83,19 +123,23 @@ export default function CreatePatternModal( {
83
123
  value={ title }
84
124
  onChange={ setTitle }
85
125
  placeholder={ __( 'My pattern' ) }
126
+ className="patterns-create-modal__name-input"
127
+ />
128
+ <CategorySelector
129
+ values={ categoryTerms }
130
+ onChange={ setCategoryTerms }
86
131
  />
87
-
88
132
  <ToggleControl
89
133
  label={ __( 'Synced' ) }
90
134
  help={ __(
91
135
  'Editing the pattern will update it anywhere it is used.'
92
136
  ) }
93
- checked={ ! syncType }
137
+ checked={ syncType === PATTERN_SYNC_TYPES.full }
94
138
  onChange={ () => {
95
139
  setSyncType(
96
- syncType === SYNC_TYPES.full
97
- ? SYNC_TYPES.unsynced
98
- : SYNC_TYPES.full
140
+ syncType === PATTERN_SYNC_TYPES.full
141
+ ? PATTERN_SYNC_TYPES.unsynced
142
+ : PATTERN_SYNC_TYPES.full
99
143
  );
100
144
  } }
101
145
  />
@@ -110,7 +154,12 @@ export default function CreatePatternModal( {
110
154
  { __( 'Cancel' ) }
111
155
  </Button>
112
156
 
113
- <Button variant="primary" type="submit">
157
+ <Button
158
+ variant="primary"
159
+ type="submit"
160
+ aria-disabled={ ! title || isSaving }
161
+ isBusy={ isSaving }
162
+ >
114
163
  { __( 'Create' ) }
115
164
  </Button>
116
165
  </HStack>
@@ -1,9 +1,14 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import { hasBlockSupport, isReusableBlock } from '@wordpress/blocks';
4
+ import {
5
+ hasBlockSupport,
6
+ isReusableBlock,
7
+ createBlock,
8
+ serialize,
9
+ } from '@wordpress/blocks';
5
10
  import { store as blockEditorStore } from '@wordpress/block-editor';
6
- import { useState } from '@wordpress/element';
11
+ import { useState, useCallback } from '@wordpress/element';
7
12
  import { MenuItem } from '@wordpress/components';
8
13
  import { symbol } from '@wordpress/icons';
9
14
  import { useSelect, useDispatch } from '@wordpress/data';
@@ -13,7 +18,9 @@ import { store as noticesStore } from '@wordpress/notices';
13
18
  /**
14
19
  * Internal dependencies
15
20
  */
21
+ import { store as patternsStore } from '../store';
16
22
  import CreatePatternModal from './create-pattern-modal';
23
+ import { unlock } from '../lock-unlock';
17
24
 
18
25
  /**
19
26
  * Menu control to convert block(s) to a pattern block.
@@ -25,6 +32,10 @@ import CreatePatternModal from './create-pattern-modal';
25
32
  */
26
33
  export default function PatternConvertButton( { clientIds, rootClientId } ) {
27
34
  const { createSuccessNotice } = useDispatch( noticesStore );
35
+ const { replaceBlocks } = useDispatch( blockEditorStore );
36
+ // Ignore reason: false positive of the lint rule.
37
+ // eslint-disable-next-line @wordpress/no-unused-vars-before-return
38
+ const { setEditingPattern } = unlock( useDispatch( patternsStore ) );
28
39
  const [ isModalOpen, setIsModalOpen ] = useState( false );
29
40
  const canConvert = useSelect(
30
41
  ( select ) => {
@@ -74,12 +85,24 @@ export default function PatternConvertButton( { clientIds, rootClientId } ) {
74
85
  },
75
86
  [ clientIds, rootClientId ]
76
87
  );
88
+ const { getBlocksByClientId } = useSelect( blockEditorStore );
89
+ const getContent = useCallback(
90
+ () => serialize( getBlocksByClientId( clientIds ) ),
91
+ [ getBlocksByClientId, clientIds ]
92
+ );
77
93
 
78
94
  if ( ! canConvert ) {
79
95
  return null;
80
96
  }
81
97
 
82
98
  const handleSuccess = ( { pattern } ) => {
99
+ const newBlock = createBlock( 'core/block', {
100
+ ref: pattern.id,
101
+ } );
102
+
103
+ replaceBlocks( clientIds, newBlock );
104
+ setEditingPattern( newBlock.clientId, true );
105
+
83
106
  createSuccessNotice(
84
107
  pattern.wp_pattern_sync_status === 'unsynced'
85
108
  ? sprintf(
@@ -111,7 +134,7 @@ export default function PatternConvertButton( { clientIds, rootClientId } ) {
111
134
  </MenuItem>
112
135
  { isModalOpen && (
113
136
  <CreatePatternModal
114
- clientIds={ clientIds }
137
+ content={ getContent }
115
138
  onSuccess={ ( pattern ) => {
116
139
  handleSuccess( pattern );
117
140
  } }
@@ -12,7 +12,8 @@ import { store as coreStore } from '@wordpress/core-data';
12
12
  /**
13
13
  * Internal dependencies
14
14
  */
15
- import { store as editorStore } from '../store';
15
+ import { store as patternsStore } from '../store';
16
+ import { unlock } from '../lock-unlock';
16
17
 
17
18
  function PatternsManageButton( { clientId } ) {
18
19
  const { canRemove, isVisible, innerBlockCount, managePatternsUrl } =
@@ -51,8 +52,11 @@ function PatternsManageButton( { clientId } ) {
51
52
  [ clientId ]
52
53
  );
53
54
 
54
- const { __experimentalConvertSyncedPatternToStatic: convertBlockToStatic } =
55
- useDispatch( editorStore );
55
+ // Ignore reason: false positive of the lint rule.
56
+ // eslint-disable-next-line @wordpress/no-unused-vars-before-return
57
+ const { convertSyncedPatternToStatic } = unlock(
58
+ useDispatch( patternsStore )
59
+ );
56
60
 
57
61
  if ( ! isVisible ) {
58
62
  return null;
@@ -64,7 +68,9 @@ function PatternsManageButton( { clientId } ) {
64
68
  { __( 'Manage patterns' ) }
65
69
  </MenuItem>
66
70
  { canRemove && (
67
- <MenuItem onClick={ () => convertBlockToStatic( clientId ) }>
71
+ <MenuItem
72
+ onClick={ () => convertSyncedPatternToStatic( clientId ) }
73
+ >
68
74
  { innerBlockCount > 1
69
75
  ? __( 'Detach patterns' )
70
76
  : __( 'Detach pattern' ) }
@@ -1,3 +1,10 @@
1
1
  .patterns-menu-items__convert-modal {
2
2
  z-index: z-index(".patterns-menu-items__convert-modal");
3
+ .patterns-menu-items__convert-modal-categories {
4
+ max-width: 300px;
5
+ }
6
+ }
7
+
8
+ .patterns-create-modal__name-input input[type="text"] {
9
+ min-height: 34px;
3
10
  }
@@ -0,0 +1,17 @@
1
+ export const PATTERN_TYPES = {
2
+ theme: 'pattern',
3
+ user: 'wp_block',
4
+ };
5
+
6
+ export const PATTERN_DEFAULT_CATEGORY = 'all-patterns';
7
+ export const PATTERN_USER_CATEGORY = 'my-patterns';
8
+ export const PATTERN_CORE_SOURCES = [
9
+ 'core',
10
+ 'pattern-directory/core',
11
+ 'pattern-directory/featured',
12
+ 'pattern-directory/theme',
13
+ ];
14
+ export const PATTERN_SYNC_TYPES = {
15
+ full: 'fully',
16
+ unsynced: 'unsynced',
17
+ };
package/src/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * Internal dependencies
3
3
  */
4
- import './store';
5
-
4
+ export { store } from './store';
6
5
  export * from './private-apis';
@@ -4,9 +4,21 @@
4
4
  import { lock } from './lock-unlock';
5
5
  import CreatePatternModal from './components/create-pattern-modal';
6
6
  import PatternsMenuItems from './components';
7
+ import {
8
+ PATTERN_TYPES,
9
+ PATTERN_DEFAULT_CATEGORY,
10
+ PATTERN_USER_CATEGORY,
11
+ PATTERN_CORE_SOURCES,
12
+ PATTERN_SYNC_TYPES,
13
+ } from './constants';
7
14
 
8
15
  export const privateApis = {};
9
16
  lock( privateApis, {
10
17
  CreatePatternModal,
11
18
  PatternsMenuItems,
19
+ PATTERN_TYPES,
20
+ PATTERN_DEFAULT_CATEGORY,
21
+ PATTERN_USER_CATEGORY,
22
+ PATTERN_CORE_SOURCES,
23
+ PATTERN_SYNC_TYPES,
12
24
  } );
@@ -2,22 +2,28 @@
2
2
  * WordPress dependencies
3
3
  */
4
4
 
5
- import { parse, serialize, createBlock } from '@wordpress/blocks';
5
+ import { parse } from '@wordpress/blocks';
6
6
  import { store as coreStore } from '@wordpress/core-data';
7
7
  import { store as blockEditorStore } from '@wordpress/block-editor';
8
8
 
9
+ /**
10
+ * Internal dependencies
11
+ */
12
+ import { PATTERN_SYNC_TYPES } from '../constants';
13
+
9
14
  /**
10
15
  * Returns a generator converting one or more static blocks into a pattern, or creating a new empty pattern.
11
16
  *
12
- * @param {string} title Pattern title.
13
- * @param {'full'|'unsynced'} syncType They way block is synced, 'full' or 'unsynced'.
14
- * @param {string[]|undefined} clientIds Optional client IDs of blocks to convert to pattern.
17
+ * @param {string} title Pattern title.
18
+ * @param {'full'|'unsynced'} syncType They way block is synced, 'full' or 'unsynced'.
19
+ * @param {string|undefined} [content] Optional serialized content of blocks to convert to pattern.
20
+ * @param {number[]|undefined} [categories] Ids of any selected categories.
15
21
  */
16
- export const __experimentalCreatePattern =
17
- ( title, syncType, clientIds ) =>
18
- async ( { registry, dispatch } ) => {
22
+ export const createPattern =
23
+ ( title, syncType, content, categories ) =>
24
+ async ( { registry } ) => {
19
25
  const meta =
20
- syncType === 'unsynced'
26
+ syncType === PATTERN_SYNC_TYPES.unsynced
21
27
  ? {
22
28
  wp_pattern_sync_status: syncType,
23
29
  }
@@ -25,33 +31,55 @@ export const __experimentalCreatePattern =
25
31
 
26
32
  const reusableBlock = {
27
33
  title,
28
- content: clientIds
29
- ? serialize(
30
- registry
31
- .select( blockEditorStore )
32
- .getBlocksByClientId( clientIds )
33
- )
34
- : undefined,
34
+ content,
35
35
  status: 'publish',
36
36
  meta,
37
+ wp_pattern_category: categories,
37
38
  };
38
39
 
39
40
  const updatedRecord = await registry
40
41
  .dispatch( coreStore )
41
42
  .saveEntityRecord( 'postType', 'wp_block', reusableBlock );
42
43
 
43
- if ( syncType === 'unsynced' || ! clientIds ) {
44
- return updatedRecord;
44
+ return updatedRecord;
45
+ };
46
+
47
+ /**
48
+ * Create a pattern from a JSON file.
49
+ * @param {File} file The JSON file instance of the pattern.
50
+ * @param {number[]|undefined} [categories] Ids of any selected categories.
51
+ */
52
+ export const createPatternFromFile =
53
+ ( file, categories ) =>
54
+ async ( { dispatch } ) => {
55
+ const fileContent = await file.text();
56
+ /** @type {import('./types').PatternJSON} */
57
+ let parsedContent;
58
+ try {
59
+ parsedContent = JSON.parse( fileContent );
60
+ } catch ( e ) {
61
+ throw new Error( 'Invalid JSON file' );
62
+ }
63
+ if (
64
+ parsedContent.__file !== 'wp_block' ||
65
+ ! parsedContent.title ||
66
+ ! parsedContent.content ||
67
+ typeof parsedContent.title !== 'string' ||
68
+ typeof parsedContent.content !== 'string' ||
69
+ ( parsedContent.syncStatus &&
70
+ typeof parsedContent.syncStatus !== 'string' )
71
+ ) {
72
+ throw new Error( 'Invalid Pattern JSON file' );
45
73
  }
46
74
 
47
- const newBlock = createBlock( 'core/block', {
48
- ref: updatedRecord.id,
49
- } );
50
- registry
51
- .dispatch( blockEditorStore )
52
- .replaceBlocks( clientIds, newBlock );
53
- dispatch.__experimentalSetEditingPattern( newBlock.clientId, true );
54
- return updatedRecord;
75
+ const pattern = await dispatch.createPattern(
76
+ parsedContent.title,
77
+ parsedContent.syncStatus,
78
+ parsedContent.content,
79
+ categories
80
+ );
81
+
82
+ return pattern;
55
83
  };
56
84
 
57
85
  /**
@@ -59,7 +87,7 @@ export const __experimentalCreatePattern =
59
87
  *
60
88
  * @param {string} clientId The client ID of the block to attach.
61
89
  */
62
- export const __experimentalConvertSyncedPatternToStatic =
90
+ export const convertSyncedPatternToStatic =
63
91
  ( clientId ) =>
64
92
  ( { registry } ) => {
65
93
  const oldBlock = registry
@@ -90,7 +118,7 @@ export const __experimentalConvertSyncedPatternToStatic =
90
118
  * @param {boolean} isEditing Whether the block should be in editing state.
91
119
  * @return {Object} Action descriptor.
92
120
  */
93
- export function __experimentalSetEditingPattern( clientId, isEditing ) {
121
+ export function setEditingPattern( clientId, isEditing ) {
94
122
  return {
95
123
  type: 'SET_EDITING_PATTERN',
96
124
  clientId,
@@ -10,6 +10,7 @@ import reducer from './reducer';
10
10
  import * as actions from './actions';
11
11
  import { STORE_NAME } from './constants';
12
12
  import * as selectors from './selectors';
13
+ import { unlock } from '../lock-unlock';
13
14
 
14
15
  /**
15
16
  * Post editor data store configuration.
@@ -20,8 +21,6 @@ import * as selectors from './selectors';
20
21
  */
21
22
  export const storeConfig = {
22
23
  reducer,
23
- selectors,
24
- actions,
25
24
  };
26
25
 
27
26
  /**
@@ -36,3 +35,5 @@ export const store = createReduxStore( STORE_NAME, {
36
35
  } );
37
36
 
38
37
  register( store );
38
+ unlock( store ).registerPrivateActions( actions );
39
+ unlock( store ).registerPrivateSelectors( selectors );
@@ -5,6 +5,6 @@
5
5
  * @param {number} clientId the clientID of the block.
6
6
  * @return {boolean} Whether the pattern is in the editing state.
7
7
  */
8
- export function __experimentalIsEditingPattern( state, clientId ) {
8
+ export function isEditingPattern( state, clientId ) {
9
9
  return state.isEditingPattern[ clientId ];
10
10
  }