@strapi/admin 4.10.2-alpha.0 → 4.10.2
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.
- package/build/6858.56d4d528.chunk.js +50 -0
- package/build/{7725.f717acdd.chunk.js → 7725.1633e06f.chunk.js} +17 -17
- package/build/9703.e590889d.chunk.js +1 -0
- package/build/{Admin-authenticatedApp.2fab70e4.chunk.js → Admin-authenticatedApp.a8373103.chunk.js} +2 -2
- package/build/{Admin_settingsPage.56e9641c.chunk.js → Admin_settingsPage.5e045f42.chunk.js} +1 -1
- package/build/content-manager.d28eb183.chunk.js +1111 -0
- package/build/index.html +1 -1
- package/build/{main.964e7203.js → main.41970e4c.js} +65 -65
- package/build/review-workflows-settings.7c0b4e73.chunk.js +61 -0
- package/build/{runtime~main.c9593d59.js → runtime~main.c20330f1.js} +1 -1
- package/ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/ReviewWorkflowsStageEE.js +11 -5
- package/ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumn.js +7 -3
- package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +16 -2
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/ReviewWorkflows.js +34 -25
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/actions/index.js +11 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/StageDragPreview/StageDragPreview.js +45 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/StageDragPreview/index.js +1 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/Stage.js +287 -63
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/components/OptionColor/OptionColor.js +27 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/components/OptionColor/index.js +1 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/components/SingleValueColor/SingleValueColor.js +31 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/components/SingleValueColor/index.js +1 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stages.js +2 -1
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/constants.js +26 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/reducer/index.js +36 -2
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/utils/colors.js +33 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/utils/getWorkflowValidationSchema.js +9 -0
- package/ee/server/constants/workflows.js +1 -0
- package/ee/server/content-types/workflow-stage/index.js +7 -0
- package/ee/server/controllers/authentication/middlewares.js +1 -1
- package/ee/server/migrations/review-workflows-stages-color.js +20 -0
- package/ee/server/register.js +2 -0
- package/ee/server/services/review-workflows/stages.js +1 -1
- package/ee/server/validation/review-workflows.js +7 -1
- package/package.json +10 -10
- package/build/content-manager.a0ff6cad.chunk.js +0 -1111
- package/build/review-workflows-settings.089d7ec5.chunk.js +0 -106
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { useField } from 'formik';
|
|
4
4
|
import { useIntl } from 'react-intl';
|
|
@@ -7,84 +7,308 @@ import {
|
|
|
7
7
|
Accordion,
|
|
8
8
|
AccordionToggle,
|
|
9
9
|
AccordionContent,
|
|
10
|
+
Box,
|
|
11
|
+
Field,
|
|
12
|
+
FieldLabel,
|
|
13
|
+
FieldError,
|
|
14
|
+
Flex,
|
|
10
15
|
Grid,
|
|
11
16
|
GridItem,
|
|
12
17
|
IconButton,
|
|
13
18
|
TextInput,
|
|
19
|
+
VisuallyHidden,
|
|
14
20
|
} from '@strapi/design-system';
|
|
15
|
-
import { useTracking } from '@strapi/helper-plugin';
|
|
16
|
-
import { Trash } from '@strapi/icons';
|
|
21
|
+
import { ReactSelect, useTracking } from '@strapi/helper-plugin';
|
|
22
|
+
import { Drag, Trash } from '@strapi/icons';
|
|
17
23
|
|
|
18
|
-
import { deleteStage, updateStage } from '../../../actions';
|
|
24
|
+
import { deleteStage, updateStagePosition, updateStage } from '../../../actions';
|
|
25
|
+
import { getAvailableStageColors } from '../../../utils/colors';
|
|
26
|
+
import { OptionColor } from './components/OptionColor';
|
|
27
|
+
import { SingleValueColor } from './components/SingleValueColor';
|
|
28
|
+
import { useDragAndDrop } from '../../../../../../../../../admin/src/content-manager/hooks';
|
|
29
|
+
import { composeRefs } from '../../../../../../../../../admin/src/content-manager/utils';
|
|
30
|
+
import { DRAG_DROP_TYPES } from '../../../constants';
|
|
19
31
|
|
|
20
|
-
|
|
32
|
+
const AVAILABLE_COLORS = getAvailableStageColors();
|
|
33
|
+
|
|
34
|
+
function StageDropPreview() {
|
|
35
|
+
return (
|
|
36
|
+
<Box
|
|
37
|
+
background="primary100"
|
|
38
|
+
borderStyle="dashed"
|
|
39
|
+
borderColor="primary600"
|
|
40
|
+
borderWidth="1px"
|
|
41
|
+
display="block"
|
|
42
|
+
hasRadius
|
|
43
|
+
padding={6}
|
|
44
|
+
shadow="tableShadow"
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function Stage({
|
|
50
|
+
id,
|
|
51
|
+
index,
|
|
52
|
+
canDelete,
|
|
53
|
+
canReorder,
|
|
54
|
+
isOpen: isOpenDefault = false,
|
|
55
|
+
stagesCount,
|
|
56
|
+
}) {
|
|
57
|
+
/**
|
|
58
|
+
*
|
|
59
|
+
* @param {number} index
|
|
60
|
+
* @returns {string}
|
|
61
|
+
*/
|
|
62
|
+
const getItemPos = (index) => `${index + 1} of ${stagesCount}`;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
*
|
|
66
|
+
* @param {number} index
|
|
67
|
+
* @returns {void}
|
|
68
|
+
*/
|
|
69
|
+
const handleGrabStage = (index) => {
|
|
70
|
+
setLiveText(
|
|
71
|
+
formatMessage(
|
|
72
|
+
{
|
|
73
|
+
id: 'dnd.grab-item',
|
|
74
|
+
defaultMessage: `{item}, grabbed. Current position in list: {position}. Press up and down arrow to change position, Spacebar to drop, Escape to cancel.`,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
item: nameField.value,
|
|
78
|
+
position: getItemPos(index),
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
*
|
|
86
|
+
* @param {number} index
|
|
87
|
+
* @returns {void}
|
|
88
|
+
*/
|
|
89
|
+
const handleDropStage = (index) => {
|
|
90
|
+
setLiveText(
|
|
91
|
+
formatMessage(
|
|
92
|
+
{
|
|
93
|
+
id: 'dnd.drop-item',
|
|
94
|
+
defaultMessage: `{item}, dropped. Final position in list: {position}.`,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
item: nameField.value,
|
|
98
|
+
position: getItemPos(index),
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
*
|
|
106
|
+
* @param {number} index
|
|
107
|
+
* @returns {void}
|
|
108
|
+
*/
|
|
109
|
+
const handleCancelDragStage = () => {
|
|
110
|
+
setLiveText(
|
|
111
|
+
formatMessage(
|
|
112
|
+
{
|
|
113
|
+
id: 'dnd.cancel-item',
|
|
114
|
+
defaultMessage: '{item}, dropped. Re-order cancelled.',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
item: nameField.value,
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const handleMoveStage = (newIndex, oldIndex) => {
|
|
124
|
+
setLiveText(
|
|
125
|
+
formatMessage(
|
|
126
|
+
{
|
|
127
|
+
id: 'dnd.reorder',
|
|
128
|
+
defaultMessage: '{item}, moved. New position in list: {position}.',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
item: nameField.value,
|
|
132
|
+
position: getItemPos(newIndex),
|
|
133
|
+
}
|
|
134
|
+
)
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
dispatch(updateStagePosition(oldIndex, newIndex));
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const [liveText, setLiveText] = React.useState(null);
|
|
21
141
|
const { formatMessage } = useIntl();
|
|
22
142
|
const { trackUsage } = useTracking();
|
|
23
|
-
const [isOpen, setIsOpen] = useState(isOpenDefault);
|
|
24
|
-
const fieldIdentifier = `stages.${index}.name`;
|
|
25
|
-
const [field, meta] = useField(fieldIdentifier);
|
|
26
143
|
const dispatch = useDispatch();
|
|
144
|
+
const [isOpen, setIsOpen] = React.useState(isOpenDefault);
|
|
145
|
+
const [nameField, nameMeta] = useField(`stages.${index}.name`);
|
|
146
|
+
const [colorField, colorMeta] = useField(`stages.${index}.color`);
|
|
147
|
+
const [{ handlerId, isDragging, handleKeyDown }, stageRef, dropRef, dragRef] = useDragAndDrop(
|
|
148
|
+
canReorder,
|
|
149
|
+
{
|
|
150
|
+
index,
|
|
151
|
+
item: {
|
|
152
|
+
name: nameField.value,
|
|
153
|
+
},
|
|
154
|
+
onGrabItem: handleGrabStage,
|
|
155
|
+
onDropItem: handleDropStage,
|
|
156
|
+
onMoveItem: handleMoveStage,
|
|
157
|
+
onCancel: handleCancelDragStage,
|
|
158
|
+
type: DRAG_DROP_TYPES.STAGE,
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const composedRef = composeRefs(stageRef, dropRef);
|
|
163
|
+
|
|
164
|
+
const colorOptions = AVAILABLE_COLORS.map(({ hex, name }) => ({
|
|
165
|
+
value: hex,
|
|
166
|
+
label: formatMessage(
|
|
167
|
+
{
|
|
168
|
+
id: 'Settings.review-workflows.stage.color.name',
|
|
169
|
+
defaultMessage: '{name}',
|
|
170
|
+
},
|
|
171
|
+
{ name }
|
|
172
|
+
),
|
|
173
|
+
color: hex,
|
|
174
|
+
}));
|
|
175
|
+
// TODO: the .toUpperCase() conversion can be removed once the hex code is normalized in
|
|
176
|
+
// the admin API
|
|
177
|
+
const colorValue = colorOptions.find(({ value }) => value === colorField.value.toUpperCase());
|
|
27
178
|
|
|
28
179
|
return (
|
|
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
|
-
|
|
180
|
+
<Box ref={composedRef}>
|
|
181
|
+
{liveText && <VisuallyHidden aria-live="assertive">{liveText}</VisuallyHidden>}
|
|
182
|
+
|
|
183
|
+
{isDragging ? (
|
|
184
|
+
<StageDropPreview />
|
|
185
|
+
) : (
|
|
186
|
+
<Accordion
|
|
187
|
+
size="S"
|
|
188
|
+
variant="primary"
|
|
189
|
+
onToggle={() => {
|
|
190
|
+
setIsOpen(!isOpen);
|
|
191
|
+
|
|
192
|
+
if (!isOpen) {
|
|
193
|
+
trackUsage('willEditStage');
|
|
194
|
+
}
|
|
195
|
+
}}
|
|
196
|
+
expanded={isOpen}
|
|
197
|
+
shadow="tableShadow"
|
|
198
|
+
>
|
|
199
|
+
<AccordionToggle
|
|
200
|
+
title={nameField.value}
|
|
201
|
+
togglePosition="left"
|
|
202
|
+
action={
|
|
203
|
+
<>
|
|
204
|
+
{canDelete && (
|
|
205
|
+
<IconButton
|
|
206
|
+
background="transparent"
|
|
207
|
+
icon={<Trash />}
|
|
208
|
+
label={formatMessage({
|
|
209
|
+
id: 'Settings.review-workflows.stage.delete',
|
|
210
|
+
defaultMessage: 'Delete stage',
|
|
211
|
+
})}
|
|
212
|
+
noBorder
|
|
213
|
+
onClick={() => dispatch(deleteStage(id))}
|
|
214
|
+
/>
|
|
215
|
+
)}
|
|
216
|
+
|
|
217
|
+
<IconButton
|
|
218
|
+
background="transparent"
|
|
219
|
+
forwardedAs="div"
|
|
220
|
+
role="button"
|
|
221
|
+
noBorder
|
|
222
|
+
tabIndex={0}
|
|
223
|
+
data-handler-id={handlerId}
|
|
224
|
+
ref={dragRef}
|
|
225
|
+
label={formatMessage({
|
|
226
|
+
id: 'Settings.review-workflows.stage.drag',
|
|
227
|
+
defaultMessage: 'Drag',
|
|
228
|
+
})}
|
|
229
|
+
onClick={(e) => e.stopPropagation()}
|
|
230
|
+
onKeyDown={handleKeyDown}
|
|
231
|
+
>
|
|
232
|
+
<Drag />
|
|
233
|
+
</IconButton>
|
|
234
|
+
</>
|
|
235
|
+
}
|
|
236
|
+
/>
|
|
237
|
+
<AccordionContent padding={6} background="neutral0" hasRadius>
|
|
238
|
+
<Grid gap={4}>
|
|
239
|
+
<GridItem col={6}>
|
|
240
|
+
<TextInput
|
|
241
|
+
{...nameField}
|
|
242
|
+
id={nameField.name}
|
|
243
|
+
label={formatMessage({
|
|
244
|
+
id: 'Settings.review-workflows.stage.name.label',
|
|
245
|
+
defaultMessage: 'Stage name',
|
|
246
|
+
})}
|
|
247
|
+
error={nameMeta.error ?? false}
|
|
248
|
+
onChange={(event) => {
|
|
249
|
+
nameField.onChange(event);
|
|
250
|
+
dispatch(updateStage(id, { name: event.target.value }));
|
|
251
|
+
}}
|
|
252
|
+
required
|
|
253
|
+
/>
|
|
254
|
+
</GridItem>
|
|
255
|
+
|
|
256
|
+
<GridItem col={6}>
|
|
257
|
+
<Field
|
|
258
|
+
error={colorMeta?.error ?? false}
|
|
259
|
+
name={colorField.name}
|
|
260
|
+
id={colorField.name}
|
|
261
|
+
required
|
|
262
|
+
>
|
|
263
|
+
<Flex direction="column" gap={1} alignItems="stretch">
|
|
264
|
+
<FieldLabel>
|
|
265
|
+
{formatMessage({
|
|
266
|
+
id: 'content-manager.reviewWorkflows.stage.color',
|
|
267
|
+
defaultMessage: 'Color',
|
|
268
|
+
})}
|
|
269
|
+
</FieldLabel>
|
|
270
|
+
|
|
271
|
+
<ReactSelect
|
|
272
|
+
components={{ Option: OptionColor, SingleValue: SingleValueColor }}
|
|
273
|
+
error={colorMeta?.error}
|
|
274
|
+
inputId={colorField.name}
|
|
275
|
+
name={colorField.name}
|
|
276
|
+
options={colorOptions}
|
|
277
|
+
onChange={({ value }) => {
|
|
278
|
+
colorField.onChange({ target: { value } });
|
|
279
|
+
dispatch(updateStage(id, { color: value }));
|
|
280
|
+
}}
|
|
281
|
+
// If no color was found in all the valid theme colors it means a user
|
|
282
|
+
// has set a custom value e.g. through the content API. In that case we
|
|
283
|
+
// display the custom color and a "Custom" label.
|
|
284
|
+
value={
|
|
285
|
+
colorValue ?? {
|
|
286
|
+
value: colorField.value,
|
|
287
|
+
label: formatMessage({
|
|
288
|
+
id: 'Settings.review-workflows.stage.color.name.custom',
|
|
289
|
+
defaultMessage: 'Custom',
|
|
290
|
+
}),
|
|
291
|
+
color: colorField.value,
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/>
|
|
295
|
+
|
|
296
|
+
<FieldError />
|
|
297
|
+
</Flex>
|
|
298
|
+
</Field>
|
|
299
|
+
</GridItem>
|
|
300
|
+
</Grid>
|
|
301
|
+
</AccordionContent>
|
|
302
|
+
</Accordion>
|
|
303
|
+
)}
|
|
304
|
+
</Box>
|
|
81
305
|
);
|
|
82
306
|
}
|
|
83
307
|
|
|
84
|
-
export { Stage };
|
|
85
|
-
|
|
86
308
|
Stage.propTypes = PropTypes.shape({
|
|
87
309
|
id: PropTypes.number.isRequired,
|
|
88
|
-
|
|
310
|
+
color: PropTypes.string.isRequired,
|
|
89
311
|
canDelete: PropTypes.bool.isRequired,
|
|
312
|
+
canReorder: PropTypes.bool.isRequired,
|
|
313
|
+
stagesCount: PropTypes.number.isRequired,
|
|
90
314
|
}).isRequired;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { components } from 'react-select';
|
|
4
|
+
import { Flex, Typography } from '@strapi/design-system';
|
|
5
|
+
|
|
6
|
+
export function OptionColor({ children, ...props }) {
|
|
7
|
+
const { color } = props.data;
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<components.Option {...props}>
|
|
11
|
+
<Flex alignItems="center" gap={2}>
|
|
12
|
+
<Flex height={2} background={color} hasRadius shrink={0} width={2} />
|
|
13
|
+
|
|
14
|
+
<Typography textColor="neutral800" ellipsis>
|
|
15
|
+
{children}
|
|
16
|
+
</Typography>
|
|
17
|
+
</Flex>
|
|
18
|
+
</components.Option>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
OptionColor.propTypes = {
|
|
23
|
+
children: PropTypes.node.isRequired,
|
|
24
|
+
data: PropTypes.shape({
|
|
25
|
+
color: PropTypes.string,
|
|
26
|
+
}).isRequired,
|
|
27
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './OptionColor';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { components } from 'react-select';
|
|
4
|
+
import { Flex, Typography } from '@strapi/design-system';
|
|
5
|
+
|
|
6
|
+
export function SingleValueColor({ children, ...props }) {
|
|
7
|
+
const { color } = props.data;
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<components.SingleValue {...props}>
|
|
11
|
+
<Flex alignItems="center" gap={2}>
|
|
12
|
+
<Flex height={2} background={color} hasRadius shrink={0} width={2} />
|
|
13
|
+
|
|
14
|
+
<Typography textColor="neutral800" ellipsis>
|
|
15
|
+
{children}
|
|
16
|
+
</Typography>
|
|
17
|
+
</Flex>
|
|
18
|
+
</components.SingleValue>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
SingleValueColor.defaultProps = {
|
|
23
|
+
children: null,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
SingleValueColor.propTypes = {
|
|
27
|
+
children: PropTypes.node,
|
|
28
|
+
data: PropTypes.shape({
|
|
29
|
+
color: PropTypes.string,
|
|
30
|
+
}).isRequired,
|
|
31
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './SingleValueColor';
|
|
@@ -45,11 +45,12 @@ function Stages({ stages }) {
|
|
|
45
45
|
return (
|
|
46
46
|
<Box key={`stage-${id}`} as="li">
|
|
47
47
|
<Stage
|
|
48
|
-
{...stage}
|
|
49
48
|
id={id}
|
|
50
49
|
index={index}
|
|
51
50
|
canDelete={stages.length > 1}
|
|
52
51
|
isOpen={!stage.id}
|
|
52
|
+
canReorder={stages.length > 1}
|
|
53
|
+
stagesCount={stages.length}
|
|
53
54
|
/>
|
|
54
55
|
</Box>
|
|
55
56
|
);
|
|
@@ -1,6 +1,32 @@
|
|
|
1
|
+
import { lightTheme } from '@strapi/design-system';
|
|
2
|
+
|
|
1
3
|
export const REDUX_NAMESPACE = 'settings_review-workflows';
|
|
2
4
|
|
|
3
5
|
export const ACTION_SET_WORKFLOWS = `Settings/Review_Workflows/SET_WORKFLOWS`;
|
|
4
6
|
export const ACTION_DELETE_STAGE = `Settings/Review_Workflows/WORKFLOW_DELETE_STAGE`;
|
|
5
7
|
export const ACTION_ADD_STAGE = `Settings/Review_Workflows/WORKFLOW_ADD_STAGE`;
|
|
6
8
|
export const ACTION_UPDATE_STAGE = `Settings/Review_Workflows/WORKFLOW_UPDATE_STAGE`;
|
|
9
|
+
export const ACTION_UPDATE_STAGE_POSITION = `Settings/Review_Workflows/WORKFLOW_UPDATE_STAGE_POSITION`;
|
|
10
|
+
|
|
11
|
+
export const STAGE_COLORS = {
|
|
12
|
+
primary600: 'Blue',
|
|
13
|
+
primary200: 'Lilac',
|
|
14
|
+
alternative600: 'Violet',
|
|
15
|
+
alternative200: 'Lavender',
|
|
16
|
+
success600: 'Green',
|
|
17
|
+
success200: 'Pale Green',
|
|
18
|
+
danger500: 'Cherry',
|
|
19
|
+
danger200: 'Pink',
|
|
20
|
+
warning600: 'Orange',
|
|
21
|
+
warning200: 'Yellow',
|
|
22
|
+
secondary600: 'Teal',
|
|
23
|
+
secondary200: 'Baby Blue',
|
|
24
|
+
neutral400: 'Gray',
|
|
25
|
+
neutral0: 'White',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const STAGE_COLOR_DEFAULT = lightTheme.colors.primary600;
|
|
29
|
+
|
|
30
|
+
export const DRAG_DROP_TYPES = {
|
|
31
|
+
STAGE: 'stage',
|
|
32
|
+
};
|
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
ACTION_DELETE_STAGE,
|
|
7
7
|
ACTION_ADD_STAGE,
|
|
8
8
|
ACTION_UPDATE_STAGE,
|
|
9
|
+
ACTION_UPDATE_STAGE_POSITION,
|
|
10
|
+
STAGE_COLOR_DEFAULT,
|
|
9
11
|
} from '../constants';
|
|
10
12
|
|
|
11
13
|
export const initialState = {
|
|
@@ -29,8 +31,18 @@ export function reducer(state = initialState, action) {
|
|
|
29
31
|
|
|
30
32
|
draft.status = status;
|
|
31
33
|
|
|
32
|
-
if (workflows) {
|
|
33
|
-
|
|
34
|
+
if (workflows?.length > 0) {
|
|
35
|
+
let defaultWorkflow = workflows[0];
|
|
36
|
+
|
|
37
|
+
// A safety net in case a stage does not have a color assigned;
|
|
38
|
+
// this normallly should not happen
|
|
39
|
+
defaultWorkflow = {
|
|
40
|
+
...defaultWorkflow,
|
|
41
|
+
stages: defaultWorkflow.stages.map((stage) => ({
|
|
42
|
+
...stage,
|
|
43
|
+
color: stage?.color ?? STAGE_COLOR_DEFAULT,
|
|
44
|
+
})),
|
|
45
|
+
};
|
|
34
46
|
|
|
35
47
|
draft.serverState.workflows = workflows;
|
|
36
48
|
draft.serverState.currentWorkflow = defaultWorkflow;
|
|
@@ -69,6 +81,7 @@ export function reducer(state = initialState, action) {
|
|
|
69
81
|
|
|
70
82
|
draft.clientState.currentWorkflow.data.stages.push({
|
|
71
83
|
...payload,
|
|
84
|
+
color: payload?.color ?? STAGE_COLOR_DEFAULT,
|
|
72
85
|
__temp_key__: newTempKey,
|
|
73
86
|
});
|
|
74
87
|
|
|
@@ -91,6 +104,27 @@ export function reducer(state = initialState, action) {
|
|
|
91
104
|
break;
|
|
92
105
|
}
|
|
93
106
|
|
|
107
|
+
case ACTION_UPDATE_STAGE_POSITION: {
|
|
108
|
+
const {
|
|
109
|
+
currentWorkflow: {
|
|
110
|
+
data: { stages },
|
|
111
|
+
},
|
|
112
|
+
} = state.clientState;
|
|
113
|
+
const { newIndex, oldIndex } = payload;
|
|
114
|
+
|
|
115
|
+
if (newIndex >= 0 && newIndex < stages.length) {
|
|
116
|
+
const stage = stages[oldIndex];
|
|
117
|
+
let newStages = [...stages];
|
|
118
|
+
|
|
119
|
+
newStages.splice(oldIndex, 1);
|
|
120
|
+
newStages.splice(newIndex, 0, stage);
|
|
121
|
+
|
|
122
|
+
draft.clientState.currentWorkflow.data.stages = newStages;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
|
|
94
128
|
default:
|
|
95
129
|
break;
|
|
96
130
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { lightTheme } from '@strapi/design-system';
|
|
2
|
+
|
|
3
|
+
import { STAGE_COLORS } from '../constants';
|
|
4
|
+
|
|
5
|
+
export function getStageColorByHex(hex) {
|
|
6
|
+
// there are multiple colors with the same hex code in the design tokens. In order to find
|
|
7
|
+
// the correct one we have to find all matching colors and then check, which ones are usable
|
|
8
|
+
// for stages.
|
|
9
|
+
const themeColors = Object.entries(lightTheme.colors).filter(([, value]) => value === hex);
|
|
10
|
+
const themeColorName = themeColors.reduce((acc, [name]) => {
|
|
11
|
+
if (STAGE_COLORS?.[name]) {
|
|
12
|
+
acc = name;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return acc;
|
|
16
|
+
}, null);
|
|
17
|
+
|
|
18
|
+
if (!themeColorName) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
themeColorName,
|
|
24
|
+
name: STAGE_COLORS[themeColorName],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getAvailableStageColors() {
|
|
29
|
+
return Object.entries(STAGE_COLORS).map(([themeColorName, name]) => ({
|
|
30
|
+
hex: lightTheme.colors[themeColorName].toUpperCase(),
|
|
31
|
+
name,
|
|
32
|
+
}));
|
|
33
|
+
}
|
package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/utils/getWorkflowValidationSchema.js
CHANGED
|
@@ -19,6 +19,15 @@ export function getWorkflowValidationSchema({ formatMessage }) {
|
|
|
19
19
|
defaultMessage: 'Name can not be longer than 255 characters',
|
|
20
20
|
})
|
|
21
21
|
),
|
|
22
|
+
color: yup
|
|
23
|
+
.string()
|
|
24
|
+
.required(
|
|
25
|
+
formatMessage({
|
|
26
|
+
id: 'Settings.review-workflows.validation.stage.color',
|
|
27
|
+
defaultMessage: 'Color is required',
|
|
28
|
+
})
|
|
29
|
+
)
|
|
30
|
+
.matches(/^#(?:[0-9a-fA-F]{3}){1,2}$/i),
|
|
22
31
|
})
|
|
23
32
|
),
|
|
24
33
|
});
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { STAGE_DEFAULT_COLOR } = require('../../constants/workflows');
|
|
4
|
+
|
|
3
5
|
module.exports = {
|
|
4
6
|
schema: {
|
|
5
7
|
collectionName: 'strapi_workflows_stages',
|
|
@@ -24,6 +26,11 @@ module.exports = {
|
|
|
24
26
|
type: 'string',
|
|
25
27
|
configurable: false,
|
|
26
28
|
},
|
|
29
|
+
color: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
configurable: false,
|
|
32
|
+
default: STAGE_DEFAULT_COLOR,
|
|
33
|
+
},
|
|
27
34
|
workflow: {
|
|
28
35
|
type: 'relation',
|
|
29
36
|
target: 'admin::workflow',
|
|
@@ -95,7 +95,7 @@ const redirectWithAuth = (ctx) => {
|
|
|
95
95
|
params: { provider },
|
|
96
96
|
} = ctx;
|
|
97
97
|
const redirectUrls = utils.getPrefixedRedirectUrls();
|
|
98
|
-
const domain = strapi.config.get('
|
|
98
|
+
const domain = strapi.config.get('admin.auth.domain');
|
|
99
99
|
const { user } = ctx.state;
|
|
100
100
|
|
|
101
101
|
const jwt = getService('token').createJwtToken(user);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { STAGE_DEFAULT_COLOR } = require('../constants/workflows');
|
|
4
|
+
|
|
5
|
+
async function migrateReviewWorkflowStagesColor({ oldContentTypes, contentTypes }) {
|
|
6
|
+
// Look for CT's color attribute
|
|
7
|
+
const hadColor = !!oldContentTypes?.['admin::workflow-stage']?.attributes?.color;
|
|
8
|
+
const hasColor = !!contentTypes['admin::workflow-stage']?.attributes?.color;
|
|
9
|
+
|
|
10
|
+
// Add the default stage color if color attribute was added
|
|
11
|
+
if (!hadColor || hasColor) {
|
|
12
|
+
await strapi.query('admin::workflow-stage').updateMany({
|
|
13
|
+
data: {
|
|
14
|
+
color: STAGE_DEFAULT_COLOR,
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = migrateReviewWorkflowStagesColor;
|
package/ee/server/register.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { features } = require('@strapi/strapi/lib/utils/ee');
|
|
4
4
|
const executeCERegister = require('../../server/register');
|
|
5
5
|
const migrateAuditLogsTable = require('./migrations/audit-logs-table');
|
|
6
|
+
const migrateReviewWorkflowStagesColor = require('./migrations/review-workflows-stages-color');
|
|
6
7
|
const createAuditLogsService = require('./services/audit-logs');
|
|
7
8
|
const reviewWorkflowsMiddlewares = require('./middlewares/review-workflows');
|
|
8
9
|
const { getService } = require('./utils');
|
|
@@ -17,6 +18,7 @@ module.exports = async ({ strapi }) => {
|
|
|
17
18
|
await auditLogsService.register();
|
|
18
19
|
}
|
|
19
20
|
if (features.isEnabled('review-workflows')) {
|
|
21
|
+
strapi.hook('strapi::content-types.afterSync').register(migrateReviewWorkflowStagesColor);
|
|
20
22
|
const reviewWorkflowService = getService('review-workflows');
|
|
21
23
|
|
|
22
24
|
reviewWorkflowsMiddlewares.contentTypeMiddleware(strapi);
|