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,