@things-factory/announcement 0.0.0-stage → 10.1.222

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/client/announcement-watch.ts +109 -0
  2. package/client/bootstrap.ts +9 -0
  3. package/client/components/announcement-popup.ts +92 -0
  4. package/client/components/announcement-receipts.ts +196 -0
  5. package/client/components/announcement-sheet.ts +214 -0
  6. package/client/components/audience-picker.ts +221 -0
  7. package/client/index.ts +1 -0
  8. package/client/pages/announcement-editor.ts +505 -0
  9. package/client/pages/announcement-list.ts +200 -0
  10. package/client/pages/announcement-manage-list.ts +113 -0
  11. package/client/pages/announcement-view.ts +172 -0
  12. package/client/route.ts +17 -0
  13. package/client/tsconfig.json +13 -0
  14. package/client/utils/announcement-api.ts +142 -0
  15. package/client/utils/announcement-badge.ts +46 -0
  16. package/client/utils/announcement-words.ts +26 -0
  17. package/dist-client/announcement-watch.d.ts +6 -0
  18. package/dist-client/announcement-watch.js +105 -0
  19. package/dist-client/announcement-watch.js.map +1 -0
  20. package/dist-client/bootstrap.d.ts +1 -0
  21. package/dist-client/bootstrap.js +8 -0
  22. package/dist-client/bootstrap.js.map +1 -0
  23. package/dist-client/components/announcement-popup.d.ts +20 -0
  24. package/dist-client/components/announcement-popup.js +98 -0
  25. package/dist-client/components/announcement-popup.js.map +1 -0
  26. package/dist-client/components/announcement-receipts.d.ts +30 -0
  27. package/dist-client/components/announcement-receipts.js +220 -0
  28. package/dist-client/components/announcement-receipts.js.map +1 -0
  29. package/dist-client/components/announcement-sheet.d.ts +17 -0
  30. package/dist-client/components/announcement-sheet.js +224 -0
  31. package/dist-client/components/announcement-sheet.js.map +1 -0
  32. package/dist-client/components/audience-picker.d.ts +32 -0
  33. package/dist-client/components/audience-picker.js +223 -0
  34. package/dist-client/components/audience-picker.js.map +1 -0
  35. package/dist-client/index.d.ts +1 -0
  36. package/dist-client/index.js +2 -0
  37. package/dist-client/index.js.map +1 -0
  38. package/dist-client/pages/announcement-editor.d.ts +59 -0
  39. package/dist-client/pages/announcement-editor.js +523 -0
  40. package/dist-client/pages/announcement-editor.js.map +1 -0
  41. package/dist-client/pages/announcement-list.d.ts +54 -0
  42. package/dist-client/pages/announcement-list.js +209 -0
  43. package/dist-client/pages/announcement-list.js.map +1 -0
  44. package/dist-client/pages/announcement-manage-list.d.ts +46 -0
  45. package/dist-client/pages/announcement-manage-list.js +115 -0
  46. package/dist-client/pages/announcement-manage-list.js.map +1 -0
  47. package/dist-client/pages/announcement-view.d.ts +30 -0
  48. package/dist-client/pages/announcement-view.js +182 -0
  49. package/dist-client/pages/announcement-view.js.map +1 -0
  50. package/dist-client/route.d.ts +1 -0
  51. package/dist-client/route.js +18 -0
  52. package/dist-client/route.js.map +1 -0
  53. package/dist-client/tsconfig.tsbuildinfo +1 -0
  54. package/dist-client/utils/announcement-api.d.ts +149 -0
  55. package/dist-client/utils/announcement-api.js +52 -0
  56. package/dist-client/utils/announcement-api.js.map +1 -0
  57. package/dist-client/utils/announcement-badge.d.ts +4 -0
  58. package/dist-client/utils/announcement-badge.js +47 -0
  59. package/dist-client/utils/announcement-badge.js.map +1 -0
  60. package/dist-client/utils/announcement-words.d.ts +11 -0
  61. package/dist-client/utils/announcement-words.js +23 -0
  62. package/dist-client/utils/announcement-words.js.map +1 -0
  63. package/dist-server/announcement-read.d.ts +6 -0
  64. package/dist-server/announcement-read.js +45 -0
  65. package/dist-server/announcement-read.js.map +1 -0
  66. package/dist-server/attachment-reader.d.ts +1 -0
  67. package/dist-server/attachment-reader.js +13 -0
  68. package/dist-server/attachment-reader.js.map +1 -0
  69. package/dist-server/index.d.ts +2 -0
  70. package/dist-server/index.js +20 -0
  71. package/dist-server/index.js.map +1 -0
  72. package/dist-server/role-tasks.d.ts +2 -0
  73. package/dist-server/role-tasks.js +13 -0
  74. package/dist-server/role-tasks.js.map +1 -0
  75. package/dist-server/service/announcement/announcement-resolver.d.ts +38 -0
  76. package/dist-server/service/announcement/announcement-resolver.js +664 -0
  77. package/dist-server/service/announcement/announcement-resolver.js.map +1 -0
  78. package/dist-server/service/announcement/announcement-rules.d.ts +150 -0
  79. package/dist-server/service/announcement/announcement-rules.js +227 -0
  80. package/dist-server/service/announcement/announcement-rules.js.map +1 -0
  81. package/dist-server/service/announcement/announcement-service.d.ts +54 -0
  82. package/dist-server/service/announcement/announcement-service.js +248 -0
  83. package/dist-server/service/announcement/announcement-service.js.map +1 -0
  84. package/dist-server/service/announcement/announcement-types.d.ts +54 -0
  85. package/dist-server/service/announcement/announcement-types.js +203 -0
  86. package/dist-server/service/announcement/announcement-types.js.map +1 -0
  87. package/dist-server/service/announcement/announcement.d.ts +124 -0
  88. package/dist-server/service/announcement/announcement.js +462 -0
  89. package/dist-server/service/announcement/announcement.js.map +1 -0
  90. package/dist-server/service/announcement/index.d.ts +4 -0
  91. package/dist-server/service/announcement/index.js +8 -0
  92. package/dist-server/service/announcement/index.js.map +1 -0
  93. package/dist-server/service/index.d.ts +7 -0
  94. package/dist-server/service/index.js +23 -0
  95. package/dist-server/service/index.js.map +1 -0
  96. package/dist-server/tsconfig.tsbuildinfo +1 -0
  97. package/package.json +51 -4
  98. package/server/announcement-read.ts +44 -0
  99. package/server/attachment-reader.ts +11 -0
  100. package/server/index.ts +16 -0
  101. package/server/role-tasks.ts +12 -0
  102. package/server/service/announcement/announcement-resolver.ts +506 -0
  103. package/server/service/announcement/announcement-rules.ts +239 -0
  104. package/server/service/announcement/announcement-service.ts +240 -0
  105. package/server/service/announcement/announcement-types.ts +138 -0
  106. package/server/service/announcement/announcement.ts +367 -0
  107. package/server/service/announcement/index.ts +5 -0
  108. package/server/service/index.ts +19 -0
  109. package/server/tsconfig.json +11 -0
  110. package/test/announcement-persistence.test.ts +271 -0
  111. package/test/announcement-read.test.ts +49 -0
  112. package/test/announcement-rules.test.ts +217 -0
  113. package/test/open-doors.test.ts +45 -0
  114. package/test/translations.test.ts +54 -0
  115. package/things-factory.config.js +13 -0
  116. package/translations/en.json +169 -0
  117. package/translations/es.json +169 -0
  118. package/translations/ja.json +169 -0
  119. package/translations/ko.json +169 -0
  120. package/translations/ms.json +169 -0
  121. package/translations/zh.json +169 -0
  122. package/README.md +0 -3
