mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-26 14:19:24 +00:00
Clean up style of automation selection
This commit is contained in:
parent
9a2fd952b1
commit
49659a17c1
91
frontend/src/components/tools/automate/AutomationEntry.tsx
Normal file
91
frontend/src/components/tools/automate/AutomationEntry.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Button, Card, Text, Title, Stack, Group, Badge, Divider } from "@mantine/core";
|
import { Title, Stack, Divider } from "@mantine/core";
|
||||||
import AddIcon from "@mui/icons-material/Add";
|
import AddCircleOutline from "@mui/icons-material/AddCircleOutline";
|
||||||
import SettingsIcon from "@mui/icons-material/Settings";
|
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 {
|
interface AutomationSelectionProps {
|
||||||
onSelectCustom: () => void;
|
onSelectCustom: () => void;
|
||||||
@ -11,91 +13,34 @@ interface AutomationSelectionProps {
|
|||||||
onCreateNew: () => void;
|
onCreateNew: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SavedAutomation {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
operations: any[];
|
|
||||||
createdAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AutomationSelection({ onSelectCustom, onSelectSuggested, onCreateNew }: AutomationSelectionProps) {
|
export default function AutomationSelection({ onSelectCustom, onSelectSuggested, onCreateNew }: AutomationSelectionProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [savedAutomations, setSavedAutomations] = useState<SavedAutomation[]>([]);
|
const { savedAutomations } = useSavedAutomations();
|
||||||
|
const suggestedAutomations = useSuggestedAutomations();
|
||||||
// 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 />,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="xl">
|
<div>
|
||||||
{/* Create New Automation */}
|
|
||||||
<Title order={3} size="h4" mb="md">
|
<Title order={3} size="h4" mb="md">
|
||||||
{t("automate.selection.saved.title", "Saved")}
|
{t("automate.selection.saved.title", "Saved")}
|
||||||
</Title>
|
</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) => (
|
{savedAutomations.map((automation) => (
|
||||||
<Button variant="subtle" fullWidth={true} onClick={() => onSelectCustom()}>
|
<AutomationEntry
|
||||||
<div style={{ flex: 1 }}>
|
key={automation.id}
|
||||||
<Group gap="xs">
|
title={automation.name}
|
||||||
{automation.operations.map((op: any, index: number) => (
|
badgeIcon={SettingsIcon}
|
||||||
<React.Fragment key={`${op.operation || op}-${index}`}>
|
operations={automation.operations.map(op => typeof op === 'string' ? op : op.operation)}
|
||||||
<Badge size="xs" variant="outline">
|
onClick={() => onSelectCustom()}
|
||||||
{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>
|
|
||||||
))}
|
))}
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
@ -104,31 +49,18 @@ export default function AutomationSelection({ onSelectCustom, onSelectSuggested,
|
|||||||
<Title order={3} size="h4" mb="md">
|
<Title order={3} size="h4" mb="md">
|
||||||
{t("automate.selection.suggested.title", "Suggested")}
|
{t("automate.selection.suggested.title", "Suggested")}
|
||||||
</Title>
|
</Title>
|
||||||
<Stack gap="md">
|
<Stack gap="xs">
|
||||||
{suggestedAutomations.map((automation) => (
|
{suggestedAutomations.map((automation) => (
|
||||||
<Button
|
<AutomationEntry
|
||||||
size="md"
|
key={automation.id}
|
||||||
variant="subtle"
|
badgeIcon={automation.icon}
|
||||||
fullWidth={true}
|
operations={automation.operations}
|
||||||
onClick={() => onSelectSuggested(automation)}
|
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>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
55
frontend/src/hooks/tools/automate/useSavedAutomations.ts
Normal file
55
frontend/src/hooks/tools/automate/useSavedAutomations.ts
Normal 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
|
||||||
|
};
|
||||||
|
}
|
41
frontend/src/hooks/tools/automate/useSuggestedAutomations.ts
Normal file
41
frontend/src/hooks/tools/automate/useSuggestedAutomations.ts
Normal 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;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user