gis-view-webgl2 0.1.7 → 0.1.8

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 (54) hide show
  1. package/README.md +9 -8
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.ts +232 -228
  4. package/dist/index.js +1 -1
  5. package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +49 -0
  6. package/docs/GisView.md +1 -1
  7. package/docs/ai/06-/344/274/232/350/257/235/347/264/242/345/274/225/344/270/216/345/244/215/346/237/245/346/226/271/346/263/225.md +48 -0
  8. package/docs/demos/BasicDemo.vue +2 -2
  9. package/docs/demos/BatchAddDemo.vue +6 -6
  10. package/docs/demos/ConnectDemo.vue +164 -33
  11. package/docs/demos/CopyMergeDemo.vue +27 -14
  12. package/docs/demos/EventDemo.vue +17 -20
  13. package/docs/demos/LabelDemo.vue +5 -5
  14. package/docs/demos/api-index.js +31 -29
  15. package/docs/demos/gis-view-demo/GisViewDemoCardHeader.vue +1 -1
  16. package/docs/demos/gis-view-demo/GisViewDemoStage.vue +1 -1
  17. package/docs/demos/gis-view-demo/panels/DataPanels.vue +1 -1
  18. package/docs/demos/gis-view-demo/useGisViewDemo.js +31 -33
  19. package/docs/format/07-/344/272/213/344/273/266/350/275/275/350/215/267/344/270/216/347/244/272/344/276/213/346/225/260/346/215/256.md +8 -7
  20. package/docs/from0/05-/347/233/270/346/234/272/344/270/216/344/272/244/344/272/222.md +1 -1
  21. package/docs/from0/07-/351/200/232/351/201/223/345/210/206/347/273/204/344/270/216/345/244/232/345/233/236/347/272/277.md +26 -10
  22. package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +1 -1
  23. package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +21 -16
  24. package/docs/manual/06-/346/225/260/346/215/256/344/270/216/345/234/272/346/231/257/346/233/264/346/226/260.md +9 -10
  25. package/docs/manual/07-/346/214/207/351/222/210/344/270/216/350/246/201/347/264/240/347/274/226/350/276/221.md +54 -31
  26. package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +3 -3
  27. package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +9 -6
  28. package/docs/manual/10-/351/200/232/351/201/223/347/274/226/350/276/221/344/270/216/346/226/260/345/242/236.md +226 -493
  29. package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +2 -2
  30. package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +12 -13
  31. package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +23 -21
  32. package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +19 -17
  33. package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +125 -78
  34. package/docs/manual/19-/350/257/273/346/225/260/344/270/216/346/213/226/346/213/275/351/242/204/350/247/210.md +1 -1
  35. package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +2 -2
  36. package/docs/manual/21-/350/257/264/346/230/216/344/270/216/345/270/270/350/247/201/351/227/256/351/242/230.md +4 -4
  37. package/docs/steps/README.md +9 -7
  38. package/docs/steps/bench-scale.html +1 -1
  39. package/docs/steps/index.html +6 -6
  40. package/docs/steps/selftest-actions.html +26 -25
  41. package/docs/steps/selftest-data.html +1 -1
  42. package/docs/steps/selftest-demos.html +102 -6
  43. package/docs/steps/selftest-docs.html +3 -3
  44. package/docs/steps/selftest-features.html +283 -51
  45. package/docs/steps/selftest-full.html +3 -3
  46. package/docs/steps/selftest-labels.html +1 -1
  47. package/docs/steps/selftest-perf.html +31 -4
  48. package/docs/steps/selftest-select.html +20 -5
  49. package/docs/steps/step-05-pick.js +4 -8
  50. package/docs/steps/step-07-channels.js +3 -5
  51. package/docs/steps/step-08-labels.js +3 -3
  52. package/docs/steps/step-10-full.js +3 -3
  53. package/docs/steps/step-12-events.js +41 -24
  54. package/package.json +1 -1
@@ -319,9 +319,36 @@
319
319
  big.removePoints(['bench-new', 'bench-new2'])
