@operato/scene-storage 10.0.0-beta.40 → 10.0.0-beta.42
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/CHANGELOG.md +29 -0
- package/MIGRATION-plan-a-slot-api.md +266 -0
- package/PLAN-A-rack-as-slot-holder.md +164 -0
- package/dist/box.js +18 -0
- package/dist/box.js.map +1 -1
- package/dist/crane-3d.d.ts +47 -2
- package/dist/crane-3d.js +246 -89
- package/dist/crane-3d.js.map +1 -1
- package/dist/crane.d.ts +96 -12
- package/dist/crane.js +395 -100
- package/dist/crane.js.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/pallet.d.ts +15 -0
- package/dist/pallet.js +38 -2
- package/dist/pallet.js.map +1 -1
- package/dist/parcel-3d.js +22 -18
- package/dist/parcel-3d.js.map +1 -1
- package/dist/parcel.d.ts +4 -3
- package/dist/parcel.js +24 -5
- package/dist/parcel.js.map +1 -1
- package/dist/rack-grid-3d.d.ts +18 -7
- package/dist/rack-grid-3d.js +372 -69
- package/dist/rack-grid-3d.js.map +1 -1
- package/dist/rack-grid-cell.d.ts +21 -72
- package/dist/rack-grid-cell.js +147 -243
- package/dist/rack-grid-cell.js.map +1 -1
- package/dist/rack-grid.d.ts +277 -56
- package/dist/rack-grid.js +1230 -695
- package/dist/rack-grid.js.map +1 -1
- package/dist/rack-materials.d.ts +9 -0
- package/dist/rack-materials.js +55 -0
- package/dist/rack-materials.js.map +1 -0
- package/dist/storage-rack-3d.d.ts +15 -0
- package/dist/storage-rack-3d.js +165 -29
- package/dist/storage-rack-3d.js.map +1 -1
- package/dist/storage-rack.d.ts +253 -32
- package/dist/storage-rack.js +726 -66
- package/dist/storage-rack.js.map +1 -1
- package/package.json +3 -3
- package/src/box.ts +18 -0
- package/src/crane-3d.ts +258 -93
- package/src/crane.ts +445 -110
- package/src/index.ts +3 -4
- package/src/pallet.ts +50 -1
- package/src/parcel-3d.ts +23 -18
- package/src/parcel.ts +24 -5
- package/src/rack-grid-3d.ts +383 -80
- package/src/rack-grid-cell.ts +161 -305
- package/src/rack-grid.ts +1263 -762
- package/src/rack-materials.ts +61 -0
- package/src/storage-rack-3d.ts +182 -29
- package/src/storage-rack.ts +819 -67
- package/test/test-carrier-lifecycle.ts +361 -0
- package/test/test-coord-alignment.ts +201 -0
- package/test/test-crane-geometry.ts +167 -0
- package/test/test-external-to-rack.ts +461 -0
- package/test/test-mover-concurrent-bug.ts +304 -0
- package/test/test-mover-rollback.ts +290 -0
- package/test/test-phase-h-carrier-pickable.ts +4 -3
- package/test/test-r19-place-absorb.ts +174 -0
- package/test/test-rack-3d-attach-real.ts +301 -0
- package/test/test-rack-concurrent.ts +254 -0
- package/test/test-rack-edge-cases.ts +323 -0
- package/test/test-rack-grid-cell.ts +318 -0
- package/test/test-rack-grid-location.ts +657 -0
- package/test/test-real-3d-positioning.ts +158 -0
- package/test/test-slot-center-convention.ts +116 -0
- package/test/test-slot-target.ts +189 -0
- package/test/test-storage-rack-batched.ts +606 -0
- package/test/test-storage-rack-click.ts +329 -0
- package/test/test-storage-rack-slot-api.ts +357 -0
- package/test/test-toscene-convention.ts +162 -0
- package/test/test-user-scenario-sequential.ts +334 -0
- package/translations/en.json +7 -1
- package/translations/ja.json +7 -1
- package/translations/ko.json +7 -1
- package/translations/ms.json +7 -1
- package/translations/zh.json +7 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/rack-column.d.ts +0 -35
- package/dist/rack-column.js +0 -258
- package/dist/rack-column.js.map +0 -1
- package/dist/rack-grid-helpers.d.ts +0 -28
- package/dist/rack-grid-helpers.js +0 -71
- package/dist/rack-grid-helpers.js.map +0 -1
- package/dist/rack-grid-location.d.ts +0 -37
- package/dist/rack-grid-location.js +0 -227
- package/dist/rack-grid-location.js.map +0 -1
- package/dist/storage-cell-3d.d.ts +0 -25
- package/dist/storage-cell-3d.js +0 -88
- package/dist/storage-cell-3d.js.map +0 -1
- package/dist/storage-cell.d.ts +0 -70
- package/dist/storage-cell.js +0 -197
- package/dist/storage-cell.js.map +0 -1
- package/src/rack-column.ts +0 -340
- package/src/rack-grid-helpers.ts +0 -77
- package/src/rack-grid-location.ts +0 -286
- package/src/storage-cell-3d.ts +0 -101
- package/src/storage-cell.ts +0 -247
- package/test/test-rack-grid.ts +0 -77
package/src/rack-grid-cell.ts
CHANGED
|
@@ -1,95 +1,94 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* RackGridCell — RackGrid 의 *configuration storage* 단위 = 한 bay 의 메타데이터 핸들.
|
|
5
|
+
*
|
|
6
|
+
* 모드별 역할:
|
|
7
|
+
* - modeling (2D editor): 자체 사각형 render + click + nature.properties UI
|
|
8
|
+
* (rack-table-cell 패턴 그대로)
|
|
9
|
+
* - 2D / 3D view: *data holder 만* — render/mesh 모두 X. 외부 location API 가
|
|
10
|
+
* 이 cell 의 state.section/unit/isEmpty 등을 *configuration source of truth* 로 lookup.
|
|
11
|
+
*
|
|
12
|
+
* 3D mesh 는 *없음* (buildRealObject = undefined). 3D stock 시각화는 *RackGrid
|
|
13
|
+
* 자체의 RackGrid3D + InstancedMesh* 가 처리. cell-component 는 *3D 리소스 0*.
|
|
14
|
+
*
|
|
15
|
+
* 영속 데이터: cellId (bay 위치), section, unit, shelfLocations, isEmpty, border 등.
|
|
16
|
+
* RackGrid 의 hierarchy override 가 *redundant 좌표 (left/top/width/height)* 만 제거 —
|
|
17
|
+
* 위 own 데이터는 그대로 직렬화 (저장/로드 시 복원).
|
|
3
18
|
*/
|
|
4
|
-
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
Component, ComponentNature, Properties, RectPath, sceneComponent
|
|
22
|
+
} from '@hatiolab/things-scene'
|
|
5
23
|
import type { State } from '@hatiolab/things-scene'
|
|
6
|
-
import { Rack } from './rack-column.js'
|
|
7
24
|
|
|
8
|
-
/** RackGridCell 컴포넌트 state */
|
|
9
25
|
export interface RackGridCellState extends State {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
26
|
+
cellId?: string // bayKey 형식: `${col-1}-${row-1}` (0-based, 2 segments)
|
|
27
|
+
section?: string // location 의 {s}
|
|
28
|
+
unit?: string // location 의 {u}
|
|
29
|
+
shelfLocations?: string // cell 별 shelf 명시 (CSV); 미지정 시 parent.shelfLocations
|
|
30
|
+
binLocations?: string // cell 내 bin 명시 (CSV) — rack-table 호환
|
|
31
|
+
isEmpty?: boolean // 통로/공실 표시
|
|
32
|
+
border?: any // 4-side border (top/left/bottom/right)
|
|
16
33
|
merged?: boolean
|
|
17
34
|
rowspan?: number
|
|
18
35
|
colspan?: number
|
|
19
36
|
}
|
|
20
37
|
|
|
21
38
|
const EMPTY_BORDER = {}
|
|
39
|
+
const EMPTY_CELL_FILL = '#efefef'
|
|
40
|
+
const EMPTY_CELL_STROKE = '#ccc'
|
|
22
41
|
|
|
23
|
-
function
|
|
24
|
-
|
|
42
|
+
function hasAnyProperty(o: any, ...props: string[]): boolean {
|
|
43
|
+
for (const p of props) if (Object.prototype.hasOwnProperty.call(o, p)) return true
|
|
44
|
+
return false
|
|
25
45
|
}
|
|
26
46
|
|
|
27
|
-
function isRightMost(idx: number,
|
|
28
|
-
return (idx + 1) % columns
|
|
47
|
+
function isRightMost(idx: number, _rows: number, columns: number): boolean {
|
|
48
|
+
return (idx + 1) % columns === 0
|
|
29
49
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
for (let p in properties) {
|
|
33
|
-
if (o.hasOwnProperty(properties[p])) {
|
|
34
|
-
return true
|
|
35
|
-
}
|
|
36
|
-
}
|
|
50
|
+
function isBottomMost(idx: number, rows: number, columns: number): boolean {
|
|
51
|
+
return idx >= (rows - 1) * columns
|
|
37
52
|
}
|
|
38
53
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
54
|
+
// RackGridCell 의 refid 충돌 회피 — load 시 root.onadded → _addTraverse 가 자식부터
|
|
55
|
+
// addRefidIndex 호출. cell 의 model.refid 가 *비어있으면* root.getNewRefid() = 1,2,3...
|
|
56
|
+
// 작은 값 부여 → 부모 RackGrid (refid 7) / Crane (202) 등 *기존 refid 와 충돌*.
|
|
57
|
+
// constructor 에서 *큰 시작값 + monotonic counter* 로 자체 부여 → 충돌 0.
|
|
58
|
+
let _cellRefidCounter = 100_000_000
|
|
42
59
|
|
|
43
|
-
/**
|
|
44
|
-
* 1. 스타일을 상속 받아야 함. (cascade-style)
|
|
45
|
-
* 2. 스타일을 동적처리할 수 있음. (로직처리)
|
|
46
|
-
* 3. 데이타를 받을 수 있음.
|
|
47
|
-
*/
|
|
48
60
|
@sceneComponent('rack-grid-cell')
|
|
49
|
-
export class RackGridCell extends RectPath(Component) {
|
|
61
|
+
export default class RackGridCell extends RectPath(Component) {
|
|
50
62
|
override get state(): RackGridCellState {
|
|
51
63
|
return super.state as RackGridCellState
|
|
52
64
|
}
|
|
53
65
|
|
|
54
66
|
_focused: boolean = false
|
|
55
67
|
|
|
68
|
+
constructor(model: any, context: any) {
|
|
69
|
+
super(model, context)
|
|
70
|
+
// refid 미부여 시 큰 값 자체 부여 — root.getNewRefid() 의 1,2,3... 충돌 회피.
|
|
71
|
+
// hierarchy override 가 save 시 refid 제거 → load 마다 fresh.
|
|
72
|
+
if (this.model.refid == null) {
|
|
73
|
+
this.model.refid = _cellRefidCounter++
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
56
77
|
get hasTextProperty() {
|
|
57
78
|
return false
|
|
58
79
|
}
|
|
59
80
|
|
|
60
|
-
get nature() {
|
|
81
|
+
get nature(): ComponentNature {
|
|
61
82
|
return {
|
|
62
83
|
mutable: false,
|
|
63
84
|
resizable: true,
|
|
64
85
|
rotatable: true,
|
|
65
86
|
properties: [
|
|
66
|
-
{
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
type: 'string',
|
|
73
|
-
label: 'unit',
|
|
74
|
-
name: 'unit'
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
type: 'string',
|
|
78
|
-
label: 'shelf-locations',
|
|
79
|
-
name: 'shelfLocations',
|
|
80
|
-
placeholder: '1,2,3,... / ,,,04'
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
type: 'textarea',
|
|
84
|
-
label: 'bin-locations',
|
|
85
|
-
name: 'binLocations',
|
|
86
|
-
placeholder: '1,2,3,...'
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
type: 'checkbox',
|
|
90
|
-
label: 'is-empty',
|
|
91
|
-
name: 'isEmpty'
|
|
92
|
-
},
|
|
87
|
+
{ type: 'string', label: 'section', name: 'section' },
|
|
88
|
+
{ type: 'string', label: 'unit', name: 'unit' },
|
|
89
|
+
{ type: 'string', label: 'shelf-locations', name: 'shelfLocations', placeholder: '1,2,3 또는 ,,,04' },
|
|
90
|
+
{ type: 'textarea', label: 'bin-locations', name: 'binLocations', placeholder: '1,2,3,...' },
|
|
91
|
+
{ type: 'checkbox', label: 'is-empty', name: 'isEmpty' },
|
|
93
92
|
{
|
|
94
93
|
type: 'location-increase-pattern',
|
|
95
94
|
label: '',
|
|
@@ -98,8 +97,16 @@ export class RackGridCell extends RectPath(Component) {
|
|
|
98
97
|
event: {
|
|
99
98
|
'increase-location-pattern': (event: CustomEvent) => {
|
|
100
99
|
const { increasingDirection, skipNumbering, startSection, startUnit } = event.detail
|
|
101
|
-
const
|
|
102
|
-
|
|
100
|
+
const parent: any = this.parent
|
|
101
|
+
if (typeof parent?.increaseLocation === 'function') {
|
|
102
|
+
// RackGrid 의 root.selected 활용 — framework 가 자동 갱신
|
|
103
|
+
const selected = (this.root as any)?.selected as RackGridCell[] | undefined
|
|
104
|
+
const keys = (selected || [])
|
|
105
|
+
.filter(c => (c as any)?.state?.type === 'rack-grid-cell')
|
|
106
|
+
.map(c => c.state.cellId)
|
|
107
|
+
.filter((k): k is string => !!k)
|
|
108
|
+
parent.increaseLocation(keys, increasingDirection, skipNumbering, startSection, startUnit)
|
|
109
|
+
}
|
|
103
110
|
}
|
|
104
111
|
}
|
|
105
112
|
}
|
|
@@ -108,297 +115,146 @@ export class RackGridCell extends RectPath(Component) {
|
|
|
108
115
|
type: 'editor-table',
|
|
109
116
|
label: '',
|
|
110
117
|
name: '',
|
|
111
|
-
property: {
|
|
112
|
-
merge: false,
|
|
113
|
-
split: false
|
|
114
|
-
}
|
|
118
|
+
property: { merge: false, split: false }
|
|
115
119
|
}
|
|
116
120
|
]
|
|
117
121
|
}
|
|
118
122
|
}
|
|
119
123
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
// 하던 것을, v10 의 ThreeCapability 도입 이후로는 *항상* 자동 호출됨. RackGrid3D
|
|
124
|
-
// createRacks 의 isEmpty 체크와 이 자동 경로 사이의 불일치로 isEmpty=true cell 도
|
|
125
|
-
// 3D 에 그려지던 regression — 여기 isEmpty 체크 추가로 두 경로 정합.
|
|
126
|
-
if (this.getState('isEmpty')) return undefined
|
|
127
|
-
return new Rack(this)
|
|
124
|
+
/** 3D mesh 미생성 — view mode 의 리소스 0. */
|
|
125
|
+
buildRealObject(): undefined {
|
|
126
|
+
return undefined
|
|
128
127
|
}
|
|
129
128
|
|
|
130
|
-
|
|
131
|
-
this.set('merged', !!merged)
|
|
132
|
-
if (merged) this.set('text', '')
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
get merged() {
|
|
136
|
-
return this.getState('merged')
|
|
137
|
-
}
|
|
129
|
+
// ── Domain getters ────────────────────────────────────
|
|
138
130
|
|
|
139
|
-
|
|
140
|
-
this.
|
|
131
|
+
get isEmpty(): boolean {
|
|
132
|
+
return !!this.getState('isEmpty')
|
|
141
133
|
}
|
|
142
134
|
|
|
143
|
-
get
|
|
144
|
-
return this.getState('rowspan')
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
set colspan(colspan) {
|
|
148
|
-
this.set('colspan', colspan)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
get colspan() {
|
|
152
|
-
return this.getState('colspan')
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
get border() {
|
|
135
|
+
get border(): any {
|
|
156
136
|
return this.state.border || EMPTY_BORDER
|
|
157
137
|
}
|
|
158
138
|
|
|
159
|
-
|
|
160
|
-
|
|
139
|
+
set merged(v: boolean) {
|
|
140
|
+
this.set('merged', !!v)
|
|
141
|
+
if (v) this.set('text', '')
|
|
161
142
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (style && style.strokeStyle && style.lineWidth && style.lineDash) {
|
|
165
|
-
context.beginPath()
|
|
166
|
-
context.moveTo(x, y)
|
|
167
|
-
context.lineTo(to_x, to_y)
|
|
168
|
-
|
|
169
|
-
Component.drawStroke(context, style)
|
|
170
|
-
}
|
|
143
|
+
get merged(): boolean {
|
|
144
|
+
return !!this.getState('merged')
|
|
171
145
|
}
|
|
172
146
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const { isEmpty } = this.state
|
|
147
|
+
set rowspan(v: number) { this.set('rowspan', v) }
|
|
148
|
+
get rowspan(): number { return (this.getState('rowspan') as number) ?? 1 }
|
|
176
149
|
|
|
177
|
-
|
|
150
|
+
set colspan(v: number) { this.set('colspan', v) }
|
|
151
|
+
get colspan(): number { return (this.getState('colspan') as number) ?? 1 }
|
|
178
152
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Cell 채우기.
|
|
184
|
-
context.beginPath()
|
|
185
|
-
context.lineWidth = 0
|
|
186
|
-
context.rect(left, top, width, height)
|
|
187
|
-
|
|
188
|
-
// Border 그리기
|
|
153
|
+
/** Parent (RackGrid) 안에서 자기 (col, row) 인덱스 — components 순서 = row * columns + col. */
|
|
154
|
+
get index(): { row: number; column: number } {
|
|
189
155
|
const parent = this.parent as any
|
|
156
|
+
if (!parent) return { row: 0, column: 0 }
|
|
190
157
|
const idx = parent.components.indexOf(this)
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
this._drawBorder(context, left, top, left + width, top, border.top)
|
|
195
|
-
this._drawBorder(context, left, top + height, left, top, border.left)
|
|
196
|
-
if (isRightMost(idx, rows, columns))
|
|
197
|
-
this._drawBorder(context, left + width, top, left + width, top + height, border.right)
|
|
198
|
-
if (isBottomMost(idx, rows, columns))
|
|
199
|
-
this._drawBorder(context, left + width, top + height, left, top + height, border.bottom)
|
|
158
|
+
const cols = parent.columns ?? 1
|
|
159
|
+
return { row: Math.floor(idx / cols), column: idx % cols }
|
|
200
160
|
}
|
|
201
161
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
context.save()
|
|
206
|
-
context.fillStyle = EMPTY_CELL_FILL_STYLE
|
|
207
|
-
context.fillRect(left, top, width, height)
|
|
208
|
-
|
|
209
|
-
context.beginPath()
|
|
210
|
-
context.lineWidth = EMPTY_CELL_LINE_WIDTH
|
|
211
|
-
context.strokeStyle = EMPTY_CELL_STROKE_STYLE
|
|
212
|
-
|
|
213
|
-
context.moveTo(left, top)
|
|
214
|
-
context.lineTo(left + width, top + height)
|
|
215
|
-
context.moveTo(left + width, top)
|
|
216
|
-
context.lineTo(left, top + height)
|
|
217
|
-
|
|
218
|
-
context.stroke()
|
|
219
|
-
context.closePath()
|
|
220
|
-
context.restore()
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
get decotag() {
|
|
224
|
-
const rackTable = this.parent
|
|
225
|
-
let { locPattern, zone = '' } = rackTable.model
|
|
226
|
-
|
|
227
|
-
locPattern = locPattern.substring(0, locPattern.indexOf('{u}') + 3)
|
|
228
|
-
|
|
229
|
-
let locationString = ''
|
|
230
|
-
if (this.getState('section') && this.getState('unit'))
|
|
231
|
-
locationString = locPattern
|
|
232
|
-
.replace('{z}', zone)
|
|
233
|
-
.replace('{s}', this.getState('section'))
|
|
234
|
-
.replace('{u}', this.getState('unit'))
|
|
235
|
-
|
|
236
|
-
return locationString || ''
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
get index() {
|
|
240
|
-
const rackTable = this.parent as any
|
|
241
|
-
const index = rackTable.components.indexOf(this)
|
|
242
|
-
|
|
243
|
-
const rowIndex = Math.floor(index / rackTable.columns)
|
|
244
|
-
const columnIndex = index % rackTable.columns
|
|
245
|
-
|
|
246
|
-
return {
|
|
247
|
-
row: rowIndex,
|
|
248
|
-
column: columnIndex
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
get rowIndex() {
|
|
253
|
-
return this.index.row
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
get columnIndex() {
|
|
257
|
-
return this.index.column
|
|
258
|
-
}
|
|
162
|
+
get rowIndex(): number { return this.index.row }
|
|
163
|
+
get columnIndex(): number { return this.index.column }
|
|
259
164
|
|
|
260
|
-
get
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
const rowIndex = this.rowIndex
|
|
264
|
-
const columnIndex = this.columnIndex
|
|
265
|
-
|
|
266
|
-
if (columnIndex === 0) return null
|
|
267
|
-
|
|
268
|
-
const leftCell = rackTable.components[rowIndex * rackTable.columns + columnIndex - 1]
|
|
269
|
-
|
|
270
|
-
return leftCell
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
get rightCell() {
|
|
274
|
-
const rackTable = this.parent as any
|
|
275
|
-
|
|
276
|
-
const rowIndex = this.rowIndex
|
|
277
|
-
const columnIndex = this.columnIndex
|
|
278
|
-
|
|
279
|
-
if (columnIndex === rackTable.columns) return null
|
|
280
|
-
|
|
281
|
-
const rightCell = rackTable.components[rowIndex * rackTable.columns + columnIndex + 1]
|
|
282
|
-
|
|
283
|
-
return rightCell
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
get aboveCell() {
|
|
287
|
-
const rackTable = this.parent as any
|
|
288
|
-
|
|
289
|
-
const rowIndex = this.rowIndex
|
|
290
|
-
const columnIndex = this.columnIndex
|
|
291
|
-
|
|
292
|
-
if (rowIndex === 0) return null
|
|
293
|
-
|
|
294
|
-
const aboveCell = rackTable.components[(rowIndex - 1) * rackTable.columns + columnIndex]
|
|
295
|
-
|
|
296
|
-
return aboveCell
|
|
165
|
+
get rowCells(): Component[] {
|
|
166
|
+
const parent = this.parent as any
|
|
167
|
+
return parent?.getCellsByRow?.(this.rowIndex) ?? []
|
|
297
168
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const rowIndex = this.rowIndex
|
|
303
|
-
const columnIndex = this.columnIndex
|
|
304
|
-
|
|
305
|
-
if (rowIndex === rackTable.rows) return null
|
|
306
|
-
|
|
307
|
-
const belowCell = rackTable.components[(rowIndex + 1) * rackTable.columns + columnIndex]
|
|
308
|
-
|
|
309
|
-
return belowCell
|
|
169
|
+
get columnCells(): Component[] {
|
|
170
|
+
const parent = this.parent as any
|
|
171
|
+
return parent?.getCellsByColumn?.(this.columnIndex) ?? []
|
|
310
172
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const rackTable = this.parent as any
|
|
314
|
-
return rackTable.getCellsByRow(this.rowIndex)
|
|
173
|
+
get notEmptyRowCells(): Component[] {
|
|
174
|
+
return this.rowCells.filter(c => !(c as any).getState?.('isEmpty'))
|
|
315
175
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const rackTable = this.parent as any
|
|
319
|
-
return rackTable.getCellsByColumn(this.columnIndex)
|
|
176
|
+
get isAisle(): boolean {
|
|
177
|
+
return this.notEmptyRowCells.length === 0
|
|
320
178
|
}
|
|
321
179
|
|
|
322
|
-
|
|
323
|
-
let aboveCell = this.aboveCell
|
|
324
|
-
while (1) {
|
|
325
|
-
const aboveRowCells = aboveCell.notEmptyRowCells
|
|
326
|
-
|
|
327
|
-
if (aboveRowCells.length > 0) return aboveRowCells
|
|
180
|
+
// ── Lifecycle ─────────────────────────────────────────
|
|
328
181
|
|
|
329
|
-
|
|
182
|
+
onchange(after: Properties, _before: Properties) {
|
|
183
|
+
if (hasAnyProperty(after, 'isEmpty')) {
|
|
184
|
+
// isEmpty 토글 시 section/unit 클리어 (rack-table-cell 동작)
|
|
185
|
+
delete this.model.unit
|
|
186
|
+
delete this.model.section
|
|
330
187
|
}
|
|
188
|
+
// Parent 의 location index 무효화 (RackGrid 가 onchange 다시 안 받으므로 자식이 알림)
|
|
189
|
+
const parent: any = this.parent
|
|
190
|
+
parent?.invalidateLocationIndex?.()
|
|
331
191
|
}
|
|
332
192
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
for (let i = rowCells.length - 1; i > 0; i--) {
|
|
337
|
-
const cell = rowCells[i]
|
|
193
|
+
onmouseenter() { this.trigger('deco') }
|
|
194
|
+
onmouseleave() { this.trigger('decoreset') }
|
|
338
195
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
196
|
+
contains(x: number, y: number) {
|
|
197
|
+
const c = super.contains(x, y)
|
|
198
|
+
if (!c) {
|
|
199
|
+
this._focused = false
|
|
200
|
+
this.invalidate()
|
|
342
201
|
}
|
|
343
|
-
|
|
344
|
-
return 0
|
|
202
|
+
return c
|
|
345
203
|
}
|
|
346
204
|
|
|
347
|
-
|
|
348
|
-
|
|
205
|
+
// ── 2D rendering (modeling editor) ────────────────────
|
|
206
|
+
//
|
|
207
|
+
// *view mode* (= app.isViewMode) 면 안 그림 — *configuration storage 만*. modeling
|
|
208
|
+
// 시에만 사각형 + border + isEmpty 패턴 시각화.
|
|
349
209
|
|
|
350
|
-
|
|
351
|
-
|
|
210
|
+
render(context: CanvasRenderingContext2D) {
|
|
211
|
+
if ((this as any).app?.isViewMode) return
|
|
352
212
|
|
|
353
|
-
|
|
213
|
+
const { left, top, width, height } = this.bounds
|
|
214
|
+
const { isEmpty } = this.state
|
|
215
|
+
const border = this.border
|
|
354
216
|
|
|
355
|
-
|
|
217
|
+
if (isEmpty) {
|
|
218
|
+
// 빈 cell — 회색 fill + 대각선
|
|
219
|
+
context.save()
|
|
220
|
+
context.fillStyle = EMPTY_CELL_FILL
|
|
221
|
+
context.fillRect(left, top, width, height)
|
|
222
|
+
context.strokeStyle = EMPTY_CELL_STROKE
|
|
223
|
+
context.lineWidth = 1
|
|
224
|
+
context.beginPath()
|
|
225
|
+
context.moveTo(left, top); context.lineTo(left + width, top + height)
|
|
226
|
+
context.moveTo(left + width, top); context.lineTo(left, top + height)
|
|
227
|
+
context.stroke()
|
|
228
|
+
context.restore()
|
|
356
229
|
}
|
|
357
230
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
return this.rowCells.filter((c: Component) => {
|
|
363
|
-
return !c.getState('isEmpty')
|
|
364
|
-
})
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
get emptyRowCells() {
|
|
368
|
-
return this.rowCells.filter((c: Component) => {
|
|
369
|
-
return c.getState('isEmpty')
|
|
370
|
-
})
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
get isAisle() {
|
|
374
|
-
return this.notEmptyRowCells.length === 0
|
|
375
|
-
}
|
|
231
|
+
// 명시적 cell 영역 표시 (border default — 자식 cell 의 미니 frame)
|
|
232
|
+
context.beginPath()
|
|
233
|
+
context.lineWidth = 0
|
|
234
|
+
context.rect(left, top, width, height)
|
|
376
235
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
236
|
+
const parent = this.parent as any
|
|
237
|
+
const idx = parent?.components?.indexOf?.(this) ?? 0
|
|
238
|
+
const columns = parent?.columns ?? 1
|
|
239
|
+
const rows = parent?.rackRows ?? 1
|
|
240
|
+
|
|
241
|
+
this._drawSide(context, left, top, left + width, top, border.top)
|
|
242
|
+
this._drawSide(context, left, top + height, left, top, border.left)
|
|
243
|
+
if (isRightMost(idx, rows, columns)) {
|
|
244
|
+
this._drawSide(context, left + width, top, left + width, top + height, border.right)
|
|
382
245
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
onmouseenter() {
|
|
386
|
-
this.trigger('deco')
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
onmouseleave() {
|
|
390
|
-
this.trigger('decoreset')
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
contains(x: number, y: number) {
|
|
394
|
-
const contains = super.contains(x, y)
|
|
395
|
-
if (!contains) {
|
|
396
|
-
this._focused = false
|
|
397
|
-
this.invalidate()
|
|
246
|
+
if (isBottomMost(idx, rows, columns)) {
|
|
247
|
+
this._drawSide(context, left + width, top + height, left, top + height, border.bottom)
|
|
398
248
|
}
|
|
249
|
+
}
|
|
399
250
|
|
|
400
|
-
|
|
251
|
+
private _drawSide(ctx: CanvasRenderingContext2D, x: number, y: number, tx: number, ty: number, style: any) {
|
|
252
|
+
if (!style?.strokeStyle || !style.lineWidth) return
|
|
253
|
+
ctx.beginPath()
|
|
254
|
+
ctx.moveTo(x, y)
|
|
255
|
+
ctx.lineTo(tx, ty)
|
|
256
|
+
Component.drawStroke(ctx, style)
|
|
401
257
|
}
|
|
402
258
|
}
|
|
403
259
|
|
|
404
|
-
;['border'].forEach(
|
|
260
|
+
;['border'].forEach(g => Component.memoize(RackGridCell.prototype, g, false))
|