@things-factory/announcement 10.1.256 → 10.1.258

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.
@@ -6,10 +6,11 @@ import { __decorate, __metadata } from "tslib";
6
6
  import '@material/web/icon/icon.js';
7
7
  import '@operato/input/ox-select.js';
8
8
  import '@operato/popup/ox-popup-list.js';
9
+ import gql from 'graphql-tag';
9
10
  import { css, html, LitElement, nothing } from 'lit';
10
11
  import { customElement, property, state } from 'lit/decorators.js';
12
+ import { QueryController } from '@operato/graphql';
11
13
  import { i18next, localize } from '@operato/i18n';
12
- import { announcementApi } from '../utils/announcement-api.js';
13
14
  import { t } from '../utils/announcement-words.js';
14
15
  const ICON = { DOMAIN: 'domain', DEPARTMENT: 'account_tree', ROLE: 'badge', USER: 'person' };
15
16
  /** The framework chooser - a popup list with its search box on top. `change` hears only a value other than the one shown. */
@@ -40,7 +41,19 @@ let AudiencePicker = class AudiencePicker extends localize(i18next)(LitElement)
40
41
  this.value = [];
41
42
  this.disabled = false;
42
43
  this.text = '';
43
- this.found = [];
44
+ /* The words searched for and the audience counted - each a moment after it stops changing. */
45
+ this.searched = '';
46
+ this.counted = [];
47
+ /* What may be chosen, the people the words find, and how many the audience names now (ADR-0190). */
48
+ this.choicesRead = new QueryController(this, gql `query { announcementAudienceChoices { domainId domainName departments { id name note } roles { id name note } } }`);
49
+ this.people = new QueryController(this, gql `query ($text: String!) { announcementPeople(text: $text) { id name note } }`, {
50
+ variables: () => (this.searched ? { text: this.searched } : undefined),
51
+ keepPrevious: true
52
+ });
53
+ this.sizeRead = new QueryController(this, gql `query ($audiences: [AnnouncementAudienceInput!]!) { announcementAudienceSize(audiences: $audiences) }`, {
54
+ variables: () => (this.counted.length ? { audiences: this.counted } : undefined),
55
+ keepPrevious: true
56
+ });
44
57
  }
45
58
  static { this.styles = css `
46
59
  :host {
@@ -110,14 +123,18 @@ let AudiencePicker = class AudiencePicker extends localize(i18next)(LitElement)
110
123
  color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
111
124
  }
112
125
  `; }