320
320
  const channelRemoveMs = now() - channelRemoveStarted
321
321
  diag('通道在场时补 2 个点', `${channelAddMs.toFixed(1)} ms(这一轮之前 40 ms 上下)`)
322
- diag('通道在场时删这 2 个点', `${channelRemoveMs.toFixed(1)} ms(这一轮之前 35 ms 上下)`)
323
- check('通道在场时的增删:通道成员表不动、点真的加上又删掉', big.getData().points.length === pointCount && big.getChannel('bench-ch').lineIndexes.length === 2, JSON.stringify({ points: big.getData().points.length, members: big.getChannel('bench-ch').lineIndexes }))
324
- big.updateOptions({ channels: [] })
322
+ diag('通道在场时删这 2 个点', `${channelRemoveMs.toFixed(1)} ms(这一轮之前 35 ms 上下)`)
323
+ check('通道在场时的增删:通道成员表不动、点真的加上又删掉', big.getData().points.length === pointCount && big.getChannel('bench-ch').lineIndexes.length === 2, JSON.stringify({ points: big.getData().points.length, members: big.getChannel('bench-ch').lineIndexes }))
324
+ // addLine 是末尾补线:已有通道声明时也走追加式补线,不能为了加一条独立线路把整份场景重排。
325
+ // 这里同时盯正确性(新线进场景、通道声明一条不动)与代价(最快一次明显低于整份重排)。
326
+ const appendLineChannelsBefore = JSON.stringify(big.getOptions().channels)
327
+ big.setCamera({ center: [10, 0], scale: 2 })
328
+ const appendLineIds = []
329
+ let appendLineFastestMs = Infinity
330
+ const appendLineStarted = now()
331
+ for (let index = 0; index < 5; index += 1) {
332
+ const callStarted = now()
333
+ const appendedLine = big.addLine({ from: 'p0', to: 'p1', id: `bench-added-line-${index}`, lineProps: { color: '#ff3300', width: 2 } })
334
+ appendLineFastestMs = Math.min(appendLineFastestMs, now() - callStarted)
335
+ if (appendedLine.ok) {
336
+ appendLineIds.push(appendedLine.lineId)
337
+ }
338
+ }
339
+ const appendLineMs = (now() - appendLineStarted) / 5
340
+ const appendLineScene = big.snapshotScene({ background: false })
341
+ const appendLinePainted = appendLineScene.lines.filter(line => line.color === 'rgba(255,51,0,1)').length
342
+ const appendLineRefreshStarted = now()
343
+ big.refresh()
344
+ const appendLineRefreshMs = now() - appendLineRefreshStarted
345
+ check('通道声明在场时 addLine 走追加式补线:5 条都进场景、通道声明不动', appendLineIds.length === 5 && appendLinePainted === 5 && JSON.stringify(big.getOptions().channels) === appendLineChannelsBefore, JSON.stringify({ ids: appendLineIds, painted: appendLinePainted }))
346
+ check('addLine 的追加代价明显低于整份重排(有通道声明也不会回落整份重建)', appendLineFastestMs * 5 < appendLineRefreshMs, `最快一次 ${appendLineFastestMs.toFixed(2)} ms · 平均 ${appendLineMs.toFixed(2)} ms vs 整份重排 ${appendLineRefreshMs.toFixed(1)} ms`)
347
+ diag('通道在场时 addLine 追加 1 条', `${appendLineMs.toFixed(2)} ms / 次(最快一次 ${appendLineFastestMs.toFixed(2)} ms)· 同一场景整份重排 ${appendLineRefreshMs.toFixed(1)} ms`)
348
+ for (const id of appendLineIds) {
349
+ big.removeLine(id)
350
+ }
351
+ big.updateOptions({ channels: [] })
325
352
 
326
353
  /* ---------------- 分区(分块)渲染值不值得:视口变化时一帧的成本怎么变 ---------------- */
