@things-factory/inquiry 10.1.252 → 10.1.254

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/inquiry",
3
- "version": "10.1.252",
3
+ "version": "10.1.254",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/index.js",
6
6
  "things-factory": true,
@@ -36,18 +36,18 @@
36
36
  "@operato/shell": "^10.29.39",
37
37
  "@operato/styles": "^10.29.1",
38
38
  "@operato/utils": "^10.0.0",
39
- "@things-factory/apptool-base": "^10.1.247",
40
- "@things-factory/attachment-base": "^10.1.250",
41
- "@things-factory/auth-base": "^10.1.250",
39
+ "@things-factory/apptool-base": "^10.1.254",
40
+ "@things-factory/attachment-base": "^10.1.254",
41
+ "@things-factory/auth-base": "^10.1.254",
42
42
  "@things-factory/env": "^10.1.149",
43
- "@things-factory/i18n-base": "^10.1.250",
44
- "@things-factory/notification": "^10.1.250",
45
- "@things-factory/shell": "^10.1.247",
46
- "@things-factory/worklist": "^10.1.250",
43
+ "@things-factory/i18n-base": "^10.1.254",
44
+ "@things-factory/notification": "^10.1.254",
45
+ "@things-factory/shell": "^10.1.254",
46
+ "@things-factory/worklist": "^10.1.254",
47
47
  "graphql-scalars": "^1.22.0",
48
48
  "graphql-tag": "^2.12.6",
49
49
  "graphql-upload": "^15.0.2",
50
50
  "lit": "^3.1.2"
51
51
  },
52
- "gitHead": "ebd127225a33ec1fbbc11923df08c355f1230b04"
52
+ "gitHead": "793e2fd617db5bb917ec892e64956fb690f7e7b0"
53
53
  }
