Clean up style of automation selection

This commit is contained in:
Connor Yoh 2025-08-19 14:01:28 +01:00
parent 9a2fd952b1
commit 49659a17c1
4 changed files with 219 additions and 100 deletions

View File

@ -0,0 +1,91 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Group, Text, Badge } from '@mantine/core';
interface AutomationEntryProps {
/** Optional title for the automation (usually for custom ones) */
title?: string;
/** MUI Icon component for the badge */
badgeIcon?: React.ComponentType<any>;
/** Array of tool operation names in the workflow */
operations: string[];
/** Click handler */
onClick: () => void;
/** Whether to keep the icon at normal color (for special cases like "Add New") */
keepIconColor?: boolean;
}
export default function AutomationEntry({
title,
badgeIcon: BadgeIcon,
operations,
onClick,
keepIconColor = false
}: AutomationEntryProps) {
const { t } = useTranslation();
const renderContent = () => {
if (title) {
// Custom automation with title
return (
<Group gap="md" align="center" justify="flex-start" style={{ width: '100%' }}>
{BadgeIcon && (
<BadgeIcon
style={{
color: keepIconColor ? 'inherit' : 'var(--mantine-color-dimmed)'
}}
/>
)}
<Text fw={600} size="sm" style={{ flex: 1, textAlign: 'left', color: 'var(--mantine-color-dimmed)' }}>
{title}
</Text>
</Group>
);
} else {
// Suggested automation showing tool chain
return (
<Group gap="md" align="center" justify="flex-start" style={{ width: '100%' }}>
{BadgeIcon && (
<BadgeIcon
style={{
color: keepIconColor ? 'inherit' : 'var(--mantine-color-dimmed)'
}}
/>
)}
<Group gap="xs" justify="flex-start" style={{ flex: 1 }}>
{operations.map((op, index) => (
<React.Fragment key={`${op}-${index}`}>
<Badge size="xs" variant="outline" style={{ color: 'var(--mantine-color-dimmed)', borderColor: 'var(--mantine-color-dimmed)' }}>
{String(t(`${op}.title`, op))}
</Badge>
{index < operations.length - 1 && (
<Text size="xs" c="dimmed" style={{ color: 'var(--mantine-color-dimmed)' }}>
</Text>
)}
</React.Fragment>
))}
</Group>
</Group>
);
}
};
return (
<Button
variant="subtle"
fullWidth
onClick={onClick}
style={{
height: 'auto',
padding: '0.75rem 1rem',
justifyContent: 'flex-start',
display: 'flex'
}}
>
<div style={{ width: '100%', display: 'flex', justifyContent: 'flex-start' }}>
{renderContent()}
</div>
</Button>
);
}

View File

