Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { SnapshotHistory } from "./pages/SnapshotHistory";
import { SnapshotRestore } from "./pages/SnapshotRestore";
import { AppContext } from "./contexts/AppContext";
import { UIPreferenceProvider } from "./contexts/UIPreferencesContext";
import { TableSortProvider } from "./contexts/TableSortContext";

export default class App extends Component {
constructor() {
Expand Down Expand Up @@ -175,7 +176,8 @@ export default class App extends Component {
<h5 className="mb-4">{this.state.repoDescription}</h5>
</NavLink>

<Routes>
<TableSortProvider>
<Routes>
<Route path="snapshots" element={<Snapshots />} />
<Route path="snapshots/new" element={<SnapshotCreate />} />
<Route path="snapshots/single-source/" element={<SnapshotHistory />} />
Expand All @@ -188,7 +190,8 @@ export default class App extends Component {
<Route path="repo" element={<Repository />} />
<Route path="preferences" element={<Preferences />} />
<Route path="/" element={<Navigate to="/snapshots" />} />
</Routes>
</Routes>
</TableSortProvider>
</Container>
</UIPreferenceProvider>
</AppContext>
Expand Down
22 changes: 20 additions & 2 deletions src/components/DirectoryItems.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";
import { Link } from "react-router";
import KopiaTable from "./KopiaTable";
import { objectLink, rfc3339TimestampForDisplay } from "../utils/formatutils";
import { compare, objectLink, rfc3339TimestampForDisplay } from "../utils/formatutils";
import { sizeWithFailures } from "../utils/uiutil";
import { UIPreferencesContext } from "../contexts/UIPreferencesContext";
import PropTypes from "prop-types";
Expand Down Expand Up @@ -47,6 +47,24 @@ export function DirectoryItems({ historyState, items }) {
id: "name",
header: "Name",
width: "",
accessorFn: (x) => x.name,
sortDescFirst: true, // first click sorts descending, like the other columns
sortingFn: (rowA, rowB, columnId) => {
const aIsDir = rowA.original.type === "d";
const bIsDir = rowB.original.type === "d";
if (aIsDir !== bIsDir) {
return aIsDir ? -1 : 1; // directories always first, in both directions
}

const aName = rowA.getValue(columnId);
const bName = rowB.getValue(columnId);
const v = compare(aName.toLowerCase(), bName.toLowerCase());
if (v !== 0) {
return v;
}

return compare(aName, bName); // exact-compare fallback for case-insensitive ties
},
cell: (x) => directoryLinkOrDownload(x.row.original, historyState),
},
{
Expand Down Expand Up @@ -77,7 +95,7 @@ export function DirectoryItems({ historyState, items }) {
},
];

return <KopiaTable data={items} columns={columns} />;
return <KopiaTable data={items} columns={columns} tableKey="directory" />;
}

DirectoryItems.propTypes = {
Expand Down
6 changes: 4 additions & 2 deletions src/components/KopiaTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
flexRender,
} from "@tanstack/react-table";
import { PAGE_SIZES, UIPreferencesContext } from "../contexts/UIPreferencesContext";
import { useTableSort } from "../contexts/TableSortContext";
import PropTypes from "prop-types";

function paginationItems(count, active, gotoPage) {
Expand Down Expand Up @@ -55,9 +56,9 @@ function paginationItems(count, active, gotoPage) {
return items;
}

export default function KopiaTable({ columns, data }) {
export default function KopiaTable({ columns, data, tableKey }) {
const { pageSize, setPageSize } = use(UIPreferencesContext);
const [sorting, setSorting] = useState([]);
const [sorting, setSorting] = useTableSort(tableKey);
const [pagination, setPagination] = useState({
pageIndex: 0, //default page index
pageSize: pageSize, //default page size
Expand Down Expand Up @@ -173,4 +174,5 @@ export default function KopiaTable({ columns, data }) {
KopiaTable.propTypes = {
columns: PropTypes.array.isRequired,
data: PropTypes.array.isRequired,
tableKey: PropTypes.string,
};
48 changes: 48 additions & 0 deletions src/contexts/TableSortContext.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, { use, useState, useCallback } from "react";
import PropTypes from "prop-types";

// Table sort state, persisted across component (re)mounts, keyed by table
// identity. In-memory only: it does not survive a browser refresh.
const TableSortContext = React.createContext(null);

export function TableSortProvider({ children }) {
const [sorts, setSorts] = useState({});

const getSort = useCallback((key) => sorts[key] ?? [], [sorts]);
// TanStack v8 onSortingChange may pass either the new SortingState or an
// updater function (state) => SortingState; resolve both to a plain array.
const setSort = useCallback((key, update) => {
setSorts((prev) => {
const current = prev[key] ?? [];
const next = typeof update === "function" ? update(current) : update;
return { ...prev, [key]: next };
});
}, []);

return (
<TableSortContext value={{ getSort, setSort }}>
{children}
</TableSortContext>
);
}

TableSortProvider.propTypes = {
children: PropTypes.node.isRequired,
};

// Returns [sorting, setSorting] compatible with TanStack's onSortingChange.
// When tableKey is provided, the state is shared across (re)mounts via the
// provider; otherwise it falls back to component-local state (behavior
// unchanged for call sites without a key).
export function useTableSort(tableKey) {
const ctx = use(TableSortContext);
const [localSorting, setLocalSorting] = useState([]);

if (!ctx || !tableKey) {
return [localSorting, setLocalSorting];
}

const sorting = ctx.getSort(tableKey);
const setSorting = (s) => ctx.setSort(tableKey, s);
return [sorting, setSorting];
}
2 changes: 1 addition & 1 deletion src/pages/Policies.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ export class PoliciesInternal extends Component {
{policies.length > 0 ? (
<div>
<p>Found {policies.length} policies matching criteria.</p>
<KopiaTable data={policies} columns={columns} />
<KopiaTable data={policies} columns={columns} tableKey="policies" />
</div>
) : this.state.selectedOwner === localPolicies && this.state.policyPath ? (
<p>
Expand Down
3 changes: 2 additions & 1 deletion src/pages/SnapshotHistory.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ class SnapshotHistoryInternal extends Component {
id: "startTime",
header: "Start time",
width: 200,
accessorFn: (x) => x.startTime, // RFC3339 strings compare correctly as strings
cell: (x) => {
let timestamp = rfc3339TimestampForDisplay(x.row.original.startTime);
return (
Expand Down Expand Up @@ -528,7 +529,7 @@ class SnapshotHistoryInternal extends Component {
)}
<Row>
<Col xs={12}>
<KopiaTable data={snapshots} columns={columns} />
<KopiaTable data={snapshots} columns={columns} tableKey="snapshot-history" />
</Col>
</Row>

Expand Down
2 changes: 1 addition & 1 deletion src/pages/Snapshots.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ export class Snapshots extends Component {
</Row>
</div>

<KopiaTable data={sources} columns={columns} />
<KopiaTable data={sources} columns={columns} tableKey="snapshots" />
<CLIEquivalent command={`snapshot list`} />
</>
);
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Tasks.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ export class Tasks extends Component {
snapshots, restore, run maintenance, etc.
</Alert>
) : (
<KopiaTable data={filteredItems} columns={columns} />
<KopiaTable data={filteredItems} columns={columns} tableKey="tasks" />
)}
</Col>
</Row>
Expand Down
Loading