gis-view-webgl2 0.1.13 → 0.1.15
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/dist/index.cjs +1 -1
- package/dist/index.d.ts +140 -60
- package/dist/index.js +1 -1
- package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +73 -5
- package/docs/GisView.md +49 -49
- 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 +87 -16
- package/docs/demos/ApiCoverageDemo.vue +1 -5
- package/docs/demos/BackendSyncDemo.vue +1 -2
- package/docs/demos/BackgroundDemo.vue +68 -1
- package/docs/demos/BasicDemo.vue +76 -76
- package/docs/demos/BatchAddDemo.vue +2 -1
- package/docs/demos/ChannelDemo.vue +1 -3
- package/docs/demos/ClusterDemo.vue +48 -1
- package/docs/demos/CopyMergeDemo.vue +899 -904
- package/docs/demos/DemoShell.vue +336 -336
- package/docs/demos/EventDemo.vue +300 -300
- package/docs/demos/ExtensionDemo.vue +200 -200
- package/docs/demos/GisViewDemo.vue +64 -64
- package/docs/demos/LabelDemo.vue +489 -489
- package/docs/demos/StyleDemo.vue +67 -67
- package/docs/demos/api-index.js +24 -29
- package/docs/demos/demos.js +92 -92
- package/docs/demos/gis-view-demo/useGisViewDemo.js +19 -14
- package/docs/demos/shared/demoData.js +107 -107
- package/docs/demos/shared/demoStyle.css +249 -249
- package/docs/format/02-/350/212/202/347/202/271/344/270/216/347/272/277/350/267/257.md +3 -3
- package/docs/format/03-/346/213/220/347/202/271/350/243/205/351/245/260/344/270/216/346/240/267/345/274/217.md +3 -0
- package/docs/from0/01-/345/205/210/346/212/212/345/234/260/345/233/276/346/203/263/346/270/205/346/245/232.md +8 -7
- package/docs/index.html +3 -3
- package/docs/internals/04-/347/233/270/346/234/272/345/235/220/346/240/207/344/270/216/345/203/217/347/264/240.md +6 -4
- package/docs/internals/14-/350/203/214/346/231/257/345/261/202.md +2 -2
- package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +36 -16
- package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +38 -84
- package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +36 -19
- 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 +1 -1
- 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 +7 -7
- package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +15 -15
- 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 +1 -1
- package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +6 -0
- package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +1 -1
- package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +2 -0
- package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +10 -8
- package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +11 -7
- package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +27 -8
- package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +27 -4
- 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 +3 -3
- package/docs/playground/index.html +1 -1
- package/docs/sample-document/345/255/227/346/256/265/350/257/246/350/247/243.md +2 -2
- package/docs/steps/check-doc-truth.mjs +1 -1
- package/docs/steps/selftest-actions.html +4 -4
- package/docs/steps/selftest-data.html +5 -5
- package/docs/steps/selftest-demos.html +1683 -1619
- package/docs/steps/selftest-docs.html +4 -4
- package/docs/steps/selftest-effects.html +1 -1
- package/docs/steps/selftest-features.html +548 -47
- package/docs/steps/selftest-full.html +566 -566
- package/docs/steps/selftest-labels.html +647 -647
- package/docs/steps/selftest-perf.html +2282 -2237
- package/docs/steps/selftest-select.html +53 -14
- package/docs/steps/step-10-full.js +1 -4
- package/docs/steps/step-12-events.js +1 -4
- package/package.json +43 -43
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
const { compileLineRules, resolveLineStyle } = await import('../../src/gis-view/line.js')
|
|
42
42
|
const { parseColor } = await import('../../src/gis-view/utils.js')
|
|
43
43
|
const { resolveGeoJson } = await import('../../src/gis-view/background.js')
|
|
44
|
+
const { boundsOfData, createGeoMapper, createGeoProjection } = await import('../../src/gis-view/projection.js')
|
|
44
45
|
// 本页断言是照「限数 + 避让」的老口径写的:统一按优化档(labelOptimize: true)跑
|
|
45
46
|
const createGisView = (host, options = {}) => createGisViewRaw(host, { labelOptimize: true, ...options })
|
|
46
47
|
const { dcLineDecoration, dcLineDecorationSymbol, pathSymbol } = await import('../demos/gisStyle.js')
|
|
@@ -344,6 +345,92 @@
|
|
|
344
345
|
Math.abs(rotatedGeo.regions[0].center[0] - 116.66666666666666) < 1e-6 && Math.abs(rotatedGeo.regions[0].center[1] - 216.66666666666669) < 1e-6,
|
|
345
346
|
JSON.stringify({ rotation: rotatedGeo.rotation, bounds: rotatedGeo.bounds, rawBounds: rotatedGeo.rawBounds, center: rotatedGeo.regions[0].center }))
|
|
346
347
|
|
|
348
|
+
// 旋转 / 平移后的投影:经纬度换算必须跟最终画出来的几何完全重合,不能只算 aspectScale
|
|
349
|
+
const rotatedProjection = createGeoProjection(rotatedGeo)
|
|
350
|
+
const rotatedProjectedCorner = rotatedProjection.project(0, 0)
|
|
351
|
+
const rotatedCornerPoint = rotatedGeo.regions[0].polygons[0][0][0]
|
|
352
|
+
const rotatedRoundtrip = rotatedProjection.unproject(rotatedProjectedCorner[0], rotatedProjectedCorner[1])
|
|
353
|
+
check('getGeoProjection 对 geoJsonStyle.bounds + rotation 做完整正反换算',
|
|
354
|
+
Math.abs(rotatedProjectedCorner[0] - rotatedCornerPoint[0]) < 1e-6 && Math.abs(rotatedProjectedCorner[1] - rotatedCornerPoint[1]) < 1e-6 &&
|
|
355
|
+
Math.abs(rotatedRoundtrip[0]) < 1e-9 && Math.abs(rotatedRoundtrip[1]) < 1e-9,
|
|
356
|
+
JSON.stringify({ projected: rotatedProjectedCorner, geometry: rotatedCornerPoint, roundtrip: rotatedRoundtrip }))
|
|
357
|
+
|
|
358
|
+
const aspectMapper = createGeoMapper({ bounds: { minX: 0, minY: 0, maxX: 10, maxY: 10 }, aspectScale: 0.75 }, { mode: 'scale' })
|
|
359
|
+
const aspectMapped = aspectMapper.map([0, 0])
|
|
360
|
+
const aspectUnmapped = aspectMapper.unmap(aspectMapped)
|
|
361
|
+
const mappedLine = aspectMapper.mapLine({ from: 'a', to: 'b', start: [0, 0], end: [10, 10], path: [[0, 0], [10, 10]] })
|
|
362
|
+
const endpointBounds = boundsOfData({ points: [], lines: [{ start: [2, 20], end: [12, 30] }] })
|
|
363
|
+
check('默认 scale 映射与 aspectScale 对齐,start / end 一起映射且参与 fit 范围',
|
|
364
|
+
Math.abs(aspectMapped[0] - 1.25) < 1e-9 && Math.abs(aspectMapped[1]) < 1e-9 &&
|
|
365
|
+
Math.abs(aspectUnmapped[0]) < 1e-9 && Math.abs(aspectUnmapped[1]) < 1e-9 &&
|
|
366
|
+
Array.isArray(mappedLine.start) && Array.isArray(mappedLine.end) && mappedLine.from === 'a' && mappedLine.to === 'b' &&
|
|
367
|
+
endpointBounds?.minX === 2 && endpointBounds?.maxX === 12 && endpointBounds?.minY === 20 && endpointBounds?.maxY === 30,
|
|
368
|
+
JSON.stringify({ mapped: aspectMapped, roundtrip: aspectUnmapped, line: mappedLine, endpointBounds }))
|
|
369
|
+
|
|
370
|
+
const nestedMapper = createGeoMapper({ bounds: { minX: 0, minY: 0, maxX: 10, maxY: 10 }, aspectScale: 1 }, {
|
|
371
|
+
mode: 'scale', scaleX: 2, scaleY: 3, offsetX: 10, offsetY: 20
|
|
372
|
+
})
|
|
373
|
+
const nestedChannel = nestedMapper.mapChannel({
|
|
374
|
+
id: 'nested', path: [[1, 1]],
|
|
375
|
+
groups: [{ lineIds: ['l1'], path: [[2, 2], { x: 3, y: 4 }], waypointOffsets: { 1: [1, 2], 2: { x: -1, y: 0.5 } } }]
|
|
376
|
+
})
|
|
377
|
+
const nestedPoint = nestedMapper.mapPoint({
|
|
378
|
+
x: 1, y: 1, decorations: [{ at: [2, 2] }, { anchor: 'fixed' }]
|
|
379
|
+
})
|
|
380
|
+
const nestedLine = nestedMapper.mapLine({
|
|
381
|
+
start: { x: 1, y: 1 }, end: [2, 2],
|
|
382
|
+
decorations: [{ position: [3, 3], at: { x: 4, y: 4 } }, { position: 0.5 }, { position: { distance: 10 } }]
|
|
383
|
+
})
|
|
384
|
+
const nestedBounds = boundsOfData({
|
|
385
|
+
points: [{ x: 0, y: 0, decorations: [{ at: [100, 200] }] }],
|
|
386
|
+
lines: [{ path: [{ x: 2, y: 3 }], decorations: [{ position: [4, 5] }] }],
|
|
387
|
+
channels: [{ groups: [{ path: [{ x: 6, y: 7 }] }] }]
|
|
388
|
+
})
|
|
389
|
+
check('映射器覆盖通道分组、对象坐标与固定坐标装饰',
|
|
390
|
+
nestedChannel.path[0][0] === 7 && nestedChannel.path[0][1] === 13 &&
|
|
391
|
+
nestedChannel.groups[0].path[0][0] === 9 && nestedChannel.groups[0].path[0][1] === 16 &&
|
|
392
|
+
nestedChannel.groups[0].path[1].x === 11 && nestedChannel.groups[0].path[1].y === 22 &&
|
|
393
|
+
nestedChannel.groups[0].waypointOffsets[1][0] === 2 && nestedChannel.groups[0].waypointOffsets[1][1] === 6 &&
|
|
394
|
+
nestedChannel.groups[0].waypointOffsets[2].x === -2 && nestedChannel.groups[0].waypointOffsets[2].y === 1.5 &&
|
|
395
|
+
nestedPoint.decorations[0].at[0] === 9 && nestedPoint.decorations[0].at[1] === 16 &&
|
|
396
|
+
nestedLine.start.x === 7 && nestedLine.start.y === 13 && nestedLine.end[0] === 9 && nestedLine.end[1] === 16 &&
|
|
397
|
+
nestedLine.decorations[0].position[0] === 11 && nestedLine.decorations[1].position === 0.5 &&
|
|
398
|
+
nestedLine.decorations[2].position.distance === 10 && nestedLine.decorations[0].at.x === 13 && nestedLine.decorations[0].at.y === 22 &&
|
|
399
|
+
nestedBounds?.minX === 0 && nestedBounds?.maxX === 100 && nestedBounds?.minY === 0 && nestedBounds?.maxY === 200,
|
|
400
|
+
JSON.stringify({ channel: nestedChannel, point: nestedPoint, line: nestedLine, bounds: nestedBounds }))
|
|
401
|
+
|
|
402
|
+
const rotatedVectorMapper = createGeoMapper({
|
|
403
|
+
bounds: { minX: 0, minY: 0, maxX: 10, maxY: 10 }, aspectScale: 1,
|
|
404
|
+
drawTransform: { scaleX: 2, scaleY: 1, offsetX: 100, offsetY: 50, center: [110, 55], rotation: 90 }
|
|
405
|
+
}, { mode: 'scale' })
|
|
406
|
+
const rotatedOffset = rotatedVectorMapper.mapChannel({ groups: [{ lineIds: ['l1'], waypointOffsets: { 0: [1, 0] } }] }).groups[0].waypointOffsets[0]
|
|
407
|
+
check('waypointOffsets 按向量映射,旋转后不叠平移',
|
|
408
|
+
Math.abs(rotatedOffset[0]) < 1e-9 && Math.abs(rotatedOffset[1] + 2) < 1e-9,
|
|
409
|
+
JSON.stringify({ offset: rotatedOffset }))
|
|
410
|
+
|
|
411
|
+
// world 模式:把当前画出来的世界坐标反投影回 GeoJSON 的真实经纬度;旋转背景后仍可往返
|
|
412
|
+
const worldMapper = createGeoMapper(rotatedGeo, { mode: 'world' })
|
|
413
|
+
const worldMappedCorner = worldMapper.map(rotatedProjectedCorner)
|
|
414
|
+
const worldRoundtrip = worldMapper.unmap(worldMappedCorner)
|
|
415
|
+
const worldOffset = worldMapper.mapChannel({ groups: [{ lineIds: ['l1'], waypointOffsets: { 0: [1, 0] } }] }).groups[0].waypointOffsets[0]
|
|
416
|
+
const worldOffsetExpected = [
|
|
417
|
+
rotatedProjection.unproject(1, 0)[0] - rotatedProjection.unproject(0, 0)[0],
|
|
418
|
+
rotatedProjection.unproject(1, 0)[1] - rotatedProjection.unproject(0, 0)[1]
|
|
419
|
+
]
|
|
420
|
+
check('world 映射把画出来的世界坐标反投影成真实经纬度,旋转后往返与向量换算一致',
|
|
421
|
+
Math.abs(worldMappedCorner[0]) < 1e-6 && Math.abs(worldMappedCorner[1]) < 1e-6 &&
|
|
422
|
+
Math.abs(worldRoundtrip[0] - rotatedProjectedCorner[0]) < 1e-6 && Math.abs(worldRoundtrip[1] - rotatedProjectedCorner[1]) < 1e-6 &&
|
|
423
|
+
Math.abs(worldOffset[0] - worldOffsetExpected[0]) < 1e-9 && Math.abs(worldOffset[1] - worldOffsetExpected[1]) < 1e-9,
|
|
424
|
+
JSON.stringify({ mapped: worldMappedCorner, roundtrip: worldRoundtrip, offset: worldOffset, expected: worldOffsetExpected }))
|
|
425
|
+
|
|
426
|
+
const normalizedGeo = resolveGeoJson({
|
|
427
|
+
type: 'Polygon',
|
|
428
|
+
coordinates: [[[0, 0], [0, 0], [10, 0], [10, 5], [10, 5], [0, 5], [0, 0]]]
|
|
429
|
+
}, { aspectScale: 1 }, 1)
|
|
430
|
+
check('GeoJSON 闭合尾点与相邻重复点在剖分前归一化,不重复铺描边',
|
|
431
|
+
normalizedGeo.strokes.length / 4 === 4 && normalizedGeo.fills.length / 6 === 2 && normalizedGeo.rawBounds?.minX === 0 && normalizedGeo.rawBounds?.maxX === 10,
|
|
432
|
+
JSON.stringify({ segments: normalizedGeo.strokes.length / 4, triangles: normalizedGeo.fills.length / 6, rawBounds: normalizedGeo.rawBounds }))
|
|
433
|
+
|
|
347
434
|
// 背景编辑模式:网架的点击 / 拖点 / 平移 / 右键 / 删除键全部停用,滚轮缩放仍保留
|
|
348
435
|
const editingBox = canvas.getBoundingClientRect()
|
|
349
436
|
const editingPointScreen = id => {
|
|
@@ -531,6 +618,59 @@
|
|
|
531
618
|
Math.abs(apiPatchedState.width - (apiWidthBefore + 2)) < 1e-6 && Math.abs(apiPatchedState.rotation - 15) < 1e-6 &&
|
|
532
619
|
view.getOptions().background?.geoJson?.type === 'FeatureCollection' && view.getOptions().background?.geoJsonStyle?.rotation === 15,
|
|
533
620
|
JSON.stringify({ width: apiPatchedState.width, rotation: apiPatchedState.rotation }))
|
|
621
|
+
const networkPoint = view.getData().points[0]
|
|
622
|
+
const expectedNetworkLngLat = view.worldToLngLat(networkPoint.x, networkPoint.y)
|
|
623
|
+
const convertedNetwork = view.mapDataToLngLat()
|
|
624
|
+
check('mapDataToLngLat:背景编辑后按当前 bounds / rotation 转换整份网架,返回副本且原数据不变',
|
|
625
|
+
Boolean(expectedNetworkLngLat && convertedNetwork?.points?.length && convertedNetwork.lines?.length) && Array.isArray(convertedNetwork.channels) &&
|
|
626
|
+
Math.abs(convertedNetwork.points[0].x - expectedNetworkLngLat[0]) < 1e-9 && Math.abs(convertedNetwork.points[0].y - expectedNetworkLngLat[1]) < 1e-9 &&
|
|
627
|
+
networkPoint.x === 100 && networkPoint.y === -70 && view.getData().points[0].x === 100,
|
|
628
|
+
JSON.stringify({ converted: convertedNetwork?.points?.[0], expected: expectedNetworkLngLat, original: networkPoint }))
|
|
629
|
+
const convertedSnapshot = JSON.stringify(convertedNetwork)
|
|
630
|
+
const restoredNetwork = view.mapLngLatToData(convertedNetwork)
|
|
631
|
+
const originalNetwork = {
|
|
632
|
+
points: view.getData().points,
|
|
633
|
+
lines: view.getData().lines,
|
|
634
|
+
channels: view.getOptions().channels
|
|
635
|
+
}
|
|
636
|
+
const sameGeometry = (left, right) => {
|
|
637
|
+
if (typeof left === 'number' && typeof right === 'number') {
|
|
638
|
+
return Math.abs(left - right) < 1e-7
|
|
639
|
+
}
|
|
640
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
641
|
+
return Array.isArray(left) && Array.isArray(right) && left.length === right.length &&
|
|
642
|
+
left.every((value, index) => sameGeometry(value, right[index]))
|
|
643
|
+
}
|
|
644
|
+
if (left && right && typeof left === 'object' && typeof right === 'object') {
|
|
645
|
+
const leftKeys = Object.keys(left)
|
|
646
|
+
return leftKeys.length === Object.keys(right).length &&
|
|
647
|
+
leftKeys.every(key => Object.prototype.hasOwnProperty.call(right, key) && sameGeometry(left[key], right[key]))
|
|
648
|
+
}
|
|
649
|
+
return left === right
|
|
650
|
+
}
|
|
651
|
+
check('mapLngLatToData:经纬度按当前底图投影写回世界坐标,节点 / 线路 / 通道分组几何与原数据一致,且不改传入的存档',
|
|
652
|
+
Boolean(restoredNetwork?.points?.length && restoredNetwork.lines?.length && Array.isArray(restoredNetwork.channels)) &&
|
|
653
|
+
sameGeometry(restoredNetwork.points, originalNetwork.points) &&
|
|
654
|
+
sameGeometry(restoredNetwork.lines, originalNetwork.lines) &&
|
|
655
|
+
sameGeometry(restoredNetwork.channels, originalNetwork.channels) &&
|
|
656
|
+
JSON.stringify(convertedNetwork) === convertedSnapshot,
|
|
657
|
+
JSON.stringify({ restored: restoredNetwork?.points?.[0], original: originalNetwork.points[0], unchanged: JSON.stringify(convertedNetwork) === convertedSnapshot }))
|
|
658
|
+
const roundtripAnchor = originalNetwork.points[0]
|
|
659
|
+
const roundtripScreenBefore = view.project(roundtripAnchor.x, roundtripAnchor.y)
|
|
660
|
+
view.updateData({ points: [], lines: [], channels: [] })
|
|
661
|
+
const emptyNetworkSizes = [view.getData().points.length, view.getData().lines.length, view.getOptions().channels.length]
|
|
662
|
+
view.updateData(restoredNetwork)
|
|
663
|
+
await hoverFrame()
|
|
664
|
+
const roundtripAnchorAfter = view.getData().points.find(point => point.id === roundtripAnchor.id)
|
|
665
|
+
const roundtripScreenAfter = roundtripAnchorAfter ? view.project(roundtripAnchorAfter.x, roundtripAnchorAfter.y) : null
|
|
666
|
+
check('保存 → 清空 → mapLngLatToData 回灌:节点 / 线路 / 通道恢复,首节点屏幕落点不变',
|
|
667
|
+
emptyNetworkSizes.every(size => size === 0) &&
|
|
668
|
+
roundtripAnchorAfter && Math.abs(roundtripAnchorAfter.x - roundtripAnchor.x) < 1e-7 && Math.abs(roundtripAnchorAfter.y - roundtripAnchor.y) < 1e-7 &&
|
|
669
|
+
view.getData().lines.length === originalNetwork.lines.length && view.getOptions().channels.length === originalNetwork.channels.length &&
|
|
670
|
+
roundtripScreenAfter && Math.hypot(roundtripScreenAfter[0] - roundtripScreenBefore[0], roundtripScreenAfter[1] - roundtripScreenBefore[1]) < 1e-6,
|
|
671
|
+
JSON.stringify({ empty: emptyNetworkSizes, world: roundtripAnchorAfter, screen: [roundtripScreenBefore, roundtripScreenAfter] }))
|
|
672
|
+
view.updateData(originalNetwork)
|
|
673
|
+
await hoverFrame()
|
|
534
674
|
const endEditorReturn = view.endBackgroundEdit()
|
|
535
675
|
await hoverFrame()
|
|
536
676
|
check('endBackgroundEdit:移除编辑层、读数回到 null 并恢复进入前的 backgroundEditing',
|
|
@@ -634,7 +774,7 @@
|
|
|
634
774
|
lines: [{ id: 'bound', from: 'p1', to: 'p2', color: '#1f5fd0', width: 8, decorations: [decoration] }],
|
|
635
775
|
showGrid: false,
|
|
636
776
|
// 这一节要加 / 删拐点,看「钉住」的装饰是不是贴着线走:拐点编辑缺省关(见 src/gis-view/features.js)
|
|
637
|
-
|
|
777
|
+
features: { waypoint: true }
|
|
638
778
|
})
|
|
639
779
|
const shot2 = async () => {
|
|
640
780
|
await settle()
|
|
@@ -1009,7 +1149,7 @@
|
|
|
1009
1149
|
lineLabel: { enabled: true, position: 0.5, maxCount: 8 },
|
|
1010
1150
|
showGrid: false,
|
|
1011
1151
|
// 这里要按住节点拖一下看标签跟不跟:拖拽缺省关(见 src/gis-view/features.js)
|
|
1012
|
-
|
|
1152
|
+
features: { drag: true }
|
|
1013
1153
|
})
|
|
1014
1154
|
const followCanvas = view9.canvas
|
|
1015
1155
|
const followBox = followCanvas.getBoundingClientRect()
|
|
@@ -1152,10 +1292,8 @@
|
|
|
1152
1292
|
fitPadding: 70,
|
|
1153
1293
|
// 复制缺省关(见 src/gis-view/features.js):这一节按住 Alt 点节点要出预览
|
|
1154
1294
|
copy: { enabled: true },
|
|
1155
|
-
// 下面还要按住 Shift
|
|
1156
|
-
boxSelect: true,
|
|
1157
|
-
// Esc 取消复制 / Ctrl+C 收剪贴板 / Ctrl+V 粘贴都走快捷键:键盘也显式打开
|
|
1158
|
-
features: { keyboard: true },
|
|
1295
|
+
// 下面还要按住 Shift 拖出选框验默认快捷键、Ctrl+C / Ctrl+V 验剪贴板:框选与键盘显式打开
|
|
1296
|
+
features: { keyboard: true, boxSelect: true },
|
|
1159
1297
|
// 显式写一遍映射,验证不依赖默认值也能监听到 onPointCopy
|
|
1160
1298
|
shortcuts: { copy: 'ctrl+c', paste: 'ctrl+v' },
|
|
1161
1299
|
onPointCopy: payload => {
|
|
@@ -1363,7 +1501,7 @@
|
|
|
1363
1501
|
fitPadding: 70,
|
|
1364
1502
|
showGrid: false,
|
|
1365
1503
|
// 这一节要按住节点拖到另一个节点上:拖拽缺省关(见 src/gis-view/features.js)
|
|
1366
|
-
|
|
1504
|
+
features: { drag: true },
|
|
1367
1505
|
merge: { distance: 8 },
|
|
1368
1506
|
onPointDrag: payload => cmDragEvents.push(payload),
|
|
1369
1507
|
onPointMerge: payload => {
|
|
@@ -1614,7 +1752,7 @@
|
|
|
1614
1752
|
showGrid: false,
|
|
1615
1753
|
merge: { confirm: payload => Promise.resolve().then(() => payload.confirm()) },
|
|
1616
1754
|
// 拖节点合并不用拖拽开关?用——拖拽缺省关,这里显式打开
|
|
1617
|
-
|
|
1755
|
+
features: { drag: true },
|
|
1618
1756
|
onPointMerge: payload => {
|
|
1619
1757
|
if (payload.phase === 'pending' && cmCancelNext) {
|
|
1620
1758
|
payload.cancel()
|
|
@@ -1650,7 +1788,7 @@
|
|
|
1650
1788
|
showGrid: false,
|
|
1651
1789
|
merge: { distance: 8, channelGroups: 'merge' },
|
|
1652
1790
|
// 拖节点合并不用拖拽开关?用——拖拽缺省关,这里显式打开
|
|
1653
|
-
|
|
1791
|
+
features: { drag: true },
|
|
1654
1792
|
onPointMerge: payload => {
|
|
1655
1793
|
cmMergeEvents5.push(payload)
|
|
1656
1794
|
if (payload.phase === 'pending') {
|
|
@@ -1690,7 +1828,7 @@
|
|
|
1690
1828
|
fitPadding: 70,
|
|
1691
1829
|
showGrid: false,
|
|
1692
1830
|
merge: { trigger: 'center', confirm: false },
|
|
1693
|
-
|
|
1831
|
+
features: { drag: true },
|
|
1694
1832
|
onPointMerge: payload => triggerEvents.push(payload)
|
|
1695
1833
|
})
|
|
1696
1834
|
await settle(120)
|
|
@@ -1711,7 +1849,7 @@
|
|
|
1711
1849
|
fitPadding: 70,
|
|
1712
1850
|
showGrid: false,
|
|
1713
1851
|
merge: { trigger: 'collision', confirm: false },
|
|
1714
|
-
|
|
1852
|
+
features: { drag: true }
|
|
1715
1853
|
})
|
|
1716
1854
|
await settle(120)
|
|
1717
1855
|
const collisionTarget = collisionView.project(140, 0)
|
|
@@ -1753,7 +1891,7 @@
|
|
|
1753
1891
|
// 合并缺省关:这一节要把节点拖到目标上验待确认状态,显式打开
|
|
1754
1892
|
merge: { enabled: true },
|
|
1755
1893
|
// 拖拽同样缺省关:上面这一拖是「按住节点拖」
|
|
1756
|
-
|
|
1894
|
+
features: { drag: true },
|
|
1757
1895
|
onPointMerge: payload => msEvents.push(payload),
|
|
1758
1896
|
onDataChange: payload => msChanges.push(payload)
|
|
1759
1897
|
})
|
|
@@ -1898,7 +2036,7 @@
|
|
|
1898
2036
|
initialView: { center: [0, 0], scale: 2.5 },
|
|
1899
2037
|
showGrid: false,
|
|
1900
2038
|
// 这一节要按住节点拖到另一个节点上:拖拽缺省关
|
|
1901
|
-
|
|
2039
|
+
features: { drag: true }
|
|
1902
2040
|
})
|
|
1903
2041
|
await settle(150)
|
|
1904
2042
|
const cmLowScreen = cmRaiseView.project(-5, 0)
|
|
@@ -1950,7 +2088,7 @@
|
|
|
1950
2088
|
cmRaiseChannelHost.style.cssText = 'width:320px;height:240px'
|
|
1951
2089
|
document.body.appendChild(cmRaiseChannelHost)
|
|
1952
2090
|
const cmRaiseChannelBuild = createChannels([
|
|
1953
|
-
{ id: 'raise-ch', from: 'ch-n1', to: 'ch-n2', lineIds: ['ch-c1', 'ch-c2'] }
|
|
2091
|
+
{ id: 'raise-ch', from: 'ch-n1', to: 'ch-n2', circuits: 2, lineIds: ['ch-c1', 'ch-c2'] }
|
|
1954
2092
|
])
|
|
1955
2093
|
const cmRaiseChannelView = createGisView(cmRaiseChannelHost, {
|
|
1956
2094
|
points: [
|
|
@@ -1958,8 +2096,8 @@
|
|
|
1958
2096
|
{ id: 'ch-n2', x: 30, y: 0, size: 8 }
|
|
1959
2097
|
],
|
|
1960
2098
|
lines: [
|
|
1961
|
-
{ id: 'ch-c1', from: 'ch-n1', to: 'ch-n2', color: '#ff3b30', width: 8 },
|
|
1962
|
-
{ id: 'ch-c2', from: 'ch-n1', to: 'ch-n2', color: '#ff3b30', width: 8 },
|
|
2099
|
+
{ id: 'ch-c1', from: 'ch-n1', to: 'ch-n2', color: '#ff3b30', width: 8, selectStyle: { color: '#ff3b30' }, decorations: [{ position: 0.25, symbol: 'square', size: 6, color: '#22c55e', followSelectColor: true }] },
|
|
2100
|
+
{ id: 'ch-c2', from: 'ch-n1', to: 'ch-n2', color: '#ff3b30', width: 8, selectStyle: { color: '#ff3b30' }, decorations: [{ position: 0.75, symbol: 'square', size: 6, color: '#22c55e', followSelectColor: true }] },
|
|
1963
2101
|
{ id: 'ch-cross', path: [[0, -20], [0, 20]], color: '#1f5fd0', width: 8 }
|
|
1964
2102
|
],
|
|
1965
2103
|
channels: cmRaiseChannelBuild.channels,
|
|
@@ -1982,6 +2120,12 @@
|
|
|
1982
2120
|
cmChannelRaisePixels.push(await cmPixelAt(cmRaiseChannelView, spot))
|
|
1983
2121
|
}
|
|
1984
2122
|
check('选中通道里的一条回线:整条通道(两条回线)都抬到最上面', cmChannelRaisePixels.every(cmIsRed), JSON.stringify(cmChannelRaisePixels))
|
|
2123
|
+
cmRaiseChannelView.selectFeatures([{ type: 'channel', channelId: 'raise-ch' }])
|
|
2124
|
+
await settle(120)
|
|
2125
|
+
const cmChannelScene = cmRaiseChannelView.snapshotScene({ background: false })
|
|
2126
|
+
check('选中整条通道:成员回线的装饰一起抬层并跟随通道选中色',
|
|
2127
|
+
cmChannelScene.raisedDecorations.length === 2 && cmChannelScene.raisedDecorations.every(glyph => glyph.fill === cmChannelScene.raisedLines[0]?.color),
|
|
2128
|
+
JSON.stringify({ selected: cmRaiseChannelView.getSelections().map(entry => entry.type + ':' + entry.id), raisedLines: cmChannelScene.raisedLines.length, decorations: cmChannelScene.raisedDecorations.map(glyph => glyph.fill) }))
|
|
1985
2129
|
cmRaiseChannelView.destroy()
|
|
1986
2130
|
cmRaiseChannelHost.remove()
|
|
1987
2131
|
|
|
@@ -2166,7 +2310,7 @@
|
|
|
2166
2310
|
{ id: 'fa-high-ch', from: 'fa-high-a', to: 'fa-high-b', lineIds: ['fa-l3', 'fa-l4'], groups: [{ id: 'fa-high', lineIds: ['fa-l3', 'fa-l4'] }] }
|
|
2167
2311
|
],
|
|
2168
2312
|
initialView: { center: [0, 0], scale: 1 },
|
|
2169
|
-
|
|
2313
|
+
features: { waypoint: true },
|
|
2170
2314
|
showGrid: false
|
|
2171
2315
|
})
|
|
2172
2316
|
await settle(100)
|
|
@@ -2358,7 +2502,7 @@
|
|
|
2358
2502
|
points: [{ id: 'edge-node', x: 0, y: 0, size: 44, color: '#ffffff', strokeColor: '#4c8dff', strokeWidth: 1.5 }],
|
|
2359
2503
|
initialView: { center: [0, 0], scale: 3 },
|
|
2360
2504
|
showGrid: false,
|
|
2361
|
-
|
|
2505
|
+
features: { drag: true }
|
|
2362
2506
|
})
|
|
2363
2507
|
await settle(180)
|
|
2364
2508
|
// 不传背景(backgroundColor 为 undefined)这一档才走交互期的后备缓冲:与屏幕上那一帧是同一条路
|
|
@@ -2418,9 +2562,8 @@
|
|
|
2418
2562
|
initialView: { center: [0, 0], scale: 3 },
|
|
2419
2563
|
showGrid: false,
|
|
2420
2564
|
pixelRatio: 1,
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
features: { pan: true }
|
|
2565
|
+
// 空白处按下拖动 = 平移、节点按下拖动 = 拖拽:进交互期那一帧要它们才走到非 MSAA 后备缓冲
|
|
2566
|
+
features: { pan: true, drag: true }
|
|
2424
2567
|
})
|
|
2425
2568
|
await settle(180)
|
|
2426
2569
|
check('奇数设备像素的画布:画布宽确实是奇数(吸附必须在设备像素坐标系里做)', cmOddView.canvas.width % 2 === 1, '画布 ' + cmOddView.canvas.width + '×' + cmOddView.canvas.height)
|
|
@@ -2481,11 +2624,10 @@
|
|
|
2481
2624
|
fitPadding: 60,
|
|
2482
2625
|
showGrid: false,
|
|
2483
2626
|
// 这一节要同时验合并与复制预览两条未完成交互:三个开关都缺省关,显式打开
|
|
2484
|
-
draggable: true,
|
|
2485
2627
|
copy: { enabled: true },
|
|
2486
2628
|
merge: { distance: 8 },
|
|
2487
|
-
//
|
|
2488
|
-
features: { channelDraft: true },
|
|
2629
|
+
// 下面还要开始一段通道草稿(点两个节点加通道):草稿与拖拽开关缺省也是关的
|
|
2630
|
+
features: { channelDraft: true, drag: true },
|
|
2489
2631
|
onPointMerge: payload => {
|
|
2490
2632
|
pendMergeEvents.push(payload)
|
|
2491
2633
|
if (payload.phase === 'pending') {
|
|
@@ -2554,7 +2696,7 @@
|
|
|
2554
2696
|
fitPadding: 60,
|
|
2555
2697
|
showGrid: false,
|
|
2556
2698
|
// 逐点开关验的是「重叠时能不能合 / 能不能复制」,先把两个开关打开、拖拽打开
|
|
2557
|
-
|
|
2699
|
+
features: { drag: true },
|
|
2558
2700
|
copy: { enabled: false },
|
|
2559
2701
|
merge: { enabled: false }
|
|
2560
2702
|
})
|
|
@@ -2589,11 +2731,9 @@
|
|
|
2589
2731
|
renderEmitInterval: 0,
|
|
2590
2732
|
// 下面要拖节点验历史、加 / 删拐点验 onDataChange、Ctrl+C / Ctrl+V 验剪贴板:
|
|
2591
2733
|
// 这几个编辑开关缺省都关(见 src/gis-view/features.js),这里显式打开
|
|
2592
|
-
draggable: true,
|
|
2593
|
-
waypointEditable: true,
|
|
2594
2734
|
copy: { enabled: true },
|
|
2595
2735
|
// Delete / Ctrl+C / Ctrl+V 这些快捷键要显式打开
|
|
2596
|
-
features: { keyboard: true },
|
|
2736
|
+
features: { keyboard: true, drag: true, waypoint: true },
|
|
2597
2737
|
onDataChange: payload => evLog.data.push(payload),
|
|
2598
2738
|
onSelectChange: payload => evLog.select.push(payload),
|
|
2599
2739
|
onShortcut: payload => evLog.shortcut.push(payload),
|
|
@@ -2764,6 +2904,90 @@
|
|
|
2764
2904
|
`原第 ${ulSplitIndex} 组 → 第 0 组`)
|
|
2765
2905
|
ulView.destroy()
|
|
2766
2906
|
ulHost.remove()
|
|
2907
|
+
/* 定向更新第一次补上状态样式 / effect:没有全局样式时也要当场进总账,不能等下一次 refresh */
|
|
2908
|
+
const dynPointHost = document.createElement('div')
|
|
2909
|
+
dynPointHost.style.cssText = 'width:420px;height:280px'
|
|
2910
|
+
document.body.appendChild(dynPointHost)
|
|
2911
|
+
const dynPointView = createGisView(dynPointHost, {
|
|
2912
|
+
points: [
|
|
2913
|
+
{ id: 'dyn-p', x: 0, y: 0, size: 16, color: '#334155' },
|
|
2914
|
+
{ id: 'dyn-q', x: 120, y: 0, size: 16 }
|
|
2915
|
+
],
|
|
2916
|
+
lines: [{ id: 'dyn-l', from: 'dyn-p', to: 'dyn-q', color: '#2563eb', width: 4 }],
|
|
2917
|
+
selectionOnTop: false,
|
|
2918
|
+
showGrid: false,
|
|
2919
|
+
fitPadding: 50
|
|
2920
|
+
})
|
|
2921
|
+
await settle(140)
|
|
2922
|
+
dynPointView.updatePoint('dyn-p', { selectStyle: { color: '#e11d48' } })
|
|
2923
|
+
dynPointView.selectFeatures('dyn-p')
|
|
2924
|
+
await settle(140)
|
|
2925
|
+
const dynPointSpot = dynPointView.project(0, 0)
|
|
2926
|
+
const dynPointSnapshot = dynPointView.snapshotScene({ background: false })
|
|
2927
|
+
const dynPointPaint = [...dynPointSnapshot.points, ...dynPointSnapshot.raised]
|
|
2928
|
+
.find(point => Math.hypot(point.x - dynPointSpot[0], point.y - dynPointSpot[1]) < 3)
|
|
2929
|
+
check('updatePoint 首次补 selectStyle 后立即选中:无全局状态样式时也走状态重解析',
|
|
2930
|
+
dynPointPaint?.fill === 'rgba(225,29,72,1)',
|
|
2931
|
+
`节点填充 ${dynPointPaint?.fill}`)
|
|
2932
|
+
dynPointView.deselect()
|
|
2933
|
+
await settle(60)
|
|
2934
|
+
dynPointView.updatePoint('dyn-p', { effect: { radius: 12, period: 8 } })
|
|
2935
|
+
await settle(160)
|
|
2936
|
+
const dynPointStats = dynPointView.getStats()
|
|
2937
|
+
check('updatePoint 首次补 effect 后无需 refresh:涟漪实例与动画时钟当场开起来',
|
|
2938
|
+
dynPointStats.pointEffectCount === 1 && dynPointStats.animating === true,
|
|
2939
|
+
JSON.stringify({ effects: dynPointStats.pointEffectCount, animating: dynPointStats.animating }))
|
|
2940
|
+
const dynPointEffectsBeforeRemove = dynPointView.getEffects().enabled
|
|
2941
|
+
dynPointView.removePoints('dyn-p')
|
|
2942
|
+
await settle(100)
|
|
2943
|
+
const dynPointEffectsAfterRemove = dynPointView.getEffects().enabled
|
|
2944
|
+
check('删掉最后一个带 effect 的节点:逐要素效果总账跟着清零,不会一直把动画判成开启',
|
|
2945
|
+
dynPointEffectsBeforeRemove === true && dynPointEffectsAfterRemove === false,
|
|
2946
|
+
JSON.stringify({ before: dynPointEffectsBeforeRemove, after: dynPointEffectsAfterRemove }))
|
|
2947
|
+
dynPointView.destroy()
|
|
2948
|
+
dynPointHost.remove()
|
|
2949
|
+
|
|
2950
|
+
const dynLineHost = document.createElement('div')
|
|
2951
|
+
dynLineHost.style.cssText = 'width:420px;height:280px'
|
|
2952
|
+
document.body.appendChild(dynLineHost)
|
|
2953
|
+
const dynLineView = createGisView(dynLineHost, {
|
|
2954
|
+
points: [{ id: 'dyn-la', x: 0, y: 0 }, { id: 'dyn-lb', x: 120, y: 0 }],
|
|
2955
|
+
lines: [{ id: 'dyn-line', from: 'dyn-la', to: 'dyn-lb', color: '#2563eb', width: 4 }],
|
|
2956
|
+
selectionOnTop: false,
|
|
2957
|
+
showGrid: false,
|
|
2958
|
+
fitPadding: 50
|
|
2959
|
+
})
|
|
2960
|
+
await settle(140)
|
|
2961
|
+
dynLineView.updateLine('dyn-line', { selectStyle: { color: '#22c55e', width: 10 } })
|
|
2962
|
+
dynLineView.selectFeatures('dyn-line')
|
|
2963
|
+
await settle(140)
|
|
2964
|
+
const dynLineSnapshot = dynLineView.snapshotScene({ background: false })
|
|
2965
|
+
const dynLinePaint = [...dynLineSnapshot.lines, ...dynLineSnapshot.raisedLines]
|
|
2966
|
+
.find(line => line.color === 'rgba(34,197,94,1)' && line.width >= 10)
|
|
2967
|
+
check('updateLine 首次补 selectStyle 后立即选中:无全局状态样式时也走状态重解析',
|
|
2968
|
+
dynLinePaint !== undefined,
|
|
2969
|
+
JSON.stringify((dynLineSnapshot.lines ?? []).map(line => [line.color, line.width])))
|
|
2970
|
+
dynLineView.deselect()
|
|
2971
|
+
await settle(60)
|
|
2972
|
+
dynLineView.updateLine('dyn-line', { effect: { period: 8, size: 10 } })
|
|
2973
|
+
await settle(160)
|
|
2974
|
+
const dynLineStats = dynLineView.getStats()
|
|
2975
|
+
check('updateLine 首次补 effect 后无需 refresh:动点与动画时钟当场开起来',
|
|
2976
|
+
dynLineStats.lineEffectCount === 1 && dynLineStats.animating === true,
|
|
2977
|
+
JSON.stringify({ effects: dynLineStats.lineEffectCount, animating: dynLineStats.animating }))
|
|
2978
|
+
let dynUnboundError = ''
|
|
2979
|
+
try {
|
|
2980
|
+
dynLineView.updatePoint('dyn-la', { id: 'dyn-la-renamed' })
|
|
2981
|
+
}
|
|
2982
|
+
catch (error) {
|
|
2983
|
+
dynUnboundError = error?.message ?? String(error)
|
|
2984
|
+
}
|
|
2985
|
+
const dynUnboundLine = dynLineView.getLine('dyn-line')
|
|
2986
|
+
check('updatePoint 改掉绑定节点 id:旧回线缺端点时按局部路径收口,不抛数组长度错误',
|
|
2987
|
+
dynUnboundError === '' && (dynUnboundLine?.path?.length ?? 0) < 2,
|
|
2988
|
+
JSON.stringify({ error: dynUnboundError || null, path: dynUnboundLine?.path ?? null }))
|
|
2989
|
+
dynLineView.destroy()
|
|
2990
|
+
dynLineHost.remove()
|
|
2767
2991
|
/* 这一轮新增:通道改名 / 通道下增减回线 / 删除分组(问题.txt 第 2 条那几个「方法有吗」) */
|
|
2768
2992
|
const cgNameBefore = cgData.length
|
|
2769
2993
|
cgView.updateChannel('ch-1', { name: '甲通道' })
|
|
@@ -3230,7 +3454,7 @@ check('beginAddChannel({ lineData }):草稿按第二段对齐条数(circuits
|
|
|
3230
3454
|
channels: mvBuilt.channels,
|
|
3231
3455
|
fitPadding: 70,
|
|
3232
3456
|
showGrid: false,
|
|
3233
|
-
|
|
3457
|
+
features: { drag: true },
|
|
3234
3458
|
history: true
|
|
3235
3459
|
})
|
|
3236
3460
|
await settle()
|
|
@@ -3437,6 +3661,103 @@ check('beginAddChannel({ lineData }):草稿按第二段对齐条数(circuits
|
|
|
3437
3661
|
stView.destroy()
|
|
3438
3662
|
stHost.remove()
|
|
3439
3663
|
|
|
3664
|
+
// features 统一收口:别名映射到规范开关、规范键优先、数组明确拒绝
|
|
3665
|
+
const featureAliasHost = document.createElement('div')
|
|
3666
|
+
featureAliasHost.style.cssText = 'width:320px;height:200px'
|
|
3667
|
+
document.body.appendChild(featureAliasHost)
|
|
3668
|
+
const featureAliasView = createGisView(featureAliasHost, {
|
|
3669
|
+
points: [{ id: 'a', x: 0, y: 0 }],
|
|
3670
|
+
showGrid: false,
|
|
3671
|
+
features: {
|
|
3672
|
+
draggable: true,
|
|
3673
|
+
waypointEditable: true,
|
|
3674
|
+
clickable: true,
|
|
3675
|
+
interactive: true,
|
|
3676
|
+
multiSelect: true,
|
|
3677
|
+
toggleSelect: true
|
|
3678
|
+
}
|
|
3679
|
+
})
|
|
3680
|
+
await settle(100)
|
|
3681
|
+
const featureAliasState = featureAliasView.getFeatures()
|
|
3682
|
+
check('features 别名:draggable / waypointEditable / clickable / interactive 映射到规范开关',
|
|
3683
|
+
featureAliasState.drag === true && featureAliasState.waypoint === true && featureAliasState.hover === true && featureAliasState.select === true && featureAliasState.pan === true && featureAliasState.zoom === true && featureAliasState.contextMenu === true,
|
|
3684
|
+
JSON.stringify(featureAliasState))
|
|
3685
|
+
check('features 读数只含 19 个规范键,不把别名混进 getFeatures()',
|
|
3686
|
+
Object.keys(featureAliasState).length === 19 && !Object.prototype.hasOwnProperty.call(featureAliasState, 'draggable') && !Object.prototype.hasOwnProperty.call(featureAliasState, 'interactive'),
|
|
3687
|
+
`${Object.keys(featureAliasState).length} 键:${Object.keys(featureAliasState).join(',')}`)
|
|
3688
|
+
check('features 收口 multiSelect / toggleSelect:有效值可直接驱动复选框',
|
|
3689
|
+
featureAliasState.multiSelect === true && featureAliasState.toggleSelect === true,
|
|
3690
|
+
JSON.stringify({ multiSelect: featureAliasState.multiSelect, toggleSelect: featureAliasState.toggleSelect }))
|
|
3691
|
+
featureAliasView.setFeatures({ draggable: false, drag: true, clickable: false, select: true, interactive: false, pan: true })
|
|
3692
|
+
const featureAliasOverride = featureAliasView.getFeatures()
|
|
3693
|
+
check('features 同一张表同时写别名与规范名:规范名优先',
|
|
3694
|
+
featureAliasOverride.drag === true && featureAliasOverride.select === true && featureAliasOverride.pan === true && featureAliasOverride.hover === false && featureAliasOverride.zoom === false && featureAliasOverride.contextMenu === false,
|
|
3695
|
+
JSON.stringify(featureAliasOverride))
|
|
3696
|
+
featureAliasView.destroy()
|
|
3697
|
+
featureAliasHost.remove()
|
|
3698
|
+
|
|
3699
|
+
// 旧顶层交互写法全部不再生效:只发迁移提示,权限必须走 features(以拖不动作为实际生效判据)
|
|
3700
|
+
const legacyFeatureNames = ['draggable', 'interactive', 'clickable', 'boxSelect', 'multiSelect', 'toggleSelect', 'waypointEditable']
|
|
3701
|
+
const legacyFeatureWarnings = []
|
|
3702
|
+
const legacyFeatureHost = document.createElement('div')
|
|
3703
|
+
legacyFeatureHost.style.cssText = 'width:320px;height:200px'
|
|
3704
|
+
document.body.appendChild(legacyFeatureHost)
|
|
3705
|
+
const legacyFeatureView = createGisView(legacyFeatureHost, {
|
|
3706
|
+
points: [{ id: 'legacy-feature', x: 0, y: 0 }],
|
|
3707
|
+
showGrid: false,
|
|
3708
|
+
draggable: true,
|
|
3709
|
+
interactive: true,
|
|
3710
|
+
clickable: true,
|
|
3711
|
+
boxSelect: true,
|
|
3712
|
+
multiSelect: true,
|
|
3713
|
+
toggleSelect: true,
|
|
3714
|
+
waypointEditable: true,
|
|
3715
|
+
onWarning: message => legacyFeatureWarnings.push(message)
|
|
3716
|
+
})
|
|
3717
|
+
await settle(100)
|
|
3718
|
+
legacyFeatureView.updateOptions({
|
|
3719
|
+
draggable: true,
|
|
3720
|
+
interactive: true,
|
|
3721
|
+
clickable: true,
|
|
3722
|
+
boxSelect: true,
|
|
3723
|
+
multiSelect: true,
|
|
3724
|
+
toggleSelect: true,
|
|
3725
|
+
waypointEditable: true
|
|
3726
|
+
})
|
|
3727
|
+
await cmDrag(legacyFeatureView, legacyFeatureView.project(0, 0), legacyFeatureView.project(40, 40))
|
|
3728
|
+
const legacyFeaturePoint = legacyFeatureView.getData().points[0]
|
|
3729
|
+
const legacyFeatureOptions = legacyFeatureView.getOptions()
|
|
3730
|
+
const legacyFeatureState = legacyFeatureView.getFeatures()
|
|
3731
|
+
const legacyWarningText = legacyFeatureWarnings.join('\n')
|
|
3732
|
+
check('七个旧顶层交互开关在创建与 updateOptions 都不再生效:逐项告警、不留在 getOptions、对应 features 仍关闭且节点拖不动',
|
|
3733
|
+
legacyFeaturePoint.x === 0 && legacyFeaturePoint.y === 0 &&
|
|
3734
|
+
legacyFeatureState.drag === false && legacyFeatureState.pan === false && legacyFeatureState.zoom === false &&
|
|
3735
|
+
legacyFeatureState.hover === false && legacyFeatureState.select === false && legacyFeatureState.contextMenu === false &&
|
|
3736
|
+
legacyFeatureState.boxSelect === false && legacyFeatureState.multiSelect === false &&
|
|
3737
|
+
legacyFeatureState.toggleSelect === false && legacyFeatureState.waypoint === false &&
|
|
3738
|
+
legacyFeatureNames.every(name => !Object.prototype.hasOwnProperty.call(legacyFeatureOptions, name)) &&
|
|
3739
|
+
legacyFeatureNames.every(name => legacyWarningText.includes(`顶层 ${name}`) && legacyWarningText.includes('features:')),
|
|
3740
|
+
JSON.stringify({ state: legacyFeatureState, point: legacyFeaturePoint, options: legacyFeatureOptions, warnings: legacyFeatureWarnings }))
|
|
3741
|
+
legacyFeatureView.destroy()
|
|
3742
|
+
legacyFeatureHost.remove()
|
|
3743
|
+
|
|
3744
|
+
const arrayWarnings = []
|
|
3745
|
+
const arrayHost = document.createElement('div')
|
|
3746
|
+
arrayHost.style.cssText = 'width:320px;height:200px'
|
|
3747
|
+
document.body.appendChild(arrayHost)
|
|
3748
|
+
const arrayView = createGisView(arrayHost, {
|
|
3749
|
+
points: [{ id: 'a', x: 0, y: 0 }],
|
|
3750
|
+
showGrid: false,
|
|
3751
|
+
features: [{ drag: true }],
|
|
3752
|
+
onWarning: message => arrayWarnings.push(message)
|
|
3753
|
+
})
|
|
3754
|
+
await settle(100)
|
|
3755
|
+
check('features 数组写法:明确警告并按缺省全关处理',
|
|
3756
|
+
arrayWarnings.some(message => message.includes('不认数组')) && Object.values(arrayView.getFeatures()).every(value => value === false),
|
|
3757
|
+
JSON.stringify({ warnings: arrayWarnings, state: arrayView.getFeatures() }))
|
|
3758
|
+
arrayView.destroy()
|
|
3759
|
+
arrayHost.remove()
|
|
3760
|
+
|
|
3440
3761
|
/* ---------------- 两层开关重合的那几项:features 读数与详细配置同一条口径(见 15.65) ----------------
|
|
3441
3762
|
tooltip / label / lineLabel / minimap 既能用功能开关关,也能在详细配置里写 enabled: false。
|
|
3442
3763
|
从前 getFeatures() 只看「这份详细配置在不在」:写了 enabled: false 还报 true。现在按「与」算 ——
|
|
@@ -3723,7 +4044,8 @@ check('beginAddChannel({ lineData }):草稿按第二段对齐条数(circuits
|
|
|
3723
4044
|
check('triggerOnBoxSelect: false(缺省):框选只改选中集并收掉旧级联', casView.getCascadeHighlight() === null, JSON.stringify(casView.getCascadeHighlight()))
|
|
3724
4045
|
|
|
3725
4046
|
// 取消选中时级联一起收掉:toggleSelect 的取消只发一条 null,接口取消也走同一口径
|
|
3726
|
-
casView.
|
|
4047
|
+
casView.setFeatures({ ...casView.getFeatures(), toggleSelect: true })
|
|
4048
|
+
casView.updateOptions({ cascadeHighlight: { scope: 'both', triggerOnClick: true, triggerOnMultiSelect: true } })
|
|
3727
4049
|
casView.selectFeatures(null)
|
|
3728
4050
|
await settle(120)
|
|
3729
4051
|
await casClick(casView.project(0, 0))
|
|
@@ -3981,10 +4303,8 @@ check('beginAddChannel({ lineData }):草稿按第二段对齐条数(circuits
|
|
|
3981
4303
|
channels: rdBuilt.channels,
|
|
3982
4304
|
fitPadding: 60,
|
|
3983
4305
|
showGrid: false,
|
|
3984
|
-
//
|
|
3985
|
-
|
|
3986
|
-
// 单选取反 / 右键读数要 select 与 contextMenu(缺省全关)
|
|
3987
|
-
features: { select: true, contextMenu: true },
|
|
4306
|
+
// 拐点编辑缺省关;单选取反 / 右键读数要 select 与 contextMenu
|
|
4307
|
+
features: { select: true, contextMenu: true, waypoint: true },
|
|
3988
4308
|
onClick: payload => rdClicks.push(payload),
|
|
3989
4309
|
onContextMenu: payload => rdMenus.push(payload)
|
|
3990
4310
|
})
|
|
@@ -4054,8 +4374,7 @@ check('beginAddChannel({ lineData }):草稿按第二段对齐条数(circuits
|
|
|
4054
4374
|
channels: csmBuilt.channels,
|
|
4055
4375
|
fitPadding: 60,
|
|
4056
4376
|
showGrid: false,
|
|
4057
|
-
//
|
|
4058
|
-
waypointEditable: false,
|
|
4377
|
+
// 点回线摊开选中集:先不开拐点编辑,Ctrl 点击才能测「整批取反」;加点那一段再打开
|
|
4059
4378
|
features: { select: true }
|
|
4060
4379
|
})
|
|
4061
4380
|
await settle(140)
|
|
@@ -4097,7 +4416,7 @@ check('beginAddChannel({ lineData }):草稿按第二段对齐条数(circuits
|
|
|
4097
4416
|
await csmClick(csmView.project(100, 160))
|
|
4098
4417
|
check('通道外的线路不受选中模式影响(还是只选自己)', csmIds() === 'csm-solo', csmIds())
|
|
4099
4418
|
// Ctrl + 点击给通道回线加点:加完后的选中 / 高亮范围与普通单击共用 channelSelectMode
|
|
4100
|
-
csmView.
|
|
4419
|
+
csmView.setFeatures({ ...csmView.getFeatures(), waypoint: true })
|
|
4101
4420
|
await settle(60)
|
|
4102
4421
|
const csmGroupSkeleton = () => csmView.getChannelGroups('csm-ch').find(group => String(group.id) === 'csm-g2')?.skeleton.length ?? 0
|
|
4103
4422
|
const csmAddBefore = csmGroupSkeleton()
|
|
@@ -4135,7 +4454,7 @@ check('beginAddChannel({ lineData }):草稿按第二段对齐条数(circuits
|
|
|
4135
4454
|
{ id: 'vwd-g2', lineIds: ['vwd-3', 'vwd-4'], path: [[100, -80], [260, -80]], waypointOffsets: { '1': { x: 0, y: 6 } } }
|
|
4136
4455
|
]
|
|
4137
4456
|
}],
|
|
4138
|
-
|
|
4457
|
+
features: { waypoint: true },
|
|
4139
4458
|
waypointVisibility: 'always',
|
|
4140
4459
|
channelWaypointDragMode: 'vertex',
|
|
4141
4460
|
fitPadding: 70,
|
|
@@ -4425,10 +4744,8 @@ check('beginAddChannel({ lineData }):草稿按第二段对齐条数(circuits
|
|
|
4425
4744
|
channels: apiBuilt.channels,
|
|
4426
4745
|
fitPadding: 60,
|
|
4427
4746
|
showGrid: false,
|
|
4428
|
-
//
|
|
4429
|
-
boxSelect: true
|
|
4430
|
-
// 单击选中也要显式打开(缺省全关)
|
|
4431
|
-
features: { select: true }
|
|
4747
|
+
// 单击选中与 Shift 框选都要显式打开(缺省全关)
|
|
4748
|
+
features: { select: true, boxSelect: true }
|
|
4432
4749
|
})
|
|
4433
4750
|
await settle()
|
|
4434
4751
|
const apiIds = () => apiView.getSelectionData().ids.join(',')
|
|
@@ -4620,6 +4937,9 @@ check('beginAddChannel({ lineData }):草稿按第二段对齐条数(circuits
|
|
|
4620
4937
|
layerView.updateLine('lay-v', { zIndex: 0 })
|
|
4621
4938
|
const resetLayer = await layerCenterColor()
|
|
4622
4939
|
check('线路 zIndex:写回 0 之后又回到规则那一层', isRed(resetLayer), `中心像素 ${resetLayer}`)
|
|
4940
|
+
layerView.updateOptions({ background: '#fafafa' })
|
|
4941
|
+
const reusedLineLayer = await layerCenterColor()
|
|
4942
|
+
check('只换背景、线路解析整半复用时,zIndex 层序仍然保留', isRed(reusedLineLayer), `中心像素 ${reusedLineLayer}`)
|
|
4623
4943
|
/* 线路方向箭头:与装饰同一条批次(画在所有线之上),全局开 / 逐条关 / 整份摘都要即时生效 */
|
|
4624
4944
|
layerView.updateOptions({ lineDirection: true })
|
|
4625
4945
|
const directionLayer = await layerCenterColor()
|
|
@@ -4706,6 +5026,187 @@ check('beginAddChannel({ lineData }):草稿按第二段对齐条数(circuits
|
|
|
4706
5026
|
followView.destroy()
|
|
4707
5027
|
followHost.remove()
|
|
4708
5028
|
}
|
|
5029
|
+
/* ---------------- 装饰随所属要素抬层 / 跟随选中色 ---------------- */
|
|
5030
|
+
{
|
|
5031
|
+
const decorationHost = document.createElement('div')
|
|
5032
|
+
decorationHost.style.cssText = 'width:360px;height:240px'
|
|
5033
|
+
document.body.append(decorationHost)
|
|
5034
|
+
const followSvgSymbol = {
|
|
5035
|
+
type: 'svg',
|
|
5036
|
+
path: 'M 2,8 L 14,8',
|
|
5037
|
+
viewBox: [0, 0, 16, 16],
|
|
5038
|
+
fill: 'currentColor',
|
|
5039
|
+
stroke: 'currentStrokeColor',
|
|
5040
|
+
strokeWidth: 4
|
|
5041
|
+
}
|
|
5042
|
+
const decorationView = createGisView(decorationHost, {
|
|
5043
|
+
points: [
|
|
5044
|
+
{
|
|
5045
|
+
id: 'decor-selected-node', x: 100, y: 60, size: 14, color: '#334155',
|
|
5046
|
+
selectStyle: { color: '#e11d48' },
|
|
5047
|
+
decorations: [
|
|
5048
|
+
{ symbol: 'square', size: 36, offset: [0, 0], color: '#f59e0b' },
|
|
5049
|
+
{ symbol: 'circle', size: 12, offset: [18, 0], color: '#06b6d4', followSelectColor: true },
|
|
5050
|
+
{ symbol: followSvgSymbol, size: 12, offset: [0, 18], color: '#f97316', strokeColor: '#0ea5e9', followSelectColor: true }
|
|
5051
|
+
]
|
|
5052
|
+
}
|
|
5053
|
+
],
|
|
5054
|
+
lines: [
|
|
5055
|
+
{
|
|
5056
|
+
id: 'decor-selected-line', path: [[-160, 60], [160, 60]], color: '#2563eb', width: 6,
|
|
5057
|
+
selectStyle: { color: '#22c55e', width: 20 },
|
|
5058
|
+
decorations: [
|
|
5059
|
+
{ position: 0.5, symbol: 'square', size: 36, color: '#ff0000' },
|
|
5060
|
+
{ position: 0.25, symbol: 'circle', size: 14, color: '#7c3aed', followSelectColor: true },
|
|
5061
|
+
{ position: 0.125, symbol: followSvgSymbol, size: 14, color: '#f97316', strokeColor: '#0ea5e9', followSelectColor: true }
|
|
5062
|
+
]
|
|
5063
|
+
},
|
|
5064
|
+
{
|
|
5065
|
+
id: 'decor-plain-line', path: [[-160, -70], [160, -70]], color: '#2563eb', width: 6,
|
|
5066
|
+
decorations: [{ position: 0.5, symbol: 'square', size: 20, color: '#eab308', followSelectColor: true }]
|
|
5067
|
+
}
|
|
5068
|
+
],
|
|
5069
|
+
initialView: { scale: 1, center: [0, 0] },
|
|
5070
|
+
background: { color: '#ffffff' },
|
|
5071
|
+
showGrid: false
|
|
5072
|
+
})
|
|
5073
|
+
await settle()
|
|
5074
|
+
const decorationSnapshot = () => decorationView.snapshotScene({ background: false })
|
|
5075
|
+
const decorationAt = (snapshot, listName, x, y) => {
|
|
5076
|
+
const [sx, sy] = decorationView.project(x, y)
|
|
5077
|
+
return snapshot[listName].find(glyph => Math.hypot(glyph.x - sx, glyph.y - sy) < 2)
|
|
5078
|
+
}
|
|
5079
|
+
const red = 'rgba(255,0,0,1)'
|
|
5080
|
+
const green = 'rgba(34,197,94,1)'
|
|
5081
|
+
const purple = 'rgba(124,58,237,1)'
|
|
5082
|
+
const gold = 'rgba(245,158,11,1)'
|
|
5083
|
+
const rose = 'rgba(225,29,72,1)'
|
|
5084
|
+
const cyan = 'rgba(6,182,212,1)'
|
|
5085
|
+
const yellow = 'rgba(234,179,8,1)'
|
|
5086
|
+
const orangeSvg = 'rgba(249, 115, 22, 1)'
|
|
5087
|
+
const skySvg = 'rgba(14, 165, 233, 1)'
|
|
5088
|
+
const greenSvg = 'rgba(34, 197, 94, 1)'
|
|
5089
|
+
const roseSvg = 'rgba(225, 29, 72, 1)'
|
|
5090
|
+
const svgFill = (glyph) => /\bfill=\x22([^\x22]+)\x22/.exec(glyph?.inner ?? '')?.[1] ?? null
|
|
5091
|
+
const svgStroke = (glyph) => /\bstroke=\x22([^\x22]+)\x22/.exec(glyph?.inner ?? '')?.[1] ?? null
|
|
5092
|
+
|
|
5093
|
+
decorationView.selectFeatures(['decor-selected-line', 'decor-selected-node'])
|
|
5094
|
+
await settle()
|
|
5095
|
+
const selectedDecorations = decorationSnapshot()
|
|
5096
|
+
check('选中线路 / 节点的 decorations 进入抬层,普通线路的装饰仍在普通层',
|
|
5097
|
+
selectedDecorations.raisedLines.length === 1 && selectedDecorations.raised.length === 1 &&
|
|
5098
|
+
selectedDecorations.raisedDecorations.length === 6 && selectedDecorations.decorations.length === 1,
|
|
5099
|
+
JSON.stringify({ raisedLines: selectedDecorations.raisedLines.length, raised: selectedDecorations.raised.length, raisedDecorations: selectedDecorations.raisedDecorations.length, decorations: selectedDecorations.decorations.length }))
|
|
5100
|
+
check('followSelectColor 打开时装饰跟随所属线路 / 节点的最终选中色,关闭时保留自己的颜色',
|
|
5101
|
+
decorationAt(selectedDecorations, 'raisedDecorations', 0, 60)?.fill === red &&
|
|
5102
|
+
decorationAt(selectedDecorations, 'raisedDecorations', -80, 60)?.fill === green &&
|
|
5103
|
+
decorationAt(selectedDecorations, 'raisedDecorations', 100, 60)?.fill === gold &&
|
|
5104
|
+
decorationAt(selectedDecorations, 'raisedDecorations', 118, 60)?.fill === rose &&
|
|
5105
|
+
decorationAt(selectedDecorations, 'decorations', 0, -70)?.fill === yellow,
|
|
5106
|
+
JSON.stringify({ lineOwn: decorationAt(selectedDecorations, 'raisedDecorations', 0, 60)?.fill, lineFollow: decorationAt(selectedDecorations, 'raisedDecorations', -80, 60)?.fill, pointOwn: decorationAt(selectedDecorations, 'raisedDecorations', 100, 60)?.fill, pointFollow: decorationAt(selectedDecorations, 'raisedDecorations', 118, 60)?.fill, unselected: decorationAt(selectedDecorations, 'decorations', 0, -70)?.fill }))
|
|
5107
|
+
const selectedSvgLine = decorationAt(selectedDecorations, 'raisedDecorations', -120, 60)
|
|
5108
|
+
const selectedSvgPoint = decorationAt(selectedDecorations, 'raisedDecorations', 100, 42)
|
|
5109
|
+
check('SVG path 的 followSelectColor 改描边:线路取绿色、节点取玫红,填充仍保留自身橙色',
|
|
5110
|
+
svgFill(selectedSvgLine) === orangeSvg && svgStroke(selectedSvgLine) === greenSvg &&
|
|
5111
|
+
svgFill(selectedSvgPoint) === orangeSvg && svgStroke(selectedSvgPoint) === roseSvg,
|
|
5112
|
+
JSON.stringify({ lineFill: svgFill(selectedSvgLine), lineStroke: svgStroke(selectedSvgLine), pointFill: svgFill(selectedSvgPoint), pointStroke: svgStroke(selectedSvgPoint) }))
|
|
5113
|
+
|
|
5114
|
+
const decorationUrl = await decorationView.exportImage({ background: '#ffffff', includeLabels: false, scale: 1 })
|
|
5115
|
+
const decorationImage = new Image()
|
|
5116
|
+
await new Promise((resolve, reject) => {
|
|
5117
|
+
decorationImage.onload = resolve
|
|
5118
|
+
decorationImage.onerror = () => reject(new Error('导出图片加载失败'))
|
|
5119
|
+
decorationImage.src = decorationUrl
|
|
5120
|
+
})
|
|
5121
|
+
const decorationCanvas = document.createElement('canvas')
|
|
5122
|
+
decorationCanvas.width = decorationImage.width
|
|
5123
|
+
decorationCanvas.height = decorationImage.height
|
|
5124
|
+
const decorationContext = decorationCanvas.getContext('2d')
|
|
5125
|
+
decorationContext.drawImage(decorationImage, 0, 0)
|
|
5126
|
+
const decorationPixels = decorationContext.getImageData(0, 0, decorationCanvas.width, decorationCanvas.height)
|
|
5127
|
+
const decorationFrame = { pixels: decorationPixels.data, width: decorationCanvas.width, height: decorationCanvas.height, base: [255, 255, 255] }
|
|
5128
|
+
const around = (x, y, half = 3) => {
|
|
5129
|
+
const spot = decorationView.project(x, y)
|
|
5130
|
+
return [spot[0] - half, spot[1] - half, spot[0] + half, spot[1] + half]
|
|
5131
|
+
}
|
|
5132
|
+
check('线路装饰抬起后压在选中线路之上(中心仍是装饰自己的红色)',
|
|
5133
|
+
colorCount(decorationFrame, around(0, 60, 2), [255, 0, 0], 20) >= 9,
|
|
5134
|
+
`中心红色像素 ${colorCount(decorationFrame, around(0, 60, 2), [255, 0, 0], 20)}`)
|
|
5135
|
+
check('节点装饰抬起后压在选中线路上、仍在节点本体之下(线宽覆盖区能看到金色装饰)',
|
|
5136
|
+
colorCount(decorationFrame, around(100, 52, 2), [245, 158, 11], 24) >= 6,
|
|
5137
|
+
`金色像素 ${colorCount(decorationFrame, around(100, 52, 2), [245, 158, 11], 24)}`)
|
|
5138
|
+
const decorationSvgUrl = await decorationView.exportImage({ type: 'svg', background: 'none', includeLabels: false })
|
|
5139
|
+
const decorationSvg = new TextDecoder().decode(Uint8Array.from(
|
|
5140
|
+
atob(String(decorationSvgUrl).slice('data:image/svg+xml;base64,'.length)),
|
|
5141
|
+
char => char.charCodeAt(0)
|
|
5142
|
+
))
|
|
5143
|
+
const raisedLinePaint = selectedDecorations.raisedLines[0]?.color
|
|
5144
|
+
const raisedDecorationPaint = decorationAt(selectedDecorations, 'raisedDecorations', 0, 60)?.fill
|
|
5145
|
+
const raisedLineAt = decorationSvg.indexOf(String(raisedLinePaint))
|
|
5146
|
+
const raisedDecorationAt = decorationSvg.indexOf(String(raisedDecorationPaint))
|
|
5147
|
+
check('SVG 导出把抬层装饰排在抬层线路之后',
|
|
5148
|
+
raisedLineAt >= 0 && raisedDecorationAt > raisedLineAt,
|
|
5149
|
+
`线路 ${raisedLineAt} / 装饰 ${raisedDecorationAt}`)
|
|
5150
|
+
|
|
5151
|
+
decorationView.deselect()
|
|
5152
|
+
await settle()
|
|
5153
|
+
const clearedDecorations = decorationSnapshot()
|
|
5154
|
+
check('取消选中后装饰回到普通层,followSelectColor 恢复装饰自己的颜色',
|
|
5155
|
+
clearedDecorations.raisedDecorations.length === 0 && clearedDecorations.decorations.length === 7 &&
|
|
5156
|
+
decorationAt(clearedDecorations, 'decorations', -80, 60)?.fill === purple &&
|
|
5157
|
+
decorationAt(clearedDecorations, 'decorations', 118, 60)?.fill === cyan,
|
|
5158
|
+
JSON.stringify({ raised: clearedDecorations.raisedDecorations.length, decorations: clearedDecorations.decorations.length, line: decorationAt(clearedDecorations, 'decorations', -80, 60)?.fill, point: decorationAt(clearedDecorations, 'decorations', 118, 60)?.fill }))
|
|
5159
|
+
const clearedSvgLine = decorationAt(clearedDecorations, 'decorations', -120, 60)
|
|
5160
|
+
const clearedSvgPoint = decorationAt(clearedDecorations, 'decorations', 100, 42)
|
|
5161
|
+
check('取消选中后 SVG path 的描边恢复装饰自己的 strokeColor',
|
|
5162
|
+
svgFill(clearedSvgLine) === orangeSvg && svgStroke(clearedSvgLine) === skySvg &&
|
|
5163
|
+
svgFill(clearedSvgPoint) === orangeSvg && svgStroke(clearedSvgPoint) === skySvg,
|
|
5164
|
+
JSON.stringify({ lineStroke: svgStroke(clearedSvgLine), pointStroke: svgStroke(clearedSvgPoint) }))
|
|
5165
|
+
|
|
5166
|
+
// selectionOnTop: false 不重排,专测「只换样式」的快路径也会重算 followSelectColor
|
|
5167
|
+
decorationView.updateOptions({ selectionOnTop: false })
|
|
5168
|
+
await settle()
|
|
5169
|
+
decorationView.selectFeatures('decor-selected-line')
|
|
5170
|
+
await settle()
|
|
5171
|
+
const fastLine = decorationSnapshot()
|
|
5172
|
+
check('selectionOnTop: false 时线路装饰仍跟随选中色(样式快路径没有跳过它)',
|
|
5173
|
+
fastLine.raisedDecorations.length === 0 && decorationAt(fastLine, 'decorations', -80, 60)?.fill === green,
|
|
5174
|
+
JSON.stringify({ raised: fastLine.raisedDecorations.length, line: decorationAt(fastLine, 'decorations', -80, 60)?.fill }))
|
|
5175
|
+
const fastSvgLine = decorationAt(fastLine, 'decorations', -120, 60)
|
|
5176
|
+
check('selectionOnTop: false 时 SVG path 的选中色仍写进描边',
|
|
5177
|
+
svgFill(fastSvgLine) === orangeSvg && svgStroke(fastSvgLine) === greenSvg,
|
|
5178
|
+
JSON.stringify({ fill: svgFill(fastSvgLine), stroke: svgStroke(fastSvgLine) }))
|
|
5179
|
+
decorationView.deselect()
|
|
5180
|
+
await settle()
|
|
5181
|
+
const fastLineCleared = decorationSnapshot()
|
|
5182
|
+
check('selectionOnTop: false 取消选中后线路装饰颜色也还原',
|
|
5183
|
+
decorationAt(fastLineCleared, 'decorations', -80, 60)?.fill === purple,
|
|
5184
|
+
`线路装饰 ${decorationAt(fastLineCleared, 'decorations', -80, 60)?.fill}`)
|
|
5185
|
+
check('selectionOnTop: false 取消选中后线路 SVG path 的描边也还原',
|
|
5186
|
+
svgStroke(decorationAt(fastLineCleared, 'decorations', -120, 60)) === skySvg,
|
|
5187
|
+
`线路 SVG 描边 ${svgStroke(decorationAt(fastLineCleared, 'decorations', -120, 60))}`)
|
|
5188
|
+
decorationView.selectFeatures('decor-selected-node')
|
|
5189
|
+
await settle()
|
|
5190
|
+
const fastPoint = decorationSnapshot()
|
|
5191
|
+
check('selectionOnTop: false 时节点装饰跟随选中色(节点槽位补丁也覆盖装饰)',
|
|
5192
|
+
decorationAt(fastPoint, 'decorations', 118, 60)?.fill === rose,
|
|
5193
|
+
`节点装饰 ${decorationAt(fastPoint, 'decorations', 118, 60)?.fill}`)
|
|
5194
|
+
const fastSvgPoint = decorationAt(fastPoint, 'decorations', 100, 42)
|
|
5195
|
+
check('selectionOnTop: false 时节点 SVG path 的选中色写进描边',
|
|
5196
|
+
svgFill(fastSvgPoint) === orangeSvg && svgStroke(fastSvgPoint) === roseSvg,
|
|
5197
|
+
JSON.stringify({ fill: svgFill(fastSvgPoint), stroke: svgStroke(fastSvgPoint) }))
|
|
5198
|
+
decorationView.deselect()
|
|
5199
|
+
await settle()
|
|
5200
|
+
const fastPointCleared = decorationSnapshot()
|
|
5201
|
+
check('selectionOnTop: false 取消选中后节点装饰颜色还原',
|
|
5202
|
+
decorationAt(fastPointCleared, 'decorations', 118, 60)?.fill === cyan,
|
|
5203
|
+
`节点装饰 ${decorationAt(fastPointCleared, 'decorations', 118, 60)?.fill}`)
|
|
5204
|
+
check('selectionOnTop: false 取消选中后节点 SVG path 的描边也还原',
|
|
5205
|
+
svgStroke(decorationAt(fastPointCleared, 'decorations', 100, 42)) === skySvg,
|
|
5206
|
+
`节点 SVG 描边 ${svgStroke(decorationAt(fastPointCleared, 'decorations', 100, 42))}`)
|
|
5207
|
+
decorationView.destroy()
|
|
5208
|
+
decorationHost.remove()
|
|
5209
|
+
}
|
|
4709
5210
|
/* ---------------- 运行时插件:view.use() ---------------- */
|
|
4710
5211
|
// options.plugins 只能在创建那一刻挂;use() 是同一个执行 / 清理口径的运行时版本 ——
|
|
4711
5212
|
// 立即执行、返回卸载函数、销毁时与 options 插件一起按相反顺序清理。这里把这几条各断言一次
|
|
@@ -4736,7 +5237,7 @@ check('beginAddChannel({ lineData }):草稿按第二段对齐条数(circuits
|
|
|
4736
5237
|
})
|
|
4737
5238
|
check('options.plugins 拿到实例本身与带 apiVersion 的只读上下文', optionPluginInstance === pluginView && optionPluginContext?.apiVersion === 1 && optionPluginContext.view === pluginView && Object.isFrozen(optionPluginContext), JSON.stringify(optionPluginContext ? { apiVersion: optionPluginContext.apiVersion, sameView: optionPluginContext.view === pluginView } : null))
|
|
4738
5239
|
check('插件上下文带独立 signal,创建期间未中止', optionSignal instanceof AbortSignal && optionSignal.aborted === false && optionSignal !== null, String(optionSignal?.aborted))
|
|
4739
|
-
let usePluginInstance = null
|
|
5240
|
+
let usePluginInstance = null
|
|
4740
5241
|
let usePluginContext = null
|
|
4741
5242
|
let useDestroyCount = 0
|
|
4742
5243
|
const disposeUse = pluginView.use((v, context) => {
|
|
@@ -5091,7 +5592,7 @@ check('beginAddChannel({ lineData }):草稿按第二段对齐条数(circuits
|
|
|
5091
5592
|
{ id: 'wp2', path: [[0, 120], [120, 120], [240, 120]], width: 6, color: '#1f5fd0' },
|
|
5092
5593
|
{ id: 'wp3', path: [[0, 240], [120, 240], [240, 240]], width: 6, color: '#1f5fd0' }
|
|
5093
5594
|
],
|
|
5094
|
-
|
|
5595
|
+
features: { waypoint: true },
|
|
5095
5596
|
waypointVisibility: 'always',
|
|
5096
5597
|
showGrid: false
|
|
5097
5598
|
})
|
|
@@ -5745,7 +6246,7 @@ check('beginAddChannel({ lineData }):草稿按第二段对齐条数(circuits
|
|
|
5745
6246
|
{ id: 'm2', from: 'a', to: 'c' }
|
|
5746
6247
|
],
|
|
5747
6248
|
channels: [{ id: 'au-ch', from: 'a', to: 'c', lineIds: ['m1', 'm2'] }],
|
|
5748
|
-
|
|
6249
|
+
features: { waypoint: true },
|
|
5749
6250
|
waypointVisibility: 'always',
|
|
5750
6251
|
copy: true,
|
|
5751
6252
|
fitPadding: 60,
|
|
@@ -5854,7 +6355,7 @@ check('beginAddChannel({ lineData }):草稿按第二段对齐条数(circuits
|
|
|
5854
6355
|
{ id: 'beta', lineIds: ['g2'], path: [[30, -40]] }
|
|
5855
6356
|
]
|
|
5856
6357
|
}],
|
|
5857
|
-
|
|
6358
|
+
features: { waypoint: true }
|
|
5858
6359
|
})
|
|
5859
6360
|
await settle()
|
|
5860
6361
|
const gwGeometry = id => gwView.listLines({}).find(entry => entry.item?.id === id)?.path ?? null
|
|
@@ -6324,7 +6825,7 @@ check('beginAddChannel({ lineData }):草稿按第二段对齐条数(circuits
|
|
|
6324
6825
|
initialView: { scale: 1, center: [2, 2] },
|
|
6325
6826
|
labelOptimize: false,
|
|
6326
6827
|
showGrid: false,
|
|
6327
|
-
|
|
6828
|
+
features: { waypoint: true },
|
|
6328
6829
|
history: { limit: 20 }
|
|
6329
6830
|
})
|
|
6330
6831
|
const splitOutcome = splitView.splitPoint('sv-a', [
|