113
- async connectedCallback() {
114
- super.connectedCallback();
115
- try {
116
- this.choices = await announcementApi.choices();
117
- }
118
- catch {
119
- this.choices = { domainId: '', domainName: '', departments: [], roles: [] };
120
- }
126
+ get choices() {
127
+ if (this.choicesRead.error)
128
+ return { domainId: '', domainName: '', departments: [], roles: [] };
129
+ return this.choicesRead.data?.announcementAudienceChoices;
130
+ }
131
+ get found() {
132
+ return (this.searched && !this.people.error && this.people.data?.announcementPeople) || [];
133
+ }
134
+ get size() {
135
+ if (!this.value.length)
136
+ return 0;
137
+ return this.sizeRead.error ? undefined : this.sizeRead.data?.announcementAudienceSize;
121
138
  }
122
139
  updated(changes) {
123
140
  if (changes.has('value'))
@@ -158,8 +175,9 @@ let AudiencePicker = class AudiencePicker extends localize(i18next)(LitElement)
158
175
  ${this.size === undefined ? nothing : html `<span class="size">${t('announcement.text.audience-size', { count: this.size })}</span>`}`;
159
176
  }
160
177
  give(target) {
178
+ clearTimeout(this.searching);
161
179
  this.text = '';
162
- this.found = [];
180
+ this.searched = '';
163
181
  this.tell([...this.value, target]);
164
182
  }
165
183
  take(target) {
@@ -172,30 +190,16 @@ let AudiencePicker = class AudiencePicker extends localize(i18next)(LitElement)
172
190
  search(text) {
173
191
  this.text = text;
174
192
  clearTimeout(this.searching);
175
- this.searching = setTimeout(async () => {
176
- try {
177
- this.found = text.trim() ? await announcementApi.people(text.trim()) : [];
178
- }
179
- catch {
180
- this.found = [];
181
- }
182
- }, 250);
193
+ this.searching = setTimeout(() => (this.searched = text.trim()), 250);
183
194
  }
184
195
  /** How many the audience names now - asked a moment after it stops changing. */
185
196
  count() {
186
197
  clearTimeout(this.counting);
187
198
  if (!this.value.length) {
188
- this.size = 0;
199
+ this.counted = [];
189
200
  return;
190
201
  }
191
- this.counting = setTimeout(async () => {
192
- try {
193
- this.size = await announcementApi.audienceSize(this.value.map(({ scope, targetId }) => ({ scope, targetId })));
194
- }
195
- catch {
196
- this.size = undefined;
197
- }
198
- }, 300);
202
+ this.counting = setTimeout(() => (this.counted = this.value.map(({ scope, targetId }) => ({ scope, targetId }))), 300);
199
203
  }
200
204
  };
201
205
  __decorate([
@@ -209,19 +213,15 @@ __decorate([
209
213
  __decorate([
210
214
  state(),
211
215
  __metadata("design:type", Object)
212
- ], AudiencePicker.prototype, "choices", void 0);
216
+ ], AudiencePicker.prototype, "text", void 0);
213
217
  __decorate([
214
218
  state(),
215
219
  __metadata("design:type", Object)
216
- ], AudiencePicker.prototype, "text", void 0);
220
+ ], AudiencePicker.prototype, "searched", void 0);
217
221
  __decorate([
218
222
  state(),
219
223
  __metadata("design:type", Array)
220
- ], AudiencePicker.prototype, "found", void 0);
221
- __decorate([
222
- state(),
223
- __metadata("design:type", Number)
224
- ], AudiencePicker.prototype, "size", void 0);
224
+ ], AudiencePicker.prototype, "counted", void 0);
225
225
  AudiencePicker = __decorate([
226
226
  customElement('announcement-audience-picker')
227
227
  ], AudiencePicker);
@@ -1 +1 @@
1
- {"version":3,"file":"audience-picker.js","sourceRoot":"","sources":["../../client/components/audience-picker.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Whom an announcement goes to - the whole domain, departments (each with those under it), roles and people\n * (ADR-0163 decision 2), with how many people that names now. Changes go up as `change` with the whole list.\n */\nimport '@material/web/icon/icon.js'\nimport '@operato/input/ox-select.js'\nimport '@operato/popup/ox-popup-list.js'\n\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\n\nimport { announcementApi, type Audience } from '../utils/announcement-api.js'\nimport { t } from '../utils/announcement-words.js'\n\ntype Choices = Awaited<ReturnType<typeof announcementApi.choices>>\n\nconst ICON: Record<string, string> = { DOMAIN: 'domain', DEPARTMENT: 'account_tree', ROLE: 'badge', USER: 'person' }\n\n/** The framework chooser - a popup list with its search box on top. `change` hears only a value other than the one shown. */\nexport function chooser(options: { value: string; display: string }[], value: string, change: (value: string) => void, placeholder: string, label: string) {\n const shown = options.find(option => option.value === value)?.display ?? placeholder\n return html`<ox-select\n .value=${value}\n aria-label=${label}\n @change=${(e: CustomEvent) => {\n /*\n * Answered by the callback. ox-select's change is composed: left to bubble, it reaches the picker's host with an\n * id for detail, after the picker's own change with the list, and the editor took the id as its audiences\n * (「e.audiences.map is not a function」 on saving, 2026-10-30).\n */\n e.stopPropagation()\n const next = String(e.detail ?? '')\n if (next !== value) change(next)\n }}\n >\n <span slot=\"label\">${shown}</span>\n <ox-popup-list align-left nowrap with-search>${options.map(option => html`<div option value=${option.value}>${option.display}</div>`)}</ox-popup-list>\n </ox-select>`\n}\n\n@customElement('announcement-audience-picker')\nexport class AudiencePicker extends localize(i18next)(LitElement) {\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n }\n .chosen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-small, 4px);\n min-height: 28px;\n align-items: center;\n }\n .chip {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 4px 2px 8px;\n border-radius: 999px;\n background: var(--md-sys-color-secondary-container);\n color: var(--md-sys-color-on-secondary-container);\n font-size: var(--ui-t-body, 14px);\n }\n .chip md-icon {\n --md-icon-size: 16px;\n }\n .chip button {\n border: 0;\n background: none;\n cursor: pointer;\n color: inherit;\n display: inline-flex;\n padding: 0;\n }\n .add {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-small, 4px) var(--spacing-medium, 8px);\n align-items: center;\n }\n .add ox-select {\n min-width: 180px;\n }\n .add > button,\n .found button {\n border: 1px solid var(--ui-line-soft, var(--md-sys-color-outline-variant));\n background: none;\n border-radius: var(--border-radius, 4px);\n padding: 2px var(--spacing-medium, 8px);\n cursor: pointer;\n font: inherit;\n color: inherit;\n }\n .people {\n position: relative;\n }\n .found {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-small, 4px);\n margin-top: var(--spacing-small, 4px);\n }\n .size,\n .none {\n font-size: var(--ui-t-small, 12px);\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n `\n\n @property({ attribute: false }) value: Audience[] = []\n @property({ type: Boolean }) disabled = false\n @state() private choices?: Choices\n @state() private text = ''\n @state() private found: { id: string; name: string; note?: string }[] = []\n @state() private size?: number\n private searching?: ReturnType<typeof setTimeout>\n private counting?: ReturnType<typeof setTimeout>\n\n async connectedCallback() {\n super.connectedCallback()\n try {\n this.choices = await announcementApi.choices()\n } catch {\n this.choices = { domainId: '', domainName: '', departments: [], roles: [] }\n }\n }\n\n updated(changes: Map<string, unknown>) {\n if (changes.has('value')) this.count()\n }\n\n render() {\n const choices = this.choices\n const has = (scope: string, id: string) => this.value.some(one => one.scope === scope && one.targetId === id)\n return html`<div class=\"chosen\">\n ${this.value.length\n ? this.value.map(\n one => html`<span class=\"chip\"\n ><md-icon>${ICON[one.scope] ?? 'label'}</md-icon>${one.targetName ?? one.targetId}${one.scope === 'DEPARTMENT' ? html`<small>${t('announcement.text.with-sub-departments')}</small>` : nothing}\n ${this.disabled ? nothing : html`<button aria-label=${t('announcement.button.remove')} @click=${() => this.take(one)}><md-icon>close</md-icon></button>`}</span\n >`\n )\n : html`<span class=\"none\">${t('announcement.text.no-audience')}</span>`}\n </div>\n ${this.disabled || !choices\n ? nothing\n : html`<div class=\"add\">\n ${has('DOMAIN', choices.domainId) ? nothing : html`<button @click=${() => this.give({ scope: 'DOMAIN', targetId: choices.domainId, targetName: t('announcement.text.everyone-in', { name: choices.domainName }) })}>${t('announcement.text.everyone')}</button>`}\n ${choices.departments.length\n ? chooser(\n choices.departments.filter(one => !has('DEPARTMENT', one.id)).map(one => ({ value: one.id, display: one.note ? `${one.name} (${one.note})` : one.name })),\n '',\n id => this.give({ scope: 'DEPARTMENT', targetId: id, targetName: choices.departments.find(one => one.id === id)?.name }),\n t('announcement.text.add-department'),\n t('announcement.text.add-department')\n )\n : nothing}\n ${choices.roles.length\n ? chooser(\n choices.roles.filter(one => !has('ROLE', one.id)).map(one => ({ value: one.id, display: one.name })),\n '',\n id => this.give({ scope: 'ROLE', targetId: id, targetName: choices.roles.find(one => one.id === id)?.name }),\n t('announcement.text.add-role'),\n t('announcement.text.add-role')\n )\n : nothing}\n <span class=\"people\">\n <input type=\"search\" .value=${this.text} placeholder=${t('announcement.text.add-person')} @input=${(e: any) => this.search(e.target.value)} />\n </span>\n </div>\n ${this.found.length\n ? html`<div class=\"found\">\n ${this.found\n .filter(one => !has('USER', one.id))\n .map(one => html`<button @click=${() => this.give({ scope: 'USER', targetId: one.id, targetName: one.name })}>${one.name}${one.note && one.note !== one.name ? ` · ${one.note}` : ''}</button>`)}\n </div>`\n : nothing}`}\n ${this.size === undefined ? nothing : html`<span class=\"size\">${t('announcement.text.audience-size', { count: this.size })}</span>`}`\n }\n\n private give(target: Audience) {\n this.text = ''\n this.found = []\n this.tell([...this.value, target])\n }\n\n private take(target: Audience) {\n this.tell(this.value.filter(one => !(one.scope === target.scope && one.targetId === target.targetId)))\n }\n\n private tell(value: Audience[]) {\n this.value = value\n this.dispatchEvent(new CustomEvent('change', { detail: value }))\n }\n\n private search(text: string) {\n this.text = text\n clearTimeout(this.searching)\n this.searching = setTimeout(async () => {\n try {\n this.found = text.trim() ? await announcementApi.people(text.trim()) : []\n } catch {\n this.found = []\n }\n }, 250)\n }\n\n /** How many the audience names now - asked a moment after it stops changing. */\n private count() {\n clearTimeout(this.counting)\n if (!this.value.length) {\n this.size = 0\n return\n }\n this.counting = setTimeout(async () => {\n try {\n this.size = await announcementApi.audienceSize(this.value.map(({ scope, targetId }) => ({ scope, targetId })))\n } catch {\n this.size = undefined\n }\n }, 300)\n }\n}\n"]}
1
+ {"version":3,"file":"audience-picker.js","sourceRoot":"","sources":["../../client/components/audience-picker.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Whom an announcement goes to - the whole domain, departments (each with those under it), roles and people\n * (ADR-0163 decision 2), with how many people that names now. Changes go up as `change` with the whole list.\n */\nimport '@material/web/icon/icon.js'\nimport '@operato/input/ox-select.js'\nimport '@operato/popup/ox-popup-list.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { QueryController } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\n\nimport { type Audience } from '../utils/announcement-api.js'\nimport { t } from '../utils/announcement-words.js'\n\ntype Choices = { domainId: string; domainName: string; departments: { id: string; name: string; note?: string }[]; roles: { id: string; name: string; note?: string }[] }\ntype Person = { id: string; name: string; note?: string }\n\nconst ICON: Record<string, string> = { DOMAIN: 'domain', DEPARTMENT: 'account_tree', ROLE: 'badge', USER: 'person' }\n\n/** The framework chooser - a popup list with its search box on top. `change` hears only a value other than the one shown. */\nexport function chooser(options: { value: string; display: string }[], value: string, change: (value: string) => void, placeholder: string, label: string) {\n const shown = options.find(option => option.value === value)?.display ?? placeholder\n return html`<ox-select\n .value=${value}\n aria-label=${label}\n @change=${(e: CustomEvent) => {\n /*\n * Answered by the callback. ox-select's change is composed: left to bubble, it reaches the picker's host with an\n * id for detail, after the picker's own change with the list, and the editor took the id as its audiences\n * (「e.audiences.map is not a function」 on saving, 2026-10-30).\n */\n e.stopPropagation()\n const next = String(e.detail ?? '')\n if (next !== value) change(next)\n }}\n >\n <span slot=\"label\">${shown}</span>\n <ox-popup-list align-left nowrap with-search>${options.map(option => html`<div option value=${option.value}>${option.display}</div>`)}</ox-popup-list>\n </ox-select>`\n}\n\n@customElement('announcement-audience-picker')\nexport class AudiencePicker extends localize(i18next)(LitElement) {\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n }\n .chosen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-small, 4px);\n min-height: 28px;\n align-items: center;\n }\n .chip {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 4px 2px 8px;\n border-radius: 999px;\n background: var(--md-sys-color-secondary-container);\n color: var(--md-sys-color-on-secondary-container);\n font-size: var(--ui-t-body, 14px);\n }\n .chip md-icon {\n --md-icon-size: 16px;\n }\n .chip button {\n border: 0;\n background: none;\n cursor: pointer;\n color: inherit;\n display: inline-flex;\n padding: 0;\n }\n .add {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-small, 4px) var(--spacing-medium, 8px);\n align-items: center;\n }\n .add ox-select {\n min-width: 180px;\n }\n .add > button,\n .found button {\n border: 1px solid var(--ui-line-soft, var(--md-sys-color-outline-variant));\n background: none;\n border-radius: var(--border-radius, 4px);\n padding: 2px var(--spacing-medium, 8px);\n cursor: pointer;\n font: inherit;\n color: inherit;\n }\n .people {\n position: relative;\n }\n .found {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-small, 4px);\n margin-top: var(--spacing-small, 4px);\n }\n .size,\n .none {\n font-size: var(--ui-t-small, 12px);\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n `\n\n @property({ attribute: false }) value: Audience[] = []\n @property({ type: Boolean }) disabled = false\n @state() private text = ''\n /* The words searched for and the audience counted - each a moment after it stops changing. */\n @state() private searched = ''\n @state() private counted: Audience[] = []\n private searching?: ReturnType<typeof setTimeout>\n private counting?: ReturnType<typeof setTimeout>\n\n /* What may be chosen, the people the words find, and how many the audience names now (ADR-0190). */\n private choicesRead = new QueryController(\n this,\n gql`query { announcementAudienceChoices { domainId domainName departments { id name note } roles { id name note } } }`\n )\n\n private people = new QueryController(this, gql`query ($text: String!) { announcementPeople(text: $text) { id name note } }`, {\n variables: () => (this.searched ? { text: this.searched } : undefined),\n keepPrevious: true\n })\n\n private sizeRead = new QueryController(this, gql`query ($audiences: [AnnouncementAudienceInput!]!) { announcementAudienceSize(audiences: $audiences) }`, {\n variables: () => (this.counted.length ? { audiences: this.counted } : undefined),\n keepPrevious: true\n })\n\n private get choices(): Choices | undefined {\n if (this.choicesRead.error) return { domainId: '', domainName: '', departments: [], roles: [] }\n return this.choicesRead.data?.announcementAudienceChoices\n }\n\n private get found(): Person[] {\n return (this.searched && !this.people.error && this.people.data?.announcementPeople) || []\n }\n\n private get size(): number | undefined {\n if (!this.value.length) return 0\n return this.sizeRead.error ? undefined : this.sizeRead.data?.announcementAudienceSize\n }\n\n updated(changes: Map<string, unknown>) {\n if (changes.has('value')) this.count()\n }\n\n render() {\n const choices = this.choices\n const has = (scope: string, id: string) => this.value.some(one => one.scope === scope && one.targetId === id)\n return html`<div class=\"chosen\">\n ${this.value.length\n ? this.value.map(\n one => html`<span class=\"chip\"\n ><md-icon>${ICON[one.scope] ?? 'label'}</md-icon>${one.targetName ?? one.targetId}${one.scope === 'DEPARTMENT' ? html`<small>${t('announcement.text.with-sub-departments')}</small>` : nothing}\n ${this.disabled ? nothing : html`<button aria-label=${t('announcement.button.remove')} @click=${() => this.take(one)}><md-icon>close</md-icon></button>`}</span\n >`\n )\n : html`<span class=\"none\">${t('announcement.text.no-audience')}</span>`}\n </div>\n ${this.disabled || !choices\n ? nothing\n : html`<div class=\"add\">\n ${has('DOMAIN', choices.domainId) ? nothing : html`<button @click=${() => this.give({ scope: 'DOMAIN', targetId: choices.domainId, targetName: t('announcement.text.everyone-in', { name: choices.domainName }) })}>${t('announcement.text.everyone')}</button>`}\n ${choices.departments.length\n ? chooser(\n choices.departments.filter(one => !has('DEPARTMENT', one.id)).map(one => ({ value: one.id, display: one.note ? `${one.name} (${one.note})` : one.name })),\n '',\n id => this.give({ scope: 'DEPARTMENT', targetId: id, targetName: choices.departments.find(one => one.id === id)?.name }),\n t('announcement.text.add-department'),\n t('announcement.text.add-department')\n )\n : nothing}\n ${choices.roles.length\n ? chooser(\n choices.roles.filter(one => !has('ROLE', one.id)).map(one => ({ value: one.id, display: one.name })),\n '',\n id => this.give({ scope: 'ROLE', targetId: id, targetName: choices.roles.find(one => one.id === id)?.name }),\n t('announcement.text.add-role'),\n t('announcement.text.add-role')\n )\n : nothing}\n <span class=\"people\">\n <input type=\"search\" .value=${this.text} placeholder=${t('announcement.text.add-person')} @input=${(e: any) => this.search(e.target.value)} />\n </span>\n </div>\n ${this.found.length\n ? html`<div class=\"found\">\n ${this.found\n .filter(one => !has('USER', one.id))\n .map(one => html`<button @click=${() => this.give({ scope: 'USER', targetId: one.id, targetName: one.name })}>${one.name}${one.note && one.note !== one.name ? ` · ${one.note}` : ''}</button>`)}\n </div>`\n : nothing}`}\n ${this.size === undefined ? nothing : html`<span class=\"size\">${t('announcement.text.audience-size', { count: this.size })}</span>`}`\n }\n\n private give(target: Audience) {\n clearTimeout(this.searching)\n this.text = ''\n this.searched = ''\n this.tell([...this.value, target])\n }\n\n private take(target: Audience) {\n this.tell(this.value.filter(one => !(one.scope === target.scope && one.targetId === target.targetId)))\n }\n\n private tell(value: Audience[]) {\n this.value = value\n this.dispatchEvent(new CustomEvent('change', { detail: value }))\n }\n\n private search(text: string) {\n this.text = text\n clearTimeout(this.searching)\n this.searching = setTimeout(() => (this.searched = text.trim()), 250)\n }\n\n /** How many the audience names now - asked a moment after it stops changing. */\n private count() {\n clearTimeout(this.counting)\n if (!this.value.length) {\n this.counted = []\n return\n }\n this.counting = setTimeout(() => (this.counted = this.value.map(({ scope, targetId }) => ({ scope, targetId }))), 300)\n }\n}\n"]}
@@ -18,6 +18,10 @@ type Form = {
18
18
  sendEmail: boolean;
19
19
  links: AnnouncementLink[];
20
20
  audiences: Audience[];
21
+ /** Files picked to attach, attached ones to take off, and whether a changed one tells everyone again. */
22
+ added: File[];
23
+ removed: string[];
24
+ notifyAgain: boolean;
21
25
  };
