@dream-encode/wp-js-plugin-utils 0.3.0 → 0.4.0

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.
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _jsxRuntime = require("react/jsx-runtime");
8
+ /**
9
+ * Wrapper for a process info key/value table.
10
+ *
11
+ * @param {Object} props
12
+ * @param {string} [props.className] Wrapper class name. Defaults to `'process-info'`.
13
+ * @param {*} props.children `ProcessInfoRow` children (or any nodes).
14
+ * @return {JSX.Element|null}
15
+ */
16
+ const ProcessInfo = ({
17
+ className = 'process-info',
18
+ children
19
+ }) => {
20
+ if (!children) {
21
+ return null;
22
+ }
23
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
24
+ className: className,
25
+ children: children
26
+ });
27
+ };
28
+ var _default = exports.default = ProcessInfo;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _element = require("@wordpress/element");
8
+ var _components = require("@wordpress/components");
9
+ var _jsxRuntime = require("react/jsx-runtime");
10
+ /**
11
+ * A single key/value row inside `ProcessInfo`.
12
+ *
13
+ * Either pass a primitive `value` or pass JSX as `children`. If `children` is
14
+ * provided the value cell is rendered as a `div` (so it can host buttons,
15
+ * spans, refs, etc.). Otherwise it is rendered as a `Text` cell.
16
+ *
17
+ * @param {Object} props
18
+ * @param {string|JSX.Element} props.label The row label (key cell).
19
+ * @param {*} [props.value] Primitive value to render in the value cell.
20
+ * @param {*} [props.children] Custom value content.
21
+ * @param {boolean} [props.show=true] Render the row only when truthy.
22
+ * @param {string} [props.className='row'] Row wrapper class name.
23
+ * @param {string} [props.keyClassName='key']
24
+ * @param {string} [props.valueClassName='value']
25
+ * @return {JSX.Element|null}
26
+ */const ProcessInfoRow = ({
27
+ label,
28
+ value,
29
+ children,
30
+ show = true,
31
+ className = 'row',
32
+ keyClassName = 'key',
33
+ valueClassName = 'value'
34
+ }) => {
35
+ if (!show) {
36
+ return null;
37
+ }
38
+ const content = children !== undefined ? children : value;
39
+ const isComplexContent = (0, _element.isValidElement)(content) || Array.isArray(content);
40
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
41
+ className: className,
42
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.__experimentalText, {
43
+ className: keyClassName,
44
+ children: label
45
+ }), isComplexContent ? /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
46
+ className: valueClassName,
47
+ children: content
48
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.__experimentalText, {
49
+ className: valueClassName,
50
+ children: content
51
+ })]
52
+ });
53
+ };
54
+ var _default = exports.default = ProcessInfoRow;
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _components = require("@wordpress/components");
8
+ var _element = require("@wordpress/element");
9
+ var _Progress = _interopRequireDefault(require("../Progress/Progress"));
10
+ var _ProcessStatusSummary = _interopRequireDefault(require("./ProcessStatusSummary"));
11
+ var _jsxRuntime = require("react/jsx-runtime");
12
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
13
+ const PENDING_STATUSES = ['pending', 'queued'];
14
+
15
+ /**
16
+ * Card-level wrapper for a single running process.
17
+ *
18
+ * Renders the process name, optional id and badges, a progress bar, a status
19
+ * summary row, an action button slot and an expandable details section.
20
+ *
21
+ * @param {Object} props
22
+ * @param {string|JSX.Element} props.name Display name shown at the top.
23
+ * @param {string|number} [props.processId] Optional id rendered as `#{id}` next to the name.
24
+ * @param {string} props.status Current process status.
25
+ * @param {number} props.percentComplete Percent complete (0-100).
26
+ * @param {string} [props.progressStatus] Optional override for the Progress bar status modifier.
27
+ * @param {Object[]} [props.runs] Forwarded to `ProcessStatusSummary`.
28
+ * @param {number} [props.startTime] Forwarded to `ProcessStatusSummary`.
29
+ * @param {number} [props.secondsRemaining] Forwarded to `ProcessStatusSummary`.
30
+ * @param {Object} [props.summaryProps] Additional props passed to `ProcessStatusSummary`.
31
+ * @param {JSX.Element} [props.badges] Badges/pill labels rendered after the name (e.g. "DRY RUN").
32
+ * @param {JSX.Element} [props.actions] Action buttons rendered on the right side.
33
+ * @param {*} [props.children] Content for the expandable details section (e.g. `<ProcessInfo>`).
34
+ * @param {boolean} [props.expandable=true] Whether the details toggle is rendered.
35
+ * @param {boolean} [props.defaultExpanded=false] Initial expanded state.
36
+ * @param {string} [props.className='process'] Wrapper class name.
37
+ * @param {string[]} [props.modifierClassNames] Additional class names appended to the wrapper.
38
+ * @param {string} [props.detailsToggleIcon='menu'] Dashicon name for the details toggle.
39
+ * @param {string} [props.idClassName='process-id'] Class for the id label.
40
+ * @param {string} [props.nameClassName='process-name'] Class for the name label.
41
+ * @param {string} [props.actionsClassName='process-buttons'] Class for the actions container.
42
+ * @param {string} [props.detailsClassName='details'] Class for the expandable details container.
43
+ * @return {JSX.Element|null}
44
+ */
45
+ const ProcessStatusInfo = ({
46
+ name,
47
+ processId,
48
+ status,
49
+ percentComplete,
50
+ progressStatus,
51
+ runs,
52
+ startTime,
53
+ secondsRemaining,
54
+ summaryProps = {},
55
+ badges,
56
+ actions,
57
+ children,
58
+ expandable = true,
59
+ defaultExpanded = false,
60
+ className = 'process',
61
+ modifierClassNames = [],
62
+ detailsToggleIcon = 'menu',
63
+ idClassName = 'process-id',
64
+ nameClassName = 'process-name',
65
+ actionsClassName = 'process-buttons',
66
+ detailsClassName = 'details'
67
+ }) => {
68
+ const [showDetails, setShowDetails] = (0, _element.useState)(defaultExpanded);
69
+ const wrapperClassName = [className, ...modifierClassNames].filter(Boolean).join(' ');
70
+ const isPending = PENDING_STATUSES.includes(status);
71
+ const showDetailsToggle = expandable && !!children && !isPending;
72
+ const toggleShowDetails = () => {
73
+ setShowDetails(current => !current);
74
+ };
75
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
76
+ className: wrapperClassName,
77
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_components.__experimentalHStack, {
78
+ alignment: "top",
79
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_components.__experimentalVStack, {
80
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_components.__experimentalHStack, {
81
+ alignment: "left",
82
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.__experimentalText, {
83
+ className: nameClassName,
84
+ children: name
85
+ }), (processId || processId === 0) && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.__experimentalText, {
86
+ className: idClassName,
87
+ variant: "muted",
88
+ children: `#${processId}`
89
+ }), badges]
90
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_Progress.default, {
91
+ currentValue: percentComplete ?? 0,
92
+ maxValue: 100,
93
+ status: progressStatus || status
94
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_ProcessStatusSummary.default, {
95
+ status: status,
96
+ percentComplete: percentComplete,
97
+ runs: runs,
98
+ startTime: startTime,
99
+ secondsRemaining: secondsRemaining,
100
+ ...summaryProps
101
+ })]
102
+ }), (showDetailsToggle || actions) && /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
103
+ className: actionsClassName,
104
+ children: [showDetailsToggle && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Icon, {
105
+ className: "process-details-icon",
106
+ icon: detailsToggleIcon,
107
+ size: 30,
108
+ onClick: toggleShowDetails
109
+ }), actions]
110
+ })]
111
+ }), !!children && /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
112
+ className: `${detailsClassName} ${showDetails ? 'open' : 'closed'}`,
113
+ children: children
114
+ })]
115
+ });
116
+ };
117
+ var _default = exports.default = ProcessStatusInfo;
@@ -0,0 +1,226 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _i18n = require("@wordpress/i18n");
8
+ var _components = require("@wordpress/components");
9
+ var _element = require("@wordpress/element");
10
+ var _time = require("../../utils/time");
11
+ var _dates = require("../../utils/dates");
12
+ var _useValueChangeEffect = _interopRequireDefault(require("../../hooks/useValueChangeEffect"));
13
+ var _jsxRuntime = require("react/jsx-runtime");
14
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
15
+ /**
16
+ * Status summary row for a long-running process.
17
+ *
18
+ * Renders a single horizontal row of muted text describing the current status,
19
+ * percent complete and (when applicable) an estimated time remaining countdown.
20
+ *
21
+ * The consumer is responsible for computing the initial `secondsRemaining`
22
+ * (e.g. via an EMA over previous run cycle times) and passing it in. The
23
+ * component manages the per-second countdown internally and resets it whenever
24
+ * the `secondsRemaining` prop changes.
25
+ *
26
+ * @param {Object} props
27
+ * @param {string} props.status Current process status.
28
+ * @param {number} props.percentComplete Percent complete (0-100).
29
+ * @param {number} [props.secondsRemaining] Pre-computed seconds remaining. Overrides `runs`/`startTime` when provided.
30
+ * @param {Object[]} [props.runs] Array of process run records used to compute time remaining via EMA.
31
+ * @param {number} [props.startTime] Unix timestamp (seconds) the process started. Used for the simple fallback.
32
+ * @param {number} [props.emaWindow=2] Window size passed to the EMA helper.
33
+ * @param {boolean} [props.emaUseCycleTime=true] Whether the EMA helper should use full cycle time.
34
+ * @param {string} [props.className='summary'] Wrapper class name.
35
+ * @param {boolean} [props.animatePercent=true] Animate the percent complete value when it changes.
36
+ * @param {boolean} [props.showEstimatedCompletedTime=true] Append the local-time ETA after the time remaining.
37
+ * @param {JSX.Element} [props.failedAction] Slot rendered after the "Failed!" label (e.g. a retry button).
38
+ * @param {Object} [props.messages] Optional per-status JSX/string overrides keyed by status name.
39
+ * @param {string} [props.textDomain='default'] Translation domain for built-in strings.
40
+ * @param {Object} [props.labels] Per-string label overrides (see `defaultLabels`).
41
+ * @return {JSX.Element|null}
42
+ */const ProcessStatusSummary = ({
43
+ status,
44
+ percentComplete,
45
+ secondsRemaining,
46
+ runs,
47
+ startTime,
48
+ emaWindow = 2,
49
+ emaUseCycleTime = true,
50
+ className = 'summary',
51
+ animatePercent = true,
52
+ showEstimatedCompletedTime = true,
53
+ failedAction,
54
+ messages = {},
55
+ textDomain = 'default',
56
+ labels = {}
57
+ }) => {
58
+ const mergedLabels = (0, _element.useMemo)(() => ({
59
+ waiting: (0, _i18n.__)('Waiting', textDomain),
60
+ inProgress: (0, _i18n.__)('In-progress', textDomain),
61
+ complete: (0, _i18n.__)('Complete', textDomain),
62
+ completing: (0, _i18n.__)('Completing process', textDomain),
63
+ failed: (0, _i18n.__)('Failed!', textDomain),
64
+ percentSuffix: (0, _i18n.__)('complete', textDomain),
65
+ calculating: (0, _i18n.__)('Calculating time remaining...', textDomain),
66
+ fewSeconds: (0, _i18n.__)('A few seconds remaining', textDomain),
67
+ /* translators: %s: Time remaining. */
68
+ approxRemaining: (0, _i18n.__)('Approx. %s remaining', textDomain),
69
+ ...labels
70
+ }), [textDomain, labels]);
71
+ const percentValue = String(parseFloat(percentComplete ?? 0).toFixed(1));
72
+ const {
73
+ displayValue: displayPercent,
74
+ elementRef: percentRef
75
+ } = (0, _useValueChangeEffect.default)(percentValue, animatePercent ? 'fast' : 'default');
76
+ const [intervalSecondsRemaining, setIntervalSecondsRemaining] = (0, _element.useState)(false);
77
+ const [estimatedCompletedTime, setEstimatedCompletedTime] = (0, _element.useState)(false);
78
+ const intervalRef = (0, _element.useRef)(null);
79
+ const computedSecondsRemaining = (0, _element.useMemo)(() => {
80
+ if (secondsRemaining !== undefined && secondsRemaining !== null) {
81
+ return Number(secondsRemaining);
82
+ }
83
+ if (!percentComplete || percentComplete >= 100) {
84
+ return 0;
85
+ }
86
+ if (Array.isArray(runs) && runs.length >= 3) {
87
+ const {
88
+ avgTime
89
+ } = (0, _time.calculateEstimatedTimeRemainingUsingEMA)(runs, emaWindow, emaUseCycleTime);
90
+ if (avgTime > 0) {
91
+ const remainingPercentage = 100 - percentComplete;
92
+ const estimatedRemainingRuns = remainingPercentage / (percentComplete / runs.length);
93
+ return Math.max(1, avgTime * estimatedRemainingRuns);
94
+ }
95
+ }
96
+ if (startTime) {
97
+ return (0, _time.calculateEstimatedTimeRemainingSimple)(startTime, percentComplete);
98
+ }
99
+ return 0;
100
+ }, [secondsRemaining, runs, startTime, percentComplete, emaWindow, emaUseCycleTime]);
101
+ (0, _element.useEffect)(() => {
102
+ if (computedSecondsRemaining && Number(computedSecondsRemaining) > 0) {
103
+ setIntervalSecondsRemaining(Number(computedSecondsRemaining));
104
+ setEstimatedCompletedTime((0, _time.addSecondsToCurrentTime)(Number(computedSecondsRemaining)));
105
+ }
106
+ }, [computedSecondsRemaining]);
107
+ (0, _element.useEffect)(() => {
108
+ if (intervalRef.current) {
109
+ clearInterval(intervalRef.current);
110
+ intervalRef.current = null;
111
+ }
112
+ if (intervalSecondsRemaining > 0 && percentComplete < 100) {
113
+ intervalRef.current = setInterval(() => {
114
+ setIntervalSecondsRemaining(old => Math.max(0, old - 1));
115
+ }, 1000);
116
+ }
117
+ return () => {
118
+ if (intervalRef.current) {
119
+ clearInterval(intervalRef.current);
120
+ intervalRef.current = null;
121
+ }
122
+ };
123
+ }, [intervalSecondsRemaining > 0, percentComplete]);
124
+ const formattedTimeRemaining = (0, _element.useMemo)(() => {
125
+ if (percentComplete >= 100 || intervalSecondsRemaining !== false && intervalSecondsRemaining <= 0) {
126
+ return mergedLabels.fewSeconds;
127
+ }
128
+ if (!intervalSecondsRemaining) {
129
+ return mergedLabels.calculating;
130
+ }
131
+ return (0, _i18n.sprintf)(mergedLabels.approxRemaining, (0, _time.secondsToDhmsShort)(intervalSecondsRemaining) || '0:01');
132
+ }, [intervalSecondsRemaining, percentComplete, mergedLabels]);
133
+ const formattedEstimatedCompletedTime = (0, _element.useMemo)(() => {
134
+ if (!estimatedCompletedTime) {
135
+ return false;
136
+ }
137
+ return ` (${(0, _dates.convertTimestampToFriendlyTime)(estimatedCompletedTime, textDomain)})`;
138
+ }, [estimatedCompletedTime, textDomain]);
139
+ if (messages[status] !== undefined && messages[status] !== null) {
140
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
141
+ className: className,
142
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.__experimentalText, {
143
+ variant: "muted",
144
+ children: messages[status]
145
+ })
146
+ });
147
+ }
148
+ switch (status) {
149
+ case 'pending':
150
+ case 'queued':
151
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
152
+ className: className,
153
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.__experimentalText, {
154
+ variant: "muted",
155
+ className: "status",
156
+ children: mergedLabels.waiting
157
+ })
158
+ });
159
+ case 'processing':
160
+ if (percentComplete >= 100) {
161
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
162
+ className: className,
163
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.__experimentalText, {
164
+ variant: "muted",
165
+ className: "status",
166
+ children: mergedLabels.completing
167
+ })
168
+ });
169
+ }
170
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
171
+ className: className,
172
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.__experimentalText, {
173
+ variant: "muted",
174
+ className: "status",
175
+ children: mergedLabels.inProgress
176
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.__experimentalText, {
177
+ variant: "muted",
178
+ className: "divider",
179
+ children: "|"
180
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_components.__experimentalText, {
181
+ variant: "muted",
182
+ className: "percent-complete",
183
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
184
+ ref: percentRef,
185
+ children: displayPercent
186
+ }), "% ", mergedLabels.percentSuffix]
187
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.__experimentalText, {
188
+ variant: "muted",
189
+ className: "divider",
190
+ children: "|"
191
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_components.__experimentalText, {
192
+ variant: "muted",
193
+ className: "time-remaining",
194
+ children: [formattedTimeRemaining, showEstimatedCompletedTime && formattedEstimatedCompletedTime]
195
+ })]
196
+ });
197
+ case 'complete':
198
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
199
+ className: className,
200
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.__experimentalText, {
201
+ variant: "muted",
202
+ className: "status",
203
+ children: mergedLabels.complete
204
+ })
205
+ });
206
+ case 'failed':
207
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
208
+ className: className,
209
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.__experimentalText, {
210
+ variant: "muted",
211
+ isDestructive: "true",
212
+ className: "status",
213
+ children: mergedLabels.failed
214
+ }), failedAction && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_element.Fragment, {
215
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.__experimentalText, {
216
+ variant: "muted",
217
+ className: "divider",
218
+ children: "|"
219
+ }), failedAction]
220
+ })]
221
+ });
222
+ default:
223
+ return null;
224
+ }
225
+ };
226
+ var _default = exports.default = ProcessStatusSummary;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "ProcessInfo", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _ProcessInfo.default;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "ProcessInfoRow", {
13
+ enumerable: true,
14
+ get: function () {
15
+ return _ProcessInfoRow.default;
16
+ }
17
+ });
18
+ Object.defineProperty(exports, "ProcessStatusInfo", {
19
+ enumerable: true,
20
+ get: function () {
21
+ return _ProcessStatusInfo.default;
22
+ }
23
+ });
24
+ Object.defineProperty(exports, "ProcessStatusSummary", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _ProcessStatusSummary.default;
28
+ }
29
+ });
30
+ Object.defineProperty(exports, "default", {
31
+ enumerable: true,
32
+ get: function () {
33
+ return _ProcessInfo.default;
34
+ }
35
+ });
36
+ var _ProcessInfo = _interopRequireDefault(require("./ProcessInfo"));
37
+ var _ProcessInfoRow = _interopRequireDefault(require("./ProcessInfoRow"));
38
+ var _ProcessStatusSummary = _interopRequireDefault(require("./ProcessStatusSummary"));
39
+ var _ProcessStatusInfo = _interopRequireDefault(require("./ProcessStatusInfo"));
40
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
@@ -0,0 +1,158 @@
1
+ /*
2
+ * Generic styling for the wp-plugin-utils Process components.
3
+ *
4
+ * `de-wp-plugin-utils-process-info` styles the key/value table rendered by
5
+ * `<ProcessInfo>` / `<ProcessInfoRow>`. Pass the same wrapper class name that
6
+ * is supplied to the `className` prop (defaults to `process-info`).
7
+ *
8
+ * .migration-info {
9
+ * @include de-wp-plugin-utils-process-info();
10
+ * }
11
+ *
12
+ * `de-wp-plugin-utils-process-status-summary` styles the horizontal status
13
+ * row rendered by `<ProcessStatusSummary>`. Pass the same wrapper class name
14
+ * that is supplied to its `className` prop (defaults to `summary`).
15
+ *
16
+ * .migration {
17
+ * @include de-wp-plugin-utils-process-status-summary();
18
+ * }
19
+ */
20
+
21
+ @mixin de-wp-plugin-utils-process-info {
22
+ display: flex;
23
+ flex-direction: column;
24
+ align-items: center;
25
+ justify-content: center;
26
+ width: 100%;
27
+
28
+ .row {
29
+ display: grid;
30
+ grid-template-columns: 150px max-content;
31
+ grid-auto-rows: 25px;
32
+ grid-template-areas: "key value";
33
+ width: 100%;
34
+
35
+ .key {
36
+ display: flex;
37
+ flex-direction: column;
38
+ align-items: flex-start;
39
+ justify-content: center;
40
+ grid-area: key;
41
+ font-weight: 600;
42
+ }
43
+
44
+ .value {
45
+ display: flex;
46
+ flex-direction: row;
47
+ align-items: center;
48
+ justify-content: center;
49
+ grid-area: value;
50
+
51
+ .percentage {
52
+ margin-left: 0.35rem;
53
+ opacity: 0.7;
54
+ font-style: italic;
55
+ }
56
+ }
57
+ }
58
+ }
59
+
60
+ @mixin de-wp-plugin-utils-process-status-summary {
61
+ display: flex;
62
+ flex-direction: row;
63
+ align-items: center;
64
+ justify-content: flex-start;
65
+ gap: 8px;
66
+ }
67
+
68
+ /*
69
+ * `de-wp-plugin-utils-process-status-info` styles the card-level wrapper
70
+ * rendered by `<ProcessStatusInfo>`. Pass the same wrapper class name that is
71
+ * supplied to its `className` prop (defaults to `process`).
72
+ *
73
+ * .migration {
74
+ * @include de-wp-plugin-utils-process-status-info();
75
+ * }
76
+ */
77
+ @mixin de-wp-plugin-utils-process-status-info {
78
+ width: 100%;
79
+ padding: calc(24px);
80
+ display: flex;
81
+ align-items: center;
82
+ flex-direction: column;
83
+ gap: calc(8px);
84
+ justify-content: space-between;
85
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
86
+ box-sizing: border-box;
87
+
88
+ & > .components-h-stack {
89
+ width: 100%;
90
+
91
+ & > .components-v-stack {
92
+ width: 100%;
93
+ flex: 1;
94
+ }
95
+ }
96
+
97
+ &:last-child {
98
+ border-bottom: none;
99
+ }
100
+
101
+ .process-name {
102
+ font-size: 18px;
103
+ font-weight: 500;
104
+ margin-left: 3px;
105
+ }
106
+
107
+ .process-id {
108
+ margin-left: 7px;
109
+ margin-top: 2px;
110
+ }
111
+
112
+ .process-buttons {
113
+ display: flex;
114
+ align-items: center;
115
+ justify-content: flex-end;
116
+ gap: 8px;
117
+ margin-left: 7rem;
118
+ padding-top: 5px;
119
+
120
+ .process-details-icon {
121
+ cursor: pointer;
122
+ }
123
+ }
124
+
125
+ &.cancelled {
126
+ opacity: 0.4;
127
+ pointer-events: none;
128
+ }
129
+
130
+ .summary {
131
+ margin-left: 3px;
132
+ display: flex;
133
+ flex-direction: row;
134
+ align-items: center;
135
+ justify-content: flex-start;
136
+ gap: 8px;
137
+ }
138
+
139
+ .details {
140
+ display: flex;
141
+ flex-direction: column;
142
+ align-items: center;
143
+ justify-content: center;
144
+ width: 100%;
145
+ padding: 0 calc(16px);
146
+ overflow: hidden;
147
+ max-height: 0;
148
+ opacity: 0;
149
+ transition: max-height 0.4s ease-in-out, opacity 0.3s ease-in-out, padding 0.3s ease-in-out;
150
+
151
+ &.open {
152
+ max-height: 800px;
153
+ opacity: 1;
154
+ padding: calc(12px) calc(16px);
155
+ padding-bottom: 0;
156
+ }
157
+ }
158
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _jsxRuntime = require("react/jsx-runtime");
8
+ /**
9
+ * HTML `<progress>` bar with a status modifier class.
10
+ *
11
+ * @param {Object} props
12
+ * @param {number} props.currentValue Current value.
13
+ * @param {number} [props.maxValue=100] Maximum value.
14
+ * @param {string} [props.status] Status modifier appended to the class name (e.g. `processing`).
15
+ * @param {string} [props.id='progress-bar'] DOM id.
16
+ * @param {string} [props.className='progress-bar'] Base class name.
17
+ * @return {JSX.Element}
18
+ */
19
+ const Progress = ({
20
+ currentValue,
21
+ maxValue = 100,
22
+ status,
23
+ id = 'progress-bar',
24
+ className = 'progress-bar'
25
+ }) => {
26
+ const classes = [className, status].filter(Boolean).join(' ');
27
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)("progress", {
28
+ id: id,
29
+ className: classes,
30
+ value: currentValue,
31
+ max: maxValue,
32
+ children: [currentValue, "%"]
33
+ });
34
+ };
35
+ var _default = exports.default = Progress;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "default", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _Progress.default;
10
+ }
11
+ });
12
+ var _Progress = _interopRequireDefault(require("./Progress"));
13
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
@@ -0,0 +1,112 @@
1
+ /*
2
+ * Generic styling for the wp-plugin-utils Progress component.
3
+ *
4
+ * Targets the `<progress>` element rendered by `<Progress>`. Apply to a
5
+ * selector that matches the `className` prop (defaults to `progress-bar`).
6
+ *
7
+ * .progress-bar {
8
+ * @include de-wp-plugin-utils-progress();
9
+ * }
10
+ */
11
+
12
+ $de-wp-plugin-utils-progress-waiting-color: #f1efef !default;
13
+ $de-wp-plugin-utils-progress-processing-color: #006FE6 !default;
14
+ $de-wp-plugin-utils-progress-complete-color: #65C728 !default;
15
+ $de-wp-plugin-utils-progress-border-radius: 5px !default;
16
+ $de-wp-plugin-utils-progress-shimmer-end-color: rgba(255, 255, 255, 0.3) !default;
17
+
18
+ @mixin de-wp-plugin-utils-progress(
19
+ $waiting-color: $de-wp-plugin-utils-progress-waiting-color,
20
+ $processing-color: $de-wp-plugin-utils-progress-processing-color,
21
+ $complete-color: $de-wp-plugin-utils-progress-complete-color,
22
+ $border-radius: $de-wp-plugin-utils-progress-border-radius,
23
+ $shimmer-end-color: $de-wp-plugin-utils-progress-shimmer-end-color
24
+ ) {
25
+ height: 20px;
26
+ width: 100%;
27
+ border-radius: $border-radius;
28
+ position: relative;
29
+
30
+ &::-webkit-progress-bar {
31
+ background: $waiting-color;
32
+ border-radius: $border-radius;
33
+ }
34
+
35
+ &.processing {
36
+ &::after {
37
+ content: '';
38
+ position: absolute;
39
+ top: 0;
40
+ left: 0;
41
+ width: 100%;
42
+ height: 100%;
43
+ background: linear-gradient(
44
+ 90deg,
45
+ transparent 0%,
46
+ $shimmer-end-color 50%,
47
+ transparent 100%
48
+ );
49
+ background-size: 200% 100%;
50
+ border-radius: $border-radius;
51
+ animation: de-wp-plugin-utils-progress-shimmer 2s infinite linear;
52
+ pointer-events: none;
53
+ }
54
+
55
+ &::-webkit-progress-value {
56
+ background: $processing-color;
57
+ animation: none;
58
+ }
59
+
60
+ &::-webkit-progress-bar {
61
+ animation: none;
62
+ }
63
+ }
64
+
65
+ &.pending,
66
+ &.queued {
67
+ &::-webkit-progress-value {
68
+ background: $waiting-color;
69
+ animation-name: de-wp-plugin-utils-progress-pulse-waiting;
70
+ animation-duration: 2s;
71
+ animation-iteration-count: infinite;
72
+ }
73
+
74
+ &::-webkit-progress-bar {
75
+ animation-name: de-wp-plugin-utils-progress-pulse-waiting;
76
+ animation-duration: 2s;
77
+ animation-iteration-count: infinite;
78
+ }
79
+ }
80
+
81
+ &.complete {
82
+ &::-webkit-progress-value {
83
+ background: $complete-color;
84
+ }
85
+ }
86
+
87
+ &::-webkit-progress-value,
88
+ &[value]::-webkit-progress-value {
89
+ border-radius: $border-radius;
90
+ transition: all 1s ease;
91
+ }
92
+ }
93
+
94
+ @keyframes de-wp-plugin-utils-progress-pulse-waiting {
95
+ 0% {
96
+ background-color: hsl( 200, 20%, 70% );
97
+ }
98
+
99
+ 100% {
100
+ background-color: hsl( 200, 20%, 95% );
101
+ }
102
+ }
103
+
104
+ @keyframes de-wp-plugin-utils-progress-shimmer {
105
+ 0% {
106
+ background-position: -200% 0;
107
+ }
108
+
109
+ 100% {
110
+ background-position: 200% 0;
111
+ }
112
+ }
@@ -33,7 +33,39 @@ Object.defineProperty(exports, "NotificationsList", {
33
33
  return _NotificationsDrawer.NotificationsList;
34
34
  }
35
35
  });
