@things-factory/setting-ui 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,8 @@
1
+ import { clientSettingStore } from '@operato/shell'
2
+ import { setThemeMode } from './set-theme-mode.js'
3
+
4
+ export default async function bootstrap() {
5
+ const themeMode = ((await clientSettingStore.get('theme'))?.value || {}).mode || 'light'
6
+
7
+ setThemeMode(themeMode)
8
+ }
@@ -0,0 +1,139 @@
1
+ import '@material/web/icon/icon.js'
2
+ import '@things-factory/form-ui'
3
+ import '@operato/data-grist'
4
+
5
+ import { i18next } from '@operato/i18n'
6
+ import { client, gqlContext } from '@operato/graphql'
7
+ import { isMobileDevice } from '@operato/utils'
8
+ import { CommonGristStyles, CommonHeaderStyles } from '@operato/styles'
9
+
10
+ import gql from 'graphql-tag'
11
+ import { css, html, LitElement } from 'lit'
12
+ import { customElement, query, state } from 'lit/decorators.js'
13
+ import { FetchOption } from '@operato/data-grist'
14
+
15
+ @customElement('partner-selector')
16
+ class PartnerSelector extends LitElement {
17
+ static styles = [
18
+ CommonGristStyles,
19
+ CommonHeaderStyles,
20
+ css`
21
+ :host {
22
+ display: flex;
23
+ flex-direction: column;
24
+ overflow: hidden;
25
+ background-color: var(--md-sys-color-surface);
26
+ }
27
+
28
+ ox-grist {
29
+ overflow-y: auto;
30
+ flex: 1;
31
+ }
32
+
33
+ ox-filters-form {
34
+ flex: 1;
35
+ }
36
+ `
37
+ ]
38
+
39
+ @state() private config: any
40
+ @state() private data: any
41
+ @state() private mode: string = isMobileDevice() ? 'LIST' : 'GRID'
42
+
43
+ @query('ox-grist') private dataGrist: any
44
+
45
+ render() {
46
+ return html`
47
+ <ox-grist .mode=${this.mode} auto-fetch .config=${this.config} .fetchHandler=${this.fetchHandler.bind(this)}>
48
+ <div slot="headroom" class="header">
49
+ <div class="filters">
50
+ <ox-filters-form></ox-filters-form>
51
+ </div>
52
+ </div>
53
+ </ox-grist>
54
+
55
+ <div class="footer">
56
+ <div filler></div>
57
+ <button @click=${this.select.bind(this)} done>
58
+ <md-icon>select_check_box</md-icon>${i18next.t('button.select')}
59
+ </button>
60
+ </div>
61
+ `
62
+ }
63
+
64
+ firstUpdated() {
65
+ this.config = {
66
+ list: { fields: ['name', 'description'] },
67
+ rows: { selectable: { multiple: true }, appendable: false, handlers: { click: 'select-row-toggle' } },
68
+ columns: [
69
+ { type: 'gutter', gutterName: 'sequence' },
70
+ { type: 'gutter', gutterName: 'row-selector', multiple: false },
71
+ {
72
+ type: 'string',
73
+ name: 'name',
74
+ header: i18next.t('field.name'),
75
+ record: { editable: false, align: 'left' },
76
+ sortable: true,
77
+ filter: 'search',
78
+ width: 100
79
+ },
80
+ {
81
+ type: 'string',
82
+ name: 'description',
83
+ header: i18next.t('field.description'),
84
+ record: { editable: false, align: 'left' },
85
+ sortable: true,
86
+ filter: 'search',
87
+ width: 200
88
+ }
89
+ ]
90
+ }
91
+ }
92
+
93
+ async fetchHandler({ filters, page, limit, sorters = [] }: FetchOption) {
94
+ const pagination = { page, limit }
95
+ const sortings = sorters
96
+
97
+ const response = await client.query({
98
+ query: gql`
99
+ query searchCustomers($filters: [Filter!]!, $pagination: Pagination!, $sortings: [Sorting!]!) {
100
+ searchCustomers(filters: $filters, pagination: $pagination, sortings: $sortings) {
101
+ items {
102
+ id
103
+ name
104
+ description
105
+ }
106
+ total
107
+ }
108
+ }
109
+ `,
110
+ variables: { filters, pagination, sortings },
111
+ context: gqlContext()
112
+ })
113
+
114
+ return {
115
+ records: response.data.searchCustomers.items || [],
116
+ total: response.data.searchCustomers.total || 0
117
+ }
118
+ }
119
+
120
+ select() {
121
+ const selectedItems = this.dataGrist.selected
122
+ if (!selectedItems.length) {
123
+ this.showToast(i18next.t('text.nothing_selected'))
124
+ return
125
+ }
126
+
127
+ history.back()
128
+
129
+ this.dispatchEvent(
130
+ new CustomEvent('select', {
131
+ detail: selectedItems[0]
132
+ })
133
+ )
134
+ }
135
+
136
+ showToast(message) {
137
+ document.dispatchEvent(new CustomEvent('notify', { detail: { message, option: { timer: 1000 } } }))
138
+ }
139
+ }
@@ -0,0 +1,30 @@
1
+ import gql from 'graphql-tag'
2
+ import { client } from '@operato/graphql'
3
+
4
+ export async function fetchSettingRule(name: string) {
5
+ const response = await client.query({
6
+ query: gql`
7
+ query ($name: String!) {
8
+ setting(name: $name) {
9
+ value
10
+ }
11
+ }
12
+ `,
13
+ variables: { name }
14
+ })
15
+
16
+ if (!response.errors) {
17
+ if (response.data.setting?.value) {
18
+ switch (response.data.setting.value.toLowerCase().trim()) {
19
+ case 'true':
20
+ return true
21
+ case 'false':
22
+ return false
23
+ default:
24
+ return response.data.setting.value.trim()
25
+ }
26
+ }
27
+
28
+ return false
29
+ }
30
+ }
@@ -0,0 +1,7 @@
1
+ export * from './fetch-setting-value'
2
+ export { SettingPage } from './pages/setting'
3
+ export { SettingList } from './pages/setting-list'
4
+
5
+ export * from './setting-lets/domain-switch-let'
6
+ export * from './setting-lets/secure-iplist-setting-let'
7
+ export * from './setting-lets/theme-mode-setting-let'
@@ -0,0 +1,379 @@
1
+ import '@things-factory/form-ui'
2
+ import '../components/partner-selector'
3
+
4
+ import gql from 'graphql-tag'
5
+ import { css, html } from 'lit'
6
+ import { customElement, query, state } from 'lit/decorators.js'
7
+ import { connect } from 'pwa-helpers/connect-mixin'
8
+
9
+ import { openPopup } from '@operato/layout'
10
+ import { i18next, localize } from '@operato/i18n'
11
+ import { PageView, store } from '@operato/shell'
12
+ import { client, gqlContext } from '@operato/graphql'
13
+ import { CommonButtonStyles, CommonHeaderStyles, CommonGristStyles, ScrollbarStyles } from '@operato/styles'
14
+ import { OxPrompt } from '@operato/popup/ox-prompt.js'
15
+ import { FetchOption, getEditor, getRenderer } from '@operato/data-grist'
16
+ import { isMobileDevice } from '@operato/utils'
17
+
18
+ @customElement('partner-setting-list')
19
+ export class PartnerSettingList extends connect(store)(localize(i18next)(PageView)) {
20
+ static styles = [
21
+ CommonGristStyles,
22
+ ScrollbarStyles,
23
+ CommonHeaderStyles,
24
+ css`
25
+ :host {
26
+ display: flex;
27
+ flex-direction: column;
28
+ overflow: hidden;
29
+ }
30
+
31
+ fieldset {
32
+ max-width: var(--input-container-max-width);
33
+ border: none;
34
+ }
35
+
36
+ label {
37
+ font: var(--label-font);
38
+ color: var(--label-color, var(--md-sys-color-on-surface));
39
+ text-transform: var(--label-text-transform);
40
+ }
41
+
42
+ input {
43
+ border: var(--border-dim-color);
44
+ border-radius: var(--border-radius);
45
+ margin: var(--input-margin);
46
+ padding: var(--input-padding);
47
+ background-color: var(--md-sys-color-surface);
48
+ font: var(--input-font);
49
+ }
50
+
51
+ ox-grist {
52
+ overflow-y: auto;
53
+ flex: 1;
54
+ }
55
+
56
+ ox-filters-form {
57
+ flex: 1;
58
+ }
59
+ `
60
+ ]
61
+
62
+ @state() private config: any
63
+ @state() private partnerDomain: any
64
+ @state() private mode: 'GRID' | 'LIST' | 'CARD' = isMobileDevice() ? 'LIST' : 'GRID'
65
+
66
+ @query('ox-grist') private dataGrist: any
67
+
68
+ render() {
69
+ return html`
70
+ <form class="multi-column-form">
71
+ <fieldset>
72
+ <label>${i18next.t('label.partner')}</label>
73
+ <input
74
+ readonly
75
+ name="partnerDomain"
76
+ value="${this.partnerDomain?.name ? this.partnerDomain.name : ''}"
77
+ @click="${this.openPartnerSelector.bind(this)}"
78
+ placeholder=${String(i18next.t('text.please_choose_partner') || '')}
79
+ />
80
+ </fieldset>
81
+ </form>
82
+
83
+ <ox-grist
84
+ .mode=${this.mode}
85
+ .config=${this.config}
86
+ .fetchHandler="${this.fetchHandler.bind(this)}"
87
+ @record-change="${this.onRecordChangeHandler.bind(this)}"
88
+ >
89
+ <div slot="headroom" class="header">
90
+ <div class="filters">
91
+ <ox-filters-form></ox-filters-form>
92
+ </div>
93
+ </div>
94
+ </ox-grist>
95
+ `
96
+ }
97
+
98
+ get context() {
99
+ return {
100
+ title: i18next.t('title.partner_setting'),
101
+ help: 'setting/partner-setting',
102
+ actions: [
103
+ {
104
+ title: i18next.t('button.save'),
105
+ action: this.save.bind(this),
106
+ ...CommonButtonStyles.save
107
+ },
108
+ {
109
+ title: i18next.t('button.delete'),
110
+ action: this.delete.bind(this),
111
+ ...CommonButtonStyles.delete
112
+ }
113
+ ]
114
+ }
115
+ }
116
+
117
+ pageInitialized() {
118
+ this.config = {
119
+ list: {
120
+ fields: ['name', 'description', 'value']
121
+ },
122
+ rows: { selectable: { multiple: true } },
123
+ columns: [
124
+ { type: 'gutter', gutterName: 'dirty' },
125
+ { type: 'gutter', gutterName: 'sequence' },
126
+ { type: 'gutter', gutterName: 'row-selector', multiple: true },
127
+ {
128
+ type: 'object',
129
+ name: 'setting',
130
+ header: i18next.t('label.setting'),
131
+ width: 280,
132
+ record: {
133
+ editable: true,
134
+ options: {
135
+ queryName: 'settings',
136
+ select: [
137
+ { name: 'id', hidden: true },
138
+ { name: 'name', header: i18next.t('field.name') },
139
+ { name: 'description', header: i18next.t('field.description') },
140
+ { name: 'category', header: i18next.t('field.category') }
141
+ ],
142
+ list: { fields: ['name', 'description', 'category'] }
143
+ }
144
+ }
145
+ },
146
+ {
147
+ type: 'string',
148
+ name: 'name',
149
+ header: i18next.t('field.name'),
150
+ record: { editable: false, align: 'left' },
151
+ sortable: true,
152
+ filter: 'search',
153
+ width: 200
154
+ },
155
+ {
156
+ type: 'string',
157
+ name: 'description',
158
+ header: i18next.t('field.description'),
159
+ record: { editable: false, align: 'left' },
160
+ sortable: true,
161
+ filter: 'search',
162
+ width: 200
163
+ },
164
+ {
165
+ type: 'code',
166
+ name: 'category',
167
+ header: i18next.t('field.category'),
168
+ record: { editable: false, codeName: 'SETTING_CATEGORIES' },
169
+ sortable: true,
170
+ width: 150
171
+ },
172
+ {
173
+ type: 'string',
174
+ name: 'value',
175
+ header: i18next.t('field.value'),
176
+ record: {
177
+ editor: function (value, column, record, rowIndex, field) {
178
+ return getEditor(record.category)(value, column, record, rowIndex, field)
179
+ },
180
+ renderer: function (value, column, record, rowIndex, field) {
181
+ return getRenderer(record.category)(value, column, record, rowIndex, field)
182
+ },
183
+ editable: true
184
+ },
185
+ sortable: true,
186
+ width: 180
187
+ },
188
+ {
189
+ type: 'datetime',
190
+ name: 'updatedAt',
191
+ header: i18next.t('field.updated_at'),
192
+ record: { editable: false, align: 'left' },
193
+ sortable: true,
194
+ width: 150
195
+ },
196
+ {
197
+ type: 'object',
198
+ name: 'updater',
199
+ header: i18next.t('field.updater'),
200
+ record: { editable: false, align: 'left' },
201
+ sortable: true,
202
+ width: 150
203
+ }
204
+ ]
205
+ }
206
+ }
207
+
208
+ pageUpdated() {
209
+ if (this.active) {
210
+ this.partnerDomain = null
211
+ this.dataGrist.data = { records: [], total: 0 }
212
+ }
213
+ }
214
+
215
+ async fetchHandler({ filters, page, limit, sorters = [] }: FetchOption) {
216
+ if (!this.partnerDomain) return { total: 0, records: [] }
217
+
218
+ const pagination = { page, limit }
219
+ const sortings = sorters
220
+
221
+ const response = await client.query({
222
+ query: gql`
223
+ query partnerSettings(
224
+ $filters: [Filter!]!
225
+ $pagination: Pagination!
226
+ $sortings: [Sorting!]!
227
+ $partnerDomain: ObjectRef!
228
+ ) {
229
+ partnerSettings(
230
+ filters: $filters
231
+ pagination: $pagination
232
+ sortings: $sortings
233
+ partnerDomain: $partnerDomain
234
+ ) {
235
+ items {
236
+ id
237
+ setting {
238
+ id
239
+ name
240
+ description
241
+ category
242
+ }
243
+ name
244
+ description
245
+ category
246
+ value
247
+ updatedAt
248
+ updater {
249
+ id
250
+ name
251
+ description
252
+ }
253
+ }
254
+ total
255
+ }
256
+ }
257
+ `,
258
+ variables: { filters, pagination, sortings, partnerDomain: { id: this.partnerDomain.id } },
259
+ context: gqlContext()
260
+ })
261
+
262
+ return {
263
+ records:
264
+ response.data.partnerSettings.items.map(item => {
265
+ return { ...item, description: item.setting.description, category: item.setting.category }
266
+ }) || [],
267
+ total: response.data.partnerSettings.total || 0
268
+ }
269
+ }
270
+
271
+ async save() {
272
+ let patches = this.dataGrist.dirtyRecords
273
+
274
+ if (!patches?.length) {
275
+ return this.showToast(i18next.t('text.nothing_changed'))
276
+ }
277
+
278
+ patches = patches.map(partnerSetting => {
279
+ let patchField: any = {
280
+ cuFlag: partnerSetting.__dirty__,
281
+ setting: { id: partnerSetting.setting.id },
282
+ partnerDomain: { id: this.partnerDomain.id }
283
+ }
284
+ if (partnerSetting.id) patchField.id = partnerSetting.id
285
+ if (partnerSetting.value) patchField.value = partnerSetting.value
286
+
287
+ return patchField
288
+ })
289
+
290
+ const response = await client.mutate({
291
+ mutation: gql`
292
+ mutation updateMultiplePartnerSetting($patches: [PartnerSettingPatch!]!) {
293
+ updateMultiplePartnerSetting(patches: $patches) {
294
+ name
295
+ }
296
+ }
297
+ `,
298
+ variables: { patches },
299
+ context: gqlContext()
300
+ })
301
+
302
+ if (!response.errors?.length) {
303
+ this.showToast(i18next.t('text.data_updated_successfully'))
304
+ this.dataGrist.fetch()
305
+ }
306
+ }
307
+
308
+ async delete() {
309
+ if (!this.dataGrist.selected?.length) {
310
+ return this.showToast(i18next.t('text.there_is_nothing_to_delete'))
311
+ }
312
+
313
+ const ids = this.dataGrist.selected.map(record => record.id)
314
+
315
+ if (
316
+ await OxPrompt.open({
317
+ type: 'warning',
318
+ title: i18next.t('button.delete'),
319
+ text: i18next.t('text.are_you_sure'),
320
+ confirmButton: { text: i18next.t('button.delete') },
321
+ cancelButton: { text: i18next.t('button.cancel') }
322
+ })
323
+ ) {
324
+ const response = await client.mutate({
325
+ mutation: gql`
326
+ mutation deletePartnerSettings($ids: [String!]!) {
327
+ deletePartnerSettings(ids: $ids)
328
+ }
329
+ `,
330
+ variables: { ids },
331
+ context: gqlContext()
332
+ })
333
+
334
+ if (!response.errors) {
335
+ await OxPrompt.open({
336
+ type: 'success',
337
+ title: i18next.t('text.completed'),
338
+ text: i18next.t('text.data_deleted_successfully'),
339
+ confirmButton: { text: i18next.t('button.confirm') }
340
+ })
341
+
342
+ this.dataGrist.fetch()
343
+ }
344
+ }
345
+ }
346
+
347
+ openPartnerSelector() {
348
+ openPopup(
349
+ html`
350
+ <partner-selector
351
+ @select="${e => {
352
+ this.partnerDomain = e.detail
353
+ this.dataGrist.fetch()
354
+ }}"
355
+ ></partner-selector>
356
+ `,
357
+ {
358
+ backdrop: true,
359
+ title: i18next.t('label.partner')
360
+ }
361
+ )
362
+ }
363
+
364
+ onRecordChangeHandler({ detail: { after, column } }) {
365
+ if (column.name === 'setting') {
366
+ this.fillUpFields(after)
367
+ }
368
+ }
369
+
370
+ fillUpFields(record) {
371
+ record.name = record.setting.name
372
+ record.description = record.setting.description
373
+ record.category = record.setting.category
374
+ }
375
+
376
+ showToast(message) {
377
+ document.dispatchEvent(new CustomEvent('notify', { detail: { message, option: { timer: 1000 } } }))
378
+ }
379
+ }