mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-09-01 17:10:24 +08:00
feat(diagram-engine): corners, borderless fills, shadows and strikethrough
Four more Tailwind classes, all four verified against draw.io's own source in
public/drawio rather than against a prose reference — which is how three earlier
exclusions turned out to be wrong:
rounded-* mxShape.js:1172-1189 — absoluteArcSize=1 switches arcSize to
absolute pixels and halves it, so the same class is the same
corner on every box. Previously excluded as 'percentage only'
shadow-sm..xl mxShape.js:505-535 — getShadowStyle reads five independent
params, not one flag, so Tailwind's offset+blur rungs map one
to one. Previously excluded as 'six sizes collapse to one'
line-through mxConstants.js:2054 FONT_STRIKETHROUGH: 8, read at both
mxText.js:723 and :1040. The bitmask has four bits, not three
border-none the only one of the four that adds something previously
inexpressible: a fill with no outline
Also fixes an edge style growing 76 characters per re-layout, without bound. The
router recomputes ports on every pass, and appending them to a style recovered
from the canvas — which already carried the previous pass's eight port keys —
grew the string forever. draw.io resolves duplicates last-wins so the arrow
always looked right; a byte-identity check is what caught it.
Two traps found while wiring the readback, both the same shape: a value the
THEME emits being recorded as one the model asked for. strokeColor=none from a
filled or ghost role, and rounded=0 from the fallback style. Either one would
outlive a set_role, since that clears style but keeps text.
Deliberately not included, with reasons in tw.ts: per-side borders and per-corner
radius (both would take the shape slot, and what a node IS matters more than
which of its edges show), per-side padding (draw.io's keys pad the label, not the
room left for children), text-shadow (a bare flag with no offset or blur),
opacity (Tailwind's is any integer, not a scale), tracking/uppercase/leading
(absent from draw.io — zero grep hits, not merely coarse).
615 tests, 90 new. Browser-verified: four shadow rungs visibly differ, radius is
real pixels, terminator + rounded-lg becomes a small-cornered rounded rect while
an untouched terminator stays a stadium.
This commit is contained in:
@@ -11,12 +11,6 @@
|
||||
*/
|
||||
|
||||
import { checkNames, resolveStyle } from "./catalog"
|
||||
import {
|
||||
type GraphEdge,
|
||||
type GraphNode,
|
||||
type GraphOptions,
|
||||
graphToOperations,
|
||||
} from "./graph"
|
||||
import {
|
||||
applyOperations,
|
||||
collectNames,
|
||||
@@ -74,6 +68,7 @@ export function restructureDiagram(
|
||||
|
||||
const applied = applyOperations(tree, ops)
|
||||
const errors = [...applied.errors]
|
||||
warnings.push(...applied.warnings)
|
||||
|
||||
// Catch invented names before rendering, so the model gets a correctable error
|
||||
// instead of a diagram with blank squares in it.
|
||||
@@ -124,69 +119,6 @@ export function restructureDiagram(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a flowchart, dependency graph, ER diagram or site map from nodes and arrows alone.
|
||||
*
|
||||
* The model gives no positions and no nesting — just what the boxes are and what points at
|
||||
* what. The engine works out how many rows there are, who shares a row, and who goes left
|
||||
* of whom, then hands the result to the same layout and edge router the architecture
|
||||
* diagrams use.
|
||||
*
|
||||
* This exists because declaring a flowchart as nesting does not work: six steps declared in
|
||||
* their natural order become one column, and every branch then has to jump over the step
|
||||
* beside it. The layering has to come from the arrows, and only the engine can see all of
|
||||
* them at once.
|
||||
*
|
||||
* Replaces the whole diagram rather than adding to it: the layer assignment depends on every
|
||||
* arrow, so one new edge can move half the nodes. Editing afterwards goes through
|
||||
* `restructureDiagram` as usual.
|
||||
*/
|
||||
export function drawGraph(
|
||||
nodes: GraphNode[],
|
||||
edges: GraphEdge[],
|
||||
opts: RestructureOptions & GraphOptions & { title?: string } = {},
|
||||
): RestructureResult {
|
||||
if (nodes.length === 0)
|
||||
return {
|
||||
xml: null,
|
||||
outline: "",
|
||||
errors: ["draw_graph: no nodes — nothing to draw."],
|
||||
warnings: [],
|
||||
}
|
||||
|
||||
const dupes = nodes
|
||||
.map((n) => n.id)
|
||||
.filter((id, i, all) => all.indexOf(id) !== i)
|
||||
if (dupes.length > 0)
|
||||
return {
|
||||
xml: null,
|
||||
outline: "",
|
||||
errors: [
|
||||
`draw_graph: duplicate node id(s): ${[...new Set(dupes)].join(", ")}.`,
|
||||
],
|
||||
warnings: [],
|
||||
}
|
||||
|
||||
const graph = graphToOperations(nodes, edges, opts)
|
||||
const warnings: string[] = []
|
||||
if (graph.unknownEndpoints.length)
|
||||
warnings.push(
|
||||
`Dropped edge(s) naming nodes that were not in the node list: ${graph.unknownEndpoints.join(", ")}.`,
|
||||
)
|
||||
if (graph.backEdges.length)
|
||||
warnings.push(
|
||||
`Loop(s) drawn but not used for ordering: ${graph.backEdges
|
||||
.map((e) => `${e.source}→${e.target}`)
|
||||
.join(", ")}.`,
|
||||
)
|
||||
|
||||
const ops: Operation[] = opts.title
|
||||
? [{ op: "set_title", title: opts.title }, ...graph.operations]
|
||||
: graph.operations
|
||||
const result = restructureDiagram("", ops, opts)
|
||||
return { ...result, warnings: [...warnings, ...result.warnings] }
|
||||
}
|
||||
|
||||
/** Read the current canvas structure without changing it. */
|
||||
export function describeDiagram(
|
||||
currentXml: string,
|
||||
|
||||
Reference in New Issue
Block a user