Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/svg-canvas-graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ const SvgCanvas = () => {
<>
<SvgLayer
elements={elements}
selected={selected}
handlePointerDown={handlePointerDown}
handlePointerMove={handlePointerMove}
handlePointerUp={handlePointerUp}
Expand Down
83 changes: 71 additions & 12 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 @@ -42,7 +44,8 @@ const SvgLayer = React.memo(

const PreStyleComponent = lineStyles[style]?.preComponent as StyleComponent | undefined;
if (PreStyleComponent) {
layers[element.line!.attr.zIndex].pre.push(
const isSelected = selected.has(element.id);
const preComponent = (
<PreStyleComponent
key={`${element.id}.pre`}
id={element.id as LineId}
Expand All @@ -53,10 +56,16 @@ const SvgLayer = React.memo(
handlePointerDown={handleEdgePointerDown}
/>
);
layers[element.line!.attr.zIndex].pre.push(
<g key={`${element.id}.pre-glow`} filter={isSelected ? 'url(#selected-glow)' : undefined}>
{preComponent}
</g>
);
}

const StyleComponent = (lineStyles[style]?.component ?? UnknownLineStyle) as StyleComponent;
layers[element.line!.attr.zIndex].main.push(
const isSelected = selected.has(element.id);
const component = (
<StyleComponent
key={element.id}
id={element.id as LineId}
Expand All @@ -68,9 +77,16 @@ const SvgLayer = React.memo(
/>
);

layers[element.line!.attr.zIndex].main.push(
<g key={`${element.id}-glow`} filter={isSelected ? 'url(#selected-glow)' : undefined}>
{component}
</g>
);

const PostStyleComponent = lineStyles[style]?.postComponent as StyleComponent | undefined;
if (PostStyleComponent) {
layers[element.line!.attr.zIndex].post.push(
const isSelected = selected.has(element.id);
const postComponent = (
<PostStyleComponent
key={`${element.id}.post`}
id={element.id as LineId}
Expand All @@ -81,14 +97,20 @@ const SvgLayer = React.memo(
handlePointerDown={handleEdgePointerDown}
/>
);
layers[element.line!.attr.zIndex].post.push(
<g key={`${element.id}.post-glow`} filter={isSelected ? 'url(#selected-glow)' : undefined}>
{postComponent}
</g>
);
}
} else if (element.type === 'station') {
const attr = element.station!;
const type = attr.type as StationType;

const PreStationComponent = allStations[type]?.preComponent;
if (PreStationComponent) {
layers[element.station!.zIndex].pre.push(
const isSelected = selected.has(element.id);
const preComponent = (
<PreStationComponent
key={`${element.id}.pre`}
id={element.id as StnId}
Expand All @@ -100,10 +122,16 @@ const SvgLayer = React.memo(
handlePointerUp={handlePointerUp}
/>
);
layers[element.station!.zIndex].pre.push(
<g key={`${element.id}.pre-glow`} filter={isSelected ? 'url(#selected-glow)' : undefined}>
{preComponent}
</g>
);
}

const StationComponent = allStations[type]?.component ?? UnknownNode;
layers[element.station!.zIndex].main.push(
const isSelected = selected.has(element.id);
const component = (
<StationComponent
key={element.id}
id={element.id as StnId}
Expand All @@ -116,9 +144,16 @@ const SvgLayer = React.memo(
/>
);

layers[element.station!.zIndex].main.push(
<g key={`${element.id}-glow`} filter={isSelected ? 'url(#selected-glow)' : undefined}>
{component}
</g>
);

const PostStationComponent = allStations[type]?.postComponent;
if (PostStationComponent) {
layers[element.station!.zIndex].post.push(
const isSelected = selected.has(element.id);
const postComponent = (
<PostStationComponent
key={`${element.id}.post`}
id={element.id as StnId}
Expand All @@ -130,14 +165,20 @@ const SvgLayer = React.memo(
handlePointerUp={handlePointerUp}
/>
);
layers[element.station!.zIndex].post.push(
<g key={`${element.id}.post-glow`} filter={isSelected ? 'url(#selected-glow)' : undefined}>
{postComponent}
</g>
);
}
} else if (element.type === 'misc-node') {
const attr = element.miscNode!;
const type = attr.type as MiscNodeType;

const PreMiscNodeComponent = miscNodes[type]?.preComponent;
if (PreMiscNodeComponent) {
layers[element.miscNode!.zIndex].pre.push(
const isSelected = selected.has(element.id);
const preComponent = (
<PreMiscNodeComponent
key={`${element.id}.pre`}
id={element.id as MiscNodeId}
Expand All @@ -150,10 +191,16 @@ const SvgLayer = React.memo(
handlePointerUp={handlePointerUp}
/>
);
layers[element.miscNode!.zIndex].pre.push(
<g key={`${element.id}.pre-glow`} filter={isSelected ? 'url(#selected-glow)' : undefined}>
{preComponent}
</g>
);
}

const MiscNodeComponent = miscNodes[type]?.component ?? UnknownNode;
layers[element.miscNode!.zIndex].main.push(
const isSelected = selected.has(element.id);
const component = (
<MiscNodeComponent
key={element.id}
id={element.id as MiscNodeId}
Expand All @@ -167,9 +214,16 @@ const SvgLayer = React.memo(
/>
);

layers[element.miscNode!.zIndex].main.push(
<g key={`${element.id}-glow`} filter={isSelected ? 'url(#selected-glow)' : undefined}>
{component}
</g>
);

const PostMiscNodeComponent = miscNodes[type]?.postComponent;
if (PostMiscNodeComponent) {
layers[element.miscNode!.zIndex].post.push(
const isSelected = selected.has(element.id);
const postComponent = (
<PostMiscNodeComponent
key={`${element.id}.post`}
id={element.id as MiscNodeId}
Expand All @@ -182,6 +236,11 @@ const SvgLayer = React.memo(
handlePointerUp={handlePointerUp}
/>
);
layers[element.miscNode!.zIndex].post.push(
<g key={`${element.id}.post-glow`} filter={isSelected ? 'url(#selected-glow)' : undefined}>
{postComponent}
</g>
);
}
}
}
Expand All @@ -192,7 +251,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 @@ -393,6 +393,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>
{gridLines && (
<GridLines
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
Loading