@whooshbang/sdk 5.1.0 → 6.0.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.
- package/README.md +232 -83
- package/dist/cli/commands.js +2 -0
- package/dist/cli/commands.js.map +1 -1
- package/dist/client.d.ts +139 -1
- package/dist/client.js +448 -4
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js.map +1 -1
- package/dist/interactions.js +18 -3
- package/dist/interactions.js.map +1 -1
- package/dist/recipient/element.js +641 -14
- package/dist/recipient/element.js.map +1 -1
- package/dist/recipient/i18n/ar.js +77 -0
- package/dist/recipient/i18n/ar.js.map +1 -1
- package/dist/recipient/i18n/catalog.d.ts +75 -0
- package/dist/recipient/i18n/catalog.js.map +1 -1
- package/dist/recipient/i18n/de.js +82 -0
- package/dist/recipient/i18n/de.js.map +1 -1
- package/dist/recipient/i18n/en.js +82 -0
- package/dist/recipient/i18n/en.js.map +1 -1
- package/dist/recipient/i18n/es.js +82 -0
- package/dist/recipient/i18n/es.js.map +1 -1
- package/dist/recipient/i18n/fa.js +77 -0
- package/dist/recipient/i18n/fa.js.map +1 -1
- package/dist/recipient/i18n/fr.js +82 -0
- package/dist/recipient/i18n/fr.js.map +1 -1
- package/dist/recipient/i18n/hi.js +82 -0
- package/dist/recipient/i18n/hi.js.map +1 -1
- package/dist/recipient/i18n/id.js +82 -0
- package/dist/recipient/i18n/id.js.map +1 -1
- package/dist/recipient/i18n/it.js +82 -0
- package/dist/recipient/i18n/it.js.map +1 -1
- package/dist/recipient/i18n/nl.js +82 -0
- package/dist/recipient/i18n/nl.js.map +1 -1
- package/dist/recipient/i18n/pl.js +82 -0
- package/dist/recipient/i18n/pl.js.map +1 -1
- package/dist/recipient/i18n/pt-BR.js +82 -0
- package/dist/recipient/i18n/pt-BR.js.map +1 -1
- package/dist/recipient/i18n/ru.js +82 -0
- package/dist/recipient/i18n/ru.js.map +1 -1
- package/dist/recipient/i18n/tr.js +82 -0
- package/dist/recipient/i18n/tr.js.map +1 -1
- package/dist/recipient/i18n/uk.js +82 -0
- package/dist/recipient/i18n/uk.js.map +1 -1
- package/dist/recipient/i18n/zh-Hans.js +77 -0
- package/dist/recipient/i18n/zh-Hans.js.map +1 -1
- package/dist/recipient/order-section.d.ts +41 -0
- package/dist/recipient/order-section.js +547 -0
- package/dist/recipient/order-section.js.map +1 -0
- package/dist/recipient/order-view.d.ts +123 -0
- package/dist/recipient/order-view.js +304 -0
- package/dist/recipient/order-view.js.map +1 -0
- package/dist/recipient/platform-mark.d.ts +1 -0
- package/dist/recipient/platform-mark.js +57 -0
- package/dist/recipient/platform-mark.js.map +1 -0
- package/dist/recipient/quiet-section.d.ts +47 -0
- package/dist/recipient/quiet-section.js +490 -0
- package/dist/recipient/quiet-section.js.map +1 -0
- package/dist/recipient/quiet-view.d.ts +84 -0
- package/dist/recipient/quiet-view.js +250 -0
- package/dist/recipient/quiet-view.js.map +1 -0
- package/dist/recipient/view.d.ts +2 -0
- package/dist/recipient/view.js.map +1 -1
- package/dist/webhooks.d.ts +30 -1
- package/dist/webhooks.js +57 -4
- package/dist/webhooks.js.map +1 -1
- package/package.json +12 -2
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { Channel, EscalationOrder, SaveEscalationOrderRequest } from "@whooshbang/contracts";
|
|
2
|
+
/**
|
|
3
|
+
* The recipient's own escalation order, as the "If you don't answer" section
|
|
4
|
+
* edits it (FXO-3672).
|
|
5
|
+
*
|
|
6
|
+
* Pure, so the rules — what a save sends, what counts as a change, what can
|
|
7
|
+
* be added back — are testable without a browser, and the element stays a
|
|
8
|
+
* renderer.
|
|
9
|
+
*/
|
|
10
|
+
export declare const ORDER_LEVELS: readonly ["low", "normal", "high", "critical"];
|
|
11
|
+
export type OrderLevel = (typeof ORDER_LEVELS)[number];
|
|
12
|
+
/** A new step's wait, as the canvas shows it. */
|
|
13
|
+
export declare const ORDER_DEFAULT_WAIT_MINUTES = 5;
|
|
14
|
+
export declare const ORDER_WAIT_MINUTES_MIN = 1;
|
|
15
|
+
/**
|
|
16
|
+
* The editors' ceiling: three digits, so the box stays small (owner
|
|
17
|
+
* 2026-11-01). The API still accepts the contract's 1440.
|
|
18
|
+
*/
|
|
19
|
+
export declare const ORDER_WAIT_MINUTES_MAX = 999;
|
|
20
|
+
/** One row of the list being edited. */
|
|
21
|
+
export interface OrderStepDraft {
|
|
22
|
+
/** Absent for a suggested step on a channel the recipient never connected. */
|
|
23
|
+
readonly bindingId?: string;
|
|
24
|
+
readonly channel: Channel;
|
|
25
|
+
readonly maskedAddress?: string;
|
|
26
|
+
readonly waitMinutes: number;
|
|
27
|
+
/** The step will be skipped when an escalation reaches it. */
|
|
28
|
+
readonly unavailable: boolean;
|
|
29
|
+
readonly reason?: NonNullable<EscalationOrder["steps"][number]["reason"]>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The project's limits on a personal order (FXO-2105): the editors offer no
|
|
33
|
+
* step past `maxSteps` and no wait outside the range, as the API refuses
|
|
34
|
+
* them. Absent from an older order, which means the contract's maxima.
|
|
35
|
+
*/
|
|
36
|
+
export interface OrderLimits {
|
|
37
|
+
readonly maxSteps: number;
|
|
38
|
+
readonly minWaitMinutes: number;
|
|
39
|
+
readonly maxWaitMinutes: number;
|
|
40
|
+
}
|
|
41
|
+
export declare function orderLimits(order: EscalationOrder): OrderLimits;
|
|
42
|
+
/** An edit in progress on one tier, and the version it started from. */
|
|
43
|
+
export interface OrderDraft {
|
|
44
|
+
readonly level: OrderLevel;
|
|
45
|
+
/** The tier's `version` when editing began; a save sends exactly this. */
|
|
46
|
+
readonly baseVersion: string | undefined;
|
|
47
|
+
readonly steps: readonly OrderStepDraft[];
|
|
48
|
+
readonly limits: OrderLimits;
|
|
49
|
+
}
|
|
50
|
+
/** One binding the recipient can add back. */
|
|
51
|
+
export interface OrderCandidate {
|
|
52
|
+
readonly bindingId: string;
|
|
53
|
+
readonly channel: Channel;
|
|
54
|
+
readonly maskedAddress?: string;
|
|
55
|
+
}
|
|
56
|
+
export declare function orderSteps(order: EscalationOrder): OrderStepDraft[];
|
|
57
|
+
/**
|
|
58
|
+
* The order as an editor starts it. One saved before the project narrowed a
|
|
59
|
+
* limit starts trimmed to it, as an escalation would run it, so saving
|
|
60
|
+
* keeps what runs and Discard keeps what was saved.
|
|
61
|
+
*/
|
|
62
|
+
export declare function startDraft(order: EscalationOrder): OrderDraft;
|
|
63
|
+
/** Whether the draft has room for another step under the project's limit. */
|
|
64
|
+
export declare function canAddStep(draft: OrderDraft): boolean;
|
|
65
|
+
/** The steps a save of this draft would store. */
|
|
66
|
+
export declare function savedSteps(draft: OrderDraft, order: EscalationOrder): OrderStepDraft[];
|
|
67
|
+
export declare function saveRequest(draft: OrderDraft, order: EscalationOrder): SaveEscalationOrderRequest;
|
|
68
|
+
/** Whether the draft differs from what the tier holds now. */
|
|
69
|
+
export declare function draftChanged(draft: OrderDraft | null, order: EscalationOrder): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Bindings the recipient can add: every binding the tier offers back, and any
|
|
72
|
+
* usable one the draft has taken out. A binding that went away cannot be added
|
|
73
|
+
* again; it was only ever kept.
|
|
74
|
+
*/
|
|
75
|
+
export declare function orderCandidates(draft: OrderDraft, order: EscalationOrder): OrderCandidate[];
|
|
76
|
+
/**
|
|
77
|
+
* Whether the step limit is what keeps the recipient from adding a channel
|
|
78
|
+
* they hold, so the editor says why the list is gone.
|
|
79
|
+
*/
|
|
80
|
+
export declare function atStepLimit(draft: OrderDraft, order: EscalationOrder): boolean;
|
|
81
|
+
export declare function moveStep(draft: OrderDraft, from: number, to: number): OrderDraft;
|
|
82
|
+
export declare function removeStep(draft: OrderDraft, index: number): OrderDraft;
|
|
83
|
+
export declare function addStep(draft: OrderDraft, candidate: OrderCandidate): OrderDraft;
|
|
84
|
+
/**
|
|
85
|
+
* What a wait box keeps of a keystroke: one that would make it longer than
|
|
86
|
+
* three digits is refused and the box keeps what it showed. A longer wait
|
|
87
|
+
* saved elsewhere (the API allows 1440) can still be shortened.
|
|
88
|
+
*/
|
|
89
|
+
export declare function typedWait(typed: string, shown: string): string;
|
|
90
|
+
/** How many digits a wait box is sized for: three, or more for a longer saved wait. */
|
|
91
|
+
export declare function waitBoxDigits(minutes: number): number;
|
|
92
|
+
/**
|
|
93
|
+
* The waits an editor accepts: the project's range, and at most three
|
|
94
|
+
* digits for the box (unless the project's shortest wait is longer).
|
|
95
|
+
*/
|
|
96
|
+
export declare function waitRange(limits?: OrderLimits): {
|
|
97
|
+
readonly min: number;
|
|
98
|
+
readonly max: number;
|
|
99
|
+
};
|
|
100
|
+
/** A typed wait as whole minutes in range, or null for nonsense. */
|
|
101
|
+
export declare function normalizeWait(typed: string, limits?: OrderLimits): number | null;
|
|
102
|
+
/** A typed wait, held to whole minutes in range; nonsense keeps the old one. */
|
|
103
|
+
export declare function setWait(draft: OrderDraft, index: number, typed: string): OrderDraft;
|
|
104
|
+
/**
|
|
105
|
+
* Re-reads each step's availability from a refreshed tier, so a channel the
|
|
106
|
+
* recipient reconnected mid-edit is no longer treated as one to leave out.
|
|
107
|
+
*/
|
|
108
|
+
export declare function refreshDraft(draft: OrderDraft, order: EscalationOrder): OrderDraft;
|
|
109
|
+
/**
|
|
110
|
+
* "Telegram, wait 5 min → Slack, wait 5 min → Email", from the steps a save
|
|
111
|
+
* would store. `step` is the copy's `orderPreviewStep`, with `{channel}` and
|
|
112
|
+
* `{minutes}`.
|
|
113
|
+
*/
|
|
114
|
+
export declare function orderPreviewText(input: {
|
|
115
|
+
readonly step: string;
|
|
116
|
+
readonly channelName: (channel: Channel) => string;
|
|
117
|
+
readonly draft: OrderDraft;
|
|
118
|
+
readonly order: EscalationOrder;
|
|
119
|
+
}): string;
|
|
120
|
+
/** "Outage alerts, Security" for "Used for", in the reader's language. */
|
|
121
|
+
export declare function orderListText(locale: string, items: readonly string[]): string;
|
|
122
|
+
/** A stable key for a row, for focus and drag bookkeeping. */
|
|
123
|
+
export declare function stepKey(step: OrderStepDraft, index: number): string;
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The recipient's own escalation order, as the "If you don't answer" section
|
|
3
|
+
* edits it (FXO-3672).
|
|
4
|
+
*
|
|
5
|
+
* Pure, so the rules — what a save sends, what counts as a change, what can
|
|
6
|
+
* be added back — are testable without a browser, and the element stays a
|
|
7
|
+
* renderer.
|
|
8
|
+
*/
|
|
9
|
+
export const ORDER_LEVELS = ["low", "normal", "high", "critical"];
|
|
10
|
+
/** A new step's wait, as the canvas shows it. */
|
|
11
|
+
export const ORDER_DEFAULT_WAIT_MINUTES = 5;
|
|
12
|
+
export const ORDER_WAIT_MINUTES_MIN = 1;
|
|
13
|
+
/**
|
|
14
|
+
* The editors' ceiling: three digits, so the box stays small (owner
|
|
15
|
+
* 2026-11-01). The API still accepts the contract's 1440.
|
|
16
|
+
*/
|
|
17
|
+
export const ORDER_WAIT_MINUTES_MAX = 999;
|
|
18
|
+
const ORDER_WAIT_MAX_DIGITS = String(ORDER_WAIT_MINUTES_MAX).length;
|
|
19
|
+
export function orderLimits(order) {
|
|
20
|
+
return {
|
|
21
|
+
maxSteps: order.limits?.max_steps ?? 20,
|
|
22
|
+
maxWaitMinutes: order.limits?.max_wait_minutes ?? 1440,
|
|
23
|
+
minWaitMinutes: order.limits?.min_wait_minutes ?? 1,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** The generated step type is loose about extra keys; read it as a string. */
|
|
27
|
+
function maskedOf(step) {
|
|
28
|
+
const masked = step.masked_address;
|
|
29
|
+
return typeof masked === "string" ? { maskedAddress: masked } : {};
|
|
30
|
+
}
|
|
31
|
+
export function orderSteps(order) {
|
|
32
|
+
return order.steps.map((step) => ({
|
|
33
|
+
...(step.binding_id === undefined ? {} : { bindingId: step.binding_id }),
|
|
34
|
+
channel: step.channel,
|
|
35
|
+
...maskedOf(step),
|
|
36
|
+
waitMinutes: step.wait_minutes,
|
|
37
|
+
unavailable: step.state === "unavailable",
|
|
38
|
+
...(step.reason === undefined ? {} : { reason: step.reason }),
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The order as an editor starts it. One saved before the project narrowed a
|
|
43
|
+
* limit starts trimmed to it, as an escalation would run it, so saving
|
|
44
|
+
* keeps what runs and Discard keeps what was saved.
|
|
45
|
+
*/
|
|
46
|
+
export function startDraft(order) {
|
|
47
|
+
const limits = orderLimits(order);
|
|
48
|
+
const steps = orderSteps(order).slice(0, limits.maxSteps);
|
|
49
|
+
return {
|
|
50
|
+
baseVersion: order.version,
|
|
51
|
+
level: order.level,
|
|
52
|
+
limits,
|
|
53
|
+
steps: steps.map((step) => withinWaitLimits(step, limits)),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* A step's wait held to the project's range. Every step's wait counts: an
|
|
58
|
+
* escalation waits on the last channel too before it moves on or ends.
|
|
59
|
+
*/
|
|
60
|
+
function withinWaitLimits(step, limits) {
|
|
61
|
+
return step.waitMinutes < limits.minWaitMinutes ||
|
|
62
|
+
step.waitMinutes > limits.maxWaitMinutes
|
|
63
|
+
? {
|
|
64
|
+
...step,
|
|
65
|
+
waitMinutes: Math.min(limits.maxWaitMinutes, Math.max(limits.minWaitMinutes, step.waitMinutes)),
|
|
66
|
+
}
|
|
67
|
+
: step;
|
|
68
|
+
}
|
|
69
|
+
/** Whether the draft has room for another step under the project's limit. */
|
|
70
|
+
export function canAddStep(draft) {
|
|
71
|
+
return draft.steps.length < draft.limits.maxSteps;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Whether a row survives a save.
|
|
75
|
+
*
|
|
76
|
+
* A personal order may keep a step whose binding went away: it stays visible
|
|
77
|
+
* and is skipped, and the service accepts it back unchanged. An inherited
|
|
78
|
+
* order's unusable step cannot become personal — there is no binding to name,
|
|
79
|
+
* or the service would refuse an inactive one — so a save leaves it out, and
|
|
80
|
+
* the preview says so by not listing it.
|
|
81
|
+
*/
|
|
82
|
+
function kept(step, personal) {
|
|
83
|
+
if (step.bindingId === undefined)
|
|
84
|
+
return false;
|
|
85
|
+
return !step.unavailable || personal;
|
|
86
|
+
}
|
|
87
|
+
/** The steps a save of this draft would store. */
|
|
88
|
+
export function savedSteps(draft, order) {
|
|
89
|
+
const personal = order.source === "subscriber_override";
|
|
90
|
+
return draft.steps.filter((step) => kept(step, personal));
|
|
91
|
+
}
|
|
92
|
+
export function saveRequest(draft, order) {
|
|
93
|
+
// Every wait within the project's range, including ones the editor does
|
|
94
|
+
// not show (the last step's) or a step that stopped being last.
|
|
95
|
+
const steps = savedSteps(draft, order).map((step) => ({
|
|
96
|
+
binding_id: step.bindingId,
|
|
97
|
+
wait_minutes: withinWaitLimits(step, draft.limits).waitMinutes,
|
|
98
|
+
}));
|
|
99
|
+
return {
|
|
100
|
+
steps: steps,
|
|
101
|
+
...(draft.baseVersion === undefined ? {} : { version: draft.baseVersion }),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function signature(steps) {
|
|
105
|
+
return JSON.stringify(steps.map((step, index) => [
|
|
106
|
+
step.bindingId ?? `${step.channel}#${String(index)}`,
|
|
107
|
+
// The last step's wait is never shown here, so it is no change; a
|
|
108
|
+
// save and a send both hold it to the limits.
|
|
109
|
+
index === steps.length - 1 ? null : step.waitMinutes,
|
|
110
|
+
]));
|
|
111
|
+
}
|
|
112
|
+
/** Whether the draft differs from what the tier holds now. */
|
|
113
|
+
export function draftChanged(draft, order) {
|
|
114
|
+
return (draft !== null && signature(draft.steps) !== signature(orderSteps(order)));
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Bindings the recipient can add: every binding the tier offers back, and any
|
|
118
|
+
* usable one the draft has taken out. A binding that went away cannot be added
|
|
119
|
+
* again; it was only ever kept.
|
|
120
|
+
*/
|
|
121
|
+
export function orderCandidates(draft, order) {
|
|
122
|
+
// At the project's step limit there is nothing to add.
|
|
123
|
+
if (!canAddStep(draft))
|
|
124
|
+
return [];
|
|
125
|
+
return addable(draft, order);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Whether the step limit is what keeps the recipient from adding a channel
|
|
129
|
+
* they hold, so the editor says why the list is gone.
|
|
130
|
+
*/
|
|
131
|
+
export function atStepLimit(draft, order) {
|
|
132
|
+
return !canAddStep(draft) && addable(draft, order).length > 0;
|
|
133
|
+
}
|
|
134
|
+
function addable(draft, order) {
|
|
135
|
+
const inDraft = new Set(draft.steps.map((step) => step.bindingId));
|
|
136
|
+
const candidates = [];
|
|
137
|
+
const offer = (candidate) => {
|
|
138
|
+
if (inDraft.has(candidate.bindingId))
|
|
139
|
+
return;
|
|
140
|
+
if (candidates.some((known) => known.bindingId === candidate.bindingId))
|
|
141
|
+
return;
|
|
142
|
+
candidates.push(candidate);
|
|
143
|
+
};
|
|
144
|
+
for (const step of order.steps)
|
|
145
|
+
if (step.state === "available" && step.binding_id !== undefined)
|
|
146
|
+
offer({
|
|
147
|
+
bindingId: step.binding_id,
|
|
148
|
+
channel: step.channel,
|
|
149
|
+
...maskedOf(step),
|
|
150
|
+
});
|
|
151
|
+
for (const binding of order.available_bindings)
|
|
152
|
+
offer({
|
|
153
|
+
bindingId: binding.binding_id,
|
|
154
|
+
channel: binding.channel,
|
|
155
|
+
...(binding.masked_address === undefined
|
|
156
|
+
? {}
|
|
157
|
+
: { maskedAddress: binding.masked_address }),
|
|
158
|
+
});
|
|
159
|
+
return candidates;
|
|
160
|
+
}
|
|
161
|
+
export function moveStep(draft, from, to) {
|
|
162
|
+
if (from === to || to < 0 || to >= draft.steps.length)
|
|
163
|
+
return draft;
|
|
164
|
+
const steps = draft.steps.slice();
|
|
165
|
+
const [moved] = steps.splice(from, 1);
|
|
166
|
+
if (moved === undefined)
|
|
167
|
+
return draft;
|
|
168
|
+
steps.splice(to, 0, moved);
|
|
169
|
+
return { ...draft, steps };
|
|
170
|
+
}
|
|
171
|
+
export function removeStep(draft, index) {
|
|
172
|
+
return {
|
|
173
|
+
...draft,
|
|
174
|
+
steps: draft.steps.filter((_, position) => position !== index),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
export function addStep(draft, candidate) {
|
|
178
|
+
if (!canAddStep(draft))
|
|
179
|
+
return draft;
|
|
180
|
+
return {
|
|
181
|
+
...draft,
|
|
182
|
+
steps: [
|
|
183
|
+
...draft.steps,
|
|
184
|
+
{
|
|
185
|
+
bindingId: candidate.bindingId,
|
|
186
|
+
channel: candidate.channel,
|
|
187
|
+
...(candidate.maskedAddress === undefined
|
|
188
|
+
? {}
|
|
189
|
+
: { maskedAddress: candidate.maskedAddress }),
|
|
190
|
+
unavailable: false,
|
|
191
|
+
waitMinutes: Math.min(waitRange(draft.limits).max, Math.max(waitRange(draft.limits).min, ORDER_DEFAULT_WAIT_MINUTES)),
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* What a wait box keeps of a keystroke: one that would make it longer than
|
|
198
|
+
* three digits is refused and the box keeps what it showed. A longer wait
|
|
199
|
+
* saved elsewhere (the API allows 1440) can still be shortened.
|
|
200
|
+
*/
|
|
201
|
+
export function typedWait(typed, shown) {
|
|
202
|
+
return /^\d+$/u.test(typed) &&
|
|
203
|
+
typed.length > ORDER_WAIT_MAX_DIGITS &&
|
|
204
|
+
typed.length > shown.length
|
|
205
|
+
? shown
|
|
206
|
+
: typed;
|
|
207
|
+
}
|
|
208
|
+
/** How many digits a wait box is sized for: three, or more for a longer saved wait. */
|
|
209
|
+
export function waitBoxDigits(minutes) {
|
|
210
|
+
return Math.max(ORDER_WAIT_MAX_DIGITS, String(minutes).length);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* The waits an editor accepts: the project's range, and at most three
|
|
214
|
+
* digits for the box (unless the project's shortest wait is longer).
|
|
215
|
+
*/
|
|
216
|
+
export function waitRange(limits) {
|
|
217
|
+
const min = Math.max(ORDER_WAIT_MINUTES_MIN, limits?.minWaitMinutes ?? 1);
|
|
218
|
+
return {
|
|
219
|
+
max: Math.max(min, Math.min(ORDER_WAIT_MINUTES_MAX, limits?.maxWaitMinutes ?? 1440)),
|
|
220
|
+
min,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/** A typed wait as whole minutes in range, or null for nonsense. */
|
|
224
|
+
export function normalizeWait(typed, limits) {
|
|
225
|
+
const parsed = Number.parseInt(typed, 10);
|
|
226
|
+
const range = waitRange(limits);
|
|
227
|
+
return Number.isNaN(parsed)
|
|
228
|
+
? null
|
|
229
|
+
: Math.min(range.max, Math.max(range.min, parsed));
|
|
230
|
+
}
|
|
231
|
+
/** A typed wait, held to whole minutes in range; nonsense keeps the old one. */
|
|
232
|
+
export function setWait(draft, index, typed) {
|
|
233
|
+
const minutes = normalizeWait(typed, draft.limits);
|
|
234
|
+
return {
|
|
235
|
+
...draft,
|
|
236
|
+
steps: draft.steps.map((step, position) => position !== index || minutes === null
|
|
237
|
+
? step
|
|
238
|
+
: { ...step, waitMinutes: minutes }),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Re-reads each step's availability from a refreshed tier, so a channel the
|
|
243
|
+
* recipient reconnected mid-edit is no longer treated as one to leave out.
|
|
244
|
+
*/
|
|
245
|
+
export function refreshDraft(draft, order) {
|
|
246
|
+
return {
|
|
247
|
+
...draft,
|
|
248
|
+
steps: draft.steps.map((step) => {
|
|
249
|
+
if (step.bindingId === undefined)
|
|
250
|
+
return step;
|
|
251
|
+
const listed = order.steps.find((candidate) => candidate.binding_id === step.bindingId);
|
|
252
|
+
if (listed)
|
|
253
|
+
return {
|
|
254
|
+
bindingId: step.bindingId,
|
|
255
|
+
channel: step.channel,
|
|
256
|
+
...maskedOf(listed),
|
|
257
|
+
unavailable: listed.state === "unavailable",
|
|
258
|
+
...(listed.reason === undefined ? {} : { reason: listed.reason }),
|
|
259
|
+
waitMinutes: step.waitMinutes,
|
|
260
|
+
};
|
|
261
|
+
const offered = order.available_bindings.find((binding) => binding.binding_id === step.bindingId);
|
|
262
|
+
if (offered)
|
|
263
|
+
return {
|
|
264
|
+
bindingId: step.bindingId,
|
|
265
|
+
channel: step.channel,
|
|
266
|
+
...(offered.masked_address === undefined
|
|
267
|
+
? {}
|
|
268
|
+
: { maskedAddress: offered.masked_address }),
|
|
269
|
+
unavailable: false,
|
|
270
|
+
waitMinutes: step.waitMinutes,
|
|
271
|
+
};
|
|
272
|
+
return step;
|
|
273
|
+
}),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* "Telegram, wait 5 min → Slack, wait 5 min → Email", from the steps a save
|
|
278
|
+
* would store. `step` is the copy's `orderPreviewStep`, with `{channel}` and
|
|
279
|
+
* `{minutes}`.
|
|
280
|
+
*/
|
|
281
|
+
export function orderPreviewText(input) {
|
|
282
|
+
const steps = savedSteps(input.draft, input.order);
|
|
283
|
+
return steps
|
|
284
|
+
.map((step, index) => index === steps.length - 1
|
|
285
|
+
? input.channelName(step.channel)
|
|
286
|
+
: input.step
|
|
287
|
+
.replaceAll("{channel}", input.channelName(step.channel))
|
|
288
|
+
.replaceAll("{minutes}", String(step.waitMinutes)))
|
|
289
|
+
.join(" → ");
|
|
290
|
+
}
|
|
291
|
+
/** "Outage alerts, Security" for "Used for", in the reader's language. */
|
|
292
|
+
export function orderListText(locale, items) {
|
|
293
|
+
try {
|
|
294
|
+
return new Intl.ListFormat(locale, { style: "short", type: "unit" }).format(items);
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
return items.join(", ");
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/** A stable key for a row, for focus and drag bookkeeping. */
|
|
301
|
+
export function stepKey(step, index) {
|
|
302
|
+
return step.bindingId ?? `${step.channel}-${String(index)}`;
|
|
303
|
+
}
|
|
304
|
+
//# sourceMappingURL=order-view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"order-view.js","sourceRoot":"","sources":["../../src/recipient/order-view.ts"],"names":[],"mappings":"AAMA;;;;;;;GAOG;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAU,CAAC;AAG3E,iDAAiD;AACjD,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC;AAC5C,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC;AACxC;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAC1C,MAAM,qBAAqB,GAAG,MAAM,CAAC,sBAAsB,CAAC,CAAC,MAAM,CAAC;AAyBpE,MAAM,UAAU,WAAW,CAAC,KAAsB;IAChD,OAAO;QACL,QAAQ,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI,EAAE;QACvC,cAAc,EAAE,KAAK,CAAC,MAAM,EAAE,gBAAgB,IAAI,IAAI;QACtD,cAAc,EAAE,KAAK,CAAC,MAAM,EAAE,gBAAgB,IAAI,CAAC;KACpD,CAAC;AACJ,CAAC;AAkBD,8EAA8E;AAC9E,SAAS,QAAQ,CAAC,IAAsC;IAGtD,MAAM,MAAM,GAAY,IAAI,CAAC,cAAc,CAAC;IAC5C,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAsB;IAC/C,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAChC,GAAG,CAAC,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;QACxE,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,GAAG,QAAQ,CAAC,IAAI,CAAC;QACjB,WAAW,EAAE,IAAI,CAAC,YAAY;QAC9B,WAAW,EAAE,IAAI,CAAC,KAAK,KAAK,aAAa;QACzC,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;KAC9D,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,KAAsB;IAC/C,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC1D,OAAO;QACL,WAAW,EAAE,KAAK,CAAC,OAAO;QAC1B,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,MAAM;QACN,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;KAC3D,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,gBAAgB,CACvB,IAAoB,EACpB,MAAmB;IAEnB,OAAO,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,cAAc;QAC7C,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,cAAc;QACxC,CAAC,CAAC;YACE,GAAG,IAAI;YACP,WAAW,EAAE,IAAI,CAAC,GAAG,CACnB,MAAM,CAAC,cAAc,EACrB,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,cAAc,EAAE,IAAI,CAAC,WAAW,CAAC,CAClD;SACF;QACH,CAAC,CAAC,IAAI,CAAC;AACX,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,UAAU,CAAC,KAAiB;IAC1C,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC;AACpD,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,IAAI,CAAC,IAAoB,EAAE,QAAiB;IACnD,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAC/C,OAAO,CAAC,IAAI,CAAC,WAAW,IAAI,QAAQ,CAAC;AACvC,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,UAAU,CACxB,KAAiB,EACjB,KAAsB;IAEtB,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,KAAK,qBAAqB,CAAC;IACxD,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,KAAiB,EACjB,KAAsB;IAEtB,wEAAwE;IACxE,gEAAgE;IAChE,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACpD,UAAU,EAAE,IAAI,CAAC,SAAmB;QACpC,YAAY,EAAE,gBAAgB,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,WAAW;KAC/D,CAAC,CAAC,CAAC;IACJ,OAAO;QACL,KAAK,EAAE,KAA4C;QACnD,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC;KAC3E,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,KAAgC;IACjD,OAAO,IAAI,CAAC,SAAS,CACnB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACzB,IAAI,CAAC,SAAS,IAAI,GAAG,IAAI,CAAC,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE;QACpD,kEAAkE;QAClE,8CAA8C;QAC9C,KAAK,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW;KACrD,CAAC,CACH,CAAC;AACJ,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,YAAY,CAC1B,KAAwB,EACxB,KAAsB;IAEtB,OAAO,CACL,KAAK,KAAK,IAAI,IAAI,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAC1E,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,KAAiB,EACjB,KAAsB;IAEtB,uDAAuD;IACvD,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,OAAO,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,KAAiB,EACjB,KAAsB;IAEtB,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,OAAO,CAAC,KAAiB,EAAE,KAAsB;IACxD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;IACnE,MAAM,UAAU,GAAqB,EAAE,CAAC;IACxC,MAAM,KAAK,GAAG,CAAC,SAAyB,EAAQ,EAAE;QAChD,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC;YAAE,OAAO;QAC7C,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,KAAK,SAAS,CAAC,SAAS,CAAC;YACrE,OAAO;QACT,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC7B,CAAC,CAAC;IACF,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK;QAC5B,IAAI,IAAI,CAAC,KAAK,KAAK,WAAW,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS;YAC7D,KAAK,CAAC;gBACJ,SAAS,EAAE,IAAI,CAAC,UAAU;gBAC1B,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,GAAG,QAAQ,CAAC,IAAI,CAAC;aAClB,CAAC,CAAC;IACP,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,kBAAkB;QAC5C,KAAK,CAAC;YACJ,SAAS,EAAE,OAAO,CAAC,UAAU;YAC7B,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,GAAG,CAAC,OAAO,CAAC,cAAc,KAAK,SAAS;gBACtC,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC,EAAE,aAAa,EAAE,OAAO,CAAC,cAAc,EAAE,CAAC;SAC/C,CAAC,CAAC;IACL,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,QAAQ,CACtB,KAAiB,EACjB,IAAY,EACZ,EAAU;IAEV,IAAI,IAAI,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACpE,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IAClC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACtC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACtC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;IAC3B,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAiB,EAAE,KAAa;IACzD,OAAO;QACL,GAAG,KAAK;QACR,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,KAAK,CAAC;KAC/D,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,OAAO,CACrB,KAAiB,EACjB,SAAyB;IAEzB,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACrC,OAAO;QACL,GAAG,KAAK;QACR,KAAK,EAAE;YACL,GAAG,KAAK,CAAC,KAAK;YACd;gBACE,SAAS,EAAE,SAAS,CAAC,SAAS;gBAC9B,OAAO,EAAE,SAAS,CAAC,OAAO;gBAC1B,GAAG,CAAC,SAAS,CAAC,aAAa,KAAK,SAAS;oBACvC,CAAC,CAAC,EAAE;oBACJ,CAAC,CAAC,EAAE,aAAa,EAAE,SAAS,CAAC,aAAa,EAAE,CAAC;gBAC/C,WAAW,EAAE,KAAK;gBAClB,WAAW,EAAE,IAAI,CAAC,GAAG,CACnB,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAC3B,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,0BAA0B,CAAC,CAClE;aACF;SACF;KACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,KAAa,EAAE,KAAa;IACpD,OAAO,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC;QACzB,KAAK,CAAC,MAAM,GAAG,qBAAqB;QACpC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM;QAC3B,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,KAAK,CAAC;AACZ,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,OAAO,IAAI,CAAC,GAAG,CAAC,qBAAqB,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC;AACjE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,MAAoB;IAI5C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,sBAAsB,EAAE,MAAM,EAAE,cAAc,IAAI,CAAC,CAAC,CAAC;IAC1E,OAAO;QACL,GAAG,EAAE,IAAI,CAAC,GAAG,CACX,GAAG,EACH,IAAI,CAAC,GAAG,CAAC,sBAAsB,EAAE,MAAM,EAAE,cAAc,IAAI,IAAI,CAAC,CACjE;QACD,GAAG;KACJ,CAAC;AACJ,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,aAAa,CAC3B,KAAa,EACb,MAAoB;IAEpB,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAChC,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;QACzB,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,OAAO,CACrB,KAAiB,EACjB,KAAa,EACb,KAAa;IAEb,MAAM,OAAO,GAAG,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACnD,OAAO;QACL,GAAG,KAAK;QACR,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CACxC,QAAQ,KAAK,KAAK,IAAI,OAAO,KAAK,IAAI;YACpC,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,CACtC;KACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC1B,KAAiB,EACjB,KAAsB;IAEtB,OAAO;QACL,GAAG,KAAK;QACR,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YAC9B,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS;gBAAE,OAAO,IAAI,CAAC;YAC9C,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAC7B,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,UAAU,KAAK,IAAI,CAAC,SAAS,CACvD,CAAC;YACF,IAAI,MAAM;gBACR,OAAO;oBACL,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,OAAO,EAAE,IAAI,CAAC,OAAO;oBACrB,GAAG,QAAQ,CAAC,MAAM,CAAC;oBACnB,WAAW,EAAE,MAAM,CAAC,KAAK,KAAK,aAAa;oBAC3C,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;oBACjE,WAAW,EAAE,IAAI,CAAC,WAAW;iBAC9B,CAAC;YACJ,MAAM,OAAO,GAAG,KAAK,CAAC,kBAAkB,CAAC,IAAI,CAC3C,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,KAAK,IAAI,CAAC,SAAS,CACnD,CAAC;YACF,IAAI,OAAO;gBACT,OAAO;oBACL,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,OAAO,EAAE,IAAI,CAAC,OAAO;oBACrB,GAAG,CAAC,OAAO,CAAC,cAAc,KAAK,SAAS;wBACtC,CAAC,CAAC,EAAE;wBACJ,CAAC,CAAC,EAAE,aAAa,EAAE,OAAO,CAAC,cAAc,EAAE,CAAC;oBAC9C,WAAW,EAAE,KAAK;oBAClB,WAAW,EAAE,IAAI,CAAC,WAAW;iBAC9B,CAAC;YACJ,OAAO,IAAI,CAAC;QACd,CAAC,CAAC;KACH,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAKhC;IACC,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IACnD,OAAO,KAAK;SACT,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CACnB,KAAK,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC;QACxB,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC;QACjC,CAAC,CAAC,KAAK,CAAC,IAAI;aACP,UAAU,CAAC,WAAW,EAAE,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;aACxD,UAAU,CAAC,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CACzD;SACA,IAAI,CAAC,KAAK,CAAC,CAAC;AACjB,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,aAAa,CAC3B,MAAc,EACd,KAAwB;IAExB,IAAI,CAAC;QACH,OAAO,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,CACzE,KAAK,CACN,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,OAAO,CAAC,IAAoB,EAAE,KAAa;IACzD,OAAO,IAAI,CAAC,SAAS,IAAI,GAAG,IAAI,CAAC,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AAC9D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function whooshbangMark(): SVGSVGElement;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The official WhooshBang icon (apps/dashboard/public/brand/whooshbang-icon.svg),
|
|
3
|
+
* built as DOM nodes for the panel's "Powered by WhooshBang" line. Inline
|
|
4
|
+
* rather than an image URL, so an integrator's content security policy never
|
|
5
|
+
* blocks it.
|
|
6
|
+
*/
|
|
7
|
+
const SVG = "http://www.w3.org/2000/svg";
|
|
8
|
+
const OUTLINE = "M1377.95,268.31c-26.55-54.14-64.13-102.36-111.65-143.36C1172.84,44.39,1049.96,0,920.24,0c-62.27,0-122.94,10.22-179.54,29.79l-2.41.83L208.69,214.23c-46.33,16.06-39.37,83.64,9.26,89.92l200,25.84c10.04,1.3,16.7,11.07,14.24,20.89l-.06.23c-1.38,5.51-5.45,9.95-10.81,11.81L40.3,495.05c-59.77,20.73-50.79,107.91,11.95,116.02l421.22,54.43c3.97.51,7.49,2.79,9.59,6.19l.19.3c4.44,7.2,1.07,16.66-6.92,19.43l-125.46,43.49c-51.7,17.92-43.94,93.34,10.33,100.35l481.94,62.3v-.03c3.16.44,6.34.85,9.52,1.24,22.23,2.67,44.8,4.04,67.58,4.04,129.72,0,252.6-44.36,346.06-124.93,47.52-41,85.1-89.21,111.65-143.36,28.37-57.82,42.73-119.42,42.73-183.11s-14.36-125.29-42.73-183.11ZM1272.02,502.62c-28.26,145.48-175.01,256.35-351.78,256.35h-346.24c-7.86,0-9.71-10.98-2.28-13.56l125.64-43.56c4.13-1.43,4.88-6.95,1.27-9.43-23.8-16.31-45.12-35.16-63.42-56.11-44.98-51.48-71.64-115.51-71.64-184.9,0-35.04,6.81-68.71,19.33-100.11,7.61-19.05,17.32-37.27,28.91-54.43,61.78-91.52,176.77-153.01,308.43-153.01,34.19,0,67.27,4.14,98.58,11.91,149.05,36.88,258.11,155.26,258.11,295.64,0,17.44-1.68,34.55-4.92,51.19Z";
|
|
9
|
+
const DOTS = [
|
|
10
|
+
["733.64", "451.42", "68.57"],
|
|
11
|
+
["920.27", "451.42", "68.57"],
|
|
12
|
+
["1106.87", "451.42", "68.57"],
|
|
13
|
+
];
|
|
14
|
+
export function whooshbangMark() {
|
|
15
|
+
const svg = document.createElementNS(SVG, "svg");
|
|
16
|
+
svg.setAttribute("aria-hidden", "true");
|
|
17
|
+
svg.setAttribute("focusable", "false");
|
|
18
|
+
svg.setAttribute("viewBox", "0 0 1420.68 902.82");
|
|
19
|
+
svg.setAttribute("width", "20");
|
|
20
|
+
svg.setAttribute("height", "13");
|
|
21
|
+
svg.classList.add("platform-mark");
|
|
22
|
+
const defs = document.createElementNS(SVG, "defs");
|
|
23
|
+
const gradient = document.createElementNS(SVG, "linearGradient");
|
|
24
|
+
gradient.id = "whooshbang-mark-gradient";
|
|
25
|
+
gradient.setAttribute("gradientUnits", "userSpaceOnUse");
|
|
26
|
+
gradient.setAttribute("x1", "1432.99");
|
|
27
|
+
gradient.setAttribute("y1", "451.41");
|
|
28
|
+
gradient.setAttribute("x2", "49.82");
|
|
29
|
+
gradient.setAttribute("y2", "451.41");
|
|
30
|
+
for (const [offset, color] of [
|
|
31
|
+
["0", "#f79e1b"],
|
|
32
|
+
// The mark's own colours, never the integrator's accent.
|
|
33
|
+
["1", "#eb001b"],
|
|
34
|
+
]) {
|
|
35
|
+
const stop = document.createElementNS(SVG, "stop");
|
|
36
|
+
stop.setAttribute("offset", offset);
|
|
37
|
+
stop.setAttribute("stop-color", color);
|
|
38
|
+
gradient.append(stop);
|
|
39
|
+
}
|
|
40
|
+
defs.append(gradient);
|
|
41
|
+
svg.append(defs);
|
|
42
|
+
const fill = "url(#whooshbang-mark-gradient)";
|
|
43
|
+
const outline = document.createElementNS(SVG, "path");
|
|
44
|
+
outline.setAttribute("d", OUTLINE);
|
|
45
|
+
outline.setAttribute("fill", fill);
|
|
46
|
+
svg.append(outline);
|
|
47
|
+
for (const [cx, cy, r] of DOTS) {
|
|
48
|
+
const dot = document.createElementNS(SVG, "circle");
|
|
49
|
+
dot.setAttribute("cx", cx);
|
|
50
|
+
dot.setAttribute("cy", cy);
|
|
51
|
+
dot.setAttribute("r", r);
|
|
52
|
+
dot.setAttribute("fill", fill);
|
|
53
|
+
svg.append(dot);
|
|
54
|
+
}
|
|
55
|
+
return svg;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=platform-mark.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform-mark.js","sourceRoot":"","sources":["../../src/recipient/platform-mark.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Channel, QuietHours } from "@whooshbang/contracts";
|
|
2
|
+
import type { RecipientPanelCopy } from "./i18n/catalog.js";
|
|
3
|
+
import { type QuietBehaviour, type QuietChannel, type QuietDraft, type QuietFieldErrors } from "./quiet-view.js";
|
|
4
|
+
/**
|
|
5
|
+
* "Quiet hours" (FXO-2103): when the recipient would rather not be
|
|
6
|
+
* interrupted, what each channel does then, where a message a channel keeps
|
|
7
|
+
* back goes, and whether critical messages break through.
|
|
8
|
+
*
|
|
9
|
+
* Every control is a native one — toggle buttons, time inputs, radios, a
|
|
10
|
+
* select and a switch — so it works by keyboard, touch and screen reader with
|
|
11
|
+
* nothing to drag. Every value reaches the DOM through `textContent` or a
|
|
12
|
+
* typed property.
|
|
13
|
+
*/
|
|
14
|
+
export declare const QUIET_SECTION_STYLES = "\n.quiet { container-type: inline-size; }\n.quiet .intro { line-height: 1.45; margin: 0 0 1rem; opacity: .85; }\n.quiet .now {\n background: color-mix(in srgb, currentColor 6%, transparent);\n border-radius: .5rem;\n font-weight: 600;\n margin: 0 0 1rem;\n padding: .625rem .875rem;\n}\n.quiet .warning {\n background: color-mix(in srgb, #c62828 8%, transparent);\n border: 1px solid color-mix(in srgb, #c62828 40%, transparent);\n border-radius: .5rem;\n line-height: 1.45;\n margin: 0 0 1rem;\n padding: .75rem .875rem;\n}\n.quiet fieldset { border: 0; margin: 0 0 1.25rem; min-width: 0; padding: 0; }\n.quiet legend { font-size: 1rem; font-weight: 600; margin-bottom: .5rem; padding: 0; }\n.quiet .period {\n border: 1px solid color-mix(in srgb, currentColor 22%, transparent);\n border-radius: .5rem;\n display: flex;\n flex-direction: column;\n gap: .625rem;\n margin-bottom: .625rem;\n padding: .75rem;\n}\n.quiet .period-head { align-items: center; display: flex; justify-content: space-between; gap: .5rem; }\n.quiet .period-head .label { font-size: .9rem; font-weight: 600; }\n.quiet .period-head button.icon {\n align-items: center;\n background: transparent;\n border-radius: .375rem;\n color: inherit;\n display: inline-flex;\n flex: 0 0 auto;\n height: 2.25rem;\n justify-content: center;\n padding: 0;\n width: 2.25rem;\n}\n.quiet .period-head button.icon:disabled { opacity: .35; }\n.quiet .days { display: grid; gap: .25rem; grid-template-columns: repeat(7, minmax(0, 1fr)); }\n.quiet .days button {\n background: transparent;\n border: 1px solid color-mix(in srgb, currentColor 30%, transparent);\n color: inherit;\n min-height: 2.75rem;\n padding: 0 .125rem;\n}\n.quiet .days button[aria-pressed=\"true\"] {\n background: var(--whooshbang-accent, #EB001B);\n border-color: var(--whooshbang-accent, #EB001B);\n color: #fff;\n font-weight: 600;\n}\n.quiet .times { align-items: flex-end; display: flex; flex-wrap: wrap; gap: .75rem; }\n.quiet .times label, .quiet .zone label { display: flex; flex-direction: column; font-size: .85rem; gap: .25rem; }\n.quiet input[type=\"time\"], .quiet select {\n background: Field;\n border: 1px solid color-mix(in srgb, currentColor 35%, transparent);\n border-radius: .375rem;\n box-sizing: border-box;\n color: inherit;\n font: inherit;\n min-height: 2.75rem;\n padding: 0 .5rem;\n}\n.quiet select { max-width: 100%; }\n.quiet input:focus-visible, .quiet select:focus-visible {\n outline: 2px solid var(--whooshbang-accent, #EB001B);\n outline-offset: 2px;\n}\n.quiet [aria-invalid=\"true\"] { border-color: #b42318; }\n.quiet .overnight { font-size: .85rem; opacity: .75; padding-bottom: .75rem; }\n.quiet .overnight[hidden] { display: none; }\n.quiet .error { color: #b42318; font-size: .85rem; margin: 0; }\n.quiet .muted { font-size: .85rem; margin: 0 0 .5rem; opacity: .75; }\n.quiet .zone { margin-top: .75rem; }\n.quiet .choice {\n border: 1px solid color-mix(in srgb, currentColor 22%, transparent);\n border-radius: .5rem;\n margin-bottom: .5rem;\n padding: .5rem .75rem;\n}\n.quiet .choice legend { font-size: .95rem; margin: 0; padding: .25rem 0; }\n.quiet .options { display: flex; flex-wrap: wrap; gap: .25rem 1rem; }\n.quiet .options label, .quiet .withheld label.radio {\n align-items: center;\n cursor: pointer;\n display: inline-flex;\n gap: .5rem;\n min-height: 2.75rem;\n}\n.quiet input[type=\"radio\"] { accent-color: var(--whooshbang-accent, #EB001B); height: 1.15rem; margin: 0; width: 1.15rem; }\n.quiet .withheld { display: flex; flex-direction: column; }\n.quiet .withheld .reroute { margin: 0 0 .5rem 1.65rem; }\n.quiet .critical { align-items: center; display: flex; gap: .75rem; justify-content: space-between; }\n.quiet .critical .text { display: flex; flex-direction: column; gap: .15rem; }\n.quiet .critical .text .name { font-weight: 600; }\n.quiet .critical .text .hint { font-size: .85rem; opacity: .75; }\n.quiet .changes { display: flex; flex-wrap: wrap; gap: .625rem; margin-top: 1.25rem; }\n.quiet .changes button { min-height: 2.75rem; padding: 0 1.1rem; }\n.quiet .changes .save { font-weight: 600; }\n.quiet button.link {\n background: transparent;\n color: var(--whooshbang-accent, #EB001B);\n margin-top: 1rem;\n min-height: 2.5rem;\n padding: 0;\n text-decoration: underline;\n}\n.quiet .quiet-status:empty { display: none; }\n.quiet .quiet-status { margin: .75rem 0 0; }\n@container (max-width: 30rem) {\n .quiet .changes { flex-direction: column; }\n .quiet .changes button { min-height: 3rem; width: 100%; }\n .quiet .days button { font-size: .8rem; }\n}\n";
|
|
15
|
+
export interface QuietSectionProps {
|
|
16
|
+
readonly copy: RecipientPanelCopy;
|
|
17
|
+
readonly locale: string;
|
|
18
|
+
readonly quiet: QuietHours;
|
|
19
|
+
readonly draft: QuietDraft;
|
|
20
|
+
/** Whether Save would change anything. */
|
|
21
|
+
readonly changed: boolean;
|
|
22
|
+
/** Whether there is an edit to discard. */
|
|
23
|
+
readonly edited: boolean;
|
|
24
|
+
readonly errors: QuietFieldErrors;
|
|
25
|
+
/** The channels to show a choice for: the ones this panel offers. */
|
|
26
|
+
readonly channels: readonly QuietChannel[];
|
|
27
|
+
readonly disabled: boolean;
|
|
28
|
+
readonly status: HTMLElement;
|
|
29
|
+
readonly channelName: (channel: Channel) => string;
|
|
30
|
+
readonly now?: Date;
|
|
31
|
+
readonly on: {
|
|
32
|
+
readonly toggleDay: (index: number, day: number) => void;
|
|
33
|
+
/** Typing; the section keeps the box it is in. */
|
|
34
|
+
readonly setTime: (index: number, edge: "start" | "end", value: string) => void;
|
|
35
|
+
readonly addWindow: () => void;
|
|
36
|
+
readonly removeWindow: (index: number) => void;
|
|
37
|
+
readonly setBehaviour: (channel: QuietChannel, behaviour: QuietBehaviour) => void;
|
|
38
|
+
readonly setWhenWithheld: (value: QuietDraft["whenWithheld"]) => void;
|
|
39
|
+
readonly setQuietChannel: (bindingId: string | undefined) => void;
|
|
40
|
+
readonly setCritical: (on: boolean) => void;
|
|
41
|
+
readonly setTimeZone: (zone: string) => void;
|
|
42
|
+
readonly save: () => void;
|
|
43
|
+
readonly discard: () => void;
|
|
44
|
+
readonly turnOff: () => void;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export declare function renderQuietSection(props: QuietSectionProps): HTMLElement;
|