@things-factory/menu-ui 8.0.0-beta.9 → 8.0.0

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 (57) hide show
  1. package/client/apptools/favorite-tool.js +130 -0
  2. package/client/bootstrap.js +57 -0
  3. package/client/components/child-menus-selector.js +150 -0
  4. package/client/components/role-select-popup.js +179 -0
  5. package/client/pages/menu-list-page.js +200 -0
  6. package/client/pages/menu-management-detail.js +384 -0
  7. package/client/pages/menu-management.js +294 -0
  8. package/client/pages/role-menus-management.js +215 -0
  9. package/client/route.js +15 -0
  10. package/client/viewparts/menu-bar.js +114 -0
  11. package/client/viewparts/menu-tile-list.js +222 -0
  12. package/client/viewparts/menu-tree-bar.js +295 -0
  13. package/dist-server/tsconfig.tsbuildinfo +1 -1
  14. package/package.json +18 -21
  15. package/server/index.ts +0 -0
  16. package/things-factory.config.js +14 -5
  17. package/dist-client/apptools/favorite-tool.d.ts +0 -28
  18. package/dist-client/apptools/favorite-tool.js +0 -139
  19. package/dist-client/apptools/favorite-tool.js.map +0 -1
  20. package/dist-client/bootstrap.d.ts +0 -4
  21. package/dist-client/bootstrap.js +0 -52
  22. package/dist-client/bootstrap.js.map +0 -1
  23. package/dist-client/components/child-menus-selector.d.ts +0 -1
  24. package/dist-client/components/child-menus-selector.js +0 -146
  25. package/dist-client/components/child-menus-selector.js.map +0 -1
  26. package/dist-client/components/role-select-popup.d.ts +0 -1
  27. package/dist-client/components/role-select-popup.js +0 -180
  28. package/dist-client/components/role-select-popup.js.map +0 -1
  29. package/dist-client/index.js +0 -2
  30. package/dist-client/index.js.map +0 -1
  31. package/dist-client/pages/menu-list-page.d.ts +0 -2
  32. package/dist-client/pages/menu-list-page.js +0 -204
  33. package/dist-client/pages/menu-list-page.js.map +0 -1
  34. package/dist-client/pages/menu-management-detail.d.ts +0 -2
  35. package/dist-client/pages/menu-management-detail.js +0 -376
  36. package/dist-client/pages/menu-management-detail.js.map +0 -1
  37. package/dist-client/pages/menu-management.d.ts +0 -3
  38. package/dist-client/pages/menu-management.js +0 -280
  39. package/dist-client/pages/menu-management.js.map +0 -1
  40. package/dist-client/pages/role-menus-management.d.ts +0 -4
  41. package/dist-client/pages/role-menus-management.js +0 -215
  42. package/dist-client/pages/role-menus-management.js.map +0 -1
  43. package/dist-client/route.d.ts +0 -1
  44. package/dist-client/route.js +0 -14
  45. package/dist-client/route.js.map +0 -1
  46. package/dist-client/tsconfig.tsbuildinfo +0 -1
  47. package/dist-client/viewparts/menu-bar.d.ts +0 -12
  48. package/dist-client/viewparts/menu-bar.js +0 -108
  49. package/dist-client/viewparts/menu-bar.js.map +0 -1
  50. package/dist-client/viewparts/menu-tile-list.d.ts +0 -13
  51. package/dist-client/viewparts/menu-tile-list.js +0 -234
  52. package/dist-client/viewparts/menu-tile-list.js.map +0 -1
  53. package/dist-client/viewparts/menu-tree-bar.d.ts +0 -28
  54. package/dist-client/viewparts/menu-tree-bar.js +0 -307
  55. package/dist-client/viewparts/menu-tree-bar.js.map +0 -1
  56. /package/{dist-client/index.d.ts → client/index.js} +0 -0
  57. /package/{dist-client → client}/themes/menu-theme.css +0 -0
