@things-factory/board-ui 8.0.0-beta.8 → 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,646 @@
1
+ import '@material/web/icon/icon.js'
2
+ import '@material/web/textfield/filled-text-field.js'
3
+ import '@material/web/select/outlined-select'
4
+ import '@material/web/button/elevated-button.js'
5
+ import '@material/web/chips/chip-set.js'
6
+ import '@material/web/chips/assist-chip.js'
7
+ import '@material/web/chips/filter-chip.js'
8
+
9
+ import gql from 'graphql-tag'
10
+ import { css, html, LitElement, nothing } from 'lit'
11
+ import { customElement, property, state } from 'lit/decorators.js'
12
+ import { connect } from 'pwa-helpers/connect-mixin'
13
+
14
+ import { i18next } from '@operato/i18n'
15
+ import { client } from '@operato/graphql'
16
+ import { navigate, store } from '@operato/shell'
17
+ import { OxPrompt } from '@operato/popup/ox-prompt.js'
18
+ import { hasPrivilege } from '@things-factory/auth-base/dist-client'
19
+
20
+ import { styles as MDTypeScaleStyles } from '@material/web/typography/md-typescale-styles'
21
+
22
+ @customElement('board-basic-info')
23
+ export class BoardInfo extends connect(store)(LitElement) {
24
+ static styles = [
25
+ MDTypeScaleStyles,
26
+ css`
27
+ :host {
28
+ position: relative;
29
+ display: block;
30
+ background-color: var(--md-sys-color-surface);
31
+ color: var(--md-sys-color-on-surface);
32
+
33
+ --md-filled-field-top-space: 12px;
34
+ --md-filled-field-bottom-space: 12px;
35
+ --md-filled-field-leading-space: 12px;
36
+ --md-filled-field-trailing-space: 12px;
37
+
38
+ --md-outlined-field-top-space: 12px;
39
+ --md-outlined-field-bottom-space: 12px;
40
+ --md-outlined-field-leading-space: 12px;
41
+ --md-outlined-field-trailing-space: 12px;
42
+ }
43
+
44
+ img {
45
+ object-fit: contain;
46
+ max-width: 100%;
47
+ max-height: 100%;
48
+ border-bottom: 2px solid rgba(0, 0, 0, 0.1);
49
+ }
50
+
51
+ form {
52
+ display: grid;
53
+ grid-template-columns: repeat(12, 1fr);
54
+ grid-gap: 10px 0;
55
+ grid-auto-rows: minmax(24px, auto);
56
+ padding: var(--spacing-large);
57
+ align-items: center;
58
+ }
59
+
60
+ md-outlined-select,
61
+ md-filled-text-field {
62
+ flex: 1;
63
+ padding: 4px 0;
64
+ min-width: unset;
65
+ }
66
+
67
+ md-chip-set {
68
+ flex: 1;
69
+ display: flex;
70
+ gap: 4px;
71
+ }
72
+
73
+ fieldset {
74
+ display: contents;
75
+ }
76
+
77
+ legend {
78
+ grid-column: span 12;
79
+ padding: var(--legend-padding);
80
+ color: var(--md-sys-color-primary);
81
+ text-transform: capitalize;
82
+ }
83
+
84
+ label {
85
+ text-transform: capitalize;
86
+ }
87
+
88
+ md-icon[dim] {
89
+ font-variation-settings: 'FILL' 1;
90
+ --md-icon-size: 14px;
91
+ color: var(--md-sys-color-outline-variant);
92
+ }
93
+
94
+ [name] {
95
+ max-width: 70px;
96
+ white-space: nowrap;
97
+ overflow: hidden;
98
+ text-overflow: ellipsis;
99
+ }
100
+
101
+ [danger] {
102
+ --md-assist-chip-elevated-container-color: var(--md-sys-color-error);
103
+ --md-assist-chip-label-text-color: var(--md-sys-color-on-error);
104
+ --md-assist-chip-leading-icon-color: var(--md-sys-color-on-error);
105
+ }
106
+
107
+ [fullwidth] {
108
+ grid-column: span 12 / auto;
109
+ display: flex;
110
+ flex-direction: column;
111
+ gap: 4px;
112
+ }
113
+
114
+ [oneline] {
115
+ grid-column: span 12 / auto;
116
+ display: flex;
117
+ flex-direction: row;
118
+ gap: 4px;
119
+ align-items: center;
120
+ }
121
+
122
+ [oneline] md-filled-text-field,
123
+ [oneline] select {
124
+ flex: 1;
125
+ }
126
+
127
+ [right] {
128
+ margin-left: auto;
129
+ }
130
+
131
+ @media screen and (max-width: 460px) {
132
+ :host {
133
+ width: 100vw;
134
+ }
135
+ }
136
+ `
137
+ ]
138
+
139
+ @property({ type: String }) boardId?: string
140
+ @property({ type: String }) groupId?: string
141
+
142
+ @state() board: any
143
+ @state() boardGroupList: any[] = []
144
+ @state() playGroupList: any[] = []
145
+ @state() showTarget: boolean = false
146
+ @state() subdomain?: string
147
+ @state() enableModeller?: boolean = false
148
+
149
+ @state() targetDomains: { id: string; name: string; subdomain: string }[] = []
150
+ @state() targetGroups: { id: string; name: string }[] = []
151
+ @state() targetSubdomain?: string
152
+ @state() targetGroupId?: string
153
+
154
+ private original: any
155
+
156
+ render() {
157
+ var board = this.board || { name: '', description: '', playGroups: [] }
158
+ var boardGroupList = this.boardGroupList || []
159
+ var playGroupList = (this.playGroupList || []).map(group => {
160
+ return {
161
+ ...group,
162
+ checked: false
163
+ }
164
+ })
165
+ ;(board.playGroups || []).map(group => {
166
+ var playGroup = playGroupList.find(g => g.id == group.id)
167
+ if (playGroup) {
168
+ playGroup.checked = true
169
+ }
170
+ })
171
+
172
+ return html`
173
+ ${board.thumbnail ? html` <img src=${board.thumbnail} /> ` : html``}
174
+
175
+ <form class="md-typescale-body-medium">
176
+ <fieldset>
177
+ <legend class="md-typescale-label-large-prominent">${i18next.t('label.information')}</legend>
178
+ <div oneline>
179
+ <md-filled-text-field
180
+ label=${String(i18next.t('label.name'))}
181
+ type="text"
182
+ .value=${board.name}
183
+ @change=${e => (this.board = { ...this.board, name: e.target.value })}
184
+ ?disabled=${!this.enableModeller}
185
+ ></md-filled-text-field>
186
+
187
+ ${this.enableModeller
188
+ ? html`
189
+ <md-icon
190
+ @click=${() => {
191
+ this.showTarget = !this.showTarget
192
+ }}
193
+ tail
194
+ >${this.showTarget ? 'arrow_drop_up' : 'arrow_drop_down'}</md-icon
195
+ >
196
+ `
197
+ : nothing}
198
+ </div>
199
+ ${this.showTarget
200
+ ? html`
201
+ <div fullwidth target>
202
+ <label class="md-typescale-label-meium-prominent">${i18next.t('label.board-copy')}</label>
203
+ <div oneline>
204
+ <md-outlined-select
205
+ label=${String(i18next.t('label.domain'))}
206
+ @change=${e => {
207
+ this.targetSubdomain = e.target.value
208
+ this.getGroupsByDomain(this.targetSubdomain)
209
+ }}
210
+ .value=${this.targetSubdomain || ''}
211
+ ?disabled=${!this.enableModeller}
212
+ >
213
+ <md-select-option aria-label="blank"></md-select-option>
214
+ ${this.targetDomains.map(
215
+ domain => html`
216
+ <md-select-option
217
+ value=${domain.subdomain}
218
+ ?selected=${domain.subdomain == this.targetSubdomain}
219
+ >
220
+ <div slot="headline">${domain.name}</div>
221
+ </md-select-option>
222
+ `
223
+ )}
224
+ </md-outlined-select>
225
+
226
+ <md-outlined-select
227
+ label=${String(i18next.t('label.group'))}
228
+ @change=${e => (this.targetGroupId = e.target.value)}
229
+ .value=${this.targetGroupId || ''}
230
+ ?disabled=${!this.enableModeller}
231
+ >
232
+ <md-select-option aria-label="blank"></md-select-option>
233
+ ${this.targetGroups.map(
234
+ group => html`
235
+ <md-select-option value=${group.id} ?selected=${group.id == this.targetGroupId}>
236
+ <div slot="headline">${group.name}</div>
237
+ </md-select-option>
238
+ `
239
+ )}
240
+ </md-outlined-select>
241
+ </div>
242
+
243
+ <md-chip-set oneline>
244
+ <md-assist-chip
245
+ elevated
246
+ label=${String(i18next.t('button.copy'))}
247
+ ?disabled=${!this.enableModeller ||
248
+ !this.original?.name ||
249
+ !this.board.name ||
250
+ !this.targetSubdomain ||
251
+ (this.original.name == this.board.name && this.subdomain == this.targetSubdomain)}
252
+ @click=${this.cloneBoard.bind(this)}
253
+ right
254
+ >
255
+ <md-icon slot="icon">content_copy</md-icon>
256
+ </md-assist-chip>
257
+
258
+ <md-assist-chip
259
+ elevated
260
+ label=${String(i18next.t('button.export'))}
261
+ @click=${this.exportBoard.bind(this)}
262
+ >
263
+ <md-icon slot="icon">download</md-icon>
264
+ </md-assist-chip>
265
+ </md-chip-set>
266
+ </div>
267
+ `
268
+ : nothing}
269
+
270
+ <md-filled-text-field
271
+ oneline
272
+ label=${String(i18next.t('label.description'))}
273
+ .value=${board.description}
274
+ @change=${e => (this.board = { ...this.board, description: e.target.value })}
275
+ ?disabled=${!this.enableModeller}
276
+ ></md-filled-text-field>
277
+
278
+ <md-outlined-select
279
+ oneline
280
+ label=${String(i18next.t('label.group'))}
281
+ @change=${e => (this.board = { ...this.board, groupId: e.target.value })}
282
+ .value=${this.groupId || ''}
283
+ .displayText=${boardGroupList?.find(item => item.id == this.groupId)?.name}
284
+ ?disabled=${!this.enableModeller}
285
+ >
286
+ <md-select-option aria-label="blank"></md-select-option>
287
+ ${boardGroupList.map(
288
+ item => html`
289
+ <md-select-option value=${item.id} ?selected=${item.id == this.groupId}>
290
+ <div slot="headline">${item.name}</div>
291
+ </md-select-option>
292
+ `
293
+ )}
294
+ </md-outlined-select>
295
+
296
+ <md-chip-set oneline>
297
+ <md-assist-chip
298
+ danger
299
+ elevated
300
+ ?disabled=${!this.enableModeller}
301
+ label=${String(i18next.t('button.delete'))}
302
+ @click=${this.deleteBoard.bind(this)}
303
+ >
304
+ <md-icon slot="icon">delete_outline</md-icon>
305
+ </md-assist-chip>
306
+
307
+ <md-assist-chip
308
+ elevated
309
+ ?disabled=${!this.enableModeller}
310
+ label=${String(i18next.t('button.save'))}
311
+ @click=${this.updateBoard.bind(this)}
312
+ right
313
+ >
314
+ <md-icon slot="icon">save</md-icon>
315
+ </md-assist-chip>
316
+ <md-assist-chip
317
+ elevated
318
+ ?disabled=${!this.enableModeller}
319
+ label=${String(i18next.t('button.edit'))}
320
+ @click=${() => navigate(`${'board-modeller/' + this.boardId}`)}
321
+ >
322
+ <md-icon slot="icon">drive_file_rename_outline</md-icon>
323
+ </md-assist-chip>
324
+ </md-chip-set>
325
+ </fieldset>
326
+
327
+ <fieldset>
328
+ <legend class="md-typescale-label-large-prominent">${i18next.t('label.status')}</legend>
329
+ <div oneline>
330
+ <label class="md-typescale-label-meium">${i18next.t('label.creator')}</label>
331
+ <md-icon dim>person</md-icon>
332
+ <div name title=${(board.creator && board.creator.name) || 'anonymous'}>
333
+ ${(board.creator && board.creator.name) || 'anonymous'}
334
+ </div>
335
+ <md-icon dim>schedule</md-icon> ${new Date(board.createdAt).toLocaleString()}
336
+ </div>
337
+
338
+ <div oneline>
339
+ <label class="md-typescale-label-meium">${i18next.t('label.updater')}</label>
340
+ <md-icon dim>person</md-icon>
341
+ <div name title=${(board.updater && board.updater.name) || 'anonymouse'}>
342
+ ${(board.updater && board.updater.name) || 'anonymouse'}
343
+ </div>
344
+ <md-icon dim>schedule</md-icon> ${new Date(board.updatedAt).toLocaleString()}
345
+ </div>
346
+
347
+ <div oneline>
348
+ <label class="md-typescale-label-meium">${i18next.t('label.state')}</label>
349
+ <md-icon dim>signpost</md-icon> ${(board.state || '').toUpperCase()}
350
+ <md-icon dim>pin</md-icon> ${board.version}
351
+ <md-chip-set>
352
+ <md-assist-chip
353
+ elevated
354
+ label=${String(i18next.t('button.release'))}
355
+ ?disabled=${!this.enableModeller || !this.isReleasable}
356
+ @click=${this.releaseBoard.bind(this)}
357
+ right
358
+ >
359
+ <md-icon slot="icon">new_releases</md-icon>
360
+ </md-assist-chip>
361
+ </md-chip-set>
362
+ </div>
363
+ </fieldset>
364
+
365
+ <fieldset>
366
+ <legend class="md-typescale-label-large-prominent">${i18next.t('label.play-group')}</legend>
367
+ <md-chip-set oneline>
368
+ ${playGroupList.map(
369
+ item => html`
370
+ <md-filter-chip
371
+ label=${item.name}
372
+ ?selected=${item.checked}
373
+ @click=${e => {
374
+ e.target.selected ? this.joinPlayGroup(item) : this.leavePlayGroup(item)
375
+ }}
376
+ ></md-filter-chip>
377
+ `
378
+ )}
379
+ </md-chip-set>
380
+ </fieldset>
381
+ </form>
382
+ `
383
+ }
384
+
385
+ stateChanged(state) {
386
+ this.subdomain = state.app?.domain?.subdomain
387
+ this.targetSubdomain = this.targetSubdomain ?? this.subdomain
388
+ }
389
+
390
+ async firstUpdated() {
391
+ this.enableModeller = await hasPrivilege({ privilege: 'mutation', category: 'board' })
392
+
393
+ this.refresh()
394
+ }
395
+
396
+ async refresh() {
397
+ var response = (
398
+ await client.query({
399
+ query: gql`
400
+ query FetchBoardById($id: String!) {
401
+ board(id: $id) {
402
+ id
403
+ name
404
+ description
405
+ model
406
+ group {
407
+ id
408
+ name
409
+ }
410
+ playGroups {
411
+ id
412
+ name
413
+ }
414
+ thumbnail
415
+ createdAt
416
+ creator {
417
+ id
418
+ name
419
+ }
420
+ state
421
+ version
422
+ updatedAt
423
+ updater {
424
+ id
425
+ name
426
+ }
427
+ }
428
+
429
+ groups {
430
+ items {
431
+ id
432
+ name
433
+ description
434
+ }
435
+ }
436
+
437
+ playGroups {
438
+ items {
439
+ id
440
+ name
441
+ description
442
+ }
443
+ total
444
+ }
445
+
446
+ domainsWithPrivilege(privilege: "mutation", category: "board") {
447
+ name
448
+ subdomain
449
+ }
450
+ }
451
+ `,
452
+ variables: { id: this.boardId }
453
+ })
454
+ ).data
455
+
456
+ this.board = response.board
457
+ this.boardGroupList = response.groups.items
458
+ this.playGroupList = response.playGroups.items
459
+
460
+ this.original = { ...this.board }
461
+
462
+ this.groupId = this.board.group?.id
463
+
464
+ this.targetDomains = response.domainsWithPrivilege
465
+ this.targetGroups = response.groups.items
466
+ this.targetGroupId = this.groupId
467
+ }
468
+
469
+ async getGroupsByDomain(subdomain?: string) {
470
+ this.targetGroups = []
471
+ this.targetGroupId = ''
472
+
473
+ if (!subdomain) {
474
+ return
475
+ }
476
+
477
+ var response = (
478
+ await client.query({
479
+ query: gql`
480
+ query {
481
+ groups {
482
+ items {
483
+ id
484
+ name
485
+ description
486
+ }
487
+ }
488
+ }
489
+ `,
490
+ variables: { id: this.boardId },
491
+ context: {
492
+ headers: {
493
+ 'x-things-factory-domain': subdomain
494
+ }
495
+ }
496
+ })
497
+ ).data
498
+
499
+ this.targetGroups = response.groups.items
500
+ }
501
+
502
+ get isReleasable() {
503
+ if (!this.board || this.board.state == 'released') {
504
+ return false
505
+ }
506
+
507
+ const { name, description, groupId } = this.board
508
+
509
+ return (
510
+ this.original &&
511
+ this.original.name == name &&
512
+ this.original.description == description &&
513
+ this.original.groupId == groupId
514
+ )
515
+ }
516
+
517
+ async cloneBoard() {
518
+ if (!this.original.name || (this.board.name == this.original.name && this.subdomain == this.targetSubdomain)) {
519
+ document.dispatchEvent(
520
+ new CustomEvent('notify', {
521
+ detail: {
522
+ level: 'error',
523
+ message: i18next.t('error.name must be unique from the original board', { name: this.original.name })
524
+ }
525
+ })
526
+ )
527
+
528
+ return
529
+ }
530
+
531
+ const { id, name, description } = this.board
532
+
533
+ this.dispatchEvent(
534
+ new CustomEvent('clone-board', {
535
+ detail: {
536
+ id,
537
+ name,
538
+ description,
539
+ targetSubdomain: this.targetSubdomain,
540
+ targetGroupId: this.targetGroupId
541
+ },
542
+ bubbles: true,
543
+ composed: true
544
+ })
545
+ )
546
+
547
+ this.close()
548
+ }
549
+
550
+ async exportBoard() {
551
+ const board = { ...this.board, model: JSON.parse(this.board.model) }
552
+
553
+ const json = JSON.stringify(board, null, 2)
554
+ const blob = new Blob([json], { type: 'application/json;charset=utf-8' })
555
+
556
+ const blobUrl = URL.createObjectURL(blob)
557
+
558
+ const link = document.createElement('a')
559
+ link.href = blobUrl
560
+ link.download = `${board.name}.json`
561
+
562
+ document.body.appendChild(link)
563
+ link.click()
564
+ document.body.removeChild(link)
565
+
566
+ URL.revokeObjectURL(blobUrl)
567
+ }
568
+
569
+ async releaseBoard() {
570
+ const { id } = this.board
571
+
572
+ this.dispatchEvent(
573
+ new CustomEvent('release-board', {
574
+ detail: {
575
+ id
576
+ },
577
+ bubbles: true,
578
+ composed: true
579
+ })
580
+ )
581
+ }
582
+
583
+ async updateBoard() {
584
+ const { id, name, description, groupId } = this.board
585
+ /* model 정보를 제외하고 업데이트 */
586
+
587
+ this.dispatchEvent(
588
+ new CustomEvent('update-board', {
589
+ detail: { id, name, description, groupId },
590
+ bubbles: true,
591
+ composed: true
592
+ })
593
+ )
594
+ }
595
+
596
+ async deleteBoard() {
597
+ if (
598
+ await OxPrompt.open({
599
+ title: i18next.t('text.are_you_sure'),
600
+ text: i18next.t('text.sure_to_x', { x: i18next.t('text.delete') }),
601
+ confirmButton: { text: i18next.t('button.confirm') },
602
+ cancelButton: { text: i18next.t('button.cancel') }
603
+ })
604
+ ) {
605
+ this.dispatchEvent(
606
+ new CustomEvent('delete-board', {
607
+ detail: this.board,
608
+ bubbles: true,
609
+ composed: true
610
+ })
611
+ )
612
+
613
+ this.close()
614
+ }
615
+ }
616
+
617
+ async joinPlayGroup(group) {
618
+ this.dispatchEvent(
619
+ new CustomEvent('join-playgroup', {
620
+ detail: {
621
+ board: this.board,
622
+ playGroup: group
623
+ },
624
+ bubbles: true,
625
+ composed: true
626
+ })
627
+ )
628
+ }
629
+
630
+ async leavePlayGroup(group) {
631
+ this.dispatchEvent(
632
+ new CustomEvent('leave-playgroup', {
633
+ detail: {
634
+ board: this.board,
635
+ playGroup: group
636
+ },
637
+ bubbles: true,
638
+ composed: true
639
+ })
640
+ )
641
+ }
642
+
643
+ close() {
644
+ history.back()
645
+ }
646
+ }
@@ -0,0 +1,56 @@
1
+ import { css, html, LitElement } from 'lit'
2
+ import { customElement, property, state } from 'lit/decorators.js'
3
+ import { OxPrompt } from '@operato/popup/ox-prompt.js'
4
+ import { i18next } from '@operato/i18n'
5
+
6
+ import './link-builder.js'
7
+ import './board-template-builder.js'
8
+
9
+ @customElement('board-info-link')
10
+ export class BoardInfoLink extends LitElement {
11
+ static styles = [
12
+ css`
13
+ :host {
14
+ display: flex;
15
+ flex-direction: column;
16
+ background-color: var(--md-sys-color-surface);
17
+ position: relative;
18
+ text-align: center;
19
+ color: var(--label-color, var(--md-sys-color-on-surface));
20
+ font: var(--label-font);
21
+
22
+ overflow-y: auto;
23
+ }
24
+
25
+ link-builder {
26
+ flex: 1;
27
+ text-align: start;
28
+ }
29
+
30
+ board-template-builder {
31
+ flex: 1;
32
+ text-align: start;
33
+ }
34
+
35
+ @media screen and (max-width: 460px) {
36
+ :host {
37
+ width: 100vw;
38
+ }
39
+ }
40
+ `
41
+ ]
42
+
43
+ @property({ type: Object }) board?: { id: string; name: string; description: string }
44
+
45
+ render() {
46
+ return html`
47
+ <link-builder
48
+ command="headless-full"
49
+ target-id=${this.board?.id || ''}
50
+ target-name=${this.board?.name || ''}
51
+ ></link-builder>
52
+
53
+ <board-template-builder .board=${this.board}> </board-template-builder>
54
+ `
55
+ }
56
+ }