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.
- package/README.md +21 -14
- package/dist/gis-view.css +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +421 -76
- package/dist/index.js +1 -1
- package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +151 -0
- package/docs/GisView.md +4 -4
- 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
- package/docs/api/index.html +23 -9
- package/docs/demos/ApiCoverageDemo.vue +644 -0
- package/docs/demos/ChannelDemo.vue +14 -12
- package/docs/demos/ClusterDemo.vue +251 -0
- package/docs/demos/CopyMergeDemo.vue +3 -4
- package/docs/demos/EventDemo.vue +3 -3
- package/docs/demos/ExtensionDemo.vue +69 -12
- package/docs/demos/GisViewDemo.vue +64 -8809
- package/docs/demos/SearchFilterDemo.vue +13 -11
- package/docs/demos/api-index.js +58 -47
- package/docs/demos/demos.js +34 -17
- package/docs/demos/gis-view-demo/GisViewDemoCardHeader.vue +29 -0
- package/docs/demos/gis-view-demo/GisViewDemoDialogs.vue +82 -0
- package/docs/demos/gis-view-demo/GisViewDemoFooter.vue +16 -0
- package/docs/demos/gis-view-demo/GisViewDemoHeading.vue +16 -0
- package/docs/demos/gis-view-demo/GisViewDemoPanelStack.vue +71 -0
- package/docs/demos/gis-view-demo/GisViewDemoStage.vue +219 -0
- package/docs/demos/gis-view-demo/demo.css +1230 -0
- package/docs/demos/gis-view-demo/demoContext.js +12 -0
- package/docs/demos/gis-view-demo/panels/DataPanels.vue +303 -0
- package/docs/demos/gis-view-demo/panels/OpsPanels.vue +174 -0
- package/docs/demos/gis-view-demo/panels/StylePanels.vue +561 -0
- package/docs/demos/gis-view-demo/panels/ViewPanels.vue +396 -0
- package/docs/demos/gis-view-demo/useGisViewDemo.js +6188 -0
- 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
- package/docs/index.html +41 -4
- 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
- package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +3 -2
- package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +104 -16
- package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +15 -14
- 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
- package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +50 -3
- 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
- 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
- package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +13 -4
- package/docs/manual/13-/345/212/250/346/225/210/344/270/216/345/233/276/345/205/203.md +3 -0
- package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +121 -49
- package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +37 -3
- 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
- package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +45 -25
- package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +91 -20
- 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
- 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
- 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
- package/docs/steps/README.md +17 -11
- package/docs/steps/selftest-actions.html +3 -5
- package/docs/steps/selftest-demo.html +27 -18
- package/docs/steps/selftest-demos.html +409 -32
- package/docs/steps/selftest-docs.html +33 -4
- package/docs/steps/selftest-effects.html +200 -0
- package/docs/steps/selftest-features.html +603 -21
- package/docs/steps/selftest-labels.html +74 -0
- package/docs/steps/selftest-perf.html +293 -20
- package/docs/steps/selftest-scale.html +150 -29
- package/docs/steps/selftest-select.html +90 -0
- package/docs/steps/step-10-full.js +1 -1
- package/docs/steps/step-12-events.js +1 -1
- 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] ===
|
|
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
|
-
/*
|
|
780
|
-
const
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
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
|
|
788
|
-
const
|
|
789
|
-
check('
|
|
790
|
-
check('
|
|
791
|
-
|
|
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
|
|
794
|
-
check('
|
|
795
|
-
|
|
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
|
|
798
|
-
check('
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
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
|
|
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
|
|
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, '按下'))
|