@strapi/admin 4.10.2-alpha.0 → 4.10.4

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 (121) hide show
  1. package/admin/src/content-manager/components/Wysiwyg/WysiwygNav.js +156 -155
  2. package/admin/src/content-manager/pages/ListSettingsView/{utils/excludedSortOptions.js → constants.js} +1 -1
  3. package/admin/src/content-manager/pages/ListSettingsView/index.js +33 -36
  4. package/admin/src/pages/Admin/index.js +3 -0
  5. package/admin/src/pages/AuthPage/components/Register/index.js +1 -1
  6. package/admin/src/pages/MarketplacePage/components/NpmPackagesFilters/FilterSelect.js +1 -1
  7. package/admin/src/pages/MarketplacePage/components/NpmPackagesFilters/FiltersPopover.js +52 -41
  8. package/admin/src/pages/MarketplacePage/components/SortSelect/index.js +16 -0
  9. package/admin/src/pages/SettingsPage/pages/Users/components/SelectRoles/index.js +2 -2
  10. package/admin/src/translations/en.json +1 -0
  11. package/admin/src/translations/ru.json +7 -0
  12. package/build/{1387.84b454d3.chunk.js → 1387.437eb420.chunk.js} +1 -1
  13. package/build/{1657.45231968.chunk.js → 1657.85034334.chunk.js} +52 -52
  14. package/build/3081.c2cdfac8.chunk.js +108 -0
  15. package/build/3816.2b58bc1a.chunk.js +214 -0
  16. package/build/462.a073ff1f.chunk.js +71 -0
  17. package/build/{4628.20631dd1.chunk.js → 4628.9cbb6df5.chunk.js} +1 -1
  18. package/build/5542.5328b662.chunk.js +71 -0
  19. package/build/{5563.986609ed.chunk.js → 5563.44e0ef15.chunk.js} +22 -22
  20. package/build/617.ce23dea8.chunk.js +155 -0
  21. package/build/6858.85d76858.chunk.js +50 -0
  22. package/build/6970.e8422af3.chunk.js +1 -0
  23. package/build/{7259.7a48aa2f.chunk.js → 7259.3ed2b60e.chunk.js} +1 -1
  24. package/build/{Admin-authenticatedApp.2fab70e4.chunk.js → Admin-authenticatedApp.dbb99d34.chunk.js} +2 -2
  25. package/build/Admin_homePage.e15dcf28.chunk.js +73 -0
  26. package/build/Admin_marketplace.87f51b29.chunk.js +55 -0
  27. package/build/Admin_pluginsPage.7df6b5a9.chunk.js +6 -0
  28. package/build/{Admin_profilePage.1b337b73.chunk.js → Admin_profilePage.1687246a.chunk.js} +1 -1
  29. package/build/Admin_settingsPage.20884a40.chunk.js +79 -0
  30. package/build/Upload_ConfigureTheView.aa64ed9a.chunk.js +1 -0
  31. package/build/{admin-app.9bfe4ec7.chunk.js → admin-app.d4fd9379.chunk.js} +4 -4
  32. package/build/{admin-edit-roles-page.d0c9497b.chunk.js → admin-edit-roles-page.6fe9de49.chunk.js} +43 -41
  33. package/build/{admin-edit-users.ba27c532.chunk.js → admin-edit-users.49363035.chunk.js} +4 -4
  34. package/build/admin-roles-list.a323aa9f.chunk.js +31 -0
  35. package/build/admin-users.16bb6e77.chunk.js +34 -0
  36. package/build/audit-logs-settings-page.48b921f9.chunk.js +129 -0
  37. package/build/content-manager.a837bfcd.chunk.js +1123 -0
  38. package/build/content-type-builder-list-view.1e821eb9.chunk.js +215 -0
  39. package/build/{content-type-builder-translation-en-json.446b611d.chunk.js → content-type-builder-translation-en-json.5e5f8607.chunk.js} +1 -1
  40. package/build/{content-type-builder.4737a30c.chunk.js → content-type-builder.51c64fe5.chunk.js} +4 -4
  41. package/build/email-settings-page.5bc22e5a.chunk.js +11 -0
  42. package/build/en-json.1f6af924.chunk.js +1 -0
  43. package/build/i18n-settings-page.1581894e.chunk.js +114 -0
  44. package/build/i18n-translation-ru-json.401bc498.chunk.js +1 -0
  45. package/build/index.html +1 -1
  46. package/build/main.31bdf27e.js +2679 -0
  47. package/build/review-workflows-settings.26409ce4.chunk.js +61 -0
  48. package/build/ru-json.678cd48b.chunk.js +1 -0
  49. package/build/{runtime~main.c9593d59.js → runtime~main.a096f3d0.js} +2 -2
  50. package/build/{sso-settings-page.f44d95d8.chunk.js → sso-settings-page.c9d7c8df.chunk.js} +1 -1
  51. package/build/upload-settings.4d962053.chunk.js +14 -0
  52. package/build/upload.4dcd513f.chunk.js +34 -0
  53. package/build/users-advanced-settings-page.dd2a4954.chunk.js +9 -0
  54. package/build/users-email-settings-page.510648ab.chunk.js +24 -0
  55. package/build/users-permissions-translation-ru-json.8e883c67.chunk.js +1 -0
  56. package/build/users-providers-settings-page.0ee7d54f.chunk.js +29 -0
  57. package/build/webhook-edit-page.36c9f20d.chunk.js +128 -0
  58. package/build/webhook-list-page.0983d83f.chunk.js +71 -0
  59. package/ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/ReviewWorkflowsStageEE.js +22 -5
  60. package/ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumn.js +7 -3
  61. package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +20 -3
  62. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/ReviewWorkflows.js +34 -25
  63. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/actions/index.js +11 -0
  64. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/StageDragPreview/StageDragPreview.js +45 -0
  65. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/StageDragPreview/index.js +1 -0
  66. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/Stage.js +290 -63
  67. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/components/OptionColor/OptionColor.js +37 -0
  68. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/components/OptionColor/index.js +1 -0
  69. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/components/SingleValueColor/SingleValueColor.js +44 -0
  70. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/components/SingleValueColor/index.js +1 -0
  71. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stages.js +2 -1
  72. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/constants.js +26 -0
  73. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/reducer/index.js +36 -2
  74. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/utils/colors.js +39 -0
  75. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/utils/getWorkflowValidationSchema.js +9 -0
  76. package/ee/server/constants/workflows.js +1 -0
  77. package/ee/server/content-types/workflow-stage/index.js +7 -0
  78. package/ee/server/controllers/authentication/middlewares.js +1 -1
  79. package/ee/server/migrations/review-workflows-stages-color.js +20 -0
  80. package/ee/server/register.js +2 -0
  81. package/ee/server/routes/audit-logs.js +43 -0
  82. package/ee/server/routes/index.js +5 -226
  83. package/ee/server/routes/license-limit.js +29 -0
  84. package/ee/server/routes/review-workflows.js +112 -0
  85. package/ee/server/routes/sso.js +60 -0
  86. package/ee/server/routes/utils.js +15 -0
  87. package/ee/server/services/review-workflows/stages.js +1 -1
  88. package/ee/server/validation/review-workflows.js +7 -1
  89. package/package.json +17 -17
  90. package/server/routes/index.js +16 -11
  91. package/webpack.alias.js +0 -1
  92. package/admin/src/content-manager/pages/ListSettingsView/utils/api.js +0 -10
  93. package/build/3081.bcf9a12f.chunk.js +0 -108
  94. package/build/462.8fff7f3b.chunk.js +0 -71
  95. package/build/5542.b8240e3f.chunk.js +0 -70
  96. package/build/6404.68405699.chunk.js +0 -100
  97. package/build/7725.f717acdd.chunk.js +0 -213
  98. package/build/Admin_homePage.9f7c0bb1.chunk.js +0 -73
  99. package/build/Admin_marketplace.a60cde15.chunk.js +0 -31
  100. package/build/Admin_pluginsPage.9e6fa51c.chunk.js +0 -6
  101. package/build/Admin_settingsPage.56e9641c.chunk.js +0 -79
  102. package/build/Upload_ConfigureTheView.4fc648b5.chunk.js +0 -1
  103. package/build/admin-roles-list.c759daa3.chunk.js +0 -31
  104. package/build/admin-users.ad5dd832.chunk.js +0 -34
  105. package/build/audit-logs-settings-page.45cb4fb5.chunk.js +0 -76
  106. package/build/content-manager.a0ff6cad.chunk.js +0 -1111
  107. package/build/content-type-builder-list-view.5ff685ec.chunk.js +0 -214
  108. package/build/email-settings-page.dc07d518.chunk.js +0 -10
  109. package/build/en-json.c7fc79af.chunk.js +0 -1
  110. package/build/i18n-settings-page.8219dd99.chunk.js +0 -60
  111. package/build/main.964e7203.js +0 -2597
  112. package/build/review-workflows-settings.089d7ec5.chunk.js +0 -106
  113. package/build/ru-json.e0662702.chunk.js +0 -1
  114. package/build/upload-settings.dd2d987c.chunk.js +0 -13
  115. package/build/upload.c8479232.chunk.js +0 -33
  116. package/build/users-advanced-settings-page.c36cfd59.chunk.js +0 -8
  117. package/build/users-email-settings-page.2716ce8e.chunk.js +0 -23
  118. package/build/users-permissions-translation-ru-json.20e177db.chunk.js +0 -1
  119. package/build/users-providers-settings-page.0d6304a5.chunk.js +0 -28
  120. package/build/webhook-edit-page.f4db86f3.chunk.js +0 -75
  121. package/build/webhook-list-page.30d73114.chunk.js +0 -71
