feat: configurable edge line style + width per type and per edge
Edge render (solid/dashed/dotted) and stroke width were hardcoded per edge type. Expose both as user settings. - Custom Style modal (Edges): line-style buttons, 1-4x width slider, live preview; left-list swatch renders the actual line. - Per-edge EdgeModal: same controls; line style follows the type preset live until overridden. - Renderer applies line_style/width_mult over BASE_STYLES (width scales markers + animation overlays); unset keeps the type default look. - Persist line_style/width_mult through serializer, canvas save, and the edges API (new nullable columns, idempotent migration). ha-relevant: yes
This commit is contained in:
@@ -250,6 +250,19 @@ describe('serializeEdge', () => {
|
||||
expect(result.marker_end).toBe('arrow')
|
||||
})
|
||||
|
||||
it('serializes line style + width multiplier', () => {
|
||||
const edge = makeRfEdge({ data: { type: 'wifi', line_style: 'dotted', width_mult: 3 } })
|
||||
const result = serializeEdge(edge)
|
||||
expect(result.line_style).toBe('dotted')
|
||||
expect(result.width_mult).toBe(3)
|
||||
})
|
||||
|
||||
it('nulls line style + width multiplier when absent', () => {
|
||||
const result = serializeEdge(makeRfEdge())
|
||||
expect(result.line_style).toBeNull()
|
||||
expect(result.width_mult).toBeNull()
|
||||
})
|
||||
|
||||
it('coerces legacy boolean markers to shape strings', () => {
|
||||
const edge = makeRfEdge({ data: { type: 'ethernet', marker_start: true, marker_end: false } })
|
||||
const result = serializeEdge(edge)
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import {
|
||||
EDGE_LINE_STYLES,
|
||||
EDGE_TYPE_BASE_WIDTH,
|
||||
EDGE_TYPE_DEFAULT_LINE,
|
||||
clampWidthMult,
|
||||
dashArrayFor,
|
||||
} from '@/utils/edgeLineStyle'
|
||||
import { EDGE_TYPE_LABELS } from '@/types'
|
||||
|
||||
describe('edgeLineStyle', () => {
|
||||
it('lists the three render styles', () => {
|
||||
expect(EDGE_LINE_STYLES).toEqual(['solid', 'dashed', 'dotted'])
|
||||
})
|
||||
|
||||
it('has a base width + default line for every edge type', () => {
|
||||
for (const t of Object.keys(EDGE_TYPE_LABELS) as (keyof typeof EDGE_TYPE_LABELS)[]) {
|
||||
expect(EDGE_TYPE_BASE_WIDTH[t]).toBeGreaterThan(0)
|
||||
expect(EDGE_LINE_STYLES).toContain(EDGE_TYPE_DEFAULT_LINE[t])
|
||||
}
|
||||
})
|
||||
|
||||
it('clampWidthMult keeps values within 1..4 and defaults to 1', () => {
|
||||
expect(clampWidthMult(undefined)).toBe(1)
|
||||
expect(clampWidthMult(NaN)).toBe(1)
|
||||
expect(clampWidthMult(0)).toBe(1)
|
||||
expect(clampWidthMult(2.5)).toBe(2.5)
|
||||
expect(clampWidthMult(9)).toBe(4)
|
||||
})
|
||||
|
||||
it('dashArrayFor returns undefined for solid and a pattern otherwise', () => {
|
||||
expect(dashArrayFor('solid', 2)).toBeUndefined()
|
||||
expect(dashArrayFor('dashed', 2)).toBe('6 4')
|
||||
expect(dashArrayFor('dotted', 2)).toBe('2 3.6')
|
||||
})
|
||||
|
||||
it('dashArrayFor scales with stroke width', () => {
|
||||
expect(dashArrayFor('dashed', 4)).toBe('12 8')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user