22
26
  declare const AnnouncementEditor_base: (new (...args: any[]) => import("lit").LitElement) & typeof PageView;
23
27
  /**
@@ -27,17 +31,22 @@ declare const AnnouncementEditor_base: (new (...args: any[]) => import("lit").Li
27
31
  */
28
32
  export declare class AnnouncementEditor extends AnnouncementEditor_base {
29
33
  static styles: import("lit").CSSResult[];
30
- announcement: AnnouncementItem | null;
31
- form: Form;
32
- files: FileRow[];
33
- added: File[];
34
- removed: string[];
35
- notifyAgain: boolean;
34
+ /** What the address opens: an announcement by id, a new one copied `from` another, or a new one. */
35
+ private address?;
36
36
  preview: boolean;
37
37
  progress?: number;
38
- loading: boolean;
39
- error: unknown;
40
- busy: boolean;
38
+ private record;
39
+ private draft;
40
+ private saves;
41
+ get announcement(): AnnouncementItem | null;
42
+ /** The form as it shows: each field's edit, else what was read (blank while it is read). */
43
+ get form(): Form;
44
+ /** The files attached so far - a copy starts with none. */
45
+ get files(): FileRow[];
46
+ /** A failed read, or an announcement the address names that is not there. */
47
+ get error(): unknown;
48
+ get loading(): boolean;
49
+ get busy(): boolean;
41
50
  get out(): boolean;
42
51
  get context(): {
43
52
  frame: string;
@@ -47,12 +56,13 @@ export declare class AnnouncementEditor extends AnnouncementEditor_base {
47
56
  title: string;
48
57
  };
49
58
  pageUpdated(changes: any, lifecycle: any): Promise<void>;
50
- private reset;
51
- private load;
52
59
  render(): import("lit-html").TemplateResult<1>;
53
60
  private pick;
54
61
  private input;
55
- /** Keeps it, then sends it out when asked - a draft that cannot go out stays kept with the reason told. */
62
+ /**
63
+ * Keeps it, then sends it out when asked - a draft that cannot go out stays kept, opened as one. Failures are told by
64
+ * the GraphQL link.
65
+ */
56
66
  private save;
57
67
  private deleteDraft;
58
68
  }
@@ -3,18 +3,20 @@ import '@material/web/icon/icon.js';
3
3
  import '@operato/input/ox-input-file.js';
4
4
  import '@operato/list-state/ox-page-state.js';
5
5
  import '../components/audience-picker.js';
6
+ import gql from 'graphql-tag';
6
7
  import { css, html, nothing } from 'lit';
7
8
  import { customElement, state } from 'lit/decorators.js';
8
9
  import { unsafeHTML } from 'lit/directives/unsafe-html.js';
10
+ import { MutationController, QueryController, uploadContext } from '@operato/graphql';
9
11
  import { i18next, localize } from '@operato/i18n';
10
12
  import { OxPrompt } from '@operato/popup/ox-prompt.js';
11
13
  import { notify } from '@operato/layout';
12
- import { navigate, PageView } from '@operato/shell';
14
+ import { navigate, PageView, DraftController } from '@operato/shell';
13
15
  import { renderMarkdown } from '@things-factory/ai-assistant/dist-client/index.js';
14
16
  import { ButtonStyles, DocumentPageStyles, FormFieldStyles } from '@operato/styles';
15
17
  import { chooser } from '../components/audience-picker.js';
16
- import { announcementApi } from '../utils/announcement-api.js';
17
- import { CATEGORIES, categoryWord, IMPORTANCES, importanceWord, reasonOf, statusWord, t } from '../utils/announcement-words.js';
18
+ import { FULL_FIELDS } from '../utils/announcement-api.js';
19
+ import { CATEGORIES, categoryWord, IMPORTANCES, importanceWord, statusWord, t } from '../utils/announcement-words.js';
18
20
  const blank = () => ({
19
21
  title: '',
20
22
  body: '',
@@ -28,7 +30,10 @@ const blank = () => ({
28
30
  popup: false,
29
31
  sendEmail: false,
30
32
  links: [],
31
- audiences: []
33
+ audiences: [],
34
+ added: [],
35
+ removed: [],
36
+ notifyAgain: false
32
37
  });
33
38
  /** `datetime-local` speaks the reader's own clock. */
34
39
  const pad = (n) => String(n).padStart(2, '0');
@@ -54,7 +59,10 @@ function formOf(one) {
54
59
  popup: one.popup,
55
60
  sendEmail: !!one.sendEmail,
56
61
  links: (one.links ?? []).map(link => ({ ...link })),
57
- audiences: (one.audiences ?? []).map(({ scope, targetId, targetName }) => ({ scope, targetId, targetName }))
62
+ audiences: (one.audiences ?? []).map(({ scope, targetId, targetName }) => ({ scope, targetId, targetName })),
63
+ added: [],
64
+ removed: [],
65
+ notifyAgain: false
58
66
  };
59
67
  }
60
68
  /**
@@ -65,16 +73,39 @@ function formOf(one) {
65
73
  let AnnouncementEditor = class AnnouncementEditor extends localize(i18next)(PageView) {
66
74
  constructor() {
67
75
  super(...arguments);
68
- this.announcement = null;
69
- this.form = blank();
70
- this.files = [];
71
- this.added = [];
72
- this.removed = [];
73
- this.notifyAgain = false;
74
76
  this.preview = false;
75
- this.loading = false;
76
- this.error = null;
77
- this.busy = false;
77
+ /* The announcement edited, or the one a new one is copied from (ADR-0190). */
78
+ this.record = new QueryController(this, gql(`query ($id: String!) { announcement(id: $id) { ${FULL_FIELDS} readCount acknowledgedCount } }`), {
79
+ variables: () => {
80
+ const id = this.address?.id || this.address?.from;
81
+ return id ? { id } : undefined;
82
+ }
83
+ });
84
+ /* What is typed over the announcement read - a copy starts without its days, a new one blank. */
85
+ this.draft = new DraftController(this, {
86
+ read: () => {
87
+ if (!this.address)
88
+ return undefined;
89
+ if (!this.address.id && !this.address.from)
90
+ return blank();
91
+ const one = this.record.data?.announcement;
92
+ if (!one)
93
+ return undefined;
94
+ return this.address.from && !this.address.id ? { ...formOf(one), startsAt: '', endsAt: '' } : formOf(one);
95
+ },
96
+ key: () => this.address
97
+ });
98
+ /* A change reads the announcement again once it is done; one that goes out or away leaves the editor. */
99
+ this.saves = {
100
+ create: new MutationController(this, gql `mutation ($input: AnnouncementInput!, $attach: [Upload!]) { createAnnouncement(input: $input, attach: $attach) { id } }`),
101
+ update: new MutationController(this, gql `
102
+ mutation ($id: String!, $input: AnnouncementInput!, $attach: [Upload!], $remove: [String!], $notifyAgain: Boolean) {
103
+ updateAnnouncement(id: $id, input: $input, attach: $attach, remove: $remove, notifyAgain: $notifyAgain) { id status }
104
+ }
105
+ `, { refetch: () => [this.record] }),
106
+ publish: new MutationController(this, gql `mutation ($id: String!) { publishAnnouncement(id: $id) { id status startsAt } }`),
107
+ remove: new MutationController(this, gql `mutation ($id: String!) { deleteAnnouncement(id: $id) }`)
108
+ };
78
109
  }
79
110
  static { this.styles = [
80
111
  FormFieldStyles,
@@ -190,6 +221,29 @@ let AnnouncementEditor = class AnnouncementEditor extends localize(i18next)(Page
190
221
  }
191
222
  `
192
223
  ]; }
224
+ get announcement() {
225
+ return (this.address?.id && this.record.data?.announcement) || null;
226
+ }
227
+ /** The form as it shows: each field's edit, else what was read (blank while it is read). */
228
+ get form() {
229
+ return Object.fromEntries(Object.entries(blank()).map(([name, given]) => [name, this.draft.value(name) ?? given]));
230
+ }
231
+ /** The files attached so far - a copy starts with none. */
232
+ get files() {
233
+ return this.announcement?.attachments ?? [];
234
+ }
235
+ /** A failed read, or an announcement the address names that is not there. */
236
+ get error() {
237
+ if (this.record.error)
238
+ return this.record.error;
239
+ return this.record.data && !this.record.data.announcement ? new Error(t('announcement.refusal.unknown-announcement')) : null;
240
+ }
241
+ get loading() {
242
+ return this.record.loading;
243
+ }
244
+ get busy() {
245
+ return Object.values(this.saves).some(save => save.pending);
246
+ }
193
247
  get out() {
194
248
  return this.announcement?.status === 'PUBLISHED';
195
249
  }
@@ -219,55 +273,21 @@ let AnnouncementEditor = class AnnouncementEditor extends localize(i18next)(Page
219
273
  if (!this.active || !(changes.active || changes.params))
220
274
  return;
221
275
  const { id, from } = lifecycle?.params ?? {};
222
- if (id) {
223
- if (id !== this.announcement?.id)
224
- await this.load(id, false);
225
- }
226
- else if (from) {
227
- await this.load(from, true);
228
- }
229
- else {
230
- this.announcement = null;
231
- this.form = blank();
232
- this.files = [];
233
- this.reset();
234
- this.updateContext();
235
- }
236
- }
237
- reset() {
238
- this.added = [];
239
- this.removed = [];
240
- this.notifyAgain = false;
276
+ const address = id ? { id } : from ? { from } : {};
277
+ if (JSON.stringify(address) === JSON.stringify(this.address))
278
+ return;
279
+ this.address = address;
241
280
  this.preview = false;
242
281
  }
243
- async load(id, asCopy) {
244
- this.loading = true;
245
- this.error = null;
246
- try {
247
- const one = await announcementApi.one(id);
248
- if (!one)
249
- throw new Error(t('announcement.refusal.unknown-announcement'));
250
- this.announcement = asCopy ? null : one;
251
- this.form = asCopy ? { ...formOf(one), startsAt: '', endsAt: '' } : formOf(one);
252
- this.files = asCopy ? [] : (one.attachments ?? []);
253
- this.reset();
254
- }
255
- catch (error) {
256
- this.error = error;
257
- }
258
- finally {
259
- this.loading = false;
260
- this.updateContext();
261
- }
262
- }
263
282
  render() {
264
283
  const form = this.form;
265
284
  const set = (patch) => {
266
- this.form = { ...form, ...patch };
285
+ for (const [name, value] of Object.entries(patch))
286
+ this.draft.set(name, value);
267
287
  this.updateContext();
268
288
  };
269
289
  const editable = !this.busy && (!this.announcement || !!this.announcement.editable);
270
- return html `<ox-page-state .loading=${this.loading} .error=${this.error} @retry=${() => this.announcement && this.load(this.announcement.id, false)}>
290
+ return html `<ox-page-state .loading=${this.loading} .error=${this.error} @retry=${() => this.record.refetch()}>
271
291
  <div class="page">
272
292
  <section class="page-section">
273
293
  <header class="page-section-header"><h3>${t('announcement.text.section-what')}</h3></header>
@@ -343,17 +363,17 @@ let AnnouncementEditor = class AnnouncementEditor extends localize(i18next)(Page
343
363
  <section class="page-section">
344
364
  <header class="page-section-header"><h3>${t('announcement.field.attachments')}</h3></header>
345
365
  <ul class="files">
346
- ${this.files.map(file => html `<li class=${this.removed.includes(file.id) ? 'removed' : ''}>
366
+ ${this.files.map(file => html `<li class=${form.removed.includes(file.id) ? 'removed' : ''}>
347
367
  <md-icon>attach_file</md-icon>${file.name}
348
368
  ${editable
349
- ? this.removed.includes(file.id)
350
- ? html `<button @click=${() => (this.removed = this.removed.filter(id => id !== file.id))}><md-icon>undo</md-icon></button>`
351
- : html `<button aria-label=${t('announcement.button.remove')} @click=${() => (this.removed = [...this.removed, file.id])}><md-icon>close</md-icon></button>`
369
+ ? form.removed.includes(file.id)
370
+ ? html `<button @click=${() => set({ removed: form.removed.filter(id => id !== file.id) })}><md-icon>undo</md-icon></button>`
371
+ : html `<button aria-label=${t('announcement.button.remove')} @click=${() => set({ removed: [...form.removed, file.id] })}><md-icon>close</md-icon></button>`
352
372
  : nothing}
353
373
  </li>`)}
354
- ${this.added.map((file, index) => html `<li>
374
+ ${form.added.map((file, index) => html `<li>
355
375
  <md-icon>upload_file</md-icon>${file.name}
356
- <button aria-label=${t('announcement.button.remove')} @click=${() => (this.added = this.added.filter((_, i) => i !== index))}><md-icon>close</md-icon></button>
376
+ <button aria-label=${t('announcement.button.remove')} @click=${() => set({ added: form.added.filter((_, i) => i !== index) })}><md-icon>close</md-icon></button>
357
377
  </li>`)}
358
378
  </ul>
359
379
  ${editable ? html `<ox-input-file quiet hide-filelist multiple icon="attach_file" .progress=${this.progress} @change=${(e) => this.pick(e)}></ox-input-file>` : nothing}
@@ -388,7 +408,7 @@ let AnnouncementEditor = class AnnouncementEditor extends localize(i18next)(Page
388
408
 
389
409
  ${this.out
390
410
  ? html `<section class="page-section">
391
- <label class="check"><input type="checkbox" .checked=${this.notifyAgain} @change=${(e) => (this.notifyAgain = e.target.checked)} />${t('announcement.text.notify-again')}</label>
411
+ <label class="check"><input type="checkbox" .checked=${form.notifyAgain} @change=${(e) => set({ notifyAgain: e.target.checked })} />${t('announcement.text.notify-again')}</label>
392
412
  <span class="hint">${t('announcement.text.new-version', { version: (this.announcement?.version ?? 1) + 1 })}</span>
393
413
  </section>`
394
414
  : nothing}
@@ -401,7 +421,7 @@ let AnnouncementEditor = class AnnouncementEditor extends localize(i18next)(Page
401
421
  if (!picked.length)
402
422
  return;
403
423
  e.target.reset?.();
404
- this.added = [...this.added, ...picked];
424
+ this.draft.set('added', [...this.form.added, ...picked]);
405
425
  }
406
426
  input() {
407
427
  const form = this.form;
@@ -421,37 +441,39 @@ let AnnouncementEditor = class AnnouncementEditor extends localize(i18next)(Page
421
441
  audiences: form.audiences.map(({ scope, targetId, targetName }) => ({ scope, targetId, targetName: targetName ?? undefined }))
422
442
  };
423
443
  }
424
- /** Keeps it, then sends it out when asked - a draft that cannot go out stays kept with the reason told. */
444
+ /**
445
+ * Keeps it, then sends it out when asked - a draft that cannot go out stays kept, opened as one. Failures are told by
446
+ * the GraphQL link.
447
+ */
425
448
  async save(publish) {
426
- this.busy = true;
449
+ if (this.busy)
450
+ return;
451
+ const id = this.announcement?.id;
452
+ const { added, removed, notifyAgain } = this.form;
453
+ const context = added.length ? uploadContext(fraction => (this.progress = fraction)) : undefined;
454
+ const saving = this.draft.submit(() => id
455
+ ? this.saves.update.run({ id, input: this.input(), attach: added, remove: removed, notifyAgain }, { context })
456
+ : this.saves.create.run({ input: this.input(), attach: added }, { context }));
457
+ /* The actions wait while it is out. */
458
+ this.updateContext();
459
+ const saved = await saving;
460
+ this.progress = undefined;
427
461
  this.updateContext();
428
- const uploading = (fraction) => (this.progress = fraction);
429
- try {
430
- let id = this.announcement?.id;
431
- if (id)
432
- await announcementApi.update(id, this.input(), this.added, this.removed, this.notifyAgain, uploading);
433
- else
434
- id = (await announcementApi.create(this.input(), this.added, uploading)).id;
435
- if (publish) {
436
- const sent = await announcementApi.publish(id);
437
- notify({ message: sent.status === 'SCHEDULED' ? t('announcement.text.scheduled-done') : t('announcement.text.published-done') });
438
- navigate(`announcement-view?id=${id}`);
462
+ if (!saved)
463
+ return;
464
+ const kept = id ?? saved.createAnnouncement.id;
465
+ if (publish) {
466
+ const sent = await this.saves.publish.run({ id: kept });
467
+ if (sent) {
468
+ notify({ message: sent.publishAnnouncement.status === 'SCHEDULED' ? t('announcement.text.scheduled-done') : t('announcement.text.published-done') });
469
+ navigate(`announcement-view?id=${kept}`);
439
470
  return;
440
471
  }
441
- notify({ message: t('announcement.text.saved') });
442
- if (this.announcement?.id === id)
443
- await this.load(id, false);
444
- else
445
- navigate(`announcement-editor?id=${id}`);
446
- }
447
- catch (error) {
448
- notify({ level: 'error', message: reasonOf(error) });
449
- }
450
- finally {
451
- this.progress = undefined;
452
- this.busy = false;
453
- this.updateContext();
454
472
  }
473
+ else
474
+ notify({ message: t('announcement.text.saved') });
475
+ if (!id)
476
+ navigate(`announcement-editor?id=${kept}`);
455
477
  }
456
478
  async deleteDraft() {
457
479
  const sure = await OxPrompt.open({
@@ -462,40 +484,16 @@ let AnnouncementEditor = class AnnouncementEditor extends localize(i18next)(Page
462
484
  });
463
485
  if (!sure)
464
486
  return;
465
- try {
466
- await announcementApi.remove(this.announcement.id);
467
- notify({ message: t('announcement.text.deleted') });
468
- navigate('announcement-manage-list');
469
- }
470
- catch (error) {
471
- notify({ level: 'error', message: reasonOf(error) });
472
- }
487
+ if (!(await this.saves.remove.run({ id: this.announcement.id })))
488
+ return;
489
+ notify({ message: t('announcement.text.deleted') });
490
+ navigate('announcement-manage-list');
473
491
  }
474
492
  };
475
493
  __decorate([
476
494
  state(),
477
495
  __metadata("design:type", Object)
478
- ], AnnouncementEditor.prototype, "announcement", void 0);
479
- __decorate([
480
- state(),
481
- __metadata("design:type", Object)
482
- ], AnnouncementEditor.prototype, "form", void 0);
483
- __decorate([
484
- state(),
485
- __metadata("design:type", Array)
486
- ], AnnouncementEditor.prototype, "files", void 0);
487
- __decorate([
488
- state(),
489
- __metadata("design:type", Array)
490
- ], AnnouncementEditor.prototype, "added", void 0);
491
- __decorate([
492
- state(),
493
- __metadata("design:type", Array)
494
- ], AnnouncementEditor.prototype, "removed", void 0);
495
- __decorate([
496
- state(),
497
- __metadata("design:type", Object)
498
- ], AnnouncementEditor.prototype, "notifyAgain", void 0);
496
+ ], AnnouncementEditor.prototype, "address", void 0);
499
497
  __decorate([
500
498
  state(),
501
499
  __metadata("design:type", Object)
@@ -504,18 +502,6 @@ __decorate([
504
502
  state(),
505
503
  __metadata("design:type", Number)
506
504
  ], AnnouncementEditor.prototype, "progress", void 0);
507
- __decorate([
508
- state(),
509
- __metadata("design:type", Object)
510
- ], AnnouncementEditor.prototype, "loading", void 0);
511
- __decorate([
512
- state(),
513
- __metadata("design:type", Object)
514
- ], AnnouncementEditor.prototype, "error", void 0);
515
- __decorate([
516
- state(),
517
- __metadata("design:type", Object)
518
- ], AnnouncementEditor.prototype, "busy", void 0);
519
505
  AnnouncementEditor = __decorate([
520
506
  customElement('announcement-editor')
521
507
  ], AnnouncementEditor);