diff --git a/packages/web_ui/src/components/HostsPage.tsx b/packages/web_ui/src/components/HostsPage.tsx index 5b1dfa15..5a9d9986 100644 --- a/packages/web_ui/src/components/HostsPage.tsx +++ b/packages/web_ui/src/components/HostsPage.tsx @@ -1,5 +1,4 @@ import React, { useContext, useRef, useState, useEffect } from "react"; -import { useNavigate } from "react-router-dom"; import { Button, Flex, Form, InputNumber, Modal, Space, Switch, Table, Tooltip, Typography } from "antd"; import { ExclamationCircleOutlined, CopyOutlined } from "@ant-design/icons"; @@ -20,6 +19,8 @@ import { import { useHosts } from "../model/host"; import { useSystems } from "../model/system"; import notify, { notifyErrorHandler } from "../util/notify"; +import useRowNavigation from "../util/useRowNavigation"; +import Link from "./Link"; const strcmp = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }).compare; @@ -199,12 +200,12 @@ function CopyButton({ text, message }: { text:string, message:string }) { export default function HostsPage() { let account = useAccount(); - let navigate = useNavigate(); let [hosts] = useHosts(); const [systems] = useSystems(); const [showRatios, setShowRatios] = useState(true); const [showNumbers, setShowNumbers] = useState(false); const [showCpuModel, setShowCpuModel] = useState(false); + const rowNav = useRowNavigation(); return strcmp(a.name, b.name), + render: (_, host) => + {host.name} + , }, { title: "CPU Model", @@ -306,11 +310,7 @@ export default function HostsPage() { dataSource={[...hosts.values()]} rowKey={host => host.id} pagination={false} - onRow={(record, rowIndex) => ({ - onClick: event => { - navigate(`/hosts/${record.id}/view`); - }, - })} + onRow={record => rowNav(`/hosts/${record.id}/view`)} /> ; diff --git a/packages/web_ui/src/components/ModsPage.tsx b/packages/web_ui/src/components/ModsPage.tsx index a6ece310..8cbfe1a2 100644 --- a/packages/web_ui/src/components/ModsPage.tsx +++ b/packages/web_ui/src/components/ModsPage.tsx @@ -18,6 +18,8 @@ import PageHeader from "./PageHeader"; import PageLayout from "./PageLayout"; import PluginExtra from "./PluginExtra"; import SectionHeader from "./SectionHeader"; +import useRowNavigation from "../util/useRowNavigation"; +import Link from "./Link"; import ModDetails from "./ModDetails"; import { Dropzone } from "./Dropzone"; import UploadButton from "./UploadButton"; @@ -507,9 +509,9 @@ function SearchModsButton() { export default function ModsPage() { let account = useAccount(); let control = useContext(ControlContext); - let navigate = useNavigate(); let [mods] = useMods(); let [modPacks] = useModPacks(); + const rowNav = useRowNavigation(); function actions(mod: lib.ModInfo) { return @@ -577,6 +579,12 @@ export default function ModsPage() { dataIndex: "name", defaultSortOrder: "ascend", sorter: (a, b) => strcmp(a.name, b.name), + render: (_, modPack) => + {modPack.name} + , }, { title: "Factorio Version", @@ -592,11 +600,7 @@ export default function ModsPage() { dataSource={[...modPacks.values()]} pagination={false} rowKey={modPack => Number(modPack.id)} - onRow={(modPack, rowIndex) => ({ - onClick: event => { - navigate(`/mods/mod-packs/${modPack.id}/view`); - }, - })} + onRow={modPack => rowNav(`/mods/mod-packs/${modPack.id}/view`)} /> diff --git a/packages/web_ui/src/components/PluginsPage.tsx b/packages/web_ui/src/components/PluginsPage.tsx index bea8cdab..38eed49f 100644 --- a/packages/web_ui/src/components/PluginsPage.tsx +++ b/packages/web_ui/src/components/PluginsPage.tsx @@ -1,5 +1,4 @@ import React, { useContext, useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; import { Table } from "antd"; import CloseCircleFilled from "@ant-design/icons/CloseCircleFilled"; import InfoCircleFilled from "@ant-design/icons/InfoCircleFilled"; @@ -10,14 +9,16 @@ import notify from "../util/notify"; import ControlContext from "./ControlContext"; import PageLayout from "./PageLayout"; import PageHeader from "./PageHeader"; +import useRowNavigation from "../util/useRowNavigation"; +import Link from "./Link"; const strcmp = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }).compare; export default function PluginsPage() { const control = useContext(ControlContext); - let navigate = useNavigate(); let [pluginList, setPluginList] = useState([]); + const rowNav = useRowNavigation(); useEffect(() => { (async () => { @@ -54,7 +55,12 @@ export default function PluginsPage() { { title: "Name", key: "name", - render: (_, plugin) => (plugin.info ? plugin.info.title : plugin.meta.name), + render: (_, plugin) => + {plugin.info ? plugin.info.title : plugin.meta.name} + , defaultSortOrder: "ascend", sorter: (a, b) => strcmp(a.info ? a.info.title : a.meta.name, b.info ? b.info.title : b.meta.name), }, @@ -86,11 +92,7 @@ export default function PluginsPage() { dataSource={tableContents} rowKey={plugin => plugin.meta.name} pagination={false} - onRow={plugin => ({ - onClick: event => { - navigate(`/plugins/${plugin.meta.name}/view`); - }, - })} + onRow={plugin => rowNav(`/plugins/${plugin.meta.name}/view`)} /> ; } diff --git a/packages/web_ui/src/components/RolesPage.tsx b/packages/web_ui/src/components/RolesPage.tsx index 4e392c27..f1b62fb0 100644 --- a/packages/web_ui/src/components/RolesPage.tsx +++ b/packages/web_ui/src/components/RolesPage.tsx @@ -11,6 +11,8 @@ import { notifyErrorHandler } from "../util/notify"; import PageHeader from "./PageHeader"; import PageLayout from "./PageLayout"; import PluginExtra from "./PluginExtra"; +import useRowNavigation from "../util/useRowNavigation"; +import Link from "./Link"; const strcmp = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }).compare; @@ -61,8 +63,8 @@ function CreateRoleButton() { export default function RolesPage() { let account = useAccount(); - let navigate = useNavigate(); const [roles] = useRoles(); + const rowNav = useRowNavigation(); return strcmp(a.name, b.name), + render: (_, role) => + {role.name} + , }, { title: "Description", @@ -84,11 +89,7 @@ export default function RolesPage() { dataSource={[...roles.values()]} pagination={false} rowKey={role => role.id} - onRow={(role, rowIndex) => ({ - onClick: event => { - navigate(`/roles/${role.id}/view`); - }, - })} + onRow={role => rowNav(`/roles/${role.id}/view`)} /> ; diff --git a/packages/web_ui/src/components/SiteLayout.tsx b/packages/web_ui/src/components/SiteLayout.tsx index 76338056..7d2a77c4 100644 --- a/packages/web_ui/src/components/SiteLayout.tsx +++ b/packages/web_ui/src/components/SiteLayout.tsx @@ -8,6 +8,7 @@ import ErrorPage from "./ErrorPage"; import ControlContext from "./ControlContext"; import ChangeLogModal from "./ChangeLogModal"; import AboutModal from "./AboutModal"; +import Link from "./Link"; import { pages } from "../pages"; import { saveJson } from "../util/save_file"; @@ -21,6 +22,12 @@ type MenuItem = Required["items"][number]; const { Header, Sider } = Layout; +// Render a sidebar menu label as a real link so right-click/middle-click can +// open it in a new tab; stopPropagation keeps the Menu onClick from also firing. +function navLink(path: string, label: string) { + return e.stopPropagation()}>{label}; +} + function isActiveDropzone(element: HTMLElement | null): boolean { if (!element) { return false; @@ -74,7 +81,12 @@ export default function SiteLayout() { } }, items: [ - { label: account.name, key: "user" }, + { + label: e.stopPropagation()}> + {account.name} + , + key: "user", + }, { label: Ctl Config