@@ -0,0 +1,384 @@
1
+ import '@things-factory/form-ui'
2
+ import '@operato/data-grist'
3
+
4
+ import gql from 'graphql-tag'
5
+ import { css, html, LitElement } from 'lit'
6
+
7
+ import { getEditor, getRenderer } from '@operato/data-grist'
8
+ import { i18next, localize } from '@operato/i18n'
9
+ import { client } from '@operato/graphql'
10
+ import { ScrollbarStyles } from '@operato/styles'
11
+ import { gqlBuilder, isMobileDevice } from '@things-factory/utils'
12
+
13
+ import { OxPrompt } from '@operato/popup/ox-prompt.js'
14
+
15
+ class MenuManagementDetail extends localize(i18next)(LitElement) {
16
+ static get styles() {
17
+ return [
18
+ ScrollbarStyles,
19
+ css`
20
+ :host {
21
+ display: flex;
22
+ flex-direction: column;
23
+ overflow: hidden;
24
+ background-color: var(--md-sys-color-surface);
25
+ }
26
+ search-form {
27
+ overflow: visible;
28
+ }
29
+ .grist {
30
+ display: flex;
31
+ flex-direction: column;
32
+ flex: 1;
33
+ overflow-y: auto;
34
+ }
35
+ ox-grist {
36
+ overflow-y: hidden;
37
+ flex: 1;
38
+ }
39
+ .button-container {
40
+ padding: 10px 0 12px 0;
41
+ text-align: center;
42
+ }
43
+ .button-container > button {
44
+ background-color: var(--button-background-color);
45
+ border: var(--button-border);
46
+ border-radius: var(--button-border-radius);
47
+ margin: var(--button-margin);
48
+ padding: var(--button-padding);
49
+ color: var(--button-color);
50
+ font: var(--button-font);
51
+ text-transform: var(--button-text-transform);
52
+ }
53
+ .button-container > button:hover,
54
+ .button-container > button:active {
55
+ background-color: var(--button-background-focus-color);
56
+ }
57
+ `
58
+ ]
59
+ }
60
+
61
+ static get properties() {
62
+ return {
63
+ menuId: String,
64
+ searchFields: Object,
65
+ config: Object
66
+ }
67
+ }
68
+
69
+ render() {
70
+ return html`
71
+ <search-form .fields=${this.searchFields} @submit=${e => this.dataGrist.fetch()}></search-form>
72
+
73
+ <div class="grist">
74
+ <ox-grist
75
+ .mode=${isMobileDevice() ? 'LIST' : 'GRID'}
76
+ .config=${this.config}
77
+ .fetchHandler=${this.fetchHandler.bind(this)}
78
+ ></ox-grist>
79
+ </div>
80
+
81
+ <div class="button-container">
82
+ <button @click=${this.save}>${i18next.t('button.save')}</button>
83
+ <button @click=${this.delete}>${i18next.t('button.delete')}</button>
84
+ </div>
85
+ `
86
+ }
87
+
88
+ get searchForm() {
89
+ return this.shadowRoot.querySelector('search-form')
90
+ }
91
+
92
+ get dataGrist() {
93
+ return this.shadowRoot.querySelector('ox-grist')
94
+ }
95
+
96
+ firstUpdated() {
97
+ this.searchFields = [
98
+ {
99
+ name: 'name',
100
+ label: i18next.t('field.name'),
101
+ type: 'text',
102
+ props: { searchOper: 'i_like' }
103
+ },
104
+ {
105
+ name: 'description',
106
+ label: i18next.t('field.description'),
107
+ type: 'text',
108
+ props: { searchOper: 'i_like' }
109
+ },
110
+ {
111
+ name: 'template',
112
+ label: i18next.t('field.template'),
113
+ type: 'text',
114
+ props: { searchOper: 'i_like' }
115
+ },
116
+ {
117
+ name: 'category',
118
+ label: i18next.t('field.category'),
119
+ type: 'text',
120
+ props: { searchOper: 'i_like' }
121
+ },
122
+ {
123
+ name: 'resourceUrl',
124
+ label: i18next.t('field.resource_url'),
125
+ type: 'text',
126
+ props: { searchOper: 'i_like' }
127
+ },
128
+ {
129
+ name: 'hiddenFlag',
130
+ label: i18next.t('field.hidden_flag'),
131
+ type: 'checkbox',
132
+ props: { searchOper: 'eq' },
133
+ attrs: ['indeterminate']
134
+ }
135
+ ]
136
+
137
+ this.config = {
138
+ rows: { selectable: { multiple: true } },
139
+ pagination: { infinite: true },
140
+ columns: [
141
+ { type: 'gutter', gutterName: 'dirty' },
142
+ { type: 'gutter', gutterName: 'sequence' },
143
+ { type: 'gutter', gutterName: 'row-selector', multiple: true },
144
+ {
145
+ type: 'string',
146
+ name: 'name',
147
+ header: i18next.t('field.name'),
148
+ record: { editable: true, align: 'left' },
149
+ sortable: true,
150
+ width: 150
151
+ },
152
+ {
153
+ type: 'integer',
154
+ name: 'rank',
155
+ header: i18next.t('field.rank'),
156
+ record: { editable: true },
157
+ sortable: true,
158
+ width: 80
159
+ },
160
+ {
161
+ type: 'select',
162
+ name: 'resourceType',
163
+ header: i18next.t('field.type'),
164
+ record: {
165
+ editable: true,
166
+ options: ['', 'board']
167
+ },
168
+ width: 80
169
+ },
170
+ {
171
+ type: 'string',
172
+ name: 'resourceId',
173
+ header: i18next.t('field.value'),
174
+ record: {
175
+ editable: true,
176
+ editor: function (value, column, record, rowIndex, field) {
177
+ const type = record.resourceType || 'string'
178
+ if (value && type !== 'board') {
179
+ delete record.resourceId
180
+ value = ''
181
+ }
182
+ return getEditor(type)(value, column, record, rowIndex, field)
183
+ },
184
+ renderer: function (value, column, record, rowIndex, field) {
185
+ const type = record.resourceType || 'string'
186
+ if (value && type !== 'board') {
187
+ delete record.resourceId
188
+ value = ''
189
+ }
190
+ return getRenderer(type)(value, column, record, rowIndex, field)
191
+ }
192
+ },
193
+ width: 140
194
+ },
195
+ {
196
+ type: 'string',
197
+ name: 'description',
198
+ header: i18next.t('field.description'),
199
+ record: { editable: true, align: 'left' },
200
+ sortable: true,
201
+ width: 200
202
+ },
203
+ {
204
+ type: 'string',
205
+ name: 'template',
206
+ header: i18next.t('field.template'),
207
+ record: { editable: true, align: 'left' },
208
+ sortable: true,
209
+ width: 160
210
+ },
211
+ {
212
+ type: 'string',
213
+ name: 'resourceUrl',
214
+ header: i18next.t('field.resource_url'),
215
+ record: { editable: true, align: 'left' },
216
+ sortable: true,
217
+ width: 160
218
+ },
219
+ {
220
+ type: 'boolean',
221
+ name: 'hiddenFlag',
222
+ header: i18next.t('field.hidden_flag'),
223
+ record: { editable: true },
224
+ sortable: true,
225
+ width: 80
226
+ },
227
+ {
228
+ type: 'resource-object',
229
+ name: 'role',
230
+ label: true,
231
+ header: i18next.t('field.required role'),
232
+ record: {
233
+ editable: true,
234
+ options: {
235
+ title: i18next.t('title.lookup role'),
236
+ queryName: 'roles'
237
+ }
238
+ },
239
+ width: 200
240
+ },
241
+ {
242
+ type: 'datetime',
243
+ name: 'updatedAt',
244
+ header: i18next.t('field.updated_at'),
245
+ record: { editable: false },
246
+ sortable: true,
247
+ width: 150
248
+ },
249
+ {
250
+ type: 'object',
251
+ name: 'updater',
252
+ header: i18next.t('field.updater'),
253
+ record: { editable: false },
254
+ sortable: true,
255
+ width: 150
256
+ }
257
+ ]
258
+ }
259
+ }
260
+
261
+ async fetchHandler({ page, limit, sorters = [{ name: 'rank' }, { name: 'name' }] }) {
262
+ const response = await client.query({
263
+ query: gql`
264
+ query {
265
+ menus(${gqlBuilder.buildArgs({
266
+ filters: [...this.searchForm.queryFilters, { name: 'parentId', operator: 'eq', value: this.menuId }],
267
+ pagination: { page, limit },
268
+ sortings: sorters
269
+ })}) {
270
+ items {
271
+ id
272
+ name
273
+ rank
274
+ description
275
+ category
276
+ template
277
+ resourceUrl
278
+ resourceType
279
+ resourceId
280
+ hiddenFlag
281
+ role {
282
+ id
283
+ name
284
+ description
285
+ }
286
+ updatedAt
287
+ updater{
288
+ id
289
+ name
290
+ description
291
+ }
292
+ }
293
+ total
294
+ }
295
+ }
296
+ `
297
+ })
298
+
299
+ return {
300
+ total: response.data.menus.total || 0,
301
+ records: response.data.menus.items || []
302
+ }
303
+ }
304
+
305
+ async save() {
306
+ const patches = this.getPatches()
307
+ if (patches && patches.length) {
308
+ const response = await client.query({
309
+ query: gql`
310
+ mutation {
311
+ updateMultipleMenu(${gqlBuilder.buildArgs({
312
+ patches
313
+ })}) {
314
+ name
315
+ }
316
+ }
317
+ `
318
+ })
319
+
320
+ if (!response.errors) this.dataGrist.fetch()
321
+ } else {
322
+ OxPrompt.open({
323
+ title: i18next.t('text.nothing_changed'),
324
+ text: i18next.t('text.there_is_nothing_to_save')
325
+ })
326
+ }
327
+ }
328
+
329
+ async delete() {
330
+ const ids = this.dataGrist.selected.map(record => record.id)
331
+ if (ids && ids.length > 0) {
332
+ if (
333
+ await OxPrompt.open({
334
+ type: 'warning',
335
+ title: i18next.t('button.delete'),
336
+ text: i18next.t('text.are_you_sure'),
337
+ confirmButton: { text: i18next.t('button.delete') },
338
+ cancelButton: { text: i18next.t('button.cancel') }
339
+ })
340
+ ) {
341
+ const response = await client.query({
342
+ query: gql`
343
+ mutation {
344
+ deleteMenus(${gqlBuilder.buildArgs({ ids })})
345
+ }
346
+ `
347
+ })
348
+
349
+ if (!response.errors) {
350
+ this.dataGrist.fetch()
351
+ }
352
+ }
353
+ } else {
354
+ OxPrompt.open({
355
+ title: i18next.t('text.nothing_selected'),
356
+ text: i18next.t('text.there_is_nothing_to_delete')
357
+ })
358
+ }
359
+ }
360
+
361
+ getPatches() {
362
+ let patches = this.dataGrist.dirtyRecords
363
+ if (patches && patches.length) {
364
+ patches = patches.map(menu => {
365
+ let patchField = menu.id ? { id: menu.id } : {}
366
+ patchField = { ...patchField, routingType: menu.routingType, menuType: menu.menuType }
367
+ const dirtyFields = menu.__dirtyfields__
368
+ for (let key in dirtyFields) {
369
+ patchField[key] = dirtyFields[key].after
370
+ }
371
+ patchField.parent = { id: this.menuId }
372
+ patchField.routingType = patchField.routingType || 'STATIC'
373
+ patchField.menuType = patchField.menuType || 'SCREEN'
374
+ patchField.cuFlag = menu.__dirty__
375
+
376
+ return patchField
377
+ })
378
+ }
379
+
380
+ return patches
381
+ }
382
+ }
383
+
384
+ customElements.define('menu-management-detail', MenuManagementDetail)
@@ -0,0 +1,294 @@
1
+ import '@things-factory/form-ui'
2
+ import '@operato/data-grist'
3
+ import './menu-management-detail'
4
+
5
+ import gql from 'graphql-tag'
6
+ import { css, html } from 'lit'
7
+
8
+ import { openPopup } from '@operato/layout'
9
+ import { i18next, localize } from '@operato/i18n'
10
+ import { client, PageView } from '@things-factory/shell'
11
+ import { CommonButtonStyles, ScrollbarStyles } from '@things-factory/styles'
12
+ import { gqlBuilder, isMobileDevice } from '@things-factory/utils'
13
+
14
+ import { OxPrompt } from '@operato/popup/ox-prompt.js'
15
+
16
+ class MenuManagement extends localize(i18next)(PageView) {
17
+ static get properties() {
18
+ return {
19
+ searchFields: Array,
20
+ config: Object,
21
+ data: Object
22
+ }
23
+ }
24
+
25
+ static get styles() {
26
+ return [
27
+ ScrollbarStyles,
28
+ css`
29
+ :host {
30
+ display: flex;
31
+ flex-direction: column;
32
+ overflow: hidden;
33
+ }
34
+
35
+ search-form {
36
+ overflow: visible;
37
+ }
38
+
39
+ ox-grist {
40
+ overflow-y: auto;
41
+ flex: 1;
42
+ }
43
+ `
44
+ ]
45
+ }
46
+
47
+ render() {
48
+ return html`
49
+ <search-form .fields=${this.searchFields} @submit=${e => this.dataGrist.fetch()}></search-form>
50
+
51
+ <ox-grist
52
+ .mode=${isMobileDevice() ? 'LIST' : 'GRID'}
53
+ .config=${this.config}
54
+ .fetchHandler="${this.fetchHandler.bind(this)}"
55
+ ></ox-grist>
56
+ `
57
+ }
58
+
59
+ get context() {
60
+ return {
61
+ title: i18next.t('title.menu_management'),
62
+ actions: [
63
+ {
64
+ title: i18next.t('button.save'),
65
+ action: this.save.bind(this),
66
+ ...CommonButtonStyles.save
67
+ },
68
+ {
69
+ title: i18next.t('button.delete'),
70
+ action: this.delete.bind(this),
71
+ ...CommonButtonStyles.delete
72
+ }
73
+ ]
74
+ }
75
+ }
76
+
77
+ get searchForm() {
78
+ return this.shadowRoot.querySelector('search-form')
79
+ }
80
+
81
+ get dataGrist() {
82
+ return this.shadowRoot.querySelector('ox-grist')
83
+ }
84
+
85
+ pageUpdated(_changes, _lifecycle) {
86
+ if (this.active) {
87
+ this.dataGrist.fetch()
88
+ }
89
+ }
90
+
91
+ pageInitialized() {
92
+ this.searchFields = [
93
+ {
94
+ name: 'name',
95
+ label: i18next.t('label.name'),
96
+ type: 'text',
97
+ props: { searchOper: 'i_like' }
98
+ },
99
+ {
100
+ name: 'description',
101
+ label: i18next.t('label.description'),
102
+ type: 'text',
103
+ props: { searchOper: 'i_like' }
104
+ }
105
+ ]
106
+
107
+ this.config = {
108
+ rows: { selectable: { multiple: true } },
109
+ columns: [
110
+ { type: 'gutter', gutterName: 'dirty' },
111
+ { type: 'gutter', gutterName: 'sequence' },
112
+ { type: 'gutter', gutterName: 'row-selector', multiple: true },
113
+ {
114
+ type: 'gutter',
115
+ gutterName: 'button',
116
+ icon: 'reorder',
117
+ handlers: {
118
+ click: (_columns, _data, _column, record, _rowIndex) => {
119
+ if (record.id && record.name) this.openMenuDetail(record.id, record.name)
120
+ }
121
+ }
122
+ },
123
+ {
124
+ type: 'string',
125
+ name: 'name',
126
+ header: i18next.t('field.name'),
127
+ record: { editable: true, align: 'left' },
128
+ sortable: true,
129
+ width: 150
130
+ },
131
+ {
132
+ type: 'integer',
133
+ name: 'rank',
134
+ header: i18next.t('field.rank'),
135
+ record: { editable: true },
136
+ sortable: true,
137
+ width: 80
138
+ },
139
+ {
140
+ type: 'string',
141
+ name: 'description',
142
+ header: i18next.t('field.description'),
143
+ record: { editable: true, align: 'left' },
144
+ sortable: true,
145
+ width: 200
146
+ },
147
+ {
148
+ type: 'boolean',
149
+ name: 'hiddenFlag',
150
+ header: i18next.t('field.hidden_flag'),
151
+ record: { editable: false },
152
+ sortable: true,
153
+ width: 80
154
+ },
155
+ {
156
+ type: 'datetime',
157
+ name: 'updatedAt',
158
+ header: i18next.t('field.updated_at'),
159
+ record: { editable: false },
160
+ sortable: true,
161
+ width: 150
162
+ },
163
+ {
164
+ type: 'object',
165
+ name: 'updater',
166
+ header: i18next.t('field.updater'),
167
+ record: { editable: false },
168
+ sortable: true,
169
+ width: 150
170
+ }
171
+ ]
172
+ }
173
+ }
174
+
175
+ async fetchHandler({ page, limit, sorters = [{ name: 'rank' }, { name: 'name' }] }) {
176
+ const response = await client.query({
177
+ query: gql`
178
+ query {
179
+ menus(${gqlBuilder.buildArgs({
180
+ filters: [...this.searchForm.queryFilters, { name: 'menuType', operator: 'eq', value: 'MENU' }],
181
+ pagination: { page, limit },
182
+ sortings: sorters
183
+ })}) {
184
+ items {
185
+ id
186
+ name
187
+ rank
188
+ description
189
+ hiddenFlag
190
+ updatedAt
191
+ updater{
192
+ id
193
+ name
194
+ description
195
+ }
196
+ }
197
+ total
198
+ }
199
+ }
200
+ `
201
+ })
202
+
203
+ return {
204
+ total: response.data.menus.total || 0,
205
+ records: response.data.menus.items || []
206
+ }
207
+ }
208
+
209
+ async save() {
210
+ const patches = this.getPatches()
211
+ if (patches && patches.length) {
212
+ const response = await client.query({
213
+ query: gql`
214
+ mutation {
215
+ updateMultipleMenu(${gqlBuilder.buildArgs({
216
+ patches
217
+ })}) {
218
+ name
219
+ }
220
+ }
221
+ `
222
+ })
223
+
224
+ if (!response.errors) this.dataGrist.fetch()
225
+ } else {
226
+ OxPrompt.open({
227
+ title: i18next.t('text.nothing_changed'),
228
+ text: i18next.t('text.there_is_nothing_to_save')
229
+ })
230
+ }
231
+ }
232
+
233
+ async delete() {
234
+ const ids = this.dataGrist.selected.map(record => record.id)
235
+ if (ids && ids.length > 0) {
236
+ if (
237
+ await OxPrompt.open({
238
+ type: 'warning',
239
+ title: i18next.t('button.delete'),
240
+ text: i18next.t('text.are_you_sure'),
241
+ confirmButton: { text: i18next.t('button.delete') },
242
+ cancelButton: { text: i18next.t('button.cancel') }
243
+ })
244
+ ) {
245
+ const response = await client.query({
246
+ query: gql`
247
+ mutation {
248
+ deleteMenus(${gqlBuilder.buildArgs({ ids })})
249
+ }
250
+ `
251
+ })
252
+
253
+ if (!response.errors) {
254
+ this.dataGrist.fetch()
255
+ }
256
+ }
257
+ } else {
258
+ OxPrompt.open({
259
+ title: i18next.t('text.nothing_selected'),
260
+ text: i18next.t('text.there_is_nothing_to_delete')
261
+ })
262
+ }
263
+ }
264
+
265
+ getPatches() {
266
+ let patches = this.dataGrist.dirtyRecords
267
+
268
+ if (patches && patches.length) {
269
+ patches = patches.map(menu => {
270
+ let patchField = menu.id ? { id: menu.id } : {}
271
+ const dirtyFields = menu.__dirtyfields__
272
+ for (let key in dirtyFields) {
273
+ patchField[key] = dirtyFields[key].after
274
+ }
275
+ patchField.cuFlag = menu.__dirty__
276
+ patchField.menuType = 'MENU'
277
+
278
+ return patchField
279
+ })
280
+ }
281
+
282
+ return patches
283
+ }
284
+
285
+ openMenuDetail(menuId, menuName) {
286
+ openPopup(html` <menu-management-detail .menuId=${menuId}></menu-management-detail> `, {
287
+ backdrop: true,
288
+ size: 'large',
289
+ title: `${i18next.t('title.menu_management_detail')} - ${menuName}`
290
+ })
291
+ }
292
+ }
293
+
294
+ customElements.define('menu-management', MenuManagement)