## Major Refactoring - Broke down large components into focused, reusable pieces - Reduced NewPresentationPage.tsx from 238 to 172 lines - Reduced PresentationEditor.tsx from 457 to 261 lines - Eliminated functions exceeding 50-line guideline ## New Reusable Components - PresentationDetailsForm: Form inputs for title/description - AspectRatioSelector: Aspect ratio selection grid - ThemeSelectionSection: Theme selection wrapper - CreationActions: Action buttons and selected theme info - EmptyPresentationState: Empty presentation state display - SlidesSidebar: Complete sidebar with slides list - SlideThumbnail: Individual slide thumbnail with actions - LoadingState: Reusable loading component with spinner - ErrorState: Reusable error display with retry/back actions ## New Hooks - useSlideOperations: Custom hook for slide duplicate/delete logic ## Code Quality Improvements - Replaced browser alert() calls with AlertDialog component - Updated imports to use direct .tsx extensions per IMPORT_STANDARDS.md - Eliminated browser confirm() calls in favor of ConfirmDialog system - Consolidated duplicate loading/error state patterns - Improved type safety throughout ## Benefits - Better maintainability through component separation - Consistent UX with shared UI components - Code reuse across presentation components - Compliance with 200-line file guideline 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
170 lines
4.7 KiB
TypeScript
170 lines
4.7 KiB
TypeScript
import { loggers } from './logger';
|
|
|
|
export interface CSSVariables {
|
|
[key: string]: string;
|
|
}
|
|
|
|
export interface ThemeMetadata {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
author?: string;
|
|
version?: string;
|
|
}
|
|
|
|
/**
|
|
* Parses CSS variables from CSS text content using regex
|
|
*/
|
|
export const parseCSSVariables = (cssContent: string): CSSVariables => {
|
|
const variables: CSSVariables = {};
|
|
|
|
// Parse :root rules and extract CSS variables
|
|
const rootRegex = /:root\s*\{([^}]+)\}/gs;
|
|
let rootMatch;
|
|
|
|
while ((rootMatch = rootRegex.exec(cssContent)) !== null) {
|
|
const rootContent = rootMatch[1];
|
|
const variableRegex = /--([^:]+):\s*([^;]+);/g;
|
|
let variableMatch;
|
|
|
|
while ((variableMatch = variableRegex.exec(rootContent)) !== null) {
|
|
const [, name, value] = variableMatch;
|
|
variables[name.trim()] = value.trim();
|
|
}
|
|
}
|
|
|
|
return variables;
|
|
};
|
|
|
|
|
|
/**
|
|
* Loads CSS file and extracts variables
|
|
*/
|
|
export const loadCSSVariables = async (cssFilePath: string): Promise<CSSVariables> => {
|
|
try {
|
|
const response = await fetch(cssFilePath);
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to load CSS file: ${cssFilePath}`);
|
|
}
|
|
const cssContent = await response.text();
|
|
return parseCSSVariables(cssContent);
|
|
} catch (error) {
|
|
loggers.theme.warn(`Could not load CSS variables from ${cssFilePath}`, error instanceof Error ? error : new Error(String(error)));
|
|
return {};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Gets computed CSS variable value from the document
|
|
*/
|
|
export const getCSSVariable = (variableName: string, element?: HTMLElement): string => {
|
|
const target = element || document.documentElement;
|
|
return getComputedStyle(target).getPropertyValue(`--${variableName}`).trim();
|
|
};
|
|
|
|
/**
|
|
* Sets CSS variable on the document or element
|
|
*/
|
|
export const setCSSVariable = (variableName: string, value: string, element?: HTMLElement): void => {
|
|
const target = element || document.documentElement;
|
|
target.style.setProperty(`--${variableName}`, value);
|
|
};
|
|
|
|
/**
|
|
* Parses theme metadata from CSS comment block using regex
|
|
*/
|
|
export const parseThemeMetadata = (cssContent: string): ThemeMetadata | null => {
|
|
const commentRegex = /\/\*\s*([\s\S]*?)\s*\*\//;
|
|
const match = commentRegex.exec(cssContent);
|
|
|
|
if (!match) return null;
|
|
|
|
const commentContent = match[1];
|
|
const metadata: Partial<ThemeMetadata> = {};
|
|
|
|
// Parse each line in the comment
|
|
const lines = commentContent.split('\n');
|
|
for (const line of lines) {
|
|
const cleanLine = line.replace(/^\s*\*\s?/, '').trim();
|
|
if (cleanLine.includes(':')) {
|
|
const [key, ...valueParts] = cleanLine.split(':');
|
|
const value = valueParts.join(':').trim();
|
|
|
|
switch (key.toLowerCase().trim()) {
|
|
case 'theme':
|
|
metadata.id = value.toLowerCase().replace(/\s+/g, '-');
|
|
break;
|
|
case 'name':
|
|
metadata.name = value;
|
|
break;
|
|
case 'description':
|
|
metadata.description = value;
|
|
break;
|
|
case 'author':
|
|
metadata.author = value;
|
|
break;
|
|
case 'version':
|
|
metadata.version = value;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure required fields
|
|
if (!metadata.id || !metadata.name || !metadata.description) {
|
|
return null;
|
|
}
|
|
|
|
return metadata as ThemeMetadata;
|
|
};
|
|
|
|
|
|
/**
|
|
* Gets all CSS variables from computed styles
|
|
*/
|
|
export const getAllCSSVariables = (element?: HTMLElement): CSSVariables => {
|
|
const target = element || document.documentElement;
|
|
const computedStyles = getComputedStyle(target);
|
|
const variables: CSSVariables = {};
|
|
|
|
// Get all CSS properties and filter for custom properties
|
|
for (let i = 0; i < computedStyles.length; i++) {
|
|
const property = computedStyles[i];
|
|
if (property.startsWith('--')) {
|
|
const name = property.substring(2); // Remove '--' prefix
|
|
const value = computedStyles.getPropertyValue(property).trim();
|
|
if (value) {
|
|
variables[name] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
return variables;
|
|
};
|
|
|
|
/**
|
|
* Loads CSS file and extracts both metadata and variables
|
|
*/
|
|
export const loadThemeFromCSS = async (cssFilePath: string): Promise<{
|
|
metadata: ThemeMetadata | null;
|
|
variables: CSSVariables;
|
|
}> => {
|
|
try {
|
|
const response = await fetch(cssFilePath);
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to load CSS file: ${cssFilePath}`);
|
|
}
|
|
const cssContent = await response.text();
|
|
|
|
return {
|
|
metadata: parseThemeMetadata(cssContent),
|
|
variables: parseCSSVariables(cssContent)
|
|
};
|
|
} catch (error) {
|
|
loggers.theme.warn(`Could not load theme from ${cssFilePath}`, error instanceof Error ? error : new Error(String(error)));
|
|
return {
|
|
metadata: null,
|
|
variables: {}
|
|
};
|
|
}
|
|
}; |