[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
170
application/state/sftp/useSftpPaneSorting.ts
Normal file
170
application/state/sftp/useSftpPaneSorting.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
DEFAULT_SFTP_COLUMN_VISIBILITY,
|
||||
normalizeSftpColumnVisibility,
|
||||
type ColumnWidths,
|
||||
type SftpColumnVisibility,
|
||||
type SortField,
|
||||
type SortOrder,
|
||||
} from "./columnLayout";
|
||||
import { STORAGE_KEY_SFTP_VISIBLE_COLUMNS } from "../../../infrastructure/config/storageKeys";
|
||||
import {
|
||||
LOCAL_STORAGE_ADAPTER_CHANGED_EVENT,
|
||||
localStorageAdapter,
|
||||
} from "../../../infrastructure/persistence/localStorageAdapter";
|
||||
import { useSftpDirectoriesFirst } from "./useSftpDirectoriesFirst";
|
||||
|
||||
export interface UseSftpPaneSortingResult {
|
||||
sortField: SortField;
|
||||
sortOrder: SortOrder;
|
||||
directoriesFirst: boolean;
|
||||
columnWidths: ColumnWidths;
|
||||
visibleColumns: SftpColumnVisibility;
|
||||
handleSort: (field: SortField) => void;
|
||||
handleResizeStart: (field: keyof ColumnWidths, e: React.MouseEvent) => void;
|
||||
toggleColumnVisibility: (field: keyof ColumnWidths) => void;
|
||||
toggleDirectoriesFirst: () => void;
|
||||
}
|
||||
|
||||
export const useSftpPaneSorting = (): UseSftpPaneSortingResult => {
|
||||
const [sortField, setSortField] = useState<SortField>("name");
|
||||
const [sortOrder, setSortOrder] = useState<SortOrder>("asc");
|
||||
const { directoriesFirst, toggleDirectoriesFirst } = useSftpDirectoriesFirst();
|
||||
const [columnWidths, setColumnWidths] = useState<ColumnWidths>({
|
||||
name: 50,
|
||||
modified: 24,
|
||||
size: 7,
|
||||
type: 9,
|
||||
owner: 10,
|
||||
});
|
||||
const [visibleColumns, setVisibleColumns] = useState<SftpColumnVisibility>(() =>
|
||||
normalizeSftpColumnVisibility(
|
||||
localStorageAdapter.read<Partial<SftpColumnVisibility>>(STORAGE_KEY_SFTP_VISIBLE_COLUMNS),
|
||||
),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const syncVisibleColumns = (event: Event) => {
|
||||
if (event instanceof StorageEvent && event.key !== STORAGE_KEY_SFTP_VISIBLE_COLUMNS) return;
|
||||
if (event instanceof CustomEvent && event.detail?.key !== STORAGE_KEY_SFTP_VISIBLE_COLUMNS) return;
|
||||
const next = normalizeSftpColumnVisibility(
|
||||
localStorageAdapter.read<Partial<SftpColumnVisibility>>(STORAGE_KEY_SFTP_VISIBLE_COLUMNS),
|
||||
);
|
||||
setVisibleColumns(next);
|
||||
};
|
||||
|
||||
window.addEventListener("storage", syncVisibleColumns);
|
||||
window.addEventListener(LOCAL_STORAGE_ADAPTER_CHANGED_EVENT, syncVisibleColumns);
|
||||
return () => {
|
||||
window.removeEventListener("storage", syncVisibleColumns);
|
||||
window.removeEventListener(LOCAL_STORAGE_ADAPTER_CHANGED_EVENT, syncVisibleColumns);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (visibleColumns[sortField]) return;
|
||||
setSortField("name");
|
||||
setSortOrder("asc");
|
||||
}, [sortField, visibleColumns]);
|
||||
|
||||
const toggleColumnVisibility = useCallback((field: keyof ColumnWidths) => {
|
||||
if (field === "name") return;
|
||||
setVisibleColumns((current) => {
|
||||
const next = {
|
||||
...DEFAULT_SFTP_COLUMN_VISIBILITY,
|
||||
...current,
|
||||
name: true,
|
||||
[field]: !current[field],
|
||||
};
|
||||
localStorageAdapter.write(STORAGE_KEY_SFTP_VISIBLE_COLUMNS, next);
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const resizingRef = useRef<{
|
||||
field: keyof ColumnWidths;
|
||||
startX: number;
|
||||
startWidth: number;
|
||||
} | null>(null);
|
||||
|
||||
const handleSort = useCallback((field: SortField) => {
|
||||
if (sortField === field) {
|
||||
setSortOrder((prev) => (prev === "asc" ? "desc" : "asc"));
|
||||
} else {
|
||||
setSortField(field);
|
||||
setSortOrder("asc");
|
||||
}
|
||||
}, [sortField]);
|
||||
|
||||
const rafIdRef = useRef<number | null>(null);
|
||||
const lastClientXRef = useRef(0);
|
||||
|
||||
const applyColumnWidth = useCallback(() => {
|
||||
if (!resizingRef.current) return;
|
||||
const { field, startX, startWidth } = resizingRef.current;
|
||||
const diff = lastClientXRef.current - startX;
|
||||
const limits: Record<keyof ColumnWidths, { min: number; max: number }> = {
|
||||
name: { min: 36, max: 78 },
|
||||
modified: { min: 18, max: 42 },
|
||||
size: { min: 5, max: 16 },
|
||||
type: { min: 6, max: 18 },
|
||||
owner: { min: 6, max: 20 },
|
||||
};
|
||||
const { min, max } = limits[field];
|
||||
const newWidth = Math.max(
|
||||
min,
|
||||
Math.min(max, startWidth + diff / 8),
|
||||
);
|
||||
setColumnWidths((prev) => ({
|
||||
...prev,
|
||||
[field]: newWidth,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const handleResizeMove = useCallback((e: MouseEvent) => {
|
||||
if (!resizingRef.current) return;
|
||||
lastClientXRef.current = e.clientX;
|
||||
if (rafIdRef.current !== null) return;
|
||||
rafIdRef.current = requestAnimationFrame(() => {
|
||||
rafIdRef.current = null;
|
||||
applyColumnWidth();
|
||||
});
|
||||
}, [applyColumnWidth]);
|
||||
|
||||
const handleResizeEnd = useCallback(() => {
|
||||
if (rafIdRef.current !== null) cancelAnimationFrame(rafIdRef.current);
|
||||
applyColumnWidth();
|
||||
rafIdRef.current = null;
|
||||
resizingRef.current = null;
|
||||
document.removeEventListener("mousemove", handleResizeMove);
|
||||
document.removeEventListener("mouseup", handleResizeEnd);
|
||||
}, [applyColumnWidth, handleResizeMove]);
|
||||
|
||||
const handleResizeStart = (
|
||||
field: keyof ColumnWidths,
|
||||
e: React.MouseEvent,
|
||||
) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
lastClientXRef.current = e.clientX;
|
||||
resizingRef.current = {
|
||||
field,
|
||||
startX: e.clientX,
|
||||
startWidth: columnWidths[field],
|
||||
};
|
||||
document.addEventListener("mousemove", handleResizeMove);
|
||||
document.addEventListener("mouseup", handleResizeEnd);
|
||||
};
|
||||
|
||||
return {
|
||||
sortField,
|
||||
sortOrder,
|
||||
directoriesFirst,
|
||||
columnWidths,
|
||||
visibleColumns,
|
||||
handleSort,
|
||||
handleResizeStart,
|
||||
toggleColumnVisibility,
|
||||
toggleDirectoriesFirst,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user