gis-view-webgl2 0.1.2 → 0.1.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.
Files changed (66) hide show
  1. package/README.md +21 -14
  2. package/dist/gis-view.css +1 -1
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.d.ts +421 -76
  5. package/dist/index.js +1 -1
  6. package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +151 -0
  7. package/docs/GisView.md +4 -4
  8. 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 +137 -0
  9. package/docs/api/index.html +23 -9
  10. package/docs/demos/ApiCoverageDemo.vue +644 -0
  11. package/docs/demos/ChannelDemo.vue +14 -12
  12. package/docs/demos/ClusterDemo.vue +251 -0
  13. package/docs/demos/CopyMergeDemo.vue +3 -4
  14. package/docs/demos/EventDemo.vue +3 -3
  15. package/docs/demos/ExtensionDemo.vue +69 -12
  16. package/docs/demos/GisViewDemo.vue +64 -8809
  17. package/docs/demos/SearchFilterDemo.vue +13 -11
  18. package/docs/demos/api-index.js +58 -47
  19. package/docs/demos/demos.js +34 -17
  20. package/docs/demos/gis-view-demo/GisViewDemoCardHeader.vue +29 -0
  21. package/docs/demos/gis-view-demo/GisViewDemoDialogs.vue +82 -0
  22. package/docs/demos/gis-view-demo/GisViewDemoFooter.vue +16 -0
  23. package/docs/demos/gis-view-demo/GisViewDemoHeading.vue +16 -0
  24. package/docs/demos/gis-view-demo/GisViewDemoPanelStack.vue +71 -0
  25. package/docs/demos/gis-view-demo/GisViewDemoStage.vue +219 -0
  26. package/docs/demos/gis-view-demo/demo.css +1230 -0
  27. package/docs/demos/gis-view-demo/demoContext.js +12 -0
  28. package/docs/demos/gis-view-demo/panels/DataPanels.vue +303 -0
  29. package/docs/demos/gis-view-demo/panels/OpsPanels.vue +174 -0
  30. package/docs/demos/gis-view-demo/panels/StylePanels.vue +561 -0
  31. package/docs/demos/gis-view-demo/panels/ViewPanels.vue +396 -0
  32. package/docs/demos/gis-view-demo/useGisViewDemo.js +6188 -0
  33. 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 +3 -2
  34. package/docs/index.html +41 -4
  35. package/docs/internals/16-03-/344/274/230/345/214/226/344/270/216/345/217/226/350/210/215-/344/270/213.md +1 -1
  36. package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +3 -2
  37. package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +104 -16
  38. package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +15 -14
  39. 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 +40 -3
  40. package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +50 -3
  41. package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +6 -2
  42. 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 +39 -7
  43. package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +13 -4
  44. package/docs/manual/13-/345/212/250/346/225/210/344/270/216/345/233/276/345/205/203.md +3 -0
  45. package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +121 -49
  46. package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +37 -3
  47. package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +17 -8
  48. package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +45 -25
  49. package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +91 -20
  50. 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 +3 -3
  51. package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +25 -1
  52. 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 +11 -8
  53. package/docs/steps/README.md +17 -11
  54. package/docs/steps/selftest-actions.html +3 -5
  55. package/docs/steps/selftest-demo.html +27 -18
  56. package/docs/steps/selftest-demos.html +409 -32
  57. package/docs/steps/selftest-docs.html +33 -4
  58. package/docs/steps/selftest-effects.html +200 -0
  59. package/docs/steps/selftest-features.html +603 -21
  60. package/docs/steps/selftest-labels.html +74 -0
  61. package/docs/steps/selftest-perf.html +293 -20
  62. package/docs/steps/selftest-scale.html +150 -29
  63. package/docs/steps/selftest-select.html +90 -0
  64. package/docs/steps/step-10-full.js +1 -1
  65. package/docs/steps/step-12-events.js +1 -1
  66. package/package.json +1 -1
