ERC: Save As

This commit is contained in:
Enrico Ros
2024-08-17 20:05:40 -07:00
parent 47b146aa38
commit af49ee72b6
4 changed files with 75 additions and 3 deletions
@@ -98,6 +98,32 @@ const GuessedMimeLookupTable: Record<string, GuessedMimeInfo> = {
'application/x-bzip2': { ext: ['bz2'], dt: 'other' },
};
const MdTitleToMimeLookupTable: Record<string, GuessedMimeType> = {
'typescript': 'text/x-typescript',
'ts': 'text/x-typescript',
'tsx': 'text/x-typescript',
'javascript': 'text/javascript',
'js': 'text/javascript',
'jsx': 'text/javascript',
'python': 'text/x-python',
'py': 'text/x-python',
'json': 'application/json',
'html': 'text/html',
'htm': 'text/html',
'css': 'text/css',
'md': 'text/markdown',
'markdown': 'text/markdown',
'csv': 'text/csv',
'tsv': 'text/csv',
'xml': 'text/xml',
'pdf': 'application/pdf',
'doc': 'application/msword',
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'xls': 'application/vnd.ms-excel',
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'ppt': 'application/vnd.ms-powerpoint',
'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
};
export function reverseLookupMimeType(fileExtension: string): GuessedMimeType | null {
for (const [mimeType, { ext }] of Object.entries(GuessedMimeLookupTable)) {
@@ -107,6 +133,15 @@ export function reverseLookupMimeType(fileExtension: string): GuessedMimeType |
return null;
}
export function reverseLookupMdTitle(mdTitle: string): { mimeType: GuessedMimeType, extension: string | null } | null {
const guessedMimeType = MdTitleToMimeLookupTable[mdTitle] || null;
if (guessedMimeType) {
const { ext } = GuessedMimeLookupTable[guessedMimeType];
return { mimeType: guessedMimeType, extension: (ext ? ext[0] : null) || null };
}
return null;
}
export function guessInputContentTypeFromMime(mimeType: GuessedMimeType): GuessedMimeContents {
return GuessedMimeLookupTable[mimeType]?.dt ?? 'plain';
}
@@ -198,6 +198,7 @@ export function EnhancedRenderCode(props: {
{contextMenuAnchor && (
<EnhancedRenderCodeMenu
anchor={contextMenuAnchor}
code={props.code} title={props.title}
onClose={handleCloseContextMenu}
isCollapsed={isCodeCollapsed}
onToggleCollapse={handleToggleCodeCollapse}
@@ -1,14 +1,17 @@
import * as React from 'react';
import { fileSave } from 'browser-fs-access';
import { useShallow } from 'zustand/react/shallow';
import { Box, ListDivider, ListItemDecorator, MenuItem } from '@mui/joy';
import CheckRoundedIcon from '@mui/icons-material/CheckRounded';
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import SaveAsIcon from '@mui/icons-material/SaveAs';
import { CloseableMenu } from '~/common/components/CloseableMenu';
import { ConfirmationModal } from '~/common/components/ConfirmationModal';
import { isLiveFileSupported } from '~/common/livefile/store-live-file';
import { reverseLookupMdTitle, reverseLookupMimeType } from '~/common/attachment-drafts/attachment.mimetypes';
import { useLabsDevMode, useUXLabsStore } from '~/common/state/store-ux-labs';
import { useOverlayComponents } from '~/common/layout/overlays/useOverlayComponents';
@@ -20,6 +23,8 @@ import { getCodeCollapseManager } from './codeCollapseManager';
*/
export function EnhancedRenderCodeMenu(props: {
anchor: HTMLElement,
title: string,
code: string,
onClose: () => void,
isCollapsed: boolean,
onToggleCollapse: () => void,
@@ -46,6 +51,33 @@ export function EnhancedRenderCodeMenu(props: {
getCodeCollapseManager().triggerCollapseAll(false);
}, []);
const handleSaveAs = React.useCallback(async () => {
// guess the mimetype from the markdown title
let mimeType = 'text/plain';
let extension = '';
const hasExtension = props.title.includes('.');
if (hasExtension) {
extension = props.title.split('.').pop()!;
mimeType = reverseLookupMimeType(extension) || 'text/plain';
} else {
const data = reverseLookupMdTitle(props.title);
if (data?.extension)
extension = data.extension;
if (data?.mimeType)
mimeType = data.mimeType;
}
// content to be saved
const blob = new Blob([props.code], { type: mimeType });
// save content
await fileSave(blob, {
fileName: props.title || undefined,
extensions: extension ? [`.${extension}`] : undefined,
mimeTypes: mimeType ? [mimeType] : undefined,
}).catch(() => null);
}, [props.code, props.title]);
const toggleEnhanceCodeBlocks = React.useCallback(() => {
// turn blocks on (may not even be called, ever)
if (!labsEnhanceCodeBlocks) {
@@ -69,6 +101,7 @@ export function EnhancedRenderCodeMenu(props: {
const liveFileSupported = isLiveFileSupported();
return (
<CloseableMenu
open={true} dense
@@ -89,7 +122,10 @@ export function EnhancedRenderCodeMenu(props: {
<ListDivider />
{/* TODO: add Download here */}
<MenuItem onClick={handleSaveAs}>
<ListItemDecorator><SaveAsIcon /></ListItemDecorator>
Save As ...
</MenuItem>
<MenuItem onClick={toggleEnhanceCodeLiveFile} disabled={!liveFileSupported}>
<ListItemDecorator>{(labsEnhanceCodeLiveFile && liveFileSupported) && <CheckRoundedIcon />}</ListItemDecorator>
+2 -2
View File
@@ -135,7 +135,7 @@ export async function downloadAllJsonV1B() {
fileName: `backup_chats_${window?.location?.hostname || 'all'}_${payload.conversations.length}_${prettyTimestampForFilenames(false)}.agi.json`,
// mimeTypes: ['application/json', 'application/big-agi'],
extensions: ['.json'],
});
}).catch(() => null);
}
/**
@@ -172,7 +172,7 @@ export async function downloadSingleChat(conversation: DConversation, format: 'j
await fileSave(blob, {
fileName: `conversation_${fileTitle}_${prettyTimestampForFilenames(false)}.agi${extension}`,
extensions: [extension],
});
}).catch(() => null);
}
/**