@things-factory/i18n-base 8.0.0-beta.9 → 8.0.2

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.
@@ -0,0 +1,16 @@
1
+ import { PageView } from '@operato/shell'
2
+
3
+ declare module '@operato/shell' {
4
+ interface PageView {
5
+ languageUpdated(i18next: any): void
6
+ }
7
+ }
8
+
9
+ export default function bootstrap() {
10
+ /*
11
+ * PageView의 lifecycle에 languageUpdate를 추가한다.
12
+ */
13
+ PageView.prototype.languageUpdated = function (i18next) {
14
+ this.pageReset()
15
+ }
16
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @license Copyright © HatioLab Inc. All rights reserved.
3
+ */
4
+
5
+ export { i18next } from '@operato/i18n/config.js'
6
+ export * from '@operato/i18n/localize.js'
@@ -0,0 +1,408 @@
1
+ import '@material/web/icon/icon.js'
2
+
3
+ import '@operato/data-grist'
4
+ import '@operato/context/ox-context-page-toolbar.js'
5
+ import '@operato/input/ox-input-select-buttons.js'
6
+
7
+ import { css, html } from 'lit'
8
+ import { customElement, query, state } from 'lit/decorators.js'
9
+ import { connect } from 'pwa-helpers/connect-mixin.js'
10
+ import gql from 'graphql-tag'
11
+
12
+ import { store, CustomAlert, PageView } from '@operato/shell'
13
+ import { client } from '@operato/graphql'
14
+ import { DataGrist, FetchOption, GristRecord } from '@operato/data-grist'
15
+ import { i18next, localize } from '@operato/i18n'
16
+ import { CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'
17
+ import { isMobileDevice } from '@operato/utils'
18
+ import { getLanguages } from '@things-factory/auth-base/dist-client'
19
+
20
+ @customElement('terminology-page')
21
+ export class TerminologyPage extends connect(store)(localize(i18next)(PageView)) {
22
+ static styles = [
23
+ ScrollbarStyles,
24
+ CommonHeaderStyles,
25
+ css`
26
+ :host {
27
+ display: flex;
28
+ flex-direction: column;
29
+
30
+ overflow: hidden;
31
+
32
+ --grid-header-padding: 2px 0 2px 9px;
33
+ }
34
+
35
+ ox-grist {
36
+ overflow-y: auto;
37
+ flex: 1;
38
+ }
39
+
40
+ .header {
41
+ grid-template-areas: 'filters actions';
42
+ }
43
+ `
44
+ ]
45
+
46
+ @state() private gristConfig: any
47
+ @state() private mode: 'CARD' | 'GRID' | 'LIST' = isMobileDevice() ? 'CARD' : 'GRID'
48
+ @state() private state?: string[]
49
+
50
+ @query('ox-grist') private grist!: DataGrist
51
+
52
+ get context() {
53
+ return {
54
+ title: i18next.t('title.terminology'),
55
+ search: {
56
+ handler: (search: string) => {
57
+ this.grist.searchText = search
58
+ },
59
+ value: this.grist?.searchText || ''
60
+ },
61
+ filter: {
62
+ handler: () => {
63
+ this.grist.toggleHeadroom()
64
+ }
65
+ },
66
+ help: 'mfg-warehouse/term-page',
67
+ actions: [
68
+ {
69
+ title: i18next.t('button.clear-cache'),
70
+ action: this.clearCache.bind(this),
71
+ icon: 'ink_eraser'
72
+ },
73
+ {
74
+ title: i18next.t('button.save'),
75
+ action: this.save.bind(this),
76
+ icon: 'save'
77
+ },
78
+ {
79
+ title: i18next.t('button.delete'),
80
+ action: this.delete.bind(this),
81
+ icon: 'delete',
82
+ emphasis: {
83
+ danger: true
84
+ }
85
+ }
86
+ ],
87
+ exportable: {
88
+ name: i18next.t('button.export'),
89
+ data: this.exportHandler.bind(this)
90
+ },
91
+ toolbar: false
92
+ }
93
+ }
94
+
95
+ async getFilterStatus() {
96
+ return (await getLanguages()).map(({ code, display }) => {
97
+ return { value: code, display }
98
+ })
99
+ }
100
+
101
+ render() {
102
+ const mode = this.mode || (isMobileDevice() ? 'LIST' : 'GRID')
103
+
104
+ return html`
105
+ <ox-grist .mode=${mode} .config=${this.gristConfig} .fetchHandler=${this.fetchHandler.bind(this)}>
106
+ <div slot="headroom" class="header">
107
+ <div class="filters">
108
+ <ox-filters-form autofocus without-search></ox-filters-form>
109
+ </div>
110
+
111
+ <ox-context-page-toolbar class="actions" .context=${this.context}></ox-context-page-toolbar>
112
+ </div>
113
+ </ox-grist>
114
+ `
115
+ }
116
+
117
+ async pageInitialized(lifecycle) {
118
+ this.gristConfig = {
119
+ list: {
120
+ fields: ['name', 'description'],
121
+ details: ['active', 'updatedAt']
122
+ },
123
+ columns: [
124
+ { type: 'gutter', gutterName: 'sequence' },
125
+ { type: 'gutter', gutterName: 'row-selector', multiple: true },
126
+ {
127
+ type: 'string',
128
+ name: 'id',
129
+ header: i18next.t('field.id'),
130
+ hidden: true,
131
+ width: 0
132
+ },
133
+ {
134
+ type: 'string',
135
+ name: 'name',
136
+ header: i18next.t('field.name'),
137
+ record: {
138
+ editable: true
139
+ },
140
+ filter: 'search',
141
+ sortable: true,
142
+ width: 250
143
+ },
144
+ {
145
+ type: 'select',
146
+ name: 'locale',
147
+ header: i18next.t('field.locale'),
148
+ record: {
149
+ editable: true,
150
+ options: [{ display: '', value: '' }, ...(await this.getFilterStatus())]
151
+ },
152
+ width: 90,
153
+ filter: {
154
+ type: 'select-buttons',
155
+ operator: 'in'
156
+ }
157
+ },
158
+ {
159
+ type: 'select',
160
+ name: 'category',
161
+ label: true,
162
+ header: i18next.t('field.category'),
163
+ record: {
164
+ editable: true,
165
+ options: ['', 'button', 'field', 'label', 'menu', 'text', 'title']
166
+ },
167
+ width: 90,
168
+ filter: 'in',
169
+ imex: true
170
+ },
171
+ {
172
+ type: 'string',
173
+ name: 'display',
174
+ label: true,
175
+ header: i18next.t('field.display'),
176
+ record: {
177
+ editable: true
178
+ },
179
+ filter: 'search',
180
+ sortable: true,
181
+ width: 380
182
+ },
183
+ {
184
+ type: 'resource-object',
185
+ name: 'updater',
186
+ header: i18next.t('field.updater'),
187
+ record: {
188
+ editable: false
189
+ },
190
+ sortable: true,
191
+ width: 120
192
+ },
193
+ {
194
+ type: 'datetime',
195
+ name: 'updatedAt',
196
+ header: i18next.t('field.updated_at'),
197
+ record: {
198
+ editable: false
199
+ },
200
+ sortable: true,
201
+ width: 180
202
+ }
203
+ ],
204
+ rows: {
205
+ appendable: true,
206
+ selectable: {
207
+ multiple: true
208
+ }
209
+ /*handlers: {
210
+ click: (columns, data, column, record, rowIndex) => {
211
+ // TODO Implement the desired sub-view(like item-view) and use it in the popup content after importing
212
+ const popup = openPopup(html`
213
+ <item-view item-id=${record.id} style="background-color: white;">
214
+ </item-view>
215
+ `, {
216
+ backdrop: true,
217
+ size: 'large',
218
+ title: i18next.t('text.item view')
219
+ })
220
+ popup.onclosed = () => {
221
+ this.grist.fetch()
222
+ }
223
+ }
224
+ }*/
225
+ },
226
+ sorters: [
227
+ {
228
+ name: 'name',
229
+ desc: true
230
+ }
231
+ ]
232
+ }
233
+ }
234
+
235
+ async pageUpdated(changes: any, lifecycle: any) {
236
+ if (this.active) {
237
+ // do something here when this page just became as active
238
+ }
239
+ }
240
+
241
+ async fetchHandler({ page = 1, limit = 100, sortings = [], filters = [] }: FetchOption) {
242
+ const response = await client.query({
243
+ query: gql`
244
+ query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
245
+ terminologies(filters: $filters, pagination: $pagination, sortings: $sortings) {
246
+ items {
247
+ id
248
+ name
249
+ locale
250
+ category
251
+ display
252
+ creator {
253
+ id
254
+ name
255
+ }
256
+ createdAt
257
+ updater {
258
+ id
259
+ name
260
+ }
261
+ updatedAt
262
+ }
263
+
264
+ total
265
+ }
266
+ }
267
+ `,
268
+ variables: {
269
+ filters,
270
+ pagination: { page, limit },
271
+ sortings
272
+ }
273
+ })
274
+
275
+ return {
276
+ total: response.data.terminologies.total || 0,
277
+ records: response.data.terminologies.items || []
278
+ }
279
+ }
280
+
281
+ exportHandler() {
282
+ let records = [] as GristRecord
283
+ if (this.grist.selected && this.grist.selected.length > 0) {
284
+ records = this.grist.selected
285
+ } else {
286
+ records = this.grist.data.records
287
+ }
288
+
289
+ var headerSetting = this.grist.compiledConfig.columns
290
+ .filter(column => column.type !== 'gutter' && column.record !== undefined && column.imex !== undefined)
291
+ .map(column => {
292
+ return column.imex === true
293
+ ? {
294
+ header: column.header.renderer(column),
295
+ key: column.name,
296
+ width: column.width,
297
+ type: column.type
298
+ }
299
+ : column.imex
300
+ })
301
+
302
+ var data = records.map(item => {
303
+ return {
304
+ id: item.id,
305
+ ...this.gristConfig.columns
306
+ .filter(column => column.type !== 'gutter' && column.record !== undefined && column.imex !== undefined)
307
+ .reduce((record, column) => {
308
+ const key = column.imex === true ? column.name : column.imex.key
309
+ record[key] = key
310
+ .split('.')
311
+ .reduce((obj, key) => (obj && obj[key] !== 'undefined' ? obj[key] : undefined), item)
312
+ return record
313
+ }, {})
314
+ }
315
+ })
316
+
317
+ return { header: headerSetting, data: data }
318
+ }
319
+
320
+ async delete() {
321
+ const selectedRecords = this.grist.selected
322
+
323
+ if (!selectedRecords || selectedRecords.length == 0) {
324
+ this.showToast(i18next.t('text.NOTHING_SELECTED'))
325
+ } else {
326
+ const answer = await CustomAlert({
327
+ type: 'question',
328
+ title: i18next.t('button.delete'),
329
+ text: i18next.t('text.are_you_sure'),
330
+ confirmButton: { text: i18next.t('button.delete') },
331
+ cancelButton: { text: i18next.t('button.cancel') }
332
+ })
333
+
334
+ if (!answer) {
335
+ return
336
+ }
337
+
338
+ let ids = selectedRecords.map(record => record.id)
339
+
340
+ const response = await client.mutate({
341
+ mutation: gql`
342
+ mutation ($ids: [String!]!) {
343
+ deleteTerminologies(ids: $ids)
344
+ }
345
+ `,
346
+ variables: {
347
+ ids
348
+ }
349
+ })
350
+
351
+ this.grist.fetch()
352
+
353
+ return response.data.deleteTerminologies
354
+ }
355
+ }
356
+
357
+ async clearCache() {
358
+ const response = await client.mutate({
359
+ mutation: gql`
360
+ mutation {
361
+ clearTranslationsCache
362
+ }
363
+ `
364
+ })
365
+
366
+ if (!response.errors) {
367
+ this.showToast(i18next.t('text.info_x_successfully', { x: i18next.t('text.clearing-cache') }))
368
+ }
369
+ }
370
+
371
+ async save() {
372
+ let changedRecords = this.grist.dirtyRecords
373
+
374
+ if (!changedRecords || changedRecords.length == 0) {
375
+ this.showToast(i18next.t('text.nothing_changed'))
376
+ } else {
377
+ const patches = changedRecords.map(patch => {
378
+ const patchField: any = patch.id ? { id: patch.id } : {}
379
+ const dirtyFields = patch.__dirtyfields__
380
+ for (let key in dirtyFields) {
381
+ patchField[key] = dirtyFields[key].after
382
+ }
383
+ patchField.cuFlag = patch.__dirty__
384
+
385
+ return patchField
386
+ })
387
+
388
+ const response = await client.mutate({
389
+ mutation: gql`
390
+ mutation ($patches: [TerminologyPatch!]!) {
391
+ updateMultipleTerminologies(patches: $patches) {
392
+ id
393
+ }
394
+ }
395
+ `,
396
+ variables: {
397
+ patches
398
+ }
399
+ })
400
+
401
+ this.grist.fetch()
402
+ }
403
+ }
404
+
405
+ showToast(message: string) {
406
+ document.dispatchEvent(new CustomEvent('notify', { detail: { message } }))
407
+ }
408
+ }
@@ -0,0 +1,7 @@
1
+ export default function route(page) {
2
+ switch (page) {
3
+ case 'terminology-page':
4
+ import('./page/terminology-page.js')
5
+ return page
6
+ }
7
+ }