mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): use buttons instead of menu for preset import/export
This commit is contained in:
parent
7a3eaa8da9
commit
26bfbdec7f
@ -1701,14 +1701,16 @@
|
||||
"deleteImage": "Delete Image",
|
||||
"deleteTemplate": "Delete Template",
|
||||
"deleteTemplate2": "Are you sure you want to delete this template? This cannot be undone.",
|
||||
"downloadCsv": "Download CSV",
|
||||
"exportPromptTemplates": "Export My Prompt Templates (CSV)",
|
||||
"editTemplate": "Edit Template",
|
||||
"exportDownloaded": "Export Downloaded",
|
||||
"exportFailed": "Unable to generate and download CSV",
|
||||
"flatten": "Flatten selected template into current prompt",
|
||||
"importTemplates": "Import Prompt Templates",
|
||||
"importTemplatesDesc": "Accepted formats: CSV or JSON",
|
||||
"importTemplatesDesc2": "Accepted columns/keys: 'name', 'prompt' or 'positive_prompt', 'negative_prompt",
|
||||
"importTemplates": "Import Prompt Templates (CSV/JSON)",
|
||||
"acceptedColumnsKeys": "Accepted columns/keys:",
|
||||
"nameColumn": "'name'",
|
||||
"positivePromptColumn": "'prompt' or 'positive_prompt'",
|
||||
"negativePromptColumn": "'negative_prompt'",
|
||||
"insertPlaceholder": "Insert placeholder",
|
||||
"myTemplates": "My Templates",
|
||||
"name": "Name",
|
||||
|
@ -0,0 +1,30 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { $stylePresetModalState } from 'features/stylePresets/store/stylePresetModal';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiPlusBold } from 'react-icons/pi';
|
||||
|
||||
export const StylePresetCreateButton = () => {
|
||||
const handleClickAddNew = useCallback(() => {
|
||||
$stylePresetModalState.set({
|
||||
prefilledFormData: null,
|
||||
updatingStylePresetId: null,
|
||||
isModalOpen: true,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
icon={<PiPlusBold />}
|
||||
tooltip={t('stylePresets.createPromptTemplate')}
|
||||
aria-label={t('stylePresets.createPromptTemplate')}
|
||||
onClick={handleClickAddNew}
|
||||
size="md"
|
||||
variant="ghost"
|
||||
w={8}
|
||||
h={8}
|
||||
/>
|
||||
);
|
||||
};
|
@ -0,0 +1,68 @@
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { IconButton, spinAnimation } from '@invoke-ai/ui-library';
|
||||
import { EMPTY_ARRAY } from 'app/store/constants';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiDownloadSimpleBold, PiSpinner } from 'react-icons/pi';
|
||||
import { useLazyExportStylePresetsQuery, useListStylePresetsQuery } from 'services/api/endpoints/stylePresets';
|
||||
|
||||
const loadingStyles: SystemStyleObject = {
|
||||
svg: { animation: spinAnimation },
|
||||
};
|
||||
|
||||
export const StylePresetExportButton = () => {
|
||||
const [exportStylePresets, { isLoading }] = useLazyExportStylePresetsQuery();
|
||||
const { t } = useTranslation();
|
||||
const { presetCount } = useListStylePresetsQuery(undefined, {
|
||||
selectFromResult: ({ data }) => {
|
||||
const userPresets = data?.filter((preset) => preset.type === 'user') ?? EMPTY_ARRAY;
|
||||
return {
|
||||
presetCount: userPresets.length,
|
||||
};
|
||||
},
|
||||
});
|
||||
const handleClickDownloadCsv = useCallback(async () => {
|
||||
let blob;
|
||||
try {
|
||||
const response = await exportStylePresets().unwrap();
|
||||
blob = new Blob([response], { type: 'text/csv' });
|
||||
} catch (error) {
|
||||
toast({
|
||||
status: 'error',
|
||||
title: t('stylePresets.exportFailed'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (blob) {
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'data.csv';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
toast({
|
||||
status: 'success',
|
||||
title: t('stylePresets.exportDownloaded'),
|
||||
});
|
||||
}
|
||||
}, [exportStylePresets, t]);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
onClick={handleClickDownloadCsv}
|
||||
icon={!isLoading ? <PiDownloadSimpleBold /> : <PiSpinner />}
|
||||
tooltip={t('stylePresets.exportPromptTemplates')}
|
||||
aria-label={t('stylePresets.exportPromptTemplates')}
|
||||
size="md"
|
||||
variant="link"
|
||||
w={8}
|
||||
h={8}
|
||||
sx={isLoading ? loadingStyles : undefined}
|
||||
isDisabled={isLoading || presetCount === 0}
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,4 +1,5 @@
|
||||
import { Flex, MenuItem, Text } from '@invoke-ai/ui-library';
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { Flex, IconButton, ListItem, spinAnimation, Text, UnorderedList } from '@invoke-ai/ui-library';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { useCallback } from 'react';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
@ -6,7 +7,11 @@ import { useTranslation } from 'react-i18next';
|
||||
import { PiSpinner, PiUploadSimpleBold } from 'react-icons/pi';
|
||||
import { useImportStylePresetsMutation } from 'services/api/endpoints/stylePresets';
|
||||
|
||||
export const StylePresetImport = ({ onClose }: { onClose: () => void }) => {
|
||||
const loadingStyles: SystemStyleObject = {
|
||||
svg: { animation: spinAnimation },
|
||||
};
|
||||
|
||||
export const StylePresetImportButton = () => {
|
||||
const [importStylePresets, { isLoading }] = useImportStylePresetsMutation();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -30,12 +35,9 @@ export const StylePresetImport = ({ onClose }: { onClose: () => void }) => {
|
||||
title: t('toast.importFailed'),
|
||||
description: error ? `${error.data.detail}` : undefined,
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
onClose();
|
||||
});
|
||||
},
|
||||
[importStylePresets, t, onClose]
|
||||
[importStylePresets, t]
|
||||
);
|
||||
|
||||
const { getInputProps, getRootProps } = useDropzone({
|
||||
@ -46,17 +48,37 @@ export const StylePresetImport = ({ onClose }: { onClose: () => void }) => {
|
||||
});
|
||||
|
||||
return (
|
||||
<MenuItem icon={!isLoading ? <PiUploadSimpleBold /> : <PiSpinner />} alignItems="flex-start" {...getRootProps()}>
|
||||
<Flex flexDir="column">
|
||||
<Text>{t('stylePresets.importTemplates')}</Text>
|
||||
<Text maxW="200px" fontSize="xs" variant="subtext">
|
||||
{t('stylePresets.importTemplatesDesc')}
|
||||
</Text>
|
||||
<Text maxW="200px" fontSize="xs" variant="subtext">
|
||||
{t('stylePresets.importTemplatesDesc2')}
|
||||
</Text>
|
||||
</Flex>
|
||||
<>
|
||||
<IconButton
|
||||
icon={!isLoading ? <PiUploadSimpleBold /> : <PiSpinner />}
|
||||
tooltip={<TooltipContent />}
|
||||
aria-label={t('stylePresets.importTemplates')}
|
||||
size="md"
|
||||
variant="link"
|
||||
w={8}
|
||||
h={8}
|
||||
sx={isLoading ? loadingStyles : undefined}
|
||||
isDisabled={isLoading}
|
||||
{...getRootProps()}
|
||||
/>
|
||||
<input {...getInputProps()} />
|
||||
</MenuItem>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const TooltipContent = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Flex flexDir="column">
|
||||
<Text pb={1} fontWeight="semibold">
|
||||
{t('stylePresets.importTemplates')}
|
||||
</Text>
|
||||
<Text>{t('stylePresets.acceptedColumnsKeys')}</Text>
|
||||
<UnorderedList>
|
||||
<ListItem>{t('stylePresets.nameColumn')}</ListItem>
|
||||
<ListItem>{t('stylePresets.positivePromptColumn')}</ListItem>
|
||||
<ListItem>{t('stylePresets.negativePromptColumn')}</ListItem>
|
||||
</UnorderedList>
|
||||
</Flex>
|
||||
);
|
||||
};
|
@ -1,12 +1,14 @@
|
||||
import { Flex, Text } from '@invoke-ai/ui-library';
|
||||
import { EMPTY_ARRAY } from 'app/store/constants';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { StylePresetExportButton } from 'features/stylePresets/components/StylePresetExportButton';
|
||||
import { StylePresetImportButton } from 'features/stylePresets/components/StylePresetImportButton';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
||||
import { useListStylePresetsQuery } from 'services/api/endpoints/stylePresets';
|
||||
|
||||
import { StylePresetCreateButton } from './StylePresetCreateButton';
|
||||
import { StylePresetList } from './StylePresetList';
|
||||
import { StylePresetMenuActions } from './StylePresetMenuActions/StylePresetMenuActions';
|
||||
import StylePresetSearch from './StylePresetSearch';
|
||||
|
||||
export const StylePresetMenu = () => {
|
||||
@ -50,7 +52,9 @@ export const StylePresetMenu = () => {
|
||||
<Flex flexDir="column" gap={2} padding={3} layerStyle="second" borderRadius="base">
|
||||
<Flex alignItems="center" gap={2} w="full" justifyContent="space-between">
|
||||
<StylePresetSearch />
|
||||
<StylePresetMenuActions />
|
||||
<StylePresetCreateButton />
|
||||
<StylePresetImportButton />
|
||||
<StylePresetExportButton />
|
||||
</Flex>
|
||||
|
||||
{data.presets.length === 0 && data.defaultPresets.length === 0 && (
|
||||
|
@ -1,47 +0,0 @@
|
||||
import { MenuItem } from '@invoke-ai/ui-library';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiDownloadSimpleBold, PiSpinner } from 'react-icons/pi';
|
||||
import { useLazyExportStylePresetsQuery } from 'services/api/endpoints/stylePresets';
|
||||
|
||||
export const StylePresetExport = ({ onClose }: { onClose: () => void }) => {
|
||||
const [exportStylePresets, { isLoading }] = useLazyExportStylePresetsQuery();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleClickDownloadCsv = useCallback(async () => {
|
||||
let blob;
|
||||
try {
|
||||
const response = await exportStylePresets().unwrap();
|
||||
blob = new Blob([response], { type: 'text/csv' });
|
||||
} catch (error) {
|
||||
toast({
|
||||
status: 'error',
|
||||
title: t('stylePresets.exportFailed'),
|
||||
});
|
||||
} finally {
|
||||
onClose();
|
||||
}
|
||||
|
||||
if (blob) {
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'data.csv';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
toast({
|
||||
status: 'success',
|
||||
title: t('stylePresets.exportDownloaded'),
|
||||
});
|
||||
}
|
||||
}, [exportStylePresets, onClose, t]);
|
||||
|
||||
return (
|
||||
<MenuItem icon={!isLoading ? <PiDownloadSimpleBold /> : <PiSpinner />} onClickCapture={handleClickDownloadCsv}>
|
||||
{t('stylePresets.downloadCsv')}
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
@ -1,50 +0,0 @@
|
||||
import { Flex, IconButton, Menu, MenuButton, MenuList, useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { $stylePresetModalState } from 'features/stylePresets/store/stylePresetModal';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiDotsThreeOutlineFill, PiPlusBold } from 'react-icons/pi';
|
||||
|
||||
import { StylePresetExport } from './StylePresetExport';
|
||||
import { StylePresetImport } from './StylePresetImport';
|
||||
|
||||
export const StylePresetMenuActions = () => {
|
||||
const { isOpen, onClose, onToggle } = useDisclosure();
|
||||
const handleClickAddNew = useCallback(() => {
|
||||
$stylePresetModalState.set({
|
||||
prefilledFormData: null,
|
||||
updatingStylePresetId: null,
|
||||
isModalOpen: true,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
<IconButton
|
||||
icon={<PiPlusBold />}
|
||||
tooltip={t('stylePresets.createPromptTemplate')}
|
||||
aria-label={t('stylePresets.createPromptTemplate')}
|
||||
onClick={handleClickAddNew}
|
||||
size="md"
|
||||
variant="ghost"
|
||||
w={8}
|
||||
h={8}
|
||||
/>
|
||||
<Menu isOpen={isOpen}>
|
||||
<MenuButton
|
||||
variant="ghost"
|
||||
as={IconButton}
|
||||
aria-label={t('stylePresets.templateActions')}
|
||||
tooltip={t('stylePresets.templateActions')}
|
||||
icon={<PiDotsThreeOutlineFill />}
|
||||
onClick={onToggle}
|
||||
/>
|
||||
<MenuList>
|
||||
<StylePresetImport onClose={onClose} />
|
||||
<StylePresetExport onClose={onClose} />
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</Flex>
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue
Block a user