327
354
  // 「把地图拆成一块一块、只画视口里的块」省的是「视口外那些实例的顶点变换」。
@@ -2323,7 +2350,7 @@
2323
2350
  await sleep(30)
2324
2351
  const selectedAfter = clusterView.getSelections().map(entry => entry.item?.id ?? entry.index).join(',')
2325
2352
  check('点聚合:点击聚合只发 clusterClick,不发普通 click,选中集不变',
2326
- clusterEvents === 1 && plainEvents === 0 && selectedAfter === selectedBefore && lastClusterPayload?.action?.type === 'clusterClick' && lastClusterPayload?.action?.data?.type === 'cluster',
2353
+ clusterEvents === 1 && plainEvents === 0 && selectedAfter === selectedBefore && lastClusterPayload?.action?.type === 'clusterClick' && lastClusterPayload?.type === 'cluster' && lastClusterPayload?.count > 1 && Array.isArray(lastClusterPayload?.pointIds) && lastClusterPayload?.action?.data === undefined && lastClusterPayload?.originalEvent instanceof Event,
2327
2354
  `clusterClick=${clusterEvents} click=${plainEvents} 选中 ${selectedBefore} → ${selectedAfter} action=${lastClusterPayload?.action?.type} clickPick=${clickPick?.type ?? 'null'} clickScreen=${clickScreen.map(value => value.toFixed(1)).join(',')} client=${clickClient.map(value => value.toFixed(1)).join(',')}`)
2328
2355
  const fitScale = clusterView.getCamera().scale
2329
2356
  // 聚合成员在相机运动期会推迟重算;这一档没有线、又显式关掉非 MSAA 后备缓冲,
@@ -497,14 +497,19 @@
497
497
  check('onPointerDown / onPointerUp / onClick(通道回线):每个鼠标载荷都带 groups 与当前 group',
498
498
  [gpDowns.at(-1), gpUps.at(-1), gpClicks.at(-1)].every(payload => payload?.groups?.length === 2 && String(payload.group?.id) === String(payload.groupId) && payload.action?.target === 'channel'),
499
499
  JSON.stringify({ down: gpDowns.at(-1)?.groups?.length, up: gpUps.at(-1)?.groups?.length, click: gpClicks.at(-1)?.groups?.length }))
500
+ const gpLineChannelFields = ['channelId', 'channelIndex', 'channel', 'groupId', 'groupIndex', 'group', 'groups', 'lineIndexes', 'lineIds', 'channelContexts']
501
+ const gpSelectedLineIndexes = payload => (payload?.items ?? []).filter(item => item.type === 'line').map(item => item.index).join(',')
502
+ const gpSelectedLineIds = payload => (payload?.items ?? []).filter(item => item.type === 'line').map(item => item.item?.id).join(',')
500
503
  const gpChannelPayloadOK = payload => String(payload?.channelId) === 'gp-ch' &&
501
504
  payload?.groups?.length === 2 &&
502
505
  payload?.channelContexts === undefined &&
503
506
  String(payload.group?.id) === String(payload.groupId) &&
