mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
New Directory View Columns (#71)
This adds several new columns to the directory view. It adds a last modified timestamp, a logo for the type, human-readable file sizes, and permissions. Several of these are configurable via the config/settings.json file.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
.dir-table {
|
||||
--col-size-size: 0.2rem;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
border-radius: 3px;
|
||||
@@ -9,7 +10,7 @@
|
||||
padding: 4px 0;
|
||||
background-color: var(--panel-bg-color);
|
||||
|
||||
.dir-table-head-cell {
|
||||
.dir-table-head-cell:not(:first-child) {
|
||||
position: relative;
|
||||
padding: 2px 4px;
|
||||
font-weight: bold;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as util from "@/util/util";
|
||||
import { Table, createColumnHelper, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
||||
import clsx from "clsx";
|
||||
import * as jotai from "jotai";
|
||||
import React from "react";
|
||||
import { atoms } from "../store/global";
|
||||
|
||||
import "./directorypreview.less";
|
||||
|
||||
@@ -16,23 +18,135 @@ interface DirectoryTableProps {
|
||||
|
||||
const columnHelper = createColumnHelper<FileInfo>();
|
||||
|
||||
const defaultColumns = [
|
||||
columnHelper.accessor("path", {
|
||||
cell: (info) => info.getValue(),
|
||||
header: () => <span>Name</span>,
|
||||
}),
|
||||
columnHelper.accessor("size", {
|
||||
cell: (info) => info.getValue(),
|
||||
header: () => <span>Size</span>,
|
||||
}),
|
||||
columnHelper.accessor("mimetype", {
|
||||
cell: (info) => info.getValue(),
|
||||
header: () => <span>Type</span>,
|
||||
}),
|
||||
];
|
||||
const displaySuffixes = {
|
||||
B: "b",
|
||||
kB: "k",
|
||||
MB: "m",
|
||||
GB: "g",
|
||||
TB: "t",
|
||||
KiB: "k",
|
||||
MiB: "m",
|
||||
GiB: "g",
|
||||
TiB: "t",
|
||||
};
|
||||
|
||||
function getBestUnit(bytes: number, si: boolean = false, sigfig: number = 3): string {
|
||||
if (bytes < 0) {
|
||||
return "";
|
||||
}
|
||||
const units = si ? ["kB", "MB", "GB", "TB"] : ["KiB", "MiB", "GiB", "TiB"];
|
||||
const divisor = si ? 1000 : 1024;
|
||||
|
||||
let currentUnit = "B";
|
||||
let currentValue = bytes;
|
||||
let idx = 0;
|
||||
while (currentValue > divisor && idx < units.length - 1) {
|
||||
currentUnit = units[idx];
|
||||
currentValue /= divisor;
|
||||
}
|
||||
|
||||
return `${parseFloat(currentValue.toPrecision(sigfig))}${displaySuffixes[currentUnit]}`;
|
||||
}
|
||||
|
||||
function getSpecificUnit(bytes: number, suffix: string): string {
|
||||
if (bytes < 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const divisors = new Map([
|
||||
["B", 1],
|
||||
["kB", 1e3],
|
||||
["MB", 1e6],
|
||||
["GB", 1e9],
|
||||
["TB", 1e12],
|
||||
["KiB", 0x400],
|
||||
["MiB", 0x400 ** 2],
|
||||
["GiB", 0x400 ** 3],
|
||||
["TiB", 0x400 ** 4],
|
||||
]);
|
||||
const divisor: number = divisors[suffix] ?? 1;
|
||||
|
||||
return `${bytes / divisor} ${displaySuffixes[suffix]}`;
|
||||
}
|
||||
|
||||
function getLastModifiedTime(
|
||||
unixMillis: number,
|
||||
locale: Intl.LocalesArgument,
|
||||
options: DateTimeFormatConfigType
|
||||
): string {
|
||||
if (locale === "C") {
|
||||
locale = "lookup";
|
||||
}
|
||||
return new Date(unixMillis).toLocaleString(locale, options); //todo use config
|
||||
}
|
||||
|
||||
const iconRegex = /^[a-z0-9- ]+$/;
|
||||
|
||||
function isIconValid(icon: string): boolean {
|
||||
if (util.isBlank(icon)) {
|
||||
return false;
|
||||
}
|
||||
return icon.match(iconRegex) != null;
|
||||
}
|
||||
|
||||
function getIconClass(icon: string): string {
|
||||
if (!isIconValid(icon)) {
|
||||
return "fa fa-solid fa-question fa-fw";
|
||||
}
|
||||
return `fa fa-solid fa-${icon} fa-fw`;
|
||||
}
|
||||
|
||||
function DirectoryTable({ data, cwd, setFileName }: DirectoryTableProps) {
|
||||
const [columns] = React.useState<typeof defaultColumns>(() => [...defaultColumns]);
|
||||
let settings = jotai.useAtomValue(atoms.settingsConfigAtom);
|
||||
const getIconFromMimeType = React.useCallback(
|
||||
(mimeType: string): string => {
|
||||
while (mimeType.length > 0) {
|
||||
let icon = settings.mimetypes[mimeType]?.icon ?? null;
|
||||
if (isIconValid(icon)) {
|
||||
return `fa fa-solid fa-${icon} fa-fw`;
|
||||
}
|
||||
mimeType = mimeType.slice(0, -1);
|
||||
}
|
||||
return "fa fa-solid fa-question fa-fw";
|
||||
},
|
||||
[settings.mimetypes]
|
||||
);
|
||||
const columns = React.useMemo(
|
||||
() => [
|
||||
columnHelper.accessor("mimetype", {
|
||||
cell: (info) => <i className={getIconFromMimeType(info.getValue() ?? "")}></i>,
|
||||
header: () => <span></span>,
|
||||
id: "logo",
|
||||
size: 25,
|
||||
}),
|
||||
columnHelper.accessor("path", {
|
||||
cell: (info) => info.getValue(),
|
||||
header: () => <span>Name</span>,
|
||||
}),
|
||||
columnHelper.accessor("modestr", {
|
||||
cell: (info) => info.getValue(),
|
||||
header: () => <span>Permissions</span>,
|
||||
size: 91,
|
||||
}),
|
||||
columnHelper.accessor("modtime", {
|
||||
cell: (info) =>
|
||||
getLastModifiedTime(info.getValue(), settings.datetime.locale, settings.datetime.format),
|
||||
header: () => <span>Last Modified</span>,
|
||||
size: 185,
|
||||
}),
|
||||
columnHelper.accessor("size", {
|
||||
cell: (info) => getBestUnit(info.getValue()),
|
||||
header: () => <span>Size</span>,
|
||||
size: 55,
|
||||
}),
|
||||
columnHelper.accessor("mimetype", {
|
||||
cell: (info) => info.getValue(),
|
||||
header: () => <span>Type</span>,
|
||||
}),
|
||||
],
|
||||
[settings]
|
||||
);
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
@@ -111,7 +225,7 @@ function TableBody({ table, cwd, setFileName }: TableBodyProps) {
|
||||
key={cell.id}
|
||||
style={{ width: `calc(var(--col-${cell.column.id}-size) * 1px)` }}
|
||||
>
|
||||
{cell.renderValue<string>()}
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
Vendored
+18
@@ -97,6 +97,16 @@ declare global {
|
||||
rtopts?: RuntimeOpts;
|
||||
};
|
||||
|
||||
type DateTimeConfigType = {
|
||||
locale: string;
|
||||
format: DateTimeFormatConfigType;
|
||||
}
|
||||
|
||||
type DateTimeFormatConfigType = {
|
||||
dateStyle: "full" | "long" | "medium" | "short";
|
||||
timeStyle: "full" | "long" | "medium" | "short";
|
||||
}
|
||||
|
||||
// wstore.FileDef
|
||||
type FileDef = {
|
||||
filetype?: string;
|
||||
@@ -112,6 +122,7 @@ declare global {
|
||||
notfound?: boolean;
|
||||
size: number;
|
||||
mode: number;
|
||||
modestr: string;
|
||||
modtime: number;
|
||||
isdir?: boolean;
|
||||
mimetype?: string;
|
||||
@@ -146,6 +157,11 @@ declare global {
|
||||
ReturnDesc: string;
|
||||
};
|
||||
|
||||
//wconfig.MimeTypeConfigType
|
||||
type MimeTypeConfigType = {
|
||||
icon: string;
|
||||
}
|
||||
|
||||
// waveobj.ORef
|
||||
type ORef = {
|
||||
otype: string;
|
||||
@@ -179,6 +195,8 @@ declare global {
|
||||
|
||||
// wconfig.SettingsConfigType
|
||||
type SettingsConfigType = {
|
||||
datetime: DateTimeConfigType;
|
||||
mimetypes: {[key:string]: MimeTypeConfigType}
|
||||
widgets: WidgetsConfigType[];
|
||||
term: TerminalConfigType;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user