From 99fc3c046acbc95a33953218b89750245ada2952 Mon Sep 17 00:00:00 2001 From: Tyler Hughes Date: Tue, 19 Nov 2024 01:01:26 -0600 Subject: [PATCH] feat: add download to zip button --- .../header/HeaderActionButtons.client.tsx | 45 ++++++++++++++- app/components/ui/IconButton.tsx | 2 +- package.json | 5 ++ pnpm-lock.yaml | 57 +++++++++++++++++++ 4 files changed, 106 insertions(+), 3 deletions(-) diff --git a/app/components/header/HeaderActionButtons.client.tsx b/app/components/header/HeaderActionButtons.client.tsx index 6f8e843..302f7e7 100644 --- a/app/components/header/HeaderActionButtons.client.tsx +++ b/app/components/header/HeaderActionButtons.client.tsx @@ -1,18 +1,59 @@ -import { useStore } from '@nanostores/react'; +import JSZip from 'jszip'; import { chatStore } from '~/lib/stores/chat'; import { workbenchStore } from '~/lib/stores/workbench'; import { classNames } from '~/utils/classNames'; +import { useStore } from '@nanostores/react'; +import type { FileMap } from '~/lib/stores/files'; +import { saveAs } from 'file-saver'; interface HeaderActionButtonsProps {} export function HeaderActionButtons({}: HeaderActionButtonsProps) { const showWorkbench = useStore(workbenchStore.showWorkbench); const { showChat } = useStore(chatStore); + const files = useStore(workbenchStore.files) as FileMap; const canHideChat = showWorkbench || !showChat; + const downloadZip = async () => { + const zip = new JSZip(); + + for (const [filePath, dirent] of Object.entries(files)) { + if (dirent?.type === 'file' && !dirent.isBinary) { + // remove '/home/project/' from the beginning of the path + const relativePath = filePath.replace(/^\/home\/project\//, ''); + + // split the path into segments + const pathSegments = relativePath.split('/'); + + // if there's more than one segment, we need to create folders + if (pathSegments.length > 1) { + let currentFolder = zip; + + for (let i = 0; i < pathSegments.length - 1; i++) { + currentFolder = currentFolder.folder(pathSegments[i])!; + } + currentFolder.file(pathSegments[pathSegments.length - 1], dirent.content); + } else { + // if there's only one segment, it's a file in the root + zip.file(relativePath, dirent.content); + } + } + } + + const content = await zip.generateAsync({ type: 'blob' }); + saveAs(content, 'project.zip'); + }; + return ( -
+
+