@things-factory/board-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.
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,85 @@
1
+ import { css, html, LitElement } from 'lit'
2
+ import { customElement, property, state } from 'lit/decorators.js'
3
+ import { ifDefined } from 'lit/directives/if-defined.js'
4
+
5
+ import { ScrollbarStyles } from '@operato/styles'
6
+
7
+ import './board-basic-info'
8
+ import './board-versions'
9
+ import './board-info-link'
10
+
11
+ @customElement('board-info')
12
+ export class BoardInfo extends LitElement {
13
+ static styles = [
14
+ ScrollbarStyles,
15
+ css`
16
+ :host {
17
+ height: 100%;
18
+ min-width: 360px;
19
+
20
+ display: flex;
21
+ flex-direction: column;
22
+ position: relative;
23
+ background-color: var(--md-sys-color-surface);
24
+ }
25
+
26
+ [tab] {
27
+ width: 100%;
28
+ display: flex;
29
+ }
30
+
31
+ [tab] > * {
32
+ flex: 1;
33
+ }
34
+
35
+ [content] {
36
+ flex: 1;
37
+ overflow-y: auto;
38
+ }
39
+
40
+ span {
41
+ padding: 5px 4px 1px;
42
+ font: var(--group-bar-textbutton);
43
+ background-color: var(--md-sys-color-secondary);
44
+ color: rgba(255, 255, 255, 0.8);
45
+ text-align: center;
46
+ text-transform: uppercase;
47
+ }
48
+
49
+ span[active] {
50
+ background-color: var(--md-sys-color-primary);
51
+ }
52
+
53
+ @media screen and (max-width: 460px) {
54
+ :host {
55
+ width: 100vw;
56
+ }
57
+ }
58
+ `
59
+ ]
60
+
61
+ @property({ type: Object }) board?: { id: string; name: string }
62
+
63
+ @state() tab: string = 'basic'
64
+
65
+ render() {
66
+ return html`
67
+ <div tab>
68
+ <span @click=${() => (this.tab = 'basic')} ?active=${this.tab == 'basic'}>basic</span>
69
+ <span @click=${() => (this.tab = 'versions')} ?active=${this.tab == 'versions'}>versions</span>
70
+ <span @click=${() => (this.tab = 'link')} ?active=${this.tab == 'link'}>link</span>
71
+ </div>
72
+ <div content>
73
+ ${this.tab == 'basic'
74
+ ? html`<board-basic-info boardId=${ifDefined(this.board?.id)}></board-basic-info>`
75
+ : this.tab == 'versions'
76
+ ? html`<board-versions boardId=${ifDefined(this.board?.id)}></board-versions>`
77
+ : html`<board-info-link .board=${this.board}></board-info-link>`}
78
+ </div>
79
+ `
80
+ }
81
+
82
+ close() {
83
+ history.back()
84
+ }
85
+ }
@@ -0,0 +1,134 @@
1
+ import '@operato/i18n/ox-i18n.js'
2
+
3
+ import { css, html, LitElement, nothing } from 'lit'
4
+ import { customElement, property, state } from 'lit/decorators.js'
5
+
6
+ import { ScrollbarStyles } from '@operato/styles'
7
+ import { i18next } from '@operato/i18n'
8
+ import { OxPrompt } from '@operato/popup/ox-prompt.js'
9
+
10
+ @customElement('board-template-builder')
11
+ export class BoardTemplateBuilder extends LitElement {
12
+ static styles = [
13
+ ScrollbarStyles,
14
+ css`
15
+ :host {
16
+ display: flex;
17
+ padding: var(--spacing-large);
18
+
19
+ /* for narrow mode */
20
+ flex-direction: column;
21
+ }
22
+
23
+ div[template] {
24
+ display: grid;
25
+ position: relative;
26
+ }
27
+
28
+ label {
29
+ font: var(--label-font);
30
+ color: var(--label-color, var(--md-sys-color-on-surface));
31
+ text-transform: capitalize;
32
+ }
33
+
34
+ input,
35
+ select,
36
+ textarea {
37
+ border: var(--border-dim-color);
38
+ border-radius: var(--border-radius);
39
+ margin: var(--input-margin);
40
+ padding: var(--input-padding);
41
+ font: var(--input-font);
42
+ flex: 1 1 0%;
43
+ }
44
+
45
+ textarea {
46
+ min-height: 132px;
47
+ resize: none;
48
+ }
49
+
50
+ select:focus,
51
+ input:focus,
52
+ textarea:focus,
53
+ button {
54
+ outline: none;
55
+ }
56
+
57
+ button:hover {
58
+ border: var(--button-activ-border);
59
+ box-shadow: var(--button-active-box-shadow);
60
+ }
61
+ `
62
+ ]
63
+
64
+ @property({ type: Object }) board?: { id: string; name: string; description: string }
65
+
66
+ @state() visibility?: string = 'private'
67
+ @state() name?: string = ''
68
+ @state() description?: string = ''
69
+
70
+ render() {
71
+ const name = this.name || this.board?.name || ''
72
+ const description = this.description || this.board?.description || ''
73
+
74
+ return html`
75
+ <div template>
76
+ <h3>
77
+ <ox-title-with-help topic="board-service/board-template" msgid="label.board-template"
78
+ >board template</ox-title-with-help
79
+ >
80
+ </h3>
81
+ <label for="name">${i18next.t('field.name')}</label>
82
+ <input id="name" type="text" .value=${name} @change=${e => (this.name = e.target.value)} />
83
+
84
+ <label for="description">${i18next.t('field.description')}</label>
85
+ <textarea
86
+ id="description"
87
+ .value=${this.description}
88
+ @change=${e => (this.description = e.target.value)}
89
+ ></textarea>
90
+
91
+ <label>${i18next.t('field.visibility')}</label>
92
+ <select
93
+ @change=${e => (this.visibility = e.target.value)}
94
+ placeholder="choose visibility ..."
95
+ .value=${this.visibility}
96
+ >
97
+ <option value="private">${i18next.t('label.visibility-private')}</option>
98
+ ${window.location.pathname.startsWith('/domain/')
99
+ ? html` <option value="domain">${i18next.t('label.visibility-domain')}</option> `
100
+ : nothing}
101
+ <option value="public">${i18next.t('label.visibility-public')}</option>
102
+ </select>
103
+
104
+ <button @click=${this.registerBoardAsTemplate}>${i18next.t('text.register-template')}</button>
105
+ </div>
106
+ `
107
+ }
108
+
109
+ async registerBoardAsTemplate() {
110
+ if (
111
+ await OxPrompt.open({
112
+ title: i18next.t('text.are_you_sure'),
113
+ text: i18next.t('text.sure_to_x', { x: i18next.t('text.register-template') }),
114
+ confirmButton: { text: i18next.t('button.confirm') },
115
+ cancelButton: { text: i18next.t('button.cancel') }
116
+ })
117
+ ) {
118
+ const { id } = this.board || {}
119
+
120
+ this.dispatchEvent(
121
+ new CustomEvent('register-template', {
122
+ detail: {
123
+ id,
124
+ name: this.name || this.board?.name,
125
+ description: this.description || this.board?.description,
126
+ visibility: this.visibility
127
+ },
128
+ bubbles: true,
129
+ composed: true
130
+ })
131
+ )
132
+ }
133
+ }
134
+ }
@@ -0,0 +1,172 @@
1
+ import '@material/web/icon/icon.js'
2
+ import '@material/web/chips/assist-chip.js'
3
+
4
+ import gql from 'graphql-tag'
5
+ import { css, html, LitElement } from 'lit'
6
+ import { customElement, property, state } from 'lit/decorators.js'
7
+
8
+ import { i18next } from '@operato/i18n'
9
+ import { client } from '@operato/graphql'
10
+
11
+ import { privileged } from '@things-factory/auth-base/dist-client'
12
+
13
+ @customElement('board-versions')
14
+ export class BoardVersions extends LitElement {
15
+ static styles = [
16
+ css`
17
+ :host {
18
+ display: block;
19
+ background-color: var(--md-sys-color-surface);
20
+ position: relative;
21
+ text-align: center;
22
+ color: var(--label-color, var(--md-sys-color-on-surface));
23
+ font: var(--label-font);
24
+ }
25
+
26
+ img {
27
+ display: block;
28
+
29
+ margin: auto;
30
+ max-width: 100%;
31
+ max-height: 100%;
32
+ border-bottom: 2px solid rgba(0, 0, 0, 0.1);
33
+ }
34
+
35
+ div[card] {
36
+ position: relative;
37
+ margin: var(--spacing-medium);
38
+ text-align: start;
39
+ border-bottom: var(--border-dim-color);
40
+ }
41
+
42
+ div[info] {
43
+ display: flex;
44
+ align-items: center;
45
+ gap: var(--spacing-medium);
46
+ }
47
+
48
+ span {
49
+ margin-left: auto;
50
+ font: var(--input-field-font);
51
+ display: flex;
52
+ align-items: center;
53
+ gap: 5px;
54
+ }
55
+
56
+ span md-icon {
57
+ font-variation-settings: 'FILL' 1;
58
+
59
+ vertical-align: middle;
60
+ font-size: var(--fontsize-large);
61
+ color: var(--md-sys-color-primary);
62
+ }
63
+
64
+ md-assist-chip {
65
+ position: absolute;
66
+ right: 3px;
67
+ top: 3px;
68
+ opacity: 0.8;
69
+
70
+ --md-assist-chip-elevated-container-color: var(--md-sys-color-primary);
71
+ --md-assist-chip-label-text-color: var(--md-sys-color-on-primary);
72
+ --md-assist-chip-leading-icon-color: var(--md-sys-color-on-primary);
73
+ }
74
+
75
+ @media screen and (max-width: 460px) {
76
+ :host {
77
+ width: 100vw;
78
+ }
79
+ }
80
+ `
81
+ ]
82
+
83
+ @property({ type: String }) boardId?: string
84
+
85
+ @state() versions: any[] = []
86
+
87
+ render() {
88
+ return html`
89
+ ${this.versions.length == 0
90
+ ? html` ${i18next.t('text.no released version information available')} `
91
+ : html`
92
+ ${this.versions.map(
93
+ version => html`
94
+ <div card>
95
+ <img src=${version.thumbnail} />
96
+ <div info>
97
+ <md-icon>pin</md-icon> ${version.version}
98
+ <span>
99
+ <md-icon>person</md-icon> ${(version.updater && version.updater.name) || 'anonymouse'}
100
+ <md-icon>schedule</md-icon> ${new Date(version.updatedAt).toLocaleString()}
101
+ </span>
102
+ </div>
103
+
104
+ ${privileged(
105
+ { privilege: 'mutation', category: 'board' },
106
+ html`
107
+ <md-assist-chip
108
+ elevated
109
+ label=${String(i18next.t('button.revert-board-version'))}
110
+ @click=${() => this.revertBoardVersion(version.version)}
111
+ >
112
+ <md-icon slot="icon">recycling</md-icon>
113
+ </md-assist-chip>
114
+ `
115
+ )}
116
+ </div>
117
+ `
118
+ )}
119
+ `}
120
+ `
121
+ }
122
+
123
+ firstUpdated() {
124
+ this.refresh()
125
+ }
126
+
127
+ async refresh() {
128
+ if (!this.boardId) {
129
+ return
130
+ }
131
+
132
+ var response = (
133
+ await client.query({
134
+ query: gql`
135
+ query FetchBoardVersionsById($id: String!) {
136
+ boardVersions(id: $id) {
137
+ id
138
+ version
139
+ thumbnail
140
+ updater {
141
+ name
142
+ }
143
+ updatedAt
144
+ }
145
+ }
146
+ `,
147
+ variables: { id: this.boardId }
148
+ })
149
+ ).data
150
+
151
+ this.versions = response.boardVersions
152
+ }
153
+
154
+ revertBoardVersion(version: number) {
155
+ this.dispatchEvent(
156
+ new CustomEvent('revert-board-version', {
157
+ detail: {
158
+ id: this.boardId,
159
+ version
160
+ },
161
+ bubbles: true,
162
+ composed: true
163
+ })
164
+ )
165
+
166
+ this.close()
167
+ }
168
+
169
+ close() {
170
+ history.back()
171
+ }
172
+ }
@@ -0,0 +1,267 @@
1
+ import '@material/web/icon/icon.js'
2
+ import '@material/web/textfield/filled-text-field.js'
3
+ import '@material/web/button/elevated-button.js'
4
+ import '@operato/input/ox-input-file.js'
5
+
6
+ import { css, html, LitElement } from 'lit'
7
+ import { customElement, property, query, state } from 'lit/decorators.js'
8
+
9
+ import { i18next } from '@operato/i18n'
10
+
11
+ import { fetchGroup } from '../graphql/group.js'
12
+
13
+ @customElement('group-info-basic')
14
+ export class GroupInfo extends LitElement {
15
+ static styles = [
16
+ css`
17
+ :host {
18
+ display: block;
19
+ color: var(--md-sys-color-on-surface);
20
+ background-color: var(--md-sys-color-surface);
21
+ height: 100%;
22
+ min-width: 360px;
23
+ overflow: auto;
24
+ position: relative;
25
+
26
+ --form-grid-gap: 2px 0;
27
+
28
+ --input-field-padding: var(--spacing-medium);
29
+ --legend-padding: var(--spacing-medium) 0 var(--spacing-small) 0;
30
+ }
31
+
32
+ form {
33
+ display: grid;
34
+ grid-template-columns: repeat(12, 1fr);
35
+ grid-gap: var(--form-grid-gap);
36
+ grid-auto-rows: minmax(24px, auto);
37
+ padding: var(--spacing-large);
38
+ align-items: center;
39
+ }
40
+
41
+ md-filled-text-field {
42
+ grid-column: span 12;
43
+ padding: 10px 0;
44
+ }
45
+
46
+ [buttons] {
47
+ grid-column: span 12;
48
+ padding: var(--spacing-medium) 0;
49
+ text-align: right;
50
+
51
+ display: flex;
52
+ gap: var(--spacing-small);
53
+ align-items: center;
54
+ }
55
+
56
+ [buttons] * {
57
+ margin: 0 0 0 auto;
58
+ }
59
+
60
+ [danger] {
61
+ float: left;
62
+ margin: 0;
63
+ --md-elevated-button-icon-color: var(--md-sys-color-on-error);
64
+ --md-elevated-button-label-text-color: var(--md-sys-color-on-error);
65
+ --md-elevated-button-container-color: var(--md-sys-color-error);
66
+ }
67
+
68
+ fieldset {
69
+ display: contents;
70
+ }
71
+
72
+ legend {
73
+ grid-column: span 12;
74
+ padding: var(--legend-padding);
75
+ font: var(--legend-font);
76
+ color: var(--md-sys-color-primary);
77
+ text-transform: capitalize;
78
+ }
79
+
80
+ label {
81
+ grid-column: span 12;
82
+ text-transform: capitalize;
83
+ color: var(--md-sys-color-primary);
84
+ font: var(--label-font);
85
+ }
86
+
87
+ span {
88
+ grid-column: span 12;
89
+ border-bottom: var(--border-dim-color);
90
+ margin-bottom: var(--spacing-medium);
91
+ padding-bottom: var(--spacing-small);
92
+ font: var(--input-field-font);
93
+ }
94
+ span md-icon {
95
+ font-variation-settings: 'FILL' 1;
96
+
97
+ vertical-align: middle;
98
+ font-size: var(--fontsize-large);
99
+ color: var(--md-sys-color-primary);
100
+ }
101
+
102
+ input,
103
+ table,
104
+ select,
105
+ textarea,
106
+ [custom-input] {
107
+ grid-column: span 12;
108
+
109
+ border: var(--input-field-border);
110
+ border-radius: var(--input-field-border-radius);
111
+ margin-bottom: var(--spacing-medium);
112
+ padding: var(--input-field-padding);
113
+ font: var(--input-field-font);
114
+ }
115
+
116
+ input[type='checkbox'],
117
+ input[type='radio'] {
118
+ place-self: center;
119
+ margin: 0;
120
+ grid-column: 1;
121
+ }
122
+
123
+ input[type='checkbox'] + label,
124
+ input[type='radio'] + label {
125
+ text-align: left;
126
+ grid-column: span 11 / auto;
127
+
128
+ font: var(--form-sublabel-font);
129
+ color: var(--md-sys-color-secondary);
130
+ }
131
+
132
+ input:focus {
133
+ outline: none;
134
+ border: 1px solid var(--focus-background-color);
135
+ }
136
+
137
+ @media screen and (max-width: 460px) {
138
+ :host {
139
+ width: 100vw;
140
+ }
141
+ }
142
+ `
143
+ ]
144
+
145
+ @property({ type: String }) groupId?: string
146
+
147
+ @state() group: any = { name: '', description: '' }
148
+
149
+ render() {
150
+ var group = this.group || { name: '', description: '' }
151
+
152
+ return html`
153
+ <form>
154
+ <fieldset>
155
+ <legend>group information</legend>
156
+
157
+ <md-filled-text-field
158
+ type="text"
159
+ label=${String(i18next.t('label.name'))}
160
+ .value=${group.name}
161
+ @change=${e => (this.group.name = e.target.value)}
162
+ ></md-filled-text-field>
163
+
164
+ <md-filled-text-field
165
+ type="text"
166
+ label=${String(i18next.t('label.description'))}
167
+ .value=${group.description}
168
+ @change=${e => (this.group.description = e.target.value)}
169
+ ></md-filled-text-field>
170
+
171
+ <label>${i18next.t('label.creator')}</label>
172
+ <span>
173
+ <md-icon>person</md-icon> ${group.creator && group.creator.name} <md-icon>schedule</md-icon> ${new Date(
174
+ group.createdAt
175
+ ).toLocaleString()}
176
+ </span>
177
+
178
+ <label>${i18next.t('label.updater')}</label>
179
+ <span>
180
+ <md-icon>person</md-icon> ${group.updater && group.updater.name} <md-icon>schedule</md-icon> ${new Date(
181
+ group.updatedAt
182
+ ).toLocaleString()}
183
+ </span>
184
+
185
+ <div buttons>
186
+ ${this.groupId
187
+ ? html`
188
+ <md-elevated-button danger @click=${this.deleteGroup.bind(this)}
189
+ ><md-icon slot="icon">delete_outline</md-icon>${i18next.t('button.delete')}</md-elevated-button
190
+ >
191
+ <md-elevated-button @click=${this.updateGroup.bind(this)}
192
+ ><md-icon slot="icon">save</md-icon>${i18next.t('button.save')}</md-elevated-button
193
+ >
194
+ `
195
+ : html`
196
+ <md-elevated-button @click=${this.createGroup.bind(this)}
197
+ ><md-icon slot="icon">create_new_folder</md-icon>${i18next.t('button.create')}</md-elevated-button
198
+ >
199
+ `}
200
+ </div>
201
+ </fieldset>
202
+ </form>
203
+ `
204
+ }
205
+
206
+ updated(changes) {
207
+ if (changes.has('groupId')) {
208
+ this.refresh()
209
+ }
210
+ }
211
+
212
+ firstUpdated() {
213
+ setTimeout(() => {
214
+ ;(this.renderRoot.querySelector('[type=text]') as any).focus()
215
+ }, 100)
216
+ }
217
+
218
+ async refresh() {
219
+ if (!this.groupId) {
220
+ /* model이 없으므로, 기본 모델을 제공함. */
221
+ this.group = { name: '', description: '' }
222
+ } else {
223
+ var response = await fetchGroup(this.groupId)
224
+ this.group = response.group
225
+ }
226
+ }
227
+
228
+ async createGroup() {
229
+ this.dispatchEvent(
230
+ new CustomEvent('create-group', {
231
+ detail: this.group,
232
+ bubbles: true,
233
+ composed: true
234
+ })
235
+ )
236
+
237
+ this.close()
238
+ }
239
+
240
+ async updateGroup() {
241
+ this.dispatchEvent(
242
+ new CustomEvent('update-group', {
243
+ detail: this.group,
244
+ bubbles: true,
245
+ composed: true
246
+ })
247
+ )
248
+
249
+ this.close()
250
+ }
251
+
252
+ async deleteGroup() {
253
+ this.dispatchEvent(
254
+ new CustomEvent('delete-group', {
255
+ detail: this.group,
256
+ bubbles: true,
257
+ composed: true
258
+ })
259
+ )
260
+
261
+ this.close()
262
+ }
263
+
264
+ close() {
265
+ history.back()
266
+ }
267
+ }