mirror of
https://github.com/Viren070/AIOStreams.git
synced 2025-12-01 23:14:04 +01:00
feat(frontend): implement formatter import/export functionality with refactored ImportModal component
This commit is contained in:
@@ -4,6 +4,7 @@ import { PageWrapper } from '../shared/page-wrapper';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
|
||||
import { cn } from '../ui/core/styling';
|
||||
import { SettingsNavCard } from '../shared/settings-card';
|
||||
import { ImportModal } from '../shared/import-modal';
|
||||
import { useUserData } from '@/context/userData';
|
||||
import {
|
||||
FaBolt,
|
||||
@@ -3041,126 +3042,6 @@ function TwoTextInputs({
|
||||
);
|
||||
}
|
||||
|
||||
function ImportModal({
|
||||
open,
|
||||
onOpenChange,
|
||||
onImport,
|
||||
}: {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onImport: (data: any) => void;
|
||||
}) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const urlModalDisclosure = useDisclosure(false);
|
||||
const [url, setUrl] = useState('');
|
||||
|
||||
const handleFileImport = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const content = e.target?.result as string;
|
||||
const data = JSON.parse(content);
|
||||
onImport(data);
|
||||
onOpenChange(false);
|
||||
} catch (error) {
|
||||
console.error('Error importing file:', error);
|
||||
toast.error('Failed to parse JSON file');
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
const handleUrlImport = async () => {
|
||||
if (!url) {
|
||||
toast.error('Please enter a URL');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch URL');
|
||||
}
|
||||
const data = await response.json();
|
||||
onImport(data);
|
||||
urlModalDisclosure.close();
|
||||
onOpenChange(false);
|
||||
} catch (error) {
|
||||
console.error('Error importing from URL:', error);
|
||||
toast.error('Failed to import from URL');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal open={open} onOpenChange={onOpenChange} title="Import">
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col gap-4">
|
||||
<input
|
||||
type="file"
|
||||
ref={fileInputRef}
|
||||
onChange={handleFileImport}
|
||||
accept=".json"
|
||||
className="hidden"
|
||||
/>
|
||||
<Button
|
||||
intent="primary"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="w-full"
|
||||
>
|
||||
Import from File
|
||||
</Button>
|
||||
<Button
|
||||
intent="primary"
|
||||
onClick={urlModalDisclosure.open}
|
||||
className="w-full"
|
||||
>
|
||||
Import from URL
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
open={urlModalDisclosure.isOpen}
|
||||
onOpenChange={urlModalDisclosure.close}
|
||||
title="Import from URL"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<TextInput
|
||||
label="URL"
|
||||
value={url}
|
||||
onValueChange={setUrl}
|
||||
placeholder="Enter URL to JSON file"
|
||||
/>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button intent="primary-outline" onClick={urlModalDisclosure.close}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
intent="primary"
|
||||
onClick={handleUrlImport}
|
||||
loading={isLoading}
|
||||
>
|
||||
Import
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface SizeRangeSliderProps {
|
||||
label: string;
|
||||
help?: string;
|
||||
|
||||
@@ -20,6 +20,10 @@ import { CopyIcon } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { NumberInput } from '../ui/number-input';
|
||||
import { PageControls } from '../shared/page-controls';
|
||||
import { Tooltip } from '../ui/tooltip';
|
||||
import { FaFileImport, FaFileExport } from 'react-icons/fa';
|
||||
import { IconButton } from '../ui/button';
|
||||
import { ImportModal } from '../shared/import-modal';
|
||||
const formatterChoices = Object.values(constants.FORMATTER_DETAILS);
|
||||
|
||||
// Remove the throttle utility and replace with FormatQueue
|
||||
@@ -95,6 +99,7 @@ function FormatterPreviewBox({
|
||||
|
||||
function Content() {
|
||||
const { userData, setUserData } = useUserData();
|
||||
const importModalDisclosure = useDisclosure(false);
|
||||
|
||||
const [formattedStream, setFormattedStream] = useState<{
|
||||
name: string;
|
||||
@@ -102,6 +107,38 @@ function Content() {
|
||||
} | null>(null);
|
||||
const [isFormatting, setIsFormatting] = useState(false);
|
||||
|
||||
const handleImport = (data: any) => {
|
||||
if (typeof data.name === 'string' && typeof data.description === 'string') {
|
||||
handleFormatterChange(
|
||||
constants.CUSTOM_FORMATTER,
|
||||
data.name,
|
||||
data.description
|
||||
);
|
||||
toast.success('Formatter imported successfully');
|
||||
} else {
|
||||
toast.error('Invalid formatter format');
|
||||
}
|
||||
};
|
||||
|
||||
const handleExport = () => {
|
||||
const data = {
|
||||
name: userData.formatter.definition?.name || '',
|
||||
description: userData.formatter.definition?.description || '',
|
||||
};
|
||||
const blob = new Blob([JSON.stringify(data, null, 2)], {
|
||||
type: 'application/json',
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'custom-formatter.json';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
toast.success('Formatter exported successfully');
|
||||
};
|
||||
|
||||
// Create format queue ref to persist between renders
|
||||
const formatQueueRef = React.useRef<FormatQueue>(new FormatQueue(200));
|
||||
|
||||
@@ -345,7 +382,37 @@ function Content() {
|
||||
placeholder="Enter a template for the stream description"
|
||||
/>
|
||||
</div>
|
||||
<SnippetsButton />
|
||||
<div className="flex gap-2 items-center">
|
||||
<SnippetsButton />
|
||||
<div className="ml-auto flex gap-2">
|
||||
<Tooltip
|
||||
trigger={
|
||||
<IconButton
|
||||
rounded
|
||||
size="sm"
|
||||
intent="primary-subtle"
|
||||
icon={<FaFileImport />}
|
||||
onClick={importModalDisclosure.open}
|
||||
/>
|
||||
}
|
||||
>
|
||||
Import
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
trigger={
|
||||
<IconButton
|
||||
rounded
|
||||
size="sm"
|
||||
intent="primary-subtle"
|
||||
icon={<FaFileExport />}
|
||||
onClick={handleExport}
|
||||
/>
|
||||
}
|
||||
>
|
||||
Export
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsCard>
|
||||
)}
|
||||
@@ -502,6 +569,12 @@ function Content() {
|
||||
</div>
|
||||
</div>
|
||||
</SettingsCard>
|
||||
|
||||
<ImportModal
|
||||
open={importModalDisclosure.isOpen}
|
||||
onOpenChange={importModalDisclosure.toggle}
|
||||
onImport={handleImport}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
'use client';
|
||||
import { useRef, useState } from 'react';
|
||||
import { Modal } from '../ui/modal';
|
||||
import { Button } from '../ui/button';
|
||||
import { TextInput } from '../ui/text-input';
|
||||
import { useDisclosure } from '@/hooks/disclosure';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export interface ImportModalProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onImport: (data: any) => void;
|
||||
}
|
||||
|
||||
export function ImportModal({
|
||||
open,
|
||||
onOpenChange,
|
||||
onImport,
|
||||
}: ImportModalProps) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const urlModalDisclosure = useDisclosure(false);
|
||||
const [url, setUrl] = useState('');
|
||||
|
||||
const handleFileImport = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const content = e.target?.result as string;
|
||||
const data = JSON.parse(content);
|
||||
onImport(data);
|
||||
onOpenChange(false);
|
||||
} catch (error) {
|
||||
console.error('Error importing file:', error);
|
||||
toast.error('Failed to parse JSON file');
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
const handleUrlImport = async () => {
|
||||
if (!url) {
|
||||
toast.error('Please enter a URL');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch URL');
|
||||
}
|
||||
const data = await response.json();
|
||||
onImport(data);
|
||||
urlModalDisclosure.close();
|
||||
onOpenChange(false);
|
||||
} catch (error) {
|
||||
console.error('Error importing from URL:', error);
|
||||
toast.error('Failed to import from URL');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal open={open} onOpenChange={onOpenChange} title="Import">
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col gap-4">
|
||||
<input
|
||||
type="file"
|
||||
ref={fileInputRef}
|
||||
onChange={handleFileImport}
|
||||
accept=".json"
|
||||
className="hidden"
|
||||
/>
|
||||
<Button
|
||||
intent="primary"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="w-full"
|
||||
>
|
||||
Import from File
|
||||
</Button>
|
||||
<Button
|
||||
intent="primary"
|
||||
onClick={urlModalDisclosure.open}
|
||||
className="w-full"
|
||||
>
|
||||
Import from URL
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
open={urlModalDisclosure.isOpen}
|
||||
onOpenChange={urlModalDisclosure.close}
|
||||
title="Import from URL"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<TextInput
|
||||
label="URL"
|
||||
value={url}
|
||||
onValueChange={setUrl}
|
||||
placeholder="Enter URL to JSON file"
|
||||
/>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button intent="primary-outline" onClick={urlModalDisclosure.close}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
intent="primary"
|
||||
onClick={handleUrlImport}
|
||||
loading={isLoading}
|
||||
>
|
||||
Import
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user