36
+ Object.defineProperty(exports, "ProcessInfo", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _Process.default;
40
+ }
41
+ });
42
+ Object.defineProperty(exports, "ProcessInfoRow", {
43
+ enumerable: true,
44
+ get: function () {
45
+ return _Process.ProcessInfoRow;
46
+ }
47
+ });
48
+ Object.defineProperty(exports, "ProcessStatusInfo", {
49
+ enumerable: true,
50
+ get: function () {
51
+ return _Process.ProcessStatusInfo;
52
+ }
53
+ });
54
+ Object.defineProperty(exports, "ProcessStatusSummary", {
55
+ enumerable: true,
56
+ get: function () {
57
+ return _Process.ProcessStatusSummary;
58
+ }
59
+ });
60
+ Object.defineProperty(exports, "Progress", {
61
+ enumerable: true,
62
+ get: function () {
63
+ return _Progress.default;
64
+ }
65
+ });
36
66
  var _Notices = _interopRequireDefault(require("./Notices"));
37
67
  var _NotificationsDrawer = _interopRequireWildcard(require("./NotificationsDrawer"));
68
+ var _Process = _interopRequireWildcard(require("./Process"));
69
+ var _Progress = _interopRequireDefault(require("./Progress"));
38
70
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
39
71
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.secondsToDhmsShortIso = exports.secondsToDhmsShort = exports.secondsToDhms = exports.addSecondsToCurrentTime = void 0;
6
+ exports.secondsToDhmsShortIso = exports.secondsToDhmsShort = exports.secondsToDhms = exports.calculateEstimatedTimeRemainingUsingSMA = exports.calculateEstimatedTimeRemainingUsingEMA = exports.calculateEstimatedTimeRemainingSimple = exports.addSecondsToCurrentTime = void 0;
7
7
  var _strings = require("./strings");
