feat: add download to zip button

This commit is contained in:
Tyler Hughes
2024-11-19 01:01:26 -06:00
parent cecbc55380
commit 99fc3c046a
4 changed files with 106 additions and 3 deletions

View File

@@ -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 (
<div className="flex">
<div className="flex gap-2">
<button
onClick={downloadZip}
className="rounded-md items-center justify-center outline-accent-600 px-3 py-1.25 text-xs bg-[#232323] text-bolt-elements-button-secondary-text enabled:hover:bg-bolt-elements-button-secondary-backgroundHover flex gap-1.7"
>
<div className="i-ph:download-bold" />
<span>Download</span>
</button>
<div className="flex border border-bolt-elements-borderColor rounded-md overflow-hidden">
<Button
active={showChat}

View File

@@ -40,7 +40,7 @@ export const IconButton = memo(
return (
<button
className={classNames(
'flex items-center text-bolt-elements-item-contentDefault bg-transparent enabled:hover:text-bolt-elements-item-contentActive rounded-md p-1 enabled:hover:bg-bolt-elements-item-backgroundActive disabled:cursor-not-allowed',
'flex items-center p-1.5 text-bolt-elements-item-contentDefault hover:text-bolt-elements-item-contentActive rounded-md',
{
[classNames('opacity-30', disabledClassName)]: disabled,
},

View File

@@ -48,6 +48,7 @@
"@remix-run/cloudflare": "^2.10.2",
"@remix-run/cloudflare-pages": "^2.10.2",
"@remix-run/react": "^2.10.2",
"@types/file-saver": "^2.0.7",
"@uiw/codemirror-theme-vscode": "^4.23.0",
"@unocss/reset": "^0.61.0",
"@webcontainer/api": "1.3.0-internal.10",
@@ -55,12 +56,16 @@
"@xterm/addon-web-links": "^0.11.0",
"@xterm/xterm": "^5.5.0",
"ai": "^3.3.4",
"classnames": "^2.5.1",
"date-fns": "^3.6.0",
"diff": "^5.2.0",
"fflate": "^0.8.2",
"file-saver": "^2.0.5",
"framer-motion": "^11.2.12",
"isbot": "^4.1.0",
"istextorbinary": "^9.5.0",
"jose": "^5.6.3",
"jszip": "^3.10.1",
"nanostores": "^0.10.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",

57
pnpm-lock.yaml generated
View File

@@ -86,6 +86,9 @@ importers:
'@remix-run/react':
specifier: ^2.10.2
version: 2.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.2)
'@types/file-saver':
specifier: ^2.0.7
version: 2.0.7
'@uiw/codemirror-theme-vscode':
specifier: ^4.23.0
version: 4.23.0(@codemirror/language@6.10.2)(@codemirror/state@6.4.1)(@codemirror/view@6.28.4)
@@ -107,12 +110,21 @@ importers:
ai:
specifier: ^3.3.4
version: 3.3.4(react@18.3.1)(sswr@2.1.0(svelte@4.2.18))(svelte@4.2.18)(vue@3.4.30(typescript@5.5.2))(zod@3.23.8)
classnames:
specifier: ^2.5.1
version: 2.5.1
date-fns:
specifier: ^3.6.0
version: 3.6.0
diff:
specifier: ^5.2.0
version: 5.2.0
fflate:
specifier: ^0.8.2
version: 0.8.2
file-saver:
specifier: ^2.0.5
version: 2.0.5
framer-motion:
specifier: ^11.2.12
version: 11.2.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -125,6 +137,9 @@ importers:
jose:
specifier: ^5.6.3
version: 5.6.3
jszip:
specifier: ^3.10.1
version: 3.10.1
nanostores:
specifier: ^0.10.3
version: 0.10.3
@@ -1702,6 +1717,9 @@ packages:
'@types/estree@1.0.5':
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
'@types/file-saver@2.0.7':
resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==}
'@types/hast@2.3.10':
resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==}
@@ -2275,6 +2293,9 @@ packages:
cipher-base@1.0.4:
resolution: {integrity: sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==}
classnames@2.5.1:
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
clean-stack@2.2.0:
resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
engines: {node: '>=6'}
@@ -2803,10 +2824,16 @@ packages:
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
engines: {node: ^12.20 || >= 14.13}
fflate@0.8.2:
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
file-entry-cache@8.0.0:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
file-saver@2.0.5:
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
@@ -3084,6 +3111,9 @@ packages:
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
engines: {node: '>= 4'}
immediate@3.0.6:
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
immutable@4.3.6:
resolution: {integrity: sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==}
@@ -3299,6 +3329,9 @@ packages:
jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
jszip@3.10.1:
resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
@@ -3313,6 +3346,9 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
lie@3.3.0:
resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==}
lilconfig@3.1.2:
resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==}
engines: {node: '>=14'}
@@ -6709,6 +6745,8 @@ snapshots:
'@types/estree@1.0.5': {}
'@types/file-saver@2.0.7': {}
'@types/hast@2.3.10':
dependencies:
'@types/unist': 2.0.10
@@ -7504,6 +7542,8 @@ snapshots:
inherits: 2.0.4
safe-buffer: 5.2.1
classnames@2.5.1: {}
clean-stack@2.2.0: {}
cli-cursor@3.1.0:
@@ -8130,10 +8170,14 @@ snapshots:
node-domexception: 1.0.0
web-streams-polyfill: 3.3.3
fflate@0.8.2: {}
file-entry-cache@8.0.0:
dependencies:
flat-cache: 4.0.1
file-saver@2.0.5: {}
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
@@ -8472,6 +8516,8 @@ snapshots:
ignore@5.3.1: {}
immediate@3.0.6: {}
immutable@4.3.6:
optional: true
@@ -8640,6 +8686,13 @@ snapshots:
optionalDependencies:
graceful-fs: 4.2.11
jszip@3.10.1:
dependencies:
lie: 3.3.0
pako: 1.0.11
readable-stream: 2.3.8
setimmediate: 1.0.5
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
@@ -8653,6 +8706,10 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
lie@3.3.0:
dependencies:
immediate: 3.0.6
lilconfig@3.1.2: {}
loader-utils@3.3.1: {}