@@ -123,6 +123,7 @@
123
123
  const plainView = createGisView(host(320, 200), {
124
124
  points: [{ id: 'p0', x: 0, y: 0 }, { id: 'p1', x: 40, y: 0 }],
125
125
  lines: [{ id: 'pl0', from: 'p0', to: 'p1', start: [-20, -20], end: [60, -20] }],
126
+ lineFollowsNode: false,
126
127
  filterWorker: false
127
128
  })
128
129
  await settle()
@@ -132,8 +133,8 @@
132
133
  `首顶点 ${JSON.stringify(firstVertex())}`)
133
134
  plainView.updatePoint('p0', { visible: false })
134
135
  await settle()
135
- check('无筛选:visible: false 的节点当场退场(绑定线的端点退回自身坐标)',
136
- plainView.isPointVisible(0) === false && firstVertex()?.[0] === -20,
136
+ check('无筛选:lineFollowsNode: false 时 visible: false 只收起节点,绑定线停在最后坐标',
137
+ plainView.isPointVisible(0) === false && firstVertex()?.[0] === 0,
137
138
  `首顶点 ${JSON.stringify(firstVertex())}`)
138
139
  plainView.updatePoint('p0', { visible: true })
139
140
  await settle()
@@ -372,6 +373,104 @@
372
373
  await settle()
373
374
  check('cascade: false 换回来,通道回线又出来', cascadeView.listLines({}).length === 2, `画出来的线 ${cascadeView.listLines({}).length} 条`)
374
375
  cascadeView.destroy()
376
+ /* 3.7 删除型筛选:不命中的要素真正从数据里删掉;无 id 线路、点级联、通道、单条事件与历史都走同一条路。
377
+ 初始 filter 在创建期就生效,ready 看到的是删完的数据;undo / redo 只回放数据,不会让 remove 筛选再删一次 */
378
+ const removeChanges = []
379
+ let removeFilterCount = 0
380
+ let removeReady = null
381
+ const filterRemoveView = createGisView(host(420, 240), {
382
+ points: [
383
+ { id: 'keep-a', x: 0, y: 0, keep: true },
384
+ { id: 'drop', x: 50, y: 0, keep: false },
385
+ { id: 'keep-b', x: 100, y: 0, keep: true }
386
+ ],
387
+ lines: [
388
+ { id: 'bound', from: 'drop', to: 'keep-a', keep: true },
389
+ { path: [[50, 20], [100, 20]], keep: false },
390
+ { id: 'kept-line', from: 'keep-a', to: 'keep-b', keep: true }
391
+ ],
392
+ channels: [{ id: 'filter-ch', from: 'drop', to: 'keep-a', lineIds: ['bound'] }],
393
+ filter: { points: { keep: true }, lines: { keep: true }, cascade: 'binding', mode: 'remove' },
394
+ filterWorker: false,
395
+ history: true,
396
+ onReady: () => queueMicrotask(() => {
397
+ const data = filterRemoveView.getData()
398
+ removeReady = { points: data.points.length, lines: data.lines.length, channels: filterRemoveView.getOptions().channels.length }
399
+ }),
400
+ onFilter: () => { removeFilterCount += 1 },
401
+ onDataChange: payload => removeChanges.push(payload)
402
+ })
403
+ await settle()
404
+ const removeData = filterRemoveView.getData()
405
+ const removeChange = removeChanges.find(payload => payload.type === 'filterRemove') ?? null
406
+ const removeFinal = filterRemoveView.getFilterResult()
407
+ check('remove 筛选:初始条件在 ready 前删完(只留命中的点 / 线,空通道一并消失)',
408
+ removeReady?.points === 2 && removeReady?.lines === 1 && removeReady?.channels === 0 &&
409
+ removeData.points.every(point => point.keep === true) && removeData.lines.length === 1 && filterRemoveView.getOptions().channels.length === 0,
410
+ `ready ${JSON.stringify(removeReady)} / 数据 ${removeData.points.length} 点 ${removeData.lines.length} 线 ${filterRemoveView.getOptions().channels.length} 通道`)
411
+ check('remove 筛选:无 id 线路按原下标删除,级联线路与通道一起清掉',
412
+ removeData.lines[0]?.id === 'kept-line' &&
413
+ JSON.stringify(removeChange?.pointIndexes) === JSON.stringify([1]) &&
414
+ JSON.stringify(removeChange?.removedLineIndexes) === JSON.stringify([0, 1]) &&
415
+ removeChange?.removedLines === 2 && removeChange?.removedChannels === 1 &&
416
+ removeChange?.mode === 'remove' && removeChange?.removedPoints?.length === 1 && removeChange?.removedPoints?.[0]?.id === 'drop' &&
417
+ removeChange?.removedLineItems?.length === 2 && removeChange?.removedLineItems.some(line => line?.id === 'bound') &&
418
+ removeChange?.points?.length === 2 && !removeChange.points.some(point => point?.id === 'drop'),
419
+ JSON.stringify({ pointIndexes: removeChange?.pointIndexes, removedLineIndexes: removeChange?.removedLineIndexes, removedPoints: removeChange?.removedPoints?.map(point => point?.id), removedLineItems: removeChange?.removedLineItems?.map(line => line?.id ?? '无 id'), remainingPoints: removeChange?.points?.map(point => point?.id), lines: removeChange?.removedLines, channels: removeChange?.removedChannels }))
420
+ check('remove 筛选:只发一条 filterRemove 数据变更,不发中间的 removePoints / removeLines',
421
+ removeChanges.length === 1 && removeChange?.type === 'filterRemove' && removeChange?.ok === true,
422
+ removeChanges.map(payload => payload.type).join(',') || '无')
423
+ check('remove 筛选:删除完成后隐藏集合为空,并只回调一次 onFilter',
424
+ removeFinal?.hiddenPoints.length === 0 && removeFinal?.hiddenLines.length === 0 && removeFilterCount === 1,
425
+ `hidden ${removeFinal?.hiddenPoints.length}/${removeFinal?.hiddenLines.length} · onFilter ${removeFilterCount}`)
426
+ check('remove 筛选:历史只记一步 filterRemove',
427
+ filterRemoveView.getHistoryState().undoCount === 1 && filterRemoveView.getHistory()[0]?.type === 'filterRemove',
428
+ JSON.stringify(filterRemoveView.getHistory()))
429
+ const removeHistoryChanges = removeChanges.length
430
+ filterRemoveView.undo()
431
+ await settle()
432
+ const removeUndone = filterRemoveView.getData()
433
+ check('remove 筛选撤销:数据原样恢复,不会被仍生效的 remove 条件再次删除',
434
+ removeUndone.points.length === 3 && removeUndone.lines.length === 3 && filterRemoveView.getOptions().channels.length === 1 &&
435
+ removeChanges.filter(payload => payload.type === 'filterRemove').length === 1,
436
+ `${removeUndone.points.length} 点 ${removeUndone.lines.length} 线 ${filterRemoveView.getOptions().channels.length} 通道 / 新增 ${removeChanges.length - removeHistoryChanges} 条事件`)
437
+ check('remove 筛选撤销:筛选仍生效,恢复出来的不命中要素按隐藏态显示',
438
+ filterRemoveView.getFilter()?.mode === 'remove' &&
439
+ JSON.stringify(filterRemoveView.getFilterResult()?.hiddenPoints) === JSON.stringify([1]) &&
440
+ JSON.stringify(filterRemoveView.getFilterResult()?.hiddenLines) === JSON.stringify([0, 1]),
441
+ JSON.stringify({ hiddenPoints: filterRemoveView.getFilterResult()?.hiddenPoints, hiddenLines: filterRemoveView.getFilterResult()?.hiddenLines }))
442
+ filterRemoveView.redo()
443
+ await settle()
444
+ const removeRedone = filterRemoveView.getData()
445
+ check('remove 筛选重做:数据回到删完的状态,不会补发第二条 filterRemove',
446
+ removeRedone.points.length === 2 && removeRedone.lines.length === 1 && filterRemoveView.getOptions().channels.length === 0 &&
447
+ removeChanges.filter(payload => payload.type === 'filterRemove').length === 1,
448
+ `${removeRedone.points.length} 点 ${removeRedone.lines.length} 线 ${filterRemoveView.getOptions().channels.length} 通道`)
449
+ filterRemoveView.destroy()
450
+ const runtimeRemoveChanges = []
451
+ const runtimeRemoveView = createGisView(host(320, 200), {
452
+ points: [{ id: 'r0', x: 0, y: 0, keep: true }, { id: 'r1', x: 60, y: 0, keep: false }],
453
+ filterWorker: false,
454
+ onDataChange: payload => runtimeRemoveChanges.push(payload.type)
455
+ })
456
+ runtimeRemoveView.setFilter({ points: { keep: true }, mode: 'remove' })
457
+ await settle()
458
+ check('remove 筛选:运行时 setFilter 同样只发一条 filterRemove 并删掉不命中项',
459
+ runtimeRemoveView.getData().points.length === 1 && runtimeRemoveChanges.filter(type => type === 'filterRemove').length === 1,
460
+ `${runtimeRemoveView.getData().points.length} 点 / ${runtimeRemoveChanges.join(',') || '无事件'}`)
461
+ runtimeRemoveView.destroy()
462
+ const isolatedRemoveView = createGisView(host(320, 200), {
463
+ points: [{ id: 'i0', x: 0, y: 0, keep: true }, { id: 'i1', x: 80, y: 0, keep: false }],
464
+ lines: [{ id: 'i-line', from: 'i1', to: 'i0', keep: true }],
465
+ filter: { points: { keep: true }, cascade: false, mode: 'remove' },
466
+ filterWorker: false
467
+ })
468
+ await settle()
469
+ const isolatedRemoveData = isolatedRemoveView.getData()
470
+ check('remove 筛选:cascade: false 时删点只删筛选结果,不按 lineFollowsNode 连带删线路',
471
+ isolatedRemoveData.points.length === 1 && isolatedRemoveData.lines.length === 1,
472
+ `${isolatedRemoveData.points.length} 点 / ${isolatedRemoveData.lines.length} 线`)
473
+ isolatedRemoveView.destroy()
375
474
  /* 4. 网格索引:线段按「真正穿过的格子」登记,一层铺不下就换更粗的一层(不再按包围盒兜底) */
376
475
  const segments = [{ start: [0, 0], end: [1000, 1000] }, { start: [0, 950], end: [1000, 1000] }]
377
476
  for (let index = 0; index < 4000; index += 1) {
@@ -776,36 +875,58 @@
776
875
  const claimPath = claimView.getLine('cl')?.path
777
876
  check('追加被线路按 id 认领的节点:回落整份重建,线跟着出来', claimPath?.length === 2 && Math.abs(claimPath[1][0] - 200) < 0.5 && claimView.getStats().pointCount === 3, `${claimPath?.length} 个顶点 / ${claimView.getStats().pointCount} 个点`)
778
877
  claimView.destroy()
779
- /* 自适应比例(zoomScaleAuto)与重定基准(setZoomBaseScale):包围盒 / 比例一变,节点与通道间距一起变 */
780
- const autoView = createGisView(host(640, 320), {
781
- points: [{ id: 'z0', x: 0, y: 0, size: 10 }, { id: 'z1', x: 600, y: 0, size: 10 }],
782
- lines: [{ id: 'zl', from: 'z0', to: 'z1', width: 2 }],
783
- zoomScaleAuto: true,
784
- showGrid: false
785
- })
878
+ /* 适应视图的节点间距(fitSpacing):远处孤立点撑大包围盒时,按最近可见节点中心距反推比例 */
879
+ const spacingPoints = [
880
+ { id: 'f0', x: 0, y: 0, size: 10 },
881
+ { id: 'f0b', x: 0, y: 0, size: 10 },
882
+ { id: 'f1', x: 60, y: 0, size: 10 },
883
+ { id: 'f2', x: 200, y: 0, size: 10 },
884
+ { id: 'f3', x: 0, y: 200, size: 10 },
885
+ { id: 'fx', x: 10000, y: 10000, size: 10 }
886
+ ]
887
+ const fitSpacingBaseView = createGisView(host(640, 320), { points: spacingPoints, showGrid: false })
888
+ const spacingView = createGisView(host(640, 320), { points: spacingPoints, fitSpacing: { minDistance: 24, maxZoom: 0 }, showGrid: false })
786
889
  await settle(140)
787
- const autoFit = autoView.getCamera().scale
788
- const autoSizeFit = autoView.snapshotScene({ background: false }).points[0]?.size ?? 0
789
- check('自适应比例:开关写进 getOptions(面板回显读得到)', autoView.getOptions().zoomScaleAuto === true, String(autoView.getOptions().zoomScaleAuto))
790
- check('自适应比例:联动基准缺省取首次适应视图时的比例', Math.abs(autoView.getZoomBaseScale() - autoFit) < 1e-9, `基准 ${autoView.getZoomBaseScale()} / 相机 ${autoFit}`)
791
- autoView.setCamera({ scale: autoFit * 2, center: [0, 0] })
890
+ const plainFit = fitSpacingBaseView.getCamera().scale
891
+ const spacingFit = spacingView.getCamera().scale
892
+ check('适应节点间距:配置写进 getOptions(面板回显读得到)', spacingView.getOptions().fitSpacing?.minDistance === 24, JSON.stringify(spacingView.getOptions().fitSpacing))
893
+ check('适应节点间距:远处孤立点撑大包围盒后仍提高比例(重合点不算最近距离)', spacingFit > plainFit * 5, `${plainFit} → ${spacingFit}`)
894
+ const spacingCenter = spacingView.getCamera().center
895
+ check('适应节点间距:相机保持在普通适应的包围盒中心', Math.abs(spacingCenter[0] - 5000) < 0.5 && Math.abs(spacingCenter[1] - 5000) < 0.5, JSON.stringify(spacingCenter))
896
+ const spacingViewport = spacingView.getViewport()
897
+ const spacingCenterScreen = spacingView.project(5000, 5000)
898
+ check('适应节点间距:放大围绕普通适应中心发生,不跳到最近节点对', Math.abs(spacingCenterScreen[0] - spacingViewport.cssWidth / 2) < 0.1 && Math.abs(spacingCenterScreen[1] - spacingViewport.cssHeight / 2) < 0.1, JSON.stringify(spacingCenterScreen))
899
+ const spacingScreen0 = spacingView.project(0, 0)
900
+ const spacingScreen1 = spacingView.project(60, 0)
901
+ const spacingScreen = Math.hypot(spacingScreen1[0] - spacingScreen0[0], spacingScreen1[1] - spacingScreen0[1])
902
+ check('适应节点间距:最近节点中心距达到 minDistance(CSS 像素)', Math.abs(spacingScreen - 24) < 0.1, String(spacingScreen))
903
+ spacingView.updateOptions({ fitSpacing: { minDistance: 24, maxZoom: 2 } })
904
+ spacingView.fitView()
792
905
  await settle(60)
793
- const autoSizeZoom = autoView.snapshotScene({ background: false }).points[0]?.size ?? 0
794
- check('自适应比例:相机放大 2 倍,节点绘制尺寸跟着翻倍(没写 pointZoomScale 时按 1 算)', Math.abs(autoSizeZoom - autoSizeFit * 2) < 0.02, `${autoSizeFit} → ${autoSizeZoom}`)
795
- autoView.setZoomBaseScale()
906
+ const spacingCapped = spacingView.getCamera().scale
907
+ check('适应节点间距:maxZoom 限制相对普通适应比例的上限', Math.abs(spacingCapped - plainFit * 2) < 1e-6, `${plainFit} → ${spacingCapped}`)
908
+ spacingView.updateOptions({ fitSpacing: false })
909
+ spacingView.fitView()
796
910
  await settle(60)
797
- const autoSizeBased = autoView.snapshotScene({ background: false }).points[0]?.size ?? 0
798
- check('重定基准:不传参数取当下相机比例,比例系数回到 1(节点回到原样大小)', Math.abs(autoView.getZoomBaseScale() - autoFit * 2) < 1e-9 && Math.abs(autoSizeBased - autoSizeFit) < 0.02, `基准 ${autoView.getZoomBaseScale()} / ${autoSizeZoom} → ${autoSizeBased}`)
799
- autoView.updateOptions({ pointZoomScale: 0 })
800
- autoView.setCamera({ scale: autoFit, center: [0, 0] })
801
- await settle(60)
802
- const autoSizeExplicit = autoView.snapshotScene({ background: false }).points[0]?.size ?? 0
803
- check('自适应比例:显式写过指数就以显式值为准(写 0 即这一项不跟随)', Math.abs(autoSizeExplicit - autoSizeFit) < 0.02, `${autoSizeFit} / ${autoSizeExplicit}`)
804
- autoView.updateOptions({ zoomScaleAuto: false })
805
- autoView.setCamera({ scale: autoFit * 2, center: [0, 0] })
806
- await settle(60)
807
- check('自适应比例:关掉开关之后节点尺寸不再跟随(比例只按显式指数算)', Math.abs((autoView.snapshotScene({ background: false }).points[0]?.size ?? 0) - autoSizeFit) < 0.02, String(autoView.snapshotScene({ background: false }).points[0]?.size))
808
- autoView.destroy()
911
+ const spacingOff = spacingView.getCamera()
912
+ check('适应节点间距:关闭后恢复普通适应比例与包围盒中心', Math.abs(spacingOff.scale - plainFit) < 1e-9 && Math.abs(spacingOff.center[0] - 5000) < 0.5, `${spacingCapped} → ${spacingOff.scale} / ${JSON.stringify(spacingOff.center)}`)
913
+ /* 规则网格里最近距离满地都是:比例仍按最近距离算,相机中心继续沿用普通适应的包围盒中心 */
914
+ const gridPoints = []
915
+ for (let row = 0; row < 5; row += 1) {
916
+ for (let column = 0; column < 5; column += 1) {
917
+ gridPoints.push({ id: `g${column}_${row}`, x: column * 10, y: row * 10, size: 10 })
918
+ }
919
+ }
920
+ gridPoints.push({ id: 'gx', x: 10000, y: 10000, size: 10 })
921
+ const gridSpacingView = createGisView(host(640, 320), { points: gridPoints, fitSpacing: { minDistance: 24, maxZoom: 0 }, showGrid: false })
922
+ await settle(140)
923
+ const gridCenter = gridSpacingView.getCamera().center
924
+ check('适应节点间距:最近距离并列时只调整比例,相机仍停在普通适应中心',
925
+ Math.abs(gridCenter[0] - 5000) <= 0.5 && Math.abs(gridCenter[1] - 5000) <= 0.5,
926
+ JSON.stringify(gridCenter))
927
+ gridSpacingView.destroy()
928
+ fitSpacingBaseView.destroy()
929
+ spacingView.destroy()
809
930
  }
810
931
  catch (error) {
811
932
  check('页面没有抛错', false, String(error && error.stack ? error.stack : error).split('\n').slice(0, 3).join(' | '))
@@ -558,6 +558,96 @@
558
558
  window.Image = nativeImage
559
559
  dualView.destroy()
560
560
  dualHost.remove()
561
+
562
+ /* ---------- 本轮:SVG 导出的描边色,以及 selectTrigger 单击 / 双击 ---------- */
563
+ const svgStrokeHost = document.createElement('div')
564
+ document.body.append(svgStrokeHost)
565
+ const strokeIcon = { type: 'svg', path: 'M3 8 L13 8', viewBox: [0, 0, 16, 16], fill: 'none', stroke: 'currentStrokeColor', strokeWidth: 4, tint: true }
566
+ const svgStrokeView = createGisView(svgStrokeHost, {
567
+ points: [{ id: 'stroke-a', x: 0, y: 0, symbol: strokeIcon, size: 28 }],
568
+ pointStyle: { size: 28, color: '#ffffff', strokeColor: '#e01f1f', strokeWidth: 2 },
569
+ showGrid: false,
570
+ fitOnInit: false,
571
+ initialView: { center: [0, 0], scale: 1 },
572
+ width: 240,
573
+ height: 180
574
+ })
575
+ await settle(120)
576
+ const decodeExportSvg = (url) => new TextDecoder().decode(Uint8Array.from(atob(String(url).slice(String(url).indexOf('base64,') + 7)), character => character.charCodeAt(0)))
577
+ const svgStrokeMarkup = decodeExportSvg(await svgStrokeView.exportImage({ type: 'svg', background: 'none', includeLabels: false }))
578
+ check('SVG 导出:currentStrokeColor 跟着节点描边色走(不再是纯白)', /stroke="rgba\(224, 31, 31, 1\)"/.test(svgStrokeMarkup), (svgStrokeMarkup.match(/<path[^>]*>/) ?? ['没有 path'])[0].slice(0, 160))
579
+ svgStrokeView.destroy()
580
+ svgStrokeHost.remove()
581
+
582
+ const triggerHost = document.createElement('div')
583
+ document.body.append(triggerHost)
584
+ const triggerClicks = []
585
+ const triggerDblClicks = []
586
+ const triggerChanges = []
587
+ const triggerView = createGisView(triggerHost, {
588
+ points: [
589
+ { id: 'trigger-a', x: 0, y: 0, size: 22 },
590
+ { id: 'trigger-b', x: 120, y: 0, size: 22 }
591
+ ],
592
+ features: { select: true },
593
+ showGrid: false,
594
+ fitOnInit: false,
595
+ initialView: { center: [60, 0], scale: 1 },
596
+ width: 400,
597
+ height: 240,
598
+ onClick: payload => triggerClicks.push(payload),
599
+ onDblClick: payload => triggerDblClicks.push(payload),
600
+ onSelectChange: payload => triggerChanges.push(payload)
601
+ })
602
+ await settle(100)
603
+ const triggerBox = triggerView.canvas.getBoundingClientRect()
604
+ const triggerAt = (x, y, extra = {}) => {
605
+ pointerId += 1
606
+ const screen = triggerView.project(x, y)
607
+ const at = { clientX: triggerBox.left + screen[0], clientY: triggerBox.top + screen[1] }
608
+ const common = { bubbles: true, cancelable: true, pointerId, pointerType: 'mouse', button: 0, buttons: 1, ...at, ...extra }
609
+ triggerView.canvas.dispatchEvent(new PointerEvent('pointerdown', common))
610
+ triggerView.canvas.dispatchEvent(new PointerEvent('pointerup', { ...common, buttons: 0 }))
611
+ }
612
+ const triggerClick = async (x, y, extra = {}) => {
613
+ triggerAt(x, y, extra)
614
+ await settle(25)
615
+ }
616
+ const triggerIds = () => triggerView.getSelectionData().ids.join(',')
617
+ await triggerClick(0, 0)
618
+ check('selectTrigger 缺省 click:第一次单击就选中', triggerIds() === 'trigger-a' && triggerClicks.at(-1)?.action?.type === 'select', `${triggerIds()} / ${triggerClicks.at(-1)?.action?.type}`)
619
+ triggerView.selectFeatures(null)
620
+ triggerView.updateOptions({ selectTrigger: 'dblClick' })
621
+ const clicksBeforeDoubleTrigger = triggerClicks.length
622
+ const doublesBeforeTrigger = triggerDblClicks.length
623
+ await triggerClick(0, 0)
624
+ check('selectTrigger: dblClick:第一下只派发 onClick,不改选中集', triggerIds() === '' && triggerClicks.length === clicksBeforeDoubleTrigger + 1 && triggerDblClicks.length === doublesBeforeTrigger, `${triggerIds()} / click ${triggerClicks.length - clicksBeforeDoubleTrigger}`)
625
+ await triggerClick(0, 0)
626
+ check('selectTrigger: dblClick:第二下选中,并同时派发 onClick 与 onDblClick', triggerIds() === 'trigger-a' && triggerClicks.at(-1)?.action?.type === 'select' && triggerClicks.at(-1)?.action?.double === true && triggerDblClicks.length === doublesBeforeTrigger + 1 && triggerDblClicks.at(-1)?.action?.type === 'dblClick', `${triggerIds()} / ${triggerClicks.at(-1)?.action?.type} / dbl ${triggerDblClicks.length - doublesBeforeTrigger}`)
627
+ const doublesBeforeBlank = triggerDblClicks.length
628
+ await triggerClick(200, 80)
629
+ const blankFirstKept = triggerIds() === 'trigger-a'
630
+ await triggerClick(200, 80)
631
+ check('selectTrigger: dblClick:点空白也要等第二下才清空', blankFirstKept && triggerIds() === '' && triggerDblClicks.length === doublesBeforeBlank + 1, `${blankFirstKept} → ${triggerIds() || '空'}`)
632
+ triggerView.updateOptions({ selectTrigger: 'click' })
633
+ await triggerClick(120, 0)
634
+ check('selectTrigger 运行时改回 click 立刻生效', triggerIds() === 'trigger-b', triggerIds())
635
+
636
+ triggerView.updateOptions({ selectTrigger: 'dblClick', multiSelect: true })
637
+ triggerView.selectFeatures(null)
638
+ triggerClicks.length = 0
639
+ triggerDblClicks.length = 0
640
+ await triggerClick(0, 0)
641
+ await triggerClick(0, 0)
642
+ check('selectTrigger: dblClick + multiSelect:第二下取反选中', triggerIds() === 'trigger-a' && triggerClicks.at(-1)?.action?.type === 'toggleSelect' && triggerDblClicks.length === 1, `${triggerIds()} / ${triggerClicks.at(-1)?.action?.type}`)
643
+ await triggerClick(120, 0)
644
+ await triggerClick(120, 0)
645
+ check('selectTrigger: dblClick + multiSelect:再双击另一个节点是累加', triggerIds() === 'trigger-a,trigger-b' && triggerClicks.at(-1)?.action?.type === 'toggleSelect', triggerIds())
646
+ await triggerClick(0, 0)
647
+ await triggerClick(0, 0)
648
+ check('selectTrigger: dblClick + multiSelect:双击已选节点是取消', triggerIds() === 'trigger-b' && triggerClicks.at(-1)?.action?.type === 'toggleSelect', triggerIds())
649
+ triggerView.destroy()
650
+ triggerHost.remove()
561
651
  }
562
652
  catch (error) {
563
653
  results.push('ERROR ' + (error && error.stack ? error.stack : error))
@@ -76,7 +76,7 @@ export const createFullView = (target, options = {}) => {
76
76
  draggable: true,
77
77
  boxSelect: true,
78
78
  waypointEditable: true,
79
- features: { pan: true, zoom: true, hover: true, select: true, contextMenu: true, keyboard: true, channelDraft: true, channelMenu: true },
79
+ features: { pan: true, zoom: true, hover: true, select: true, contextMenu: true, keyboard: true, channelDraft: true },
80
80
  // 组件的回调都带载荷:type / index / item 就是「当前是哪个元素」,world 是光标处的世界坐标
81
81
  onHover: payload => forward.onHover?.(payload),
82
82
  onClick: payload => forward.onClick?.(payload),
@@ -119,7 +119,7 @@ export const createActionView = (target, options = {}) => {
119
119
  draggable: true,
120
120
  boxSelect: true,
121
121
  waypointEditable: true,
122
- features: { pan: true, zoom: true, hover: true, select: true, contextMenu: true, keyboard: true, channelDraft: true, channelMenu: true },
122
+ features: { pan: true, zoom: true, hover: true, select: true, contextMenu: true, keyboard: true, channelDraft: true },
123
123
  // 下面五条是同一条路:每个回调先报一句判读,再交给页面自己那一条(日志、草稿读数都还在)
124
124
  onPointerDown: (payload, event) => {
125
125
  report(describeAction(payload, '按下'))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gis-view-webgl2",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "WebGL2 矢量视图:点、线与 GeoJSON / 图片背景层,无内置界面,点击 / 双击 / 拖拽可配置",
5
5
  "type": "module",
6
6
  "scripts": {