Allow pinned hosts with name sorting (#1043)

This commit is contained in:
ZacharyZcR
2026-07-13 23:00:16 +08:00
committed by GitHub
parent d33c90dd06
commit f2b7dae234
3 changed files with 223 additions and 74 deletions
+53 -74
View File
@@ -44,16 +44,11 @@ import {
} from "@/main-axios";
import type { SSHHostWithStatus } from "@/main-axios";
import type { Host, HostFolder, TabType } from "@/types/ui-types";
type SortKey =
| "default"
| "name-asc"
| "name-desc"
| "ip-asc"
| "ip-desc"
| "status-online"
| "status-offline"
| "pinned";
import {
resolveHostSortPreferences,
sortHostTree,
type SortKey,
} from "@/sidebar/host-sort";
type FilterState = {
status: ("online" | "offline" | "pinned")[];
@@ -127,43 +122,6 @@ function groupHosts(
return { name: "root", children };
}
function sortHostTree(folder: HostFolder, key: SortKey): HostFolder {
if (key === "default") return folder;
const comparator = (a: Host | HostFolder, b: Host | HostFolder): number => {
const aIsFolder = isFolder(a);
const bIsFolder = isFolder(b);
if (aIsFolder && !bIsFolder) return -1;
if (!aIsFolder && bIsFolder) return 1;
if (aIsFolder && bIsFolder)
return (a as HostFolder).name.localeCompare((b as HostFolder).name);
const ha = a as Host,
hb = b as Host;
switch (key) {
case "name-asc":
return ha.name.localeCompare(hb.name);
case "name-desc":
return hb.name.localeCompare(ha.name);
case "ip-asc":
return ha.ip.localeCompare(hb.ip);
case "ip-desc":
return hb.ip.localeCompare(ha.ip);
case "status-online":
return (hb.online ? 1 : 0) - (ha.online ? 1 : 0);
case "status-offline":
return (ha.online ? 1 : 0) - (hb.online ? 1 : 0);
case "pinned":
return (hb.pin ? 1 : 0) - (ha.pin ? 1 : 0);
}
return 0;
};
const sortedChildren = [...folder.children]
.sort(comparator)
.map((child) => (isFolder(child) ? sortHostTree(child, key) : child));
return { ...folder, children: sortedChildren };
}
function hostPassesFilters(host: Host, filters: FilterState): boolean {
if (filters.status.length > 0) {
const ok =
@@ -255,9 +213,18 @@ export function HostsPanel({
const [proxmoxDefaultUsername, setProxmoxDefaultUsername] = useState<
string | undefined
>(undefined);
const [sortKey, setSortKey] = useState<SortKey>(
() => (localStorage.getItem("hostSortKey") as SortKey) ?? "default",
);
const [sortKey, setSortKey] = useState<SortKey>(() => {
return resolveHostSortPreferences(
localStorage.getItem("hostSortKey"),
localStorage.getItem("hostPinnedFirst"),
).sortKey;
});
const [pinnedFirst, setPinnedFirst] = useState(() => {
return resolveHostSortPreferences(
localStorage.getItem("hostSortKey"),
localStorage.getItem("hostPinnedFirst"),
).pinnedFirst;
});
const [groupKey, setGroupKey] = useState<GroupKey>(
() => (localStorage.getItem("hostGroupKey") as GroupKey) ?? "folder",
);
@@ -280,6 +247,11 @@ export function HostsPanel({
localStorage.setItem("hostSortKey", key);
}
function handlePinnedFirstChange(enabled: boolean) {
setPinnedFirst(enabled);
localStorage.setItem("hostPinnedFirst", String(enabled));
}
function handleGroupChange(key: GroupKey) {
setGroupKey(key);
localStorage.setItem("hostGroupKey", key);
@@ -654,7 +626,7 @@ export function HostsPanel({
<Button
variant="ghost"
size="icon"
className={`size-7 ${sortKey !== "default" ? "text-accent-brand" : "text-muted-foreground hover:text-foreground"}`}
className={`size-7 ${sortKey !== "default" || pinnedFirst ? "text-accent-brand" : "text-muted-foreground hover:text-foreground"}`}
title={t("hosts.sortHosts")}
>
<ArrowUpDown className="size-3.5" />
@@ -708,28 +680,32 @@ export function HostsPanel({
</DropdownMenuItem>
))}
<DropdownMenuSeparator />
{(["status-online", "status-offline", "pinned"] as const).map(
(key) => (
<DropdownMenuItem
key={key}
onClick={() => handleSortChange(key)}
className="flex items-center gap-1.5"
>
{sortKey === key ? (
<Check className="size-3 shrink-0 text-accent-brand" />
) : (
<span className="size-3 shrink-0 inline-block" />
)}
{t(
key === "status-online"
? "hosts.sortOnlineFirst"
: key === "status-offline"
? "hosts.sortOfflineFirst"
: "hosts.sortPinnedFirst",
)}
</DropdownMenuItem>
),
)}
{(["status-online", "status-offline"] as const).map((key) => (
<DropdownMenuItem
key={key}
onClick={() => handleSortChange(key)}
className="flex items-center gap-1.5"
>
{sortKey === key ? (
<Check className="size-3 shrink-0 text-accent-brand" />
) : (
<span className="size-3 shrink-0 inline-block" />
)}
{t(
key === "status-online"
? "hosts.sortOnlineFirst"
: "hosts.sortOfflineFirst",
)}
</DropdownMenuItem>
))}
<DropdownMenuSeparator />
<DropdownMenuCheckboxItem
checked={pinnedFirst}
onCheckedChange={handlePinnedFirstChange}
onSelect={(event) => event.preventDefault()}
>
{t("hosts.sortPinnedFirst")}
</DropdownMenuCheckboxItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
@@ -1017,7 +993,10 @@ export function HostsPanel({
children={
hostTree
? groupHosts(
applyFilters(sortHostTree(hostTree, sortKey), filterState),
applyFilters(
sortHostTree(hostTree, sortKey, pinnedFirst),
filterState,
),
groupKey,
groupLabel,
).children