File details view PT1
|
|
@ -1,12 +1,16 @@
|
|||
import styled from "styled-components";
|
||||
import StyledLoading from "components/system/Files/FileManager/StyledLoading";
|
||||
import StyledDetailsFileManager from "components/system/Files/Views/Details/StyledFileManager";
|
||||
import StyledIconFileManager from "components/system/Files/Views/Icon/StyledFileManager";
|
||||
|
||||
const StyledFileExplorer = styled.div`
|
||||
${StyledIconFileManager} {
|
||||
column-gap: 2px;
|
||||
${StyledDetailsFileManager}, ${StyledIconFileManager} {
|
||||
height: ${({ theme }) =>
|
||||
`calc(100% - ${theme.sizes.fileExplorer.navBarHeight} - ${theme.sizes.fileExplorer.statusBarHeight})`};
|
||||
}
|
||||
|
||||
${StyledIconFileManager} {
|
||||
column-gap: 2px;
|
||||
padding: 6px;
|
||||
|
||||
figcaption {
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ const FileExplorer: FC<ComponentProcessProps> = ({ id }) => {
|
|||
return url ? (
|
||||
<StyledFileExplorer>
|
||||
<Navigation ref={inputRef} hideSearch={Boolean(mountUrl)} id={id} />
|
||||
<FileManager id={id} url={url} view="icon" showStatusBar />
|
||||
<FileManager id={id} url={url} showStatusBar />
|
||||
</StyledFileExplorer>
|
||||
) : // eslint-disable-next-line unicorn/no-null
|
||||
null;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ const Desktop: FC = ({ children }) => {
|
|||
<StyledDesktop ref={desktopRef}>
|
||||
<FileManager
|
||||
url={DESKTOP_PATH}
|
||||
view="icon"
|
||||
allowMovingDraggableEntries
|
||||
hideLoading
|
||||
hideScrolling
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import { basename, dirname, extname, join } from "path";
|
|||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import Buttons from "components/system/Dialogs/Properties/Buttons";
|
||||
import useStats from "components/system/Dialogs/Properties/useStats";
|
||||
import extensions from "components/system/Files/FileEntry/extensions";
|
||||
import {
|
||||
getFileType,
|
||||
getIconFromIni,
|
||||
getModifiedTime,
|
||||
} from "components/system/Files/FileEntry/functions";
|
||||
|
|
@ -46,8 +46,7 @@ const GeneralTab: FC<TabProps> = ({ icon, id, isShortcut, pid, url }) => {
|
|||
const { closeWithTransition, icon: setIcon } = useProcesses();
|
||||
const { setIconPositions } = useSession();
|
||||
const extension = useMemo(() => getExtension(url || ""), [url]);
|
||||
const { type } = extensions[extension] || {};
|
||||
const extType = type || `${extension.toUpperCase().replace(".", "")} File`;
|
||||
const extType = getFileType(extension);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const { fs, readdir, rename, stat, updateFolder } = useFileSystem();
|
||||
const stats = useStats(url);
|
||||
|
|
|
|||
64
components/system/Files/FileEntry/ColumnRow.tsx
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import type Stats from "browserfs/dist/node/core/node_fs_stats";
|
||||
import { useCallback, useState, useRef, useEffect, memo } from "react";
|
||||
import { useTheme } from "styled-components";
|
||||
import StyledColumnRow from "components/system/Files/FileEntry/StyledColumnRow";
|
||||
import { type Columns } from "components/system/Files/FileManager/Columns/constants";
|
||||
import {
|
||||
getDateModified,
|
||||
getFileType,
|
||||
} from "components/system/Files/FileEntry/functions";
|
||||
import { UNKNOWN_SIZE } from "contexts/fileSystem/core";
|
||||
import { useFileSystem } from "contexts/fileSystem";
|
||||
import { getExtension, getFormattedSize } from "utils/functions";
|
||||
|
||||
type ColumnDataProps = {
|
||||
date: string;
|
||||
size: string;
|
||||
type: string;
|
||||
};
|
||||
|
||||
const ColumnRow: FC<{
|
||||
columns: Columns;
|
||||
isDirectory: boolean;
|
||||
isShortcut: boolean;
|
||||
path: string;
|
||||
stats: Stats;
|
||||
}> = ({ columns, isDirectory, isShortcut, path, stats }) => {
|
||||
const { stat } = useFileSystem();
|
||||
const { formats } = useTheme();
|
||||
const getColumnData = useCallback(async (): Promise<ColumnDataProps> => {
|
||||
const fullStats = stats.size === UNKNOWN_SIZE ? await stat(path) : stats;
|
||||
|
||||
return {
|
||||
date: getDateModified(path, fullStats, formats.dateModified),
|
||||
size: isDirectory ? "" : getFormattedSize(fullStats.size, true),
|
||||
type: isDirectory
|
||||
? "File folder"
|
||||
: isShortcut
|
||||
? "Shortcut"
|
||||
: getFileType(getExtension(path)),
|
||||
};
|
||||
}, [formats.dateModified, isDirectory, isShortcut, path, stat, stats]);
|
||||
const [columnData, setColumnData] = useState<ColumnDataProps>();
|
||||
const creatingRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!columnData && !creatingRef.current) {
|
||||
creatingRef.current = true;
|
||||
getColumnData().then((newColumnData) => {
|
||||
setColumnData(newColumnData);
|
||||
creatingRef.current = false;
|
||||
});
|
||||
}
|
||||
}, [columnData, getColumnData]);
|
||||
|
||||
return (
|
||||
<StyledColumnRow>
|
||||
<div style={{ width: columns?.date.width }}>{columnData?.date}</div>
|
||||
<div style={{ width: columns?.type.width }}>{columnData?.type}</div>
|
||||
<div style={{ width: columns?.size.width }}>{columnData?.size}</div>
|
||||
</StyledColumnRow>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ColumnRow);
|
||||
19
components/system/Files/FileEntry/StyledColumnRow.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import styled from "styled-components";
|
||||
|
||||
const StyledColumnRow = styled.div`
|
||||
display: flex;
|
||||
|
||||
div {
|
||||
color: #fff;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&:last-child {
|
||||
margin-right: -6px;
|
||||
padding-right: 6px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledColumnRow;
|
||||
|
|
@ -19,7 +19,7 @@ type IconProps = {
|
|||
};
|
||||
|
||||
type SharedSubIconProps = {
|
||||
imgSize?: 64 | 32 | 16;
|
||||
imgSize?: 64 | 32 | 16 | 8;
|
||||
isDesktop?: boolean;
|
||||
};
|
||||
|
||||
|
|
@ -51,18 +51,25 @@ const SubIcon: FC<SubIconProps> = ({
|
|||
totalSubIcons,
|
||||
view,
|
||||
}) => {
|
||||
const iconView = useMemo(
|
||||
() =>
|
||||
FileEntryIconSize[
|
||||
![SHORTCUT_ICON, FOLDER_FRONT_ICON].includes(icon) &&
|
||||
!icon.startsWith("blob:") &&
|
||||
!icon.startsWith(ICON_CACHE) &&
|
||||
!icon.startsWith(YT_ICON_CACHE)
|
||||
? "sub"
|
||||
: view
|
||||
],
|
||||
[icon, view]
|
||||
);
|
||||
const iconView = useMemo(() => {
|
||||
const isSub =
|
||||
![SHORTCUT_ICON, FOLDER_FRONT_ICON].includes(icon) &&
|
||||
!icon.startsWith("blob:") &&
|
||||
!icon.startsWith(ICON_CACHE) &&
|
||||
!icon.startsWith(YT_ICON_CACHE);
|
||||
|
||||
if (icon === SHORTCUT_ICON && view === "details") {
|
||||
return {
|
||||
displaySize: 16,
|
||||
imgSize: 48,
|
||||
};
|
||||
}
|
||||
|
||||
return FileEntryIconSize[
|
||||
isSub ? (view === "details" ? "detailsSub" : "sub") : view
|
||||
];
|
||||
}, [icon, view]);
|
||||
|
||||
const style = useMemo((): React.CSSProperties | undefined => {
|
||||
if (icon === FOLDER_FRONT_ICON) return { zIndex: 3 };
|
||||
|
||||
|
|
@ -120,10 +127,19 @@ const SubIcons: FC<SubIconsProps> = ({
|
|||
: subIcons,
|
||||
[showShortcutIcon, subIcons]
|
||||
);
|
||||
const filteredSubIcons = useMemo(
|
||||
() => icons?.filter((subIcon) => subIcon !== icon) || [],
|
||||
[icon, icons]
|
||||
);
|
||||
const filteredSubIcons = useMemo(() => {
|
||||
const iconsLength = icons?.length;
|
||||
|
||||
if (
|
||||
iconsLength &&
|
||||
view === "details" &&
|
||||
icons[iconsLength - 1] === FOLDER_FRONT_ICON
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return icons?.filter((subIcon) => subIcon !== icon) || [];
|
||||
}, [icon, icons, view]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import processDirectory from "contexts/process/directory";
|
|||
import {
|
||||
AUDIO_FILE_EXTENSIONS,
|
||||
BASE_2D_CONTEXT_OPTIONS,
|
||||
DEFAULT_LOCALE,
|
||||
DYNAMIC_EXTENSION,
|
||||
DYNAMIC_PREFIX,
|
||||
FOLDER_BACK_ICON,
|
||||
|
|
@ -78,7 +79,13 @@ export const isExistingFile = (
|
|||
export const getModifiedTime = (path: string, stats: FileStat): number => {
|
||||
const { mtimeMs } = stats;
|
||||
|
||||
return isExistingFile(stats) ? get9pModifiedTime(path) || mtimeMs : mtimeMs;
|
||||
if (isExistingFile(stats)) {
|
||||
const storedMtime = get9pModifiedTime(path);
|
||||
|
||||
if (storedMtime > 0) return storedMtime;
|
||||
}
|
||||
|
||||
return mtimeMs;
|
||||
};
|
||||
|
||||
export const getIconFromIni = (
|
||||
|
|
@ -879,3 +886,21 @@ export const getTextWrapData = (
|
|||
width: Math.min(maxWidth, totalWidth),
|
||||
};
|
||||
};
|
||||
|
||||
export const getDateModified = (
|
||||
path: string,
|
||||
fullStats: Stats,
|
||||
format: Intl.DateTimeFormatOptions
|
||||
): string => {
|
||||
const modifiedTime = getModifiedTime(path, fullStats);
|
||||
const date = new Date(modifiedTime).toISOString().slice(0, 10);
|
||||
const time = new Intl.DateTimeFormat(DEFAULT_LOCALE, format).format(
|
||||
modifiedTime
|
||||
);
|
||||
|
||||
return `${date} ${time}`;
|
||||
};
|
||||
|
||||
export const getFileType = (extension: string): string =>
|
||||
extensions[extension]?.type ||
|
||||
`${extension.toUpperCase().replace(".", "")} File`;
|
||||
|
|
|
|||
|
|
@ -10,12 +10,14 @@ import {
|
|||
} from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { m as motion } from "framer-motion";
|
||||
import ColumnRow from "components/system/Files/FileEntry/ColumnRow";
|
||||
import { type Columns } from "components/system/Files/FileManager/Columns/constants";
|
||||
import StyledFigure from "components/system/Files/FileEntry/StyledFigure";
|
||||
import SubIcons from "components/system/Files/FileEntry/SubIcons";
|
||||
import extensions from "components/system/Files/FileEntry/extensions";
|
||||
import {
|
||||
getCachedIconUrl,
|
||||
getModifiedTime,
|
||||
getDateModified,
|
||||
getFileType,
|
||||
getTextWrapData,
|
||||
} from "components/system/Files/FileEntry/functions";
|
||||
import useFile from "components/system/Files/FileEntry/useFile";
|
||||
|
|
@ -38,7 +40,6 @@ import useDoubleClick from "hooks/useDoubleClick";
|
|||
import Button from "styles/common/Button";
|
||||
import Icon from "styles/common/Icon";
|
||||
import {
|
||||
DEFAULT_LOCALE,
|
||||
ICON_CACHE,
|
||||
ICON_CACHE_EXTENSION,
|
||||
ICON_PATH,
|
||||
|
|
@ -76,6 +77,7 @@ const RenameBox = dynamic(
|
|||
);
|
||||
|
||||
type FileEntryProps = {
|
||||
columns?: Columns;
|
||||
fileActions: FileActions;
|
||||
fileManagerId?: string;
|
||||
fileManagerRef: React.MutableRefObject<HTMLOListElement | null>;
|
||||
|
|
@ -129,6 +131,7 @@ const focusing: string[] = [];
|
|||
const cacheQueue: (() => Promise<void>)[] = [];
|
||||
|
||||
const FileEntry: FC<FileEntryProps> = ({
|
||||
columns,
|
||||
fileActions,
|
||||
fileManagerId,
|
||||
fileManagerRef,
|
||||
|
|
@ -153,9 +156,10 @@ const FileEntry: FC<FileEntryProps> = ({
|
|||
const { url: changeUrl } = useProcesses();
|
||||
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
||||
const isVisible = useIsVisible(buttonRef, fileManagerRef, isDesktop);
|
||||
const isDirectory = useMemo(() => stats.isDirectory(), [stats]);
|
||||
const [{ comment, getIcon, icon, pid, subIcons, url }, setInfo] = useFileInfo(
|
||||
path,
|
||||
stats.isDirectory(),
|
||||
isDirectory,
|
||||
hasNewFolderIcon,
|
||||
isDesktop || isVisible
|
||||
);
|
||||
|
|
@ -226,7 +230,7 @@ const FileEntry: FC<FileEntryProps> = ({
|
|||
const isDynamicIconLoaded = useRef(false);
|
||||
const getIconAbortController = useRef<AbortController>();
|
||||
const createTooltip = useCallback(async (): Promise<string> => {
|
||||
if (stats.isDirectory()) return "";
|
||||
if (isDirectory) return "";
|
||||
|
||||
if (isShortcut) {
|
||||
if (comment) return comment;
|
||||
|
|
@ -244,28 +248,21 @@ const FileEntry: FC<FileEntryProps> = ({
|
|||
return "";
|
||||
}
|
||||
|
||||
const type =
|
||||
extensions[extension]?.type ||
|
||||
`${extension.toUpperCase().replace(".", "")} File`;
|
||||
const type = getFileType(extension);
|
||||
const fullStats = stats.size === UNKNOWN_SIZE ? await stat(path) : stats;
|
||||
const { size: sizeInBytes } = fullStats;
|
||||
const modifiedTime = getModifiedTime(path, fullStats);
|
||||
const size = getFormattedSize(sizeInBytes);
|
||||
const toolTip = `Type: ${type}${
|
||||
size === "-1 bytes" ? "" : `\nSize: ${size}`
|
||||
}`;
|
||||
const date = new Date(modifiedTime).toISOString().slice(0, 10);
|
||||
const time = new Intl.DateTimeFormat(
|
||||
DEFAULT_LOCALE,
|
||||
formats.dateModified
|
||||
).format(modifiedTime);
|
||||
const dateModified = `${date} ${time}`;
|
||||
const dateModified = getDateModified(path, fullStats, formats.dateModified);
|
||||
|
||||
return `${toolTip}\nDate modified: ${dateModified}`;
|
||||
}, [
|
||||
comment,
|
||||
extension,
|
||||
formats.dateModified,
|
||||
isDirectory,
|
||||
isShortcut,
|
||||
path,
|
||||
stat,
|
||||
|
|
@ -300,6 +297,17 @@ const FileEntry: FC<FileEntryProps> = ({
|
|||
url,
|
||||
urlExt,
|
||||
]);
|
||||
const showColumn = useMemo(
|
||||
() => isVisible && columns !== undefined && view === "details",
|
||||
[columns, isVisible, view]
|
||||
);
|
||||
const columnWidth = useMemo(
|
||||
() =>
|
||||
showColumn && columns
|
||||
? columns.name.width - sizes.fileManager.detailsStartPadding
|
||||
: 0,
|
||||
[columns, showColumn, sizes.fileManager.detailsStartPadding]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoadingFileManager && isVisible && !isIconCached.current) {
|
||||
|
|
@ -564,6 +572,11 @@ const FileEntry: FC<FileEntryProps> = ({
|
|||
[listView]
|
||||
)}
|
||||
$renaming={renaming}
|
||||
style={
|
||||
showColumn
|
||||
? { maxWidth: columnWidth, minWidth: columnWidth }
|
||||
: undefined
|
||||
}
|
||||
{...(isHeading && {
|
||||
"aria-level": 1,
|
||||
role: "heading",
|
||||
|
|
@ -605,14 +618,23 @@ const FileEntry: FC<FileEntryProps> = ({
|
|||
)}
|
||||
{listView && openInFileExplorer && <Down flip={showInFileManager} />}
|
||||
</StyledFigure>
|
||||
{showColumn && columns && (
|
||||
<ColumnRow
|
||||
columns={columns}
|
||||
isDirectory={isDirectory}
|
||||
isShortcut={isShortcut}
|
||||
path={path}
|
||||
stats={stats}
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
{showInFileManager && (
|
||||
<FileManager
|
||||
url={url}
|
||||
view="list"
|
||||
hideFolders
|
||||
hideLoading
|
||||
hideShortcutIcons
|
||||
isStartMenu
|
||||
loadIconsImmediately
|
||||
readOnly
|
||||
skipFsWatcher
|
||||
|
|
|
|||
61
components/system/Files/FileManager/Columns/StyledColumns.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import styled from "styled-components";
|
||||
|
||||
const StyledColumns = styled.span`
|
||||
background-color: rgb(32, 32, 32);
|
||||
display: block;
|
||||
margin-right: ${({ theme }) => theme.sizes.fileManager.detailsStartPadding}px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
width: fit-content;
|
||||
z-index: 1;
|
||||
|
||||
ol {
|
||||
display: flex;
|
||||
height: ${({ theme }) => theme.sizes.fileManager.columnHeight};
|
||||
|
||||
li {
|
||||
color: rgb(222, 222, 222);
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
padding-left: 6px;
|
||||
place-items: center;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
|
||||
div {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&::after {
|
||||
border-right: 1px solid rgb(99, 99, 99);
|
||||
content: "";
|
||||
cursor: col-resize;
|
||||
height: 25px;
|
||||
padding-left: ${({ theme }) =>
|
||||
theme.sizes.fileManager.columnResizeWidth}px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgb(67, 67, 67);
|
||||
|
||||
&::after {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: rgb(131, 131, 131);
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
padding-left: 17px;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledColumns;
|
||||
35
components/system/Files/FileManager/Columns/constants.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
type ColumnData = {
|
||||
name: string;
|
||||
width: number;
|
||||
};
|
||||
|
||||
export const MAX_STEPS_PER_RESIZE = 15;
|
||||
export type ColumnName = "date" | "name" | "size" | "type";
|
||||
|
||||
export type Columns = Record<ColumnName, ColumnData>;
|
||||
|
||||
export const DEFAULT_COLUMN_ORDER: ColumnName[] = [
|
||||
"name",
|
||||
"date",
|
||||
"type",
|
||||
"size",
|
||||
];
|
||||
|
||||
export const DEFAULT_COLUMNS: Columns = {
|
||||
date: {
|
||||
name: "Date modified",
|
||||
width: 150,
|
||||
},
|
||||
name: {
|
||||
name: "Name",
|
||||
width: 150,
|
||||
},
|
||||
size: {
|
||||
name: "Size",
|
||||
width: 80,
|
||||
},
|
||||
type: {
|
||||
name: "Type",
|
||||
width: 80,
|
||||
},
|
||||
};
|
||||
105
components/system/Files/FileManager/Columns/index.tsx
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import { memo, useRef } from "react";
|
||||
import { useTheme } from "styled-components";
|
||||
import { sortFiles } from "components/system/Files/FileManager/functions";
|
||||
import { type SortBy } from "components/system/Files/FileManager/useSortBy";
|
||||
import StyledColumns from "components/system/Files/FileManager/Columns/StyledColumns";
|
||||
import {
|
||||
DEFAULT_COLUMN_ORDER,
|
||||
MAX_STEPS_PER_RESIZE,
|
||||
type ColumnName,
|
||||
type Columns as ColumnsObject,
|
||||
} from "components/system/Files/FileManager/Columns/constants";
|
||||
import { useSession } from "contexts/session";
|
||||
import { type Files } from "components/system/Files/FileManager/useFolder";
|
||||
|
||||
type ColumnsProps = {
|
||||
columns: ColumnsObject;
|
||||
directory: string;
|
||||
files: Files;
|
||||
setColumns: React.Dispatch<React.SetStateAction<ColumnsObject | undefined>>;
|
||||
};
|
||||
|
||||
const Columns: FC<ColumnsProps> = ({
|
||||
columns,
|
||||
directory,
|
||||
files,
|
||||
setColumns,
|
||||
}) => {
|
||||
const { sizes } = useTheme();
|
||||
const draggingRef = useRef("");
|
||||
const lastClientX = useRef(0);
|
||||
const { setSortOrder, sortOrders } = useSession();
|
||||
// eslint-disable-next-line unicorn/no-unreadable-array-destructuring
|
||||
const [, , ascending] = sortOrders[directory] ?? [];
|
||||
|
||||
return (
|
||||
<StyledColumns>
|
||||
<ol>
|
||||
{DEFAULT_COLUMN_ORDER.map((name) => (
|
||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions
|
||||
<li
|
||||
key={columns[name].name}
|
||||
onClick={() => {
|
||||
const sortBy = name as SortBy;
|
||||
|
||||
setSortOrder(
|
||||
directory,
|
||||
Object.keys(sortFiles(directory, files, sortBy, !ascending)),
|
||||
sortBy,
|
||||
!ascending
|
||||
);
|
||||
}}
|
||||
onPointerDownCapture={(event) => {
|
||||
const widthToEdge =
|
||||
(event.target as HTMLElement).clientWidth -
|
||||
event.nativeEvent.offsetX;
|
||||
const startDragging =
|
||||
widthToEdge <= sizes.fileManager.columnResizeWidth &&
|
||||
widthToEdge >= 0;
|
||||
|
||||
draggingRef.current = startDragging ? name : "";
|
||||
lastClientX.current = event.clientX;
|
||||
}}
|
||||
onPointerMoveCapture={(event) => {
|
||||
if (draggingRef.current) {
|
||||
const dragName = draggingRef.current as ColumnName;
|
||||
|
||||
setColumns((currentColumns) => {
|
||||
if (!currentColumns?.[dragName]) return currentColumns;
|
||||
|
||||
const newColumns = { ...currentColumns };
|
||||
const newSize =
|
||||
newColumns[dragName].width +
|
||||
event.clientX -
|
||||
lastClientX.current;
|
||||
|
||||
if (
|
||||
newSize < sizes.fileManager.columnMinWidth ||
|
||||
Math.abs(lastClientX.current - event.clientX) >
|
||||
MAX_STEPS_PER_RESIZE
|
||||
) {
|
||||
return newColumns;
|
||||
}
|
||||
|
||||
newColumns[dragName].width = newSize;
|
||||
lastClientX.current = event.clientX;
|
||||
|
||||
return newColumns;
|
||||
});
|
||||
}
|
||||
}}
|
||||
onPointerUpCapture={() => {
|
||||
draggingRef.current = "";
|
||||
lastClientX.current = 0;
|
||||
}}
|
||||
style={{ width: `${columns[name].width}px` }}
|
||||
>
|
||||
<div>{columns[name].name}</div>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</StyledColumns>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Columns);
|
||||
|
|
@ -6,7 +6,7 @@ const NoGlobalPointerEvents = createGlobalStyle`
|
|||
}
|
||||
`;
|
||||
|
||||
const StyledSelectionComponent = styled.span`
|
||||
export const StyledSelectionComponent = styled.span`
|
||||
background-color: ${({ theme }) => theme.colors.selectionHighlightBackground};
|
||||
border: ${({ theme }) => `1px solid ${theme.colors.selectionHighlight}`};
|
||||
position: absolute;
|
||||
|
|
|
|||
|
|
@ -6,21 +6,26 @@ import {
|
|||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useTheme } from "styled-components";
|
||||
import { type FileManagerViewNames } from "components/system/Files/Views";
|
||||
import StyledStatusBar from "components/system/Files/FileManager/StyledStatusBar";
|
||||
import { type FileDrop } from "components/system/Files/FileManager/useFileDrop";
|
||||
import { useFileSystem } from "contexts/fileSystem";
|
||||
import useResizeObserver from "hooks/useResizeObserver";
|
||||
import { getFormattedSize, haltEvent, label } from "utils/functions";
|
||||
import { UNKNOWN_SIZE } from "contexts/fileSystem/core";
|
||||
import Icon from "styles/common/Icon";
|
||||
import Button from "styles/common/Button";
|
||||
|
||||
type StatusBarProps = {
|
||||
count: number;
|
||||
directory: string;
|
||||
fileDrop: FileDrop;
|
||||
selected: string[];
|
||||
setView: (view: FileManagerViewNames) => void;
|
||||
view: FileManagerViewNames;
|
||||
};
|
||||
|
||||
const MINIMUM_STATUSBAR_WIDTH = 225;
|
||||
const UNCALCULATED_SIZE = -2;
|
||||
|
||||
const StatusBar: FC<StatusBarProps> = ({
|
||||
|
|
@ -28,12 +33,18 @@ const StatusBar: FC<StatusBarProps> = ({
|
|||
directory,
|
||||
fileDrop,
|
||||
selected,
|
||||
setView,
|
||||
view,
|
||||
}) => {
|
||||
const { exists, lstat, stat } = useFileSystem();
|
||||
const [selectedSize, setSelectedSize] = useState(UNKNOWN_SIZE);
|
||||
const [showSelected, setShowSelected] = useState(false);
|
||||
const updateShowSelected = (width: number): void =>
|
||||
setShowSelected(width > MINIMUM_STATUSBAR_WIDTH);
|
||||
const { sizes } = useTheme();
|
||||
const updateShowSelected = useCallback(
|
||||
(width: number): void =>
|
||||
setShowSelected(width > sizes.fileExplorer.minimumStatusBarWidth),
|
||||
[sizes.fileExplorer.minimumStatusBarWidth]
|
||||
);
|
||||
const statusBarRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -68,13 +79,13 @@ const StatusBar: FC<StatusBarProps> = ({
|
|||
if (statusBarRef.current) {
|
||||
updateShowSelected(statusBarRef.current.getBoundingClientRect().width);
|
||||
}
|
||||
}, []);
|
||||
}, [updateShowSelected]);
|
||||
|
||||
useResizeObserver(
|
||||
statusBarRef.current,
|
||||
useCallback<ResizeObserverCallback>(
|
||||
([{ contentRect: { width = 0 } = {} }]) => updateShowSelected(width),
|
||||
[]
|
||||
[updateShowSelected]
|
||||
)
|
||||
);
|
||||
|
||||
|
|
@ -95,6 +106,30 @@ const StatusBar: FC<StatusBarProps> = ({
|
|||
: ""}
|
||||
</div>
|
||||
)}
|
||||
<nav className="views">
|
||||
<Button
|
||||
className={view === "details" ? "active" : undefined}
|
||||
onClick={() => setView("details")}
|
||||
{...label("Displays information about each item in the window.")}
|
||||
>
|
||||
<Icon
|
||||
displaySize={16}
|
||||
imgSize={16}
|
||||
src="/System/Icons/details_view.webp"
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
className={view === "icon" ? "active" : undefined}
|
||||
onClick={() => setView("icon")}
|
||||
{...label("Display items by using thumbnails.")}
|
||||
>
|
||||
<Icon
|
||||
displaySize={16}
|
||||
imgSize={16}
|
||||
src="/System/Icons/icon_view.webp"
|
||||
/>
|
||||
</Button>
|
||||
</nav>
|
||||
</StyledStatusBar>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const StyledStatusBar = styled.footer`
|
|||
font-size: 12px;
|
||||
font-weight: 200;
|
||||
height: ${({ theme }) => theme.sizes.fileExplorer.statusBarHeight};
|
||||
padding: 0 5px;
|
||||
padding: 0 4px 0 5px;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
|
|
@ -35,6 +35,36 @@ const StyledStatusBar = styled.footer`
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
|
||||
button {
|
||||
border: 1px solid transparent;
|
||||
display: flex;
|
||||
height: ${({ theme }) => theme.sizes.fileExplorer.statusBarHeight};
|
||||
place-content: center;
|
||||
place-items: center;
|
||||
width: 22px;
|
||||
|
||||
&:hover {
|
||||
background-color: rgb(77, 77, 77);
|
||||
border: 1px solid rgb(99, 99, 99);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: rgb(102, 102, 102);
|
||||
border: 1px solid rgb(131, 131, 131);
|
||||
|
||||
picture {
|
||||
padding-left: 1px;
|
||||
padding-top: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledStatusBar;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import { basename, join } from "path";
|
||||
import { memo, useEffect, useMemo, useRef, useState } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import {
|
||||
DEFAULT_COLUMNS,
|
||||
type Columns as ColumnsObject,
|
||||
} from "components/system/Files/FileManager/Columns/constants";
|
||||
import FileEntry from "components/system/Files/FileEntry";
|
||||
import StyledSelection from "components/system/Files/FileManager/Selection/StyledSelection";
|
||||
import useSelection from "components/system/Files/FileManager/Selection/useSelection";
|
||||
|
|
@ -10,10 +14,7 @@ import useFileKeyboardShortcuts from "components/system/Files/FileManager/useFil
|
|||
import useFocusableEntries from "components/system/Files/FileManager/useFocusableEntries";
|
||||
import useFolder from "components/system/Files/FileManager/useFolder";
|
||||
import useFolderContextMenu from "components/system/Files/FileManager/useFolderContextMenu";
|
||||
import {
|
||||
type FileManagerViewNames,
|
||||
FileManagerViews,
|
||||
} from "components/system/Files/Views";
|
||||
import { FileManagerViews } from "components/system/Files/Views";
|
||||
import { useFileSystem } from "contexts/fileSystem";
|
||||
import {
|
||||
FOCUSABLE_ELEMENT,
|
||||
|
|
@ -22,6 +23,8 @@ import {
|
|||
SHORTCUT_EXTENSION,
|
||||
} from "utils/constants";
|
||||
import { getExtension, haltEvent } from "utils/functions";
|
||||
import Columns from "components/system/Files/FileManager/Columns";
|
||||
import { useSession } from "contexts/session";
|
||||
|
||||
const StatusBar = dynamic(
|
||||
() => import("components/system/Files/FileManager/StatusBar")
|
||||
|
|
@ -50,9 +53,10 @@ type FileManagerProps = {
|
|||
skipFsWatcher?: boolean;
|
||||
skipSorting?: boolean;
|
||||
url: string;
|
||||
view: FileManagerViewNames;
|
||||
};
|
||||
|
||||
const DEFAULT_VIEW = "icon";
|
||||
|
||||
const FileManager: FC<FileManagerProps> = ({
|
||||
allowMovingDraggableEntries,
|
||||
hideFolders,
|
||||
|
|
@ -68,8 +72,18 @@ const FileManager: FC<FileManagerProps> = ({
|
|||
skipFsWatcher,
|
||||
skipSorting,
|
||||
url,
|
||||
view,
|
||||
}) => {
|
||||
const { views, setViews } = useSession();
|
||||
const view = useMemo(() => {
|
||||
if (isDesktop) return "icon";
|
||||
if (isStartMenu) return "list";
|
||||
|
||||
return views[url] || DEFAULT_VIEW;
|
||||
}, [isDesktop, isStartMenu, url, views]);
|
||||
const isDetailsView = useMemo(() => view === "details", [view]);
|
||||
const [columns, setColumns] = useState<ColumnsObject | undefined>(() =>
|
||||
isDetailsView ? DEFAULT_COLUMNS : undefined
|
||||
);
|
||||
const [currentUrl, setCurrentUrl] = useState(url);
|
||||
const [renaming, setRenaming] = useState("");
|
||||
const [mounted, setMounted] = useState<boolean>(false);
|
||||
|
|
@ -129,7 +143,7 @@ const FileManager: FC<FileManagerProps> = ({
|
|||
!isDesktop &&
|
||||
!isStartMenu &&
|
||||
!loading &&
|
||||
view === "icon" &&
|
||||
view !== "list" &&
|
||||
fileKeys.length === 0;
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -198,6 +212,10 @@ const FileManager: FC<FileManagerProps> = ({
|
|||
}
|
||||
}, [isDesktop, isStartMenu, loading]);
|
||||
|
||||
useEffect(() => {
|
||||
setColumns(isDetailsView ? DEFAULT_COLUMNS : undefined);
|
||||
}, [isDetailsView]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading ? (
|
||||
|
|
@ -220,6 +238,14 @@ const FileManager: FC<FileManagerProps> = ({
|
|||
})}
|
||||
{...FOCUSABLE_ELEMENT}
|
||||
>
|
||||
{isDetailsView && columns && (
|
||||
<Columns
|
||||
columns={columns}
|
||||
directory={url}
|
||||
files={files}
|
||||
setColumns={setColumns}
|
||||
/>
|
||||
)}
|
||||
{isSelecting && <StyledSelection style={selectionStyling} />}
|
||||
{fileKeys.map((file) => (
|
||||
<StyledFileEntry
|
||||
|
|
@ -232,6 +258,7 @@ const FileManager: FC<FileManagerProps> = ({
|
|||
{...focusableEntry(file)}
|
||||
>
|
||||
<FileEntry
|
||||
columns={columns}
|
||||
fileActions={fileActions}
|
||||
fileManagerId={id}
|
||||
fileManagerRef={fileManagerRef}
|
||||
|
|
@ -263,6 +290,11 @@ const FileManager: FC<FileManagerProps> = ({
|
|||
directory={url}
|
||||
fileDrop={fileDrop}
|
||||
selected={focusedEntries}
|
||||
setView={(newView) => {
|
||||
setViews((currentViews) => ({ ...currentViews, [url]: newView }));
|
||||
setColumns(newView === "details" ? DEFAULT_COLUMNS : undefined);
|
||||
}}
|
||||
view={view}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
|||
50
components/system/Files/Views/Details/StyledFileEntry.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import styled from "styled-components";
|
||||
import { type StyledFileEntryProps } from "components/system/Files/Views";
|
||||
|
||||
const StyledFileEntry = styled.li<StyledFileEntryProps>`
|
||||
margin-left: ${({ theme }) => theme.sizes.fileManager.detailsStartPadding}px;
|
||||
width: fit-content;
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
padding-left: 4px;
|
||||
text-align: left;
|
||||
|
||||
figure {
|
||||
bottom: 1px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 22px;
|
||||
place-items: center;
|
||||
position: relative;
|
||||
|
||||
figcaption {
|
||||
color: ${({ theme }) => theme.colors.fileEntry.text};
|
||||
font-size: ${({ theme }) => theme.sizes.fileEntry.fontSize};
|
||||
overflow: hidden;
|
||||
padding-left: 4px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.colors.fileEntry.background};
|
||||
}
|
||||
|
||||
&.focus-within {
|
||||
background-color: ${({ theme }) =>
|
||||
theme.colors.fileEntry.backgroundFocused};
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme, $selecting }) =>
|
||||
$selecting
|
||||
? theme.colors.fileEntry.backgroundFocused
|
||||
: theme.colors.fileEntry.backgroundFocusedHover};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledFileEntry;
|
||||
24
components/system/Files/Views/Details/StyledFileManager.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import styled from "styled-components";
|
||||
import { StyledSelectionComponent } from "components/system/Files/FileManager/Selection/StyledSelection";
|
||||
import { type StyledFileManagerProps } from "components/system/Files/Views";
|
||||
import ScrollBars from "styles/common/ScrollBars";
|
||||
|
||||
const StyledFileManager = styled.ol<StyledFileManagerProps>`
|
||||
${({ $scrollable }) => ($scrollable ? ScrollBars() : undefined)};
|
||||
|
||||
contain: strict;
|
||||
overflow: ${({ $isEmptyFolder, $scrollable }) =>
|
||||
!$isEmptyFolder && $scrollable ? undefined : "hidden"};
|
||||
pointer-events: ${({ $selecting }) => ($selecting ? "auto" : undefined)};
|
||||
scrollbar-gutter: auto;
|
||||
|
||||
picture:not(:first-of-type) {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
${StyledSelectionComponent} {
|
||||
top: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledFileManager;
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import StyledDetailsFileEntry from "components/system/Files/Views/Details/StyledFileEntry";
|
||||
import StyledDetailsFileManager from "components/system/Files/Views/Details/StyledFileManager";
|
||||
import StyledIconFileEntry from "components/system/Files/Views/Icon/StyledFileEntry";
|
||||
import StyledIconFileManager from "components/system/Files/Views/Icon/StyledFileManager";
|
||||
import StyledListFileEntry from "components/system/Files/Views/List/StyledFileEntry";
|
||||
|
|
@ -18,14 +20,21 @@ export type StyledFileManagerProps = {
|
|||
|
||||
type FileManagerView = {
|
||||
StyledFileEntry: typeof StyledIconFileEntry | typeof StyledListFileEntry;
|
||||
/* eslint-disable @typescript-eslint/no-duplicate-type-constituents */
|
||||
StyledFileManager:
|
||||
| typeof StyledDetailsFileManager
|
||||
| typeof StyledIconFileManager
|
||||
| typeof StyledListFileManager;
|
||||
/* eslint-enable @typescript-eslint/no-duplicate-type-constituents */
|
||||
};
|
||||
|
||||
export type FileManagerViewNames = "icon" | "list";
|
||||
export type FileManagerViewNames = "details" | "icon" | "list";
|
||||
|
||||
export const FileManagerViews: Record<FileManagerViewNames, FileManagerView> = {
|
||||
details: {
|
||||
StyledFileEntry: StyledDetailsFileEntry,
|
||||
StyledFileManager: StyledDetailsFileManager,
|
||||
},
|
||||
icon: {
|
||||
StyledFileEntry: StyledIconFileEntry,
|
||||
StyledFileManager: StyledIconFileManager,
|
||||
|
|
@ -37,9 +46,16 @@ export const FileManagerViews: Record<FileManagerViewNames, FileManagerView> = {
|
|||
};
|
||||
|
||||
export const FileEntryIconSize: Record<
|
||||
FileManagerViewNames | "sub",
|
||||
FileManagerViewNames | "detailsSub" | "sub",
|
||||
IconProps
|
||||
> = {
|
||||
details: {
|
||||
imgSize: 16,
|
||||
},
|
||||
detailsSub: {
|
||||
displaySize: 8,
|
||||
imgSize: 16,
|
||||
},
|
||||
icon: {
|
||||
imgSize: 48,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -110,7 +110,6 @@ const StartMenu: FC<StartMenuProps> = ({ toggleStartMenu }) => {
|
|||
<Sidebar height={height} />
|
||||
<FileManager
|
||||
url={START_MENU_PATH}
|
||||
view="list"
|
||||
hideLoading
|
||||
hideShortcutIcons
|
||||
isStartMenu
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { join } from "path";
|
||||
import { type FSModule } from "browserfs/dist/node/core/FS";
|
||||
import type Stats from "browserfs/dist/node/core/node_fs_stats";
|
||||
import extensions from "components/system/Files/FileEntry/extensions";
|
||||
import {
|
||||
getCachedIconUrl,
|
||||
getFileType,
|
||||
getInfoWithExtension,
|
||||
getInfoWithoutExtension,
|
||||
} from "components/system/Files/FileEntry/functions";
|
||||
|
|
@ -121,5 +121,4 @@ export const fileType = (
|
|||
? "YouTube Video"
|
||||
: stats?.isDirectory() || !extension
|
||||
? "File folder"
|
||||
: extensions[extension]?.type ||
|
||||
`${extension.toUpperCase().replace(".", "")} File`;
|
||||
: getFileType(extension);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { type Position } from "react-rnd";
|
|||
import { type SortBy } from "components/system/Files/FileManager/useSortBy";
|
||||
import { type Size } from "components/system/Window/RndWindow/useResizable";
|
||||
import { type ThemeName } from "styles/themes";
|
||||
import { type FileManagerViewNames } from "components/system/Files/Views";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
|
@ -25,6 +26,8 @@ type SortOrder = [string[], SortBy?, boolean?];
|
|||
|
||||
export type SortOrders = Record<string, SortOrder>;
|
||||
|
||||
export type Views = Record<string, FileManagerViewNames>;
|
||||
|
||||
export type ClockSource = "local" | "ntp";
|
||||
|
||||
export type RecentFiles = [string, string, string][];
|
||||
|
|
@ -45,6 +48,7 @@ export type SessionData = {
|
|||
runHistory: string[];
|
||||
sortOrders: SortOrders;
|
||||
themeName: ThemeName;
|
||||
views: Views;
|
||||
wallpaperFit: WallpaperFit;
|
||||
wallpaperImage: string;
|
||||
windowStates: WindowStates;
|
||||
|
|
@ -69,6 +73,7 @@ export type SessionContextState = SessionData & {
|
|||
ascending?: boolean
|
||||
) => void;
|
||||
setThemeName: React.Dispatch<React.SetStateAction<ThemeName>>;
|
||||
setViews: React.Dispatch<React.SetStateAction<Views>>;
|
||||
setWallpaper: (image: string, fit?: WallpaperFit) => void;
|
||||
setWindowStates: React.Dispatch<React.SetStateAction<WindowStates>>;
|
||||
stackOrder: string[];
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { type ApiError } from "browserfs/dist/node/core/api_error";
|
|||
import { type SortBy } from "components/system/Files/FileManager/useSortBy";
|
||||
import { useFileSystem } from "contexts/fileSystem";
|
||||
import {
|
||||
type Views,
|
||||
type IconPositions,
|
||||
type RecentFiles,
|
||||
type SessionContextState,
|
||||
|
|
@ -52,6 +53,7 @@ const useSessionContextState = (): SessionContextState => {
|
|||
const [sortOrders, setSortOrders] = useState(
|
||||
Object.create(null) as SortOrders
|
||||
);
|
||||
const [views, setViews] = useState(Object.create(null) as Views);
|
||||
const [iconPositions, setIconPositions] = useState(
|
||||
Object.create(null) as IconPositions
|
||||
);
|
||||
|
|
@ -195,6 +197,7 @@ const useSessionContextState = (): SessionContextState => {
|
|||
runHistory,
|
||||
sortOrders,
|
||||
themeName,
|
||||
views,
|
||||
wallpaperFit,
|
||||
wallpaperImage,
|
||||
windowStates,
|
||||
|
|
@ -223,6 +226,7 @@ const useSessionContextState = (): SessionContextState => {
|
|||
sessionLoaded,
|
||||
sortOrders,
|
||||
themeName,
|
||||
views,
|
||||
wallpaperFit,
|
||||
wallpaperImage,
|
||||
windowStates,
|
||||
|
|
@ -261,6 +265,9 @@ const useSessionContextState = (): SessionContextState => {
|
|||
) {
|
||||
setSortOrders(session.sortOrders);
|
||||
}
|
||||
if (session.views && Object.keys(session.views).length > 0) {
|
||||
setViews(session.views);
|
||||
}
|
||||
if (
|
||||
session.iconPositions &&
|
||||
Object.keys(session.iconPositions).length > 0
|
||||
|
|
@ -361,12 +368,14 @@ const useSessionContextState = (): SessionContextState => {
|
|||
setRunHistory,
|
||||
setSortOrder,
|
||||
setThemeName,
|
||||
setViews,
|
||||
setWallpaper,
|
||||
setWindowStates,
|
||||
sortOrders,
|
||||
stackOrder,
|
||||
themeName,
|
||||
updateRecentFiles,
|
||||
views,
|
||||
wallpaperFit,
|
||||
wallpaperImage,
|
||||
windowStates,
|
||||
|
|
|
|||
BIN
public/System/Icons/16x16/details_view.png
Normal file
|
After Width: | Height: | Size: 142 B |
BIN
public/System/Icons/16x16/details_view.webp
Normal file
|
After Width: | Height: | Size: 94 B |
BIN
public/System/Icons/16x16/icon_view.png
Normal file
|
After Width: | Height: | Size: 340 B |
BIN
public/System/Icons/16x16/icon_view.webp
Normal file
|
After Width: | Height: | Size: 236 B |
BIN
public/System/Icons/32x32/details_view.png
Normal file
|
After Width: | Height: | Size: 216 B |
BIN
public/System/Icons/32x32/details_view.webp
Normal file
|
After Width: | Height: | Size: 176 B |
BIN
public/System/Icons/32x32/icon_view.png
Normal file
|
After Width: | Height: | Size: 943 B |
BIN
public/System/Icons/32x32/icon_view.webp
Normal file
|
After Width: | Height: | Size: 602 B |
BIN
public/System/Icons/48x48/details_view.png
Normal file
|
After Width: | Height: | Size: 427 B |
BIN
public/System/Icons/48x48/details_view.webp
Normal file
|
After Width: | Height: | Size: 352 B |
BIN
public/System/Icons/48x48/icon_view.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/System/Icons/48x48/icon_view.webp
Normal file
|
After Width: | Height: | Size: 1016 B |
|
|
@ -18,12 +18,17 @@ const sizes = {
|
|||
renameWidth: 75,
|
||||
},
|
||||
fileExplorer: {
|
||||
minimumStatusBarWidth: 247,
|
||||
navBarHeight: "35px",
|
||||
navInputHeight: 22,
|
||||
statusBarHeight: "23px",
|
||||
},
|
||||
fileManager: {
|
||||
columnGap: "1px",
|
||||
columnHeight: "25px",
|
||||
columnMinWidth: 80,
|
||||
columnResizeWidth: 6,
|
||||
detailsStartPadding: 14,
|
||||
gridEntryHeight: "70px",
|
||||
gridEntryWidth: "74px",
|
||||
padding: "5px 0",
|
||||
|
|
|
|||
|
|
@ -614,11 +614,22 @@ const bytesInMB = 1022976; // 1024 * 999
|
|||
const bytesInGB = 1047527424; // 1024 * 1024 * 999
|
||||
const bytesInTB = 1072668082176; // 1024 * 1024 * 1024 * 999
|
||||
|
||||
const formatNumber = (number: number): string => {
|
||||
const formattedNumber = new Intl.NumberFormat("en-US", {
|
||||
maximumSignificantDigits: number < 1 ? 2 : 4,
|
||||
minimumSignificantDigits: number < 1 ? 2 : 3,
|
||||
}).format(Number(number.toFixed(4).slice(0, -2)));
|
||||
const formatNumber = (number: number, roundUpNumber = false): string => {
|
||||
const formattedNumber = new Intl.NumberFormat(
|
||||
"en-US",
|
||||
roundUpNumber
|
||||
? undefined
|
||||
: {
|
||||
maximumSignificantDigits: number < 1 ? 2 : 4,
|
||||
minimumSignificantDigits: number < 1 ? 2 : 3,
|
||||
}
|
||||
).format(
|
||||
roundUpNumber
|
||||
? Math.ceil(Number(number))
|
||||
: Number(number.toFixed(4).slice(0, -2))
|
||||
);
|
||||
|
||||
if (roundUpNumber) return formattedNumber;
|
||||
|
||||
const [integer, decimal] = formattedNumber.split(".");
|
||||
|
||||
|
|
@ -630,7 +641,14 @@ const formatNumber = (number: number): string => {
|
|||
return formattedNumber;
|
||||
};
|
||||
|
||||
export const getFormattedSize = (size = 0): string => {
|
||||
export const getFormattedSize = (size = 0, asKB = false): string => {
|
||||
if (asKB) {
|
||||
if (size === 0) return "0 KB";
|
||||
if (size <= bytesInKB) return "1 KB";
|
||||
|
||||
return `${formatNumber(size / bytesInKB, true)} KB`;
|
||||
}
|
||||
|
||||
if (size === 1) return "1 byte";
|
||||
if (size < bytesInKB) return `${size} bytes`;
|
||||
if (size < bytesInMB) return `${formatNumber(size / bytesInKB)} KB`;
|
||||
|
|
|
|||