Skip to content
1 change: 1 addition & 0 deletions src/components/svg-canvas-graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ const SvgCanvas = () => {
<>
<SvgLayer
elements={elements}
selected={selected}
handlePointerDown={handlePointerDown}
handlePointerMove={handlePointerMove}
handlePointerUp={handlePointerUp}
Expand Down
43 changes: 32 additions & 11 deletions src/components/svg-layer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { LineId, MiscNodeId, NodeId, StnId } from '../constants/constants';
import { Id, LineId, MiscNodeId, NodeId, StnId } from '../constants/constants';
import { ExternalLineStyleAttributes, LineStyleComponentProps } from '../constants/lines';
import { MiscNodeType } from '../constants/nodes';
import { StationType } from '../constants/stations';
Expand All @@ -11,6 +11,7 @@ import { default as allStations } from './svgs/stations/stations';

interface SvgLayerProps {
elements: Element[];
selected: Set<Id>;
handlePointerDown: (node: NodeId, e: React.PointerEvent<SVGElement>) => void;
handlePointerMove: (node: NodeId, e: React.PointerEvent<SVGElement>) => void;
handlePointerUp: (node: NodeId, e: React.PointerEvent<SVGElement>) => void;
Expand All @@ -24,7 +25,8 @@ type StyleComponent = React.FC<

const SvgLayer = React.memo(
(props: SvgLayerProps) => {
const { elements, handlePointerDown, handlePointerMove, handlePointerUp, handleEdgePointerDown } = props;
const { elements, selected, handlePointerDown, handlePointerMove, handlePointerUp, handleEdgePointerDown } =
props;

const layers = Object.fromEntries(
Array.from({ length: 21 }, (_, i) => [
Expand All @@ -33,6 +35,9 @@ const SvgLayer = React.memo(
])
);
for (const element of elements) {
const isSelected = selected.has(element.id);
const selectedGlowFilter = isSelected ? 'url(#selected-glow)' : undefined;

if (element.type === 'line') {
const id = element.id as LineId;
const type = element.line!.attr.type;
Expand All @@ -44,7 +49,7 @@ const SvgLayer = React.memo(
const PreStyleComponent = lineStyles[style]?.preComponent as StyleComponent | undefined;
if (PreStyleComponent) {
layers[element.line!.attr.zIndex].pre.push(
<g key={`${id}.pre`} id={`${id}.pre`}>
<g key={`${id}.pre`} id={`${id}.pre`} filter={selectedGlowFilter}>
<PreStyleComponent
id={id}
type={type}
Expand All @@ -59,7 +64,7 @@ const SvgLayer = React.memo(

const StyleComponent = (lineStyles[style]?.component ?? UnknownLineStyle) as StyleComponent;
layers[element.line!.attr.zIndex].main.push(
<g key={id} id={id}>
<g key={id} id={id} filter={selectedGlowFilter}>
<StyleComponent
id={id}
type={type}
Expand All @@ -74,7 +79,7 @@ const SvgLayer = React.memo(
const PostStyleComponent = lineStyles[style]?.postComponent as StyleComponent | undefined;
if (PostStyleComponent) {
layers[element.line!.attr.zIndex].post.push(
<g key={`${id}.post`} id={`${id}.post`}>
<g key={`${id}.post`} id={`${id}.post`} filter={selectedGlowFilter}>
<PostStyleComponent
id={id}
type={type}
Expand All @@ -98,6 +103,7 @@ const SvgLayer = React.memo(
key={`${element.id}.pre`}
id={`${element.id}.pre`}
transform={`translate(${attr.x}, ${attr.y})`}
filter={selectedGlowFilter}
>
<PreStationComponent
id={id}
Expand All @@ -114,7 +120,7 @@ const SvgLayer = React.memo(

const StationComponent = allStations[type]?.component ?? UnknownNode;
layers[element.station!.zIndex].main.push(
<g key={id} id={id} transform={`translate(${attr.x}, ${attr.y})`}>
<g key={id} id={id} transform={`translate(${attr.x}, ${attr.y})`} filter={selectedGlowFilter}>
<StationComponent
id={id}
x={attr.x}
Expand All @@ -130,7 +136,12 @@ const SvgLayer = React.memo(
const PostStationComponent = allStations[type]?.postComponent;
if (PostStationComponent) {
layers[element.station!.zIndex].post.push(
<g key={`${id}.post`} id={`${id}.post`} transform={`translate(${attr.x}, ${attr.y})`}>
<g
key={`${id}.post`}
id={`${id}.post`}
transform={`translate(${attr.x}, ${attr.y})`}
filter={selectedGlowFilter}
>
<PostStationComponent
id={id}
x={attr.x}
Expand All @@ -151,7 +162,12 @@ const SvgLayer = React.memo(
const PreMiscNodeComponent = miscNodes[type]?.preComponent;
if (PreMiscNodeComponent) {
layers[element.miscNode!.zIndex].pre.push(
<g key={`${id}.pre`} id={`${id}.pre`} transform={`translate(${attr.x}, ${attr.y})`}>
<g
key={`${id}.pre`}
id={`${id}.pre`}
transform={`translate(${attr.x}, ${attr.y})`}
filter={selectedGlowFilter}
>
<PreMiscNodeComponent
id={id}
x={attr.x}
Expand All @@ -168,7 +184,7 @@ const SvgLayer = React.memo(

const MiscNodeComponent = miscNodes[type]?.component ?? UnknownNode;
layers[element.miscNode!.zIndex].main.push(
<g key={id} id={id} transform={`translate(${attr.x}, ${attr.y})`}>
<g key={id} id={id} transform={`translate(${attr.x}, ${attr.y})`} filter={selectedGlowFilter}>
<MiscNodeComponent
id={id}
x={attr.x}
Expand All @@ -185,7 +201,12 @@ const SvgLayer = React.memo(
const PostMiscNodeComponent = miscNodes[type]?.postComponent;
if (PostMiscNodeComponent) {
layers[element.miscNode!.zIndex].post.push(
<g key={`${id}.post`} id={`${id}.post`} transform={`translate(${attr.x}, ${attr.y})`}>
<g
key={`${id}.post`}
id={`${id}.post`}
transform={`translate(${attr.x}, ${attr.y})`}
filter={selectedGlowFilter}
>
<PostMiscNodeComponent
id={id}
x={attr.x}
Expand All @@ -208,7 +229,7 @@ const SvgLayer = React.memo(

return jsxElements;
},
(prevProps, nextProps) => prevProps.elements === nextProps.elements
(prevProps, nextProps) => prevProps.elements === nextProps.elements && prevProps.selected === nextProps.selected
);

export default SvgLayer;
26 changes: 26 additions & 0 deletions src/components/svg-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,32 @@ const SvgWrapper = () => {
<rect x="0" y="0" width="2.5" height="2.5" fill="black" fillOpacity="50%" />
<rect x="2.5" y="2.5" width="2.5" height="2.5" fill="black" fillOpacity="50%" />
</pattern>
<filter
id="selected-glow"
// Only apply the filter to the area within the current viewbox
// to correctly show when a path has no width/height in certain directions.
x={svgViewBoxMin.x}
y={svgViewBoxMin.y}
width={(width * svgViewBoxZoom) / 100}
height={(height * svgViewBoxZoom) / 100}
filterUnits="userSpaceOnUse"
>
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 1 0
0 0 0 1 0
0 0 0 0 0
0 0 0 1 0"
result="yellowBase"
/>
<feMorphology operator="dilate" radius="1.5" in="yellowBase" result="thickYellow" />
<feGaussianBlur in="thickYellow" stdDeviation="3" result="blur1" />
<feMerge>
<feMergeNode in="blur1" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>

<g
Expand Down
6 changes: 5 additions & 1 deletion src/util/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,14 @@ export const makeRenderReadySVGElement = async (
if (el.classList.length === 0) el.removeAttribute('class');
});
});
// Remove masks that only help users find and click the elements, but should not be shown on final export.
// remove masks that only help users find and click the elements, but should not be shown on final export
elem.querySelectorAll('[fill="url(#opaque)"]').forEach(el => {
el.remove();
});
// remove selection glow effect on final export
elem.querySelectorAll('[filter="url(#selected-glow)"]').forEach(el => {
el.removeAttribute('filter');
});
// remove virtual nodes and text hinting rect
// remove the overlay elements that are used for event handling or id info
elem.querySelectorAll('.removeMe').forEach(el => {
Expand Down