diff --git a/public/themes-manifest.json b/public/themes-manifest.json index b6c19f3..080cc03 100644 --- a/public/themes-manifest.json +++ b/public/themes-manifest.json @@ -12,5 +12,5 @@ "hasMasterSlide": true } }, - "generated": "2025-08-21T16:40:45.572Z" + "generated": "2025-08-21T17:31:29.998Z" } \ No newline at end of file diff --git a/public/themes/CLAUDE.md b/public/themes/CLAUDE.md new file mode 100644 index 0000000..e69de29 diff --git a/src/components/AppHeader.tsx b/src/components/AppHeader.tsx index 67620a0..fc63d5c 100644 --- a/src/components/AppHeader.tsx +++ b/src/components/AppHeader.tsx @@ -1,99 +1,63 @@ import React from 'react'; -import { Link, useLocation } from 'react-router-dom'; +import { Link, useLocation, useParams } from 'react-router-dom'; export const AppHeader: React.FC = () => { const location = useLocation(); + const params = useParams(); const getPageTitle = () => { - if (location.pathname === '/') { - return 'Welcome to Slideshare'; - } - if (location.pathname === '/presentations') { - return 'My Presentations'; - } - if (location.pathname === '/presentations/new') { - return 'Create New Presentation'; - } - if (location.pathname.includes('/slide/') && location.pathname.includes('/edit')) { - const segments = location.pathname.split('/'); - const slideId = segments[4]; - if (slideId === 'new') { - return 'Add New Slide'; - } else { - return 'Edit Slide'; - } - } - if (location.pathname.includes('/presentations/') && location.pathname.includes('/slides/')) { - const segments = location.pathname.split('/'); - if (segments.length === 6 && segments[1] === 'presentations') { - const mode = segments[3]; // 'edit' or 'view' - const slideNumber = segments[5]; - if (mode === 'edit') { - return `Editing Slide ${slideNumber}`; - } else if (mode === 'view') { - return `Viewing Slide ${slideNumber}`; - } - return `Presentation Slide ${slideNumber}`; - } - } - if (location.pathname === '/themes') { - return 'Theme Browser'; - } - if (location.pathname.includes('/themes/')) { - const segments = location.pathname.split('/'); - if (segments.length === 3) { - return `Theme: ${segments[2]}`; - } - if (segments.length === 5 && segments[3] === 'layouts') { - return `Layout: ${segments[4]}`; + const { pathname } = location; + + // Route patterns with titles (cleaner than manual parsing) + const routes = [ + { pattern: /^\/$/, title: 'Welcome to Slideshare' }, + { pattern: /^\/presentations$/, title: 'My Presentations' }, + { pattern: /^\/presentations\/new$/, title: 'Create New Presentation' }, + { pattern: /^\/presentations\/[^/]+\/edit\/slides\/(\d+)$/, title: () => `Editing Slide ${params.slideNumber}` }, + { pattern: /^\/presentations\/[^/]+\/view\/slides\/(\d+)$/, title: () => `Viewing Slide ${params.slideNumber}` }, + { pattern: /^\/presentations\/[^/]+\/present\/(\d+)$/, title: () => `Presenting Slide ${params.slideNumber}` }, + { pattern: /^\/presentations\/[^/]+\/slide\/new\/edit$/, title: 'Add New Slide' }, + { pattern: /^\/presentations\/[^/]+\/slide\/[^/]+\/edit$/, title: 'Edit Slide' }, + { pattern: /^\/themes$/, title: 'Theme Browser' }, + { pattern: /^\/themes\/([^/]+)$/, title: () => `Theme: ${params.themeId}` }, + { pattern: /^\/themes\/[^/]+\/layouts\/([^/]+)$/, title: () => `Layout: ${params.layoutId}` }, + { pattern: /^\/themes\/[^/]+\/layouts\/[^/]+\/preview$/, title: () => `Preview Layout` }, + ]; + + for (const route of routes) { + if (route.pattern.test(pathname)) { + return typeof route.title === 'function' ? route.title() : route.title; } } + return 'Slideshare'; }; const getPageDescription = () => { - if (location.pathname === '/') { - return 'Create beautiful presentations with customizable themes'; - } - if (location.pathname === '/presentations') { - return 'View and manage all your presentations'; - } - if (location.pathname === '/presentations/new') { - return 'Select a theme and enter details for your new presentation'; - } - if (location.pathname.includes('/slide/') && location.pathname.includes('/edit')) { - const segments = location.pathname.split('/'); - const slideId = segments[4]; - if (slideId === 'new') { - return 'Choose a layout and add content for your new slide'; - } else { - return 'Edit slide content and layout'; - } - } - if (location.pathname.includes('/presentations/') && location.pathname.includes('/slides/')) { - const segments = location.pathname.split('/'); - if (segments.length === 6 && segments[1] === 'presentations') { - const mode = segments[3]; - if (mode === 'edit') { - return 'Edit slide content, add notes, and manage your presentation'; - } else if (mode === 'view') { - return 'View your presentation slides in read-only mode'; - } - } - return 'View and manage your presentation slides'; - } - if (location.pathname === '/themes') { - return 'Browse and select themes for your presentations'; - } - if (location.pathname.includes('/themes/')) { - const segments = location.pathname.split('/'); - if (segments.length === 3) { - return 'View theme details, layouts, and color palette'; - } - if (segments.length === 5 && segments[3] === 'layouts') { - return 'View layout template, slots, and configuration'; + const { pathname } = location; + + // Route patterns with descriptions + const routes = [ + { pattern: /^\/$/, description: 'Create beautiful presentations with customizable themes' }, + { pattern: /^\/presentations$/, description: 'View and manage all your presentations' }, + { pattern: /^\/presentations\/new$/, description: 'Enter details for your new presentation' }, + { pattern: /^\/presentations\/[^/]+\/edit\/slides\/(\d+)$/, description: 'Edit slide content, add notes, and manage your presentation' }, + { pattern: /^\/presentations\/[^/]+\/view\/slides\/(\d+)$/, description: 'View your presentation slides in read-only mode' }, + { pattern: /^\/presentations\/[^/]+\/present\/(\d+)$/, description: 'Present your slides in full screen mode' }, + { pattern: /^\/presentations\/[^/]+\/slide\/new\/edit$/, description: 'Choose a layout and add content for your new slide' }, + { pattern: /^\/presentations\/[^/]+\/slide\/[^/]+\/edit$/, description: 'Edit slide content and layout' }, + { pattern: /^\/themes$/, description: 'Browse and select themes for your presentations' }, + { pattern: /^\/themes\/([^/]+)$/, description: 'View theme details, layouts, and color palette' }, + { pattern: /^\/themes\/[^/]+\/layouts\/([^/]+)$/, description: 'View layout template, slots, and configuration' }, + { pattern: /^\/themes\/[^/]+\/layouts\/[^/]+\/preview$/, description: 'Preview layout with sample content' }, + ]; + + for (const route of routes) { + if (route.pattern.test(pathname)) { + return route.description; } } + return 'Slide authoring and presentation tool'; }; @@ -110,9 +74,12 @@ export const AppHeader: React.FC = () => { Create Presentation - - Themes - + {/* Theme browsing - Coming soon */} +
+ + Themes + +
diff --git a/src/components/Welcome.tsx b/src/components/Welcome.tsx index 49b4d30..1369d0f 100644 --- a/src/components/Welcome.tsx +++ b/src/components/Welcome.tsx @@ -1,7 +1,10 @@ import React from 'react'; -import { Link } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; +import { Button } from './ui/buttons/Button.tsx'; export const Welcome: React.FC = () => { + const navigate = useNavigate(); + return (
@@ -10,9 +13,13 @@ export const Welcome: React.FC = () => { Create beautiful presentations with customizable themes and layouts

- +
@@ -66,7 +73,8 @@ export const Welcome: React.FC = () => {

Getting Started

-
+ {/* Theme browsing step - Coming soon */} +
1

Browse Themes

@@ -77,17 +85,17 @@ export const Welcome: React.FC = () => {
-
2
+
1

Create Presentation

- Start a new presentation using your chosen theme + Start a new presentation with the default theme

-
3
+
2

Add Content

@@ -97,7 +105,7 @@ export const Welcome: React.FC = () => {

-
4
+
3

Present

@@ -114,9 +122,13 @@ export const Welcome: React.FC = () => {

Start building your presentation with our theme collection

- +
diff --git a/src/components/presentations/NewPresentationPage.tsx b/src/components/presentations/NewPresentationPage.tsx index 3234ea7..f761039 100644 --- a/src/components/presentations/NewPresentationPage.tsx +++ b/src/components/presentations/NewPresentationPage.tsx @@ -107,8 +107,8 @@ export const NewPresentationPage: React.FC = () => {

Error Loading Themes

{error}

-
@@ -119,11 +119,11 @@ export const NewPresentationPage: React.FC = () => {

Create New Presentation

@@ -160,7 +160,7 @@ export const NewPresentationPage: React.FC = () => { error={error} creating={creating} presentationTitle={presentationTitle} - onCancel={() => navigate('/themes')} + onCancel={() => navigate(-1)} onCreate={handleCreatePresentation} />
diff --git a/src/components/presentations/PresentationEditor.tsx b/src/components/presentations/PresentationEditor.tsx index f8c7d73..b1b8788 100644 --- a/src/components/presentations/PresentationEditor.tsx +++ b/src/components/presentations/PresentationEditor.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { useParams, useNavigate, Link } from 'react-router-dom'; import type { Presentation } from '../../types/presentation.ts'; import type { Theme } from '../../types/theme.ts'; -import { getPresentationById, updatePresentation } from '../../utils/presentationStorage.ts'; +import { getPresentationById } from '../../utils/presentationStorage.ts'; import { loadTheme } from '../../utils/themeLoader.ts'; import { useDialog } from '../../hooks/useDialog.ts'; import { AlertDialog } from '../ui/AlertDialog.tsx'; diff --git a/src/components/presentations/PresentationMode.tsx b/src/components/presentations/PresentationMode.tsx index 13de5fb..f88608c 100644 --- a/src/components/presentations/PresentationMode.tsx +++ b/src/components/presentations/PresentationMode.tsx @@ -156,7 +156,8 @@ export const PresentationMode: React.FC = () => { }, [presentationId]); const exitPresentationMode = () => { - navigate(`/presentations/${presentationId}/view/slides/${currentSlideIndex + 1}`); + // Navigate back to the previous page in history + navigate(-1); }; const renderSlideContent = (slide: SlideContent): string => { diff --git a/src/components/presentations/PresentationsList.tsx b/src/components/presentations/PresentationsList.tsx index ac8651b..30997f5 100644 --- a/src/components/presentations/PresentationsList.tsx +++ b/src/components/presentations/PresentationsList.tsx @@ -1,8 +1,11 @@ import React, { useState, useEffect } from 'react'; -import { Link, useNavigate } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import type { Presentation } from '../../types/presentation.ts'; import { getAllPresentations, deletePresentation } from '../../utils/presentationStorage.ts'; import { loggers } from '../../utils/logger.ts'; +import { ConfirmDialog } from '../ui/ConfirmDialog.tsx'; +import { AlertDialog } from '../ui/AlertDialog.tsx'; +import { Button } from '../ui/buttons/Button.tsx'; import './PresentationsList.css'; export const PresentationsList: React.FC = () => { @@ -11,6 +14,19 @@ export const PresentationsList: React.FC = () => { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [deleting, setDeleting] = useState(null); + + // Dialog states + const [confirmDialog, setConfirmDialog] = useState<{ + isOpen: boolean; + presentationId: string; + presentationName: string; + }>({ isOpen: false, presentationId: '', presentationName: '' }); + + const [alertDialog, setAlertDialog] = useState<{ + isOpen: boolean; + message: string; + type?: 'error' | 'success'; + }>({ isOpen: false, message: '' }); useEffect(() => { loadPresentations(); @@ -29,18 +45,34 @@ export const PresentationsList: React.FC = () => { } }; - const handleDeletePresentation = async (id: string, name: string) => { - if (!confirm(`Are you sure you want to delete "${name}"? This action cannot be undone.`)) { - return; - } + const handleDeletePresentation = (id: string, name: string) => { + setConfirmDialog({ + isOpen: true, + presentationId: id, + presentationName: name + }); + }; + + const confirmDeletePresentation = async () => { + const { presentationId } = confirmDialog; + setConfirmDialog({ isOpen: false, presentationId: '', presentationName: '' }); try { - setDeleting(id); - await deletePresentation(id); - setPresentations(prev => prev.filter(p => p.metadata.id !== id)); + setDeleting(presentationId); + await deletePresentation(presentationId); + setPresentations(prev => prev.filter(p => p.metadata.id !== presentationId)); + setAlertDialog({ + isOpen: true, + message: 'Presentation deleted successfully', + type: 'success' + }); } catch (err) { loggers.presentation.error('Failed to delete presentation', err instanceof Error ? err : new Error(String(err))); - alert('Failed to delete presentation. Please try again.'); + setAlertDialog({ + isOpen: true, + message: 'Failed to delete presentation. Please try again.', + type: 'error' + }); } finally { setDeleting(null); } @@ -100,14 +132,6 @@ export const PresentationsList: React.FC = () => {

My Presentations

Manage and organize your presentation library

-
- - Create New Presentation - -
@@ -116,12 +140,13 @@ export const PresentationsList: React.FC = () => {

No presentations yet

Create your first presentation to get started

- navigate('/presentations/new')} > Create Your First Presentation - +
) : ( @@ -233,22 +258,36 @@ export const PresentationsList: React.FC = () => {

- - navigate('/presentations/new')} > Create New - +
)}
+ + {/* Confirmation Dialog for Deletion */} + setConfirmDialog({ isOpen: false, presentationId: '', presentationName: '' })} + onConfirm={confirmDeletePresentation} + title="Delete Presentation" + message={`Are you sure you want to delete "${confirmDialog.presentationName}"? This action cannot be undone.`} + type="danger" + confirmText="Delete" + isDestructive={true} + /> + + {/* Alert Dialog for Success/Error Messages */} + setAlertDialog({ isOpen: false, message: '' })} + message={alertDialog.message} + type={alertDialog.type} + /> ); }; \ No newline at end of file diff --git a/src/components/slide-editor/SlideEditor.tsx b/src/components/slide-editor/SlideEditor.tsx index 630601c..518f01a 100644 --- a/src/components/slide-editor/SlideEditor.tsx +++ b/src/components/slide-editor/SlideEditor.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useParams, Link } from 'react-router-dom'; +import { useParams, Link, useNavigate } from 'react-router-dom'; import { SlideEditorErrorBoundary } from './SlideEditorErrorBoundary.tsx'; import { LoadingState } from './LoadingState.tsx'; import { ErrorState } from './ErrorState.tsx'; @@ -11,6 +11,7 @@ import { useSlideEditor } from './useSlideEditor.ts'; import './SlideEditor.css'; export const SlideEditor: React.FC = () => { + const navigate = useNavigate(); const { presentationId, slideId } = useParams<{ presentationId: string; slideId: string; @@ -43,6 +44,21 @@ export const SlideEditor: React.FC = () => { cancelEditing, } = useSlideEditor({ presentationId, slideId }); + // Helper function to get current slide number for presentation + const getCurrentSlideNumber = (): number => { + if (!presentation || !slideId || slideId === 'new') { + return 1; // Default to first slide for new slides + } + + const slideIndex = presentation.slides.findIndex(slide => slide.id === slideId); + return slideIndex !== -1 ? slideIndex + 1 : 1; + }; + + const handlePresentFromHere = () => { + const slideNumber = getCurrentSlideNumber(); + navigate(`/presentations/${presentationId}/present/${slideNumber}`); + }; + if (loading) { return ; } @@ -76,12 +92,20 @@ export const SlideEditor: React.FC = () => {
{currentStep === 'content' && selectedLayout && ( - setShowPreview(true)} - > - Full Preview - + <> + setShowPreview(true)} + > + Full Preview + + + Present from here + + )}
diff --git a/src/plugins/themeWatcher.ts b/src/plugins/themeWatcher.ts index b6f0ee2..57cc102 100644 --- a/src/plugins/themeWatcher.ts +++ b/src/plugins/themeWatcher.ts @@ -17,7 +17,7 @@ export function themeWatcherPlugin(): Plugin { const watcher = fs.watch( publicThemesPath, { recursive: true }, - (eventType: string, filename: string | null) => { + (_eventType: string, filename: string | null) => { if (filename && (filename.endsWith('.css') || filename.endsWith('.html'))) { console.log(`Theme file changed: ${filename}`); diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 674d405..b7ef394 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -5,25 +5,29 @@ import log from 'loglevel'; */ // Define log levels for different parts of the application -export enum LogLevel { - TRACE = 0, - DEBUG = 1, - INFO = 2, - WARN = 3, - ERROR = 4, - SILENT = 5, -} +export const LogLevel = { + TRACE: 0, + DEBUG: 1, + INFO: 2, + WARN: 3, + ERROR: 4, + SILENT: 5, +} as const; + +export type LogLevel = typeof LogLevel[keyof typeof LogLevel]; // Log categories for better organization -export enum LogCategory { - PRESENTATION = 'presentation', - THEME = 'theme', - STORAGE = 'storage', - UI = 'ui', - SECURITY = 'security', - PERFORMANCE = 'performance', - GENERAL = 'general', -} +export const LogCategory = { + PRESENTATION: 'presentation', + THEME: 'theme', + STORAGE: 'storage', + UI: 'ui', + SECURITY: 'security', + PERFORMANCE: 'performance', + GENERAL: 'general', +} as const; + +export type LogCategory = typeof LogCategory[keyof typeof LogCategory]; /** * Configure logging based on environment