feat(dashboard): tableau de bord Second Brain configurable avec chargement progressif
Briefing granulaire, pistes rapides puis enrichissement async, layout persisté v5, suggestions agents, intégration Gmail et navigation sidebar alignée sur /home. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,6 +10,12 @@ import type { AppState, BinaryFiles } from '@excalidraw/excalidraw/types'
|
||||
import type { ExcalidrawImperativeAPI } from '@excalidraw/excalidraw/types'
|
||||
import '@excalidraw/excalidraw/index.css'
|
||||
import type { PresentationSpec } from '@/lib/types/presentation'
|
||||
import { useLanguage } from '@/lib/i18n'
|
||||
|
||||
function SlidesLoadingFallback() {
|
||||
const { t } = useLanguage()
|
||||
return <div className="absolute inset-0 flex items-center justify-center bg-zinc-950 text-white/40 text-sm">{t('lab.loadingSlides')}</div>
|
||||
}
|
||||
|
||||
const Excalidraw = dynamic(
|
||||
async () => (await import('@excalidraw/excalidraw')).Excalidraw,
|
||||
@@ -18,7 +24,7 @@ const Excalidraw = dynamic(
|
||||
|
||||
const SlidesRenderer = dynamic(
|
||||
() => import('./slides-renderer').then(m => ({ default: m.SlidesRenderer })),
|
||||
{ ssr: false, loading: () => <div className="absolute inset-0 flex items-center justify-center bg-zinc-950 text-white/40 text-sm">Chargement des slides…</div> }
|
||||
{ ssr: false, loading: () => <SlidesLoadingFallback /> }
|
||||
)
|
||||
|
||||
interface CanvasBoardProps {
|
||||
@@ -62,6 +68,7 @@ function parseCanvasScene(initialData?: string): {
|
||||
}
|
||||
|
||||
function PptxViewer({ data, name }: { data: PptxPayload; name: string }) {
|
||||
const { t } = useLanguage()
|
||||
const handleDownload = () => {
|
||||
try {
|
||||
const byteCharacters = atob(data.base64)
|
||||
@@ -83,7 +90,7 @@ function PptxViewer({ data, name }: { data: PptxPayload; name: string }) {
|
||||
URL.revokeObjectURL(url)
|
||||
} catch (e) {
|
||||
console.error('[PptxViewer] Download failed:', e)
|
||||
toast.error('Failed to download presentation')
|
||||
toast.error(t('lab.downloadFailed'))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +113,7 @@ function PptxViewer({ data, name }: { data: PptxPayload; name: string }) {
|
||||
className="flex items-center gap-2 px-6 py-3 bg-primary text-primary-foreground rounded-xl hover:bg-primary/90 transition-colors font-medium shadow-sm"
|
||||
>
|
||||
<Download className="w-5 h-5" />
|
||||
Download .pptx
|
||||
{t('lab.exportPpt')}
|
||||
</button>
|
||||
<p className="text-xs text-muted-foreground max-w-sm text-center">
|
||||
This file can be opened in Microsoft PowerPoint, Google Slides, or Keynote.
|
||||
@@ -117,6 +124,7 @@ function PptxViewer({ data, name }: { data: PptxPayload; name: string }) {
|
||||
}
|
||||
|
||||
function SlidesViewer({ data, name, canvasId }: { data: SlidesPayload; name: string; canvasId?: string | null }) {
|
||||
const { t } = useLanguage()
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null)
|
||||
const [currentSlide, setCurrentSlide] = useState(1)
|
||||
const [isLoaded, setIsLoaded] = useState(!!data.spec) // spec → React renderer, no iframe loading
|
||||
@@ -194,27 +202,27 @@ function SlidesViewer({ data, name, canvasId }: { data: SlidesPayload; name: str
|
||||
href={`/api/canvas/slides/pptx?id=${canvasId}`}
|
||||
download
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 bg-white/10 hover:bg-white/20 rounded-lg text-white/70 hover:text-white text-xs font-medium transition-all"
|
||||
title="Exporter en PowerPoint"
|
||||
title={t('lab.exportPpt')}
|
||||
>
|
||||
<Download className="w-3.5 h-3.5" />
|
||||
Export PPTX
|
||||
{t('lab.exportPpt')}
|
||||
</a>
|
||||
)}
|
||||
<button
|
||||
onClick={downloadHtml}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 bg-white/10 hover:bg-white/20 rounded-lg text-white/70 hover:text-white text-xs font-medium transition-all"
|
||||
title="Télécharger le HTML"
|
||||
title={t('lab.downloadHtml')}
|
||||
>
|
||||
<Download className="w-3.5 h-3.5" />
|
||||
Export HTML
|
||||
{t('lab.downloadHtml')}
|
||||
</button>
|
||||
<button
|
||||
onClick={openFullscreen}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 bg-white/10 hover:bg-white/20 rounded-lg text-white/70 hover:text-white text-xs font-medium transition-all"
|
||||
title="Ouvrir en plein écran"
|
||||
title={t('lab.openFullscreen')}
|
||||
>
|
||||
<Maximize2 className="w-3.5 h-3.5" />
|
||||
Plein écran
|
||||
{t('lab.openFullscreen')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -228,7 +236,7 @@ function SlidesViewer({ data, name, canvasId }: { data: SlidesPayload; name: str
|
||||
{!isLoaded && (
|
||||
<div className="absolute inset-0 z-20 flex flex-col items-center justify-center bg-zinc-950 gap-3">
|
||||
<div className="w-8 h-8 rounded-full border-2 border-white/10 border-t-white/60 animate-spin" />
|
||||
<span className="text-xs text-white/30">Chargement de la présentation…</span>
|
||||
<span className="text-xs text-white/30">{t('lab.loadingPresentation')}</span>
|
||||
</div>
|
||||
)}
|
||||
<iframe
|
||||
@@ -244,7 +252,7 @@ function SlidesViewer({ data, name, canvasId }: { data: SlidesPayload; name: str
|
||||
<button
|
||||
onClick={() => navigate(-1)}
|
||||
className="absolute left-2 top-1/2 -translate-y-1/2 z-10 w-9 h-9 flex items-center justify-center rounded-full bg-black/40 hover:bg-black/70 backdrop-blur-sm border border-white/10 text-white/40 hover:text-white transition-all opacity-0 group-hover:opacity-100"
|
||||
aria-label="Slide précédent"
|
||||
aria-label={t('lab.previousSlide')}
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
@@ -252,7 +260,7 @@ function SlidesViewer({ data, name, canvasId }: { data: SlidesPayload; name: str
|
||||
<button
|
||||
onClick={() => navigate(1)}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 z-10 w-9 h-9 flex items-center justify-center rounded-full bg-black/40 hover:bg-black/70 backdrop-blur-sm border border-white/10 text-white/40 hover:text-white transition-all opacity-0 group-hover:opacity-100"
|
||||
aria-label="Slide suivant"
|
||||
aria-label={t('lab.nextSlide')}
|
||||
>
|
||||
<ChevronRight className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user