@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,124 @@
1
+ import '@material/web/icon/icon.js'
2
+
3
+ import gql from 'graphql-tag'
4
+ import { css, html, LitElement, nothing } from 'lit'
5
+ import { customElement, property } from 'lit/decorators.js'
6
+ import { connect } from 'pwa-helpers/connect-mixin.js'
7
+
8
+ import { store } from '@operato/shell'
9
+ import { client } from '@operato/graphql'
10
+
11
+ import { UPDATE_FAVORITES } from '@things-factory/fav-base/client'
12
+
13
+ @customElement('favorite-tool')
14
+ export class FavoriteTool extends connect(store)(LitElement) {
15
+ static styles = css`
16
+ :host {
17
+ display: inline-block;
18
+ vertical-align: middle;
19
+ line-height: 0;
20
+ }
21
+
22
+ [favorable] {
23
+ opacity: 0.5;
24
+ }
25
+ `
26
+
27
+ @property({ type: Array }) favorites: any[] = []
28
+ @property({ type: Object }) user: any
29
+ @property({ type: String }) resourceId?: string
30
+ @property({ type: Boolean }) favored?: boolean
31
+ @property({ type: Array }) acceptedPages: any[] = []
32
+
33
+ page: any
34
+
35
+ render() {
36
+ var renderable = (this.acceptedPages || []).indexOf(this.page) !== -1
37
+
38
+ return renderable
39
+ ? html`
40
+ <md-icon @click=${this.onClick.bind(this)} ?favorable=${!this.favored}
41
+ >${this.favored ? 'star' : 'star_border'}</md-icon
42
+ >
43
+ `
44
+ : nothing
45
+ }
46
+
47
+ updated(changes) {
48
+ if (changes.has('user')) {
49
+ this.refreshFavorites()
50
+ }
51
+
52
+ this.favored = (this.favorites || []).includes(this.resourceId)
53
+ }
54
+
55
+ stateChanged(state) {
56
+ this.page = state.route.page
57
+ this.favorites = state.favorite.favorites
58
+ this.user = state.auth.user
59
+ this.resourceId = state.route.resourceId
60
+ }
61
+
62
+ onClick(event) {
63
+ if (!this.resourceId) {
64
+ return
65
+ }
66
+
67
+ if (this.favored) {
68
+ this.removeFavorite(this.resourceId)
69
+ } else {
70
+ this.addFavorite(this.resourceId)
71
+ }
72
+ }
73
+
74
+ async refreshFavorites() {
75
+ if (!this.user || !this.user.email) {
76
+ return
77
+ }
78
+
79
+ const response = await client.query({
80
+ query: gql`
81
+ query {
82
+ myFavorites {
83
+ id
84
+ routing
85
+ }
86
+ }
87
+ `
88
+ })
89
+
90
+ store.dispatch({
91
+ type: UPDATE_FAVORITES,
92
+ favorites: response.data.myFavorites.map(favorite => favorite.routing)
93
+ })
94
+ }
95
+
96
+ async removeFavorite(routing) {
97
+ await client.query({
98
+ query: gql`
99
+ mutation {
100
+ deleteFavorite(routing: "${routing}")
101
+ }
102
+ `
103
+ })
104
+
105
+ this.refreshFavorites()
106
+ }
107
+
108
+ async addFavorite(routing) {
109
+ await client.query({
110
+ query: gql`
111
+ mutation {
112
+ createFavorite(favorite: {
113
+ routing: "${routing}"
114
+ }) {
115
+ id
116
+ routing
117
+ }
118
+ }
119
+ `
120
+ })
121
+
122
+ this.refreshFavorites()
123
+ }
124
+ }
@@ -0,0 +1,272 @@
1
+ import '@material/web/icon/icon.js'
2
+ import '@operato/board/ox-board-creation-card.js'
3
+
4
+ import gql from 'graphql-tag'
5
+ import { css, html, LitElement, PropertyValues, nothing } from 'lit'
6
+ import { customElement, property, state, query } from 'lit/decorators.js'
7
+ import { keyed } from 'lit/directives/keyed.js'
8
+
9
+ import { client } from '@operato/graphql'
10
+
11
+ import { privileged } from '@things-factory/auth-base/dist-client'
12
+
13
+ @customElement('board-tile-list')
14
+ export class BoardTileList extends LitElement {
15
+ static styles = [
16
+ css`
17
+ :host {
18
+ overflow: auto;
19
+ padding: var(--popup-content-padding);
20
+ display: grid;
21
+ background-color: var(--md-sys-color-background);
22
+
23
+ grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
24
+ grid-auto-rows: var(--card-list-rows-height);
25
+ grid-gap: 20px;
26
+ }
27
+
28
+ [card] {
29
+ position: relative;
30
+ align-items: center;
31
+ overflow: hidden;
32
+ }
33
+
34
+ [card][create] {
35
+ overflow: visible;
36
+ background-color: initial;
37
+ }
38
+
39
+ [card] > a {
40
+ display: flex;
41
+ clip-path: border-box;
42
+ }
43
+
44
+ [card]:hover {
45
+ cursor: pointer;
46
+ }
47
+
48
+ [name] {
49
+ text-overflow: ellipsis;
50
+ white-space: nowrap;
51
+ overflow: hidden;
52
+ margin-top: var(--spacing-small);
53
+ width: calc(100% - 45px);
54
+ color: var(--md-sys-color-on-background);
55
+ font-weight: bolder;
56
+ font-size: var(--fontsize-small);
57
+ }
58
+
59
+ img {
60
+ flex: 1;
61
+ object-fit: contain;
62
+ }
63
+
64
+ md-icon[iconBtn] {
65
+ float: right;
66
+ margin-top: -20px;
67
+ margin-left: 2px;
68
+ color: var(--board-list-star-color);
69
+ font-size: 1.4em;
70
+ }
71
+ md-icon[info] {
72
+ color: var(--md-sys-color-primary);
73
+ }
74
+
75
+ md-icon[iconBtn][favored],
76
+ md-icon[info]:hover {
77
+ color: var(--board-list-star-active-color);
78
+ }
79
+
80
+ a {
81
+ display: block;
82
+ border-radius: var(--card-list-border-radius);
83
+ border: var(--border-dim-color);
84
+ box-sizing: border-box;
85
+ color: var(--card-list-color);
86
+ background-color: var(--card-list-background-color);
87
+ margin: 0px;
88
+ height: calc(100% - 25px);
89
+ }
90
+
91
+ :host > *:hover [info] {
92
+ opacity: 1;
93
+ -webkit-transition: opacity 0.8s;
94
+ -moz-transition: opacity 0.8s;
95
+ -o-transition: opacity 0.8s;
96
+ transition: opacity 0.8s;
97
+ }
98
+
99
+ [draggable='true'] {
100
+ cursor: grab;
101
+ }
102
+
103
+ @media screen and (max-width: 800px), screen and (max-height: 600px) {
104
+ ox-board-creation-card {
105
+ display: none;
106
+ }
107
+ }
108
+ `
109
+ ]
110
+
111
+ @property({ type: Array }) boards: any[] = []
112
+ @property({ type: Array }) favorites: any[] = []
113
+ @property({ type: Array }) groups: any[] = []
114
+ @property({ type: String }) group?: string
115
+ @property({ type: Boolean }) creatable?: boolean = false
116
+ @property({ type: String, attribute: 'search-text' }) searchText?: string
117
+ @property({ type: Boolean, attribute: 'reorderable' }) reorderable: boolean = false
118
+
119
+ private draggedItem
120
+
121
+ connectedCallback() {
122
+ super.connectedCallback()
123
+
124
+ if (this.reorderable) {
125
+ this.renderRoot.addEventListener('dragstart', (e: Event) => {
126
+ const target = e.target! as HTMLElement
127
+ this.draggedItem = target.closest('[card]')
128
+ ;(e as DragEvent).dataTransfer?.setData('text/plain', target.innerHTML)
129
+ })
130
+
131
+ this.renderRoot.addEventListener('dragover', (e: Event) => {
132
+ e.preventDefault()
133
+
134
+ const target = e.target! as HTMLElement
135
+ const targetItem = target.closest('[card]')
136
+ if (targetItem && targetItem !== this.draggedItem) {
137
+ const targetRect = targetItem.getBoundingClientRect()
138
+ const mousePos = (e as DragEvent).clientX - targetRect.left
139
+
140
+ if (mousePos < targetRect.width / 2) {
141
+ // 마우스 위치가 아이템 좌측 반절에 있을 때
142
+ this.renderRoot.insertBefore(this.draggedItem, targetItem)
143
+ } else {
144
+ // 마우스 위치가 아이템 우측 반절에 있을 때
145
+ this.renderRoot.insertBefore(this.draggedItem, targetItem.nextSibling)
146
+ }
147
+ }
148
+ })
149
+
150
+ this.renderRoot.addEventListener('drop', (e: Event) => {
151
+ e.preventDefault()
152
+
153
+ const boardIds = Array.from(this.renderRoot.querySelectorAll('[card]'))
154
+ .map(board => board.getAttribute('id'))
155
+ .filter(Boolean)
156
+
157
+ this.dispatchEvent(
158
+ new CustomEvent('reordered', {
159
+ detail: {
160
+ groupId: this.group,
161
+ boardIds
162
+ }
163
+ })
164
+ )
165
+ })
166
+ }
167
+ }
168
+
169
+ render() {
170
+ var boards = this.boards || []
171
+
172
+ return html`
173
+ ${this.creatable
174
+ ? privileged(
175
+ { privilege: 'mutation', category: 'board' },
176
+ html`
177
+ <ox-board-creation-card
178
+ .groups=${this.groups}
179
+ .defaultGroup=${this.group}
180
+ @create-board=${e => this.onCreateBoard(e)}
181
+ card
182
+ create
183
+ ></ox-board-creation-card>
184
+ `
185
+ )
186
+ : nothing}
187
+ ${keyed(
188
+ Date.now(),
189
+ boards.map(
190
+ board => html`
191
+ <div card draggable="true" id=${board.id}>
192
+ <a href="board-viewer/${board.id}"> <img src=${board.thumbnail} /> </a>
193
+
194
+ <div name>${board.name}</div>
195
+ <!-- <div description>${board.description}</div> -->
196
+
197
+ <md-icon
198
+ iconBtn
199
+ info
200
+ @click=${e => {
201
+ this.infoBoard(board)
202
+ e.preventDefault()
203
+ }}
204
+ >info</md-icon
205
+ >
206
+
207
+ ${(this.favorites || []).includes(board.id)
208
+ ? html` <md-icon iconBtn favored @click=${e => this.removeFavorite(board.id)}>star</md-icon> `
209
+ : html` <md-icon iconBtn @click=${e => this.addFavorite(board.id)}>star_border</md-icon> `}
210
+ </div>
211
+ `
212
+ )
213
+ )}
214
+ `
215
+ }
216
+
217
+ updated(changes: PropertyValues<this>) {
218
+ var creationCard = this.renderRoot.querySelector('ox-board-creation-card') as any
219
+ if (creationCard) {
220
+ creationCard.reset()
221
+ }
222
+ }
223
+
224
+ onCreateBoard(e) {
225
+ this.dispatchEvent(
226
+ new CustomEvent('create-board', {
227
+ detail: e.detail
228
+ })
229
+ )
230
+ }
231
+
232
+ infoBoard(board) {
233
+ this.dispatchEvent(
234
+ new CustomEvent('info-board', {
235
+ detail: board
236
+ })
237
+ )
238
+ }
239
+
240
+ async removeFavorite(boardId) {
241
+ await client.query({
242
+ query: gql`
243
+ mutation {
244
+ deleteFavorite(routing: "${boardId}")
245
+ }
246
+ `
247
+ })
248
+
249
+ this.refreshFavorites()
250
+ }
251
+
252
+ async addFavorite(boardId) {
253
+ await client.query({
254
+ query: gql`
255
+ mutation {
256
+ createFavorite(favorite: {
257
+ routing: "${boardId}"
258
+ }) {
259
+ id
260
+ routing
261
+ }
262
+ }
263
+ `
264
+ })
265
+
266
+ this.refreshFavorites()
267
+ }
268
+
269
+ async refreshFavorites() {
270
+ this.dispatchEvent(new CustomEvent('refresh-favorites'))
271
+ }
272
+ }
@@ -0,0 +1,63 @@
1
+ import { css } from 'lit'
2
+
3
+ export const GroupBarStyles = css`
4
+ :host {
5
+ background-color: var(--group-bar-background-color);
6
+
7
+ overflow-x: hidden;
8
+ }
9
+
10
+ ul {
11
+ display: flex;
12
+ flex-direction: row;
13
+ box-sizing: border-box;
14
+ list-style: none;
15
+ margin: 0;
16
+ padding: 0;
17
+ white-space: nowrap;
18
+ }
19
+
20
+ li {
21
+ display: flex;
22
+ align-items: center;
23
+
24
+ box-sizing: border-box;
25
+ border-bottom: var(--group-bar-line);
26
+ align-self: auto;
27
+ min-height: 43px;
28
+ padding: 4px 8px;
29
+
30
+ * {
31
+ vertical-align: middle;
32
+ }
33
+
34
+ a {
35
+ display: block;
36
+ text-decoration: none;
37
+ font: var(--group-bar-textbutton);
38
+ color: var(--md-sys-color-on-secondary-container);
39
+ opacity: 0.8;
40
+ }
41
+
42
+ a md-icon {
43
+ font-variation-settings: 'FILL' 1;
44
+ }
45
+
46
+ &[active] {
47
+ border-color: var(--group-bar-active-line-color);
48
+
49
+ a {
50
+ font: var(--group-bar-textbutton-active);
51
+ opacity: 1;
52
+ }
53
+ }
54
+
55
+ &[padding] {
56
+ flex: 1;
57
+ }
58
+
59
+ &[add] * {
60
+ color: var(--md-sys-color-on-secondary-container);
61
+ }
62
+ }
63
+ `
@@ -0,0 +1,99 @@
1
+ import '@material/web/icon/icon.js'
2
+
3
+ import { html, LitElement } from 'lit'
4
+ import { customElement, property } from 'lit/decorators.js'
5
+ import ScrollBooster from 'scrollbooster'
6
+
7
+ import { longpressable } from '@operato/utils'
8
+ import { privileged } from '@things-factory/auth-base/dist-client'
9
+ import { GroupBarStyles } from './group-bar-styles'
10
+
11
+ @customElement('group-bar')
12
+ export default class GroupBar extends LitElement {
13
+ static styles = [GroupBarStyles]
14
+
15
+ @property({ type: Array }) groups?: any[]
16
+ @property({ type: String }) groupId?: string
17
+ @property({ type: String }) targetPage?: string
18
+
19
+ private __sb?: ScrollBooster
20
+
21
+ render() {
22
+ return html`
23
+ <ul>
24
+ <li ?active=${!this.groupId}>
25
+ <a href=${this.targetPage || ''}><md-icon>dashboard</md-icon></a>
26
+ </li>
27
+
28
+ <li ?active=${this.groupId === 'favor'}>
29
+ <a href="${this.targetPage}/favor"><md-icon>star</md-icon></a>
30
+ </li>
31
+
32
+ ${privileged(
33
+ { privilege: 'mutation', category: 'board' },
34
+ html`<li ?active=${this.groupId === 'mywork'}>
35
+ <a href="${this.targetPage}/mywork"><md-icon>engineering</md-icon></a>
36
+ </li>`
37
+ )}
38
+ ${(this.groups || []).map(
39
+ group => html`
40
+ <li ?active=${this.groupId === group.id} @long-press=${e => this.infoGroup(group.id)}>
41
+ <a href=${`${this.targetPage}/${group.id}`}>${group.name}</a>
42
+ </li>
43
+ `
44
+ )}
45
+
46
+ <li padding></li>
47
+
48
+ <li add>
49
+ <md-icon @click=${e => this.infoGroup()}>add</md-icon>
50
+ </li>
51
+ </ul>
52
+ `
53
+ }
54
+
55
+ infoGroup(groupId?: string) {
56
+ this.dispatchEvent(
57
+ new CustomEvent('info-group', {
58
+ detail: groupId
59
+ })
60
+ )
61
+ }
62
+
63
+ onWheelEvent(e) {
64
+ var delta = Math.max(-1, Math.min(1, e.wheelDelta || -e.detail))
65
+ this.scrollLeft -= delta * 40
66
+
67
+ e.preventDefault()
68
+ }
69
+
70
+ updated(change) {
71
+ if (change.has('groups')) {
72
+ /* groups가 바뀔 때마다, contents의 폭이 달라지므로, 다시 폭을 계산해준다. */
73
+ this.__sb && this.__sb.updateMetrics()
74
+ }
75
+
76
+ if (change.has('groupId')) {
77
+ var active = this.renderRoot.querySelector('li[active]')
78
+ active && active.scrollIntoView()
79
+ }
80
+ }
81
+
82
+ firstUpdated() {
83
+ var scrollTarget = this.renderRoot.querySelector('ul')
84
+
85
+ /* long-press */
86
+ longpressable(scrollTarget!)
87
+
88
+ scrollTarget!.addEventListener('mousewheel', this.onWheelEvent.bind(this), false)
89
+
90
+ this.__sb = new ScrollBooster({
91
+ viewport: this,
92
+ content: scrollTarget,
93
+ mode: 'x',
94
+ onUpdate: data => {
95
+ this.scrollLeft = data.position.x
96
+ }
97
+ })
98
+ }
99
+ }
@@ -0,0 +1,88 @@
1
+ import '@material/web/icon/icon.js'
2
+
3
+ import { html, LitElement } from 'lit'
4
+ import { customElement, property } from 'lit/decorators.js'
5
+ import ScrollBooster from 'scrollbooster'
6
+
7
+ import { longpressable } from '@operato/utils'
8
+ import { GroupBarStyles } from './group-bar-styles'
9
+
10
+ @customElement('play-group-bar')
11
+ export default class PlayGroupBar extends LitElement {
12
+ static styles = [GroupBarStyles]
13
+
14
+ @property({ type: Array }) groups: { id: string; name: string }[] = []
15
+ @property({ type: String }) groupId: string = ''
16
+ @property({ type: String, attribute: 'target-page' }) targetPage: string = ''
17
+
18
+ private __sb?: ScrollBooster
19
+
20
+ render() {
21
+ return html`
22
+ <ul>
23
+ ${(this.groups || []).map(
24
+ group => html`
25
+ <li ?active=${this.groupId === group.id} @long-press=${e => this._infoGroup(group)}>
26
+ <a href=${`${this.targetPage}/${group.id}`}>${group.name}</a>
27
+ </li>
28
+ `
29
+ )}
30
+
31
+ <li padding>&nbsp;</li>
32
+
33
+ <li add>
34
+ <md-icon @click=${e => this._infoGroup()}>add</md-icon>
35
+ </li>
36
+ </ul>
37
+ `
38
+ }
39
+
40
+ _infoGroup(group?: { id: string; name: string }) {
41
+ this.dispatchEvent(
42
+ new CustomEvent('info-play-group', {
43
+ detail: group
44
+ })
45
+ )
46
+ }
47
+
48
+ _onWheelEvent(e) {
49
+ var delta = Math.max(-1, Math.min(1, e.wheelDelta || -e.detail))
50
+ this.scrollLeft -= delta * 40
51
+
52
+ e.preventDefault()
53
+ }
54
+
55
+ _onClickAdd(e) {
56
+ // TODO Implements
57
+ }
58
+
59
+ updated(change) {
60
+ if (change.has('groups')) {
61
+ /* groups가 바뀔 때마다, contents의 폭이 달라지므로, 다시 폭을 계산해준다. */
62
+ this.__sb && this.__sb.updateMetrics()
63
+ }
64
+
65
+ if (change.has('groupId')) {
66
+ var active = this.renderRoot.querySelector('li[active]')
67
+ active && active.scrollIntoView()
68
+ }
69
+ }
70
+
71
+ firstUpdated() {
72
+ var scrollTarget = this.renderRoot.querySelector('ul')
73
+
74
+ /* long-press */
75
+ longpressable(scrollTarget!)
76
+
77
+ scrollTarget!.addEventListener('mousewheel', this._onWheelEvent.bind(this), false)
78
+
79
+ this.__sb = new ScrollBooster({
80
+ viewport: this,
81
+ content: scrollTarget,
82
+ mode: 'x',
83
+ onUpdate: data => {
84
+ this.scrollLeft = data.position.x
85
+ }
86
+ })
87
+ }
88
+ }