diff --git a/src/components/panels/tools/tools.tsx b/src/components/panels/tools/tools.tsx
index 77bd499b7..699ed58ca 100644
--- a/src/components/panels/tools/tools.tsx
+++ b/src/components/panels/tools/tools.tsx
@@ -17,24 +17,25 @@ import {
useColorModeValue,
} from '@chakra-ui/react';
import { LanguageCode } from '@railmapgen/rmg-translate';
+import { RmgSelect } from '@railmapgen/rmg-components';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { IconContext } from 'react-icons';
import { MdCode, MdExpandLess, MdExpandMore, MdOpenInNew } from 'react-icons/md';
import { Theme } from '../../../constants/constants';
-import { LinePathType } from '../../../constants/lines';
+import { LinePathType, LineStyleType } from '../../../constants/lines';
import { MAX_MASTER_NODE_FREE } from '../../../constants/master';
import { MiscNodeType } from '../../../constants/nodes';
import { StationType } from '../../../constants/stations';
import { useRootDispatch, useRootSelector } from '../../../redux';
import { setToolsPanelExpansion } from '../../../redux/app/app-slice';
-import { setMode, setTheme } from '../../../redux/runtime/runtime-slice';
+import { setMode, setSelectedLineStyle, setTheme } from '../../../redux/runtime/runtime-slice';
import { usePaletteTheme } from '../../../util/hooks';
-import { linePaths } from '../../svgs/lines/lines';
+import { linePaths, lineStyles } from '../../svgs/lines/lines';
import miscNodes from '../../svgs/nodes/misc-nodes';
import stations from '../../svgs/stations/stations';
import ThemeButton from '../theme-button';
-import { localizedMiscNodes, localizedStaions } from './localized-order';
+import { localizedMiscNodes, localizedStaions, localizedLineStyles } from './localized-order';
const buttonStyle: SystemStyleObject = {
justifyContent: 'flex-start',
@@ -74,6 +75,7 @@ const ToolsPanel = () => {
} = useRootSelector(state => state.app);
const {
mode,
+ selectedLineStyle,
count: { masters: masterNodesCount },
} = useRootSelector(state => state.runtime);
const bgColor = useColorModeValue('white', 'var(--chakra-colors-gray-800)');
@@ -151,6 +153,26 @@ const ToolsPanel = () => {
+ {isTextShown && (
+
+
+ {t('panel.tools.section.lineStyle')}
+
+ [
+ lineStyle,
+ t(lineStyles[lineStyle].metadata.displayName).toString(),
+ ]) ?? []
+ )}
+ value={selectedLineStyle}
+ onChange={({ target: { value } }) => {
+ dispatch(setSelectedLineStyle(value as LineStyleType));
+ }}
+ />
+
+ )}
+
{Object.values(LinePathType)
.filter(type => !(type === LinePathType.Simple && unlockSimplePathAttempts >= 0))
.map(type => (
diff --git a/src/components/svg-canvas-graph.tsx b/src/components/svg-canvas-graph.tsx
index 762020443..c87512359 100644
--- a/src/components/svg-canvas-graph.tsx
+++ b/src/components/svg-canvas-graph.tsx
@@ -40,7 +40,7 @@ import { makeParallelIndex } from '../util/parallel';
import { getLines, getNodes } from '../util/process-elements';
import SnapPointGuideLines from './snap-point-guide-lines';
import SvgLayer from './svg-layer';
-import { linePaths } from './svgs/lines/lines';
+import { linePaths, lineStyles } from './svgs/lines/lines';
import singleColor from './svgs/lines/styles/single-color';
import miscNodes from './svgs/nodes/misc-nodes';
import { default as stations } from './svgs/stations/stations';
@@ -77,6 +77,7 @@ const SvgCanvas = () => {
mode,
keepLastPath,
theme,
+ selectedLineStyle,
} = useRootSelector(state => state.runtime);
const size = useWindowSize();
const { height, width } = getCanvasSize(size);
@@ -325,14 +326,18 @@ const SvgCanvas = () => {
const parallelIndex = autoParallel
? makeParallelIndex(graph.current, type, source, target, 'from')
: -1;
+ const lineStyleAttrs = selectedLineStyle === LineStyleType.SingleColor
+ ? { color: theme }
+ : structuredClone(lineStyles[selectedLineStyle].defaultAttrs);
+
graph.current.addDirectedEdgeWithKey(newLineId, source, target, {
visible: true,
zIndex: 0,
type,
// deep copy to prevent mutual reference
[type]: structuredClone(linePaths[type].defaultAttrs),
- style: LineStyleType.SingleColor,
- [LineStyleType.SingleColor]: { color: theme },
+ style: selectedLineStyle,
+ [selectedLineStyle]: lineStyleAttrs,
reconcileId: '',
parallelIndex,
});
diff --git a/src/components/svgs/lines/styles/bjsubway-dotted.tsx b/src/components/svgs/lines/styles/bjsubway-dotted.tsx
index ee3bc1cb9..6ddf2c6b0 100644
--- a/src/components/svgs/lines/styles/bjsubway-dotted.tsx
+++ b/src/components/svgs/lines/styles/bjsubway-dotted.tsx
@@ -14,7 +14,7 @@ import {
import { ColorAttribute, ColorField } from '../../../panels/details/color-field';
const BjsubwayDotted = (props: LineStyleComponentProps) => {
- const { id, path, styleAttrs, handlePointerDown } = props;
+ const { id, path, styleAttrs, newLine, handlePointerDown } = props;
const { color = defaultBjsubwayDottedAttributes.color } = styleAttrs ?? defaultBjsubwayDottedAttributes;
const onPointerDown = React.useCallback(
@@ -25,7 +25,12 @@ const BjsubwayDotted = (props: LineStyleComponentProps
const bgColor = useColorModeValue('white', 'var(--chakra-colors-gray-800)');
return (
-
+
diff --git a/src/components/svgs/lines/styles/bjsubway-single-color.tsx b/src/components/svgs/lines/styles/bjsubway-single-color.tsx
index 8790892bd..839c8ee25 100644
--- a/src/components/svgs/lines/styles/bjsubway-single-color.tsx
+++ b/src/components/svgs/lines/styles/bjsubway-single-color.tsx
@@ -13,7 +13,7 @@ import {
import { ColorAttribute, ColorField } from '../../../panels/details/color-field';
const BjsubwaySingleColor = (props: LineStyleComponentProps) => {
- const { id, path, styleAttrs, handlePointerDown } = props;
+ const { id, path, styleAttrs, newLine, handlePointerDown } = props;
const { color = defaultBjsubwaySingleColorAttributes.color } = styleAttrs ?? defaultBjsubwaySingleColorAttributes;
const onPointerDown = React.useCallback(
@@ -22,7 +22,12 @@ const BjsubwaySingleColor = (props: LineStyleComponentProps
+
diff --git a/src/components/svgs/lines/styles/china-railway.tsx b/src/components/svgs/lines/styles/china-railway.tsx
index ed35f0607..063b40cb7 100644
--- a/src/components/svgs/lines/styles/china-railway.tsx
+++ b/src/components/svgs/lines/styles/china-railway.tsx
@@ -13,7 +13,7 @@ import {
import { ColorAttribute, ColorField } from '../../../panels/details/color-field';
const ChinaRailway = (props: LineStyleComponentProps) => {
- const { id, path, styleAttrs, handlePointerDown } = props;
+ const { id, path, styleAttrs, newLine, handlePointerDown } = props;
const { color = defaultChinaRailwayAttributes.color } = styleAttrs ?? defaultChinaRailwayAttributes;
const onPointerDown = React.useCallback(
@@ -22,7 +22,12 @@ const ChinaRailway = (props: LineStyleComponentProps) =>
);
return (
-
+
diff --git a/src/components/svgs/lines/styles/dual-color.tsx b/src/components/svgs/lines/styles/dual-color.tsx
index d56d1d3d3..5a9ce3843 100644
--- a/src/components/svgs/lines/styles/dual-color.tsx
+++ b/src/components/svgs/lines/styles/dual-color.tsx
@@ -19,7 +19,7 @@ import { makeShortPathParallel } from '../../../../util/bezier-parallel';
import { ColorField } from '../../../panels/details/color-field';
const DualColor = (props: LineStyleComponentProps) => {
- const { id, type, path, styleAttrs, handlePointerDown } = props;
+ const { id, type, path, styleAttrs, newLine, handlePointerDown } = props;
const { colorA = defaultDualColorAttributes.colorA, colorB = defaultDualColorAttributes.colorB } =
styleAttrs ?? defaultDualColorAttributes;
@@ -39,7 +39,12 @@ const DualColor = (props: LineStyleComponentProps) => {
}, [path]);
return (
-
+
diff --git a/src/components/svgs/lines/styles/river.tsx b/src/components/svgs/lines/styles/river.tsx
index 7318dbe56..474c41d81 100644
--- a/src/components/svgs/lines/styles/river.tsx
+++ b/src/components/svgs/lines/styles/river.tsx
@@ -7,7 +7,7 @@ import { LinePathAttributes, LinePathType, LineStyle, LineStyleComponentProps }
import { ColorAttribute } from '../../../panels/details/color-field';
const River = (props: LineStyleComponentProps) => {
- const { id, path, styleAttrs, handlePointerDown } = props;
+ const { id, path, styleAttrs, newLine, handlePointerDown } = props;
const { color = defaultRiverAttributes.color, width = defaultRiverAttributes.width } =
styleAttrs ?? defaultRiverAttributes;
@@ -25,7 +25,8 @@ const River = (props: LineStyleComponentProps) => {
strokeWidth={width}
strokeLinecap="round"
cursor="pointer"
- onPointerDown={onPointerDown}
+ onPointerDown={newLine ? undefined : onPointerDown}
+ pointerEvents={newLine ? 'none' : undefined}
/>
);
};
diff --git a/src/components/svgs/lines/styles/shmetro-virtual-int.tsx b/src/components/svgs/lines/styles/shmetro-virtual-int.tsx
index f86dc8826..2c5ad92ac 100644
--- a/src/components/svgs/lines/styles/shmetro-virtual-int.tsx
+++ b/src/components/svgs/lines/styles/shmetro-virtual-int.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import { LinePathAttributes, LinePathType, LineStyle, LineStyleComponentProps } from '../../../../constants/lines';
const ShmetroVirtualInt = (props: LineStyleComponentProps) => {
- const { id, path, handlePointerDown } = props;
+ const { id, path, newLine, handlePointerDown } = props;
const onPointerDown = React.useCallback(
(e: React.PointerEvent) => handlePointerDown(id, e),
@@ -10,7 +10,12 @@ const ShmetroVirtualInt = (props: LineStyleComponentProps
+
diff --git a/src/i18n/translations/en.json b/src/i18n/translations/en.json
index 93234f31f..f645bb6d0 100644
--- a/src/i18n/translations/en.json
+++ b/src/i18n/translations/en.json
@@ -13,6 +13,7 @@
"showLess": "Show less",
"section": {
"lineDrawing": "Line drawing",
+ "lineStyle": "Line style",
"stations": "Stations",
"miscellaneousNodes": "Miscellaneous nodes"
},
diff --git a/src/i18n/translations/zh-Hans.json b/src/i18n/translations/zh-Hans.json
index 49558756c..7a68cbd95 100644
--- a/src/i18n/translations/zh-Hans.json
+++ b/src/i18n/translations/zh-Hans.json
@@ -13,6 +13,7 @@
"showLess": "显示更少",
"section": {
"lineDrawing": "线段绘制",
+ "lineStyle": "线段样式",
"stations": "车站",
"miscellaneousNodes": "杂项节点"
},
diff --git a/src/i18n/translations/zh-Hant.json b/src/i18n/translations/zh-Hant.json
index 8c6d91017..e4f8e619f 100644
--- a/src/i18n/translations/zh-Hant.json
+++ b/src/i18n/translations/zh-Hant.json
@@ -13,6 +13,7 @@
"showLess": "顯示更少",
"section": {
"lineDrawing": "線段繪製",
+ "lineStyle": "線段樣式",
"stations": "車站",
"miscellaneousNodes": "雜項節點"
},
diff --git a/src/redux/runtime/runtime-slice.ts b/src/redux/runtime/runtime-slice.ts
index f9de2c6d3..a5bc28cb5 100644
--- a/src/redux/runtime/runtime-slice.ts
+++ b/src/redux/runtime/runtime-slice.ts
@@ -4,6 +4,7 @@ import { Translation } from '@railmapgen/rmg-translate';
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { RootState } from '..';
import { CityCode, Id, MiscNodeId, NodeType, RuntimeMode, StationCity, StnId, Theme } from '../../constants/constants';
+import { LineStyleType } from '../../constants/lines';
import { MAX_MASTER_NODE_FREE, MAX_MASTER_NODE_PRO } from '../../constants/master';
import { MiscNodeType } from '../../constants/nodes';
import { STATION_TYPE_VALUES, StationType } from '../../constants/stations';
@@ -37,6 +38,11 @@ interface RuntimeState {
edges: number;
};
mode: RuntimeMode;
+ /**
+ * The selected line style for new lines.
+ * Defaults to SingleColor.
+ */
+ selectedLineStyle: LineStyleType;
/**
* The last tool used by the user.
* Will be undefined on first load.
@@ -83,6 +89,7 @@ const initialState: RuntimeState = {
edges: Date.now(),
},
mode: 'free',
+ selectedLineStyle: LineStyleType.SingleColor,
lastTool: undefined,
keepLastPath: false,
theme: [CityCode.Shanghai, 'sh1', '#E3002B', MonoColour.white],
@@ -202,6 +209,9 @@ const runtimeSlice = createSlice({
if (state.mode !== 'free') state.lastTool = state.mode;
state.mode = action.payload;
},
+ setSelectedLineStyle: (state, action: PayloadAction) => {
+ state.selectedLineStyle = action.payload;
+ },
setKeepLastPath: (state, action: PayloadAction) => {
state.keepLastPath = action.payload;
},
@@ -286,6 +296,7 @@ export const {
setRefreshNodes,
setRefreshEdges,
setMode,
+ setSelectedLineStyle,
setKeepLastPath,
setTheme,
openPaletteAppClip,