@@ -1,27 +0,0 @@
1
- import './components/inquiry-todo.js'
2
-
3
- import { store } from '@operato/shell'
4
- import { auth } from '@things-factory/auth-base/dist-client/auth.js'
5
- import { ADD_USER_MENU } from '@things-factory/apptool-base/client'
6
-
7
- import { openInquiryWriter } from './components/inquiry-writer.js'
8
- import { startInquiryWatch, stopInquiryWatch } from './inquiry-watch.js'
9
-
10
- /*
11
- * The way to write one is in the menu the header's avatar opens (ADR-0184 decision 9) - on every screen of every
12
- * application, carrying the screen it was opened on. The badge follows the person.
13
- */
14
- export default function bootstrap() {
15
- store.dispatch({
16
- type: ADD_USER_MENU,
17
- item: {
18
- name: 'inquiry',
19
- icon: 'contact_support',
20
- label: 'inquiry.button.send',
21
- order: 30,
22
- action: () => openInquiryWriter()
23
- }
24
- })
25
- auth.on('profile', () => startInquiryWatch())
26
- auth.on('presignout', () => stopInquiryWatch())
27
- }
@@ -1,277 +0,0 @@
1
- /*
2
- * One inquiry as its thread (ADR-0184 decisions 2-4): what was asked, from which screen and application, the files, and
3
- * the replies between its writer and the handlers. Below it, the reply box and the moves the person may make.
4
- */
5
- import '@material/web/icon/icon.js'
6
- import '@material/web/button/filled-button.js'
7
- import '@material/web/button/outlined-button.js'
8
- import '@material/web/button/text-button.js'
9
- import '@operato/input/ox-input-file.js'
10
-
11
- import { css, html, LitElement, nothing } from 'lit'
12
- import { customElement, property, state } from 'lit/decorators.js'
13
-
14
- import { i18next, localize } from '@operato/i18n'
15
- import { notify } from '@operato/layout'
16
- import { ButtonStyles, FormFieldStyles } from '@operato/styles'
17
-
18
- import { inquiriesChanged, inquiryApi, type FileRow, type InquiryItem } from '../utils/inquiry-api.js'
19
- import { KIND_ICONS, kindWord, reasonOf, statusWord, t, visibilityWord, when } from '../utils/inquiry-words.js'
20
-
21
- /** The words of a move button - a move to CLOSED by its writer and by a handler read the same. */
22
- const moveWord = (status: string) => t(`inquiry.move.${status}`, { defaultValue: statusWord(status) })
23
-
24
- @customElement('inquiry-thread')
25
- export class InquiryThread extends localize(i18next)(LitElement) {
26
- static styles = [
27
- FormFieldStyles,
28
- ButtonStyles,
29
- css`
30
- :host {
31
- display: flex;
32
- flex-direction: column;
33
- gap: var(--spacing-medium, 8px);
34
- color: var(--md-sys-color-on-surface);
35
- }
36
- h2 {
37
- margin: 0;
38
- font-size: var(--ui-t-title, 16px);
39
- }
40
- .meta {
41
- display: flex;
42
- flex-wrap: wrap;
43
- align-items: center;
44
- gap: var(--spacing-small, 4px) var(--spacing-medium, 8px);
45
- font-size: var(--ui-t-small, 12px);
46
- color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
47
- }
48
- .chip {
49
- display: inline-flex;
50
- align-items: center;
51
- gap: 2px;
52
- padding: 0 var(--spacing-small, 4px);
53
- border-radius: var(--border-radius, 4px);
54
- border: 1px solid var(--ui-line-soft, var(--md-sys-color-outline-variant));
55
- line-height: 1.6;
56
- }
57
- .chip md-icon {
58
- --md-icon-size: 14px;
59
- }
60
- .chip.status {
61
- color: var(--md-sys-color-primary);
62
- border-color: currentColor;
63
- }
64
- .text {
65
- white-space: pre-wrap;
66
- word-break: break-word;
67
- line-height: 1.6;
68
- }
69
- .context {
70
- font-size: var(--ui-t-small, 12px);
71
- color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
72
- word-break: break-all;
73
- }
74
- .context a {
75
- color: inherit;
76
- }
77
- ul.files {
78
- margin: 0;
79
- padding: 0;
80
- list-style: none;
81
- display: flex;
82
- flex-wrap: wrap;
83
- gap: var(--spacing-small, 4px) var(--spacing-medium, 8px);
84
- font-size: var(--ui-t-small, 12px);
85
- }
86
- ul.files a {
87
- display: inline-flex;
88
- align-items: center;
89
- gap: 2px;
90
- color: var(--md-sys-color-primary);
91
- }
92
- ul.files md-icon {
93
- --md-icon-size: 14px;
94
- }
95
- ol.replies {
96
- margin: 0;
97
- padding: 0;
98
- list-style: none;
99
- display: flex;
100
- flex-direction: column;
101
- gap: var(--spacing-medium, 8px);
102
- }
103
- ol.replies li {
104
- padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);
105
- border-radius: var(--border-radius, 4px);
106
- border: 1px solid var(--ui-line-soft, var(--md-sys-color-outline-variant));
107
- display: flex;
108
- flex-direction: column;
109
- gap: 4px;
110
- }
111
- ol.replies li.handler {
112
- background: var(--ui-panel-2, var(--md-sys-color-surface-container));
113
- }
114
- ol.replies li.answer {
115
- border-color: var(--md-sys-color-primary);
116
- }
117
- .move {
118
- font-size: var(--ui-t-small, 12px);
119
- color: var(--md-sys-color-primary);
120
- font-weight: 600;
121
- }
122
- .box {
123
- display: flex;
124
- flex-direction: column;
125
- gap: var(--spacing-small, 4px);
126
- }
127
- textarea {
128
- min-height: 96px;
129
- font-family: inherit;
130
- box-sizing: border-box;
131
- width: 100%;
132
- }
133
- .hint {
134
- font-size: var(--ui-t-small, 12px);
135
- color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
136
- }
137
- .row {
138
- display: flex;
139
- flex-wrap: wrap;
140
- align-items: center;
141
- gap: var(--spacing-small, 4px) var(--spacing-medium, 8px);
142
- }
143
- .row .spacer {
144
- flex: 1;
145
- }
146
- `
147
- ]
148
-
149
- @property() inquiryId = ''
150
- @state() one: InquiryItem | null = null
151
- @state() missing = false
152
- @state() text = ''
153
- @state() files: File[] = []
154
- @state() busy = false
155
- /* Set only while files go up - the picker shows its bar then. */
156
- @state() progress?: number
157
-
158
- updated(changes: Map<string, unknown>) {
159
- if (changes.has('inquiryId')) this.load(true)
160
- }
161
-
162
- async load(opened = false) {
163
- if (!this.inquiryId) return
164
- try {
165
- this.one = await inquiryApi.one(this.inquiryId)
166
- this.missing = !this.one
167
- /* Its writer opening it reads the handlers' words - the badge counts again. */
168
- if (opened && this.one?.mine && this.one.isNew) {
169
- await inquiryApi.read(this.one.id)
170
- inquiriesChanged()
171
- }
172
- } catch (error) {
173
- notify({ level: 'error', message: reasonOf(error) })
174
- }
175
- }
176
-
177
- render() {
178
- if (this.missing) return html`<p class="hint">${t('inquiry.text.not-found')}</p>`
179
- const one = this.one
180
- if (!one) return nothing
181
- const replies = one.replies ?? []
182
- const handlerMoves = (one.moves ?? []).filter(status => status !== 'CLOSED')
183
- return html`
184
- <div class="meta">
185
- <span class="chip"><md-icon>${KIND_ICONS[one.kind] ?? 'help'}</md-icon>${kindWord(one.kind)}</span>
186
- <span class="chip status">${statusWord(one.status)}</span>
187
- ${one.visibility === 'PRIVATE' ? html`<span class="chip"><md-icon>lock</md-icon>${visibilityWord(one.visibility)}</span>` : nothing}
188
- <span>${one.creator?.name ?? ''} · ${when(one.createdAt)}</span>
189
- ${one.assignee ? html`<span>${t('inquiry.text.assignee', { name: one.assignee.name ?? '' })}</span>` : nothing}
190
- </div>
191
- <h2>${one.title}</h2>
192
- ${one.body ? html`<div class="text">${one.body}</div>` : nothing} ${this.renderFiles(one.attachments)}
193
- <div class="context">
194
- ${one.pageUrl ? html`<div>${t('inquiry.field.page')}: ${/^https?:\/\//i.test(one.pageUrl) ? html`<a href=${one.pageUrl} target="_blank" rel="noopener">${one.pageUrl}</a>` : one.pageUrl}</div>` : nothing}
195
- ${one.appName ? html`<div>${t('inquiry.field.app')}: ${one.appName}${one.appVersion ? ` ${one.appVersion}` : ''}</div>` : nothing}
196
- </div>
197
-
198
- ${replies.length
199
- ? html`<ol class="replies">
200
- ${replies.map(
201
- reply => html`<li class=${[reply.byHandler ? 'handler' : '', reply.answer ? 'answer' : ''].join(' ')}>
202
- <div class="meta">
203
- <span>${reply.creator?.name ?? ''}</span>
204
- ${reply.byHandler ? html`<span class="chip">${t('inquiry.text.handler')}</span>` : nothing}
205
- ${reply.answer ? html`<span class="chip status"><md-icon>check</md-icon>${t('inquiry.text.answer')}</span>` : nothing}
206
- <span>${when(reply.createdAt)}</span>
207
- </div>
208
- ${reply.status && !reply.answer ? html`<span class="move">${t('inquiry.text.moved', { status: statusWord(reply.status) })}</span>` : nothing}
209
- ${reply.body ? html`<div class="text">${reply.body}</div>` : nothing} ${this.renderFiles(reply.attachments)}
210
- </li>`
211
- )}
212
- </ol>`
213
- : nothing}
214
- ${one.canReply ? this.renderBox(one, handlerMoves) : one.status === 'CLOSED' ? html`<p class="hint">${t('inquiry.text.closed-note')}</p>` : nothing}
215
- `
216
- }
217
-
218
- private renderFiles(files?: FileRow[]) {
219
- if (!files?.length) return nothing
220
- return html`<ul class="files">
221
- ${files.map(file => html`<li><a href=${file.fullpath} target="_blank" rel="noopener" download=${file.name}><md-icon>attach_file</md-icon>${file.name}</a></li>`)}
222
- </ul>`
223
- }
224
-
225
- private renderBox(one: InquiryItem, handlerMoves: string[]) {
226
- const said = !!this.text.trim() || this.files.length > 0
227
- const canClose = (one.moves ?? []).includes('CLOSED')
228
- return html`<div class="box">
229
- <textarea .value=${this.text} placeholder=${t(one.asHandler ? 'inquiry.text.reply-placeholder-handler' : 'inquiry.text.reply-placeholder')} @input=${(e: any) => (this.text = e.target.value)}></textarea>
230
- <div class="row">
231
- ${this.files.map(file => html`<span class="hint">${file.name}</span>`)}
232
- <ox-input-file quiet hide-filelist multiple icon="attach_file" .progress=${this.progress} @change=${(e: CustomEvent) => this.pick(e)}></ox-input-file>
233
- <span class="spacer"></span>
234
- ${one.asHandler && !one.assignee ? html`<md-text-button ?disabled=${this.busy} @click=${() => this.act(() => inquiryApi.take(one.id))}>${t('inquiry.button.take')}</md-text-button>` : nothing}
235
- ${one.asHandler
236
- ? handlerMoves.map(status => html`<md-text-button ?disabled=${this.busy} @click=${() => this.move(one, status)}>${moveWord(status)}</md-text-button>`)
237
- : nothing}
238
- ${canClose ? html`<md-text-button ?disabled=${this.busy} @click=${() => this.move(one, 'CLOSED')}>${moveWord('CLOSED')}</md-text-button>` : nothing}
239
- <md-outlined-button ?disabled=${!said || this.busy} @click=${() => this.reply(one, false)}>${t('inquiry.button.reply')}</md-outlined-button>
240
- ${one.asHandler ? html`<md-filled-button ?disabled=${!said || this.busy} @click=${() => this.reply(one, true)}>${t('inquiry.button.answer')}</md-filled-button>` : nothing}
241
- </div>
242
- <span class="hint">${t('inquiry.text.move-hint')}</span>
243
- </div>`
244
- }
245
-
246
- private pick(e: CustomEvent) {
247
- const picked = [...((e.detail as File[]) ?? [])]
248
- if (!picked.length) return
249
- ;(e.target as any).reset?.()
250
- this.files = [...this.files, ...picked]
251
- }
252
-
253
- private async act(work: () => Promise<unknown>) {
254
- this.busy = true
255
- try {
256
- await work()
257
- this.text = ''
258
- this.files = []
259
- inquiriesChanged()
260
- await this.load()
261
- } catch (error) {
262
- notify({ level: 'error', message: reasonOf(error) })
263
- } finally {
264
- this.busy = false
265
- this.progress = undefined
266
- }
267
- }
268
-
269
- private reply(one: InquiryItem, asAnswer: boolean) {
270
- return this.act(() => inquiryApi.reply(one.id, this.text.trim(), asAnswer, this.files, fraction => (this.progress = fraction)))
271
- }
272
-
273
- /* What is written in the box goes with the move as its reason. */
274
- private move(one: InquiryItem, status: string) {
275
- return this.act(() => inquiryApi.move(one.id, status, this.text.trim() || undefined))
276
- }
277
- }
@@ -1,36 +0,0 @@
1
- import '@material/web/button/filled-button.js'
2
-
3
- import { css, html, LitElement } from 'lit'
4
- import { customElement, property } from 'lit/decorators.js'
5
-
6
- import { i18next } from '@operato/i18n'
7
- import { navigate } from '@operato/shell'
8
-
9
- /*
10
- * 문의 처리의 할 일 (ADR-0184 decision 7) - what the worklist to-do shows: the way to the inquiry. Answering, deciding or
11
- * closing it there closes the to-do; nothing is answered here.
12
- */
13
- @customElement('inquiry-todo')
14
- export class InquiryTodo extends LitElement {
15
- static styles = css`
16
- :host {
17
- display: block;
18
- padding: var(--spacing-large, 12px);
19
- font-size: var(--ui-t-body, 13px);
20
- line-height: 1.6;
21
- color: var(--ui-ink);
22
- }
23
-
24
- p {
25
- margin: 0 0 var(--spacing-medium, 8px);
26
- }
27
- `
28
-
29
- @property({ attribute: false }) input: { inquiryId?: string } = {}
30
-
31
- render() {
32
- const id = this.input?.inquiryId ?? ''
33
- return html`<p>${i18next.t('inquiry.text.todo-body')}</p>
34
- <md-filled-button ?disabled=${!id} @click=${() => navigate(`inquiry-list?id=${id}`)}>${i18next.t('inquiry.button.open')}</md-filled-button>`
35
- }
36
- }
@@ -1,212 +0,0 @@
1
- /*
2
- * 문의 · 제안 보내기 (ADR-0184 decisions 1, 4, 5, 9) - the window the avatar menu opens on any screen: the kind, the title,
3
- * the words, files, and who reads it. The screen it was opened on goes with it; the application, its version, the
4
- * writer and the domain are the server's to take.
5
- */
6
- import '@material/web/icon/icon.js'
7
- import '@material/web/button/filled-button.js'
8
- import '@material/web/button/text-button.js'
9
- import '@operato/input/ox-input-file.js'
10
- import '@operato/input/ox-input-select-buttons.js'
11
- import '@operato/popup/ox-popup-actions.js'
12
-
13
- import { css, html, LitElement, nothing } from 'lit'
14
- import { customElement, property, state } from 'lit/decorators.js'
15
-
16
- import { i18next, localize } from '@operato/i18n'
17
- import { notify, openPopup } from '@operato/layout'
18
- import { CommonPopupStyles, FormFieldStyles, PopupBodyStyles, PopupFieldStyles } from '@operato/styles'
19
-
20
- import { inquiriesChanged, inquiryApi } from '../utils/inquiry-api.js'
21
- import { KINDS, kindWord, reasonOf, t } from '../utils/inquiry-words.js'
22
-
23
- const MAX_TITLE = 200
24
-
25
- /** Opens the window, carrying the screen it is opened on. */
26
- export function openInquiryWriter(kind = 'QUESTION') {
27
- const pageUrl = location.href
28
- const handle: any = openPopup(
29
- html`<inquiry-writer .kind=${kind} .pageUrl=${pageUrl} @close=${() => handle.close()}></inquiry-writer>`,
30
- {
31
- backdrop: true,
32
- size: 'large',
33
- title: t('inquiry.title.write')
34
- }
35
- )
36
- return handle
37
- }
38
-
39
- @customElement('inquiry-writer')
40
- export class InquiryWriter extends localize(i18next)(LitElement) {
41
- /* The house shape of a popup (page-standard sections 7, 8): only the body scrolls, fields are labels above native
42
- inputs, the one button row is `ox-popup-actions`. */
43
- static styles = [
44
- CommonPopupStyles,
45
- PopupBodyStyles,
46
- FormFieldStyles,
47
- PopupFieldStyles,
48
- css`
49
- ox-input-select-buttons {
50
- align-self: flex-start;
51
- }
52
- textarea {
53
- min-height: 160px;
54
- resize: vertical;
55
- }
56
- .note {
57
- color: var(--ui-muted);
58
- font-size: var(--ui-t-small, 11.5px);
59
- word-break: break-all;
60
- }
61
- .files {
62
- display: flex;
63
- flex-wrap: wrap;
64
- gap: var(--spacing-small, 4px);
65
- align-items: center;
66
- color: var(--ui-ink);
67
- }
68
- .file {
69
- display: inline-flex;
70
- align-items: center;
71
- gap: 2px;
72
- }
73
- .file md-icon {
74
- --md-icon-size: 14px;
75
- }
76
- .file button {
77
- border: 0;
78
- background: transparent;
79
- box-shadow: none;
80
- padding: 0;
81
- cursor: pointer;
82
- color: var(--ui-muted);
83
- }
84
- .check {
85
- display: flex;
86
- align-items: center;
87
- gap: var(--spacing-small, 4px);
88
- color: var(--ui-ink);
89
- }
90
- /* The field rule stretches its inputs to the row; a checkbox keeps its own size. */
91
- .field .check input {
92
- width: auto;
93
- margin: 0;
94
- }
95
- `
96
- ]
97
-
98
- @property() kind = 'QUESTION'
99
- @property() pageUrl = ''
100
- @state() title = ''
101
- @state() body = ''
102
- @state() privately = false
103
- @state() files: File[] = []
104
- @state() sending = false
105
- /* Set only while files go up - the picker shows its bar then. */
106
- @state() progress?: number
107
-
108
- render() {
109
- const title = this.title.trim()
110
- return html`
111
- <div class="body">
112
- <div class="field">
113
- <ox-input-select-buttons
114
- .value=${this.kind}
115
- .options=${KINDS.map(code => ({ display: kindWord(code), value: code }))}
116
- @change=${(e: CustomEvent) => e.detail && (this.kind = e.detail)}
117
- ></ox-input-select-buttons>
118
- <span>${t(`inquiry.text.kind-hint-${this.kind.toLowerCase()}`)}</span>
119
- </div>
120
- <label class="field">
121
- <span
122
- >${t('inquiry.field.title')} ·
123
- ${t('inquiry.text.title-limit', { count: this.title.length, max: MAX_TITLE })}</span
124
- >
125
- <input .value=${this.title} maxlength=${MAX_TITLE} @input=${(e: any) => (this.title = e.target.value)} />
126
- </label>
127
- <label class="field">
128
- <span>${t('inquiry.field.body')}</span>
129
- <textarea
130
- .value=${this.body}
131
- placeholder=${t(`inquiry.text.body-placeholder-${this.kind.toLowerCase()}`)}
132
- @input=${(e: any) => (this.body = e.target.value)}
133
- ></textarea>
134
- </label>
135
- <div class="files">
136
- ${this.files.map(
137
- (file, index) =>
138
- html`<span class="file"
139
- ><md-icon>attach_file</md-icon>${file.name}<button
140
- aria-label=${t('inquiry.button.remove')}
141
- @click=${() => (this.files = this.files.filter((_, i) => i !== index))}
142
- >
143
- <md-icon>close</md-icon>
144
- </button></span
145
- >`
146
- )}
147
- <ox-input-file
148
- quiet
149
- hide-filelist
150
- multiple
151
- icon="attach_file"
152
- .progress=${this.progress}
153
- @change=${(e: CustomEvent) => this.pick(e)}
154
- ></ox-input-file>
155
- </div>
156
- <div class="field">
157
- <label class="check"
158
- ><input
159
- type="checkbox"
160
- .checked=${this.privately}
161
- @change=${(e: any) => (this.privately = e.target.checked)}
162
- />${t('inquiry.text.private-label')}</label
163
- >
164
- <span>${this.privately ? t('inquiry.text.private-hint') : t('inquiry.text.domain-hint')}</span>
165
- </div>
166
- ${this.pageUrl ? html`<div class="note">${t('inquiry.text.page-hint', { page: this.pageUrl })}</div>` : nothing}
167
- </div>
168
- <ox-popup-actions>
169
- <md-text-button slot="cancel" @click=${() => this.dispatchEvent(new CustomEvent('close'))}
170
- >${t('inquiry.button.cancel')}</md-text-button
171
- >
172
- <md-filled-button slot="confirm" ?disabled=${!title || this.sending} @click=${() => this.send()}
173
- >${t('inquiry.button.submit')}</md-filled-button
174
- >
175
- </ox-popup-actions>
176
- `
177
- }
178
-
179
- private pick(e: CustomEvent) {
180
- const picked = [...((e.detail as File[]) ?? [])]
181
- /* The picker's own reset says `change` again, with nothing - that one is let go. */
182
- if (!picked.length) return
183
- ;(e.target as any).reset?.()
184
- this.files = [...this.files, ...picked]
185
- }
186
-
187
- private async send() {
188
- this.sending = true
189
- try {
190
- await inquiryApi.create(
191
- {
192
- kind: this.kind,
193
- title: this.title.trim(),
194
- body: this.body,
195
- visibility: this.privately ? 'PRIVATE' : 'DOMAIN',
196
- pageUrl: this.pageUrl
197
- },
198
- this.files,
199
- fraction => (this.progress = fraction)
200
- )
201
- inquiriesChanged()
202
- notify({ message: t('inquiry.text.sent') })
203
- /* The writer stays on the screen they were on - the answer comes as a notice and on the menu's badge. */
204
- this.dispatchEvent(new CustomEvent('close'))
205
- } catch (error) {
206
- notify({ level: 'error', message: reasonOf(error) })
207
- } finally {
208
- this.sending = false
209
- this.progress = undefined
210
- }
211
- }
212
- }
package/client/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export { generateInquiryBadge, refreshInquiryBadge } from './utils/inquiry-badge.js'
2
- export { openInquiryWriter } from './components/inquiry-writer.js'
@@ -1,45 +0,0 @@
1
- /*
2
- * Keeps the menu badge up with inquiries while the application is open (ADR-0184 decision 8). The notice of an inquiry
3
- * reaches the screen through notification's subscription; the count itself is asked of the server.
4
- */
5
- import gql from 'graphql-tag'
6
-
7
- import { subscribe } from '@operato/graphql'
8
-
9
- import { refreshInquiryBadge } from './utils/inquiry-badge.js'
10
-
11
- window.addEventListener('inquiries-changed', () => refreshInquiryBadge())
12
- let subscription: { unsubscribe(): void } | undefined
13
- let watching = false
14
-
15
- export async function startInquiryWatch(): Promise<void> {
16
- if (watching) return
17
- watching = true
18
- refreshInquiryBadge()
19
- try {
20
- subscription = await subscribe(
21
- {
22
- query: gql`
23
- subscription {
24
- notification(subjects: ["info"]) {
25
- type
26
- }
27
- }
28
- `
29
- },
30
- {
31
- next({ data }: any) {
32
- if (data?.notification?.type === 'inquiry') refreshInquiryBadge()
33
- }
34
- }
35
- )
36
- } catch {
37
- /* without the live line the badge comes at the next sign-in or the next change on a screen */
38
- }
39
- }
40
-
41
- export function stopInquiryWatch(): void {
42
- subscription?.unsubscribe()
43
- subscription = undefined
44
- watching = false
45
- }