@ -1,9 +1,11 @@
import React, { useState, useEffect } from "react";
import React from "react";
import { useTranslation } from "react-i18next";
import { Button, Card, Text, Title, Stack, Group, Badge, Divider } from "@mantine/core";
import AddIcon from "@mui/icons-material/Add";
import { Title, Stack, Divider } from "@mantine/core";
import AddCircleOutline from "@mui/icons-material/AddCircleOutline";
import SettingsIcon from "@mui/icons-material/Settings";
import StarIcon from "@mui/icons-material/Star";
import AutomationEntry from "./AutomationEntry";
import { useSuggestedAutomations } from "../../../hooks/tools/automate/useSuggestedAutomations";
import { useSavedAutomations } from "../../../hooks/tools/automate/useSavedAutomations";
interface AutomationSelectionProps {
onSelectCustom: () => void;
@ -11,91 +13,34 @@ interface AutomationSelectionProps {
onCreateNew: () => void;
}
interface SavedAutomation {
id: string;
name: string;
description?: string;
operations: any[];
createdAt: string;
}
export default function AutomationSelection({ onSelectCustom, onSelectSuggested, onCreateNew }: AutomationSelectionProps) {
const { t } = useTranslation();
const [savedAutomations, setSavedAutomations] = useState<SavedAutomation[]>([]);
// Load saved automations from IndexedDB
useEffect(() => {
loadSavedAutomations();
}, []);
const loadSavedAutomations = async () => {
try {
const { automationStorage } = await import("../../../services/automationStorage");
const automations = await automationStorage.getAllAutomations();
setSavedAutomations(automations);
} catch (error) {
console.error("Error loading saved automations:", error);
setSavedAutomations([]);
}
};
// Suggested automations - these are pre-defined common workflows
const suggestedAutomations = [
{
id: "compress-and-merge",
name: t("automate.suggested.compressAndMerge.name", "Compress & Merge"),
description: t("automate.suggested.compressAndMerge.description", "Compress multiple PDFs then merge them into one"),
operations: ["compress", "merge"],
icon: <StarIcon />,
},
{
id: "ocr-and-convert",
name: t("automate.suggested.ocrAndConvert.name", "OCR & Convert"),
description: t("automate.suggested.ocrAndConvert.description", "Apply OCR to PDFs then convert to different format"),
operations: ["ocr", "convert"],
icon: <StarIcon />,
},
{
id: "secure-workflow",
name: t("automate.suggested.secureWorkflow.name", "Secure Workflow"),
description: t("automate.suggested.secureWorkflow.description", "Sanitize, add password, and set permissions"),
operations: ["sanitize", "addPassword", "changePermissions"],
icon: <StarIcon />,
},
];
const { savedAutomations } = useSavedAutomations();
const suggestedAutomations = useSuggestedAutomations();
return (
<Stack gap="xl">
{/* Create New Automation */}
<div>
<Title order={3} size="h4" mb="md">
{t("automate.selection.saved.title", "Saved")}
</Title>
<Button variant="subtle" onClick={onCreateNew}>
<Group gap="md" align="center">
<AddIcon color="primary" />
<Text fw={600}>{t("automate.selection.createNew.title", "Create New Automation")}</Text>
</Group>
</Button>
<Stack gap="xs">
<AutomationEntry
title={t("automate.selection.createNew.title", "Create New Automation")}
badgeIcon={AddCircleOutline}
operations={[]}
onClick={onCreateNew}
keepIconColor={true}
/>
{/* Saved Automations */}
{savedAutomations.map((automation) => (
<Button variant="subtle" fullWidth={true} onClick={() => onSelectCustom()}>
<div style={{ flex: 1 }}>
<Group gap="xs">
{automation.operations.map((op: any, index: number) => (
<React.Fragment key={`${op.operation || op}-${index}`}>
<Badge size="xs" variant="outline">
{String(t(`tools.${op.operation || op}.name`, op.operation || op))}
</Badge>
{index < automation.operations.length - 1 && (
<Text size="xs" c="dimmed">
</Text>
)}
</React.Fragment>
))}
</Group>
</div>
</Button>
<AutomationEntry
key={automation.id}
title={automation.name}
badgeIcon={SettingsIcon}
operations={automation.operations.map(op => typeof op === 'string' ? op : op.operation)}
onClick={() => onSelectCustom()}
/>
))}
<Divider />
@ -104,31 +49,18 @@ export default function AutomationSelection({ onSelectCustom, onSelectSuggested,
<Title order={3} size="h4" mb="md">
{t("automate.selection.suggested.title", "Suggested")}
</Title>
<Stack gap="md">
<Stack gap="xs">
{suggestedAutomations.map((automation) => (
<Button
size="md"
variant="subtle"
fullWidth={true}
<AutomationEntry
key={automation.id}
badgeIcon={automation.icon}
operations={automation.operations}
onClick={() => onSelectSuggested(automation)}
style={{ paddingLeft: "0" }}
>
<Group gap="xs">
{automation.operations.map((op, index) => (
<React.Fragment key={op}>
{t(`${op}.title`, op)}
{index < automation.operations.length - 1 && (
<Text size="xs" c="dimmed">
</Text>
)}
</React.Fragment>
))}
</Group>
</Button>
/>
))}
</Stack>
</div>
</Stack>
</div>
);
}

View File

@ -0,0 +1,55 @@
import { useState, useEffect, useCallback } from 'react';
import { AutomationConfig } from '../../../services/automationStorage';
export interface SavedAutomation extends AutomationConfig {}
export function useSavedAutomations() {
const [savedAutomations, setSavedAutomations] = useState<SavedAutomation[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const loadSavedAutomations = useCallback(async () => {
try {
setLoading(true);
setError(null);
const { automationStorage } = await import('../../../services/automationStorage');
const automations = await automationStorage.getAllAutomations();
setSavedAutomations(automations);
} catch (err) {
console.error('Error loading saved automations:', err);
setError(err as Error);
setSavedAutomations([]);
} finally {
setLoading(false);
}
}, []);
const refreshAutomations = useCallback(() => {
loadSavedAutomations();
}, [loadSavedAutomations]);
const deleteAutomation = useCallback(async (id: string) => {
try {
const { automationStorage } = await import('../../../services/automationStorage');
await automationStorage.deleteAutomation(id);
// Refresh the list after deletion
refreshAutomations();
} catch (err) {
console.error('Error deleting automation:', err);
throw err;
}
}, [refreshAutomations]);
// Load automations on mount
useEffect(() => {
loadSavedAutomations();
}, [loadSavedAutomations]);
return {
savedAutomations,
loading,
error,
refreshAutomations,
deleteAutomation
};
}

View File

@ -0,0 +1,41 @@
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import StarIcon from '@mui/icons-material/Star';
export interface SuggestedAutomation {
id: string;
name: string;
description: string;
operations: string[];
icon: React.ComponentType<any>;
}
export function useSuggestedAutomations(): SuggestedAutomation[] {
const { t } = useTranslation();
const suggestedAutomations = useMemo<SuggestedAutomation[]>(() => [
{
id: "compress-and-merge",
name: t("automate.suggested.compressAndMerge.name", "Compress & Merge"),
description: t("automate.suggested.compressAndMerge.description", "Compress multiple PDFs then merge them into one"),
operations: ["compress", "merge"],
icon: StarIcon,
},
{
id: "ocr-and-convert",
name: t("automate.suggested.ocrAndConvert.name", "OCR & Convert"),
description: t("automate.suggested.ocrAndConvert.description", "Apply OCR to PDFs then convert to different format"),
operations: ["ocr", "convert"],
icon: StarIcon,
},
{
id: "secure-workflow",
name: t("automate.suggested.secureWorkflow.name", "Secure Workflow"),
description: t("automate.suggested.secureWorkflow.description", "Sanitize, add password, and set permissions"),
operations: ["sanitize", "addPassword", "changePermissions"],
icon: StarIcon,
},
], [t]);
return suggestedAutomations;
}