diff --git a/src/components/page-header/settings-modal.tsx b/src/components/page-header/settings-modal.tsx index 54c824636..96c0e7f7f 100644 --- a/src/components/page-header/settings-modal.tsx +++ b/src/components/page-header/settings-modal.tsx @@ -76,7 +76,7 @@ const SettingsModal = (props: { isOpen: boolean; onClose: () => void }) => { const handleAdditionalTelemetry = (allowAppTelemetry: boolean) => dispatch(setTelemetryApp(allowAppTelemetry)); const maximumParallelLines = activeSubscriptions.RMP_CLOUD ? MAX_PARALLEL_LINES_PRO : MAX_PARALLEL_LINES_FREE; - const isParallelLineDisabled = parallelLinesCount > maximumParallelLines; + const isParallelLineDisabled = parallelLinesCount >= maximumParallelLines; return ( diff --git a/src/components/svg-wrapper.tsx b/src/components/svg-wrapper.tsx index 9500acd6f..8c5e500d5 100644 --- a/src/components/svg-wrapper.tsx +++ b/src/components/svg-wrapper.tsx @@ -22,6 +22,7 @@ import { FONTS_CSS, loadFontCss } from '../util/fonts'; import { findEdgesConnectedByNodes, findNodesExist, findNodesInRectangle } from '../util/graph'; import { getCanvasSize, getMousePosition, isMacClient, pointerPosToSVGCoord, roundToNearestN } from '../util/helpers'; import { useWindowSize } from '../util/hooks'; +import { MAX_PARALLEL_LINES_FREE } from '../util/parallel'; import SvgCanvas from './svg-canvas-graph'; import miscNodes from './svgs/nodes/misc-nodes'; import stations from './svgs/stations/stations'; @@ -49,12 +50,14 @@ const SvgWrapper = () => { theme, refresh: { nodes: refreshNodes }, masterNodesCount, + parallelLinesCount, } = useRootSelector(state => state.runtime); const size = useWindowSize(); const { height, width } = getCanvasSize(size); const isMasterDisabled = !activeSubscriptions.RMP_CLOUD && masterNodesCount + 1 > MAX_MASTER_NODE_FREE; + const isParallelDisabled = !activeSubscriptions.RMP_CLOUD && parallelLinesCount + 1 > MAX_PARALLEL_LINES_FREE; // Find nodes existence on each update and load fonts if needed. React.useEffect(() => { @@ -271,6 +274,7 @@ const SvgWrapper = () => { s, graph.current, isMasterDisabled, + isParallelDisabled, roundToNearestN(svgMidX, 5), roundToNearestN(svgMidY, 5) ); diff --git a/src/i18n/translations/en.json b/src/i18n/translations/en.json index 2afbe5347..55f89fa13 100644 --- a/src/i18n/translations/en.json +++ b/src/i18n/translations/en.json @@ -512,7 +512,11 @@ "title": "Settings", "pro": "This is a PRO feature and an account with a subscription is required.", "proWithTrial": "This is a PRO feature with a limited free trial available.", - "proLimitExceed": "Current action reaches the limit. Subscribe to unlock more!", + "proLimitExceed": { + "master": "Master nodes exceed the free tier.", + "parallel": "Parallel lines exceed the free tier.", + "solution": "Remove them to dismiss this warning, or subscribe to unlock more!" + }, "subscription": { "title": "Subscription Status", "logged-out": "You are currently logged out.", diff --git a/src/i18n/translations/ja.json b/src/i18n/translations/ja.json index d073322d3..602fd3a45 100644 --- a/src/i18n/translations/ja.json +++ b/src/i18n/translations/ja.json @@ -512,7 +512,11 @@ "title": "設定", "pro": "これはPRO機能であり、サブスクリプションが必要です。", "proWithTrial": "これはPRO機能で、無料の限定トライアルが利用可能です。", - "proLimitExceed": "現在の操作は制限に達しています。サブスクリプションを登録してさらにアンロックしてください!", + "proLimitExceed": { + "master": "大師節点が無料枠を超えています。", + "parallel": "平行路線が無料枠を超えています。", + "solution": "これらを削除して警告を解除するか、サブスクリプションに登録してさらに多くの機能を利用してください!" + }, "subscription": { "title": "サブスクリプションステータス", "logged-out": "現在ログアウトしています。", diff --git a/src/i18n/translations/ko.json b/src/i18n/translations/ko.json index 984e2778e..6cce138b1 100644 --- a/src/i18n/translations/ko.json +++ b/src/i18n/translations/ko.json @@ -511,7 +511,11 @@ "title": "설정", "pro": "이것은 PRO 기능이며, 구독이 필요한 계정입니다.", "proWithTrial": "이것은 PRO 기능이며, 제한된 무료 체험이 가능합니다.", - "proLimitExceed": "현재 작업이 한도에 도달했습니다. 더 많은 기능을 사용하려면 구독하세요!", + "proLimitExceed": { + "master": "마스터 노드가 무료 사용 한도를 초과했습니다.", + "parallel": "평행선이 무료 사용 한도를 초과했습니다.", + "solution": "경고를 해제하려면 이 항목들을 제거하거나 구독을 통해 더 많은 기능을 잠금 해제하세요!" + }, "subscription": { "title": "구독 상태", "logged-out": "현재 로그아웃 상태입니다.", @@ -541,7 +545,7 @@ "undo": "취소하다.", "redo": "다시 하다." }, - "procedures":{ + "procedures": { "title": "절차", "translate": { "title": "노드 좌표 변환", diff --git a/src/i18n/translations/zh-Hans.json b/src/i18n/translations/zh-Hans.json index e6c96f6a9..ceb7d3067 100644 --- a/src/i18n/translations/zh-Hans.json +++ b/src/i18n/translations/zh-Hans.json @@ -511,7 +511,11 @@ "title": "设置", "pro": "这是一个专业功能,需要带有订阅的账户。", "proWithTrial": "这是一个PRO功能,并提供有限的免费试用。", - "proLimitExceed": "当前操作已达到限制。订阅以解锁更多!", + "proLimitExceed": { + "master": "大师节点超出了免费额度。", + "parallel": "平行线段超出了免费额度。", + "solution": "移除它们以消除此警告,或订阅以解锁更多功能!" + }, "subscription": { "title": "订阅状态", "logged-out": "您当前已登出。", @@ -541,7 +545,7 @@ "undo": "撤销。", "redo": "重做。" }, - "procedures":{ + "procedures": { "title": "过程", "translate": { "title": "转化节点坐标", diff --git a/src/i18n/translations/zh-Hant.json b/src/i18n/translations/zh-Hant.json index 68b8579c7..4ff497bcb 100644 --- a/src/i18n/translations/zh-Hant.json +++ b/src/i18n/translations/zh-Hant.json @@ -511,7 +511,11 @@ "title": "設置", "pro": "這是一個專業功能,需要带有訂閱的帳戶。", "proWithTrial": "這是一個PRO功能,並提供有限的免費試用。", - "proLimitExceed": "當前操作已達到限制。訂閱以解鎖更多功能!", + "proLimitExceed": { + "master": "大師節點超出了免費額度。", + "parallel": "平行線段超出了免費額度。", + "solution": "移除它們以解除此警告,或訂閱以解鎖更多功能!" + }, "subscription": { "title": "訂閱狀態", "logged-out": "您目前已登出。", @@ -541,7 +545,7 @@ "undo": "撤銷。", "redo": "重做。" }, - "procedures":{ + "procedures": { "title": "过程", "translate": { "title": "轉化節點坐標", diff --git a/src/redux/runtime/runtime-slice.ts b/src/redux/runtime/runtime-slice.ts index a0a553d2a..5f16b7a6f 100644 --- a/src/redux/runtime/runtime-slice.ts +++ b/src/redux/runtime/runtime-slice.ts @@ -97,7 +97,7 @@ export const refreshNodesThunk = createAsyncThunk('runtime/refreshNodes', async dispatch( setGlobalAlert({ status: 'warning', - message: i18n.t('header.settings.proLimitExceed'), + message: `${i18n.t('header.settings.proLimitExceed.master')} ${i18n.t('header.settings.proLimitExceed.solution')}`, }) ); } @@ -115,12 +115,14 @@ export const refreshEdgesThunk = createAsyncThunk('runtime/refreshEdges', async const maximumParallelLines = state.account.activeSubscriptions.RMP_CLOUD ? MAX_PARALLEL_LINES_PRO : MAX_PARALLEL_LINES_FREE; - if (parallelLinesCount > maximumParallelLines) { + if (parallelLinesCount == maximumParallelLines) { dispatch(setAutoParallel(false)); + } + if (parallelLinesCount > maximumParallelLines) { dispatch( setGlobalAlert({ status: 'warning', - message: i18n.t('header.settings.proLimitExceed'), + message: `${i18n.t('header.settings.proLimitExceed.parallel')} ${i18n.t('header.settings.proLimitExceed.solution')}`, }) ); } diff --git a/src/util/clipboard.ts b/src/util/clipboard.ts index 7d826a06c..df7c9d689 100644 --- a/src/util/clipboard.ts +++ b/src/util/clipboard.ts @@ -58,6 +58,7 @@ export const exportSelectedNodesAndEdges = ( * @param s The text from the clipboard. * @param graph The graph. * @param filterMaster Whether filter master nodes on paste (no subscription only). + * @param filterParallel Whether filter parallel lines on paste (no subscription only). * @param x The central x of the svg canvas. Nodes and edges added will repositioned around this point. * @param y The central y of the svg canvas. Nodes and edges added will repositioned around this point. * @returns The nodes and edges added to the graph. @@ -66,6 +67,7 @@ export const importSelectedNodesAndEdges = ( s: string, graph: MultiDirectedGraph, filterMaster: boolean, + filterParallel: boolean, x: number, y: number ) => { @@ -102,6 +104,17 @@ export const importSelectedNodesAndEdges = ( ) : edgesWithAttrs; + // Set parallelIndex to -1 (disable) if requested. + // Note users might exceed the current limit (5) if copy and paste 1...4 parallel lines. + // This will result in a at max 8 parallel lines situation. A finer solution might be required. + if (filterParallel) { + for (const edge in filteredEdges) { + if (filteredEdges[edge as LineId].attr.parallelIndex >= 0) { + filteredEdges[edge as LineId].attr.parallelIndex = -1; + } + } + } + // add nodes and edges into the graph const [offsetX, offsetY] = [x - avgX, y - avgY]; Object.entries(filteredNodes).forEach(([node, attr]) => { diff --git a/src/util/parallel.ts b/src/util/parallel.ts index 3c2cc666e..de7027005 100644 --- a/src/util/parallel.ts +++ b/src/util/parallel.ts @@ -96,7 +96,7 @@ export const makeParallelPaths = (parallelLines: EdgeEntry= 1 ? attr.roundCornerFactor : MIN_ROUND_CORNER_FACTOR; + const baseRoundCornerFactor = Math.max(MIN_ROUND_CORNER_FACTOR, attr.roundCornerFactor); const [x1, y1, x2, y2] = [ baseLineEntry.sourceAttributes.x,