@operato/board 8.0.0-alpha.4 → 8.0.0-alpha.41

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@operato/board",
3
- "version": "8.0.0-alpha.4",
3
+ "version": "8.0.0-alpha.41",
4
4
  "description": "Webcomponent for board following open-wc recommendations",
5
5
  "author": "heartyoh",
6
6
  "main": "dist/src/index.js",
@@ -26,6 +26,7 @@
26
26
  "./ox-board-list.js": "./dist/src/ox-board-list.js",
27
27
  "./ox-board-template-list.js": "./dist/src/ox-board-template-list.js",
28
28
  "./ox-board-viewer.js": "./dist/src/ox-board-viewer.js",
29
+ "./ox-board-template-viewer.js": "./dist/src/ox-board-template-viewer.js",
29
30
  "./ox-board-player.js": "./dist/src/ox-board-player.js",
30
31
  "./ox-board-modeller.js": "./dist/src/ox-board-modeller.js",
31
32
  "./ox-editor-board-selector.js": "./dist/src/ox-editor-board-selector.js",
@@ -57,6 +58,9 @@
57
58
  "ox-board-viewer.js": [
58
59
  "./dist/src/ox-board-viewer.d.ts"
59
60
  ],
61
+ "ox-board-template-viewer.js": [
62
+ "./dist/src/ox-board-template-viewer.d.ts"
63
+ ],
60
64
  "ox-board-player.js": [
61
65
  "./dist/src/ox-board-player.d.ts"
62
66
  ],
@@ -95,18 +99,18 @@
95
99
  "dependencies": {
96
100
  "@material/web": "^2.0.0",
97
101
  "@open-wc/scoped-elements": "^2.1.3",
98
- "@operato/app": "^8.0.0-alpha.4",
99
- "@operato/data-grist": "^8.0.0-alpha.4",
100
- "@operato/font": "^8.0.0-alpha.4",
101
- "@operato/graphql": "^8.0.0-alpha.0",
102
- "@operato/i18n": "^8.0.0-alpha.0",
103
- "@operato/input": "^8.0.0-alpha.4",
104
- "@operato/layout": "^8.0.0-alpha.4",
105
- "@operato/markdown": "^8.0.0-alpha.4",
106
- "@operato/popup": "^8.0.0-alpha.4",
107
- "@operato/property-editor": "^8.0.0-alpha.4",
108
- "@operato/styles": "^8.0.0-alpha.4",
109
- "@operato/utils": "^8.0.0-alpha.0",
102
+ "@operato/app": "^8.0.0-alpha.41",
103
+ "@operato/data-grist": "^8.0.0-alpha.41",
104
+ "@operato/font": "^8.0.0-alpha.41",
105
+ "@operato/graphql": "^8.0.0-alpha.37",
106
+ "@operato/i18n": "^8.0.0-alpha.37",
107
+ "@operato/input": "^8.0.0-alpha.41",
108
+ "@operato/layout": "^8.0.0-alpha.41",
109
+ "@operato/markdown": "^8.0.0-alpha.37",
110
+ "@operato/popup": "^8.0.0-alpha.41",
111
+ "@operato/property-editor": "^8.0.0-alpha.41",
112
+ "@operato/styles": "^8.0.0-alpha.37",
113
+ "@operato/utils": "^8.0.0-alpha.37",
110
114
  "@polymer/paper-dropdown-menu": "^3.2.0",
111
115
  "@types/file-saver": "^2.0.4",
112
116
  "@types/sortablejs": "^1.10.7",
@@ -118,23 +122,23 @@
118
122
  "xlsx": "^0.18.5"
119
123
  },
