-
Notifications
You must be signed in to change notification settings - Fork 4.3k
/
index.js
202 lines (192 loc) · 5.82 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
/**
* WordPress dependencies
*/
import { Button, Flex, FlexItem } from '@wordpress/components';
import { __, _n, sprintf } from '@wordpress/i18n';
import {
useCallback,
useRef,
createInterpolateElement,
} from '@wordpress/element';
import {
__experimentalUseDialog as useDialog,
useInstanceId,
} from '@wordpress/compose';
import { useDispatch } from '@wordpress/data';
/**
* Internal dependencies
*/
import EntityTypeList from './entity-type-list';
import { useIsDirty } from './hooks/use-is-dirty';
import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';
function identity( values ) {
return values;
}
/**
* Renders the component for managing saved states of entities.
*
* @param {Object} props The component props.
* @param {Function} props.close The function to close the dialog.
* @param {Function} props.renderDialog The function to render the dialog.
*
* @return {React.ReactNode} The rendered component.
*/
export default function EntitiesSavedStates( {
close,
renderDialog = undefined,
} ) {
const isDirtyProps = useIsDirty();
return (
<EntitiesSavedStatesExtensible
close={ close }
renderDialog={ renderDialog }
{ ...isDirtyProps }
/>
);
}
/**
* Renders a panel for saving entities with dirty records.
*
* @param {Object} props The component props.
* @param {string} props.additionalPrompt Additional prompt to display.
* @param {Function} props.close Function to close the panel.
* @param {Function} props.onSave Function to call when saving entities.
* @param {boolean} props.saveEnabled Flag indicating if save is enabled.
* @param {string} props.saveLabel Label for the save button.
* @param {Function} props.renderDialog Function to render a custom dialog.
* @param {Array} props.dirtyEntityRecords Array of dirty entity records.
* @param {boolean} props.isDirty Flag indicating if there are dirty entities.
* @param {Function} props.setUnselectedEntities Function to set unselected entities.
* @param {Array} props.unselectedEntities Array of unselected entities.
*
* @return {React.ReactNode} The rendered component.
*/
export function EntitiesSavedStatesExtensible( {
additionalPrompt = undefined,
close,
onSave = identity,
saveEnabled: saveEnabledProp = undefined,
saveLabel = __( 'Save' ),
renderDialog = undefined,
dirtyEntityRecords,
isDirty,
setUnselectedEntities,
unselectedEntities,
} ) {
const saveButtonRef = useRef();
const { saveDirtyEntities } = unlock( useDispatch( editorStore ) );
// To group entities by type.
const partitionedSavables = dirtyEntityRecords.reduce( ( acc, record ) => {
const { name } = record;
if ( ! acc[ name ] ) {
acc[ name ] = [];
}
acc[ name ].push( record );
return acc;
}, {} );
// Sort entity groups.
const {
site: siteSavables,
wp_template: templateSavables,
wp_template_part: templatePartSavables,
...contentSavables
} = partitionedSavables;
const sortedPartitionedSavables = [
siteSavables,
templateSavables,
templatePartSavables,
...Object.values( contentSavables ),
].filter( Array.isArray );
const saveEnabled = saveEnabledProp ?? isDirty;
// Explicitly define this with no argument passed. Using `close` on
// its own will use the event object in place of the expected saved entities.
const dismissPanel = useCallback( () => close(), [ close ] );
const [ saveDialogRef, saveDialogProps ] = useDialog( {
onClose: () => dismissPanel(),
} );
const dialogLabel = useInstanceId( EntitiesSavedStatesExtensible, 'label' );
const dialogDescription = useInstanceId(
EntitiesSavedStatesExtensible,
'description'
);
return (
<div
ref={ saveDialogRef }
{ ...saveDialogProps }
className="entities-saved-states__panel"
role={ renderDialog ? 'dialog' : undefined }
aria-labelledby={ renderDialog ? dialogLabel : undefined }
aria-describedby={ renderDialog ? dialogDescription : undefined }
>
<Flex className="entities-saved-states__panel-header" gap={ 2 }>
<FlexItem
isBlock
as={ Button }
variant="secondary"
size="compact"
onClick={ dismissPanel }
>
{ __( 'Cancel' ) }
</FlexItem>
<FlexItem
isBlock
as={ Button }
ref={ saveButtonRef }
variant="primary"
size="compact"
disabled={ ! saveEnabled }
accessibleWhenDisabled
onClick={ () =>
saveDirtyEntities( {
onSave,
dirtyEntityRecords,
entitiesToSkip: unselectedEntities,
close,
} )
}
className="editor-entities-saved-states__save-button"
>
{ saveLabel }
</FlexItem>
</Flex>
<div className="entities-saved-states__text-prompt">
<div
className="entities-saved-states__text-prompt--header-wrapper"
id={ renderDialog ? dialogLabel : undefined }
>
<strong className="entities-saved-states__text-prompt--header">
{ __( 'Are you ready to save?' ) }
</strong>
{ additionalPrompt }
</div>
<p id={ renderDialog ? dialogDescription : undefined }>
{ isDirty
? createInterpolateElement(
sprintf(
/* translators: %d: number of site changes waiting to be saved. */
_n(
'There is <strong>%d site change</strong> waiting to be saved.',
'There are <strong>%d site changes</strong> waiting to be saved.',
dirtyEntityRecords.length
),
dirtyEntityRecords.length
),
{ strong: <strong /> }
)
: __( 'Select the items you want to save.' ) }
</p>
</div>
{ sortedPartitionedSavables.map( ( list ) => {
return (
<EntityTypeList
key={ list[ 0 ].name }
list={ list }
unselectedEntities={ unselectedEntities }
setUnselectedEntities={ setUnselectedEntities }
/>
);
} ) }
</div>
);
}