@operato/scene-table 8.0.0-beta.1 → 8.0.0-beta.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.
@@ -1,727 +0,0 @@
1
- /*
2
- * Copyright © HatioLab Inc. All rights reserved.
3
- */
4
-
5
- import './data-cell'
6
- import './data-list-layout'
7
-
8
- import { Component, ComponentNature, Container, Control, Layout, Model, State, Style } from '@hatiolab/things-scene'
9
-
10
- import {
11
- above,
12
- after,
13
- array,
14
- before,
15
- below,
16
- buildCopiedCell,
17
- buildNewCell,
18
- CLEAR_STYLE,
19
- columnControlHandler,
20
- isBottomMost,
21
- isLeftMost,
22
- isRightMost,
23
- isTopMost,
24
- rowControlHandler,
25
- setCellBorder,
26
- WHERE,
27
- adjustCellWidth,
28
- adjustCellHeight
29
- } from '../helper-functions'
30
-
31
- const NATURE: ComponentNature = {
32
- mutable: false,
33
- resizable: true,
34
- rotatable: true,
35
- properties: [
36
- {
37
- type: 'number',
38
- label: 'columns',
39
- name: 'columns',
40
- property: 'columns'
41
- }
42
- ],
43
- 'value-property': 'data',
44
- help: 'scene/component/data-list'
45
- }
46
-
47
- export default class DataList extends Container {
48
- reflowing: boolean = false
49
-
50
- postrender(context: CanvasRenderingContext2D) {
51
- super.postrender(context)
52
-
53
- // child component들을 다 그린 후에 scrollbar를 표현한다.
54
- if (this.app.isViewMode) {
55
- this.renderScrollbar(context)
56
- }
57
- }
58
-
59
- renderScrollbar(context: CanvasRenderingContext2D) {
60
- var { left, top, width, height } = this.bounds
61
- var { offset = { x: 0, y: 0 }, data } = this.state
62
-
63
- var fullHeight = ((data && data.length) || 0) * (this.heights[0] / this.heights_sum) * height
64
-
65
- if (fullHeight <= height) {
66
- return
67
- }
68
-
69
- var start = (-offset.y / fullHeight) * height
70
- var end = ((-offset.y + height) / fullHeight) * height
71
-
72
- context.strokeStyle = 'gray'
73
- context.lineWidth = 10
74
- context.globalAlpha = 0.3
75
-
76
- context.beginPath()
77
- context.moveTo(left + width - 10, top + start)
78
- context.lineTo(left + width - 10, top + end)
79
-
80
- context.stroke()
81
- }
82
-
83
- created() {
84
- this.set('rows', 2)
85
-
86
- var rows = 1
87
-
88
- var tobeSize = rows * this.columns
89
- var gap = this.size() - tobeSize
90
-
91
- if (this.data) {
92
- this.setCellsData()
93
- }
94
-
95
- if (gap == 0) {
96
- return
97
- } else if (gap > 0) {
98
- let removals = this.components.slice(gap)
99
- this.remove(removals)
100
- } else {
101
- let newbies = []
102
-
103
- for (let i = 0; i < -gap; i++) newbies.push(buildNewCell('data-cell', this.app))
104
-
105
- this.add(newbies)
106
- }
107
-
108
- var widths = this.getState('widths')
109
- var heights = this.getState('heights')
110
-
111
- if (!widths || widths.length < this.columns) this.set('widths', this.widths)
112
- if (!heights || heights.length < rows) this.set('heights', this.heights)
113
- }
114
-
115
- _onwheel(e: WheelEvent) {
116
- e.stopPropagation()
117
-
118
- const { height } = this.bounds
119
- const { offset = { x: 0, y: 0 } } = this.state
120
-
121
- const recordHeight = (this.heights[0] / this.heights.reduce((sum: number, height: number) => sum + height)) * height
122
-
123
- const minX = 0
124
- const minY = this.data && this.data.length ? Math.min(-recordHeight * this.data.length + height, 0) : 0
125
-
126
- if (e.deltaY === 0 && e.deltaX === 0) return
127
-
128
- const x = e.deltaX + offset.x
129
- const y = -e.deltaY + offset.y
130
-
131
- // 계산된 새로운 offset이 경계 내에 있는지 확인
132
- const clampedX = Math.max(Math.min(0, x), minX)
133
- const clampedY = Math.max(Math.min(0, y), minY)
134
-
135
- // offset이 실제로 변경되는 경우에만 preventDefault 호출
136
- if (offset.x !== clampedX || offset.y !== clampedY) {
137
- this.setState({
138
- offset: {
139
- x: clampedX,
140
- y: clampedY
141
- }
142
- })
143
-
144
- e.preventDefault() // 기본 동작 방지 (스크롤 막기)
145
- }
146
- }
147
-
148
- private __START_OFFSET?: { x: number; y: number }
149
- private __START_Y?: number
150
-
151
- _ontouchstart(e: TouchEvent) {
152
- e.stopPropagation()
153
- // e.preventDefault()
154
-
155
- this.__START_OFFSET = this.state.offset || {
156
- x: 0,
157
- y: 0
158
- }
159
- this.__START_Y = e.touches[0].clientY // 터치의 Y 좌표 저장
160
- }
161
-
162
- _ontouchmove(e: TouchEvent) {
163
- e.stopPropagation()
164
-
165
- if (!this.__START_OFFSET) {
166
- return
167
- }
168
-
169
- const { height } = this.bounds
170
- const recordHeight = (this.heights[0] / this.heights.reduce((sum: number, height: number) => sum + height)) * height
171
- const minY = this.data && this.data.length ? Math.min(-recordHeight * this.data.length + height, 0) : 0
172
-
173
- const x = 0
174
- const y = this.__START_OFFSET.y + (e.touches[0].clientY - this.__START_Y!) / this.rootModel.state.scale.y
175
-
176
- // 새로운 offset 계산 후 경계 내로 클램핑
177
- const clampedY = Math.max(Math.min(0, y), minY)
178
-
179
- const offset = this.state.offset || { x: 0, y: 0 }
180
-
181
- // offset이 실제로 변경될 수 있는 경우에만 preventDefault 호출
182
- if (offset.y !== clampedY) {
183
- this.setState('offset', {
184
- x: 0,
185
- y: clampedY
186
- })
187
-
188
- e.preventDefault() // 기본 동작 방지 (스크롤 막기)
189
- }
190
- }
191
-
192
- _ontouchend(e: TouchEvent) {
193
- e.stopPropagation()
194
- // e.preventDefault()
195
-
196
- delete this.__START_OFFSET
197
- delete this.__START_Y
198
- }
199
-
200
- get layout() {
201
- return Layout.get('data-list')
202
- }
203
-
204
- containable(component: Component) {
205
- return component.getState('type') == 'data-cell'
206
- }
207
-
208
- get focusible() {
209
- /* 이 컨테이너에는 컴포넌트를 임의로 추가 및 삭제할 수 없도록 함 */
210
- return false
211
- }
212
-
213
- get widths_sum(): number {
214
- var widths = this.widths
215
- return widths
216
- ? widths
217
- .filter((width: number, i: number) => i < this.columns)
218
- .reduce((sum: number, width: number) => sum + width, 0)
219
- : this.columns
220
- }
221
-
222
- get heights_sum(): number {
223
- var heights = this.heights
224
- return heights ? heights.filter((height, i) => i < this.rows).reduce((sum, height) => sum + height, 0) : this.rows
225
- }
226
-
227
- get widths(): number[] {
228
- var widths = this.getState('widths')
229
-
230
- if (!widths) return array(1, this.columns)
231
-
232
- if (widths.length < this.columns) return widths.concat(array(1, this.columns - widths.length))
233
- else if (widths.length > this.columns) return widths.slice(0, this.columns)
234
-
235
- return widths
236
- }
237
-
238
- get heights(): number[] {
239
- var heights = this.getState('heights')
240
- var rows = 2
241
-
242
- if (!heights) {
243
- return array(1, rows)
244
- }
245
-
246
- if (heights.length < rows) return heights.concat(array(1, rows - heights.length))
247
- else if (heights.length > rows) return heights.slice(0, rows)
248
-
249
- return heights
250
- }
251
-
252
- get columns() {
253
- return Number(this.getState('columns'))
254
- }
255
-
256
- get rows(): number {
257
- /* 1 for first record fields, 1 for rest record fields */
258
- return 2
259
- }
260
-
261
- get nature(): ComponentNature {
262
- return NATURE
263
- }
264
-
265
- get controls(): Control[] {
266
- var widths = this.widths
267
- var heights = this.heights
268
- var inside = this.textBounds
269
-
270
- var width_unit = inside.width / this.widths_sum
271
- var height_unit = inside.height / this.heights_sum
272
-
273
- var x = inside.left
274
- var y = inside.top
275
-
276
- var controls: Control[] = []
277
-
278
- widths.slice(0, this.columns - 1).forEach(width => {
279
- x += width * width_unit
280
- controls.push({
281
- x: x,
282
- y: inside.top,
283
- handler: columnControlHandler
284
- })
285
- })
286
-
287
- heights.slice(0, this.rows - 1).forEach(height => {
288
- y += height * height_unit
289
- controls.push({
290
- x: inside.left,
291
- y: y,
292
- handler: rowControlHandler
293
- })
294
- })
295
-
296
- return controls
297
- }
298
-
299
- onchange(after: State, before: State) {
300
- if ('data' in after) {
301
- this.setCellsData()
302
- }
303
-
304
- if ('columns' in after) {
305
- let { columns } = this
306
-
307
- this.buildCells(columns, Number(before.columns))
308
- }
309
- }
310
-
311
- get eventMap() {
312
- return {
313
- '(self)': {
314
- '(descendant)': {
315
- change: this.oncellchanged
316
- },
317
- '(all)': {
318
- wheel: this._onwheel,
319
- touchstart: this._ontouchstart,
320
- touchmove: this._ontouchmove,
321
- touchend: this._ontouchend
322
- }
323
- }
324
- }
325
- }
326
-
327
- oncellchanged(after: State, before: State, hint: { origin: Component }) {
328
- if (this.reflowing) {
329
- return
330
- }
331
-
332
- if ('width' in after) {
333
- var diff = after.width - before.width
334
- var index = this.components.indexOf(hint.origin)
335
-
336
- if (index !== -1) {
337
- adjustCellWidth(this, index % this.columns, diff)
338
- }
339
- }
340
-
341
- if ('height' in after) {
342
- var diff = after.height - before.height
343
- var index = this.components.indexOf(hint.origin)
344
-
345
- if (index !== -1) {
346
- adjustCellHeight(this, 0, diff)
347
- }
348
- }
349
- }
350
-
351
- reflow() {
352
- this.reflowing = true
353
- super.reflow()
354
- this.reflowing = false
355
- }
356
-
357
- setCellsData() {
358
- if (!this.app.isViewMode) {
359
- return
360
- }
361
-
362
- var data = this.data || []
363
- if (!(data instanceof Array)) {
364
- data = [data]
365
- }
366
-
367
- /* 기존의 레코드를 모두 삭제 (템플릿 레코드만 남긴다.) */
368
- this.remove(this.components.slice(this.columns), true /* silent - do not let root-container refreshMappings */)
369
-
370
- /* template 레코드의 데이터를 클리어시킨다 */
371
- var templates = this.getCellsByRow(0)
372
- templates.forEach(field => {
373
- field.data = ''
374
- })
375
-
376
- /* 데이터의 크기만큼 새로운 레코드를 만든다 */
377
- if (data.length > 1) {
378
- let newbies: Component[] = []
379
-
380
- for (let i = 1; i < data.length; i++) {
381
- newbies = newbies.concat(
382
- templates.map(field => {
383
- const { refid, id, data, ...org } = field.model
384
- return Model.compile(org, this.app)
385
- })
386
- )
387
- }
388
-
389
- this.add(newbies, true /* silent - do not let root-container refreshMappings */)
390
- }
391
-
392
- data.forEach((record: any[], idx: number) => {
393
- let data = {
394
- _idx: idx,
395
- ...record
396
- }
397
- let row = this.getCellsByRow(idx)
398
- row.forEach(field => {
399
- // @ts-ignore TODO - remove this comment later
400
- field.value = data
401
- })
402
- })
403
-
404
- /* 스크롤 위치를 조정 */
405
- var { height } = this.bounds
406
- var fullHeight = data.length * (this.heights[0] / this.heights_sum) * height
407
-
408
- if (fullHeight <= height) {
409
- // 데이터가 화면보다 작을 경우, 스크롤 위치를 최상단으로 이동
410
- this.setState({
411
- offset: {
412
- x: 0,
413
- y: 0
414
- }
415
- })
416
- } else {
417
- // 데이터가 화면보다 클 경우, 현재 스크롤 위치를 보정
418
- var minY = Math.min(-fullHeight + height, 0)
419
- var { offset = { x: 0, y: 0 } } = this.state
420
-
421
- this.setState({
422
- offset: {
423
- x: 0,
424
- y: Math.max(minY, offset.y)
425
- }
426
- })
427
- }
428
- }
429
-
430
- setCellsStyle(cells: Component[], style: Style, where: WHERE, clearBefore = true) {
431
- var components = this.components
432
- var total = components.length
433
- var columns = this.getState('columns')
434
-
435
- var indices = cells.map(cell => components.indexOf(cell))
436
- indices.forEach(i => {
437
- var cell = components[i]
438
- clearBefore && setCellBorder(cell, CLEAR_STYLE, 'all')
439
-
440
- switch (where) {
441
- case 'all':
442
- setCellBorder(cell, style, where)
443
-
444
- if (isLeftMost(total, columns, indices, i)) setCellBorder(components[before(columns, i)], style, 'right')
445
- if (isRightMost(total, columns, indices, i)) setCellBorder(components[after(columns, i)], style, 'left')
446
- if (isTopMost(total, columns, indices, i)) setCellBorder(components[above(columns, i)], style, 'bottom')
447
- if (isBottomMost(total, columns, indices, i)) setCellBorder(components[below(columns, i)], style, 'top')
448
- break
449
- case 'in':
450
- if (!isLeftMost(total, columns, indices, i)) {
451
- setCellBorder(cell, style, 'left')
452
- }
453
- if (!isRightMost(total, columns, indices, i)) {
454
- setCellBorder(cell, style, 'right')
455
- }
456
- if (!isTopMost(total, columns, indices, i)) {
457
- setCellBorder(cell, style, 'top')
458
- }
459
- if (!isBottomMost(total, columns, indices, i)) {
460
- setCellBorder(cell, style, 'bottom')
461
- }
462
- break
463
- case 'out':
464
- if (isLeftMost(total, columns, indices, i)) {
465
- setCellBorder(cell, style, 'left')
466
- setCellBorder(components[before(columns, i)], style, 'right')
467
- }
468
- if (isRightMost(total, columns, indices, i)) {
469
- setCellBorder(cell, style, 'right')
470
- setCellBorder(components[after(columns, i)], style, 'left')
471
- }
472
- if (isTopMost(total, columns, indices, i)) {
473
- setCellBorder(cell, style, 'top')
474
- setCellBorder(components[above(columns, i)], style, 'bottom')
475
- }
476
- if (isBottomMost(total, columns, indices, i)) {
477
- setCellBorder(cell, style, 'bottom')
478
- setCellBorder(components[below(columns, i)], style, 'top')
479
- }
480
- break
481
- case 'left':
482
- if (isLeftMost(total, columns, indices, i)) {
483
- setCellBorder(cell, style, 'left')
484
- setCellBorder(components[before(columns, i)], style, 'right')
485
- }
486
- break
487
- case 'right':
488
- if (isRightMost(total, columns, indices, i)) {
489
- setCellBorder(cell, style, 'right')
490
- setCellBorder(components[after(columns, i)], style, 'left')
491
- }
492
- break
493
- case 'center':
494
- if (!isLeftMost(total, columns, indices, i)) {
495
- setCellBorder(cell, style, 'left')
496
- }
497
- if (!isRightMost(total, columns, indices, i)) {
498
- setCellBorder(cell, style, 'right')
499
- }
500
- break
501
- case 'middle':
502
- if (!isTopMost(total, columns, indices, i)) {
503
- setCellBorder(cell, style, 'top')
504
- }
505
- if (!isBottomMost(total, columns, indices, i)) {
506
- setCellBorder(cell, style, 'bottom')
507
- }
508
- break
509
- case 'top':
510
- if (isTopMost(total, columns, indices, i)) {
511
- setCellBorder(cell, style, 'top')
512
- setCellBorder(components[above(columns, i)], style, 'bottom')
513
- }
514
- break
515
- case 'bottom':
516
- if (isBottomMost(total, columns, indices, i)) {
517
- setCellBorder(cell, style, 'bottom')
518
- setCellBorder(components[below(columns, i)], style, 'top')
519
- }
520
- break
521
- case 'clear':
522
- setCellBorder(cell, CLEAR_STYLE, 'all')
523
-
524
- if (isLeftMost(total, columns, indices, i))
525
- setCellBorder(components[before(columns, i)], CLEAR_STYLE, 'right')
526
- if (isRightMost(total, columns, indices, i)) setCellBorder(components[after(columns, i)], CLEAR_STYLE, 'left')
527
- if (isTopMost(total, columns, indices, i)) setCellBorder(components[above(columns, i)], CLEAR_STYLE, 'bottom')
528
- if (isBottomMost(total, columns, indices, i)) setCellBorder(components[below(columns, i)], CLEAR_STYLE, 'top')
529
- }
530
- })
531
- }
532
-
533
- buildCells(newcolumns: number, oldcolumns: number) {
534
- var minrows = 1
535
-
536
- if (newcolumns > oldcolumns) {
537
- for (let r = 0; r < minrows; r++) {
538
- for (let c = oldcolumns; c < newcolumns; c++) {
539
- this.insertComponentAt(buildNewCell('data-cell', this.app), r * newcolumns + c)
540
- }
541
- }
542
- } else if (newcolumns < oldcolumns) {
543
- let removals = []
544
-
545
- for (let r = 0; r < minrows; r++) {
546
- for (let c = newcolumns; c < oldcolumns; c++) {
547
- removals.push(this.components[r * oldcolumns + c])
548
- }
549
- }
550
-
551
- this.remove(removals)
552
- }
553
-
554
- this.set({
555
- widths: this.widths,
556
- heights: this.heights
557
- })
558
-
559
- this.setCellsData()
560
- }
561
-
562
- getRowColumn(cell: Component) {
563
- var idx = this.components.indexOf(cell)
564
-
565
- return {
566
- column: idx % this.columns,
567
- row: Math.floor(idx / this.columns)
568
- }
569
- }
570
-
571
- getCellsByRow(row: number): Component[] {
572
- return this.components.slice(row * this.columns, (row + 1) * this.columns)
573
- }
574
-
575
- getCellsByColumn(column: number): Component[] {
576
- var cells = []
577
- for (let i = 0; i < this.rows; i++) cells.push(this.components[this.columns * i + column])
578
-
579
- return cells
580
- }
581
-
582
- mergeCells(cells: Component[]) {}
583
-
584
- splitCells(cells: Component[]) {}
585
-
586
- deleteRows(cells: Component[]) {}
587
-
588
- deleteColumns(cells: Component[]) {
589
- // 만약 선택한 셀이 병합된 셀이라면 삭제하지 않는다.
590
- // if (cells[0].merged == true) return false
591
-
592
- // 먼저 cells 위치의 열을 구한다.
593
- let columns: number[] = []
594
- cells.forEach(cell => {
595
- let column = this.getRowColumn(cell).column
596
- if (-1 == columns.indexOf(column)) columns.push(column)
597
- })
598
- columns.sort((a, b) => {
599
- return a - b
600
- })
601
- columns.reverse()
602
-
603
- columns.forEach(column => {
604
- var widths = this.widths.slice()
605
- this.remove(this.getCellsByColumn(column))
606
-
607
- widths.splice(column, 1)
608
- this.model.columns -= 1 // 고의적으로, change 이벤트가 발생하지 않도록 set(..)을 사용하지 않음.
609
- this.set('widths', widths)
610
- })
611
- }
612
-
613
- insertCellsAbove(cells: Component[]) {}
614
-
615
- insertCellsBelow(cells: Component[]) {}
616
-
617
- insertCellsLeft(cells: Component[]) {
618
- // 먼저 cells 위치의 열을 구한다.
619
- let columns: number[] = []
620
- cells.forEach(cell => {
621
- let column = this.getRowColumn(cell).column
622
- if (-1 == columns.indexOf(column)) columns.push(column)
623
- })
624
- columns.sort((a, b) => {
625
- return a - b
626
- })
627
- columns.reverse()
628
-
629
- // 열 2개 이상은 추가 안함. 임시로 막아놓음
630
- if (columns.length >= 2) return false
631
- let insertionColumnPosition = columns[0]
632
- let newbieColumnWidths: number[] = []
633
- let newbieCells: Component[] = []
634
- columns.forEach(column => {
635
- for (let i = 0; i < this.rows; i++)
636
- newbieCells.push(buildCopiedCell(this.components[column + this.columns * i].model, this.app))
637
- newbieColumnWidths.push(this.widths[column])
638
-
639
- let increasedColumns = this.columns
640
- let index = this.rows
641
- newbieCells.reverse().forEach(cell => {
642
- if (index == 0) {
643
- index = this.rows
644
- increasedColumns++
645
- }
646
-
647
- index--
648
- this.insertComponentAt(cell, insertionColumnPosition + index * increasedColumns)
649
- })
650
-
651
- let widths = this.widths.slice()
652
- this.model.columns += columns.length // 고의적으로, change 이벤트가 발생하지 않도록 set(..)을 사용하지 않음.
653
-
654
- widths.splice(insertionColumnPosition, 0, ...newbieColumnWidths)
655
-
656
- this.set('widths', widths)
657
- })
658
- }
659
-
660
- insertCellsRight(cells: Component[]) {
661
- // 먼저 cells 위치의 열을 구한다.
662
- let columns: number[] = []
663
- cells.forEach(cell => {
664
- let column = this.getRowColumn(cell).column
665
- if (-1 == columns.indexOf(column)) columns.push(column)
666
- })
667
- columns.sort((a, b) => {
668
- return a - b
669
- })
670
- columns.reverse()
671
- // 열 2개 이상은 추가 안함. 임시로 막아놓음
672
- if (columns.length >= 2) return false
673
- let insertionColumnPosition = columns[columns.length - 1] + 1
674
- let newbieColumnWidths: number[] = []
675
- let newbieCells: Component[] = []
676
- columns.forEach(column => {
677
- for (let i = 0; i < this.rows; i++)
678
- newbieCells.push(buildCopiedCell(this.components[column + this.columns * i].model, this.app))
679
- newbieColumnWidths.push(this.widths[column])
680
-
681
- let increasedColumns = this.columns
682
- let index = this.rows
683
- newbieCells.reverse().forEach(cell => {
684
- if (index == 0) {
685
- index = this.rows
686
- increasedColumns++
687
- }
688
-
689
- index--
690
- this.insertComponentAt(cell, insertionColumnPosition + index * increasedColumns)
691
- })
692
-
693
- let widths = this.widths.slice()
694
- this.model.columns += columns.length // 고의적으로, change 이벤트가 발생하지 않도록 set(..)을 사용하지 않음.
695
-
696
- widths.splice(insertionColumnPosition, 0, ...newbieColumnWidths)
697
-
698
- this.set('widths', widths)
699
- })
700
- }
701
-
702
- distributeHorizontal(cells: Component[]) {
703
- var columns: number[] = []
704
-
705
- cells.forEach(cell => {
706
- let rowcolumn = this.getRowColumn(cell)
707
-
708
- if (-1 == columns.indexOf(rowcolumn.column)) columns.push(rowcolumn.column)
709
- })
710
-
711
- var sum = columns.reduce((sum, column) => {
712
- return sum + this.widths[column]
713
- }, 0)
714
-
715
- var newval = Math.round((sum / columns.length) * 100) / 100
716
- var widths = this.widths.slice()
717
- columns.forEach(column => {
718
- widths[column] = newval
719
- })
720
-
721
- this.set('widths', widths)
722
- }
723
-
724
- distributeVertical(cells: Component[]) {}
725
- }
726
-
727
- Component.register('data-list', DataList)