@things-factory/board-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 (55) hide show
  1. package/client/apptools/favorite-tool.ts +124 -0
  2. package/client/board-list/board-tile-list.ts +272 -0
  3. package/client/board-list/group-bar-styles.ts +63 -0
  4. package/client/board-list/group-bar.ts +99 -0
  5. package/client/board-list/play-group-bar.ts +88 -0
  6. package/client/board-provider.ts +92 -0
  7. package/client/bootstrap.ts +39 -0
  8. package/client/data-grist/board-editor.ts +113 -0
  9. package/client/data-grist/board-renderer.ts +134 -0
  10. package/client/data-grist/color-map-editor.ts +17 -0
  11. package/client/data-grist/color-ranges-editor.ts +17 -0
  12. package/client/graphql/board-template.ts +141 -0
  13. package/client/graphql/board.ts +273 -0
  14. package/client/graphql/favorite-board.ts +25 -0
  15. package/client/graphql/group.ts +138 -0
  16. package/client/graphql/index.ts +6 -0
  17. package/client/graphql/my-board.ts +25 -0
  18. package/client/graphql/play-group.ts +189 -0
  19. package/client/index.ts +10 -0
  20. package/client/pages/attachment-list-page.ts +142 -0
  21. package/client/pages/board-list-page.ts +603 -0
  22. package/client/pages/board-modeller-page.ts +288 -0
  23. package/client/pages/board-player-by-name-page.ts +29 -0
  24. package/client/pages/board-player-page.ts +241 -0
  25. package/client/pages/board-template/board-template-list-page.ts +248 -0
  26. package/client/pages/board-viewer-by-name-page.ts +24 -0
  27. package/client/pages/board-viewer-page.ts +271 -0
  28. package/client/pages/font-list-page.ts +31 -0
  29. package/client/pages/play-list-page.ts +400 -0
  30. package/client/pages/printable-board-viewer-page.ts +54 -0
  31. package/client/pages/theme/theme-editors.ts +56 -0
  32. package/client/pages/theme/theme-list-page.ts +313 -0
  33. package/client/pages/things-scene-components-with-tools.import +0 -0
  34. package/client/pages/things-scene-components.import +0 -0
  35. package/client/route.ts +51 -0
  36. package/client/setting-let/board-view-setting-let.ts +68 -0
  37. package/client/themes/board-theme.css +77 -0
  38. package/client/things-scene-import.d.ts +4 -0
  39. package/client/viewparts/board-basic-info.ts +646 -0
  40. package/client/viewparts/board-info-link.ts +56 -0
  41. package/client/viewparts/board-info.ts +85 -0
  42. package/client/viewparts/board-template-builder.ts +134 -0
  43. package/client/viewparts/board-versions.ts +172 -0
  44. package/client/viewparts/group-info-basic.ts +267 -0
  45. package/client/viewparts/group-info-import.ts +132 -0
  46. package/client/viewparts/group-info.ts +87 -0
  47. package/client/viewparts/index.ts +3 -0
  48. package/client/viewparts/link-builder.ts +210 -0
  49. package/client/viewparts/play-group-info-basic.ts +268 -0
  50. package/client/viewparts/play-group-info-link.ts +46 -0
  51. package/client/viewparts/play-group-info.ts +81 -0
  52. package/dist-client/tsconfig.tsbuildinfo +1 -1
  53. package/dist-server/tsconfig.tsbuildinfo +1 -1
  54. package/package.json +19 -19
  55. package/server/index.ts +0 -0
