add folders fixes and improvements
This commit is contained in:
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user