mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-16 07:24:53 +09:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8229db7533 |
No files matched your search
@@ -31,6 +31,13 @@ Many thanks to the following for contributing to this release:
|
|||||||
[@username](https://github.com/username)
|
[@username](https://github.com/username)
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Fixed cramped plugin rows and wrapping header branding on mobile screens.
|
||||||
|
|
||||||
|
|
||||||
## Version 2.1.0
|
## Version 2.1.0
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -172,10 +172,12 @@ export function PluginVersions(props: { status: PluginStatus, latest?: string })
|
|||||||
{versions.map(version => {
|
{versions.map(version => {
|
||||||
const now = count(machine => machine.running === version);
|
const now = count(machine => machine.running === version);
|
||||||
const after = count(machine => machine.afterRestart === version);
|
const after = count(machine => machine.afterRestart === version);
|
||||||
return <div key={version}>
|
return <div key={version} className="plugin-version">
|
||||||
<Text>{version}</Text>
|
<span>
|
||||||
{props.latest === version && <Tag color="success" style={{ marginLeft: 6 }}>latest</Tag>}
|
<Text>{version}</Text>
|
||||||
<Text type="secondary" style={{ marginLeft: 12 }}>
|
{props.latest === version && <Tag color="success" style={{ marginLeft: 6 }}>latest</Tag>}
|
||||||
|
</span>
|
||||||
|
<Text type="secondary">
|
||||||
{plural(now, "machine")}{now !== after && ` → ${after} after restart`}
|
{plural(now, "machine")}{now !== after && ` → ${after} after restart`}
|
||||||
</Text>
|
</Text>
|
||||||
</div>;
|
</div>;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useContext, useEffect, useState } from "react";
|
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 CloseCircleFilled from "@ant-design/icons/CloseCircleFilled";
|
||||||
|
|
||||||
import type { PluginWebApi } from "@clusterio/lib";
|
import type { PluginWebApi } from "@clusterio/lib";
|
||||||
@@ -48,6 +48,8 @@ function WebError(props: { error: string }) {
|
|||||||
export default function PluginsPage() {
|
export default function PluginsPage() {
|
||||||
const control = useContext(ControlContext);
|
const control = useContext(ControlContext);
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
|
const screens = Grid.useBreakpoint();
|
||||||
|
const isMobile = screens.md === false;
|
||||||
let [pluginList, setPluginList] = useState<PluginWebApi[]>([]);
|
let [pluginList, setPluginList] = useState<PluginWebApi[]>([]);
|
||||||
const tableState = useTableQueryState<PluginRow>({
|
const tableState = useTableQueryState<PluginRow>({
|
||||||
namespace: "plugin", defaultSortKey: "name",
|
namespace: "plugin", defaultSortKey: "name",
|
||||||
@@ -96,6 +98,24 @@ export default function PluginsPage() {
|
|||||||
const latestOf = (plugin: PluginRow) => (
|
const latestOf = (plugin: PluginRow) => (
|
||||||
plugin.status?.npmPackage ? latestVersions[plugin.status.npmPackage] : undefined
|
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" }]}>
|
return <PageLayout nav={[{ name: "Plugins" }]}>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
@@ -115,14 +135,22 @@ export default function PluginsPage() {
|
|||||||
</Space>}
|
</Space>}
|
||||||
/>
|
/>
|
||||||
<Table
|
<Table
|
||||||
|
className="plugins-table"
|
||||||
|
tableLayout={isMobile ? "fixed" : "auto"}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
title: "Name",
|
title: "Name",
|
||||||
key: "name",
|
key: "name",
|
||||||
className: "table-link-cell",
|
className: "table-link-cell",
|
||||||
render: (_, plugin) => <Link to={pluginPath(plugin)} style={{ color: "inherit" }}>
|
render: (_, plugin) => <>
|
||||||
{plugin.title}
|
<Link to={pluginPath(plugin)} style={{ color: "inherit" }}>
|
||||||
</Link>,
|
{plugin.title}
|
||||||
|
</Link>
|
||||||
|
{isMobile && <div className="plugin-mobile-details">
|
||||||
|
<div>{versions(plugin)}</div>
|
||||||
|
{updateAction(plugin)}
|
||||||
|
</div>}
|
||||||
|
</>,
|
||||||
sorter: (a, b) => strcmp(a.title, b.title),
|
sorter: (a, b) => strcmp(a.title, b.title),
|
||||||
sortOrder: tableState.sortOrder("name"),
|
sortOrder: tableState.sortOrder("name"),
|
||||||
filteredValue: tableState.filteredValue("name"),
|
filteredValue: tableState.filteredValue("name"),
|
||||||
@@ -131,28 +159,14 @@ export default function PluginsPage() {
|
|||||||
{
|
{
|
||||||
title: "Versions",
|
title: "Versions",
|
||||||
key: "versions",
|
key: "versions",
|
||||||
render: (_, plugin) => <>
|
responsive: ["md"],
|
||||||
{plugin.status
|
render: (_, plugin) => versions(plugin),
|
||||||
? <PluginVersions status={plugin.status} latest={latestOf(plugin)} />
|
|
||||||
: plugin.meta?.version}
|
|
||||||
{plugin.webError && <WebError error={plugin.webError} />}
|
|
||||||
</>,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "actions",
|
key: "actions",
|
||||||
align: "right",
|
align: "right",
|
||||||
render: (_, plugin) => (plugin.status
|
responsive: ["md"],
|
||||||
// Keep clicks on the update action from opening the plugin
|
render: (_, plugin) => updateAction(plugin),
|
||||||
? <span onClick={event => { event.stopPropagation(); }}>
|
|
||||||
<PluginUpdateButton
|
|
||||||
status={plugin.status}
|
|
||||||
title={plugin.title}
|
|
||||||
latest={latestOf(plugin)}
|
|
||||||
installer={installer}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
: null
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
dataSource={tableContents}
|
dataSource={tableContents}
|
||||||
|
|||||||
@@ -169,8 +169,10 @@ export default function SiteLayout() {
|
|||||||
/>;
|
/>;
|
||||||
|
|
||||||
let accountMenu = <Dropdown menu={accountMenuProps} trigger={["click"]} placement="bottomRight">
|
let accountMenu = <Dropdown menu={accountMenuProps} trigger={["click"]} placement="bottomRight">
|
||||||
<Button type="text" icon={<UserOutlined />} style={{ marginInlineStart: "auto" }}>
|
<Button type="text" icon={<UserOutlined />} style={{ marginInlineStart: "auto" }}
|
||||||
{account.name}
|
aria-label={`Account menu for ${account.name}`}
|
||||||
|
>
|
||||||
|
<span className="site-layout-account-name">{account.name}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Dropdown>;
|
</Dropdown>;
|
||||||
|
|
||||||
@@ -228,7 +230,7 @@ export default function SiteLayout() {
|
|||||||
<Link to="/" style={{ display: "flex", alignItems: "center" }}>
|
<Link to="/" style={{ display: "flex", alignItems: "center" }}>
|
||||||
<img src={logo} width={56} height={56} alt="Clusterio logo" />
|
<img src={logo} width={56} height={56} alt="Clusterio logo" />
|
||||||
</Link>
|
</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>
|
<Typography.Title level={4} style={{ margin: 0, lineHeight: 1.2 }}>Clusterio</Typography.Title>
|
||||||
<Tooltip title="View changelog" placement="right">
|
<Tooltip title="View changelog" placement="right">
|
||||||
<Typography.Text
|
<Typography.Text
|
||||||
|
|||||||
@@ -72,6 +72,38 @@ body {
|
|||||||
background: #001529;
|
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. */
|
/* Keep the sidebar fixed in place (below the header) while content scrolls. */
|
||||||
.site-layout-sider {
|
.site-layout-sider {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
|
|||||||
@@ -2,6 +2,21 @@
|
|||||||
|
|
||||||
This folder contains files used for performing manual tests that are non-trivial to automate.
|
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
|
## 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.
|
Run `node packages/lib/dist/node/build_mod.js --source-dir test/manual/test_mod/ --output-dir mods/` to create the test mod.
|
||||||
|
|||||||
Reference in new issue
Block a user