Skip to content

Commit 91071a1

Browse files
authored
Merge pull request #1961 from Stijnus/BoltDIY_Fix_Export_Sync
feat: move export/sync buttons to workbench and standardize styling
2 parents 8d30017 + 5517f7d commit 91071a1

File tree

3 files changed

+83
-81
lines changed

3 files changed

+83
-81
lines changed

app/components/chat/chatExportAndImport/ExportChatButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { classNames } from '~/utils/classNames';
44

55
export const ExportChatButton = ({ exportChat }: { exportChat?: () => void }) => {
66
return (
7-
<div className="flex border border-bolt-elements-borderColor rounded-md overflow-hidden mr-2 text-sm">
7+
<div className="flex border border-bolt-elements-borderColor rounded-md overflow-hidden">
88
<DropdownMenu.Root>
99
<DropdownMenu.Trigger className="rounded-md items-center justify-center [&:is(:disabled,.disabled)]:cursor-not-allowed [&:is(:disabled,.disabled)]:opacity-60 px-3 py-1.5 text-xs bg-accent-500 text-white hover:text-bolt-elements-item-contentAccent [&:not(:disabled,.disabled)]:hover:bg-bolt-elements-button-primary-backgroundHover outline-accent-500 flex gap-1.7">
1010
Export

app/components/header/HeaderActionButtons.client.tsx

Lines changed: 3 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,21 @@
1+
import { useState } from 'react';
12
import { useStore } from '@nanostores/react';
23
import { workbenchStore } from '~/lib/stores/workbench';
3-
import { useState, useCallback } from 'react';
4-
import { streamingState } from '~/lib/stores/streaming';
5-
import { ExportChatButton } from '~/components/chat/chatExportAndImport/ExportChatButton';
6-
import { useChatHistory } from '~/lib/persistence';
74
import { DeployButton } from '~/components/deploy/DeployButton';
8-
import { toast } from 'react-toastify';
9-
import { classNames } from '~/utils/classNames';
10-
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
115

126
interface HeaderActionButtonsProps {
137
chatStarted: boolean;
148
}
159

16-
export function HeaderActionButtons({ chatStarted }: HeaderActionButtonsProps) {
10+
export function HeaderActionButtons({ chatStarted: _chatStarted }: HeaderActionButtonsProps) {
1711
const [activePreviewIndex] = useState(0);
1812
const previews = useStore(workbenchStore.previews);
1913
const activePreview = previews[activePreviewIndex];
20-
const isStreaming = useStore(streamingState);
21-
const { exportChat } = useChatHistory();
22-
const [isSyncing, setIsSyncing] = useState(false);
2314

24-
const shouldShowButtons = !isStreaming && activePreview;
25-
26-
const handleSyncFiles = useCallback(async () => {
27-
setIsSyncing(true);
28-
29-
try {
30-
const directoryHandle = await window.showDirectoryPicker();
31-
await workbenchStore.syncFiles(directoryHandle);
32-
toast.success('Files synced successfully');
33-
} catch (error) {
34-
console.error('Error syncing files:', error);
35-
toast.error('Failed to sync files');
36-
} finally {
37-
setIsSyncing(false);
38-
}
39-
}, []);
15+
const shouldShowButtons = activePreview;
4016

4117
return (
4218
<div className="flex items-center gap-1">
43-
{/* Export Chat Button */}
44-
{chatStarted && shouldShowButtons && <ExportChatButton exportChat={exportChat} />}
45-
46-
{/* Sync Button */}
47-
{shouldShowButtons && (
48-
<div className="flex border border-bolt-elements-borderColor rounded-md overflow-hidden text-sm">
49-
<DropdownMenu.Root>
50-
<DropdownMenu.Trigger
51-
disabled={isSyncing}
52-
className="rounded-md items-center justify-center [&:is(:disabled,.disabled)]:cursor-not-allowed [&:is(:disabled,.disabled)]:opacity-60 px-3 py-1.5 text-xs bg-accent-500 text-white hover:text-bolt-elements-item-contentAccent [&:not(:disabled,.disabled)]:hover:bg-bolt-elements-button-primary-backgroundHover outline-accent-500 flex gap-1.7"
53-
>
54-
{isSyncing ? 'Syncing...' : 'Sync'}
55-
<span className={classNames('i-ph:caret-down transition-transform')} />
56-
</DropdownMenu.Trigger>
57-
<DropdownMenu.Content
58-
className={classNames(
59-
'min-w-[240px] z-[250]',
60-
'bg-white dark:bg-[#141414]',
61-
'rounded-lg shadow-lg',
62-
'border border-gray-200/50 dark:border-gray-800/50',
63-
'animate-in fade-in-0 zoom-in-95',
64-
'py-1',
65-
)}
66-
sideOffset={5}
67-
align="end"
68-
>
69-
<DropdownMenu.Item
70-
className={classNames(
71-
'cursor-pointer flex items-center w-full px-4 py-2 text-sm text-bolt-elements-textPrimary hover:bg-bolt-elements-item-backgroundActive gap-2 rounded-md group relative',
72-
)}
73-
onClick={handleSyncFiles}
74-
disabled={isSyncing}
75-
>
76-
<div className="flex items-center gap-2">
77-
{isSyncing ? <div className="i-ph:spinner" /> : <div className="i-ph:cloud-arrow-down" />}
78-
<span>{isSyncing ? 'Syncing...' : 'Sync Files'}</span>
79-
</div>
80-
</DropdownMenu.Item>
81-
</DropdownMenu.Content>
82-
</DropdownMenu.Root>
83-
</div>
84-
)}
85-
8619
{/* Deploy Button */}
8720
{shouldShowButtons && <DeployButton />}
8821

app/components/workbench/Workbench.client.tsx

Lines changed: 79 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
type OnScrollCallback as OnEditorScroll,
1414
} from '~/components/editor/codemirror/CodeMirrorEditor';
1515
import { IconButton } from '~/components/ui/IconButton';
16-
import { PanelHeaderButton } from '~/components/ui/PanelHeaderButton';
1716
import { Slider, type SliderOptions } from '~/components/ui/Slider';
1817
import { workbenchStore, type WorkbenchViewType } from '~/lib/stores/workbench';
1918
import { classNames } from '~/utils/classNames';
@@ -26,6 +25,10 @@ import useViewport from '~/lib/hooks';
2625
import { usePreviewStore } from '~/lib/stores/previews';
2726
import { chatStore } from '~/lib/stores/chat';
2827
import type { ElementInfo } from './Inspector';
28+
import { ExportChatButton } from '~/components/chat/chatExportAndImport/ExportChatButton';
29+
import { useChatHistory } from '~/lib/persistence';
30+
import { streamingState } from '~/lib/stores/streaming';
31+
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
2932

3033
interface WorkspaceProps {
3134
chatStarted?: boolean;
@@ -302,6 +305,9 @@ export const Workbench = memo(
302305
const canHideChat = showWorkbench || !showChat;
303306

304307
const isSmallViewport = useViewport(1024);
308+
const streaming = useStore(streamingState);
309+
const { exportChat } = useChatHistory();
310+
const [isSyncing, setIsSyncing] = useState(false);
305311

306312
const setSelectedView = (view: WorkbenchViewType) => {
307313
workbenchStore.currentView.set(view);
@@ -351,6 +357,21 @@ export const Workbench = memo(
351357
workbenchStore.currentView.set('diff');
352358
}, []);
353359

360+
const handleSyncFiles = useCallback(async () => {
361+
setIsSyncing(true);
362+
363+
try {
364+
const directoryHandle = await window.showDirectoryPicker();
365+
await workbenchStore.syncFiles(directoryHandle);
366+
toast.success('Files synced successfully');
367+
} catch (error) {
368+
console.error('Error syncing files:', error);
369+
toast.error('Failed to sync files');
370+
} finally {
371+
setIsSyncing(false);
372+
}
373+
}, []);
374+
354375
return (
355376
chatStarted && (
356377
<motion.div
@@ -386,15 +407,63 @@ export const Workbench = memo(
386407
<div className="ml-auto" />
387408
{selectedView === 'code' && (
388409
<div className="flex overflow-y-auto">
389-
<PanelHeaderButton
390-
className="mr-1 text-sm"
391-
onClick={() => {
392-
workbenchStore.toggleTerminal(!workbenchStore.showTerminal.get());
393-
}}
394-
>
395-
<div className="i-ph:terminal" />
396-
Toggle Terminal
397-
</PanelHeaderButton>
410+
{/* Export Chat Button */}
411+
<ExportChatButton exportChat={exportChat} />
412+
413+
{/* Sync Button */}
414+
<div className="flex border border-bolt-elements-borderColor rounded-md overflow-hidden ml-1">
415+
<DropdownMenu.Root>
416+
<DropdownMenu.Trigger
417+
disabled={isSyncing || streaming}
418+
className="rounded-md items-center justify-center [&:is(:disabled,.disabled)]:cursor-not-allowed [&:is(:disabled,.disabled)]:opacity-60 px-3 py-1.5 text-xs bg-accent-500 text-white hover:text-bolt-elements-item-contentAccent [&:not(:disabled,.disabled)]:hover:bg-bolt-elements-button-primary-backgroundHover outline-accent-500 flex gap-1.7"
419+
>
420+
{isSyncing ? 'Syncing...' : 'Sync'}
421+
<span className={classNames('i-ph:caret-down transition-transform')} />
422+
</DropdownMenu.Trigger>
423+
<DropdownMenu.Content
424+
className={classNames(
425+
'min-w-[240px] z-[250]',
426+
'bg-white dark:bg-[#141414]',
427+
'rounded-lg shadow-lg',
428+
'border border-gray-200/50 dark:border-gray-800/50',
429+
'animate-in fade-in-0 zoom-in-95',
430+
'py-1',
431+
)}
432+
sideOffset={5}
433+
align="end"
434+
>
435+
<DropdownMenu.Item
436+
className={classNames(
437+
'cursor-pointer flex items-center w-full px-4 py-2 text-sm text-bolt-elements-textPrimary hover:bg-bolt-elements-item-backgroundActive gap-2 rounded-md group relative',
438+
)}
439+
onClick={handleSyncFiles}
440+
disabled={isSyncing}
441+
>
442+
<div className="flex items-center gap-2">
443+
{isSyncing ? (
444+
<div className="i-ph:spinner" />
445+
) : (
446+
<div className="i-ph:cloud-arrow-down" />
447+
)}
448+
<span>{isSyncing ? 'Syncing...' : 'Sync Files'}</span>
449+
</div>
450+
</DropdownMenu.Item>
451+
</DropdownMenu.Content>
452+
</DropdownMenu.Root>
453+
</div>
454+
455+
{/* Toggle Terminal Button */}
456+
<div className="flex border border-bolt-elements-borderColor rounded-md overflow-hidden ml-1">
457+
<button
458+
onClick={() => {
459+
workbenchStore.toggleTerminal(!workbenchStore.showTerminal.get());
460+
}}
461+
className="rounded-md items-center justify-center [&:is(:disabled,.disabled)]:cursor-not-allowed [&:is(:disabled,.disabled)]:opacity-60 px-3 py-1.5 text-xs bg-accent-500 text-white hover:text-bolt-elements-item-contentAccent [&:not(:disabled,.disabled)]:hover:bg-bolt-elements-button-primary-backgroundHover outline-accent-500 flex gap-1.7"
462+
>
463+
<div className="i-ph:terminal" />
464+
Toggle Terminal
465+
</button>
466+
</div>
398467
</div>
399468
)}
400469

0 commit comments

Comments
 (0)