feat: customizable link color per edge
- Add custom_color field to EdgeData type, Edge DB model and all schemas - HomelableEdge applies custom_color as stroke override (before selected highlight) - EDGE_DEFAULT_COLORS extracted to utils/edgeColors.ts (react-refresh compliant) - EdgeModal: color picker row showing effective color (custom or type default) with hex value, Reset button when custom color is active - Auto-migration adds custom_color column to edges table
This commit is contained in:
@@ -64,6 +64,7 @@ export default function App() {
|
||||
label: e.data?.label ?? null,
|
||||
vlan_id: e.data?.vlan_id ?? null,
|
||||
speed: e.data?.speed ?? null,
|
||||
custom_color: e.data?.custom_color ?? null,
|
||||
}))
|
||||
await canvasApi.save({ nodes: nodesToSave, edges: edgesToSave, viewport: {} })
|
||||
markSaved()
|
||||
|
||||
@@ -26,9 +26,11 @@ export function HomelableEdge({ id, sourceX, sourceY, targetX, targetY, sourcePo
|
||||
const [edgePath, labelX, labelY] = getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition })
|
||||
|
||||
const edgeType: EdgeType = data?.type ?? 'ethernet'
|
||||
const customColor = data?.custom_color as string | undefined
|
||||
const style: React.CSSProperties = {
|
||||
...EDGE_STYLES[edgeType],
|
||||
...(edgeType === 'vlan' ? { stroke: getVlanColor(data?.vlan_id as number | undefined) } : {}),
|
||||
...(customColor ? { stroke: customColor } : {}),
|
||||
...(selected ? { stroke: '#00d4ff', filter: 'drop-shadow(0 0 4px #00d4ff88)' } : {}),
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { useState } from 'react'
|
||||
import { RotateCcw } from 'lucide-react'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { EDGE_TYPE_LABELS, type EdgeData, type EdgeType } from '@/types'
|
||||
import { EDGE_DEFAULT_COLORS } from '@/utils/edgeColors'
|
||||
|
||||
const EDGE_TYPES = Object.entries(EDGE_TYPE_LABELS) as [EdgeType, string][]
|
||||
|
||||
@@ -21,6 +23,9 @@ export function EdgeModal({ open, onClose, onSubmit, onDelete, initial, title =
|
||||
const [type, setType] = useState<EdgeType>(initial?.type ?? 'ethernet')
|
||||
const [label, setLabel] = useState(initial?.label ?? '')
|
||||
const [vlanId, setVlanId] = useState(initial?.vlan_id?.toString() ?? '')
|
||||
const [customColor, setCustomColor] = useState<string | undefined>(initial?.custom_color)
|
||||
|
||||
const effectiveColor = customColor ?? EDGE_DEFAULT_COLORS[type]
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
@@ -28,6 +33,7 @@ export function EdgeModal({ open, onClose, onSubmit, onDelete, initial, title =
|
||||
type,
|
||||
label: label || undefined,
|
||||
vlan_id: type === 'vlan' && vlanId ? parseInt(vlanId) : undefined,
|
||||
custom_color: customColor,
|
||||
})
|
||||
onClose()
|
||||
}
|
||||
@@ -84,6 +90,37 @@ export function EdgeModal({ open, onClose, onSubmit, onDelete, initial, title =
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-xs text-muted-foreground">Color</Label>
|
||||
{customColor && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setCustomColor(undefined)}
|
||||
className="flex items-center gap-1 text-[10px] text-muted-foreground/60 hover:text-muted-foreground transition-colors"
|
||||
>
|
||||
<RotateCcw size={10} /> Reset
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<label
|
||||
className="relative flex items-center gap-2.5 px-2.5 h-8 rounded-md border cursor-pointer"
|
||||
style={{ borderColor: customColor ? effectiveColor : '#30363d', background: '#21262d' }}
|
||||
>
|
||||
<input
|
||||
type="color"
|
||||
value={effectiveColor}
|
||||
onChange={(e) => setCustomColor(e.target.value)}
|
||||
className="absolute opacity-0 w-0 h-0"
|
||||
/>
|
||||
<div className="w-4 h-4 rounded-sm shrink-0 border border-white/10" style={{ background: effectiveColor }} />
|
||||
<span className="font-mono text-xs" style={{ color: customColor ? effectiveColor : '#8b949e' }}>
|
||||
{effectiveColor}
|
||||
</span>
|
||||
{!customColor && <span className="text-[10px] text-muted-foreground/50 ml-auto">default</span>}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between gap-2 pt-1">
|
||||
{onDelete ? (
|
||||
<Button type="button" variant="ghost" size="sm" className="text-[#f85149] hover:text-[#f85149] hover:bg-[#f85149]/10" onClick={handleDelete}>
|
||||
|
||||
@@ -49,6 +49,7 @@ export interface EdgeData extends Record<string, unknown> {
|
||||
label?: string
|
||||
vlan_id?: number
|
||||
speed?: string
|
||||
custom_color?: string
|
||||
}
|
||||
|
||||
export const NODE_TYPE_LABELS: Record<NodeType, string> = {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { EdgeType } from '@/types'
|
||||
|
||||
export const EDGE_DEFAULT_COLORS: Record<EdgeType, string> = {
|
||||
ethernet: '#30363d',
|
||||
wifi: '#00d4ff',
|
||||
iot: '#e3b341',
|
||||
vlan: '#00d4ff',
|
||||
virtual: '#8b949e',
|
||||
}
|
||||
Reference in New Issue
Block a user