From ecd2464bbf58ed89cbf96a8cb6f89729d57f7356 Mon Sep 17 00:00:00 2001 From: Sylvie Crowe <107814465+oneirocosm@users.noreply.github.com> Date: Thu, 27 Jun 2024 18:13:42 -0700 Subject: [PATCH] Hidden Files (#86) Adds the following changes - rename "Permissions" to "Perm" - use a "-" if the mimetype is unknown - add a button to hide and show hidden files - fix the datetime to format based on how far in the past the date is --- frontend/app/view/directorypreview.tsx | 174 ++++++++++++++----------- pkg/wconfig/datetimestyle.go | 59 --------- pkg/wconfig/settingsconfig.go | 19 --- 3 files changed, 100 insertions(+), 152 deletions(-) delete mode 100644 pkg/wconfig/datetimestyle.go diff --git a/frontend/app/view/directorypreview.tsx b/frontend/app/view/directorypreview.tsx index a58efddfe..a72cb1834 100644 --- a/frontend/app/view/directorypreview.tsx +++ b/frontend/app/view/directorypreview.tsx @@ -1,6 +1,7 @@ // Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import { Button } from "@/element/button"; import * as services from "@/store/services"; import * as util from "@/util/util"; import { @@ -12,6 +13,7 @@ import { useReactTable, } from "@tanstack/react-table"; import clsx from "clsx"; +import dayjs from "dayjs"; import * as jotai from "jotai"; import React from "react"; import { ContextMenuModel } from "../store/contextmenu"; @@ -21,12 +23,12 @@ import "./directorypreview.less"; interface DirectoryTableProps { data: FileInfo[]; - cwd: string; focusIndex: number; - enter: boolean; setFocusIndex: (_: number) => void; setFileName: (_: string) => void; setSearch: (_: string) => void; + setSelectedPath: (_: string) => void; + setRefresh: React.Dispatch>; } const columnHelper = createColumnHelper(); @@ -83,15 +85,17 @@ function getSpecificUnit(bytes: number, suffix: string): string { return `${bytes / divisor} ${displaySuffixes[suffix]}`; } -function getLastModifiedTime( - unixMillis: number, - locale: Intl.LocalesArgument, - options: DateTimeFormatConfigType -): string { - if (locale === "C") { - locale = "lookup"; +function getLastModifiedTime(unixMillis: number): string { + let fileDatetime = dayjs(new Date(unixMillis)); + let nowDatetime = dayjs(new Date()); + + if (nowDatetime.year() != fileDatetime.year()) { + return dayjs(fileDatetime).format("M/D/YY"); + } else if (nowDatetime.month() != fileDatetime.month()) { + return dayjs(fileDatetime).format("MMM D"); + } else { + return dayjs(fileDatetime).format("h:mm A"); } - return new Date(unixMillis).toLocaleString(locale, options); //todo use config } const iconRegex = /^[a-z0-9- ]+$/; @@ -121,41 +125,23 @@ function getSortIcon(sortType: string | boolean): React.ReactNode { } } -function handleFileContextMenu(e: React.MouseEvent, path: string) { - e.preventDefault(); - e.stopPropagation(); - let menu: ContextMenuItem[] = []; - menu.push({ - label: "Open in New Block", - click: async () => { - const blockDef = { - view: "preview", - meta: { file: path }, - }; - await createBlock(blockDef); - }, - }); - menu.push({ - label: "Delete File", - click: async () => { - await services.FileService.DeleteFile(path).catch((e) => console.log(e)); //todo these errors need a popup - }, - }); - menu.push({ - label: "Download File", - click: async () => { - getApi().downloadFile(path); - }, - }); - ContextMenuModel.showContextMenu(menu, e); -} - function cleanMimetype(input: string): string { + if (input == "") { + return "-"; + } const truncated = input.split(";")[0]; return truncated.trim(); } -function DirectoryTable({ data, cwd, focusIndex, enter, setFocusIndex, setFileName, setSearch }: DirectoryTableProps) { +function DirectoryTable({ + data, + focusIndex, + setFocusIndex, + setFileName, + setSearch, + setSelectedPath, + setRefresh, +}: DirectoryTableProps) { let settings = jotai.useAtomValue(atoms.settingsConfigAtom); const getIconFromMimeType = React.useCallback( (mimeType: string): string => { @@ -198,16 +184,12 @@ function DirectoryTable({ data, cwd, focusIndex, enter, setFocusIndex, setFileNa }), columnHelper.accessor("modestr", { cell: (info) => {info.getValue()}, - header: () => Permissions, + header: () => Perm, size: 91, sortingFn: "alphanumeric", }), columnHelper.accessor("modtime", { - cell: (info) => ( - - {getLastModifiedTime(info.getValue(), settings.datetime.locale, settings.datetime.format)} - - ), + cell: (info) => {getLastModifiedTime(info.getValue())}, header: () => Last Modified, size: 185, sortingFn: "datetime", @@ -290,22 +272,22 @@ function DirectoryTable({ data, cwd, focusIndex, enter, setFocusIndex, setFileNa {table.getState().columnSizingInfo.isResizingColumn ? ( ) : ( )} @@ -314,26 +296,60 @@ function DirectoryTable({ data, cwd, focusIndex, enter, setFocusIndex, setFileNa interface TableBodyProps { table: Table; - cwd: string; focusIndex: number; - enter: boolean; setFocusIndex: (_: number) => void; setFileName: (_: string) => void; setSearch: (_: string) => void; + setSelectedPath: (_: string) => void; + setRefresh: React.Dispatch>; } -function TableBody({ table, cwd, focusIndex, enter, setFocusIndex, setFileName, setSearch }: TableBodyProps) { - let [refresh, setRefresh] = React.useState(false); - +function TableBody({ + table, + focusIndex, + setFocusIndex, + setFileName, + setSearch, + setSelectedPath, + setRefresh, +}: TableBodyProps) { React.useEffect(() => { - const selected = (table.getSortedRowModel()?.flatRows[focusIndex]?.getValue("path") as string) ?? null; - if (selected != null) { - setFileName(selected); - setSearch(""); - } - }, [enter]); + setSelectedPath((table.getSortedRowModel()?.flatRows[focusIndex]?.getValue("path") as string) ?? null); + }, [table, focusIndex]); + + const handleFileContextMenu = React.useCallback( + (e: React.MouseEvent, path: string) => { + e.preventDefault(); + e.stopPropagation(); + let menu: ContextMenuItem[] = []; + menu.push({ + label: "Open in New Block", + click: async () => { + const blockDef = { + view: "preview", + meta: { file: path }, + }; + await createBlock(blockDef); + }, + }); + menu.push({ + label: "Delete File", + click: async () => { + await services.FileService.DeleteFile(path).catch((e) => console.log(e)); //todo these errors need a popup + setRefresh((current) => !current); + }, + }); + menu.push({ + label: "Download File", + click: async () => { + getApi().downloadFile(path); + }, + }); + ContextMenuModel.showContextMenu(menu, e); + }, + [setRefresh] + ); - table.getRow; return (
{table.getRowModel().rows.map((row, idx) => ( @@ -376,10 +392,12 @@ interface DirectoryPreviewProps { function DirectoryPreview({ fileNameAtom }: DirectoryPreviewProps) { const [searchText, setSearchText] = React.useState(""); - let [focusIndex, setFocusIndex] = React.useState(0); + const [focusIndex, setFocusIndex] = React.useState(0); const [content, setContent] = React.useState([]); - let [fileName, setFileName] = jotai.useAtom(fileNameAtom); - const [enter, setEnter] = React.useState(false); + const [fileName, setFileName] = jotai.useAtom(fileNameAtom); + const [hideHiddenFiles, setHideHiddenFiles] = React.useState(true); + const [selectedPath, setSelectedPath] = React.useState(""); + const [refresh, setRefresh] = React.useState(false); React.useEffect(() => { const getContent = async () => { @@ -387,12 +405,15 @@ function DirectoryPreview({ fileNameAtom }: DirectoryPreviewProps) { const serializedContent = util.base64ToString(file?.data64); let content: FileInfo[] = JSON.parse(serializedContent); let filtered = content.filter((fileInfo) => { + if (hideHiddenFiles && fileInfo.name.startsWith(".")) { + return false; + } return fileInfo.name.toLowerCase().includes(searchText); }); setContent(filtered); }; getContent(); - }, [fileName, searchText]); + }, [fileName, searchText, hideHiddenFiles, refresh]); const handleKeyDown = React.useCallback( (e) => { @@ -410,16 +431,13 @@ function DirectoryPreview({ fileNameAtom }: DirectoryPreviewProps) { break; case "Enter": e.preventDefault(); - setEnter((current) => !current); - /* - const fullPath = fileName.concat("/", newFileName); - setFileName(fullPath); - */ + setFileName(selectedPath); + setSearchText(""); break; default: } }, - [content, focusIndex, setEnter] + [content, focusIndex, selectedPath] ); React.useEffect(() => { @@ -443,15 +461,23 @@ function DirectoryPreview({ fileNameAtom }: DirectoryPreviewProps) { autoFocus={true} value={searchText} /> + +
); diff --git a/pkg/wconfig/datetimestyle.go b/pkg/wconfig/datetimestyle.go deleted file mode 100644 index 357e38d2a..000000000 --- a/pkg/wconfig/datetimestyle.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2024, Command Line Inc. -// SPDX-License-Identifier: Apache-2.0 - -package wconfig - -import ( - "encoding/json" - "fmt" -) - -type DateTimeStyle uint8 - -const ( - DateTimeStyleFull = iota + 1 - DateTimeStyleLong - DateTimeStyleMedium - DateTimeStyleShort -) - -var dateTimeStyleToString = map[uint8]string{ - 1: "full", - 2: "long", - 3: "medium", - 4: "short", -} - -var stringToDateTimeStyle = map[string]uint8{ - "full": 1, - "long": 2, - "medium": 3, - "short": 4, -} - -func (dts DateTimeStyle) String() string { - return dateTimeStyleToString[uint8(dts)] -} - -func parseDateTimeStyle(input string) (DateTimeStyle, error) { - value, ok := stringToDateTimeStyle[input] - if !ok { - return DateTimeStyle(0), fmt.Errorf("%q is not a valid date-time style", input) - } - return DateTimeStyle(value), nil -} - -func (dts DateTimeStyle) MarshalJSON() ([]byte, error) { - return json.Marshal(dts.String()) -} - -func (dts *DateTimeStyle) UnmarshalJSON(data []byte) (err error) { - var buffer string - if err := json.Unmarshal(data, &buffer); err != nil { - return err - } - if *dts, err = parseDateTimeStyle(buffer); err != nil { - return err - } - return nil -} diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index 4d23af27a..674143a8c 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -27,17 +27,6 @@ type TerminalConfigType struct { FontFamily string `json:"fontfamily,omitempty"` } -type DateTimeConfigType struct { - Locale string `json:"locale"` - Format DateTimeFormatConfigType `json:"format"` -} - -type DateTimeFormatConfigType struct { - DateStyle DateTimeStyle `json:"dateStyle"` - TimeStyle DateTimeStyle `json:"timeStyle"` - //TimeZone string `json:"timeZone"` TODO: need a universal way to obtain this before adding it -} - type MimeTypeConfigType struct { Icon string `json:"icon"` Color string `json:"color"` @@ -49,7 +38,6 @@ type BlockHeaderOpts struct { type SettingsConfigType struct { MimeTypes map[string]MimeTypeConfigType `json:"mimetypes"` - DateTime DateTimeConfigType `json:"datetime"` Term TerminalConfigType `json:"term"` Widgets []WidgetsConfigType `json:"widgets"` BlockHeader BlockHeaderOpts `json:"blockheader"` @@ -57,13 +45,6 @@ type SettingsConfigType struct { func getSettingsConfigDefaults() SettingsConfigType { return SettingsConfigType{ - DateTime: DateTimeConfigType{ - Locale: wavebase.DetermineLocale(), - Format: DateTimeFormatConfigType{ - DateStyle: DateTimeStyleMedium, - TimeStyle: DateTimeStyleMedium, - }, - }, MimeTypes: map[string]MimeTypeConfigType{ "audio": {Icon: "file-audio"}, "application/pdf": {Icon: "file-pdf"},