@things-factory/menu-ui 8.0.0 → 9.0.0-beta.3

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 (56) hide show
  1. package/dist-client/apptools/favorite-tool.d.ts +28 -0
  2. package/dist-client/apptools/favorite-tool.js +139 -0
  3. package/dist-client/apptools/favorite-tool.js.map +1 -0
  4. package/dist-client/bootstrap.d.ts +4 -0
  5. package/dist-client/bootstrap.js +52 -0
  6. package/dist-client/bootstrap.js.map +1 -0
  7. package/dist-client/components/child-menus-selector.d.ts +1 -0
  8. package/dist-client/components/child-menus-selector.js +146 -0
  9. package/dist-client/components/child-menus-selector.js.map +1 -0
  10. package/dist-client/components/role-select-popup.d.ts +1 -0
  11. package/dist-client/components/role-select-popup.js +180 -0
  12. package/dist-client/components/role-select-popup.js.map +1 -0
  13. package/dist-client/index.js +2 -0
  14. package/dist-client/index.js.map +1 -0
  15. package/dist-client/pages/menu-list-page.d.ts +2 -0
  16. package/dist-client/pages/menu-list-page.js +204 -0
  17. package/dist-client/pages/menu-list-page.js.map +1 -0
  18. package/dist-client/pages/menu-management-detail.d.ts +2 -0
  19. package/dist-client/pages/menu-management-detail.js +376 -0
  20. package/dist-client/pages/menu-management-detail.js.map +1 -0
  21. package/dist-client/pages/menu-management.d.ts +3 -0
  22. package/dist-client/pages/menu-management.js +280 -0
  23. package/dist-client/pages/menu-management.js.map +1 -0
  24. package/dist-client/pages/role-menus-management.d.ts +4 -0
  25. package/dist-client/pages/role-menus-management.js +215 -0
  26. package/dist-client/pages/role-menus-management.js.map +1 -0
  27. package/dist-client/route.d.ts +1 -0
  28. package/dist-client/route.js +14 -0
  29. package/dist-client/route.js.map +1 -0
  30. package/dist-client/tsconfig.tsbuildinfo +1 -0
  31. package/dist-client/viewparts/menu-bar.d.ts +12 -0
  32. package/dist-client/viewparts/menu-bar.js +108 -0
  33. package/dist-client/viewparts/menu-bar.js.map +1 -0
  34. package/dist-client/viewparts/menu-tile-list.d.ts +13 -0
  35. package/dist-client/viewparts/menu-tile-list.js +234 -0
  36. package/dist-client/viewparts/menu-tile-list.js.map +1 -0
  37. package/dist-client/viewparts/menu-tree-bar.d.ts +28 -0
  38. package/dist-client/viewparts/menu-tree-bar.js +307 -0
  39. package/dist-client/viewparts/menu-tree-bar.js.map +1 -0
  40. package/package.json +21 -18
  41. package/things-factory.config.js +5 -14
  42. package/client/apptools/favorite-tool.js +0 -130
  43. package/client/bootstrap.js +0 -57
  44. package/client/components/child-menus-selector.js +0 -150
  45. package/client/components/role-select-popup.js +0 -179
  46. package/client/pages/menu-list-page.js +0 -200
  47. package/client/pages/menu-management-detail.js +0 -384
  48. package/client/pages/menu-management.js +0 -294
  49. package/client/pages/role-menus-management.js +0 -215
  50. package/client/route.js +0 -15
  51. package/client/viewparts/menu-bar.js +0 -114
  52. package/client/viewparts/menu-tile-list.js +0 -222
  53. package/client/viewparts/menu-tree-bar.js +0 -295
  54. package/server/index.ts +0 -0
  55. /package/{client/index.js → dist-client/index.d.ts} +0 -0
  56. /package/{client → dist-client}/themes/menu-theme.css +0 -0