504
- payload.items?.every(item => item.type !== 'line' || (item.channelId === payload.channelId && item.channelIndex === payload.channelIndex && item.groupId !== undefined && item.groupIndex !== undefined && item.group === undefined && item.groups === undefined))
505
- check('onSelect / onSelectChange(通道回线):单通道只摊平在顶层,items 只留标量归属且不重复挂 group / groups',
507
+ payload.lineIndexes?.join(',') === gpSelectedLineIndexes(payload) &&
508
+ payload.lineIds?.join(',') === gpSelectedLineIds(payload) &&
509
+ payload.items?.every(item => item.type !== 'line' || gpLineChannelFields.every(key => item[key] === undefined))
510
+ check('onSelect / onSelectChange(通道回线):单通道上下文只在顶层,items 不重复挂任何通道字段',
506
511
  gpChannelPayloadOK(gpSelects.at(-1)) && gpChannelPayloadOK(gpChanges.at(-1)) && gpSelects.at(-1)?.items === gpChanges.at(-1)?.items,
507
- JSON.stringify({ select: gpSelects.at(-1)?.channelId, change: gpChanges.at(-1)?.channelId, groups: gpSelects.at(-1)?.groups?.length, contexts: gpSelects.at(-1)?.channelContexts, items: gpSelects.at(-1)?.items?.map(item => ({ channelId: item.channelId, channelIndex: item.channelIndex, groupId: item.groupId, groupIndex: item.groupIndex, group: item.group, groups: item.groups })) }))
512
+ JSON.stringify({ select: gpSelects.at(-1)?.channelId, change: gpChanges.at(-1)?.channelId, groups: gpSelects.at(-1)?.groups?.length, lineIds: gpSelects.at(-1)?.lineIds, items: gpSelects.at(-1)?.items?.map(item => ({ id: item.item?.id, channelId: item.channelId, groupId: item.groupId })) }))
508
513
  check('真实点击的派发顺序:onClick → onSelect → onSelectChange',
509
514
  gpOrder.slice(-3).join('>') === 'click>select>selectChange',
510
515
  gpOrder.join('>'))
@@ -513,8 +518,18 @@
513
518
  gpOrder.length = 0
514
519
  gpView.selectFeatures({ type: 'channel', channelId: 'gp-ch' })
515
520
  check('selectFeatures(整条通道):onSelect / onSelectChange 都带通道上下文,顺序是 onSelect → onSelectChange',
516
- gpOrder.join('>') === 'select>selectChange' && gpChannelPayloadOK(gpSelects.at(-1)) && gpChannelPayloadOK(gpChanges.at(-1)) && gpSelects.at(-1)?.items?.length === 3,
521
+ gpOrder.join('>') === 'select>selectChange' && gpChannelPayloadOK(gpSelects.at(-1)) && gpChannelPayloadOK(gpChanges.at(-1)) && gpSelects.at(-1)?.items?.length === 3 && gpSelects.at(-1)?.lineIds?.join(',') === 'gp-l1,gp-l2,gp-l3',
517
522
  JSON.stringify({ order: gpOrder, select: gpSelects.at(-1), change: gpChanges.at(-1) }))
523
+ const gpSelectionLines = gpView.getSelections().filter(entry => entry.type === 'line')
524
+ const gpOwner = gpView.getLineChannel(gpSelectionLines[0]?.index)
525
+ const gpSelectionData = gpView.getSelectionData()
526
+ check('getSelections / getSelectionData / getLineChannel:选中镜像不带通道字段,单条线路仍能反查通道与分组',
527
+ gpSelectionLines.length === 3 &&
528
+ gpSelectionLines.every(entry => gpLineChannelFields.every(key => entry[key] === undefined)) &&
529
+ gpSelectionData.items.every(entry => entry.type !== 'line' || gpLineChannelFields.every(key => entry[key] === undefined)) &&
530
+ gpSelectionData.channels.map(channel => String(channel.id)).join(',') === 'gp-ch' &&
531
+ String(gpOwner?.channelId) === 'gp-ch' && String(gpOwner?.channel?.id) === 'gp-ch' && gpOwner?.channelIndex === 0 && String(gpOwner?.group?.id) === 'gp-g1',
532
+ JSON.stringify({ lines: gpSelectionLines.length, owner: gpOwner && { channelId: gpOwner.channelId, channelIndex: gpOwner.channelIndex, groupId: gpOwner.groupId }, channels: gpSelectionData.channels.map(channel => channel.id) }))
518
533
  gpOrder.length = 0
