add folders fixes and improvements

This commit is contained in:
Reece
2026-03-13 00:35:44 +00:00
parent 0f1c0c2e3f
commit 4e419d135d
5 changed files with 72 additions and 64 deletions
@@ -7384,6 +7384,7 @@ automation = "Automation"
automationSaved = "Automation saved"
createFolder = "Create Folder"
stepsSaved = "Steps saved — click Create Folder to finish"
automationRequired = "Add at least one configured step before saving."
color = "Accent color"
createTitle = "New Watch Folder"
description = "Description"
@@ -6,6 +6,7 @@ import EditIcon from '@mui/icons-material/Edit';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
import CloseIcon from '@mui/icons-material/Close';
import FolderPlusIcon from '@mui/icons-material/CreateNewFolder';
import PauseCircleOutlineIcon from '@mui/icons-material/PauseCircleOutline';
import PlayCircleOutlineIcon from '@mui/icons-material/PlayCircleOutline';
@@ -348,6 +349,10 @@ function FolderCard({
function HowItWorks() {
const { t } = useTranslation();
const [dismissed, setDismissed] = useState(() => sessionStorage.getItem('wf_howItWorks_dismissed') === '1');
if (dismissed) return null;
const steps = [
{
n: '1',
@@ -376,11 +381,22 @@ function HowItWorks() {
backgroundColor: 'var(--bg-toolbar)',
}}
>
<Group gap="xs" mb="sm">
<InfoOutlinedIcon style={{ fontSize: '1rem', color: 'var(--mantine-color-blue-filled)' }} />
<Text fw={600} size="xs">
{t('smartFolders.howItWorks.title', 'How Watch Folders work')}
</Text>
<Group gap="xs" mb="sm" justify="space-between">
<Group gap="xs">
<InfoOutlinedIcon style={{ fontSize: '1rem', color: 'var(--mantine-color-blue-filled)' }} />
<Text fw={600} size="xs">
{t('smartFolders.howItWorks.title', 'How Watch Folders work')}
</Text>
</Group>
<ActionIcon
size="xs"
variant="subtle"
color="gray"
onClick={() => { sessionStorage.setItem('wf_howItWorks_dismissed', '1'); setDismissed(true); }}
aria-label="Dismiss"
>
<CloseIcon style={{ fontSize: '0.75rem', color: 'var(--mantine-color-text)' }} />
</ActionIcon>
</Group>
<Group gap="xl" wrap="nowrap" align="flex-start">
{steps.map((step) => (
@@ -632,25 +648,7 @@ export function SmartFolderHomePage() {
<EmptyState onCreate={() => setCreateModalOpen(true)} />
) : (
<Stack gap="md">
{folders.map((folder) => {
const status = statuses[folder.id] ?? 'idle';
const isProcessing =
processingFolderIds.has(folder.id) || status === 'processing';
return (
<FolderCard
key={folder.id}
folder={folder}
status={status}
isProcessing={isProcessing}
onEdit={handleEdit}
onDelete={setDeleteTarget}
onOpen={navigateToFolder}
onDropFiles={processFiles}
onDropSidebarFile={handleDropSidebarFile}
onTogglePause={handleTogglePause}
/>
);
})}
<HowItWorks />
{/* Add another prompt */}
<Box
@@ -688,7 +686,25 @@ export function SmartFolderHomePage() {
</Text>
</Box>
<HowItWorks />
{folders.map((folder) => {
const status = statuses[folder.id] ?? 'idle';
const isProcessing =
processingFolderIds.has(folder.id) || status === 'processing';
return (
<FolderCard
key={folder.id}
folder={folder}
status={status}
isProcessing={isProcessing}
onEdit={handleEdit}
onDelete={setDeleteTarget}
onOpen={navigateToFolder}
onDropFiles={processFiles}
onDropSidebarFile={handleDropSidebarFile}
onTogglePause={handleTogglePause}
/>
);
})}
</Stack>
)}
</Box>
@@ -7,9 +7,7 @@ import {
TextInput,
Textarea,
Divider,
ColorSwatch,
SimpleGrid,
Text,
ColorInput,
} from '@mantine/core';
import { useTranslation } from 'react-i18next';
import { SmartFolder } from '@app/types/smartFolders';
@@ -19,7 +17,7 @@ import AutomationCreation from '@app/components/tools/automate/AutomationCreatio
import { useToolWorkflow } from '@app/contexts/ToolWorkflowContext';
import { smartFolderStorage } from '@app/services/smartFolderStorage';
const ACCENT_COLORS = [
const ACCENT_SWATCHES = [
'#3b82f6', '#0ea5e9', '#14b8a6', '#22c55e',
'#f97316', '#ef4444', '#9333ea', '#ec4899',
'#6366f1', '#eab308', '#64748b', '#0f172a',
@@ -48,9 +46,9 @@ export function SmartFolderManagementModal({
const [description, setDescription] = useState(editFolder?.description ?? '');
const [icon, setIcon] = useState(editFolder?.icon ?? 'FolderIcon');
const [accentColor, setAccentColor] = useState(editFolder?.accentColor ?? '#3b82f6');
const [customColor, setCustomColor] = useState('');
const [saving, setSaving] = useState(false);
const [nameError, setNameError] = useState('');
const [automationError, setAutomationError] = useState('');
// AutomationCreation exposes its save function via this ref
const automationSaveTrigger = useRef<(() => void) | null>(null);
@@ -60,8 +58,9 @@ export function SmartFolderManagementModal({
setDescription(editFolder?.description ?? '');
setIcon(editFolder?.icon ?? 'FolderIcon');
setAccentColor(editFolder?.accentColor ?? '#3b82f6');
setCustomColor('');
setNameError('');
setAutomationError('');
setSaving(false);
}, [editFolder]);
@@ -116,6 +115,7 @@ export function SmartFolderManagementModal({
setNameError(t('smartFolders.modal.nameTooLong', 'Folder name must be 50 characters or less'));
return;
}
setAutomationError('');
setSaving(true);
// Trigger automation save; onComplete handles the rest
automationSaveTrigger.current?.();
@@ -161,34 +161,15 @@ export function SmartFolderManagementModal({
size="sm"
/>
<Stack gap="xs">
<Text size="sm" fw={600}>{t('smartFolders.modal.color', 'Accent color')}</Text>
<Group gap="xs">
<SimpleGrid cols={12} spacing={4}>
{ACCENT_COLORS.map((color) => (
<ColorSwatch
key={color}
color={color}
size={20}
style={{ cursor: 'pointer', outline: accentColor === color ? `2px solid ${color}` : 'none', outlineOffset: 2 }}
onClick={() => setAccentColor(color)}
/>
))}
</SimpleGrid>
<TextInput
placeholder="#hex"
value={customColor}
onChange={(e) => {
setCustomColor(e.currentTarget.value);
if (/^#[0-9a-fA-F]{6}$/.test(e.currentTarget.value)) {
setAccentColor(e.currentTarget.value);
}
}}
size="xs"
style={{ width: '5.5rem' }}
/>
</Group>
</Stack>
<ColorInput
label={t('smartFolders.modal.color', 'Accent color')}
value={accentColor}
onChange={setAccentColor}
format="hex"
swatches={ACCENT_SWATCHES}
size="sm"
popoverProps={{ withinPortal: true }}
/>
</Stack>
<Divider label={t('smartFolders.modal.automation', 'Automation')} labelPosition="left" />
@@ -198,11 +179,15 @@ export function SmartFolderManagementModal({
existingAutomation={existingAutomation ?? undefined}
onBack={handleClose}
onComplete={handleAutomationComplete}
onSaveFailed={() => { setSaving(false); setAutomationError(t('smartFolders.modal.automationRequired', 'Add at least one configured step before saving.')); }}
toolRegistry={toolRegistry}
hideMetadata
nameOverride={name.trim() || 'Watch Folder Automation'}
saveTriggerRef={automationSaveTrigger}
/>
{automationError && (
<Text size="xs" c="red">{automationError}</Text>
)}
<Divider />
@@ -643,9 +643,14 @@ export function WatchFolderFileList({ files, folderId, onSendToFolder, onNavigat
<ScrollArea style={{ flex: 1, minHeight: 0 }}>
<Box style={{ padding: '0.25rem 0' }}>
{files.length === 0 ? (
<Text size="xs" c="dimmed" ta="center" py="xl" px="sm">
{t('smartFolders.fileList.empty', 'No files yet')}
</Text>
<Box py="xl" px="sm" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '0.5rem' }}>
<Text size="xs" c="dimmed" ta="center">
{t('smartFolders.fileList.empty', 'No files yet')}
</Text>
<Button size="xs" variant="light" leftSection={<AddIcon style={{ fontSize: '0.875rem' }} />} onClick={handleUploadClick}>
{t('smartFolders.fileList.uploadFiles', 'Upload PDFs')}
</Button>
</Box>
) : processedFiles.length === 0 ? (
<Text size="xs" c="dimmed" ta="center" py="md" px="sm">
{search ? t('smartFolders.fileList.noResults', 'No files match your search') : t('smartFolders.fileList.noneInFilter', 'No files in this category')}
@@ -31,9 +31,10 @@ interface AutomationCreationProps {
hideMetadata?: boolean;
nameOverride?: string;
saveTriggerRef?: React.MutableRefObject<(() => void) | null>;
onSaveFailed?: () => void;
}
export default function AutomationCreation({ mode, existingAutomation, onBack, onComplete, toolRegistry, hideMetadata = false, nameOverride, saveTriggerRef }: AutomationCreationProps) {
export default function AutomationCreation({ mode, existingAutomation, onBack, onComplete, toolRegistry, hideMetadata = false, nameOverride, saveTriggerRef, onSaveFailed }: AutomationCreationProps) {
const { t } = useTranslation();
const {
@@ -104,7 +105,7 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o
};
const saveAutomation = async () => {
if (!canSave()) return;
if (!canSave()) { onSaveFailed?.(); return; }
const automationData = {
name: effectiveName.trim(),