@@ -0,0 +1,248 @@
1
+ import '@operato/data-grist'
2
+ import '@operato/board/ox-board-template-viewer.js'
3
+
4
+ import { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'
5
+ import { PageView, store } from '@operato/shell'
6
+ import { css, html } from 'lit'
7
+ import { customElement, property, query, state } from 'lit/decorators.js'
8
+ import { ScopedElementsMixin } from '@open-wc/scoped-elements'
9
+ import { DataGrist, FetchOption } from '@operato/data-grist'
10
+ import { client } from '@operato/graphql'
11
+ import { i18next, localize } from '@operato/i18n'
12
+
13
+ import { connect } from 'pwa-helpers/connect-mixin'
14
+ import gql from 'graphql-tag'
15
+ import { openPopup } from '@operato/layout'
16
+
17
+ @customElement('board-template-list-page')
18
+ export class BoardTemplateListPage extends connect(store)(localize(i18next)(ScopedElementsMixin(PageView))) {
19
+ static styles = [
20
+ ScrollbarStyles,
21
+ CommonGristStyles,
22
+ CommonHeaderStyles,
23
+ css`
24
+ :host {
25
+ display: flex;
26
+
27
+ width: 100%;
28
+
29
+ --grid-record-emphasized-background-color: #8b0000;
30
+ --grid-record-emphasized-color: #ff6b6b;
31
+ }
32
+
33
+ ox-grist {
34
+ overflow-y: auto;
35
+ flex: 1;
36
+ }
37
+
38
+ ox-filters-form {
39
+ flex: 1;
40
+ }
41
+ `
42
+ ]
43
+
44
+ @property({ type: Object }) gristConfig: any
45
+ @state() visibility: string = ''
46
+
47
+ @query('ox-grist') private grist!: DataGrist
48
+
49
+ get context() {
50
+ return {
51
+ title: i18next.t('title.board-template list'),
52
+ search: {
53
+ handler: (search: string) => {
54
+ this.grist.searchText = search
55
+ },
56
+ value: this.grist?.searchText || ''
57
+ },
58
+ filter: {
59
+ handler: () => {
60
+ this.grist.toggleHeadroom()
61
+ }
62
+ },
63
+ board_topmenu: true,
64
+ help: 'board-service/board-template'
65
+ }
66
+ }
67
+
68
+ render() {
69
+ return html`
70
+ <ox-grist mode="CARD" .config=${this.gristConfig} .fetchHandler=${this.fetchHandler.bind(this)}>
71
+ <div slot="headroom" class="header">
72
+ <div class="filters">
73
+ <ox-filters-form autofocus without-search></ox-filters-form>
74
+ </div>
75
+ </div>
76
+ </ox-grist>
77
+ `
78
+ }
79
+
80
+ async pageInitialized(lifecycle: any) {
81
+ this.gristConfig = {
82
+ list: {
83
+ thumbnail: 'thumbnail',
84
+ fields: ['name', 'description'],
85
+ details: ['visibility', 'creator', 'createdAt']
86
+ },
87
+ columns: [
88
+ { type: 'gutter', gutterName: 'sequence' },
89
+ {
90
+ type: 'string',
91
+ name: 'name',
92
+ header: i18next.t('field.name'),
93
+ record: {
94
+ editable: false
95
+ },
96
+ filter: 'search',
97
+ sortable: true,
98
+ width: 150
99
+ },
100
+ {
101
+ type: 'string',
102
+ name: 'description',
103
+ header: i18next.t('field.description'),
104
+ record: {
105
+ editable: false
106
+ },
107
+ filter: 'search',
108
+ width: 200
109
+ },
110
+ {
111
+ type: 'boolean',
112
+ name: 'mine',
113
+ record: {
114
+ editable: false
115
+ },
116
+ hidden: true,
117
+ width: 0
118
+ },
119
+ {
120
+ type: 'select-buttons',
121
+ name: 'visibility',
122
+ header: i18next.t('field.visibility'),
123
+ label: true,
124
+ record: {
125
+ editable: false
126
+ },
127
+ filter: {
128
+ operator: 'in',
129
+ options: ['private', 'public', 'domain'],
130
+ value: this.visibility,
131
+ label: '' /* empty intentionally */
132
+ },
133
+ width: 200
134
+ },
135
+ {
136
+ type: 'string',
137
+ name: 'tags',
138
+ header: i18next.t('field.tags'),
139
+ label: true,
140
+ record: {
141
+ renderer: (value, column, record, rowIndex, field) => (value || []).join(', '),
142
+ editable: false
143
+ },
144
+ width: 200
145
+ },
146
+ {
147
+ type: 'image',
148
+ name: 'thumbnail',
149
+ header: i18next.t('field.thumbnail'),
150
+ record: {
151
+ editable: false,
152
+ align: 'center',
153
+ wide: true
154
+ },
155
+ width: 200
156
+ },
157
+ {
158
+ type: 'resource-object',
159
+ name: 'creator',
160
+ header: i18next.t('field.creator'),
161
+ label: true,
162
+ record: {
163
+ editable: false
164
+ },
165
+ sortable: true,
166
+ width: 120
167
+ },
168
+ {
169
+ type: 'datetime',
170
+ name: 'createdAt',
171
+ header: i18next.t('field.created_at'),
172
+ label: true,
173
+ record: {
174
+ editable: false
175
+ },
176
+ sortable: true,
177
+ width: 180
178
+ }
179
+ ],
180
+ rows: {
181
+ selectable: false,
182
+ appendable: false,
183
+ handlers: {
184
+ click: (_columns, _data, _column, record, _rowIndex) => {
185
+ const boardTemplate = {
186
+ ...record,
187
+ model: JSON.parse(record.model)
188
+ }
189
+
190
+ openPopup(html` <ox-board-template-viewer .boardTemplate=${boardTemplate}></ox-board-template-viewer> `, {
191
+ backdrop: true,
192
+ size: 'large',
193
+ title: i18next.t('label.board-template')
194
+ })
195
+ }
196
+ }
197
+ },
198
+ sorters: [
199
+ {
200
+ name: 'name'
201
+ }
202
+ ]
203
+ }
204
+ }
205
+
206
+ async pageUpdated(changes: any, lifecycle: any) {
207
+ if (this.active) {
208
+ this.grist.fetch()
209
+ }
210
+ }
211
+
212
+ async fetchHandler({ page = 1, limit = 100, sortings = [], filters = [] }: FetchOption) {
213
+ const response = await client.query({
214
+ query: gql`
215
+ query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
216
+ responses: boardTemplates(filters: $filters, pagination: $pagination, sortings: $sortings) {
217
+ items {
218
+ id
219
+ name
220
+ description
221
+ model
222
+ thumbnail
223
+ visibility
224
+ tags
225
+ mine
226
+ creator {
227
+ id
228
+ name
229
+ }
230
+ createdAt
231
+ }
232
+ total
233
+ }
234
+ }
235
+ `,
236
+ variables: {
237
+ filters,
238
+ pagination: { page, limit },
239
+ sortings
240
+ }
241
+ })
242
+
243
+ return {
244
+ total: response.data.responses.total || 0,
245
+ records: response.data.responses.items || []
246
+ }
247
+ }
248
+ }
@@ -0,0 +1,24 @@
1
+ import gql from 'graphql-tag'
2
+ import { customElement } from 'lit/decorators.js'
3
+ import { client, gqlContext } from '@operato/graphql'
4
+
5
+ import { BoardViewerPage } from './board-viewer-page'
6
+
7
+ @customElement('board-viewer-by-name-page')
8
+ export class BoardViewerByNamePage extends BoardViewerPage {
9
+ async fetch(name: string) {
10
+ return await client.query({
11
+ query: gql`
12
+ query FetchBoardByName($name: String!) {
13
+ response: boardByName(name: $name) {
14
+ id
15
+ name
16
+ model
17
+ }
18
+ }
19
+ `,
20
+ variables: { name },
21
+ context: gqlContext()
22
+ })
23
+ }
24
+ }
@@ -0,0 +1,271 @@
1
+ import './things-scene-components.import'
2
+ import '@operato/board/ox-board-viewer.js'
3
+ import '@operato/oops'
4
+
5
+ import gql from 'graphql-tag'
6
+ import { css, html } from 'lit'
7
+ import { customElement, property, query, state } from 'lit/decorators.js'
8
+ import { connect } from 'pwa-helpers/connect-mixin.js'
9
+
10
+ import { buildLabelPrintCommand } from '@operato/barcode'
11
+ import { BoardViewer } from '@operato/board'
12
+ import { store, PageView } from '@operato/shell'
13
+ import { client, gqlContext, subscribe } from '@operato/graphql'
14
+ import { clientSettingStore } from '@operato/shell/object-store.js'
15
+
16
+ import { provider } from '../board-provider'
17
+
18
+ const NOOP = () => {}
19
+
20
+ function parseQuery(query: any) {
21
+ for (const key in query) {
22
+ if (query.hasOwnProperty(key)) {
23
+ try {
24
+ query[key] = JSON.parse(query[key])
25
+ } catch (error) {
26
+ // do nothing
27
+ }
28
+ }
29
+ }
30
+
31
+ return query
32
+ }
33
+
34
+ @customElement('board-viewer-page')
35
+ export class BoardViewerPage extends connect(store)(PageView) {
36
+ static styles = [
37
+ css`
38
+ :host {
39
+ display: flex;
40
+ flex-direction: column;
41
+
42
+ width: 100%; /* 전체화면보기를 위해서 필요함. */
43
+ height: 100%;
44
+
45
+ overflow: hidden;
46
+ position: relative;
47
+ }
48
+
49
+ ox-board-viewer {
50
+ flex: 1;
51
+ }
52
+
53
+ ox-oops-spinner {
54
+ display: none;
55
+ position: absolute;
56
+ left: 50%;
57
+ top: 50%;
58
+ transform: translate(-50%, -50%);
59
+ }
60
+
61
+ ox-oops-spinner[show] {
62
+ display: block;
63
+ }
64
+
65
+ ox-oops-note {
66
+ display: block;
67
+ position: absolute;
68
+ left: 50%;
69
+ top: 50%;
70
+ transform: translate(-50%, -50%);
71
+ }
72
+ `
73
+ ]
74
+
75
+ @property({ type: Object }) _board: any
76
+ @property({ type: String }) _boardId?: string
77
+ @property({ type: String }) _baseUrl?: string
78
+ @property({ type: String }) _help?: string
79
+ @property({ type: Boolean }) _interactive?: boolean
80
+ @property({ type: Boolean }) _showSpinner?: boolean
81
+
82
+ @state() data: any
83
+
84
+ subscriptionForAutoRefresh
85
+
86
+ @query('ox-board-viewer') boardViewer!: BoardViewer
87
+
88
+ get oopsNote() {
89
+ return {
90
+ icon: 'insert_chart_outlined',
91
+ title: 'EMPTY BOARD',
92
+ description: 'There are no board to be shown'
93
+ }
94
+ }
95
+
96
+ get context() {
97
+ return {
98
+ /* can set the page title with the 'title' parameter. */
99
+ title:
100
+ this.lifecycle.params['title'] ||
101
+ (this._board ? this._board.name : this._showSpinner ? 'Fetching board...' : 'Board Not Found'),
102
+ help: this._help
103
+ }
104
+ }
105
+
106
+ render() {
107
+ var oops = !this._showSpinner && !this._board && this.oopsNote
108
+
109
+ return oops
110
+ ? html` <ox-oops-note icon=${oops.icon} title=${oops.title} description=${oops.description}></ox-oops-note> `
111
+ : html`
112
+ <ox-board-viewer
113
+ .board=${this._board}
114
+ .provider=${provider}
115
+ ?hide-fullscreen=${this._interactive}
116
+ ?hide-navigation=${this._interactive}
117
+ .data=${this.data}
118
+ history
119
+ ></ox-board-viewer>
120
+ <ox-oops-spinner ?show=${this._showSpinner}></ox-oops-spinner>
121
+ `
122
+ }
123
+
124
+ updated(changes) {
125
+ if (changes.has('_boardId')) {
126
+ this.refresh()
127
+ }
128
+ }
129
+
130
+ pageUpdated(changes, lifecycle) {
131
+ if (this.active) {
132
+ this._boardId = lifecycle.resourceId
133
+ this._interactive = lifecycle.params['interactive'] === 'true'
134
+ this._help = lifecycle.params['help']
135
+ this.data = parseQuery({ ...lifecycle.params })
136
+ } else {
137
+ /*
138
+ * 비활성화된 페이지에서 render update가 발생하지 않으므로, 강제로 scene을 close 한다.
139
+ * 화면이 inactive 될 때, 굳이 scene을 close하는 이유는,
140
+ * 새로운 board가 선택되어 뷰어가 열릴 때, 기존 보드 잔상이 보이지 않도록 하기위해서이다.
141
+ */
142
+ this.stopSubscribing()
143
+
144
+ if (this._boardId) {
145
+ let boardViewer = this.boardViewer
146
+ boardViewer && boardViewer.closeScene()
147
+ }
148
+
149
+ this._boardId = ''
150
+ }
151
+ }
152
+
153
+ stateChanged(state) {
154
+ this._baseUrl = state.app.baseUrl
155
+ }
156
+
157
+ async fetch(id: string) {
158
+ if (!id) {
159
+ return
160
+ }
161
+
162
+ return await client.query({
163
+ query: gql`
164
+ query ($id: String!) {
165
+ response: board(id: $id) {
166
+ id
167
+ name
168
+ model
169
+ }
170
+ }
171
+ `,
172
+ variables: { id },
173
+ context: gqlContext()
174
+ })
175
+ }
176
+
177
+ async refresh() {
178
+ if (!this._boardId) {
179
+ this._board = null
180
+ return
181
+ }
182
+
183
+ try {
184
+ this._showSpinner = true
185
+ this.updateContext()
186
+
187
+ var { data, errors } = (await this.fetch(this._boardId)) || {}
188
+
189
+ var board = data?.response
190
+
191
+ if (!board) {
192
+ this._board = null
193
+ const message = errors?.map(error => error.message).join('\n')
194
+ throw message || 'board not found'
195
+ }
196
+
197
+ this._board = {
198
+ ...board,
199
+ model: JSON.parse(board.model)
200
+ }
201
+ } catch (ex) {
202
+ document.dispatchEvent(
203
+ new CustomEvent('notify', {
204
+ detail: {
205
+ level: 'error',
206
+ message: ex,
207
+ ex
208
+ }
209
+ })
210
+ )
211
+ } finally {
212
+ this._showSpinner = false
213
+ this.updateContext()
214
+
215
+ const { autoRefresh = true } = (await clientSettingStore.get('board-view'))?.value || {}
216
+ autoRefresh && this.startSubscribingForAutoRefresh()
217
+ }
218
+ }
219
+
220
+ async startSubscribingForAutoRefresh() {
221
+ if (!this._board) {
222
+ return
223
+ }
224
+
225
+ await this.stopSubscribing()
226
+
227
+ this.subscriptionForAutoRefresh = await subscribe(
228
+ {
229
+ query: gql`
230
+ subscription ($id: String!) {
231
+ board(id: $id) {
232
+ id
233
+ }
234
+ }
235
+ `,
236
+ variables: {
237
+ id: this._board.id
238
+ }
239
+ },
240
+ {
241
+ next: async ({ data }) => {
242
+ await this.stopSubscribing()
243
+
244
+ if (data) {
245
+ this.refresh()
246
+ }
247
+ }
248
+ }
249
+ )
250
+ }
251
+
252
+ async stopSubscribing() {
253
+ await this.subscriptionForAutoRefresh?.unsubscribe()
254
+ delete this.subscriptionForAutoRefresh
255
+ }
256
+
257
+ async getGrf() {
258
+ var { labelRotation } = this._board.model
259
+
260
+ var { width, height, data } = (await this.boardViewer.getSceneImageData()) || {}
261
+ if (!width || !data) {
262
+ throw 'Cannot get SceneImageData...'
263
+ }
264
+
265
+ return buildLabelPrintCommand(data as Uint8ClampedArray, width, height, labelRotation, false, false)
266
+ }
267
+
268
+ async printTrick(image) {
269
+ await this.boardViewer.printTrick(image)
270
+ }
271
+ }
@@ -0,0 +1,31 @@
1
+ import '@operato/font'
2
+
3
+ import { css, html } from 'lit'
4
+ import { customElement } from 'lit/decorators.js'
5
+
6
+ import { PageView } from '@operato/shell'
7
+
8
+ @customElement('font-list-page')
9
+ export class FontListPage extends PageView {
10
+ static styles = [
11
+ css`
12
+ :host {
13
+ display: flex;
14
+ }
15
+
16
+ font-selector {
17
+ flex: 1;
18
+ }
19
+ `
20
+ ]
21
+
22
+ get context() {
23
+ return {
24
+ title: 'font list'
25
+ }
26
+ }
27
+
28
+ render() {
29
+ return html` <font-selector .creatable=${true}></font-selector> `
30
+ }
31
+ }