fix(icons): emit valid single-fill SVG so node icons render as image resources
Icon markup had duplicate 'fill' attributes (white body + stroke's fill:none on one element). Inline SVG parses leniently, but as an <image>/<img> data-URI the SVG is parsed as strict XML where duplicate attributes are fatal, showing broken icons on the canvas. Split fill into FILL/NONE/SOLID so each element declares exactly one fill, and add width/height for intrinsic sizing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d495401bc1
commit
701ed61a82
@ -117,7 +117,10 @@ export function createNode(opts: CreateNodeOptions): dia.Element {
|
||||
size: { ...type.size },
|
||||
});
|
||||
|
||||
node.attr("icon/xlinkHref", iconDataUri(type.icon));
|
||||
// Set both href (SVG2) and xlink:href (legacy) for maximum renderer support.
|
||||
const iconUri = iconDataUri(type.icon);
|
||||
node.attr("icon/xlinkHref", iconUri);
|
||||
node.attr("icon/href", iconUri);
|
||||
const props = { ...buildDefaults(type.properties), ...(opts.props ?? {}) };
|
||||
node.attr("label/text", (props.title as string) || type.label);
|
||||
node.attr("accent/fill", (props.color as string) || "#2f80ed");
|
||||
|
||||
@ -4,65 +4,73 @@
|
||||
* an icon by key; the canvas renders it as an <image> via a data URI, keeping
|
||||
* node geometry and the vector art decoupled — exactly the "nodes are defined
|
||||
* by an SVG set with port configuration" model.
|
||||
*
|
||||
* IMPORTANT: markup must be valid XML. When an SVG is loaded as an image
|
||||
* resource (data URI) it is parsed strictly, so every element may carry at most
|
||||
* ONE `fill` attribute. Use FILL (white body), NONE (open line paths), or SOLID.
|
||||
*/
|
||||
|
||||
const S = 'stroke="#2b2f36" stroke-width="2.5" fill="none" stroke-linejoin="round" stroke-linecap="round"';
|
||||
// Stroke styling WITHOUT a fill, so each shape declares its own fill exactly once.
|
||||
const S = 'stroke="#2b2f36" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round"';
|
||||
const FILL = 'fill="#ffffff"';
|
||||
const NONE = 'fill="none"';
|
||||
const SOLID = 'fill="#2b2f36"';
|
||||
|
||||
function svg(inner: string): string {
|
||||
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">${inner}</svg>`;
|
||||
// Explicit width/height give the SVG an intrinsic size so it renders when
|
||||
// referenced from an SVG <image> element (not just an HTML <img>).
|
||||
return `<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">${inner}</svg>`;
|
||||
}
|
||||
|
||||
export const SVG_ICONS: Record<string, string> = {
|
||||
source: svg(
|
||||
`<path d="M10 20 h44 v24 a22 8 0 0 1 -44 0 z" ${FILL} ${S}/>` +
|
||||
`<ellipse cx="32" cy="20" rx="22" ry="8" ${FILL} ${S}/>` +
|
||||
`<path d="M24 30 l8 8 l8 -8" ${S}/>`,
|
||||
`<path d="M24 30 l8 8 l8 -8" ${NONE} ${S}/>`,
|
||||
),
|
||||
sink: svg(
|
||||
`<path d="M12 16 h40 l-6 30 a14 6 0 0 1 -28 0 z" ${FILL} ${S}/>` +
|
||||
`<path d="M26 40 l6 8 l6 -8" ${S}/>`,
|
||||
`<path d="M26 40 l6 8 l6 -8" ${NONE} ${S}/>`,
|
||||
),
|
||||
tank: svg(
|
||||
`<rect x="14" y="12" width="36" height="40" rx="6" ${FILL} ${S}/>` +
|
||||
`<path d="M14 40 h36" ${S}/>` +
|
||||
`<path d="M20 40 a6 5 0 0 0 12 0 a6 5 0 0 0 12 0" ${S}/>`,
|
||||
`<path d="M14 40 h36" ${NONE} ${S}/>` +
|
||||
`<path d="M20 40 a6 5 0 0 0 12 0 a6 5 0 0 0 12 0" ${NONE} ${S}/>`,
|
||||
),
|
||||
pump: svg(
|
||||
`<circle cx="32" cy="32" r="18" ${FILL} ${S}/>` +
|
||||
`<path d="M32 32 l14 -10 M32 32 l14 10 M32 14 v10" ${S}/>` +
|
||||
`<circle cx="32" cy="32" r="4" fill="#2b2f36"/>`,
|
||||
`<path d="M32 32 l14 -10 M32 32 l14 10 M32 14 v10" ${NONE} ${S}/>` +
|
||||
`<circle cx="32" cy="32" r="4" ${SOLID}/>`,
|
||||
),
|
||||
valve: svg(
|
||||
`<path d="M14 18 l18 14 l-18 14 z" ${FILL} ${S}/>` +
|
||||
`<path d="M50 18 l-18 14 l18 14 z" ${FILL} ${S}/>` +
|
||||
`<path d="M32 32 v-14 M24 14 h16" ${S}/>`,
|
||||
`<path d="M32 32 v-14 M24 14 h16" ${NONE} ${S}/>`,
|
||||
),
|
||||
junction: svg(
|
||||
`<path d="M8 32 h48 M32 32 v20" ${S}/>` +
|
||||
`<circle cx="32" cy="32" r="5" fill="#2b2f36"/>`,
|
||||
`<path d="M8 32 h48 M32 32 v20" ${NONE} ${S}/>` +
|
||||
`<circle cx="32" cy="32" r="5" ${SOLID}/>`,
|
||||
),
|
||||
filter: svg(
|
||||
`<rect x="14" y="14" width="36" height="36" rx="4" ${FILL} ${S}/>` +
|
||||
`<path d="M20 24 h24 M20 32 h24 M20 40 h24" ${S}/>`,
|
||||
`<path d="M20 24 h24 M20 32 h24 M20 40 h24" ${NONE} ${S}/>`,
|
||||
),
|
||||
meter: svg(
|
||||
`<circle cx="32" cy="32" r="18" ${FILL} ${S}/>` +
|
||||
`<path d="M32 32 l10 -8" ${S}/>` +
|
||||
`<circle cx="32" cy="32" r="3" fill="#2b2f36"/>` +
|
||||
`<path d="M32 14 v4 M50 32 h-4 M32 50 v-4 M14 32 h4" ${S}/>`,
|
||||
`<path d="M32 32 l10 -8" ${NONE} ${S}/>` +
|
||||
`<circle cx="32" cy="32" r="3" ${SOLID}/>` +
|
||||
`<path d="M32 14 v4 M50 32 h-4 M32 50 v-4 M14 32 h4" ${NONE} ${S}/>`,
|
||||
),
|
||||
heater: svg(
|
||||
`<circle cx="32" cy="32" r="18" ${FILL} ${S}/>` +
|
||||
`<path d="M24 40 q4 -8 0 -16 M32 40 q4 -8 0 -16 M40 40 q4 -8 0 -16" ${S}/>`,
|
||||
`<path d="M24 40 q4 -8 0 -16 M32 40 q4 -8 0 -16 M40 40 q4 -8 0 -16" ${NONE} ${S}/>`,
|
||||
),
|
||||
elbow: svg(
|
||||
`<path d="M16 48 v-16 a16 16 0 0 1 16 -16 h16" ${FILL} ${S}/>` +
|
||||
`<path d="M16 48 v-16 a16 16 0 0 1 16 -16 h16" ${S}/>`,
|
||||
`<path d="M16 48 v-16 a16 16 0 0 1 16 -16 h16" ${FILL} ${S}/>`,
|
||||
),
|
||||
compressor: svg(
|
||||
`<path d="M14 20 h36 v24 h-36 z" ${FILL} ${S}/>` +
|
||||
`<path d="M14 44 l36 -24 M22 44 v-24 M32 44 v-24 M42 44 v-24" ${S}/>`,
|
||||
`<path d="M14 44 l36 -24 M22 44 v-24 M32 44 v-24 M42 44 v-24" ${NONE} ${S}/>`,
|
||||
),
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user