Spaces:
Sleeping
Sleeping
Download frontend/src/components/Sidebar.tsx from Sid-WC121/visual-notebook: direct link, hf CLI and curl.
- Browser
- Download file 9.63 kB
-
https://huggingface.co/spaces/Sid-WC121/visual-notebook/resolve/main/frontend/src/components/Sidebar.tsx
- Command line
-
hf download hf://spaces/Sid-WC121/visual-notebook/frontend/src/components/Sidebar.tsx
-
curl -L -o Sidebar.tsx https://huggingface.co/spaces/Sid-WC121/visual-notebook/resolve/main/frontend/src/components/Sidebar.tsx
9.63 kB
| import React from 'react'; | |
| import { useUIStore, type SidebarTab } from '../store/ui'; | |
| import { useSessionStore } from '../store/session'; | |
| import { SchemaView } from './SchemaView'; | |
| import { ManipulationPanel } from './ManipulationPanel'; | |
| import { VisualizationPanel } from './VisualizationPanel'; | |
| import { useExport, useExportNotebook } from '../api/hooks'; | |
| const TABS: { key: SidebarTab; label: string; icon: React.ReactNode }[] = [ | |
| { | |
| key: 'data', | |
| label: 'Data', | |
| icon: ( | |
| <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 10h18M3 14h18" /> | |
| </svg> | |
| ), | |
| }, | |
| { | |
| key: 'transform', | |
| label: 'Transform', | |
| icon: ( | |
| <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /> | |
| </svg> | |
| ), | |
| }, | |
| { | |
| key: 'visualize', | |
| label: 'Visualize', | |
| icon: ( | |
| <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6m6 0V9a2 2 0 012-2h2a2 2 0 012 2v10m6 0v-4a2 2 0 00-2-2h-2a2 2 0 00-2 2v4" /> | |
| </svg> | |
| ), | |
| }, | |
| { | |
| key: 'export', | |
| label: 'Export', | |
| icon: ( | |
| <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> | |
| </svg> | |
| ), | |
| }, | |
| ]; | |
| function ExportTab() { | |
| const { download: downloadCSV, loading: csvLoading } = useExport(); | |
| const { download: downloadNotebook, loading: nbLoading } = useExportNotebook(); | |
| const { columns, rowCount, tableName } = useSessionStore(); | |
| return ( | |
| <div className="p-4 space-y-4"> | |
| {/* Jupyter Notebook Export - PROMINENT */} | |
| <div> | |
| <h4 className="text-sm font-semibold text-gray-900 mb-3">Export</h4> | |
| <div className="bg-gradient-to-br from-brand-50 to-brand-100/50 border border-brand-200 rounded-xl p-4"> | |
| <div className="flex items-center gap-2.5 mb-2"> | |
| <div className="w-8 h-8 bg-brand-600 rounded-lg flex items-center justify-center flex-shrink-0"> | |
| <svg className="w-4 h-4 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" /> | |
| </svg> | |
| </div> | |
| <div> | |
| <span className="text-sm font-semibold text-gray-900">Jupyter Notebook</span> | |
| <span className="ml-2 text-[10px] bg-brand-200 text-brand-800 px-1.5 py-0.5 rounded-full font-medium">Recommended</span> | |
| </div> | |
| </div> | |
| <p className="text-xs text-gray-600 mb-3 leading-relaxed"> | |
| Export as <span className="font-mono bg-white/60 px-1 rounded">.ipynb</span> with clean Python code — only pandas and the relevant operations, ready to run. | |
| </p> | |
| <button | |
| onClick={() => downloadNotebook()} | |
| disabled={nbLoading} | |
| className="w-full px-4 py-2.5 bg-brand-600 text-white rounded-lg hover:bg-brand-700 transition-colors font-medium text-sm shadow-sm disabled:opacity-50" | |
| > | |
| {nbLoading ? ( | |
| <span className="flex items-center justify-center gap-2"> | |
| <span className="animate-spin w-4 h-4 border-2 border-white border-t-transparent rounded-full" /> | |
| Generating... | |
| </span> | |
| ) : ( | |
| <span className="flex items-center justify-center gap-2"> | |
| <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /> | |
| </svg> | |
| Export Notebook (.ipynb) | |
| </span> | |
| )} | |
| </button> | |
| </div> | |
| </div> | |
| {/* CSV Export */} | |
| <div> | |
| <div className="bg-gray-50 rounded-lg p-3"> | |
| <div className="flex items-center gap-2 mb-1"> | |
| <svg className="w-4 h-4 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> | |
| </svg> | |
| <span className="text-sm font-medium text-gray-700">CSV File</span> | |
| </div> | |
| <p className="text-xs text-gray-500 mb-2"> | |
| Raw data export ({rowCount.toLocaleString()} rows, {columns.length} columns) | |
| </p> | |
| <button | |
| onClick={() => downloadCSV()} | |
| disabled={csvLoading} | |
| className="btn-secondary w-full text-xs" | |
| > | |
| {csvLoading ? 'Exporting...' : 'Export CSV'} | |
| </button> | |
| </div> | |
| </div> | |
| {/* Data Summary */} | |
| <div> | |
| <h4 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">Summary</h4> | |
| <div className="bg-gray-50 rounded-lg p-3 space-y-1.5 text-xs text-gray-600"> | |
| <div className="flex justify-between"> | |
| <span>Table</span> | |
| <span className="font-medium text-gray-900 truncate ml-2 max-w-[120px]" title={tableName || '—'}>{tableName || '—'}</span> | |
| </div> | |
| <div className="flex justify-between"> | |
| <span>Rows</span> | |
| <span className="font-medium text-gray-900">{rowCount.toLocaleString()}</span> | |
| </div> | |
| <div className="flex justify-between"> | |
| <span>Columns</span> | |
| <span className="font-medium text-gray-900">{columns.length}</span> | |
| </div> | |
| <div className="flex justify-between"> | |
| <span>Numeric</span> | |
| <span className="font-medium text-gray-900">{columns.filter(c => c.column_type === 'numeric').length}</span> | |
| </div> | |
| <div className="flex justify-between"> | |
| <span>Categorical</span> | |
| <span className="font-medium text-gray-900">{columns.filter(c => c.column_type === 'categorical').length}</span> | |
| </div> | |
| <div className="flex justify-between"> | |
| <span>Datetime</span> | |
| <span className="font-medium text-gray-900">{columns.filter(c => c.column_type === 'datetime').length}</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| export function Sidebar() { | |
| const { activeSidebarTab, setActiveSidebarTab } = useUIStore(); | |
| const { isLoaded } = useSessionStore(); | |
| if (!isLoaded) return null; | |
| return ( | |
| <div className="w-80 flex-shrink-0 bg-white border-r border-gray-200 flex flex-col h-full"> | |
| {/* Tab bar */} | |
| <div className="flex border-b border-gray-200 flex-shrink-0"> | |
| {TABS.map((tab) => ( | |
| <button | |
| key={tab.key} | |
| onClick={() => setActiveSidebarTab(tab.key)} | |
| className={`sidebar-tab flex flex-col items-center gap-0.5 py-2.5 ${ | |
| activeSidebarTab === tab.key ? 'sidebar-tab-active' : 'sidebar-tab-inactive' | |
| }`} | |
| title={tab.label} | |
| > | |
| {tab.icon} | |
| <span className="text-[10px]">{tab.label}</span> | |
| </button> | |
| ))} | |
| </div> | |
| {/* Tab content */} | |
| <div className="flex-1 sidebar-scroll"> | |
| {activeSidebarTab === 'data' && <DataTab />} | |
| {activeSidebarTab === 'transform' && <ManipulationPanel />} | |
| {activeSidebarTab === 'visualize' && <VisualizationPanel />} | |
| {activeSidebarTab === 'export' && <ExportTab />} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| function DataTab() { | |
| const { columns, rowCount } = useSessionStore(); | |
| const numericCount = columns.filter(c => c.column_type === 'numeric').length; | |
| const categoricalCount = columns.filter(c => c.column_type === 'categorical').length; | |
| const datetimeCount = columns.filter(c => c.column_type === 'datetime').length; | |
| return ( | |
| <div className="p-4"> | |
| <div className="flex items-center justify-between mb-3"> | |
| <h4 className="text-sm font-semibold text-gray-900">Schema</h4> | |
| <span className="text-xs text-gray-500">{rowCount.toLocaleString()} rows</span> | |
| </div> | |
| {/* Column type summary */} | |
| <div className="flex gap-2 mb-3 flex-wrap"> | |
| {numericCount > 0 && ( | |
| <span className="inline-flex items-center gap-1 text-[10px] px-2 py-0.5 bg-blue-50 text-blue-700 rounded-full"> | |
| <span className="font-bold">#</span> {numericCount} numeric | |
| </span> | |
| )} | |
| {categoricalCount > 0 && ( | |
| <span className="inline-flex items-center gap-1 text-[10px] px-2 py-0.5 bg-amber-50 text-amber-700 rounded-full"> | |
| <span className="font-bold">Aa</span> {categoricalCount} categorical | |
| </span> | |
| )} | |
| {datetimeCount > 0 && ( | |
| <span className="inline-flex items-center gap-1 text-[10px] px-2 py-0.5 bg-green-50 text-green-700 rounded-full"> | |
| <span className="font-bold">DT</span> {datetimeCount} datetime | |
| </span> | |
| )} | |
| </div> | |
| <SchemaView compact /> | |
| </div> | |
| ); | |
| } | |