@things-factory/board-ui 4.0.3 → 4.0.4

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.
@@ -1,495 +0,0 @@
1
- import { LitElement, html, css } from 'lit-element'
2
-
3
- import '@material/mwc-fab'
4
- import '@material/mwc-icon'
5
-
6
- import { create } from '@hatiolab/things-scene'
7
- import { togglefullscreen, isIOS } from '@things-factory/utils'
8
-
9
- import { style } from './board-viewer-style'
10
-
11
- export class BoardViewer extends LitElement {
12
- constructor() {
13
- super()
14
-
15
- this.board = {}
16
- this.provider = null
17
- this.scene = null
18
-
19
- this.forward = []
20
- this.backward = []
21
-
22
- this.hideFullscreen = false
23
- }
24
-
25
- static get properties() {
26
- return {
27
- board: Object,
28
- provider: Object,
29
- baseUrl: String,
30
- data: Object,
31
- hideFullscreen: {
32
- type: Boolean,
33
- reflect: true,
34
- attribute: 'hide-fullscreen'
35
- },
36
- hideNavigation: {
37
- type: Boolean,
38
- reflect: true,
39
- attribute: 'hide-navigation'
40
- }
41
- }
42
- }
43
-
44
- static get styles() {
45
- return [
46
- style,
47
- css`
48
- [hidden] {
49
- display: none;
50
- }
51
-
52
- @media print {
53
- mwc-fab,
54
- mwc-con {
55
- display: none;
56
- }
57
- }
58
- `
59
- ]
60
- }
61
-
62
- render() {
63
- var fullscreen =
64
- !isIOS() && !this.hideFullscreen
65
- ? html`
66
- <mwc-fab
67
- id="fullscreen"
68
- .icon=${document.fullscreenElement ? 'fullscreen_exit' : 'fullscreen'}
69
- @click=${e => this.onTapFullscreen(e)}
70
- @mouseover=${e => this.transientShowButtons(true)}
71
- @mouseout=${e => this.transientShowButtons()}
72
- title="fullscreen"
73
- ></mwc-fab>
74
- `
75
- : html``
76
-
77
- var prev = !this.hideNavigation
78
- ? html`
79
- <mwc-icon
80
- id="prev"
81
- @click=${e => this.onTapPrev(e)}
82
- @mouseover=${e => this.transientShowButtons(true)}
83
- @mouseout=${e => this.transientShowButtons()}
84
- hidden
85
- >keyboard_arrow_left</mwc-icon
86
- >
87
- `
88
- : html``
89
-
90
- var next = !this.hideNavigation
91
- ? html`
92
- <mwc-icon
93
- id="next"
94
- @click=${e => this.onTapNext(e)}
95
- @mouseover=${e => this.transientShowButtons(true)}
96
- @mouseout=${e => this.transientShowButtons()}
97
- hidden
98
- >keyboard_arrow_right</mwc-icon
99
- >
100
- `
101
- : html``
102
-
103
- return html`
104
- ${prev}
105
-
106
- <div
107
- id="target"
108
- @touchstart=${e => this.transientShowButtons()}
109
- @mousemove=${e => this.transientShowButtons()}
110
- ></div>
111
-
112
- ${next} ${fullscreen}
113
- `
114
- }
115
-
116
- firstUpdated() {
117
- window.addEventListener('resize', () => {
118
- this.scene && this.scene.fit()
119
- })
120
-
121
- this.shadowRoot.addEventListener(
122
- 'close-scene',
123
- e => {
124
- e.preventDefault()
125
- this.onTapPrev()
126
- },
127
- false
128
- )
129
- }
130
-
131
- updated(changes) {
132
- if (changes.has('board')) {
133
- if (this.board && this.board.id) {
134
- this.initScene()
135
- } else {
136
- this.closeScene()
137
- }
138
- }
139
-
140
- if (changes.has('data') && this.scene && this.data) {
141
- this.scene.data = this.data
142
- }
143
- }
144
-
145
- initScene() {
146
- if (!this.board || !this.board.id) return
147
-
148
- var scene = create({
149
- model: {
150
- ...this.board.model
151
- },
152
- mode: 0,
153
- refProvider: this.provider
154
- })
155
-
156
- if (this.baseUrl) {
157
- scene.baseUrl = this.baseUrl
158
- }
159
-
160
- this.provider.add(this.board.id, scene)
161
-
162
- this.showScene(this.board.id)
163
-
164
- /* provider.add 시에 추가된 레퍼런스 카운트를 다운시켜주어야 함 */
165
- scene.release()
166
- }
167
-
168
- closeScene() {
169
- if (this.scene) {
170
- this.unbindSceneEvents(this.scene)
171
-
172
- this.scene.target = null
173
- this.scene.release()
174
-
175
- delete this.scene
176
- }
177
-
178
- // delete queued scenes
179
- this.forward.forEach(scene => scene.release())
180
- this.forward = []
181
-
182
- this.backward.forEach(scene => scene.release())
183
- this.backward = []
184
- }
185
-
186
- get target() {
187
- return this.shadowRoot.querySelector('#target')
188
- }
189
-
190
- get prev() {
191
- return this.shadowRoot.querySelector('#prev')
192
- }
193
-
194
- get next() {
195
- return this.shadowRoot.querySelector('#next')
196
- }
197
-
198
- get fullscreen() {
199
- return this.shadowRoot.querySelector('#fullscreen')
200
- }
201
-
202
- releaseScene() {
203
- if (this.scene) {
204
- this.unbindSceneEvents(this.scene, this)
205
-
206
- this.scene.target = null
207
- this.scene.release()
208
-
209
- delete this.scene
210
-
211
- // delete queued scenes
212
- this.forward.forEach(scene => {
213
- scene.release()
214
- })
215
- this.forward = []
216
-
217
- this.backward.forEach(scene => {
218
- scene.release()
219
- })
220
- this.backward = []
221
-
222
- this.transientShowButtons()
223
- }
224
- }
225
-
226
- setupScene(scene) {
227
- this.scene = scene
228
-
229
- /* scene의 기존 target을 보관한다. */
230
- this._oldtarget = this.scene.target
231
-
232
- this.scene.fit(this.board.model.fitMode)
233
- this.scene.target = this.target
234
-
235
- if (this.data) {
236
- this.scene.data = this.data
237
- }
238
-
239
- this.bindSceneEvents(this.scene)
240
-
241
- this.transientShowButtons()
242
- }
243
-
244
- async showScene(boardId, bindingData) {
245
- if (!boardId) return
246
-
247
- try {
248
- var scene = await this.provider.get(boardId, true)
249
-
250
- var old_scene = this.scene
251
- this.scene = scene
252
-
253
- if (scene.target === this.target) {
254
- scene.release()
255
- return
256
- }
257
-
258
- if (old_scene) {
259
- /* old scene을 backward에 보관한다. */
260
- this.unbindSceneEvents(old_scene)
261
- /* 원래의 target에 되돌린다. */
262
- old_scene.target = this._oldtarget
263
- this.backward.push(old_scene)
264
- }
265
-
266
- this.forward.forEach(scene => {
267
- scene.release()
268
- })
269
-
270
- /* forward를 비운다. */
271
- this.forward = []
272
-
273
- this.setupScene(scene)
274
-
275
- if (bindingData) {
276
- scene.data = bindingData
277
- }
278
- } catch (e) {
279
- console.error(e)
280
- }
281
- }
282
-
283
- bindSceneEvents() {
284
- this.scene.on('goto', this.onLinkGoto, this)
285
- this.scene.on('link-open', this.onLinkOpen, this)
286
- this.scene.on('link-move', this.onLinkMove, this)
287
- this.scene.on('route-page', this.onRoutePage, this)
288
- }
289
-
290
- unbindSceneEvents(scene) {
291
- this.scene.off('goto', this.onLinkGoto, this)
292
- this.scene.off('link-open', this.onLinkOpen, this)
293
- this.scene.off('link-move', this.onLinkMove, this)
294
- this.scene.off('route-page', this.onRoutePage, this)
295
- }
296
-
297
- transientShowButtons(stop) {
298
- var buttons = []
299
- !this.hideNavigation && buttons.push(this.next, this.prev)
300
- !this.hideFullscreen && buttons.push(this.fullscreen)
301
-
302
- if (buttons.length == 0) {
303
- return
304
- }
305
-
306
- if (!this._fade_animations) {
307
- this._fade_animations = buttons
308
- .filter(button => button)
309
- .map(button => {
310
- let animation = button.animate(
311
- [
312
- {
313
- opacity: 1,
314
- easing: 'ease-in'
315
- },
316
- { opacity: 0 }
317
- ],
318
- { delay: 1000, duration: 2000 }
319
- )
320
-
321
- animation.onfinish = () => {
322
- button.setAttribute('hidden', '')
323
- }
324
-
325
- return animation
326
- })
327
- }
328
-
329
- this.forward.length <= 0 ? this.next.setAttribute('hidden', '') : this.next.removeAttribute('hidden')
330
- this.backward.length <= 0 ? this.prev.setAttribute('hidden', '') : this.prev.removeAttribute('hidden')
331
- this.fullscreen && this.fullscreen.removeAttribute('hidden')
332
-
333
- this._fade_animations.forEach(animation => {
334
- animation.cancel()
335
- if (stop) return
336
-
337
- animation.play()
338
- })
339
- }
340
-
341
- /* event handlers */
342
-
343
- onTapNext() {
344
- var scene = this.forward.pop()
345
- if (!scene) return
346
-
347
- if (this.scene) {
348
- this.scene.target = null
349
- /* 원래의 target에 되돌린다. */
350
- this.scene.target = this._oldtarget
351
- this.unbindSceneEvents(this.scene)
352
- this.backward.push(this.scene)
353
- }
354
-
355
- this.setupScene(scene)
356
- }
357
-
358
- onTapPrev() {
359
- var scene = this.backward.pop()
360
- if (!scene) return
361
-
362
- if (this.scene) {
363
- this.scene.target = null
364
- /* 원래의 target에 되돌린다. */
365
- this.scene.target = this._oldtarget
366
- this.unbindSceneEvents(this.scene)
367
- this.forward.push(this.scene)
368
- }
369
-
370
- this.setupScene(scene)
371
- }
372
-
373
- onTapFullscreen() {
374
- togglefullscreen(
375
- this,
376
- () => {
377
- this.requestUpdate()
378
- },
379
- () => {
380
- this.requestUpdate()
381
- }
382
- )
383
- }
384
-
385
- onLinkGoto(targetBoardId, value, fromComponent) {
386
- this.showScene(targetBoardId, fromComponent.data)
387
- }
388
-
389
- onLinkOpen(url, value, fromComponent) {
390
- if (!url) return
391
-
392
- try {
393
- window.open(url)
394
- } catch (ex) {
395
- document.dispatchEvent(
396
- new CustomEvent('notify', {
397
- detail: {
398
- level: 'error',
399
- message: ex,
400
- ex
401
- }
402
- })
403
- )
404
- }
405
- }
406
-
407
- onLinkMove(url, value, fromComponent) {
408
- if (!url) return
409
-
410
- location.href = url
411
- }
412
-
413
- onRoutePage(page) {
414
- if (!page) {
415
- return
416
- }
417
-
418
- history.pushState({}, '', page)
419
- window.dispatchEvent(new Event('popstate'))
420
- }
421
-
422
- async getSceneImageData(base64 = false) {
423
- if (!this.scene) {
424
- return
425
- }
426
-
427
- var { width, height } = this.scene.model
428
- var pixelRatio = window.devicePixelRatio
429
-
430
- // 1. Scene의 바운드에 근거하여, 오프스크린 캔바스를 만든다.
431
- var canvas = document.createElement('canvas')
432
- canvas.width = Number(width)
433
- canvas.height = Number(height)
434
-
435
- var root = this.scene.root
436
- // 2. 모델레이어의 원래 위치와 스케일을 저장한다.
437
- var translate = root.get('translate')
438
- var scale = root.get('scale')
439
-
440
- // 3. 위치와 스케일 기본 설정.
441
- root.set('translate', { x: 0, y: 0 })
442
- root.set('scale', { x: 1 / pixelRatio, y: 1 / pixelRatio })
443
-
444
- // 4. 오프스크린 캔바스의 Context2D를 구한뒤, 모델레이어를 그 위에 그린다.
445
- var context = canvas.getContext('2d')
446
-
447
- root.draw(context)
448
-
449
- root.set('translate', translate)
450
- root.set('scale', scale)
451
-
452
- var data = base64 ? canvas.toDataURL() : context.getImageData(0, 0, width, height).data
453
-
454
- return {
455
- width,
456
- height,
457
- data
458
- }
459
- }
460
-
461
- async printTrick(image) {
462
- var viewTarget
463
- var printTarget
464
-
465
- if (!image) {
466
- image = (await this.getSceneImageData(true)).data
467
- }
468
-
469
- printTarget = document.createElement('img')
470
- printTarget.id = 'target'
471
- printTarget.src = image
472
- printTarget.style.width = '100%'
473
- printTarget.style.height = '100%'
474
-
475
- const x = mql => {
476
- if (mql.matches) {
477
- if (!viewTarget) {
478
- viewTarget = this.renderRoot.getElementById('target')
479
- this.renderRoot.replaceChild(printTarget, viewTarget)
480
- }
481
- } else {
482
- this.renderRoot.replaceChild(viewTarget, printTarget)
483
- printTarget.remove()
484
- mediaQueryList.removeListener(x)
485
- }
486
- }
487
-
488
- if (window.matchMedia) {
489
- var mediaQueryList = window.matchMedia('print')
490
- mediaQueryList.addListener(x)
491
- }
492
- }
493
- }
494
-
495
- customElements.define('board-viewer', BoardViewer)