mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 09:33:10 -07:00
203 lines
5.3 KiB
TypeScript
203 lines
5.3 KiB
TypeScript
import {
|
|
Stack,
|
|
Text,
|
|
Group,
|
|
Button,
|
|
Select,
|
|
Modal,
|
|
TextInput,
|
|
Badge,
|
|
} from '@mantine/core';
|
|
import {
|
|
useState,
|
|
useCallback,
|
|
forwardRef,
|
|
type ComponentPropsWithoutRef,
|
|
} from 'react';
|
|
import { Save, File, Plus } from 'lucide-react';
|
|
import type { ConfigFile } from '@/types';
|
|
import styles from '@/styles/layout.module.css';
|
|
|
|
interface ConfigFileManagerProps {
|
|
configFiles: ConfigFile[];
|
|
selectedFile: string | null;
|
|
onFileSelection: (fileName: string) => Promise<void>;
|
|
onCreateNewConfig: (configName: string) => Promise<void>;
|
|
onSaveConfig: () => void;
|
|
onLoadConfigFiles: () => Promise<void>;
|
|
}
|
|
|
|
interface SelectItemProps extends ComponentPropsWithoutRef<'div'> {
|
|
label: string;
|
|
extension: string;
|
|
}
|
|
|
|
const getBadgeColor = (extension: string) => {
|
|
switch (extension.toLowerCase()) {
|
|
case '.kcpps':
|
|
return 'blue';
|
|
case '.kcppt':
|
|
return 'green';
|
|
default:
|
|
return 'gray';
|
|
}
|
|
};
|
|
|
|
const SelectItem = forwardRef<HTMLDivElement, SelectItemProps>(
|
|
({ label, extension, ...others }, ref) => (
|
|
<div ref={ref} {...others}>
|
|
<Group justify="space-between" wrap="nowrap">
|
|
<Text size="sm" truncate>
|
|
{label}
|
|
</Text>
|
|
<Badge size="xs" variant="light" color={getBadgeColor(extension)}>
|
|
{extension}
|
|
</Badge>
|
|
</Group>
|
|
</div>
|
|
)
|
|
);
|
|
|
|
SelectItem.displayName = 'SelectItem';
|
|
|
|
export const ConfigFileManager = ({
|
|
configFiles,
|
|
selectedFile,
|
|
onFileSelection,
|
|
onCreateNewConfig,
|
|
onSaveConfig,
|
|
}: ConfigFileManagerProps) => {
|
|
const [configModalOpened, setConfigModalOpened] = useState(false);
|
|
const [newConfigName, setNewConfigName] = useState('');
|
|
|
|
const existingConfigNames = configFiles.map((file) => {
|
|
const extension = file.name.split('.').pop() || '';
|
|
return file.name.replace(`.${extension}`, '').toLowerCase();
|
|
});
|
|
|
|
const trimmedConfigName = newConfigName.trim();
|
|
const configNameExists =
|
|
trimmedConfigName &&
|
|
existingConfigNames.includes(trimmedConfigName.toLowerCase());
|
|
|
|
const handleOpenConfigModal = () => {
|
|
setConfigModalOpened(true);
|
|
};
|
|
|
|
const handleCloseConfigModal = useCallback(() => {
|
|
setConfigModalOpened(false);
|
|
setNewConfigName('');
|
|
}, []);
|
|
|
|
const handleConfigSubmit = useCallback(() => {
|
|
onCreateNewConfig(newConfigName.trim());
|
|
setConfigModalOpened(false);
|
|
setNewConfigName('');
|
|
}, [newConfigName, onCreateNewConfig]);
|
|
|
|
const selectData = configFiles.map((file) => {
|
|
const extension = file.name.split('.').pop() || '';
|
|
const nameWithoutExtension = file.name.replace(`.${extension}`, '');
|
|
|
|
return {
|
|
value: file.name,
|
|
label: nameWithoutExtension,
|
|
extension: `.${extension}`,
|
|
};
|
|
});
|
|
|
|
return (
|
|
<>
|
|
<Stack gap="xs">
|
|
<Text fw={500} size="sm">
|
|
Configuration
|
|
</Text>
|
|
<Group gap="xs" align="flex-end">
|
|
<div className={styles.flex1}>
|
|
<Select
|
|
placeholder="Select a configuration file"
|
|
value={selectedFile}
|
|
onChange={(value: string | null) => {
|
|
if (value === '__new__') {
|
|
return;
|
|
}
|
|
if (value) {
|
|
onFileSelection(value);
|
|
}
|
|
}}
|
|
data={selectData}
|
|
leftSection={<File size={16} />}
|
|
searchable
|
|
clearable={false}
|
|
renderOption={({ option }) => {
|
|
const dataItem = selectData.find(
|
|
(item) => item.value === option.value
|
|
);
|
|
const extension = dataItem?.extension || '';
|
|
return (
|
|
<SelectItem label={option.label} extension={extension} />
|
|
);
|
|
}}
|
|
/>
|
|
</div>
|
|
<Button
|
|
variant="light"
|
|
leftSection={<Plus size={14} />}
|
|
size="sm"
|
|
onClick={() => handleOpenConfigModal()}
|
|
>
|
|
New
|
|
</Button>
|
|
|
|
<Button
|
|
variant="outline"
|
|
leftSection={<Save size={14} />}
|
|
size="sm"
|
|
onClick={() => {
|
|
if (selectedFile) {
|
|
onSaveConfig();
|
|
} else {
|
|
handleOpenConfigModal();
|
|
}
|
|
}}
|
|
>
|
|
Save
|
|
</Button>
|
|
</Group>
|
|
</Stack>
|
|
|
|
<Modal
|
|
opened={configModalOpened}
|
|
onClose={handleCloseConfigModal}
|
|
title="Create New Configuration"
|
|
size="sm"
|
|
>
|
|
<Stack gap="md">
|
|
<TextInput
|
|
label="Name"
|
|
placeholder="Enter a name for the new configuration"
|
|
value={newConfigName}
|
|
onChange={(event) => setNewConfigName(event.currentTarget.value)}
|
|
data-autofocus
|
|
error={
|
|
configNameExists
|
|
? 'A configuration with this name already exists'
|
|
: undefined
|
|
}
|
|
/>
|
|
<Group justify="flex-end" gap="sm">
|
|
<Button variant="outline" onClick={handleCloseConfigModal}>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
disabled={!trimmedConfigName || !!configNameExists}
|
|
onClick={handleConfigSubmit}
|
|
>
|
|
Create
|
|
</Button>
|
|
</Group>
|
|
</Stack>
|
|
</Modal>
|
|
</>
|
|
);
|
|
};
|