@@ -1,15 +1,32 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { Typography } from '@strapi/design-system';
3
+ import { Box, Flex, Typography } from '@strapi/design-system';
4
+ import { pxToRem } from '@strapi/helper-plugin';
5
+
6
+ import { getStageColorByHex } from '../../../../../pages/SettingsPage/pages/ReviewWorkflows/utils/colors';
7
+
8
+ export function ReviewWorkflowsStageEE({ color, name }) {
9
+ const { themeColorName } = getStageColorByHex(color);
4
10
 
5
- export function ReviewWorkflowsStageEE({ name }) {
6
11
  return (
7
- <Typography fontWeight="regular" textColor="neutral700">
8
- {name}
9
- </Typography>
12
+ <Flex alignItems="center" gap={2} maxWidth={pxToRem(300)}>
13
+ <Box
14
+ height={2}
15
+ background={color}
16
+ borderColor={themeColorName === 'neutral0' ? 'neutral150' : 'transparent'}
17
+ hasRadius
18
+ shrink={0}
19
+ width={2}
20
+ />
21
+
22
+ <Typography fontWeight="regular" textColor="neutral700" ellipsis>
23
+ {name}
24
+ </Typography>
25
+ </Flex>
10
26
  );
11
27
  }
12
28
 
13
29
  ReviewWorkflowsStageEE.propTypes = {
30
+ color: PropTypes.string.isRequired,
14
31
  name: PropTypes.string.isRequired,
15
32
  };
@@ -4,6 +4,7 @@ import { Typography } from '@strapi/design-system';
4
4
 
5
5
  import ReviewWorkflowsStage from '.';
6
6
  import getTrad from '../../../../../../../admin/src/content-manager/utils/getTrad';
7
+ import { STAGE_COLOR_DEFAULT } from '../../../../../pages/SettingsPage/pages/ReviewWorkflows/constants';
7
8
 
8
9
  export default (layout) => {
9
10
  const { formatMessage } = useIntl();
@@ -24,7 +25,7 @@ export default (layout) => {
24
25
  key: '__strapi_reviewWorkflows_stage_temp_key__',
25
26
  name: 'strapi_reviewWorkflows_stage',
26
27
  fieldSchema: {
27
- type: 'custom',
28
+ type: 'relation',
28
29
  },
29
30
  metadatas: {
30
31
  label: formatMessage({
@@ -32,7 +33,8 @@ export default (layout) => {
32
33
  defaultMessage: 'Review stage',
33
34
  }),
34
35
  searchable: false,
35
- sortable: false,
36
+ sortable: true,
37
+ mainField: 'name',
36
38
  },
37
39
  cellFormatter({ strapi_reviewWorkflows_stage }) {
38
40
  // if entities are created e.g. through lifecycle methods
@@ -41,7 +43,9 @@ export default (layout) => {
41
43
  return <Typography textColor="neutral800">-</Typography>;
42
44
  }
43
45
 
44
- return <ReviewWorkflowsStage name={strapi_reviewWorkflows_stage.name} />;
46
+ const { color, name } = strapi_reviewWorkflows_stage;
47
+
48
+ return <ReviewWorkflowsStage color={color ?? STAGE_COLOR_DEFAULT} name={name} />;
45
49
  },
46
50
  };
47
51
  };
@@ -11,6 +11,8 @@ import { useIntl } from 'react-intl';
11
11
  import { useMutation } from 'react-query';
12
12
 
13
13
  import { useReviewWorkflows } from '../../../../pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows';
14
+ import { OptionColor } from '../../../../pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/components/OptionColor';
15
+ import { SingleValueColor } from '../../../../pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/components/SingleValueColor';
14
16
  import Information from '../../../../../../admin/src/content-manager/pages/EditView/Information';
15
17
 
16
18
  const ATTRIBUTE_NAME = 'strapi_reviewWorkflows_stage';
@@ -61,7 +63,10 @@ export function InformationBoxEE() {
61
63
  onSuccess() {
62
64
  toggleNotification({
63
65
  type: 'success',
64
- message: { id: 'notification.success.saved', defaultMessage: 'Saved' },
66
+ message: {
67
+ id: 'content-manager.reviewWorkflows.stage.notification.saved',
68
+ defaultMessage: 'Success: Review stage updated',
69
+ },
65
70
  });
66
71
  },
67
72
  }
@@ -113,6 +118,8 @@ export function InformationBoxEE() {
113
118
  <ReactSelect
114
119
  components={{
115
120
  LoadingIndicator: () => <Loader small />,
121
+ Option: OptionColor,
122
+ SingleValue: SingleValueColor,
116
123
  }}
117
124
  error={formattedError}
118
125
  inputId={ATTRIBUTE_NAME}
@@ -122,9 +129,19 @@ export function InformationBoxEE() {
122
129
  name={ATTRIBUTE_NAME}
123
130
  onChange={handleStageChange}
124
131
  options={
125
- workflow ? workflow.stages.map(({ id, name }) => ({ value: id, label: name })) : []
132
+ workflow
133
+ ? workflow.stages.map(({ id, color, name }) => ({
134
+ value: id,
135
+ label: name,
136
+ color,
137
+ }))
138
+ : []
126
139
  }
127
- value={{ value: activeWorkflowStage?.id, label: activeWorkflowStage?.name }}
140
+ value={{
141
+ value: activeWorkflowStage?.id,
142
+ label: activeWorkflowStage?.name,
143
+ color: activeWorkflowStage?.color,
144
+ }}
128
145
  />
129
146
 
130
147
  <FieldError />
@@ -18,12 +18,24 @@ import { Check } from '@strapi/icons';
18
18
 
19
19
  import { Stages } from './components/Stages';
20
20
  import { reducer, initialState } from './reducer';
21
- import { REDUX_NAMESPACE } from './constants';
21
+ import { REDUX_NAMESPACE, DRAG_DROP_TYPES } from './constants';
22
22
  import { useInjectReducer } from '../../../../../../admin/src/hooks/useInjectReducer';
23
23
  import { useReviewWorkflows } from './hooks/useReviewWorkflows';
24
24
  import { setWorkflows } from './actions';
25
25
  import { getWorkflowValidationSchema } from './utils/getWorkflowValidationSchema';
26
26
  import adminPermissions from '../../../../../../admin/src/permissions';
27
+ import { StageDragPreview } from './components/StageDragPreview';
28
+ import { DragLayer } from '../../../../../../admin/src/components/DragLayer';
29
+
30
+ function renderDragLayerItem({ type, item }) {
31
+ switch (type) {
32
+ case DRAG_DROP_TYPES.STAGE:
33
+ return <StageDragPreview {...item} />;
34
+
35
+ default:
36
+ return null;
37
+ }
38
+ }
27
39
 
28
40
  export function ReviewWorkflowsPage() {
29
41
  const { trackUsage } = useTracking();
@@ -47,31 +59,15 @@ export function ReviewWorkflowsPage() {
47
59
 
48
60
  const { mutateAsync, isLoading } = useMutation(
49
61
  async ({ workflowId, stages }) => {
50
- try {
51
- const {
52
- data: { data },
53
- } = await put(`/admin/review-workflows/workflows/${workflowId}/stages`, {
54
- data: stages,
55
- });
62
+ const {
63
+ data: { data },
64
+ } = await put(`/admin/review-workflows/workflows/${workflowId}/stages`, {
65
+ data: stages,
66
+ });
56
67
 
57
- return data;
58
- } catch (error) {
59
- toggleNotification({
60
- type: 'warning',
61
- message: formatAPIError(error),
62
- });
63
- }
64
-
65
- return null;
68
+ return data;
66
69
  },
67
70
  {
68
- onError(error) {
69
- toggleNotification({
70
- type: 'warning',
71
- message: formatAPIError(error),
72
- });
73
- },
74
-
75
71
  onSuccess() {
76
72
  toggleNotification({
77
73
  type: 'success',
@@ -81,8 +77,19 @@ export function ReviewWorkflowsPage() {
81
77
  }
82
78
  );
83
79
 
84
- const updateWorkflowStages = (workflowId, stages) => {
85
- return mutateAsync({ workflowId, stages });
80
+ const updateWorkflowStages = async (workflowId, stages) => {
81
+ try {
82
+ const res = await mutateAsync({ workflowId, stages });
83
+
84
+ return res;
85
+ } catch (error) {
86
+ toggleNotification({
87
+ type: 'warning',
88
+ message: formatAPIError(error),
89
+ });
90
+
91
+ return null;
92
+ }
86
93
  };
87
94
 
88
95
  const submitForm = async () => {
@@ -135,6 +142,8 @@ export function ReviewWorkflowsPage() {
135
142
  })}
136
143
  />
137
144
  <Main tabIndex={-1}>
145
+ <DragLayer renderItem={renderDragLayerItem} />
146
+
138
147
  <FormikProvider value={formik}>
139
148
  <Form onSubmit={formik.handleSubmit}>
140
149
  <HeaderLayout
@@ -3,6 +3,7 @@ import {
3
3
  ACTION_DELETE_STAGE,
4
4
  ACTION_ADD_STAGE,
5
5
  ACTION_UPDATE_STAGE,
6
+ ACTION_UPDATE_STAGE_POSITION,
6
7
  } from '../constants';
7
8
 
8
9
  export function setWorkflows({ status, data }) {
@@ -40,3 +41,13 @@ export function updateStage(stageId, payload) {
40
41
  },
41
42
  };
42
43
  }
44
+
45
+ export function updateStagePosition(oldIndex, newIndex) {
46
+ return {
47
+ type: ACTION_UPDATE_STAGE_POSITION,
48
+ payload: {
49
+ newIndex,
50
+ oldIndex,
51
+ },
52
+ };
53
+ }
@@ -0,0 +1,45 @@
1
+ import * as React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import styled from 'styled-components';
4
+ import { Flex, Typography } from '@strapi/design-system';
5
+ import { CarretDown } from '@strapi/icons';
6
+ import { pxToRem } from '@strapi/helper-plugin';
7
+
8
+ const Toggle = styled(Flex)`
9
+ svg path {
10
+ fill: ${({ theme }) => theme.colors.neutral600};
11
+ }
12
+ `;
13
+
14
+ export function StageDragPreview({ name }) {
15
+ return (
16
+ <Flex
17
+ background="primary100"
18
+ borderStyle="dashed"
19
+ borderColor="primary600"
20
+ borderWidth="1px"
21
+ gap={3}
22
+ hasRadius
23
+ padding={3}
24
+ shadow="tableShadow"
25
+ width={pxToRem(300)}
26
+ >
27
+ <Toggle
28
+ alignItems="center"
29
+ background="neutral200"
30
+ borderRadius="50%"
31
+ height={6}
32
+ justifyContent="center"
33
+ width={6}
34
+ >
35
+ <CarretDown width={`${8 / 16}rem`} />
36
+ </Toggle>
37
+
38
+ <Typography fontWeight="bold">{name}</Typography>
39
+ </Flex>
40
+ );
41
+ }
42
+
43
+ StageDragPreview.propTypes = {
44
+ name: PropTypes.string.isRequired,
45
+ };
@@ -0,0 +1 @@
1
+ export * from './StageDragPreview';
@@ -1,4 +1,4 @@
1
- import React, { useState } from 'react';
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,311 @@ 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';
23
+ import { getEmptyImage } from 'react-dnd-html5-backend';
17
24
 
18
- import { deleteStage, updateStage } from '../../../actions';
25
+ import { deleteStage, updateStagePosition, updateStage } from '../../../actions';
26
+ import { getAvailableStageColors } from '../../../utils/colors';
27
+ import { OptionColor } from './components/OptionColor';
28
+ import { SingleValueColor } from './components/SingleValueColor';
29
+ import { useDragAndDrop } from '../../../../../../../../../admin/src/content-manager/hooks';
30
+ import { composeRefs } from '../../../../../../../../../admin/src/content-manager/utils';
31
+ import { DRAG_DROP_TYPES } from '../../../constants';
19
32
 
20
- function Stage({ id, name, index, canDelete, isOpen: isOpenDefault = false }) {
33
+ const AVAILABLE_COLORS = getAvailableStageColors();
34
+
35
+ function StageDropPreview() {
36
+ return (
37
+ <Box
38
+ background="primary100"
39
+ borderStyle="dashed"
40
+ borderColor="primary600"
41
+ borderWidth="1px"
42
+ display="block"
43
+ hasRadius
44
+ padding={6}
45
+ shadow="tableShadow"
46
+ />
47
+ );
48
+ }
49
+
50
+ export function Stage({
51
+ id,
52
+ index,
53
+ canDelete,
54
+ canReorder,
55
+ isOpen: isOpenDefault = false,
56
+ stagesCount,
57
+ }) {
58
+ /**
59
+ *
60
+ * @param {number} index
61
+ * @returns {string}
62
+ */
63
+ const getItemPos = (index) => `${index + 1} of ${stagesCount}`;
64
+
65
+ /**
66
+ *
67
+ * @param {number} index
68
+ * @returns {void}
69
+ */
70
+ const handleGrabStage = (index) => {
71
+ setLiveText(
72
+ formatMessage(
73
+ {
74
+ id: 'dnd.grab-item',
75
+ defaultMessage: `{item}, grabbed. Current position in list: {position}. Press up and down arrow to change position, Spacebar to drop, Escape to cancel.`,
76
+ },
77
+ {
78
+ item: nameField.value,
79
+ position: getItemPos(index),
80
+ }
81
+ )
82
+ );
83
+ };
84
+
85
+ /**
86
+ *
87
+ * @param {number} index
88
+ * @returns {void}
89
+ */
90
+ const handleDropStage = (index) => {
91
+ setLiveText(
92
+ formatMessage(
93
+ {
94
+ id: 'dnd.drop-item',
95
+ defaultMessage: `{item}, dropped. Final position in list: {position}.`,
96
+ },
97
+ {
98
+ item: nameField.value,
99
+ position: getItemPos(index),
100
+ }
101
+ )
102
+ );
103
+ };
104
+
105
+ /**
106
+ *
107
+ * @param {number} index
108
+ * @returns {void}
109
+ */
110
+ const handleCancelDragStage = () => {
111
+ setLiveText(
112
+ formatMessage(
113
+ {
114
+ id: 'dnd.cancel-item',
115
+ defaultMessage: '{item}, dropped. Re-order cancelled.',
116
+ },
117
+ {
118
+ item: nameField.value,
119
+ }
120
+ )
121
+ );
122
+ };
123
+
124
+ const handleMoveStage = (newIndex, oldIndex) => {
125
+ setLiveText(
126
+ formatMessage(
127
+ {
128
+ id: 'dnd.reorder',
129
+ defaultMessage: '{item}, moved. New position in list: {position}.',
130
+ },
131
+ {
132
+ item: nameField.value,
133
+ position: getItemPos(newIndex),
134
+ }
135
+ )
136
+ );
137
+
138
+ dispatch(updateStagePosition(oldIndex, newIndex));
139
+ };
140
+
141
+ const [liveText, setLiveText] = React.useState(null);
21
142
  const { formatMessage } = useIntl();
22
143
  const { trackUsage } = useTracking();
23
- const [isOpen, setIsOpen] = useState(isOpenDefault);
24
- const fieldIdentifier = `stages.${index}.name`;
25
- const [field, meta] = useField(fieldIdentifier);
26
144
  const dispatch = useDispatch();
145
+ const [isOpen, setIsOpen] = React.useState(isOpenDefault);
146
+ const [nameField, nameMeta] = useField(`stages.${index}.name`);
147
+ const [colorField, colorMeta] = useField(`stages.${index}.color`);
148
+ const [{ handlerId, isDragging, handleKeyDown }, stageRef, dropRef, dragRef, dragPreviewRef] =
149
+ useDragAndDrop(canReorder, {
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
+ const composedRef = composeRefs(stageRef, dropRef);
162
+
163
+ const colorOptions = AVAILABLE_COLORS.map(({ hex, name }) => ({
164
+ value: hex,
165
+ label: formatMessage(
166
+ {
167
+ id: 'Settings.review-workflows.stage.color.name',
168
+ defaultMessage: '{name}',
169
+ },
170
+ { name }
171
+ ),
172
+ color: hex,
173
+ }));
174
+ // TODO: the .toUpperCase() conversion can be removed once the hex code is normalized in
175
+ // the admin API
176
+ const colorValue = colorOptions.find(({ value }) => value === colorField.value.toUpperCase());
177
+
178
+ React.useEffect(() => {
179
+ dragPreviewRef(getEmptyImage(), { captureDraggingState: false });
180
+ }, [dragPreviewRef, index]);
27
181
 
28
182
  return (
29
- <Accordion
30
- size="S"
31
- variant="primary"
32
- onToggle={() => {
33
- setIsOpen(!isOpen);
34
-
35
- if (!isOpen) {
36
- trackUsage('willEditStage');
37
- }
38
- }}
39
- expanded={isOpen}
40
- shadow="tableShadow"
41
- >
42
- <AccordionToggle
43
- title={name}
44
- togglePosition="left"
45
- action={
46
- canDelete ? (
47
- <IconButton
48
- background="transparent"
49
- noBorder
50
- onClick={() => dispatch(deleteStage(id))}
51
- label={formatMessage({
52
- id: 'Settings.review-workflows.stage.delete',
53
- defaultMessage: 'Delete stage',
54
- })}
55
- icon={<Trash />}
56
- />
57
- ) : null
58
- }
59
- />
60
- <AccordionContent padding={6} background="neutral0" hasRadius>
61
- <Grid gap={4}>
62
- <GridItem col={6}>
63
- <TextInput
64
- {...field}
65
- id={fieldIdentifier}
66
- value={name}
67
- label={formatMessage({
68
- id: 'Settings.review-workflows.stage.name.label',
69
- defaultMessage: 'Stage name',
70
- })}
71
- error={meta.error ?? false}
72
- onChange={(event) => {
73
- field.onChange(event);
74
- dispatch(updateStage(id, { name: event.target.value }));
75
- }}
76
- />
77
- </GridItem>
78
- </Grid>
79
- </AccordionContent>
80
- </Accordion>
183
+ <Box ref={composedRef}>
184
+ {liveText && <VisuallyHidden aria-live="assertive">{liveText}</VisuallyHidden>}
185
+
186
+ {isDragging ? (
187
+ <StageDropPreview />
188
+ ) : (
189
+ <Accordion
190
+ size="S"
191
+ variant="primary"
192
+ onToggle={() => {
193
+ setIsOpen(!isOpen);
194
+
195
+ if (!isOpen) {
196
+ trackUsage('willEditStage');
197
+ }
198
+ }}
199
+ expanded={isOpen}
200
+ shadow="tableShadow"
201
+ >
202
+ <AccordionToggle
203
+ title={nameField.value}
204
+ togglePosition="left"
205
+ action={
206
+ <Flex>
207
+ {canDelete && (
208
+ <IconButton
209
+ background="transparent"
210
+ icon={<Trash />}
211
+ label={formatMessage({
212
+ id: 'Settings.review-workflows.stage.delete',
213
+ defaultMessage: 'Delete stage',
214
+ })}
215
+ noBorder
216
+ onClick={() => dispatch(deleteStage(id))}
217
+ />
218
+ )}
219
+
220
+ <IconButton
221
+ background="transparent"
222
+ forwardedAs="div"
223
+ role="button"
224
+ noBorder
225
+ tabIndex={0}
226
+ data-handler-id={handlerId}
227
+ ref={dragRef}
228
+ label={formatMessage({
229
+ id: 'Settings.review-workflows.stage.drag',
230
+ defaultMessage: 'Drag',
231
+ })}
232
+ onClick={(e) => e.stopPropagation()}
233
+ onKeyDown={handleKeyDown}
234
+ >
235
+ <Drag />
236
+ </IconButton>
237
+ </Flex>
238
+ }
239
+ />
240
+ <AccordionContent padding={6} background="neutral0" hasRadius>
241
+ <Grid gap={4}>
242
+ <GridItem col={6}>
243
+ <TextInput
244
+ {...nameField}
245
+ id={nameField.name}
246
+ label={formatMessage({
247
+ id: 'Settings.review-workflows.stage.name.label',
248
+ defaultMessage: 'Stage name',
249
+ })}
250
+ error={nameMeta.error ?? false}
251
+ onChange={(event) => {
252
+ nameField.onChange(event);
253
+ dispatch(updateStage(id, { name: event.target.value }));
254
+ }}
255
+ required
256
+ />
257
+ </GridItem>
258
+
259
+ <GridItem col={6}>
260
+ <Field
261
+ error={colorMeta?.error ?? false}
262
+ name={colorField.name}
263
+ id={colorField.name}
264
+ required
265
+ >
266
+ <Flex direction="column" gap={1} alignItems="stretch">
267
+ <FieldLabel>
268
+ {formatMessage({
269
+ id: 'content-manager.reviewWorkflows.stage.color',
270
+ defaultMessage: 'Color',
271
+ })}
272
+ </FieldLabel>
273
+
274
+ <ReactSelect
275
+ components={{ Option: OptionColor, SingleValue: SingleValueColor }}
276
+ error={colorMeta?.error}
277
+ inputId={colorField.name}
278
+ name={colorField.name}
279
+ options={colorOptions}
280
+ onChange={({ value }) => {
281
+ colorField.onChange({ target: { value } });
282
+ dispatch(updateStage(id, { color: value }));
283
+ }}
284
+ // If no color was found in all the valid theme colors it means a user
285
+ // has set a custom value e.g. through the content API. In that case we
286
+ // display the custom color and a "Custom" label.
287
+ value={
288
+ colorValue ?? {
289
+ value: colorField.value,
290
+ label: formatMessage({
291
+ id: 'Settings.review-workflows.stage.color.name.custom',
292
+ defaultMessage: 'Custom',
293
+ }),
294
+ color: colorField.value,
295
+ }
296
+ }
297
+ />
298
+
299
+ <FieldError />
300
+ </Flex>
301
+ </Field>
302
+ </GridItem>
303
+ </Grid>
304
+ </AccordionContent>
305
+ </Accordion>
306
+ )}
307
+ </Box>
81
308
  );
82
309
  }
83
310
 
84
- export { Stage };
85
-
86
311
  Stage.propTypes = PropTypes.shape({
87
312
  id: PropTypes.number.isRequired,
88
- name: PropTypes.string.isRequired,
313
+ color: PropTypes.string.isRequired,
89
314
  canDelete: PropTypes.bool.isRequired,
315
+ canReorder: PropTypes.bool.isRequired,
316
+ stagesCount: PropTypes.number.isRequired,
90
317
  }).isRequired;