mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
Add download app button (#111)
This commit is contained in:
parent
46b7b58fd5
commit
194d0336f4
57
app/components/header/DownloadButton.tsx
Normal file
57
app/components/header/DownloadButton.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import ReactModal from 'react-modal';
|
||||
import { toast } from 'react-toastify';
|
||||
import { workbenchStore } from '~/lib/stores/workbench';
|
||||
import { downloadRepository } from '~/lib/replay/Deploy';
|
||||
|
||||
ReactModal.setAppElement('#root');
|
||||
|
||||
// Component for downloading an app's contents to disk.
|
||||
|
||||
export function DownloadButton() {
|
||||
const handleDownload = async () => {
|
||||
const repositoryId = workbenchStore.repositoryId.get();
|
||||
if (!repositoryId) {
|
||||
toast.error('No repository ID found');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const repositoryContents = await downloadRepository(repositoryId);
|
||||
|
||||
// Convert base64 to blob
|
||||
const byteCharacters = atob(repositoryContents);
|
||||
const byteNumbers = new Array(byteCharacters.length);
|
||||
for (let i = 0; i < byteCharacters.length; i++) {
|
||||
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
||||
}
|
||||
const byteArray = new Uint8Array(byteNumbers);
|
||||
const blob = new Blob([byteArray], { type: 'application/zip' });
|
||||
|
||||
// Create download link and trigger save dialog
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `repository-${repositoryId}.zip`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
toast.success('Repository downloaded successfully');
|
||||
} catch (error) {
|
||||
console.error('Error downloading repository:', error);
|
||||
toast.error('Failed to download repository');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className="flex gap-2 bg-bolt-elements-sidebar-buttonBackgroundDefault text-bolt-elements-sidebar-buttonText hover:bg-bolt-elements-sidebar-buttonBackgroundHover rounded-md p-2 transition-theme"
|
||||
onClick={handleDownload}
|
||||
>
|
||||
<div className="i-ph:download-fill text-[1.3em]" />
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
}
|
@ -8,6 +8,7 @@ import { Feedback } from './Feedback';
|
||||
import { Suspense } from 'react';
|
||||
import { ClientAuth } from '~/components/auth/ClientAuth';
|
||||
import { DeployChatButton } from './DeployChatButton';
|
||||
import { DownloadButton } from './DownloadButton';
|
||||
|
||||
export function Header() {
|
||||
const chatStarted = useStore(chatStore.started);
|
||||
@ -43,9 +44,14 @@ export function Header() {
|
||||
)}
|
||||
|
||||
{chatStarted && (
|
||||
<span className="flex-1 px-4 truncate text-center text-bolt-elements-textPrimary">
|
||||
<ClientOnly>{() => <DeployChatButton />}</ClientOnly>
|
||||
</span>
|
||||
<>
|
||||
<span className="flex-1 px-4 truncate text-center text-bolt-elements-textPrimary">
|
||||
<ClientOnly>{() => <DeployChatButton />}</ClientOnly>
|
||||
</span>
|
||||
<span className="flex-1 px-4 truncate text-center text-bolt-elements-textPrimary">
|
||||
<ClientOnly>{() => <DownloadButton />}</ClientOnly>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
|
@ -65,3 +65,14 @@ export async function deployRepository(repositoryId: string, settings: DeploySet
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function downloadRepository(repositoryId: string): Promise<string> {
|
||||
const { repositoryContents } = (await sendCommandDedicatedClient({
|
||||
method: 'Nut.getRepository',
|
||||
params: {
|
||||
repositoryId,
|
||||
},
|
||||
})) as { repositoryContents: string };
|
||||
|
||||
return repositoryContents;
|
||||
}
|
||||
|
@ -15,6 +15,6 @@ export async function updateDevelopmentServer(repositoryId: string | undefined)
|
||||
console.log('UpdateDevelopmentServer', new Date().toISOString(), repositoryURL);
|
||||
|
||||
workbenchStore.showWorkbench.set(repositoryURL !== undefined);
|
||||
workbenchStore.repositoryId.set(repositoryURL);
|
||||
workbenchStore.repositoryId.set(repositoryId);
|
||||
workbenchStore.previewURL.set(repositoryURL);
|
||||
}
|
||||
|
@ -1,61 +0,0 @@
|
||||
import type { Message } from '~/lib/persistence/message';
|
||||
import { generateId } from './fileUtils';
|
||||
import JSZip from 'jszip';
|
||||
|
||||
interface FileArtifact {
|
||||
content: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export async function getFileRepositoryContents(files: File[]): Promise<string> {
|
||||
const artifacts: FileArtifact[] = await Promise.all(
|
||||
files.map(async (file) => {
|
||||
return new Promise<FileArtifact>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = () => {
|
||||
const content = reader.result as string;
|
||||
const relativePath = file.webkitRelativePath.split('/').slice(1).join('/');
|
||||
resolve({
|
||||
content,
|
||||
path: relativePath,
|
||||
});
|
||||
};
|
||||
reader.onerror = reject;
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
const zip = new JSZip();
|
||||
|
||||
for (const { path, content } of artifacts) {
|
||||
zip.file(path, content);
|
||||
}
|
||||
|
||||
return await zip.generateAsync({ type: 'base64' });
|
||||
}
|
||||
|
||||
// TODO: Common up with createMessagesForRepository.
|
||||
export function createChatFromFolder(folderName: string, repositoryId: string): Message[] {
|
||||
const filesContent = `I've imported the contents of the "${folderName}" folder.`;
|
||||
|
||||
const userMessage: Message = {
|
||||
role: 'user',
|
||||
id: generateId(),
|
||||
content: `Import the "${folderName}" folder`,
|
||||
type: 'text',
|
||||
};
|
||||
|
||||
const filesMessage: Message = {
|
||||
role: 'assistant',
|
||||
content: filesContent,
|
||||
id: generateId(),
|
||||
repositoryId,
|
||||
type: 'text',
|
||||
};
|
||||
|
||||
const messages = [userMessage, filesMessage];
|
||||
|
||||
return messages;
|
||||
}
|
Loading…
Reference in New Issue
Block a user