120
124
  "devDependencies": {
121
- "@custom-elements-manifest/analyzer": "^0.9.2",
125
+ "@custom-elements-manifest/analyzer": "^0.10.0",
122
126
  "@hatiolab/prettier-config": "^1.0.0",
123
127
  "@hatiolab/things-scene": "^8.0.0-alpha",
124
128
  "@open-wc/eslint-config": "^12.0.3",
125
- "@open-wc/testing": "^3.1.6",
126
- "@rollup/plugin-image": "^2.1.1",
127
- "@rollup/plugin-json": "^4.1.0",
129
+ "@open-wc/testing": "^4.0.0",
130
+ "@rollup/plugin-image": "^3.0.3",
131
+ "@rollup/plugin-json": "^6.1.0",
128
132
  "@types/lodash-es": "^4.17.6",
129
133
  "@types/w3c-web-usb": "^1.0.5",
130
- "@typescript-eslint/eslint-plugin": "^7.0.1",
131
- "@typescript-eslint/parser": "^7.0.1",
132
- "@web/dev-server": "^0.3.0",
134
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
135
+ "@typescript-eslint/parser": "^8.0.0",
136
+ "@web/dev-server": "^0.4.0",
133
137
  "@web/dev-server-storybook": "^2.0.1",
134
- "@web/test-runner": "^0.18.0",
138
+ "@web/test-runner": "^0.19.0",
135
139
  "@webcomponents/scoped-custom-element-registry": "^0.0.9",
136
- "concurrently": "^8.0.1",
137
- "eslint": "^8.39.0",
140
+ "concurrently": "^9.0.0",
141
+ "eslint": "^9.0.0",
138
142
  "eslint-config-prettier": "^9.1.0",
139
143
  "husky": "^9.0.11",
140
144
  "lint-staged": "^15.2.2",
@@ -155,5 +159,5 @@
155
159
  "prettier --write"
156
160
  ]
157
161
  },
158
- "gitHead": "ac097b448ea96721b3418132e92988afdf764519"
162
+ "gitHead": "0c2adcc994cd105055c9355810fa9c7c8196ca41"
159
163
  }
package/src/index.ts CHANGED
@@ -7,3 +7,4 @@ export * from './ox-board-viewer.js'
7
7
  export * from './ox-board-player.js'
8
8
  export * from './ox-board-modeller.js'
9
9
  export * from './ox-board-list.js'
10
+ export * from './ox-board-template-viewer.js'
@@ -301,7 +301,9 @@ export class BoardPlayer extends LitElement {
301
301
  }
302
302
 