519
534
  gpView.selectFeatures([
520
535
  { type: 'channel', channelId: 'gp-ch' },
@@ -529,7 +544,7 @@
529
544
  gpSelects.at(-1).channelContexts.map(context => `${context.lineIndexes.length}/${context.lineIds.length}`).join(',') === '3/3,1/1' &&
530
545
  gpSelects.at(-1).channelContexts.map(context => context.lineIds.join('+')).join(',') === 'gp-l1+gp-l2+gp-l3,gp-l4' &&
531
546
  gpSelects.at(-1).items === gpChanges.at(-1)?.items &&
532
- gpSelects.at(-1).items.every(item => item.type !== 'line' || (item.group === undefined && item.groups === undefined)),
547
+ gpSelects.at(-1).items.every(item => item.type !== 'line' || gpLineChannelFields.every(key => item[key] === undefined)),
533
548
  JSON.stringify({ order: gpOrder, contexts: gpSelects.at(-1)?.channelContexts?.map(context => ({ channelId: context.channelId, lines: context.lineIds })), items: gpSelects.at(-1)?.items?.length }))
534
549
  gpOrder.length = 0
535
550
  const gpAt = gpView.selectAt(gpClientX, gpClientY)
@@ -85,20 +85,16 @@ export const createPickView = (target, options = {}) => {
85
85
  /** 点优先于线:两个都命中也算点中节点 */
86
86
  const pick = (x, y) => pickPoint(x, y) ?? pickLine(x, y)
87
87
 
88
- /** 统一的指针载荷:命中的元素 + 按钮 + 光标位置(空白处 type 为 null、hit 为 false) */
88
+ /** 统一的指针载荷:命中的元素 + 光标位置(空白处 type 为 null、hit 为 false;按键读 originalEvent) */
89
89
  const pointerPayload = (hit, point, event) => ({
90
90
  type: hit ? hit.type : null,
91
91
  index: hit ? hit.index : -1,
92
92
  item: hit ? hit.item : null,
93
93
  hit: Boolean(hit),
94
94
  ...(hit ?? {}),
95
- button: event.button,
96
- buttons: event.buttons,
97
95
  world: core.unproject(point[0], point[1]),
98
96
  x: point[0],
99
97
  y: point[1],
100
- clientX: event.clientX,
101
- clientY: event.clientY,
102
98
  originalEvent: event
103
99
  })
104
100
  const canvasPointOf = event => {
@@ -180,7 +176,7 @@ export const createPickView = (target, options = {}) => {
180
176
  const point = canvasPointOf(event)
181
177
  const hit = pick(point[0], point[1])
182
178
  // 左键 / 中键 / 右键都发一条:使用方在按下这一刻就能拿到光标下的元素
183
- listeners.onPointerDown?.(pointerPayload(hit, point, event), event)
179
+ listeners.onPointerDown?.(pointerPayload(hit, point, event))
184
180
  if (event.button !== 0) {
185
181
  return
186
182
  }
@@ -215,7 +211,7 @@ export const createPickView = (target, options = {}) => {
215
211
  const point = canvasPointOf(event)
216
212
  const hit = pick(point[0], point[1])
217
213
  if (event.button === 0 || event.button === 2) {
218
- listeners.onPointerUp?.(pointerPayload(hit, point, event), event)
214
+ listeners.onPointerUp?.(pointerPayload(hit, point, event))
219
215
  }
220
216
  const active = pressed
221
217
  pressed = null
@@ -253,7 +249,7 @@ export const createPickView = (target, options = {}) => {
253
249
  /** 右键:点、线、空白都发一条 onContextMenu,命中什么都在载荷里 */
254
250
  const handleContextMenu = event => {
255
251
  const point = canvasPointOf(event)
256
- listeners.onContextMenu?.(pointerPayload(pick(point[0], point[1]), point, event), event)
252
+ listeners.onContextMenu?.(pointerPayload(pick(point[0], point[1]), point, event))
257
253
  }
258
254
 
259
255
  // 框选矩形:一个绝对定位的 DOM 层,样式由使用方决定(组件不内置界面)
@@ -221,9 +221,9 @@ export const createChannelView = (target, options = {}) => {
221
221
  onClick: payload => listeners.onClick?.(channelize(payload)),
222
222
  onDblClick: payload => listeners.onDblClick?.(channelize(payload)),
223
223
  onSelect: payload => listeners.onSelect?.(payload ? { ...payload, items: payload.items.map(channelize).filter(Boolean) } : null),
224
- onPointerDown: (payload, event) => listeners.onPointerDown?.(channelize(payload), event),
225
- onPointerUp: (payload, event) => listeners.onPointerUp?.(channelize(payload), event),
226
- onContextMenu: (payload, event) => listeners.onContextMenu?.(channelize(payload), event)
224
+ onPointerDown: payload => listeners.onPointerDown?.(channelize(payload)),
225
+ onPointerUp: payload => listeners.onPointerUp?.(channelize(payload)),
226
+ onContextMenu: payload => listeners.onContextMenu?.(channelize(payload))
227
227
  })
228
228
 
229
229
  // ---------- 新增通道:草稿状态机 ----------
@@ -301,8 +301,6 @@ export const createChannelView = (target, options = {}) => {
301
301
  x: point[0],
302
302
  y: point[1],
303
303
  world: core.unproject(point[0], point[1]),
304
- clientX: event.clientX,
305
- clientY: event.clientY,
306
304
  originalEvent: event
307
305
  })
308
306
 
@@ -130,9 +130,9 @@ export const createLabelView = (target, options = {}) => {
130
130
  renderTooltip(payload, payload ? payload.screen : null)
131
131
  listeners.onHover?.(payload)
132
132
  },
133
- onPointerDown: (payload, event) => listeners.onPointerDown?.(payload, event),
134
- onPointerUp: (payload, event) => listeners.onPointerUp?.(payload, event),
135
- onContextMenu: (payload, event) => listeners.onContextMenu?.(payload, event)
133
+ onPointerDown: payload => listeners.onPointerDown?.(payload),
134
+ onPointerUp: payload => listeners.onPointerUp?.(payload),
135
+ onContextMenu: payload => listeners.onContextMenu?.(payload)
136
136
  })
137
137
  canvas.addEventListener('pointermove', handlePointerMove)
138
138
  // 每帧同步一次标签:注册进第 1 步的渲染任务表,画的顺序在 WebGL 之后,谁都不挡谁
@@ -82,9 +82,9 @@ export const createFullView = (target, options = {}) => {
82
82
  onClick: payload => forward.onClick?.(payload),
83
83
  onDblClick: payload => forward.onDblClick?.(payload),
84
84
  onSelect: payload => forward.onSelect?.(payload),
85
- onPointerDown: (payload, event) => forward.onPointerDown?.(payload, event),
86
- onPointerUp: (payload, event) => forward.onPointerUp?.(payload, event),
87
- onContextMenu: (payload, event) => forward.onContextMenu?.(payload, event),
85
+ onPointerDown: payload => forward.onPointerDown?.(payload),
86
+ onPointerUp: payload => forward.onPointerUp?.(payload),
87
+ onContextMenu: payload => forward.onContextMenu?.(payload),
88
88
  onChannelDraftClick: payload => forward.onChannelDraftClick?.(payload),
89
89
  onChannelDraft: payload => {
90
90
  if (payload?.phase === 'commit' && payload.lineId) {
@@ -9,21 +9,20 @@
9
9
  * (select 选中 / clearSelection 点空白 / addWaypoint 加点 / contextMenu / dblClick);
10
10
  * - action.target:点在了什么上面(point / line / channel / empty)。通道回线的 type 也是 line,
11
11
  * 但要按通道处理,所以 target 单独给 channel;
12
- * - action.data:按 target 直接取到的那份数据 —— 节点 / 线路是数据里那个对象、通道回线是通道定义、
13
- * 空白是 null。不用再去 payload.item / payload.channel 里翻;
14
- * - action.button(left / middle / right,悬停是 none)、action.modifiers(ctrl / meta / shift / alt)、
15
- * action.moved(这一次按下之后有没有真的拖动过)、action.double(是不是双击里的第二下);
16
- * - action.left / action.right:左键 / 右键的一眼判定(中键两边都是 false);松开与点击载荷另带
17
- * action.press(**按下时的意图**,与那一刻 onPointerDown 的 action.type 同一份)、action.duration
12
+ * - 命中的数据直接读载荷:节点 / 线路读 payload.item、通道回线读 payload.channel、选中集读
13
+ * payload.items、拐点读 payload.line,不再另打包一份 action.data;
14
+ * - 按键 / 修饰键 / 指针位置读 payload.originalEvent(button / ctrlKey / metaKey / shiftKey / altKey /
15
+ * clientX / clientY),action 只留 type / target / moved / double 这些判读;
16
+ * - 松开与点击载荷另带 action.press(**按下时的意图**,与那一刻 onPointerDown 的 action.type 同一份)、action.duration
18
17
  * (按了多久,毫秒)与 action.longPress(超过 500ms 且中间没拖动过 —— 触摸 / 触控笔按它出菜单)。
19
- * 中键按住就是临时平移:按下 / 松开的 action.type 都是 pan、action.button 是 middle,不经过 onClick。
18
+ * 中键按住就是临时平移:按下 / 松开的 action.type 都是 pan、originalEvent.button 是 1,不经过 onClick。
20
19
  *
21
20
  * 于是「按右键弹哪一份菜单」就是 switch (payload.action.target),「拖完的是节点还是拐点」就是
22
21
  * switch (payload.action.type),不用再各记一套 payload 形状。选中集变更也有同一套 action.type:
23
22
  * boxSelect / toggleSelect(Ctrl 点击取反)/ select / clearSelection —— 框选没框到东西与
24
23
  * 「点空白清空」在载荷上是同一个形状(都是 null),只有这一份补充读数能把它们分开。
25
24
  * 标签事件(labelClick / labelDrag / labelContextMenu / labelDblClick)同一套写法:
26
- * target 恒为 'label'、data 是 { label, parent }。
25
+ * target 恒为 'label'、标签与归属直接读载荷顶层的 label / parent。
27
26
  *
28
27
  * 两个只读查询:想主动问「这儿是什么」而不必先订阅一遍事件再把最近一次载荷记住,用
29
28
  * - pickAt(clientX, clientY):参数就是鼠标事件里的 clientX / clientY,返回与指针载荷同一份结果
@@ -59,7 +58,21 @@ const withChannels = (data) => {
59
58
  /** action.target -> 中文:日志里读起来不用记英文名 */
60
59
  const TARGET_NAMES = { point: '节点', line: '线路', channel: '通道回线', label: '标签', waypoint: '拐点', selection: '选中集', empty: '空白' }
61
60
 
62
- /** action.data 里那一份数据的名字:节点 / 线路看 name 与 id,选中集与标签给条数或内容 */
61
+ /** 按 action.target 取载荷上那一份数据:节点 / 线路读 item、通道读 channel、选中集读 items */
62
+ const dataOfAction = (payload, action) => {
63
+ if (Array.isArray(payload?.items)) {
64
+ return payload.items
65
+ }
66
+ if (payload?.label) {
67
+ return payload.label
68
+ }
69
+ if (action?.target === 'waypoint') {
70
+ return payload.line ?? null
71
+ }
72
+ return payload?.channel ?? payload?.item ?? null
73
+ }
74
+
75
+ /** 数据那一份的名字:节点 / 线路看 name 与 id,选中集与标签给条数或内容 */
63
76
  const nameOfData = (data) => {
64
77
  if (Array.isArray(data)) {
65
78
  return `${data.length} 个要素`
@@ -67,10 +80,6 @@ const nameOfData = (data) => {
67
80
  if (!data || typeof data !== 'object') {
68
81
  return ''
69
82
  }
70
- if (data.label) {
71
- // 标签事件的 data 是 { label, parent }
72
- return data.label.text ?? data.label.id ?? '标签'
73
- }
74
83
  return data.name ?? data.id ?? ''
75
84
  }
76
85
 
@@ -81,19 +90,27 @@ const describeAction = (payload, phase) => {
81
90
  return `${phase}:—`
82
91
  }
83
92
  const parts = [action.type, `→ ${TARGET_NAMES[action.target] ?? action.target}`]
84
- const name = nameOfData(action.data)
93
+ const name = nameOfData(dataOfAction(payload, action))
85
94
  if (name !== '') {
86
95
  parts.push(String(name))
87
96
  }
88
- parts.push(action.button === 'right' ? '右键' : action.button === 'middle' ? '中键' : '左键')
97
+ const button = payload.originalEvent?.button
98
+ if (button === 0 || button === 1 || button === 2) {
99
+ parts.push(button === 2 ? '右键' : button === 1 ? '中键' : '左键')
100
+ }
89
101
  if (action.moved) {
90
102
  parts.push('拖动过')
91
103
  }
92
104
  if (action.double) {
93
105
  parts.push('双击的第二下')
94
106
  }
95
- const modifiers = action.modifiers ?? {}
96
- const held = Object.keys(modifiers).filter(key => modifiers[key]).join('+')
107
+ const event = payload.originalEvent
108
+ const held = [
109
+ event?.ctrlKey ? 'ctrl' : null,
110
+ event?.metaKey ? 'meta' : null,
111
+ event?.shiftKey ? 'shift' : null,
112
+ event?.altKey ? 'alt' : null
113
+ ].filter(Boolean).join('+')
97
114
  if (held) {
98
115
  parts.push(`按住 ${held}`)
99
116
  }
@@ -121,13 +138,13 @@ export const createActionView = (target, options = {}) => {
121
138
  waypointEditable: true,
122
139
  features: { pan: true, zoom: true, hover: true, select: true, contextMenu: true, keyboard: true, channelDraft: true },
123
140
  // 下面五条是同一条路:每个回调先报一句判读,再交给页面自己那一条(日志、草稿读数都还在)
124
- onPointerDown: (payload, event) => {
141
+ onPointerDown: payload => {
125
142
  report(describeAction(payload, '按下'))
126
- options.onPointerDown?.(payload, event)
143
+ options.onPointerDown?.(payload)
127
144
  },
128
- onPointerUp: (payload, event) => {
145
+ onPointerUp: payload => {
129
146
  report(describeAction(payload, '松开'))
130
- options.onPointerUp?.(payload, event)
147
+ options.onPointerUp?.(payload)
131
148
  },
132
149
  onClick: payload => {
133
150
  // 空白处的点击载荷是 null(选中已经清空):这一下的判读在 onSelectChange 那条路上
@@ -138,13 +155,13 @@ export const createActionView = (target, options = {}) => {
138
155
  report(describeAction(payload, '双击'))
139
156
  options.onDblClick?.(payload)
140
157
  },
141
- onContextMenu: (payload, event) => {
158
+ onContextMenu: payload => {
142
159
  report(describeAction(payload, '右键'))
143
- options.onContextMenu?.(payload, event)
160
+ options.onContextMenu?.(payload)
144
161
  },
145
162
  // 选中集变更:action.type 把框选 / 取反 / 选中 / 清空分开(前两种不经过 onClick)
146
163
  onSelectChange: event => {
147
- report(`选中:${event.action.type} · ${event.count} 个(节点 ${event.points} / 线路 ${event.lines} / 通道 ${event.channels})`)
164
+ report(`选中:${event.action.type} · ${event.count} 个(节点 ${event.points} / 线路 ${event.lines} / 通道 ${event.channels})→ ${event.points ? '节点' : event.lines ? '线路' : event.channels ? '通道' : '空白'}`)
148
165
  options.onSelectChange?.(event)
149
166
  },
150
167
  onCameraChange: camera => options.onCameraChange?.(camera)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gis-view-webgl2",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "WebGL2 矢量视图:点、线与 GeoJSON / 图片背景层,无内置界面,点击 / 双击 / 拖拽可配置",
5
5
  "type": "module",
6
6
  "scripts": {