2024-08-27 17:03:16 -04:00
|
|
|
import { useRef, useState, useEffect } from 'react';
|
|
|
|
|
import { useSetRecoilState } from 'recoil';
|
|
|
|
|
import * as Tabs from '@radix-ui/react-tabs';
|
2025-05-01 14:40:39 -04:00
|
|
|
import { ArrowLeft, ChevronLeft, ChevronRight, RefreshCw, X } from 'lucide-react';
|
2025-01-23 18:19:04 -05:00
|
|
|
import type { SandpackPreviewRef, CodeEditorRef } from '@codesandbox/sandpack-react';
|
2024-08-27 17:03:16 -04:00
|
|
|
import useArtifacts from '~/hooks/Artifacts/useArtifacts';
|
2025-01-23 18:19:04 -05:00
|
|
|
import DownloadArtifact from './DownloadArtifact';
|
|
|
|
|
import { useEditorContext } from '~/Providers';
|
|
|
|
|
import useLocalize from '~/hooks/useLocalize';
|
|
|
|
|
import ArtifactTabs from './ArtifactTabs';
|
|
|
|
|
import { CopyCodeButton } from './Code';
|
2024-08-27 17:03:16 -04:00
|
|
|
import store from '~/store';
|
|
|
|
|
|
|
|
|
|
export default function Artifacts() {
|
2025-01-23 18:19:04 -05:00
|
|
|
const localize = useLocalize();
|
|
|
|
|
const { isMutating } = useEditorContext();
|
|
|
|
|
const editorRef = useRef<CodeEditorRef>();
|
2024-08-27 17:03:16 -04:00
|
|
|
const previewRef = useRef<SandpackPreviewRef>();
|
|
|
|
|
const [isVisible, setIsVisible] = useState(false);
|
2025-01-23 18:19:04 -05:00
|
|
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
2025-05-01 14:40:39 -04:00
|
|
|
const setArtifactsVisible = useSetRecoilState(store.artifactsVisibility);
|
2024-08-27 17:03:16 -04:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
setIsVisible(true);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
activeTab,
|
|
|
|
|
isMermaid,
|
|
|
|
|
setActiveTab,
|
|
|
|
|
currentIndex,
|
2025-01-23 18:19:04 -05:00
|
|
|
isSubmitting,
|
2024-08-27 17:03:16 -04:00
|
|
|
cycleArtifact,
|
|
|
|
|
currentArtifact,
|
|
|
|
|
orderedArtifactIds,
|
|
|
|
|
} = useArtifacts();
|
|
|
|
|
|
|
|
|
|
if (currentArtifact === null || currentArtifact === undefined) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleRefresh = () => {
|
|
|
|
|
setIsRefreshing(true);
|
|
|
|
|
const client = previewRef.current?.getClient();
|
|
|
|
|
if (client != null) {
|
|
|
|
|
client.dispatch({ type: 'refresh' });
|
|
|
|
|
}
|
|
|
|
|
setTimeout(() => setIsRefreshing(false), 750);
|
|
|
|
|
};
|
|
|
|
|
|
2025-05-01 14:40:39 -04:00
|
|
|
const closeArtifacts = () => {
|
|
|
|
|
setIsVisible(false);
|
|
|
|
|
setTimeout(() => setArtifactsVisible(false), 300);
|
|
|
|
|
};
|
|
|
|
|
|
2024-08-27 17:03:16 -04:00
|
|
|
return (
|
|
|
|
|
<Tabs.Root value={activeTab} onValueChange={setActiveTab} asChild>
|
|
|
|
|
{/* Main Parent */}
|
2025-01-23 18:19:04 -05:00
|
|
|
<div className="flex h-full w-full items-center justify-center">
|
2024-08-27 17:03:16 -04:00
|
|
|
{/* Main Container */}
|
|
|
|
|
<div
|
2025-05-01 14:40:39 -04:00
|
|
|
className={`flex h-full w-full flex-col overflow-hidden border border-border-medium bg-surface-primary text-xl text-text-primary shadow-xl transition-all duration-500 ease-in-out ${
|
|
|
|
|
isVisible ? 'scale-100 opacity-100 blur-0' : 'scale-105 opacity-0 blur-sm'
|
2024-08-27 17:03:16 -04:00
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
{/* Header */}
|
|
|
|
|
<div className="flex items-center justify-between border-b border-border-medium bg-surface-primary-alt p-2">
|
|
|
|
|
<div className="flex items-center">
|
2025-05-01 14:40:39 -04:00
|
|
|
<button className="mr-2 text-text-secondary" onClick={closeArtifacts}>
|
|
|
|
|
<ArrowLeft className="h-4 w-4" />
|
2024-08-27 17:03:16 -04:00
|
|
|
</button>
|
|
|
|
|
<h3 className="truncate text-sm text-text-primary">{currentArtifact.title}</h3>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
{/* Refresh button */}
|
|
|
|
|
{activeTab === 'preview' && (
|
|
|
|
|
<button
|
|
|
|
|
className={`mr-2 text-text-secondary transition-transform duration-500 ease-in-out ${
|
|
|
|
|
isRefreshing ? 'rotate-180' : ''
|
|
|
|
|
}`}
|
|
|
|
|
onClick={handleRefresh}
|
|
|
|
|
disabled={isRefreshing}
|
|
|
|
|
aria-label="Refresh"
|
|
|
|
|
>
|
|
|
|
|
<RefreshCw
|
|
|
|
|
size={16}
|
|
|
|
|
className={`transform ${isRefreshing ? 'animate-spin' : ''}`}
|
|
|
|
|
/>
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
2025-01-23 18:19:04 -05:00
|
|
|
{activeTab !== 'preview' && isMutating && (
|
|
|
|
|
<RefreshCw size={16} className="mr-2 animate-spin text-text-secondary" />
|
|
|
|
|
)}
|
|
|
|
|
{/* Tabs */}
|
2024-08-27 17:03:16 -04:00
|
|
|
<Tabs.List className="mr-2 inline-flex h-7 rounded-full border border-border-medium bg-surface-tertiary">
|
|
|
|
|
<Tabs.Trigger
|
|
|
|
|
value="preview"
|
2025-01-23 18:19:04 -05:00
|
|
|
disabled={isMutating}
|
2024-08-27 17:03:16 -04:00
|
|
|
className="border-0.5 flex items-center gap-1 rounded-full border-transparent py-1 pl-2.5 pr-2.5 text-xs font-medium text-text-secondary data-[state=active]:border-border-light data-[state=active]:bg-surface-primary-alt data-[state=active]:text-text-primary"
|
|
|
|
|
>
|
2025-01-23 18:19:04 -05:00
|
|
|
{localize('com_ui_preview')}
|
2024-08-27 17:03:16 -04:00
|
|
|
</Tabs.Trigger>
|
|
|
|
|
<Tabs.Trigger
|
|
|
|
|
value="code"
|
|
|
|
|
className="border-0.5 flex items-center gap-1 rounded-full border-transparent py-1 pl-2.5 pr-2.5 text-xs font-medium text-text-secondary data-[state=active]:border-border-light data-[state=active]:bg-surface-primary-alt data-[state=active]:text-text-primary"
|
|
|
|
|
>
|
2025-01-23 18:19:04 -05:00
|
|
|
{localize('com_ui_code')}
|
2024-08-27 17:03:16 -04:00
|
|
|
</Tabs.Trigger>
|
|
|
|
|
</Tabs.List>
|
2025-05-01 14:40:39 -04:00
|
|
|
<button className="text-text-secondary" onClick={closeArtifacts}>
|
|
|
|
|
<X className="h-4 w-4" />
|
2024-08-27 17:03:16 -04:00
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/* Content */}
|
2025-01-23 18:19:04 -05:00
|
|
|
<ArtifactTabs
|
|
|
|
|
isMermaid={isMermaid}
|
|
|
|
|
artifact={currentArtifact}
|
|
|
|
|
isSubmitting={isSubmitting}
|
|
|
|
|
editorRef={editorRef as React.MutableRefObject<CodeEditorRef>}
|
|
|
|
|
previewRef={previewRef as React.MutableRefObject<SandpackPreviewRef>}
|
|
|
|
|
/>
|
2024-08-27 17:03:16 -04:00
|
|
|
{/* Footer */}
|
|
|
|
|
<div className="flex items-center justify-between border-t border-border-medium bg-surface-primary-alt p-2 text-sm text-text-secondary">
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
<button onClick={() => cycleArtifact('prev')} className="mr-2 text-text-secondary">
|
2025-05-01 14:40:39 -04:00
|
|
|
<ChevronLeft className="h-4 w-4" />
|
2024-08-27 17:03:16 -04:00
|
|
|
</button>
|
|
|
|
|
<span className="text-xs">{`${currentIndex + 1} / ${
|
|
|
|
|
orderedArtifactIds.length
|
|
|
|
|
}`}</span>
|
|
|
|
|
<button onClick={() => cycleArtifact('next')} className="ml-2 text-text-secondary">
|
2025-05-01 14:40:39 -04:00
|
|
|
<ChevronRight className="h-4 w-4" />
|
2024-08-27 17:03:16 -04:00
|
|
|
</button>
|
|
|
|
|
</div>
|
2025-01-23 18:19:04 -05:00
|
|
|
<div className="flex items-center gap-2">
|
2024-08-27 17:03:16 -04:00
|
|
|
<CopyCodeButton content={currentArtifact.content ?? ''} />
|
|
|
|
|
{/* Download Button */}
|
2025-01-23 18:19:04 -05:00
|
|
|
<DownloadArtifact artifact={currentArtifact} />
|
2024-08-27 17:03:16 -04:00
|
|
|
{/* Publish button */}
|
|
|
|
|
{/* <button className="border-0.5 min-w-[4rem] whitespace-nowrap rounded-md border-border-medium bg-[radial-gradient(ellipse,_var(--tw-gradient-stops))] from-surface-active from-50% to-surface-active px-3 py-1 text-xs font-medium text-text-primary transition-colors hover:bg-surface-active hover:text-text-primary active:scale-[0.985] active:bg-surface-active">
|
|
|
|
|
Publish
|
|
|
|
|
</button> */}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Tabs.Root>
|
|
|
|
|
);
|
|
|
|
|
}
|