@@ -0,0 +1,221 @@
1
+ /*
2
+ * Whom an announcement goes to - the whole domain, departments (each with those under it), roles and people
3
+ * (ADR-0163 decision 2), with how many people that names now. Changes go up as `change` with the whole list.
4
+ */
5
+ import '@material/web/icon/icon.js'
6
+ import '@operato/input/ox-select.js'
7
+ import '@operato/popup/ox-popup-list.js'
8
+
9
+ import { css, html, LitElement, nothing } from 'lit'
10
+ import { customElement, property, state } from 'lit/decorators.js'
11
+
12
+ import { i18next, localize } from '@operato/i18n'
13
+
14
+ import { announcementApi, type Audience } from '../utils/announcement-api.js'
15
+ import { t } from '../utils/announcement-words.js'
16
+
17
+ type Choices = Awaited<ReturnType<typeof announcementApi.choices>>
18
+
19
+ const ICON: Record<string, string> = { DOMAIN: 'domain', DEPARTMENT: 'account_tree', ROLE: 'badge', USER: 'person' }
20
+
21
+ /** The framework chooser - a popup list with its search box on top. `change` hears only a value other than the one shown. */
22
+ export function chooser(options: { value: string; display: string }[], value: string, change: (value: string) => void, placeholder: string, label: string) {
23
+ const shown = options.find(option => option.value === value)?.display ?? placeholder
24
+ return html`<ox-select
25
+ .value=${value}
26
+ aria-label=${label}
27
+ @change=${(e: CustomEvent) => {
28
+ const next = String(e.detail ?? '')
29
+ if (next !== value) change(next)
30
+ }}
31
+ >
32
+ <span slot="label">${shown}</span>
33
+ <ox-popup-list align-left nowrap with-search>${options.map(option => html`<div option value=${option.value}>${option.display}</div>`)}</ox-popup-list>
34
+ </ox-select>`
35
+ }
36
+
37
+ @customElement('announcement-audience-picker')
38
+ export class AudiencePicker extends localize(i18next)(LitElement) {
39
+ static styles = css`
40
+ :host {
41
+ display: flex;
42
+ flex-direction: column;
43
+ gap: var(--spacing-medium, 8px);
44
+ }
45
+ .chosen {
46
+ display: flex;
47
+ flex-wrap: wrap;
48
+ gap: var(--spacing-small, 4px);
49
+ min-height: 28px;
50
+ align-items: center;
51
+ }
52
+ .chip {
53
+ display: inline-flex;
54
+ align-items: center;
55
+ gap: 4px;
56
+ padding: 2px 4px 2px 8px;
57
+ border-radius: 999px;
58
+ background: var(--md-sys-color-secondary-container);
59
+ color: var(--md-sys-color-on-secondary-container);
60
+ font-size: var(--ui-t-body, 14px);
61
+ }
62
+ .chip md-icon {
63
+ --md-icon-size: 16px;
64
+ }
65
+ .chip button {
66
+ border: 0;
67
+ background: none;
68
+ cursor: pointer;
69
+ color: inherit;
70
+ display: inline-flex;
71
+ padding: 0;
72
+ }
73
+ .add {
74
+ display: flex;
75
+ flex-wrap: wrap;
76
+ gap: var(--spacing-small, 4px) var(--spacing-medium, 8px);
77
+ align-items: center;
78
+ }
79
+ .add ox-select {
80
+ min-width: 180px;
81
+ }
82
+ .add > button,
83
+ .found button {
84
+ border: 1px solid var(--ui-line-soft, var(--md-sys-color-outline-variant));
85
+ background: none;
86
+ border-radius: var(--border-radius, 4px);
87
+ padding: 2px var(--spacing-medium, 8px);
88
+ cursor: pointer;
89
+ font: inherit;
90
+ color: inherit;
91
+ }
92
+ .people {
93
+ position: relative;
94
+ }
95
+ .found {
96
+ display: flex;
97
+ flex-wrap: wrap;
98
+ gap: var(--spacing-small, 4px);
99
+ margin-top: var(--spacing-small, 4px);
100
+ }
101
+ .size,
102
+ .none {
103
+ font-size: var(--ui-t-small, 12px);
104
+ color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
105
+ }
106
+ `
107
+
108
+ @property({ attribute: false }) value: Audience[] = []
109
+ @property({ type: Boolean }) disabled = false
110
+ @state() private choices?: Choices
111
+ @state() private text = ''
112
+ @state() private found: { id: string; name: string; note?: string }[] = []
113
+ @state() private size?: number
114
+ private searching?: ReturnType<typeof setTimeout>
115
+ private counting?: ReturnType<typeof setTimeout>
116
+
117
+ async connectedCallback() {
118
+ super.connectedCallback()
119
+ try {
120
+ this.choices = await announcementApi.choices()
121
+ } catch {
122
+ this.choices = { domainId: '', domainName: '', departments: [], roles: [] }
123
+ }
124
+ }
125
+
126
+ updated(changes: Map<string, unknown>) {
127
+ if (changes.has('value')) this.count()
128
+ }
129
+
130
+ render() {
131
+ const choices = this.choices
132
+ const has = (scope: string, id: string) => this.value.some(one => one.scope === scope && one.targetId === id)
133
+ return html`<div class="chosen">
134
+ ${this.value.length
135
+ ? this.value.map(
136
+ one => html`<span class="chip"
137
+ ><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}
138
+ ${this.disabled ? nothing : html`<button aria-label=${t('announcement.button.remove')} @click=${() => this.take(one)}><md-icon>close</md-icon></button>`}</span
139
+ >`
140
+ )
141
+ : html`<span class="none">${t('announcement.text.no-audience')}</span>`}
142
+ </div>
143
+ ${this.disabled || !choices
144
+ ? nothing
145
+ : html`<div class="add">
146
+ ${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>`}
147
+ ${choices.departments.length
148
+ ? chooser(
149
+ choices.departments.filter(one => !has('DEPARTMENT', one.id)).map(one => ({ value: one.id, display: one.note ? `${one.name} (${one.note})` : one.name })),
150
+ '',
151
+ id => this.give({ scope: 'DEPARTMENT', targetId: id, targetName: choices.departments.find(one => one.id === id)?.name }),
152
+ t('announcement.text.add-department'),
153
+ t('announcement.text.add-department')
154
+ )
155
+ : nothing}
156
+ ${choices.roles.length
157
+ ? chooser(
158
+ choices.roles.filter(one => !has('ROLE', one.id)).map(one => ({ value: one.id, display: one.name })),
159
+ '',
160
+ id => this.give({ scope: 'ROLE', targetId: id, targetName: choices.roles.find(one => one.id === id)?.name }),
161
+ t('announcement.text.add-role'),
162
+ t('announcement.text.add-role')
163
+ )
164
+ : nothing}
165
+ <span class="people">
166
+ <input type="search" .value=${this.text} placeholder=${t('announcement.text.add-person')} @input=${(e: any) => this.search(e.target.value)} />
167
+ </span>
168
+ </div>
169
+ ${this.found.length
170
+ ? html`<div class="found">
171
+ ${this.found
172
+ .filter(one => !has('USER', one.id))
173
+ .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>`)}
174
+ </div>`
175
+ : nothing}`}
176
+ ${this.size === undefined ? nothing : html`<span class="size">${t('announcement.text.audience-size', { count: this.size })}</span>`}`
177
+ }
178
+
179
+ private give(target: Audience) {
180
+ this.text = ''
181
+ this.found = []
182
+ this.tell([...this.value, target])
183
+ }
184
+
185
+ private take(target: Audience) {
186
+ this.tell(this.value.filter(one => !(one.scope === target.scope && one.targetId === target.targetId)))
187
+ }
188
+
189
+ private tell(value: Audience[]) {
190
+ this.value = value
191
+ this.dispatchEvent(new CustomEvent('change', { detail: value }))
192
+ }
193
+
194
+ private search(text: string) {
195
+ this.text = text
196
+ clearTimeout(this.searching)
197
+ this.searching = setTimeout(async () => {
198
+ try {
199
+ this.found = text.trim() ? await announcementApi.people(text.trim()) : []
200
+ } catch {
201
+ this.found = []
202
+ }
203
+ }, 250)
204
+ }
205
+
206
+ /** How many the audience names now - asked a moment after it stops changing. */
207
+ private count() {
208
+ clearTimeout(this.counting)
209
+ if (!this.value.length) {
210
+ this.size = 0
211
+ return
212
+ }
213
+ this.counting = setTimeout(async () => {
214
+ try {
215
+ this.size = await announcementApi.audienceSize(this.value.map(({ scope, targetId }) => ({ scope, targetId })))
216
+ } catch {
217
+ this.size = undefined
218
+ }
219
+ }, 300)
220
+ }
221
+ }
@@ -0,0 +1 @@
1
+ export { generateAnnouncementBadge, refreshAnnouncementBadge } from './utils/announcement-badge.js'