8
8
  /**
9
9
  * Convert seconds to a human readable format.
@@ -80,4 +80,135 @@ const addSecondsToCurrentTime = seconds => {
80
80
  rawDate = new Date(rawDate.getTime() + Number(seconds * 1000));
81
81
  return rawDate.getTime() / 1000;
82
82
  };
83
- exports.addSecondsToCurrentTime = addSecondsToCurrentTime;
83
+
84
+ /**
85
+ * Calculate an estimated time remaining based on elapsed time and percent complete.
86
+ *
87
+ * @param {number} startTime Unix timestamp (seconds) when the process started.
88
+ * @param {number} percentComplete Percent complete (0-100).
89
+ * @return {number}
90
+ */
91
+ exports.addSecondsToCurrentTime = addSecondsToCurrentTime;
92
+ const calculateEstimatedTimeRemainingSimple = (startTime, percentComplete) => {
93
+ if (!startTime || percentComplete <= 0 || percentComplete >= 100) {
94
+ return 0;
95
+ }
96
+ const secondsSinceStartTime = Date.now() / 1000 - Number(startTime);
97
+ const decimalComplete = percentComplete / 100;
98
+ const totalSeconds = secondsSinceStartTime / decimalComplete;
99
+ const remaining = totalSeconds - secondsSinceStartTime;
100
+ return Math.max(1, remaining);
101
+ };
102
+
103
+ /**
104
+ * Calculate an estimated time remaining, using Simple Moving Average (SMA).
105
+ *
106
+ * @param {number[]} times Array of previous run times (seconds).
107
+ * @param {number} window Subset of times to use.
108
+ * @param {number} n Number of loops.
109
+ * @return {number[]}
110
+ */
111
+ exports.calculateEstimatedTimeRemainingSimple = calculateEstimatedTimeRemainingSimple;
112
+ const calculateEstimatedTimeRemainingUsingSMA = (times, window = 2, n = Infinity) => {
113
+ if (!times || times.length < window) {
114
+ return [];
115
+ }
116
+ let index = window - 1;
117
+ const length = times.length + 1;
118
+ const simpleMovingAverages = [];
119
+ let numberOfSMAsCalculated = 0;
120
+ while (++index < length && numberOfSMAsCalculated++ < n) {
121
+ const windowSlice = times.slice(index - window, index);
122
+ const sum = windowSlice.reduce((prev, curr) => prev + curr, 0);
123
+ simpleMovingAverages.push(sum / window);
124
+ }
125
+ return simpleMovingAverages;
126
+ };
127
+
128
+ /**
129
+ * Calculate an estimated time remaining, using Exponential Moving Average (EMA).
130
+ *
131
+ * Each run object should expose at least `start_time`. Optional fields
132
+ * `completed_time`, `last_attempt_time` and `total_time` are used when present.
133
+ *
134
+ * @param {Object[]} runs Array of process run records.
135
+ * @param {number} window Subset of times to use.
136
+ * @param {boolean} useCycleTime Whether to use full cycle time instead of pure processing time.
137
+ * @return {{emaValues: number[], avgTime: number}}
138
+ */
139
+ exports.calculateEstimatedTimeRemainingUsingSMA = calculateEstimatedTimeRemainingUsingSMA;
140
+ const calculateEstimatedTimeRemainingUsingEMA = (runs, window = 2, useCycleTime = true) => {
141
+ if (!runs || runs.length < window + 1) {
142
+ return {
143
+ emaValues: [],
144
+ avgTime: 0
145
+ };
146
+ }
147
+ const sortedRuns = [...runs].sort((a, b) => Number(a.start_time) - Number(b.start_time));
148
+ let timesToUse = [];
149
+ if (useCycleTime) {
150
+ const runsWithCycleTimes = [];
151
+ for (let i = 1; i < sortedRuns.length; i++) {
152
+ const previousRun = sortedRuns[i - 1];
153
+ const currentRun = sortedRuns[i];
154
+ const processingTime = currentRun.completed_time ? Number(currentRun.completed_time) - Number(currentRun.start_time) : currentRun.last_attempt_time ? Number(currentRun.last_attempt_time) - Number(currentRun.start_time) : 0;
155
+ const previousEndTime = previousRun.completed_time ? Number(previousRun.completed_time) : previousRun.last_attempt_time ? Number(previousRun.last_attempt_time) : Number(previousRun.start_time);
156
+ const currentEndTime = currentRun.completed_time ? Number(currentRun.completed_time) : currentRun.last_attempt_time ? Number(currentRun.last_attempt_time) : Number(currentRun.start_time);
157
+ const cycleTime = currentEndTime - previousEndTime;
158
+ if (cycleTime > 0 && processingTime > 0) {
159
+ runsWithCycleTimes.push({
160
+ ...currentRun,
161
+ total_time: processingTime,
162
+ cycle_time: cycleTime
163
+ });
164
+ }
165
+ }
166
+ if (runsWithCycleTimes.length < window) {
167
+ return {
168
+ emaValues: [],
169
+ avgTime: 0
170
+ };
171
+ }
172
+ timesToUse = runsWithCycleTimes.map(run => run.cycle_time);
173
+ } else {
174
+ timesToUse = sortedRuns.map(run => {
175
+ if (typeof run.total_time === 'number' && run.total_time > 0) {
176
+ return run.total_time;
177
+ }
178
+ const startTime = run.start_time ? Number(run.start_time) : null;
179
+ const endTime = run.completed_time ? Number(run.completed_time) : run.last_attempt_time ? Number(run.last_attempt_time) : null;
180
+ return startTime && endTime ? endTime - startTime : 0;
181
+ }).filter(time => time > 0);
182
+ if (timesToUse.length < window) {
183
+ return {
184
+ emaValues: [],
185
+ avgTime: 0
186
+ };
187
+ }
188
+ }
189
+ let index = window - 1;
190
+ let previousEmaIndex = 0;
191
+ const length = timesToUse.length;
192
+ const smoothingFactor = 2 / (window + 1);
193
+ const exponentialMovingAverages = [];
194
+ const smaValues = calculateEstimatedTimeRemainingUsingSMA(timesToUse, window, 1);
195
+ const sma = smaValues.length > 0 ? smaValues[0] : 0;
196
+ if (sma <= 0) {
197
+ return {
198
+ emaValues: [],
199
+ avgTime: 0
200
+ };
201
+ }
202
+ exponentialMovingAverages.push(sma);
203
+ while (++index < length) {
204
+ const value = timesToUse[index];
205
+ const previousEma = exponentialMovingAverages[previousEmaIndex++];
206
+ const currentEma = (value - previousEma) * smoothingFactor + previousEma;
207
+ exponentialMovingAverages.push(currentEma);
208
+ }
209
+ return {
210
+ emaValues: exponentialMovingAverages,
211
+ avgTime: exponentialMovingAverages.length > 0 ? exponentialMovingAverages[exponentialMovingAverages.length - 1] : 0
212
+ };
213
+ };
214
+ exports.calculateEstimatedTimeRemainingUsingEMA = calculateEstimatedTimeRemainingUsingEMA;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dream-encode/wp-js-plugin-utils",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Common JS functionality used by custom WP plugins.",
5
5
  "keywords": [
6
6
  "wordpress",
@@ -28,6 +28,8 @@
28
28
  "./data-migrations": "./dist/data-migrations/index.js",
29
29
  "./components": "./dist/components/index.js",
30
30
  "./components/notifications-drawer/styles": "./dist/components/NotificationsDrawer/styles/_notifications-drawer.scss",
31
+ "./components/process/styles": "./dist/components/Process/styles/_process.scss",
32
+ "./components/progress/styles": "./dist/components/Progress/styles/_progress.scss",
31
33
  "./hooks": "./dist/hooks/index.js",
32
34
  "./api": "./dist/api/index.js",
33
35
  "./utils": "./dist/utils/index.js",