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 = () => {
navigate('/themes')}
+ onClick={() => navigate(-1)}
className="back-button"
type="button"
>
- ← Back to Themes
+ ← Back
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 = () => {
-
- Refresh
-
- 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