Feat: FileManager (#98) (#703)

* feat: add file manager module

- Complete file manager implementation with UI/UX
- Add drive management functionality
- Add file upload/download with progress tracking
- Add stamp integration and handling
- Add bulk operations and context menus

Co-authored-by: Roland Seres <roland.seres90@gmail.com>
Co-authored-by: nidishk <nidishkrishnan45@gmail.com>
This commit is contained in:
Bálint Ujvári
2025-11-12 11:26:00 +01:00
committed by GitHub
parent 1249c0df71
commit 5bfe2a0331
107 changed files with 21529 additions and 5578 deletions
@@ -0,0 +1,88 @@
.fm-modal-window.fm-get-info-modal {
display: flex;
flex-direction: column;
max-height: clamp(320px, calc(100vh - 96px), 90vh);
}
.fm-modal-window.fm-get-info-modal .fm-modal-window-header,
.fm-modal-window.fm-get-info-modal .fm-modal-window-footer {
flex: 0 0 auto;
}
.fm-modal-window.fm-get-info-modal .fm-modal-window-body,
.fm-get-info-body {
flex: 1 1 auto;
min-height: 0;
overflow: auto;
-webkit-overflow-scrolling: touch;
overscroll-behavior: contain;
padding-right: 2px;
}
.fm-modal-window.fm-get-info-modal .fm-modal-window-body::-webkit-scrollbar,
.fm-get-info-body::-webkit-scrollbar {
width: 10px;
}
.fm-modal-window.fm-get-info-modal .fm-modal-window-body::-webkit-scrollbar-thumb,
.fm-get-info-body::-webkit-scrollbar-thumb {
background: #d1d5db;
border-radius: 6px;
}
.fm-modal-window.fm-get-info-modal .fm-modal-window-body::-webkit-scrollbar-track,
.fm-get-info-body::-webkit-scrollbar-track {
background: #f3f4f6;
}
.fm-get-info-modal-group {
display: flex;
flex-direction: column;
gap: 16px;
}
.fm-get-info-modal-group-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
font-weight: 700;
}
.fm-get-info-modal-group-properties {
display: flex;
flex-direction: column;
gap: 8px;
}
.fm-get-info-modal-property-row {
display: flex;
justify-content: space-between;
gap: 8px;
align-items: center;
}
.fm-get-info-modal-property-label {
color: #555;
flex: 0 0 auto;
}
.fm-get-info-modal-property-value {
display: inline-flex;
align-items: center;
gap: 6px;
max-width: 60%;
overflow-wrap: anywhere;
word-break: break-word;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
color: #111827;
}
.fm-copy-btn {
margin-left: 6px;
border: none;
background: transparent;
cursor: pointer;
padding: 2px;
line-height: 0;
}
.fm-copy-btn:hover {
background: #f5f5f5;
border-radius: 4px;
}
@@ -0,0 +1,78 @@
import { ReactElement, useState } from 'react'
import './GetInfoModal.scss'
import { Button } from '../Button/Button'
import { createPortal } from 'react-dom'
import InfoIcon from 'remixicon-react/InformationLineIcon'
import ClipboardIcon from 'remixicon-react/FileCopyLineIcon'
import type { FileProperty, FilePropertyGroup } from '../../utils/infoGroups'
interface GetInfoModalProps {
name: string
properties: FilePropertyGroup[]
onCancelClick: () => void
}
export function GetInfoModal({ name, onCancelClick, properties }: GetInfoModalProps): ReactElement {
const modalRoot = document.querySelector('.fm-main') || document.body
const [copiedKey, setCopiedKey] = useState<string | null>(null)
const handleCopy = async (prop: FileProperty) => {
try {
await navigator.clipboard.writeText(prop.raw ?? prop.value)
setCopiedKey(prop.key)
window.setTimeout(() => setCopiedKey(null), 1200)
} catch {
/* noop */
}
}
return createPortal(
<div className="fm-modal-container">
<div className="fm-modal-window fm-get-info-modal">
<div className="fm-modal-window-header">
<InfoIcon /> <span className="fm-main-font-color">File Information - {name}</span>
</div>
<div className="fm-modal-window-body fm-get-info-body">
{properties.map(group => (
<div key={group.title} className="fm-get-info-modal-group">
<div className="fm-get-info-modal-group-title">
{group.icon}
{group.title}
</div>
<div className="fm-get-info-modal-group-properties">
{group.properties.map(prop => (
<div key={prop.key} className="fm-get-info-modal-property-row">
<span className="fm-get-info-modal-property-label">{prop.label}</span>
<span className="fm-get-info-modal-property-value">
{prop.value}
{(prop.raw || prop.value.includes('...')) && (
<button
className="fm-copy-btn"
onClick={() => handleCopy(prop)}
aria-label={`Copy ${prop.label}`}
type="button"
title={copiedKey === prop.key ? 'Copied!' : 'Copy'}
>
<ClipboardIcon size="14px" />
</button>
)}
</span>
</div>
))}
</div>
</div>
))}
</div>
<div className="fm-modal-window-footer">
<div className="fm-get-info-modal-footer-one-button">
<Button label="Close" variant="secondary" onClick={onCancelClick} />
</div>
</div>
</div>
</div>,
modalRoot,
)
}