@chatwarden/player-react 0.1.2 → 0.1.3
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 +48 -24
- package/dist/src/Dialog.d.ts.map +1 -1
- package/dist/src/Dialog.js +2 -13
- package/dist/src/Dialog.js.map +1 -1
- package/dist/src/ReportDialog.d.ts +30 -16
- package/dist/src/ReportDialog.d.ts.map +1 -1
- package/dist/src/ReportDialog.js +59 -70
- package/dist/src/ReportDialog.js.map +1 -1
- package/dist/src/i18n.d.ts +26 -2
- package/dist/src/i18n.d.ts.map +1 -1
- package/dist/src/i18n.js +26 -2
- package/dist/src/i18n.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/package.json +5 -5
- package/src/Dialog.tsx +2 -12
- package/src/ReportDialog.tsx +137 -122
- package/src/i18n.ts +26 -2
- package/src/index.ts +1 -1
- package/src/styles.css +45 -61
- package/dist/src/FollowUp.d.ts +0 -35
- package/dist/src/FollowUp.d.ts.map +0 -1
- package/dist/src/FollowUp.js +0 -69
- package/dist/src/FollowUp.js.map +0 -1
- package/dist/src/Picker.d.ts +0 -14
- package/dist/src/Picker.d.ts.map +0 -1
- package/dist/src/Picker.js +0 -14
- package/dist/src/Picker.js.map +0 -1
- package/src/FollowUp.tsx +0 -139
- package/src/Picker.tsx +0 -60
package/src/styles.css
CHANGED
|
@@ -393,60 +393,19 @@
|
|
|
393
393
|
}
|
|
394
394
|
|
|
395
395
|
/* ---- The report form --------------------------------------------------------------------------------------------- */
|
|
396
|
-
.cw-player .cw-
|
|
396
|
+
.cw-player .cw-field {
|
|
397
397
|
min-width: 0;
|
|
398
|
-
margin:
|
|
399
|
-
border: 0;
|
|
400
|
-
}
|
|
401
|
-
.cw-player .cw-fieldset[aria-invalid='true'] {
|
|
402
|
-
border-left: 3px solid var(--cw-red, var(--cwd-red));
|
|
403
|
-
padding-left: var(--cw-space-3, var(--cwd-space-3));
|
|
404
|
-
}
|
|
405
|
-
.cw-player .cw-legend {
|
|
406
|
-
margin-bottom: var(--cw-space-2, var(--cwd-space-2));
|
|
407
|
-
padding: 0;
|
|
408
|
-
font-weight: 600;
|
|
409
|
-
}
|
|
410
|
-
.cw-player .cw-choice {
|
|
411
|
-
display: flex;
|
|
412
|
-
align-items: center;
|
|
413
|
-
gap: var(--cw-space-3, var(--cwd-space-3));
|
|
414
|
-
min-height: var(--cw-control-height, var(--cwd-control-height));
|
|
415
|
-
padding: 0 var(--cw-space-3, var(--cwd-space-3));
|
|
416
|
-
border: var(--cw-border-width, var(--cwd-border-width)) solid var(--cw-border, var(--cwd-border));
|
|
417
|
-
border-radius: var(--cw-radius-s, var(--cwd-radius-s));
|
|
418
|
-
cursor: pointer;
|
|
419
|
-
}
|
|
420
|
-
.cw-player .cw-choice + .cw-choice {
|
|
421
|
-
margin-top: var(--cw-space-1, var(--cwd-space-1));
|
|
398
|
+
margin-bottom: var(--cw-space-3, var(--cwd-space-3));
|
|
422
399
|
}
|
|
423
|
-
.cw-player .cw-
|
|
424
|
-
|
|
425
|
-
background: var(--cw-accent-soft, var(--cwd-accent-soft));
|
|
400
|
+
.cw-player .cw-field > .cw-hint {
|
|
401
|
+
margin: var(--cw-space-1, var(--cwd-space-1)) 0 0;
|
|
426
402
|
}
|
|
427
|
-
.cw-player .cw-
|
|
428
|
-
display: flex;
|
|
429
|
-
flex-direction: column;
|
|
403
|
+
.cw-player .cw-extra {
|
|
430
404
|
min-width: 0;
|
|
431
|
-
|
|
432
|
-
overflow-wrap: anywhere;
|
|
433
|
-
}
|
|
434
|
-
.cw-player .cw-choice-hint {
|
|
435
|
-
color: var(--cw-ink-muted, var(--cwd-ink-muted));
|
|
436
|
-
font-size: var(--cw-text-s, var(--cwd-text-s));
|
|
405
|
+
margin-bottom: var(--cw-space-3, var(--cwd-space-3));
|
|
437
406
|
}
|
|
438
|
-
.cw-player .cw-
|
|
439
|
-
|
|
440
|
-
margin: 0 0 var(--cw-space-3, var(--cwd-space-3));
|
|
441
|
-
padding: 0;
|
|
442
|
-
border: 0;
|
|
443
|
-
}
|
|
444
|
-
.cw-player .cw-group-legend {
|
|
445
|
-
margin-bottom: var(--cw-space-1, var(--cwd-space-1));
|
|
446
|
-
padding: 0;
|
|
447
|
-
color: var(--cw-ink-muted, var(--cwd-ink-muted));
|
|
448
|
-
font-size: var(--cw-text-s, var(--cwd-text-s));
|
|
449
|
-
font-weight: 600;
|
|
407
|
+
.cw-player .cw-field > .cw-field-error {
|
|
408
|
+
margin: var(--cw-space-2, var(--cwd-space-2)) 0 0;
|
|
450
409
|
}
|
|
451
410
|
.cw-player .cw-quote {
|
|
452
411
|
margin: 0 0 var(--cw-space-3, var(--cwd-space-3));
|
|
@@ -460,34 +419,59 @@
|
|
|
460
419
|
gap: var(--cw-space-2, var(--cwd-space-2));
|
|
461
420
|
margin-bottom: var(--cw-space-3, var(--cwd-space-3));
|
|
462
421
|
}
|
|
463
|
-
.cw-player .cw-disclosure {
|
|
464
|
-
margin-bottom: var(--cw-space-3, var(--cwd-space-3));
|
|
465
|
-
}
|
|
466
|
-
.cw-player .cw-panel {
|
|
467
|
-
min-width: 0;
|
|
468
|
-
margin-bottom: var(--cw-space-3, var(--cwd-space-3));
|
|
469
|
-
}
|
|
470
|
-
.cw-player input[type='radio'] {
|
|
471
|
-
flex: none;
|
|
472
|
-
accent-color: var(--cw-accent, var(--cwd-accent));
|
|
473
|
-
}
|
|
474
422
|
.cw-player .cw-label {
|
|
475
423
|
display: block;
|
|
476
424
|
margin-bottom: var(--cw-space-1, var(--cwd-space-1));
|
|
477
425
|
font-weight: 600;
|
|
478
426
|
}
|
|
479
|
-
.cw-player textarea
|
|
427
|
+
.cw-player textarea,
|
|
428
|
+
.cw-player select {
|
|
480
429
|
display: block;
|
|
481
430
|
width: 100%;
|
|
482
431
|
max-width: 100%;
|
|
432
|
+
min-width: 0;
|
|
483
433
|
padding: var(--cw-space-2, var(--cwd-space-2)) var(--cw-space-3, var(--cwd-space-3));
|
|
484
434
|
border: var(--cw-border-width, var(--cwd-border-width)) solid var(--cw-border-strong, var(--cwd-border-strong));
|
|
485
435
|
border-radius: var(--cw-radius-s, var(--cwd-radius-s));
|
|
486
436
|
background: var(--cw-surface, var(--cwd-surface));
|
|
487
437
|
color: var(--cw-ink, var(--cwd-ink));
|
|
488
438
|
font: inherit;
|
|
439
|
+
}
|
|
440
|
+
.cw-player textarea {
|
|
489
441
|
resize: vertical;
|
|
490
442
|
}
|
|
443
|
+
.cw-player select {
|
|
444
|
+
min-height: var(--cw-control-height, var(--cwd-control-height));
|
|
445
|
+
text-overflow: ellipsis;
|
|
446
|
+
}
|
|
447
|
+
/* A reason chosen is a choice you made: the accent, as the old radio was. */
|
|
448
|
+
.cw-player select:has(option:checked:not([value=''])) {
|
|
449
|
+
border-color: var(--cw-accent, var(--cwd-accent));
|
|
450
|
+
background: var(--cw-accent-soft, var(--cwd-accent-soft));
|
|
451
|
+
}
|
|
452
|
+
.cw-player select[aria-invalid='true'] {
|
|
453
|
+
border-color: var(--cw-red, var(--cwd-red));
|
|
454
|
+
}
|
|
455
|
+
/* The one door to the advanced part: a text button, small, in the accent (it is the next action). */
|
|
456
|
+
.cw-player .cw-linkbtn {
|
|
457
|
+
min-height: 2rem;
|
|
458
|
+
margin: 0 0 var(--cw-space-3, var(--cwd-space-3));
|
|
459
|
+
padding: 0;
|
|
460
|
+
border: 0;
|
|
461
|
+
background: none;
|
|
462
|
+
color: var(--cw-accent, var(--cwd-accent));
|
|
463
|
+
font: inherit;
|
|
464
|
+
font-size: var(--cw-text-s, var(--cwd-text-s));
|
|
465
|
+
text-decoration: underline;
|
|
466
|
+
text-underline-offset: 0.2em;
|
|
467
|
+
cursor: pointer;
|
|
468
|
+
}
|
|
469
|
+
/* A thumb needs the same target as the other buttons. */
|
|
470
|
+
@media (pointer: coarse) {
|
|
471
|
+
.cw-player .cw-linkbtn {
|
|
472
|
+
min-height: var(--cw-control-height, var(--cwd-control-height));
|
|
473
|
+
}
|
|
474
|
+
}
|
|
491
475
|
.cw-player textarea + .cw-hint {
|
|
492
476
|
margin-top: var(--cw-space-1, var(--cwd-space-1));
|
|
493
477
|
}
|
package/dist/src/FollowUp.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type { MutableRefObject } from 'react';
|
|
2
|
-
import type { ReportDetailResult } from '@chatwarden/core';
|
|
3
|
-
import type { PlayerText } from './i18n.js';
|
|
4
|
-
import type { ReasonGroup } from './reporting.js';
|
|
5
|
-
import type { PlayerT } from './i18n.js';
|
|
6
|
-
/** What the player adds after a one-tap report. Your server calls `cw.reports.addDetail(reportId, reporterSeat, detail)`. */
|
|
7
|
-
export interface ReportFollowUp {
|
|
8
|
-
reason?: string;
|
|
9
|
-
note?: string;
|
|
10
|
-
}
|
|
11
|
-
/** How the follow-up ended, for the dialog to say: added, or the refusal word (an unknown one reads as a failure). */
|
|
12
|
-
export type FollowUpOutcome = {
|
|
13
|
-
ok: true;
|
|
14
|
-
} | {
|
|
15
|
-
ok: false;
|
|
16
|
-
refused: string;
|
|
17
|
-
};
|
|
18
|
-
/**
|
|
19
|
-
* The optional step after a one-tap report: an "Add a reason" button that opens the reasons (in groups) and a note, and sends them to
|
|
20
|
-
* `onAddDetail` for the report just filed. Nothing is sent when nothing is chosen. One thing on its way at a time, shared with the
|
|
21
|
-
* dialog through `sending` (so Escape and Close wait for it); `onBusy` tells the dialog to dim its Close button.
|
|
22
|
-
*/
|
|
23
|
-
export declare function FollowUp(props: {
|
|
24
|
-
t: PlayerT;
|
|
25
|
-
groups: readonly ReasonGroup[];
|
|
26
|
-
reportId: string;
|
|
27
|
-
onAddDetail: (reportId: string, detail: ReportFollowUp) => Promise<ReportDetailResult>;
|
|
28
|
-
sending: MutableRefObject<boolean>;
|
|
29
|
-
busy: boolean;
|
|
30
|
-
onBusy: (busy: boolean) => void;
|
|
31
|
-
onDone: (outcome: FollowUpOutcome) => void;
|
|
32
|
-
}): import("react").JSX.Element;
|
|
33
|
-
/** The plain message for a follow-up refusal; one a newer kit adds gets the general one. */
|
|
34
|
-
export declare const DETAIL_REFUSED_TEXT: Readonly<Record<string, PlayerText>>;
|
|
35
|
-
//# sourceMappingURL=FollowUp.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FollowUp.d.ts","sourceRoot":"","sources":["../../src/FollowUp.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAa,gBAAgB,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAG3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,6HAA6H;AAC7H,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,sHAAsH;AACtH,MAAM,MAAM,eAAe,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAM5E;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE;IAC9B,CAAC,EAAE,OAAO,CAAC;IACX,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACvF,OAAO,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAChC,MAAM,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;CAC5C,+BA0FA;AAED,4FAA4F;AAC5F,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAOpE,CAAC"}
|
package/dist/src/FollowUp.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useId, useState } from 'react';
|
|
3
|
-
import { NOTE_MAX } from './constants.js';
|
|
4
|
-
import { PlayerError } from './client.js';
|
|
5
|
-
import { Picker } from './Picker.js';
|
|
6
|
-
import { errorText } from './ui.js';
|
|
7
|
-
/** A failure to send as an error the components can word: anything that is not already a PlayerError is a dropped connection. */
|
|
8
|
-
const failureOf = (e) => e instanceof PlayerError ? e : new PlayerError({ status: 0, code: 'network', message: 'Could not reach the server.' });
|
|
9
|
-
/**
|
|
10
|
-
* The optional step after a one-tap report: an "Add a reason" button that opens the reasons (in groups) and a note, and sends them to
|
|
11
|
-
* `onAddDetail` for the report just filed. Nothing is sent when nothing is chosen. One thing on its way at a time, shared with the
|
|
12
|
-
* dialog through `sending` (so Escape and Close wait for it); `onBusy` tells the dialog to dim its Close button.
|
|
13
|
-
*/
|
|
14
|
-
export function FollowUp(props) {
|
|
15
|
-
const { t, groups, reportId, onAddDetail, sending, busy, onBusy, onDone } = props;
|
|
16
|
-
const name = useId();
|
|
17
|
-
const noteId = useId();
|
|
18
|
-
const missingId = useId();
|
|
19
|
-
const panelId = useId();
|
|
20
|
-
const [open, setOpen] = useState(false);
|
|
21
|
-
const [reason, setReason] = useState(null);
|
|
22
|
-
const [note, setNote] = useState('');
|
|
23
|
-
const [missing, setMissing] = useState(false);
|
|
24
|
-
const [failure, setFailure] = useState(null);
|
|
25
|
-
const trimmed = note.trim();
|
|
26
|
-
const submit = async (e) => {
|
|
27
|
-
e.preventDefault();
|
|
28
|
-
if (sending.current)
|
|
29
|
-
return;
|
|
30
|
-
if (reason === null && trimmed === '') {
|
|
31
|
-
setMissing(true);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
sending.current = true;
|
|
35
|
-
onBusy(true);
|
|
36
|
-
setFailure(null);
|
|
37
|
-
try {
|
|
38
|
-
const detail = { ...(reason === null ? {} : { reason }), ...(trimmed === '' ? {} : { note: trimmed }) };
|
|
39
|
-
const raw = await onAddDetail(reportId, detail);
|
|
40
|
-
// Anything that is not a result object (undefined, null, a string) is not a success and not a crash: it reads as an unknown refusal.
|
|
41
|
-
const answer = typeof raw === 'object' && raw !== null && !Array.isArray(raw) ? raw : { ok: false, refused: 'unknown' };
|
|
42
|
-
onDone(answer.ok === true ? { ok: true } : { ok: false, refused: String(answer.refused) });
|
|
43
|
-
}
|
|
44
|
-
catch (error) {
|
|
45
|
-
setFailure(failureOf(error));
|
|
46
|
-
}
|
|
47
|
-
finally {
|
|
48
|
-
sending.current = false;
|
|
49
|
-
onBusy(false);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
return (_jsxs("form", { onSubmit: (e) => void submit(e), noValidate: true, children: [_jsx("button", { type: "button", className: "cw-btn cw-disclosure", "aria-expanded": open, "aria-controls": open ? panelId : undefined, onClick: () => setOpen(!open), children: t('player.report.addReason') }), open && (_jsxs("div", { id: panelId, className: "cw-panel", children: [_jsx("p", { className: "cw-hint", children: t('player.report.addReasonHint') }), _jsx(Picker, { groups: groups, name: name, value: reason, legend: t('player.report.detail.legend'), invalid: missing, describedBy: missingId, onChange: (id) => {
|
|
53
|
-
setReason(id);
|
|
54
|
-
setMissing(false);
|
|
55
|
-
} }), missing && (_jsx("p", { id: missingId, className: "cw-field-error", role: "alert", children: t('player.report.detail.need') })), _jsx("label", { className: "cw-label", htmlFor: noteId, children: t('player.report.note') }), _jsx("textarea", { id: noteId, className: "cw-textarea", rows: 3, maxLength: NOTE_MAX, value: note, onChange: (e) => {
|
|
56
|
-
setNote(e.target.value);
|
|
57
|
-
setMissing(false);
|
|
58
|
-
} }), failure !== null && (_jsxs("p", { className: "cw-error", role: "alert", children: [t('player.report.detail.failed'), " ", errorText(t, failure)] })), _jsx("div", { className: "cw-actions", children: _jsx("button", { type: "submit", className: "cw-btn cw-btn--primary", "aria-disabled": busy || undefined, "aria-busy": busy || undefined, children: t('player.report.detail.send') }) })] }))] }));
|
|
59
|
-
}
|
|
60
|
-
/** The plain message for a follow-up refusal; one a newer kit adds gets the general one. */
|
|
61
|
-
export const DETAIL_REFUSED_TEXT = {
|
|
62
|
-
off: 'player.report.detail.refused.off',
|
|
63
|
-
not_found: 'player.report.detail.refused.not_found',
|
|
64
|
-
not_reporter: 'player.report.detail.refused.not_reporter',
|
|
65
|
-
too_late: 'player.report.detail.refused.too_late',
|
|
66
|
-
already_added: 'player.report.detail.refused.already_added',
|
|
67
|
-
reason_unknown: 'player.report.detail.refused.reason_unknown',
|
|
68
|
-
};
|
|
69
|
-
//# sourceMappingURL=FollowUp.js.map
|
package/dist/src/FollowUp.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FollowUp.js","sourceRoot":"","sources":["../../src/FollowUp.tsx"],"names":[],"mappings":"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"}
|
package/dist/src/Picker.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { RefObject } from 'react';
|
|
2
|
-
import type { ReasonGroup } from './reporting.js';
|
|
3
|
-
/** The radios, by group. One radio group (one name) across the groups, so the arrow keys walk all of them and Tab stops once. */
|
|
4
|
-
export declare function Picker(props: {
|
|
5
|
-
groups: readonly ReasonGroup[];
|
|
6
|
-
name: string;
|
|
7
|
-
value: string | null;
|
|
8
|
-
onChange: (id: string) => void;
|
|
9
|
-
legend: string;
|
|
10
|
-
invalid?: boolean;
|
|
11
|
-
describedBy?: string | undefined;
|
|
12
|
-
fieldset?: RefObject<HTMLFieldSetElement | null>;
|
|
13
|
-
}): import("react").JSX.Element;
|
|
14
|
-
//# sourceMappingURL=Picker.d.ts.map
|
package/dist/src/Picker.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Picker.d.ts","sourceRoot":"","sources":["../../src/Picker.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAc,MAAM,gBAAgB,CAAC;AAE9D,iIAAiI;AACjI,wBAAgB,MAAM,CAAC,KAAK,EAAE;IAC5B,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,EAAE,SAAS,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;CAClD,+BA6CA"}
|
package/dist/src/Picker.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useId } from 'react';
|
|
3
|
-
/** The radios, by group. One radio group (one name) across the groups, so the arrow keys walk all of them and Tab stops once. */
|
|
4
|
-
export function Picker(props) {
|
|
5
|
-
const { groups, name, value, onChange, legend, invalid, describedBy, fieldset } = props;
|
|
6
|
-
const base = useId();
|
|
7
|
-
const choice = (r) => {
|
|
8
|
-
const label = `${base}-${r.id}-l`;
|
|
9
|
-
const hint = `${base}-${r.id}-h`;
|
|
10
|
-
return (_jsxs("label", { className: "cw-choice", children: [_jsx("input", { type: "radio", name: name, value: r.id, checked: value === r.id, "aria-labelledby": label, "aria-describedby": r.hint === '' ? undefined : hint, onChange: () => onChange(r.id) }), _jsxs("span", { className: "cw-choice-text", children: [_jsx("span", { id: label, children: r.label }), r.hint !== '' && (_jsx("span", { id: hint, className: "cw-choice-hint", children: r.hint }))] })] }, r.id));
|
|
11
|
-
};
|
|
12
|
-
return (_jsxs("fieldset", { ref: fieldset, className: "cw-fieldset", role: "radiogroup", "aria-invalid": invalid || undefined, "aria-describedby": invalid ? describedBy : undefined, children: [_jsx("legend", { className: "cw-legend", children: legend }), groups.map((g, i) => g.name === '' ? (_jsx("div", { className: "cw-group", children: g.items.map(choice) }, `g${i}`)) : (_jsxs("fieldset", { className: "cw-fieldset cw-group", children: [_jsx("legend", { className: "cw-group-legend", children: g.name }), g.items.map(choice)] }, `g${i}`)))] }));
|
|
13
|
-
}
|
|
14
|
-
//# sourceMappingURL=Picker.js.map
|
package/dist/src/Picker.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Picker.js","sourceRoot":"","sources":["../../src/Picker.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAI9B,iIAAiI;AACjI,MAAM,UAAU,MAAM,CAAC,KAStB;IACC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IACxF,MAAM,IAAI,GAAG,KAAK,EAAE,CAAC;IACrB,MAAM,MAAM,GAAG,CAAC,CAAa,EAAE,EAAE;QAC/B,MAAM,KAAK,GAAG,GAAG,IAAI,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC;QAClC,MAAM,IAAI,GAAG,GAAG,IAAI,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC;QACjC,OAAO,CACL,iBAAkB,SAAS,EAAC,WAAW,aACrC,gBACE,IAAI,EAAC,OAAO,EACZ,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,CAAC,CAAC,EAAE,EACX,OAAO,EAAE,KAAK,KAAK,CAAC,CAAC,EAAE,qBACN,KAAK,sBACJ,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EAClD,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,GAC9B,EACF,gBAAM,SAAS,EAAC,gBAAgB,aAC9B,eAAM,EAAE,EAAE,KAAK,YAAG,CAAC,CAAC,KAAK,GAAQ,EAChC,CAAC,CAAC,IAAI,KAAK,EAAE,IAAI,CAChB,eAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAC,gBAAgB,YACvC,CAAC,CAAC,IAAI,GACF,CACR,IACI,KAjBG,CAAC,CAAC,EAAE,CAkBR,CACT,CAAC;IACJ,CAAC,CAAC;IACF,OAAO,CACL,oBAAU,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAC,aAAa,EAAC,IAAI,EAAC,YAAY,kBAAe,OAAO,IAAI,SAAS,sBAAoB,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,aACxJ,iBAAQ,SAAS,EAAC,WAAW,YAAE,MAAM,GAAU,EAC9C,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACnB,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,CACd,cAAmB,SAAS,EAAC,UAAU,YACpC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IADZ,IAAI,CAAC,EAAE,CAEX,CACP,CAAC,CAAC,CAAC,CACF,oBAAwB,SAAS,EAAC,sBAAsB,aACtD,iBAAQ,SAAS,EAAC,iBAAiB,YAAE,CAAC,CAAC,IAAI,GAAU,EACpD,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,KAFP,IAAI,CAAC,EAAE,CAGX,CACZ,CACF,IACQ,CACZ,CAAC;AACJ,CAAC"}
|
package/src/FollowUp.tsx
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { useId, useState } from 'react';
|
|
2
|
-
import type { FormEvent, MutableRefObject } from 'react';
|
|
3
|
-
import type { ReportDetailResult } from '@chatwarden/core';
|
|
4
|
-
import { NOTE_MAX } from './constants.js';
|
|
5
|
-
import { PlayerError } from './client.js';
|
|
6
|
-
import type { PlayerText } from './i18n.js';
|
|
7
|
-
import { Picker } from './Picker.js';
|
|
8
|
-
import type { ReasonGroup } from './reporting.js';
|
|
9
|
-
import { errorText } from './ui.js';
|
|
10
|
-
import type { PlayerT } from './i18n.js';
|
|
11
|
-
|
|
12
|
-
/** What the player adds after a one-tap report. Your server calls `cw.reports.addDetail(reportId, reporterSeat, detail)`. */
|
|
13
|
-
export interface ReportFollowUp {
|
|
14
|
-
reason?: string;
|
|
15
|
-
note?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** How the follow-up ended, for the dialog to say: added, or the refusal word (an unknown one reads as a failure). */
|
|
19
|
-
export type FollowUpOutcome = { ok: true } | { ok: false; refused: string };
|
|
20
|
-
|
|
21
|
-
/** A failure to send as an error the components can word: anything that is not already a PlayerError is a dropped connection. */
|
|
22
|
-
const failureOf = (e: unknown): PlayerError =>
|
|
23
|
-
e instanceof PlayerError ? e : new PlayerError({ status: 0, code: 'network', message: 'Could not reach the server.' });
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* The optional step after a one-tap report: an "Add a reason" button that opens the reasons (in groups) and a note, and sends them to
|
|
27
|
-
* `onAddDetail` for the report just filed. Nothing is sent when nothing is chosen. One thing on its way at a time, shared with the
|
|
28
|
-
* dialog through `sending` (so Escape and Close wait for it); `onBusy` tells the dialog to dim its Close button.
|
|
29
|
-
*/
|
|
30
|
-
export function FollowUp(props: {
|
|
31
|
-
t: PlayerT;
|
|
32
|
-
groups: readonly ReasonGroup[];
|
|
33
|
-
reportId: string;
|
|
34
|
-
onAddDetail: (reportId: string, detail: ReportFollowUp) => Promise<ReportDetailResult>;
|
|
35
|
-
sending: MutableRefObject<boolean>;
|
|
36
|
-
busy: boolean;
|
|
37
|
-
onBusy: (busy: boolean) => void;
|
|
38
|
-
onDone: (outcome: FollowUpOutcome) => void;
|
|
39
|
-
}) {
|
|
40
|
-
const { t, groups, reportId, onAddDetail, sending, busy, onBusy, onDone } = props;
|
|
41
|
-
const name = useId();
|
|
42
|
-
const noteId = useId();
|
|
43
|
-
const missingId = useId();
|
|
44
|
-
const panelId = useId();
|
|
45
|
-
const [open, setOpen] = useState(false);
|
|
46
|
-
const [reason, setReason] = useState<string | null>(null);
|
|
47
|
-
const [note, setNote] = useState('');
|
|
48
|
-
const [missing, setMissing] = useState(false);
|
|
49
|
-
const [failure, setFailure] = useState<PlayerError | null>(null);
|
|
50
|
-
|
|
51
|
-
const trimmed = note.trim();
|
|
52
|
-
const submit = async (e: FormEvent): Promise<void> => {
|
|
53
|
-
e.preventDefault();
|
|
54
|
-
if (sending.current) return;
|
|
55
|
-
if (reason === null && trimmed === '') {
|
|
56
|
-
setMissing(true);
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
sending.current = true;
|
|
60
|
-
onBusy(true);
|
|
61
|
-
setFailure(null);
|
|
62
|
-
try {
|
|
63
|
-
const detail: ReportFollowUp = { ...(reason === null ? {} : { reason }), ...(trimmed === '' ? {} : { note: trimmed }) };
|
|
64
|
-
const raw: unknown = await onAddDetail(reportId, detail);
|
|
65
|
-
// Anything that is not a result object (undefined, null, a string) is not a success and not a crash: it reads as an unknown refusal.
|
|
66
|
-
const answer = typeof raw === 'object' && raw !== null && !Array.isArray(raw) ? (raw as ReportDetailResult) : ({ ok: false, refused: 'unknown' } as unknown as ReportDetailResult);
|
|
67
|
-
onDone(answer.ok === true ? { ok: true } : { ok: false, refused: String((answer as { refused?: unknown }).refused) });
|
|
68
|
-
} catch (error) {
|
|
69
|
-
setFailure(failureOf(error));
|
|
70
|
-
} finally {
|
|
71
|
-
sending.current = false;
|
|
72
|
-
onBusy(false);
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
return (
|
|
77
|
-
<form onSubmit={(e) => void submit(e)} noValidate>
|
|
78
|
-
<button type="button" className="cw-btn cw-disclosure" aria-expanded={open} aria-controls={open ? panelId : undefined} onClick={() => setOpen(!open)}>
|
|
79
|
-
{t('player.report.addReason')}
|
|
80
|
-
</button>
|
|
81
|
-
{open && (
|
|
82
|
-
<div id={panelId} className="cw-panel">
|
|
83
|
-
<p className="cw-hint">{t('player.report.addReasonHint')}</p>
|
|
84
|
-
<Picker
|
|
85
|
-
groups={groups}
|
|
86
|
-
name={name}
|
|
87
|
-
value={reason}
|
|
88
|
-
legend={t('player.report.detail.legend')}
|
|
89
|
-
invalid={missing}
|
|
90
|
-
describedBy={missingId}
|
|
91
|
-
onChange={(id) => {
|
|
92
|
-
setReason(id);
|
|
93
|
-
setMissing(false);
|
|
94
|
-
}}
|
|
95
|
-
/>
|
|
96
|
-
{missing && (
|
|
97
|
-
<p id={missingId} className="cw-field-error" role="alert">
|
|
98
|
-
{t('player.report.detail.need')}
|
|
99
|
-
</p>
|
|
100
|
-
)}
|
|
101
|
-
<label className="cw-label" htmlFor={noteId}>
|
|
102
|
-
{t('player.report.note')}
|
|
103
|
-
</label>
|
|
104
|
-
<textarea
|
|
105
|
-
id={noteId}
|
|
106
|
-
className="cw-textarea"
|
|
107
|
-
rows={3}
|
|
108
|
-
maxLength={NOTE_MAX}
|
|
109
|
-
value={note}
|
|
110
|
-
onChange={(e) => {
|
|
111
|
-
setNote(e.target.value);
|
|
112
|
-
setMissing(false);
|
|
113
|
-
}}
|
|
114
|
-
/>
|
|
115
|
-
{failure !== null && (
|
|
116
|
-
<p className="cw-error" role="alert">
|
|
117
|
-
{t('player.report.detail.failed')} {errorText(t, failure)}
|
|
118
|
-
</p>
|
|
119
|
-
)}
|
|
120
|
-
<div className="cw-actions">
|
|
121
|
-
<button type="submit" className="cw-btn cw-btn--primary" aria-disabled={busy || undefined} aria-busy={busy || undefined}>
|
|
122
|
-
{t('player.report.detail.send')}
|
|
123
|
-
</button>
|
|
124
|
-
</div>
|
|
125
|
-
</div>
|
|
126
|
-
)}
|
|
127
|
-
</form>
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/** The plain message for a follow-up refusal; one a newer kit adds gets the general one. */
|
|
132
|
-
export const DETAIL_REFUSED_TEXT: Readonly<Record<string, PlayerText>> = {
|
|
133
|
-
off: 'player.report.detail.refused.off',
|
|
134
|
-
not_found: 'player.report.detail.refused.not_found',
|
|
135
|
-
not_reporter: 'player.report.detail.refused.not_reporter',
|
|
136
|
-
too_late: 'player.report.detail.refused.too_late',
|
|
137
|
-
already_added: 'player.report.detail.refused.already_added',
|
|
138
|
-
reason_unknown: 'player.report.detail.refused.reason_unknown',
|
|
139
|
-
};
|
package/src/Picker.tsx
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { useId } from 'react';
|
|
2
|
-
import type { RefObject } from 'react';
|
|
3
|
-
import type { ReasonGroup, ReasonItem } from './reporting.js';
|
|
4
|
-
|
|
5
|
-
/** The radios, by group. One radio group (one name) across the groups, so the arrow keys walk all of them and Tab stops once. */
|
|
6
|
-
export function Picker(props: {
|
|
7
|
-
groups: readonly ReasonGroup[];
|
|
8
|
-
name: string;
|
|
9
|
-
value: string | null;
|
|
10
|
-
onChange: (id: string) => void;
|
|
11
|
-
legend: string;
|
|
12
|
-
invalid?: boolean;
|
|
13
|
-
describedBy?: string | undefined;
|
|
14
|
-
fieldset?: RefObject<HTMLFieldSetElement | null>;
|
|
15
|
-
}) {
|
|
16
|
-
const { groups, name, value, onChange, legend, invalid, describedBy, fieldset } = props;
|
|
17
|
-
const base = useId();
|
|
18
|
-
const choice = (r: ReasonItem) => {
|
|
19
|
-
const label = `${base}-${r.id}-l`;
|
|
20
|
-
const hint = `${base}-${r.id}-h`;
|
|
21
|
-
return (
|
|
22
|
-
<label key={r.id} className="cw-choice">
|
|
23
|
-
<input
|
|
24
|
-
type="radio"
|
|
25
|
-
name={name}
|
|
26
|
-
value={r.id}
|
|
27
|
-
checked={value === r.id}
|
|
28
|
-
aria-labelledby={label}
|
|
29
|
-
aria-describedby={r.hint === '' ? undefined : hint}
|
|
30
|
-
onChange={() => onChange(r.id)}
|
|
31
|
-
/>
|
|
32
|
-
<span className="cw-choice-text">
|
|
33
|
-
<span id={label}>{r.label}</span>
|
|
34
|
-
{r.hint !== '' && (
|
|
35
|
-
<span id={hint} className="cw-choice-hint">
|
|
36
|
-
{r.hint}
|
|
37
|
-
</span>
|
|
38
|
-
)}
|
|
39
|
-
</span>
|
|
40
|
-
</label>
|
|
41
|
-
);
|
|
42
|
-
};
|
|
43
|
-
return (
|
|
44
|
-
<fieldset ref={fieldset} className="cw-fieldset" role="radiogroup" aria-invalid={invalid || undefined} aria-describedby={invalid ? describedBy : undefined}>
|
|
45
|
-
<legend className="cw-legend">{legend}</legend>
|
|
46
|
-
{groups.map((g, i) =>
|
|
47
|
-
g.name === '' ? (
|
|
48
|
-
<div key={`g${i}`} className="cw-group">
|
|
49
|
-
{g.items.map(choice)}
|
|
50
|
-
</div>
|
|
51
|
-
) : (
|
|
52
|
-
<fieldset key={`g${i}`} className="cw-fieldset cw-group">
|
|
53
|
-
<legend className="cw-group-legend">{g.name}</legend>
|
|
54
|
-
{g.items.map(choice)}
|
|
55
|
-
</fieldset>
|
|
56
|
-
),
|
|
57
|
-
)}
|
|
58
|
-
</fieldset>
|
|
59
|
-
);
|
|
60
|
-
}
|