chunker ui redo
This commit is contained in:
97
ui/common/components/FileManager.css
Normal file
97
ui/common/components/FileManager.css
Normal file
@@ -0,0 +1,97 @@
|
||||
.file-manager {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.fm-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.fm-header h2 {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.fm-scan-btn {
|
||||
padding: 0.25rem 0.6rem;
|
||||
background: var(--bg-input);
|
||||
color: var(--text-secondary);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
.fm-scan-btn:hover:not(:disabled) {
|
||||
color: var(--text-primary);
|
||||
background: var(--border-light);
|
||||
}
|
||||
|
||||
.fm-list {
|
||||
list-style: none;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.fm-empty {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.fm-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.4rem 0.6rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
transition: background 0.1s;
|
||||
}
|
||||
.fm-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.fm-clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
.fm-clickable:hover {
|
||||
background: var(--bg-input);
|
||||
}
|
||||
|
||||
.fm-selected {
|
||||
background: var(--accent) !important;
|
||||
color: #fff;
|
||||
}
|
||||
.fm-selected .fm-meta {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.fm-item-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.fm-filename {
|
||||
font-size: var(--font-size-sm);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.fm-meta {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.fm-actions {
|
||||
flex-shrink: 0;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
84
ui/common/components/FileManager.tsx
Normal file
84
ui/common/components/FileManager.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* FileManager — pluggable file browser for S3/MinIO files.
|
||||
*
|
||||
* Handles both input file selection and output file listing.
|
||||
* Used by timeline (assets + output), chunker (assets + chunk output),
|
||||
* and future tools.
|
||||
*/
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { formatSize } from "../utils/format";
|
||||
import "./FileManager.css";
|
||||
|
||||
export interface FileEntry {
|
||||
key: string;
|
||||
name: string;
|
||||
size?: number;
|
||||
meta?: string;
|
||||
}
|
||||
|
||||
interface FileManagerProps {
|
||||
title: string;
|
||||
files: FileEntry[];
|
||||
selectedKey?: string | null;
|
||||
onSelect?: (file: FileEntry) => void;
|
||||
onScan?: () => void;
|
||||
scanning?: boolean;
|
||||
emptyMessage?: string;
|
||||
renderActions?: (file: FileEntry) => ReactNode;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function FileManager({
|
||||
title,
|
||||
files,
|
||||
selectedKey,
|
||||
onSelect,
|
||||
onScan,
|
||||
scanning = false,
|
||||
emptyMessage = "No files",
|
||||
renderActions,
|
||||
disabled = false,
|
||||
}: FileManagerProps) {
|
||||
return (
|
||||
<div className="file-manager">
|
||||
<div className="fm-header">
|
||||
<h2>{title}</h2>
|
||||
{onScan && (
|
||||
<button
|
||||
className="fm-scan-btn"
|
||||
onClick={onScan}
|
||||
disabled={scanning || disabled}
|
||||
>
|
||||
{scanning ? "Scanning..." : "Scan Folder"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<ul className="fm-list">
|
||||
{files.length === 0 ? (
|
||||
<li className="fm-empty">{emptyMessage}</li>
|
||||
) : (
|
||||
files.map((file) => (
|
||||
<li
|
||||
key={file.key}
|
||||
className={`fm-item ${selectedKey === file.key ? "fm-selected" : ""} ${onSelect && !disabled ? "fm-clickable" : ""}`}
|
||||
onClick={() => onSelect && !disabled && onSelect(file)}
|
||||
title={file.name}
|
||||
>
|
||||
<div className="fm-item-info">
|
||||
<span className="fm-filename">{file.name}</span>
|
||||
<span className="fm-meta">
|
||||
{file.size != null && formatSize(file.size)}
|
||||
{file.meta && (file.size != null ? ` · ${file.meta}` : file.meta)}
|
||||
</span>
|
||||
</div>
|
||||
{renderActions && (
|
||||
<div className="fm-actions">{renderActions(file)}</div>
|
||||
)}
|
||||
</li>
|
||||
))
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
33
ui/common/components/StatusDot.tsx
Normal file
33
ui/common/components/StatusDot.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* StatusDot — small colored indicator for connection/state.
|
||||
*/
|
||||
|
||||
const STATE_COLORS: Record<string, string> = {
|
||||
connected: "var(--success)",
|
||||
idle: "var(--text-muted)",
|
||||
processing: "var(--processing)",
|
||||
stopped: "var(--text-muted)",
|
||||
error: "var(--error)",
|
||||
done: "var(--success)",
|
||||
};
|
||||
|
||||
interface StatusDotProps {
|
||||
state: string;
|
||||
glow?: boolean;
|
||||
}
|
||||
|
||||
export function StatusDot({ state, glow = false }: StatusDotProps) {
|
||||
const color = STATE_COLORS[state] || "var(--text-muted)";
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
display: "inline-block",
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: "50%",
|
||||
background: color,
|
||||
boxShadow: glow ? `0 0 6px ${color}` : undefined,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user