|
| 1 | +import React from 'react' |
| 2 | +import { CodeBlock } from '~/components/Markdown' |
| 3 | +import { FileExplorer } from './FileExplorer' |
| 4 | +import { InteractiveSandbox } from './InteractiveSandbox' |
| 5 | +import { CodeExplorerTopBar } from './CodeExplorerTopBar' |
| 6 | +import type { GitHubFileNode } from '~/utils/documents.server' |
| 7 | +import type { Library } from '~/libraries' |
| 8 | + |
| 9 | +function overrideExtension(ext: string | undefined) { |
| 10 | + if (!ext) return 'txt' |
| 11 | + |
| 12 | + // Override some extensions |
| 13 | + if (['cts', 'mts'].includes(ext)) return 'ts' |
| 14 | + if (['cjs', 'mjs'].includes(ext)) return 'js' |
| 15 | + if (['prettierrc', 'babelrc', 'webmanifest'].includes(ext)) return 'json' |
| 16 | + if (['env', 'example'].includes(ext)) return 'sh' |
| 17 | + if ( |
| 18 | + [ |
| 19 | + 'gitignore', |
| 20 | + 'prettierignore', |
| 21 | + 'log', |
| 22 | + 'gitattributes', |
| 23 | + 'editorconfig', |
| 24 | + 'lock', |
| 25 | + 'opts', |
| 26 | + 'Dockerfile', |
| 27 | + 'dockerignore', |
| 28 | + 'npmrc', |
| 29 | + 'nvmrc', |
| 30 | + ].includes(ext) |
| 31 | + ) |
| 32 | + return 'txt' |
| 33 | + |
| 34 | + return ext |
| 35 | +} |
| 36 | + |
| 37 | +interface CodeExplorerProps { |
| 38 | + activeTab: 'code' | 'sandbox' |
| 39 | + codeSandboxUrl: string |
| 40 | + currentCode: string |
| 41 | + currentPath: string |
| 42 | + examplePath: string |
| 43 | + githubContents: GitHubFileNode[] | undefined |
| 44 | + library: Library |
| 45 | + prefetchFileContent: (path: string) => void |
| 46 | + setActiveTab: (tab: 'code' | 'sandbox') => void |
| 47 | + setCurrentPath: (path: string) => void |
| 48 | + stackBlitzUrl: string |
| 49 | +} |
| 50 | + |
| 51 | +export function CodeExplorer({ |
| 52 | + activeTab, |
| 53 | + codeSandboxUrl, |
| 54 | + currentCode, |
| 55 | + currentPath, |
| 56 | + examplePath, |
| 57 | + githubContents, |
| 58 | + library, |
| 59 | + prefetchFileContent, |
| 60 | + setActiveTab, |
| 61 | + setCurrentPath, |
| 62 | + stackBlitzUrl, |
| 63 | +}: CodeExplorerProps) { |
| 64 | + const [isFullScreen, setIsFullScreen] = React.useState(false) |
| 65 | + const [isSidebarOpen, setIsSidebarOpen] = React.useState(true) |
| 66 | + |
| 67 | + // Add escape key handler |
| 68 | + React.useEffect(() => { |
| 69 | + const handleEsc = (e: KeyboardEvent) => { |
| 70 | + if (e.key === 'Escape' && isFullScreen) { |
| 71 | + setIsFullScreen(false) |
| 72 | + } |
| 73 | + } |
| 74 | + window.addEventListener('keydown', handleEsc) |
| 75 | + return () => window.removeEventListener('keydown', handleEsc) |
| 76 | + }, [isFullScreen]) |
| 77 | + |
| 78 | + // Add sidebar close handler |
| 79 | + React.useEffect(() => { |
| 80 | + const handleCloseSidebar = () => { |
| 81 | + setIsSidebarOpen(false) |
| 82 | + } |
| 83 | + window.addEventListener('closeSidebar', handleCloseSidebar) |
| 84 | + return () => window.removeEventListener('closeSidebar', handleCloseSidebar) |
| 85 | + }, []) |
| 86 | + |
| 87 | + return ( |
| 88 | + <div |
| 89 | + className={`flex flex-col min-h-[60dvh] sm:min-h-[80dvh] border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden ${ |
| 90 | + isFullScreen ? 'fixed inset-0 z-50 bg-white dark:bg-gray-900 p-4' : '' |
| 91 | + }`} |
| 92 | + > |
| 93 | + <CodeExplorerTopBar |
| 94 | + activeTab={activeTab} |
| 95 | + setActiveTab={setActiveTab} |
| 96 | + isFullScreen={isFullScreen} |
| 97 | + setIsFullScreen={setIsFullScreen} |
| 98 | + isSidebarOpen={isSidebarOpen} |
| 99 | + setIsSidebarOpen={setIsSidebarOpen} |
| 100 | + /> |
| 101 | + |
| 102 | + <div className="relative flex-1"> |
| 103 | + <div |
| 104 | + className={`absolute inset-0 flex ${ |
| 105 | + activeTab === 'code' ? '' : 'hidden' |
| 106 | + }`} |
| 107 | + > |
| 108 | + <FileExplorer |
| 109 | + currentPath={currentPath} |
| 110 | + githubContents={githubContents} |
| 111 | + isSidebarOpen={isSidebarOpen} |
| 112 | + libraryColor={library.bgStyle} |
| 113 | + prefetchFileContent={prefetchFileContent} |
| 114 | + setCurrentPath={setCurrentPath} |
| 115 | + /> |
| 116 | + <div className="flex-1 overflow-auto relative"> |
| 117 | + <CodeBlock |
| 118 | + isEmbedded |
| 119 | + className={`h-full ${ |
| 120 | + isFullScreen ? 'max-h-[90dvh]' : 'max-h-[80dvh]' |
| 121 | + }`} |
| 122 | + > |
| 123 | + <code |
| 124 | + className={`language-${overrideExtension( |
| 125 | + currentPath.split('.').pop() |
| 126 | + )}`} |
| 127 | + > |
| 128 | + {currentCode} |
| 129 | + </code> |
| 130 | + </CodeBlock> |
| 131 | + </div> |
| 132 | + </div> |
| 133 | + <InteractiveSandbox |
| 134 | + isActive={activeTab === 'sandbox'} |
| 135 | + codeSandboxUrl={codeSandboxUrl} |
| 136 | + stackBlitzUrl={stackBlitzUrl} |
| 137 | + examplePath={examplePath} |
| 138 | + libraryName={library.name} |
| 139 | + embedEditor={library.embedEditor || 'stackblitz'} |
| 140 | + /> |
| 141 | + </div> |
| 142 | + </div> |
| 143 | + ) |
| 144 | +} |
0 commit comments