diff --git a/app/components/header/DownloadButton.tsx b/app/components/header/DownloadButton.tsx new file mode 100644 index 00000000..a2bc727a --- /dev/null +++ b/app/components/header/DownloadButton.tsx @@ -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 ( + <> + + + ); +} diff --git a/app/components/header/Header.tsx b/app/components/header/Header.tsx index 33e15962..12201392 100644 --- a/app/components/header/Header.tsx +++ b/app/components/header/Header.tsx @@ -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 && ( - - {() => } - + <> + + {() => } + + + {() => } + + )}
diff --git a/app/lib/replay/Deploy.ts b/app/lib/replay/Deploy.ts index c6fac4f2..ecc0e17f 100644 --- a/app/lib/replay/Deploy.ts +++ b/app/lib/replay/Deploy.ts @@ -65,3 +65,14 @@ export async function deployRepository(repositoryId: string, settings: DeploySet return result; } + +export async function downloadRepository(repositoryId: string): Promise { + const { repositoryContents } = (await sendCommandDedicatedClient({ + method: 'Nut.getRepository', + params: { + repositoryId, + }, + })) as { repositoryContents: string }; + + return repositoryContents; +} diff --git a/app/lib/replay/DevelopmentServer.ts b/app/lib/replay/DevelopmentServer.ts index e074997e..65ccc63c 100644 --- a/app/lib/replay/DevelopmentServer.ts +++ b/app/lib/replay/DevelopmentServer.ts @@ -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); } diff --git a/app/utils/folderImport.ts b/app/utils/folderImport.ts deleted file mode 100644 index 111280f5..00000000 --- a/app/utils/folderImport.ts +++ /dev/null @@ -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 { - const artifacts: FileArtifact[] = await Promise.all( - files.map(async (file) => { - return new Promise((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; -}