303
303
  async start() {
304
- if (!this.boards || this.boards.length == 0) return
304
+ if (!this.boards || this.boards.length == 0) {
305
+ return
306
+ }
305
307
 
306
308
  this.started = true
307
309
  this.playing = true
@@ -0,0 +1,198 @@
1
+ import '@material/web/icon/icon.js'
2
+ import '@material/web/fab/fab.js'
3
+
4
+ import { css, html, LitElement, PropertyValues } from 'lit'
5
+ import { customElement, property, query, state } from 'lit/decorators.js'
6
+
7
+ import { create, SCENE_MODE } from '@hatiolab/things-scene'
8
+ import { ScrollbarStyles } from '@operato/styles'
9
+
10
+ @customElement('ox-board-template-viewer')
11
+ export class BoardTemplateViewer extends LitElement {
12
+ static styles = [
13
+ ScrollbarStyles,
14
+ css`
15
+ :host {
16
+ display: flex;
17
+ flex-direction: column;
18
+
19
+ position: relative;
20
+
21
+ width: 100%; /* 전체화면보기를 위해서 필요함. */
22
+ overflow: hidden;
23
+ }
24
+
25
+ #target {
26
+ flex: 1;
27
+
28
+ width: 100%; /* 전체화면보기를 위해서 필요함. */
29
+ height: 100%;
30
+
31
+ outline: 0;
32
+ }
33
+
34
+ @media print {
35
+ md-fab,
36
+ md-icon {
37
+ display: none;
38
+ }
39
+ }
40
+ `
41
+ ]
42
+
43
+ @property({ type: Object }) boardTemplate: any = {}
44
+
45
+ @state() _scene: any = null
46
+
47
+ @query('#target') _target!: HTMLElement
48
+
49
+ render() {
50
+ return html` <div id="target"></div> `
51
+ }
52
+
53
+ private resizeHandler = () => {
54
+ this._scene && this._scene.fit()
55
+ }
56
+
57
+ connectedCallback() {
58
+ super.connectedCallback()
59
+
60
+ window.addEventListener('resize', this.resizeHandler)
61
+
62
+ window.addEventListener('orientationchange', this.resizeHandler)
63
+ }
64
+
65
+ disconnectedCallback() {
66
+ window.removeEventListener('resize', this.resizeHandler)
67
+ window.removeEventListener('orientationchange', this.resizeHandler)
68
+
69
+ super.disconnectedCallback()
70
+
71
+ this.closeScene()
72
+ }
73
+
74
+ updated(changes: PropertyValues<this>) {
75
+ if (changes.has('boardTemplate')) {
76
+ this.closeScene()
77
+
78
+ if (this.boardTemplate && this.boardTemplate.id) {
79
+ this.initScene()
80
+ }
81
+ }
82
+ }
83
+
84
+ initScene() {
85
+ if (!this.boardTemplate || !this.boardTemplate.id) return
86
+
87
+ this._scene = create({
88
+ model: {
89
+ ...this.boardTemplate.model
90
+ },
91
+ mode: SCENE_MODE.VIEW
92
+ })
93
+
94
+ this.setupScene({ id: this.boardTemplate.id, scene: this._scene })
95
+ }
96
+
97
+ closeScene() {
98
+ if (this._scene) {
99
+ this._scene.target = null
100
+ this._scene.release && this._scene.release()
101
+
102
+ this._scene = null
103
+ }
104
+ }
105
+
106
+ releaseScene() {
107
+ this.closeScene()
108
+ }
109
+
110
+ setupScene({ id, scene }: { id: string; scene: any }) {
111
+ this._scene = scene
112
+
113
+ const backgroundColor = this._scene?.root.state.fillStyle
114
+ if (typeof backgroundColor === 'string') {
115
+ this.style.backgroundColor = backgroundColor
116
+ } else {
117
+ this.style.backgroundColor = 'initial'
118
+ }
119
+
120
+ /* scene의 기존 target을 보관한다. */
121
+
122
+ this._scene.fit(this._scene.fitMode)
123
+ this._scene.target = this._target
124
+ }
125
+
126
+ /* event handlers */
127
+ async getSceneImageData(base64 = false) {
128
+ if (!this._scene) {
129
+ return
130
+ }
131
+
132
+ var { width, height } = this._scene.model
133
+ var pixelRatio = window.devicePixelRatio
134
+
135
+ // 1. Scene의 바운드에 근거하여, 오프스크린 캔바스를 만든다.
136
+ var canvas = document.createElement('canvas')
137
+ canvas.width = Number(width)
138
+ canvas.height = Number(height)
139
+
140
+ var root = this._scene.root
141
+ // 2. 모델레이어의 원래 위치와 스케일을 저장한다.
142
+ var translate = root.get('translate')
143
+ var scale = root.get('scale')
144
+
145
+ // 3. 위치와 스케일 기본 설정.
146
+ root.set('translate', { x: 0, y: 0 })
147
+ root.set('scale', { x: 1 / pixelRatio, y: 1 / pixelRatio })
148
+
149
+ // 4. 오프스크린 캔바스의 Context2D를 구한뒤, 모델레이어를 그 위에 그린다.
150
+ var context = canvas.getContext('2d')
151
+
152
+ root.draw(context)
153
+
154
+ root.set('translate', translate)
155
+ root.set('scale', scale)
156
+
157
+ var data = base64 ? canvas.toDataURL() : context!.getImageData(0, 0, width, height).data
158
+
159
+ return {
160
+ width,
161
+ height,
162
+ data
163
+ }
164
+ }
165
+
166
+ async printTrick(image: string) {
167
+ var viewTarget: HTMLElement | null = null
168
+ var printTarget: HTMLImageElement | null = null
169
+
170
+ if (!image) {
171
+ image = (await this.getSceneImageData(true))?.data as string
172
+ }
173
+
174
+ printTarget = document.createElement('img')
175
+ printTarget.id = 'target'
176
+ printTarget.src = image
177
+ printTarget.style.width = '100%'
178
+ printTarget.style.height = '100%'
179
+
180
+ const x = (mql: MediaQueryListEvent) => {
181
+ if (mql.matches) {
182
+ if (!viewTarget) {
183
+ viewTarget = (this.renderRoot as ShadowRoot)!.getElementById('target')
184
+ this.renderRoot.replaceChild(printTarget!, viewTarget!)
185
+ }
186
+ } else {
187
+ this.renderRoot.replaceChild(viewTarget!, printTarget!)
188
+ printTarget!.remove()
189
+ mediaQueryList.removeEventListener('change', x)
190
+ }
191
+ }
192
+
193
+ if (window.matchMedia) {
194
+ var mediaQueryList = window.matchMedia('print')
195
+ mediaQueryList.addEventListener('change', x)
196
+ }
197
+ }
198
+ }
@@ -305,7 +305,7 @@ export class BoardViewer extends LitElement {
305
305
  this.transientShowButtons()
306
306
  }
307
307
 
308
- setupScene({ id, scene }: { id: string; scene: any }) {
308
+ setupScene({ id, scene }: { id: string; scene: any }, history?: -1 | 0 | 1) {
309
309
  this._scene = scene
310
310
 
311
311
  const backgroundColor = this._scene?.root.state.fillStyle
@@ -332,6 +332,14 @@ export class BoardViewer extends LitElement {
332
332
  this.bindSceneEvents()
333
333
 
334
334
  this.transientShowButtons()
335
+
336
+ if (typeof history !== 'undefined' /* && document.querySelector('things-app') */) {
337
+ const currentUrl = new URL(window.location.href)
338
+ const paths = currentUrl.pathname.split('/')
339
+
340
+ paths[paths.length - 1] = this.currentBoardId
341
+ window.history.replaceState({}, '', `${currentUrl.origin}${paths.join('/')}${currentUrl.search}`)
342
+ }
335
343
  }
336
344
 
337
345
  async showScene(boardId: string, bindingData?: any) {
@@ -360,7 +368,7 @@ export class BoardViewer extends LitElement {
360
368
  /* forward를 비운다. */
361
369
  this._forward = []
362
370
 
363
- this.setupScene({ id: boardId, scene })
371
+ this.setupScene({ id: boardId, scene }, 0)
364
372
 
365
373
  if (bindingData) {
366
374
  scene.data = bindingData
@@ -465,10 +473,10 @@ export class BoardViewer extends LitElement {
465
473
  /* 원래의 target에 되돌린다. */
466
474
  this._scene.target = this._oldtarget
467
475
  this.unbindSceneEvents(this._scene)
468
- this._backward.push({ id: id!, scene: this._scene })
476
+ this._backward.push({ id: this.currentBoardId!, scene: this._scene })
469
477
  }
470
478
 
471
- this.setupScene({ id: id!, scene })
479
+ this.setupScene({ id: id!, scene }, 1)
472
480
  }
473
481
 
474
482
  onTapPrev() {
@@ -480,10 +488,10 @@ export class BoardViewer extends LitElement {
480
488
  /* 원래의 target에 되돌린다. */
481
489
  this._scene.target = this._oldtarget
482
490
  this.unbindSceneEvents(this._scene)
483
- this._forward.push({ id: id!, scene: this._scene })
491
+ this._forward.push({ id: this.currentBoardId!, scene: this._scene })
484
492
  }
485
493
 
486
- this.setupScene({ id: id!, scene })
494
+ this.setupScene({ id: id!, scene }, -1)
487
495
  }
488
496
 
489
497
  onTapFullscreen() {
@@ -81,6 +81,8 @@ class BoardWrapper extends LitElement {
81
81
  }
82
82
 
83
83
  _onBoardChanged() {
84
+ this.closeScene()
85
+
84
86
  this.initScene()
85
87
  }
86
88
 
@@ -138,7 +140,7 @@ class BoardWrapper extends LitElement {
138
140
  this._scene.target = this._targetEl
139
141
 
140
142
  requestAnimationFrame(() => {
141
- if (this._scene.target.offsetWidth) {
143
+ if (this._scene?.target.offsetWidth) {
142
144
  this._scene.fit()
143
145
  }
144
146
  })
@@ -1,3 +1,4 @@
1
+ import '@operato/input/ox-input-search.js'
1
2
  import './ox-board-creation-card'
2
3
 
3
4
  import gql from 'graphql-tag'
@@ -9,6 +10,7 @@ import { i18next, localize } from '@operato/i18n'
9
10
  import { ScrollbarStyles } from '@operato/styles'
10
11
  import InfiniteScrollable from '@operato/utils/mixins/infinite-scrollable.js'
11
12
  import { InheritedValueType } from '@operato/shell'
13
+ import { adjustFilters } from '@operato/utils'
12
14
 
13
15
  const FETCH_BOARD_LIST_GQL = gql`
14
16
  query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!], $inherited: InheritedValueType) {
@@ -97,7 +99,7 @@ export class BoardSelector extends InfiniteScrollable(localize(i18next)(LitEleme
97
99
  .card > .name {
98
100
  color: var(--md-sys-color-on-secondary);
99
101
  background-color: var(--md-sys-color-secondary);
100
- opacity: 0.8;
102
+ opacity: 0.9;
101
103
  margin-top: -35px;
102
104
  width: 100%;
103
105
  font-weight: bolder;
@@ -106,12 +108,11 @@ export class BoardSelector extends InfiniteScrollable(localize(i18next)(LitEleme
106
108
  }
107
109
 
108
110
  .card > .description {
109
- color: var(--md-sys-color-on-surface);
110
- background-color: var(--md-sys-color-surface);
111
+ color: var(--md-sys-color-on-secondary);
112
+ background-color: var(--md-sys-color-secondary);
111
113
  width: 100%;
112
- min-height: 15px;
114
+ min-height: 16px;
113
115
  font-size: 0.6rem;
114
- color: #fff;
115
116
  text-indent: 7px;
116
117
  }
117
118
  .card img {
@@ -120,12 +121,22 @@ export class BoardSelector extends InfiniteScrollable(localize(i18next)(LitEleme
120
121
  }
121
122
 
122
123
  #filter {
124
+ display: flex;
125
+ align-items: center;
123
126
  padding: var(--popup-content-padding);
124
127
  color: var(--md-sys-color-on-surface-variant);
125
128
  background-color: var(--md-sys-color-surface-variant);
126
129
  box-shadow: var(--box-shadow);
127
130
  }
128
131
 
132
+ #filter > ox-input-search {
133
+ margin-right: auto;
134
+ border: var(--md-sys-color-primary) 1px solid;
135
+ padding: var(--spacing-small) var(--spacing-small);
136
+ border-radius: 999px;
137
+ color: var(--md-sys-color-primary);
138
+ }
139
+
129
140
  #filter > div {
130
141
  float: right;
131
142
  margin-left: 10px;
@@ -145,6 +156,7 @@ export class BoardSelector extends InfiniteScrollable(localize(i18next)(LitEleme
145
156
  @property({ type: Array }) groups: { id: string; name: string; description: string }[] = []
146
157
  @property({ type: Array }) boards: any[] = []
147
158
  @property({ type: String }) group?: string
159
+ @property({ type: String }) search?: string
148
160
  @property({ type: String }) inherited?: InheritedValueType = InheritedValueType.Include
149
161
  @property({ type: Boolean }) creatable: boolean = false
150
162
  @property({ type: String }) value?: string
@@ -161,6 +173,11 @@ export class BoardSelector extends InfiniteScrollable(localize(i18next)(LitEleme
161
173
  render() {
162
174
  return html`
163
175
  <div id="filter">
176
+ <ox-input-search
177
+ @change=${(e: Event) => {
178
+ this.search = (e.currentTarget as any)?.value
179
+ }}
180
+ ></ox-input-search>
164
181
  <div>
165
182
  <label for="group">Group</label>
166
183
  <select
@@ -233,7 +250,7 @@ export class BoardSelector extends InfiniteScrollable(localize(i18next)(LitEleme
233
250
  }
234
251
 
235
252
  async updated(changed: PropertyValues<this>) {
236
- if (changed.has('group') || changed.has('inherited')) {
253
+ if (changed.has('group') || changed.has('inherited') || changed.has('search')) {
237
254
  this.refreshBoards()
238
255
  }
239
256
  }
@@ -294,12 +311,29 @@ export class BoardSelector extends InfiniteScrollable(localize(i18next)(LitEleme
294
311
  page
295
312
  }
296
313
 
297
- if (this.group)
314
+ if (this.group) {
298
315
  filters.push({
299
316
  name: 'groupId',
300
317
  operator: 'eq',
301
318
  value: this.group
302
319
  })
320
+ }
321
+
322
+ if (this.search) {
323
+ const value = `%${this.search.trim()}%`
324
+
325
+ filters.push({
326
+ name: 'name',
327
+ operator: 'search',
328
+ value: this.search
329
+ })
330
+
331
+ filters.push({
332
+ name: 'description',
333
+ operator: 'search',
334
+ value: this.search
335
+ })
336
+ }
303
337
 
304
338
  var variables = {
305
339
  filters,
@@ -310,7 +344,6 @@ export class BoardSelector extends InfiniteScrollable(localize(i18next)(LitEleme
310
344
 
311
345
  var boardListResponse = await client.query({
312
346
  query: FETCH_BOARD_LIST_GQL,
313
- context: gqlContext(),
314
347
  variables
315
348
  })
316
349