Prepare v0.1.0 release
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import type { CSSProperties, DragEvent as ReactDragEvent, MouseEvent as ReactMouseEvent, ReactNode } from "react";
|
||||
import { Copy, Download, Folder, FolderOpen, Home, MoveRight, Plus, Trash2, UploadCloud } from "lucide-react";
|
||||
import { Button, Dialog } from "@govoplan/core-webui";
|
||||
import { Copy, Download, FolderOpen, Home, MoveRight, Plus, Trash2, UploadCloud } from "lucide-react";
|
||||
import { Button, Dialog, ExplorerTree, type ExplorerTreeNodeContext } from "@govoplan/core-webui";
|
||||
import type { ConflictAction, FileSpace, RenameResponse } from "../../../api/files";
|
||||
import type { ConflictDialogState, FileActionTarget, FileConflictItem, FolderNode, ContextMenuState } from "../types";
|
||||
import { isPathUnderOrSame, normalizeFolder, treeNodeKey } from "../utils/fileManagerUtils";
|
||||
@@ -111,80 +111,35 @@ export function FolderTree({
|
||||
disabled?: boolean;
|
||||
depth?: number;
|
||||
}) {
|
||||
if (nodes.length === 0) return null;
|
||||
return (
|
||||
<div className="file-tree-children">
|
||||
{nodes.map((node) => {
|
||||
const isActive = activeSpaceId === spaceId && currentFolder === node.path;
|
||||
<ExplorerTree
|
||||
nodes={nodes}
|
||||
getNodeId={(node) => treeNodeKey(spaceId, node.path)}
|
||||
getNodeLabel={(node) => node.name}
|
||||
getNodeChildren={(node) => node.children}
|
||||
activeId={activeSpaceId === spaceId ? treeNodeKey(spaceId, currentFolder) : ""}
|
||||
expandedIds={expandedKeys}
|
||||
onOpen={(node) => onOpen(spaceId, node.path)}
|
||||
onToggle={(node) => onToggle(spaceId, node.path)}
|
||||
disabled={disabled}
|
||||
depth={depth}
|
||||
getNodeWrapClassName={(node) => {
|
||||
const target = { spaceId, folderPath: node.path };
|
||||
const isDropTarget = dragDropEnabled && dropTargetKey === `${target.spaceId}:${normalizeFolder(target.folderPath)}`;
|
||||
const hasChildren = node.children.length > 0;
|
||||
const isExpanded = expandedKeys.has(treeNodeKey(spaceId, node.path));
|
||||
return (
|
||||
<div key={node.path}>
|
||||
<div
|
||||
className={`file-tree-node-wrap ${isActive ? "is-active" : ""} ${isDropTarget ? "is-drop-target" : ""}`}
|
||||
style={{ paddingLeft: `${Math.min(depth * 14, 56)}px` }}
|
||||
draggable={dragDropEnabled && !disabled}
|
||||
onDragStart={dragDropEnabled && onDragStartFolder ? (event) => onDragStartFolder(spaceId, node.path, event) : undefined}
|
||||
onDragEnd={dragDropEnabled ? onDragEndFolder : undefined}
|
||||
onContextMenu={contextMenuEnabled && onContextMenu ? (event) => onContextMenu(event, spaceId, node.path) : undefined}
|
||||
onDragOver={dragDropEnabled && onDragOverTarget ? (event) => {
|
||||
onDragOverTarget(event, target);
|
||||
if (hasChildren && !isExpanded) onRequestDragExpand?.(spaceId, node.path);
|
||||
} : undefined}
|
||||
onDragLeave={dragDropEnabled ? onClearDropState : undefined}
|
||||
onDrop={dragDropEnabled && onDropOnTarget ? (event) => void onDropOnTarget(event, target) : undefined}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="file-tree-toggle"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
if (hasChildren) onToggle(spaceId, node.path);
|
||||
}}
|
||||
disabled={disabled || !hasChildren}
|
||||
aria-label={`${isExpanded ? "Collapse" : "Expand"} ${node.name}`}
|
||||
aria-expanded={hasChildren ? isExpanded : undefined}
|
||||
>
|
||||
{isExpanded ? <FolderOpen size={15} aria-hidden="true" /> : <Folder size={15} aria-hidden="true" />}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="file-tree-node"
|
||||
onClick={() => onOpen(spaceId, node.path)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<span>{node.name}</span>
|
||||
</button>
|
||||
</div>
|
||||
{hasChildren && isExpanded && (
|
||||
<FolderTree
|
||||
nodes={node.children}
|
||||
activeSpaceId={activeSpaceId}
|
||||
spaceId={spaceId}
|
||||
currentFolder={currentFolder}
|
||||
dropTargetKey={dropTargetKey}
|
||||
expandedKeys={expandedKeys}
|
||||
onOpen={onOpen}
|
||||
onToggle={onToggle}
|
||||
onContextMenu={onContextMenu}
|
||||
onDragOverTarget={onDragOverTarget}
|
||||
onDropOnTarget={onDropOnTarget}
|
||||
onClearDropState={onClearDropState}
|
||||
onRequestDragExpand={onRequestDragExpand}
|
||||
onDragStartFolder={onDragStartFolder}
|
||||
onDragEndFolder={onDragEndFolder}
|
||||
dragDropEnabled={dragDropEnabled}
|
||||
contextMenuEnabled={contextMenuEnabled}
|
||||
disabled={disabled}
|
||||
depth={depth + 1}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
return dragDropEnabled && dropTargetKey === `${target.spaceId}:${normalizeFolder(target.folderPath)}` ? "is-drop-target" : undefined;
|
||||
}}
|
||||
getNodeWrapStyle={(_node, context: ExplorerTreeNodeContext) => ({ paddingLeft: `${Math.min(context.depth * 14, 56)}px` })}
|
||||
getNodeDraggable={() => dragDropEnabled && !disabled}
|
||||
onContextMenu={contextMenuEnabled && onContextMenu ? (event, node) => onContextMenu(event, spaceId, node.path) : undefined}
|
||||
onDragStart={dragDropEnabled && onDragStartFolder ? (event, node) => onDragStartFolder(spaceId, node.path, event) : undefined}
|
||||
onDragEnd={dragDropEnabled && onDragEndFolder ? () => onDragEndFolder() : undefined}
|
||||
onDragOver={dragDropEnabled && onDragOverTarget ? (event, node, context) => {
|
||||
const target = { spaceId, folderPath: node.path };
|
||||
onDragOverTarget(event, target);
|
||||
if (context.hasChildren && !context.expanded) onRequestDragExpand?.(spaceId, node.path);
|
||||
} : undefined}
|
||||
onDragLeave={dragDropEnabled && onClearDropState ? () => onClearDropState() : undefined}
|
||||
onDrop={dragDropEnabled && onDropOnTarget ? (event, node) => void onDropOnTarget(event, { spaceId, folderPath: node.path }) : undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { formatDateTime as formatPlatformDateTime } from "@govoplan/core-webui";
|
||||
import type { FileFolder, ManagedFile } from "../../../api/files";
|
||||
import type { DragSelectionState, EntrySelectionKey, ExplorerEntry, FileEntry, FolderEntry, FolderNode, SortColumn, SortDirection } from "../types";
|
||||
|
||||
@@ -297,7 +298,5 @@ export function formatBytes(value: number): string {
|
||||
}
|
||||
|
||||
export function formatDate(value: string): string {
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return value;
|
||||
return date.toLocaleString();
|
||||
return formatPlatformDateTime(value);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Folder } from "lucide-react";
|
||||
import { createElement, lazy } from "react";
|
||||
import type { PlatformWebModule } from "@govoplan/core-webui";
|
||||
const FilesPage = lazy(() => import("./features/files/FilesPage"));
|
||||
@@ -10,7 +9,7 @@ export const filesModule: PlatformWebModule = {
|
||||
label: "Files",
|
||||
version: "1.0.0",
|
||||
dependencies: ["access"],
|
||||
navItems: [{ to: "/files", label: "Files", icon: Folder, anyOf: fileRead, order: 40 }],
|
||||
navItems: [{ to: "/files", label: "Files", iconName: "folder", anyOf: fileRead, order: 40 }],
|
||||
routes: [
|
||||
{ path: "/files", anyOf: fileRead, order: 40, render: ({ settings, auth }) => createElement(FilesPage, { settings, auth }) }
|
||||
]
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: 10px 8px 16px;
|
||||
padding: 0px 0px 16px;
|
||||
}
|
||||
|
||||
.file-tree-space + .file-tree-space {
|
||||
@@ -73,7 +73,7 @@
|
||||
grid-template-columns: 26px minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
border-radius: 9px;
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
.file-tree-node,
|
||||
@@ -90,7 +90,7 @@
|
||||
place-items: center;
|
||||
width: 26px;
|
||||
height: 32px;
|
||||
border-radius: 9px;
|
||||
border-radius: 0px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
padding: 8px 9px;
|
||||
border-radius: 9px;
|
||||
border-radius: 0px;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
user-select: none;
|
||||
@@ -137,7 +137,7 @@
|
||||
.file-tree-root:hover:not(:disabled),
|
||||
.file-tree-root:focus-visible,
|
||||
.file-tree-root.is-active {
|
||||
background: rgba(13, 110, 253, .08);
|
||||
background: var(--line);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -149,8 +149,8 @@
|
||||
|
||||
.file-tree-node-wrap.is-drop-target,
|
||||
.file-tree-root.is-drop-target {
|
||||
background: rgba(13, 110, 253, .14);
|
||||
box-shadow: inset 0 0 0 2px rgba(13, 110, 253, .28);
|
||||
background: var(--line);
|
||||
box-shadow: inset 0 0 0 2px var(--line-dark);
|
||||
}
|
||||
|
||||
.file-tree-node-wrap.is-selected .file-tree-select {
|
||||
@@ -240,8 +240,8 @@
|
||||
|
||||
.file-list-drop-target.is-active,
|
||||
.file-list-drop-target.is-drop-target {
|
||||
background: rgba(13, 110, 253, .05);
|
||||
box-shadow: inset 0 0 0 2px rgba(13, 110, 253, .22);
|
||||
background: var(--line);
|
||||
box-shadow: inset 0 0 0 2px var(--line-dark);
|
||||
}
|
||||
|
||||
.file-list-table {
|
||||
@@ -295,18 +295,18 @@
|
||||
}
|
||||
|
||||
.file-list-row:focus-visible {
|
||||
outline: 2px solid rgba(13, 110, 253, .35);
|
||||
outline: 2px solid var(--line-dark);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.file-list-row:hover,
|
||||
.file-list-row.is-selected {
|
||||
background: rgba(13, 110, 253, .07);
|
||||
background: var(--line);
|
||||
}
|
||||
|
||||
.file-list-row.is-drop-target {
|
||||
background: rgba(13, 110, 253, .13);
|
||||
box-shadow: inset 0 0 0 2px rgba(13, 110, 253, .28);
|
||||
background: var(--line);
|
||||
box-shadow: inset 0 0 0 2px var(--line-dark);
|
||||
}
|
||||
|
||||
.file-parent-row.is-disabled {
|
||||
|
||||
Reference in New Issue
Block a user