Compare commits

..
Author SHA1 Message Date
Danielv123 8229db7533 Fix plugin list and header layout on mobile 2026-11-15 18:26:41 +01:00
6 changed files with 101 additions and 29 deletions

No files matched your search

+7
View File
@@ -31,6 +31,13 @@ Many thanks to the following for contributing to this release:
[@username](https://github.com/username)
-->
## Unreleased
### Fixes
- Fixed cramped plugin rows and wrapping header branding on mobile screens.
## Version 2.1.0
### Features
@@ -172,10 +172,12 @@ export function PluginVersions(props: { status: PluginStatus, latest?: string })
{versions.map(version => {
const now = count(machine => machine.running === version);
const after = count(machine => machine.afterRestart === version);
return <div key={version}>
<Text>{version}</Text>
{props.latest === version && <Tag color="success" style={{ marginLeft: 6 }}>latest</Tag>}
<Text type="secondary" style={{ marginLeft: 12 }}>
return <div key={version} className="plugin-version">
<span>
<Text>{version}</Text>
{props.latest === version && <Tag color="success" style={{ marginLeft: 6 }}>latest</Tag>}
</span>
<Text type="secondary">
{plural(now, "machine")}{now !== after && ` → ${after} after restart`}
</Text>
</div>;
+36 -22
View File
@@ -1,5 +1,5 @@
import React, { useContext, useEffect, useState } from "react";
import { Space, Table, Tooltip, Typography } from "antd";
import { Grid, Space, Table, Tooltip, Typography } from "antd";
import CloseCircleFilled from "@ant-design/icons/CloseCircleFilled";
import type { PluginWebApi } from "@clusterio/lib";
@@ -48,6 +48,8 @@ function WebError(props: { error: string }) {
export default function PluginsPage() {
const control = useContext(ControlContext);
const account = useAccount();
const screens = Grid.useBreakpoint();
const isMobile = screens.md === false;
let [pluginList, setPluginList] = useState<PluginWebApi[]>([]);
const tableState = useTableQueryState<PluginRow>({
namespace: "plugin", defaultSortKey: "name",
@@ -96,6 +98,24 @@ export default function PluginsPage() {
const latestOf = (plugin: PluginRow) => (
plugin.status?.npmPackage ? latestVersions[plugin.status.npmPackage] : undefined
);
const versions = (plugin: PluginRow) => <>
{plugin.status
? <PluginVersions status={plugin.status} latest={latestOf(plugin)} />
: plugin.meta?.version}
{plugin.webError && <WebError error={plugin.webError} />}
</>;
const updateAction = (plugin: PluginRow) => (plugin.status
// Keep clicks on the update action from opening the plugin.
? <span onClick={event => { event.stopPropagation(); }}>
<PluginUpdateButton
status={plugin.status}
title={plugin.title}
latest={latestOf(plugin)}
installer={installer}
/>
</span>
: null
);
return <PageLayout nav={[{ name: "Plugins" }]}>
<PageHeader
@@ -115,14 +135,22 @@ export default function PluginsPage() {
</Space>}
/>
<Table
className="plugins-table"
tableLayout={isMobile ? "fixed" : "auto"}
columns={[
{
title: "Name",
key: "name",
className: "table-link-cell",
render: (_, plugin) => <Link to={pluginPath(plugin)} style={{ color: "inherit" }}>
{plugin.title}
</Link>,
render: (_, plugin) => <>
<Link to={pluginPath(plugin)} style={{ color: "inherit" }}>
{plugin.title}
</Link>
{isMobile && <div className="plugin-mobile-details">
<div>{versions(plugin)}</div>
{updateAction(plugin)}
</div>}
</>,
sorter: (a, b) => strcmp(a.title, b.title),
sortOrder: tableState.sortOrder("name"),
filteredValue: tableState.filteredValue("name"),
@@ -131,28 +159,14 @@ export default function PluginsPage() {
{
title: "Versions",
key: "versions",
render: (_, plugin) => <>
{plugin.status
? <PluginVersions status={plugin.status} latest={latestOf(plugin)} />
: plugin.meta?.version}
{plugin.webError && <WebError error={plugin.webError} />}
</>,
responsive: ["md"],
render: (_, plugin) => versions(plugin),
},
{
key: "actions",
align: "right",
render: (_, plugin) => (plugin.status
// Keep clicks on the update action from opening the plugin
? <span onClick={event => { event.stopPropagation(); }}>
<PluginUpdateButton
status={plugin.status}
title={plugin.title}
latest={latestOf(plugin)}
installer={installer}
/>
</span>
: null
),
responsive: ["md"],
render: (_, plugin) => updateAction(plugin),
},
]}
dataSource={tableContents}
@@ -169,8 +169,10 @@ export default function SiteLayout() {
/>;
let accountMenu = <Dropdown menu={accountMenuProps} trigger={["click"]} placement="bottomRight">
<Button type="text" icon={<UserOutlined />} style={{ marginInlineStart: "auto" }}>
{account.name}
<Button type="text" icon={<UserOutlined />} style={{ marginInlineStart: "auto" }}
aria-label={`Account menu for ${account.name}`}
>
<span className="site-layout-account-name">{account.name}</span>
</Button>
</Dropdown>;
@@ -228,7 +230,7 @@ export default function SiteLayout() {
<Link to="/" style={{ display: "flex", alignItems: "center" }}>
<img src={logo} width={56} height={56} alt="Clusterio logo" />
</Link>
<Flex vertical justify="center">
<Flex vertical justify="center" className="site-layout-brand">
<Typography.Title level={4} style={{ margin: 0, lineHeight: 1.2 }}>Clusterio</Typography.Title>
<Tooltip title="View changelog" placement="right">
<Typography.Text
+32
View File
@@ -72,6 +72,38 @@ body {
background: #001529;
}
.site-layout-brand {
flex-shrink: 0;
white-space: nowrap;
}
@media (max-width: 767px) {
.site-layout-account-name {
display: none;
}
.plugins-table .table-link-cell > a {
font-weight: 600;
overflow-wrap: anywhere;
}
}
.plugin-mobile-details {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 12px;
margin-top: 8px;
overflow-wrap: anywhere;
}
.plugin-version {
display: flex;
flex-wrap: wrap;
align-items: baseline;
column-gap: 12px;
}
/* Keep the sidebar fixed in place (below the header) while content scrolls. */
.site-layout-sider {
position: sticky;
+15
View File
@@ -2,6 +2,21 @@
This folder contains files used for performing manual tests that are non-trivial to automate.
## Plugins page on small screens
Use a cluster with several plugins, including one with different running and installed versions and one with a web load error.
Compare the Plugins page at 390px and 1440px viewport widths, and check the 768px breakpoint.
1. On phones, check that each row shows its name, versions, machine counts, status and update action vertically, without clipping or breaking ordinary words.
2. Check that a long plugin name wraps and the header branding stays on one line.
3. Search and sort by name, reload, and check that the selected filter and sort order remain applied.
4. Open an update or restart checklist, then cancel it; update actions must not navigate to the plugin page.
5. Open a plugin by its name and by clicking elsewhere in its row.
6. Open the account menu using the user icon on mobile; the full account name should appear in the menu.
7. On desktop, check that the name, versions and update action remain separate columns.
## Setup test mod
Run `node packages/lib/dist/node/build_mod.js --source-dir test/manual/test_mod/ --output-dir mods/` to create the test mod.