@@ -1,294 +0,0 @@
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)
@@ -1,215 +0,0 @@
1
- import '@material/mwc-button'
2
- import '@things-factory/component-ui'
3
- import '../components/role-select-popup'
4
- import '../components/child-menus-selector'
5
-
6
- import gql from 'graphql-tag'
7
- import { css, html } from 'lit'
8
-
9
- import { openPopup } from '@operato/layout'
10
- import { i18next, localize } from '@operato/i18n'
11
- import { client, PageView } from '@things-factory/shell'
12
-
13
- class RoleMenusManagement extends localize(i18next)(PageView) {
14
- static get styles() {
15
- return [
16
- css`
17
- :host {
18
- display: flex;
19
- flex-direction: column;
20
- background-color: var(--md-sys-color-background);
21
- padding: var(--spacing-large);
22
- overflow: auto;
23
- }
24
- [input-container] {
25
- display: flex;
26
- background-color: var(--md-sys-color-surface);
27
- margin: var(--spacing-large) 0;
28
- padding: var(--spacing-large);
29
- border-radius: var(--border-radius);
30
- box-shadow: var(--box-shadow);
31
- clear: both;
32
- gap: 10px;
33
- }
34
- mwc-buton {
35
- margin: auto;
36
- }
37
- h2 {
38
- margin: var(--title-margin);
39
- font: var(--title-font);
40
- color: var(--title-text-color);
41
- }
42
- [subtitle] {
43
- padding: var(--subtitle-padding);
44
- font: var(--subtitle-font);
45
- color: var(--subtitle-text-color);
46
- }
47
- input {
48
- border: var(--border-dim-color);
49
- border-radius: var(--border-radius);
50
- padding: var(--input-padding);
51
- min-width: 250px;
52
- font: var(--input-font);
53
- }
54
- label {
55
- display: flex;
56
- }
57
- @media screen and (max-width: 480px) {
58
- input {
59
- min-width: 0;
60
- }
61
- }
62
- `
63
- ]
64
- }
65
-
66
- static get properties() {
67
- return {
68
- selectedRole: Object,
69
- parentMenus: Array,
70
- roleMenus: Array,
71
- menuSet: Object,
72
- targetRole: Object
73
- }
74
- }
75
-
76
- get context() {
77
- return {
78
- title: i18next.t('title.role_menus_management')
79
- }
80
- }
81
-
82
- render() {
83
- const data = { menus: this.parentMenus || [] }
84
- const menuSet = this.menuSet || {}
85
-
86
- return html`
87
- <h2>${i18next.t('title.menus_by_role')}</h2>
88
- <div subtitle>${i18next.t('field.role')}</div>
89
- <div input-container>
90
- <label><input name="name" type="text" disabled /></label>
91
- <mwc-button @click=${() => this.onSelectRole()} label="${i18next.t('button.select')}" raised></mwc-button>
92
- </div>
93
-
94
- <div subtitle>${i18next.t('title.menus')}</div>
95
- <div id="children-menus">
96
- <div>
97
- <quick-find-list
98
- .data="${data}"
99
- .contentRenderer="${(item, tabKey) =>
100
- this.targetRole
101
- ? html`<child-menus-selector
102
- @updateRoleMenus="${async () => {
103
- await this.getMenus(this.targetRole)
104
- }}"
105
- .parentMenu=${item}
106
- .roleMenus=${menuSet[item.name]}
107
- .targetRole=${this.targetRole}
108
- ></child-menus-selector>`
109
- : this.showToast(i18next.t('error.value is empty', { value: i18next.t('field.role') }))}"
110
- ></quick-find-list>
111
- </div>
112
- </div>
113
- `
114
- }
115
-
116
- get roleInput() {
117
- return this.shadowRoot.querySelector('input')
118
- }
119
-
120
- async pageUpdated(changes) {
121
- if (this.active) {
122
- this.refresh()
123
- }
124
- }
125
-
126
- async refresh() {
127
- const response = await client.query({
128
- query: gql`
129
- query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
130
- menus(filters: $filters, pagination: $pagination, sortings: $sortings) {
131
- items {
132
- id
133
- name
134
- description
135
- children {
136
- id
137
- name
138
- }
139
- }
140
- total
141
- }
142
- }
143
- `,
144
- variables: {
145
- filters: [],
146
- pagination: {},
147
- sortings: [{ name: 'name' }]
148
- }
149
- })
150
-
151
- if (!response.errors?.length) {
152
- const menus = response.data.menus.items || []
153
- this.parentMenus = menus.filter(menu => menu.children?.length)
154
- }
155
- }
156
-
157
- async getMenus(targetRole) {
158
- const response = await client.query({
159
- query: gql`
160
- query ($roleId: String!) {
161
- roleMenus(roleId: $roleId) {
162
- items {
163
- id
164
- name
165
- description
166
- parent {
167
- id
168
- name
169
- }
170
- }
171
- }
172
- }
173
- `,
174
- variables: {
175
- roleId: targetRole.id
176
- }
177
- })
178
-
179
- if (!response.errors?.length) {
180
- this.roleMenus = response.data.roleMenus.items || []
181
- this.menuSet = (this.roleMenus || []).reduce((menuSet, menu) => {
182
- const parentMenu = menu.parent.name
183
- if (!menuSet[parentMenu]) menuSet[parentMenu] = []
184
- menuSet[parentMenu].push(menu)
185
-
186
- return menuSet
187
- }, {})
188
- }
189
- }
190
-
191
- onSelectRole() {
192
- openPopup(
193
- html`
194
- <role-select-popup
195
- @selected="${e => {
196
- this.selectedRole = e.detail
197
- this.roleInput.value = this.selectedRole.name
198
- this.targetRole = this.selectedRole || {}
199
- this.getMenus(this.targetRole)
200
- }}"
201
- ></role-select-popup>
202
- `,
203
- {
204
- size: 'large',
205
- title: `${i18next.t('title.role_select')}`
206
- }
207
- )
208
- }
209
-
210
- showToast(message) {
211
- document.dispatchEvent(new CustomEvent('notify', { detail: { message, option: { timer: 1000 } } }))
212
- }
213
- }
214
-
215
- window.customElements.define('role-menus-management', RoleMenusManagement)
package/client/route.js DELETED
@@ -1,15 +0,0 @@
1
- export default function route(page) {
2
- switch (page) {
3
- case 'menu-list':
4
- import('./pages/menu-list-page')
5
- return page
6
-
7
- case 'menus':
8
- import('./pages/menu-management')
9
- return page
10
-
11
- case 'role-menus':
12
- import('./pages/role-menus-management')
13
- return page
14
- }
15
- }
@@ -1,114 +0,0 @@
1
- import '@material/web/icon/icon.js'
2
-
3
- import { css, html, LitElement } from 'lit'
4
-
5
- import ScrollBooster from 'scrollbooster'
6
-
7
- export default class MenuBar extends LitElement {
8
- static get styles() {
9
- return [
10
- css`
11
- :host {
12
- background-color: var(--menu-bar-background-color, #242d30);
13
-
14
- overflow-x: hidden;
15
- }
16
-
17
- ul {
18
- display: flex;
19
- list-style: none;
20
- margin: 0;
21
- padding: 0;
22
- white-space: nowrap;
23
- }
24
-
25
- li {
26
- display: inline-block;
27
- padding: 0px 3px;
28
-
29
- border-bottom: var(--menu-bar-line);
30
- }
31
-
32
- li[active] {
33
- border-color: var(--menu-bar-active-line-color, red);
34
- }
35
-
36
- li a {
37
- display: block;
38
- padding: 5px 4px 1px 4px;
39
- text-decoration: none;
40
- font: var(--menu-bar-textbutton);
41
- color: rgba(255, 255, 255, 0.8);
42
- }
43
-
44
- li[active] a {
45
- font: var(--menu-bar-textbutton-active);
46
- color: rgba(255, 255, 255, 1);
47
- }
48
- `
49
- ]
50
- }
51
-
52
- static get properties() {
53
- return {
54
- menus: Array,
55
- menuId: String
56
- }
57
- }
58
-
59
- render() {
60
- var topmenus = this.menus || []
61
-
62
- return html`
63
- <ul>
64
- <li ?active=${this.menuId !== 0 && !this.menuId}>
65
- <a href="menu-list"><md-icon>star</md-icon></a>
66
- </li>
67
-
68
- ${topmenus.map(
69
- (menu, idx) => html`
70
- <li ?active=${this.menuId === String(idx)}>
71
- <a href=${`${menu.routing || 'menu-list'}/${idx}`}>${menu.name}</a>
72
- </li>
73
- `
74
- )}
75
- </ul>
76
- `
77
- }
78
-
79
- _onWheelEvent(e) {
80
- var delta = Math.max(-1, Math.min(1, e.wheelDelta || -e.detail))
81
- this.scrollLeft -= delta * 40
82
-
83
- e.preventDefault()
84
- }
85
-
86
- updated(change) {
87
- if (change.has('menus')) {
88
- /* menus가 바뀔 때마다, contents의 폭이 달라지므로, 다시 폭을 계산해준다. */
89
- this.__sb && this.__sb.updateMetrics()
90
- }
91
-
92
- if (change.has('menuId')) {
93
- var active = this.shadowRoot.querySelector('li[active]')
94
- active && active.scrollIntoView()
95
- }
96
- }
97
-
98
- firstUpdated() {
99
- var scrollTarget = this.shadowRoot.querySelector('ul')
100
-
101
- scrollTarget.addEventListener('mousewheel', this._onWheelEvent.bind(this), false)
102
-
103
- this.__sb = new ScrollBooster({
104
- viewport: this,
105
- content: scrollTarget,
106
- mode: 'x',
107
- onUpdate: data => {
108
- this.scrollLeft = data.position.x
109
- }
110
- })
111
- }
112
- }
113
-
114
- window.customElements.define('menu-bar', MenuBar)