## Major Features Added: ### Presentation Management - Complete CRUD operations for presentations (create, read, update, delete) - IndexedDB storage for offline presentation data - Comprehensive presentation list view with metadata - Navigation integration with header menu ### Slide Management - Full slide editor with layout selection and content editing - Live preview with theme styling applied - Speaker notes functionality - Enhanced layout previews with realistic sample content - Themed layout selection with proper CSS inheritance ### Aspect Ratio System - Support for 3 common presentation formats: 16:9, 4:3, 16:10 - Global CSS system baked into theme engine - Visual aspect ratio selection in presentation creation - Responsive scaling for different viewing contexts - Print-optimized styling with proper dimensions ### User Experience Improvements - Enhanced sample content generation for realistic previews - Improved navigation with presentation management - Better form styling and user interaction - Comprehensive error handling and loading states - Mobile-responsive design throughout ### Technical Infrastructure - Complete TypeScript type system for presentations - Modular component architecture - CSS aspect ratio classes for theme consistency - Enhanced template rendering with live updates - Robust storage utilities with proper error handling 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
214 lines
6.1 KiB
TypeScript
214 lines
6.1 KiB
TypeScript
import type { Presentation, PresentationMetadata, CreatePresentationRequest } from '../types/presentation';
|
|
|
|
const DB_NAME = 'SlideshareDB';
|
|
const DB_VERSION = 1;
|
|
const PRESENTATIONS_STORE = 'presentations';
|
|
|
|
/**
|
|
* Initialize IndexedDB database
|
|
*/
|
|
export const initializeDB = (): Promise<IDBDatabase> => {
|
|
return new Promise((resolve, reject) => {
|
|
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
|
|
request.onerror = () => {
|
|
reject(new Error('Failed to open IndexedDB database'));
|
|
};
|
|
|
|
request.onsuccess = () => {
|
|
resolve(request.result);
|
|
};
|
|
|
|
request.onupgradeneeded = (event) => {
|
|
const db = (event.target as IDBOpenDBRequest).result;
|
|
|
|
// Create presentations object store
|
|
if (!db.objectStoreNames.contains(PRESENTATIONS_STORE)) {
|
|
const store = db.createObjectStore(PRESENTATIONS_STORE, { keyPath: 'metadata.id' });
|
|
|
|
// Create indexes for efficient querying
|
|
store.createIndex('name', 'metadata.name', { unique: false });
|
|
store.createIndex('theme', 'metadata.theme', { unique: false });
|
|
store.createIndex('createdAt', 'metadata.createdAt', { unique: false });
|
|
store.createIndex('updatedAt', 'metadata.updatedAt', { unique: false });
|
|
}
|
|
};
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Generate a unique ID for presentations
|
|
*/
|
|
const generatePresentationId = (): string => {
|
|
return `presentation_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
};
|
|
|
|
/**
|
|
* Create a new presentation
|
|
*/
|
|
export const createPresentation = async (request: CreatePresentationRequest): Promise<Presentation> => {
|
|
const db = await initializeDB();
|
|
|
|
const now = new Date().toISOString();
|
|
const presentation: Presentation = {
|
|
metadata: {
|
|
id: generatePresentationId(),
|
|
name: request.name,
|
|
description: request.description,
|
|
theme: request.theme,
|
|
aspectRatio: request.aspectRatio,
|
|
createdAt: now,
|
|
updatedAt: now
|
|
},
|
|
slides: []
|
|
};
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const transaction = db.transaction([PRESENTATIONS_STORE], 'readwrite');
|
|
const store = transaction.objectStore(PRESENTATIONS_STORE);
|
|
|
|
const request = store.add(presentation);
|
|
|
|
request.onerror = () => {
|
|
reject(new Error('Failed to create presentation'));
|
|
};
|
|
|
|
request.onsuccess = () => {
|
|
resolve(presentation);
|
|
};
|
|
|
|
transaction.onerror = () => {
|
|
reject(new Error('Transaction failed while creating presentation'));
|
|
};
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Get all presentations
|
|
*/
|
|
export const getAllPresentations = async (): Promise<Presentation[]> => {
|
|
const db = await initializeDB();
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const transaction = db.transaction([PRESENTATIONS_STORE], 'readonly');
|
|
const store = transaction.objectStore(PRESENTATIONS_STORE);
|
|
|
|
const request = store.getAll();
|
|
|
|
request.onerror = () => {
|
|
reject(new Error('Failed to retrieve presentations'));
|
|
};
|
|
|
|
request.onsuccess = () => {
|
|
// Sort by creation date (newest first)
|
|
const presentations = request.result.sort((a: Presentation, b: Presentation) =>
|
|
new Date(b.metadata.createdAt).getTime() - new Date(a.metadata.createdAt).getTime()
|
|
);
|
|
resolve(presentations);
|
|
};
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Get a presentation by ID
|
|
*/
|
|
export const getPresentationById = async (id: string): Promise<Presentation | null> => {
|
|
const db = await initializeDB();
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const transaction = db.transaction([PRESENTATIONS_STORE], 'readonly');
|
|
const store = transaction.objectStore(PRESENTATIONS_STORE);
|
|
|
|
const request = store.get(id);
|
|
|
|
request.onerror = () => {
|
|
reject(new Error('Failed to retrieve presentation'));
|
|
};
|
|
|
|
request.onsuccess = () => {
|
|
resolve(request.result || null);
|
|
};
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Update a presentation
|
|
*/
|
|
export const updatePresentation = async (presentation: Presentation): Promise<Presentation> => {
|
|
const db = await initializeDB();
|
|
|
|
// Update the updatedAt timestamp
|
|
presentation.metadata.updatedAt = new Date().toISOString();
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const transaction = db.transaction([PRESENTATIONS_STORE], 'readwrite');
|
|
const store = transaction.objectStore(PRESENTATIONS_STORE);
|
|
|
|
const request = store.put(presentation);
|
|
|
|
request.onerror = () => {
|
|
reject(new Error('Failed to update presentation'));
|
|
};
|
|
|
|
request.onsuccess = () => {
|
|
resolve(presentation);
|
|
};
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Delete a presentation
|
|
*/
|
|
export const deletePresentation = async (id: string): Promise<void> => {
|
|
const db = await initializeDB();
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const transaction = db.transaction([PRESENTATIONS_STORE], 'readwrite');
|
|
const store = transaction.objectStore(PRESENTATIONS_STORE);
|
|
|
|
const request = store.delete(id);
|
|
|
|
request.onerror = () => {
|
|
reject(new Error('Failed to delete presentation'));
|
|
};
|
|
|
|
request.onsuccess = () => {
|
|
resolve();
|
|
};
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Get presentations by theme
|
|
*/
|
|
export const getPresentationsByTheme = async (theme: string): Promise<Presentation[]> => {
|
|
const db = await initializeDB();
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const transaction = db.transaction([PRESENTATIONS_STORE], 'readonly');
|
|
const store = transaction.objectStore(PRESENTATIONS_STORE);
|
|
const index = store.index('theme');
|
|
|
|
const request = index.getAll(theme);
|
|
|
|
request.onerror = () => {
|
|
reject(new Error('Failed to retrieve presentations by theme'));
|
|
};
|
|
|
|
request.onsuccess = () => {
|
|
// Sort by creation date (newest first)
|
|
const presentations = request.result.sort((a: Presentation, b: Presentation) =>
|
|
new Date(b.metadata.createdAt).getTime() - new Date(a.metadata.createdAt).getTime()
|
|
);
|
|
resolve(presentations);
|
|
};
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Get presentation metadata only (for listing views)
|
|
*/
|
|
export const getPresentationMetadata = async (): Promise<PresentationMetadata[]> => {
|
|
const presentations = await getAllPresentations();
|
|
return presentations.map(p => p.metadata);
|
|
}; |