mirror of
https://github.com/stackblitz/bolt.new
synced 2025-06-26 18:17:50 +00:00
fix: ui change
small changes to the ui
This commit is contained in:
commit
77ebb76b54
@ -1,59 +1,18 @@
|
||||
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 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}
|
||||
|
||||
@ -40,7 +40,7 @@ export const IconButton = memo(
|
||||
return (
|
||||
<button
|
||||
className={classNames(
|
||||
'flex items-center p-1.5 text-bolt-elements-item-contentDefault hover:text-bolt-elements-item-contentActive rounded-md',
|
||||
'flex items-center justify-center p-1.5 text-bolt-elements-item-contentActive bg-bolt-elements-item-backgroundActive rounded-md hover:bg-bolt-elements-item-backgroundHover transition-colors',
|
||||
{
|
||||
[classNames('opacity-30', disabledClassName)]: disabled,
|
||||
},
|
||||
@ -56,22 +56,23 @@ export const IconButton = memo(
|
||||
onClick?.(event);
|
||||
}}
|
||||
>
|
||||
{children ? children : <div className={classNames(icon, getIconSize(size), iconClassName)}></div>}
|
||||
{children ? children : <div className={classNames(icon, getIconSize(size), 'scale-125', iconClassName)}></div>}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
function getIconSize(size: IconSize) {
|
||||
if (size === 'sm') {
|
||||
return 'text-sm';
|
||||
} else if (size === 'md') {
|
||||
return 'text-md';
|
||||
} else if (size === 'lg') {
|
||||
return 'text-lg';
|
||||
} else if (size === 'xl') {
|
||||
return 'text-xl';
|
||||
} else {
|
||||
return 'text-2xl';
|
||||
switch (size) {
|
||||
case 'sm':
|
||||
return 'text-sm';
|
||||
case 'md':
|
||||
return 'text-base';
|
||||
case 'lg':
|
||||
return 'text-lg';
|
||||
case 'xl':
|
||||
return 'text-xl';
|
||||
case 'xxl':
|
||||
return 'text-2xl';
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@ import { motion, type HTMLMotionProps, type Variants } from 'framer-motion';
|
||||
import { computed } from 'nanostores';
|
||||
import { memo, useCallback, useEffect } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import JSZip from 'jszip';
|
||||
import { saveAs } from 'file-saver';
|
||||
import {
|
||||
type OnChangeCallback as OnEditorChange,
|
||||
type OnScrollCallback as OnEditorScroll,
|
||||
@ -63,6 +65,36 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
|
||||
const files = useStore(workbenchStore.files);
|
||||
const selectedView = useStore(workbenchStore.currentView);
|
||||
|
||||
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');
|
||||
};
|
||||
|
||||
const setSelectedView = (view: WorkbenchViewType) => {
|
||||
workbenchStore.currentView.set(view);
|
||||
};
|
||||
@ -122,15 +154,24 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
|
||||
<Slider selected={selectedView} options={sliderOptions} setSelected={setSelectedView} />
|
||||
<div className="ml-auto" />
|
||||
{selectedView === 'code' && (
|
||||
<PanelHeaderButton
|
||||
className="mr-1 text-sm"
|
||||
onClick={() => {
|
||||
workbenchStore.toggleTerminal(!workbenchStore.showTerminal.get());
|
||||
}}
|
||||
>
|
||||
<div className="i-ph:terminal" />
|
||||
Toggle Terminal
|
||||
</PanelHeaderButton>
|
||||
<>
|
||||
<PanelHeaderButton
|
||||
className="mr-1 text-sm"
|
||||
onClick={downloadZip}
|
||||
>
|
||||
<div className="i-ph:download-bold" />
|
||||
Download
|
||||
</PanelHeaderButton>
|
||||
<PanelHeaderButton
|
||||
className="mr-1 text-sm"
|
||||
onClick={() => {
|
||||
workbenchStore.toggleTerminal(!workbenchStore.showTerminal.get());
|
||||
}}
|
||||
>
|
||||
<div className="i-ph:terminal" />
|
||||
Toggle Terminal
|
||||
</PanelHeaderButton>
|
||||
</>
|
||||
)}
|
||||
<IconButton
|
||||
icon="i-ph:x-circle"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user