Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
b3280eb
feat: add theme package option for gallery build
rustoma Dec 23, 2025
cf68b3f
docs: add custom themes documentation and update build command options
rustoma Dec 23, 2025
f7a5dbb
docs: enhance custom themes documentation and update build command to…
rustoma Dec 28, 2025
9a1d4a3
feat: add create-theme command and implementation for generating cust…
rustoma Dec 28, 2025
cd9f65a
feat: enhance createTheme function to support custom theme paths and …
rustoma Dec 28, 2025
cb2573c
feat: add gallery script to package.json and implement monorepo root …
rustoma Dec 28, 2025
297da7b
feat: update package.json generation to use theme name directly and a…
rustoma Dec 28, 2025
35a393e
feat: update yarn.lock with new theme dependencies and modify createT…
rustoma Dec 28, 2025
d8b70ea
feat: add support for dynamic header images in theme templates and en…
rustoma Dec 28, 2025
e91d6b5
feat: add Hero component to theme templates and update createTheme fu…
rustoma Dec 28, 2025
2bbc14b
chore: update yarn.lock to remove unused theme dependencies and strea…
rustoma Dec 28, 2025
a1d7793
feat: add create-theme command documentation and enhance themes guide…
rustoma Dec 31, 2025
49994d0
docs: fix formatting in README.md for create-theme and telemetry comm…
rustoma Dec 31, 2025
13b0a3a
feat: update ESLint configuration for Astro files and simplify import…
rustoma Dec 31, 2025
0a71caa
docs: expand README.md with development setup instructions and worksp…
rustoma Dec 31, 2025
e3a2382
chore: bump version in package.json to 2.1.17
rustoma Dec 31, 2025
21de85a
feat: implement base theme structure with components, layouts, and ut…
rustoma Jan 4, 2026
c571e1f
docs: enhance theme creation documentation with detailed explanations…
rustoma Jan 4, 2026
80b9904
feat: introduce bundled base theme template for theme creation with c…
rustoma Jan 4, 2026
f1cf839
docs: clarify base theme template location and local development inst…
rustoma Jan 4, 2026
70c9995
Merge branch 'main' of https://github.com/SimplePhotoGallery/core int…
rustoma Jan 14, 2026
34c3426
feat: generate README.md from template for new themes
rustoma Jan 14, 2026
44fad4e
chore: update TypeScript configuration and improve README generation
rustoma Jan 15, 2026
0032c75
chore: add process import to Astro configuration and utility functions
rustoma Jan 15, 2026
0284586
chore: update TypeScript configuration and clean up imports in Astro …
rustoma Jan 15, 2026
942f13b
chore: refine ESLint configuration for TypeScript and JavaScript files
rustoma Jan 15, 2026
3b02edb
chore: format code for consistency and improve readability in theme t…
rustoma Jan 15, 2026
dddcd86
chore: update ESLint configuration by removing duplicate import and e…
rustoma Jan 15, 2026
229c54b
fix: docs table layout
haltakov Jan 18, 2026
09f3583
chore: enhance theme structure by adding client and theme modules, up…
rustoma Jan 20, 2026
fd11103
Merge branch 'feature/themes-support' of https://github.com/SimplePho…
rustoma Jan 20, 2026
22cc49a
feat: implement createGalleryLightbox function for enhanced PhotoSwip…
rustoma Jan 20, 2026
b350195
refactor: streamline PhotoSwipe module imports in createGalleryLightb…
rustoma Jan 20, 2026
1bd7ec4
refactor: format code
rustoma Jan 20, 2026
d47b534
chore: clean up imports in Astro config files and ensure consistent u…
rustoma Jan 20, 2026
1f219d1
feat: restructure client and gallery modules
rustoma Jan 22, 2026
b345eb4
feat: add CSS utility functions for color manipulation and theming su…
rustoma Jan 22, 2026
f7dafdc
feat: introduce CSS custom properties for PhotoSwipe customization an…
rustoma Jan 22, 2026
c4f58a6
feat: add PhotoSwipe CSS styles and enhance lightbox integration with…
rustoma Jan 23, 2026
ac4d110
feat: add LoadGalleryDataOptions interface and validation support for…
rustoma Jan 23, 2026
54ded36
feat: enable validation in loadGalleryData function for improved data…
rustoma Jan 23, 2026
7965d92
chore: enhance documentation with architecture overview and common pa…
rustoma Jan 23, 2026
d240f1f
feat: implement theme configuration support for thumbnails, enhancing…
rustoma Jan 23, 2026
ba1dae2
feat: enhance thumbnail configuration with extraction function and va…
rustoma Jan 24, 2026
e626be2
docs: update theme configuration documentation to clarify loading pri…
rustoma Jan 24, 2026
5001505
docs: remove outdated theme module documentation to streamline README
rustoma Jan 27, 2026
a2a2b8e
docs: expand configuration documentation to include theme and thumbna…
rustoma Jan 27, 2026
8e54a6f
feat: implement new theme components and enhance README documentation…
rustoma Jan 27, 2026
4fa8fef
fix: update thumbnail size and row height for gallery layout; enhance…
rustoma Jan 27, 2026
47a1607
feat: add deep linking support for PhotoSwipe integration; enhance th…
rustoma Jan 27, 2026
7553e00
refactor: simplify image handling in MainHead and MainLayout componen…
rustoma Jan 27, 2026
beb3049
fix: formatting and lint isues
haltakov Jan 29, 2026
0842bbd
fix: CI for complex common package
haltakov Jan 29, 2026
b9d95f1
fix: broken test
haltakov Jan 29, 2026
774ed36
feat: make the row height reponsive and default to 160px
haltakov Jan 29, 2026
fce46b8
chore: fix yarn.lock
haltakov Jan 29, 2026
d3a8311
chore: update versions to 2.1.0
haltakov Jan 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/check-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:

- name: Run checks (common)
working-directory: ./common
run: yarn check
run: yarn check && yarn build

- name: Run checks (gallery)
working-directory: ./gallery
Expand Down
71 changes: 71 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,78 @@ This will:
- Ubuntu/Debian: `sudo apt install ffmpeg`
- Windows: [Download from ffmpeg.org](https://ffmpeg.org/download.html)

## Development

This is a monorepo using Yarn workspaces. To set up the development environment:

1. **Clone the repository**

```bash
git clone https://github.com/SimplePhotoGallery/core.git
cd spg-core
```

2. **Install dependencies**

```bash
yarn install
```

3. **Build the `common` package** (required for TypeScript/ESLint to resolve `@simple-photo-gallery/common`)

```bash
yarn workspace @simple-photo-gallery/common build
```

4. **Build the gallery package** (optional, for testing CLI changes)

```bash
yarn workspace simple-photo-gallery build
```

5. **Run the CLI in development mode**
```bash
yarn workspace simple-photo-gallery gallery
```

### Workspace Packages

- **`common/`** - Shared types, schemas, and utilities used by both the CLI and themes
- Gallery types and Zod validation schemas
- Theme utilities (data loading, path resolution, markdown parsing)
- Client-side utilities (PhotoSwipe, blurhash, CSS helpers)
- See [common/README.md](common/README.md) for full API documentation
- **`gallery/`** - CLI tool (`simple-photo-gallery`)
- Includes base theme template bundled at `gallery/src/modules/create-theme/templates/base/`
- **`themes/modern/`** - Default theme package (reference implementation)

### Building Packages

Each workspace package can be built individually:

- `yarn workspace @simple-photo-gallery/common build`
- `yarn workspace simple-photo-gallery build`
- `yarn workspace @simple-photo-gallery/theme-modern build`

## Supported Formats

**Images:** JPEG, PNG, WebP, GIF, TIFF
**Videos:** MP4, MOV, AVI, WebM, MKV

## Architecture

This project uses a multi-theme architecture:
- **Common package** provides shared utilities for all themes
- **Themes** focus only on layout and presentation
- **CLI** handles gallery generation and theme orchestration

See the [Architecture Documentation](./docs/architecture.md) for details on how the system works, including:
- Package structure and dependencies
- Data flow from photos to static HTML
- Theme system design and resolution
- Multi-theme support implementation
- Guidelines for adding new features

## Detailed Documentation

For advanced usage, customization, and deployment options, see the comprehensive [documentation](./docs/README.md):
Expand All @@ -76,7 +143,11 @@ For advanced usage, customization, and deployment options, see the comprehensive
- [`build`](./docs/commands/build.md) - Generate static HTML galleries
- [`thumbnails`](./docs/commands/thumbnails.md) - Generate optimized thumbnails
- [`clean`](./docs/commands/clean.md) - Remove gallery files
- [`create-theme`](./docs/commands/create-theme.md) - Scaffold a new theme package
- [`telemetry`](./docs/commands/telemetry.md) - Manage anonymous telemetry preferences
- **[Gallery Configuration](./docs/configuration.md)** - Manual editing of `gallery.json` and advanced features like sections
- **[Custom Themes](./docs/themes.md)** - Create and use custom themes
- **[Common Package API](./common/README.md)** - Utilities and types for theme development
- **[Deployment Guide](./docs/deployment.md)** - Guidelines for hosting your gallery

## Python Version
Expand Down
16 changes: 16 additions & 0 deletions common/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# @simple-photo-gallery/common

Shared utilities and types for Simple Photo Gallery themes and CLI.

## Installation

```bash
npm install @simple-photo-gallery/common
```

## Package Exports

- `.` - Gallery types and Zod validation schemas
- `./theme` - Theme utilities for data loading and resolution
- `./client` - Browser-side utilities (PhotoSwipe, blurhash, CSS)
- `./styles/photoswipe` - PhotoSwipe CSS bundle
27 changes: 25 additions & 2 deletions common/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@simple-photo-gallery/common",
"version": "1.0.6",
"version": "2.1.0",
"description": "Shared utilities and types for Simple Photo Gallery",
"license": "MIT",
"author": "Vladimir Haltakov, Tomasz Rusin",
Expand All @@ -16,7 +16,16 @@
"types": "./dist/gallery.d.ts",
"import": "./dist/gallery.js",
"require": "./dist/gallery.cjs"
}
},
"./theme": {
"types": "./dist/theme.d.ts",
"import": "./dist/theme.js"
},
"./client": {
"types": "./dist/client.d.ts",
"import": "./dist/client.js"
},
"./styles/photoswipe": "./dist/styles/photoswipe/photoswipe.css"
},
"files": [
"dist"
Expand All @@ -33,12 +42,26 @@
"prepublish": "yarn build"
},
"dependencies": {
"marked": "^16.0.0",
"zod": "^4.0.14"
},
"peerDependencies": {
"blurhash": "^2.0.5",
"photoswipe": "^5.4.4"
},
"peerDependenciesMeta": {
"blurhash": {
"optional": true
},
"photoswipe": {
"optional": true
}
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.30.1",
"@types/node": "^24.0.10",
"@types/photoswipe": "^4.1.6",
"@typescript-eslint/eslint-plugin": "^8.38.0",
"@typescript-eslint/parser": "^8.38.0",
"eslint": "^9.30.1",
Expand Down
1 change: 1 addition & 0 deletions common/src/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './client/index';
39 changes: 39 additions & 0 deletions common/src/client/blurhash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { decode } from 'blurhash';

/**
* Decode a single blurhash and draw it to a canvas element.
*
* @param canvas - The canvas element with a data-blur-hash attribute
* @param width - The width to decode at (default: 32)
* @param height - The height to decode at (default: 32)
*/
export function decodeBlurhashToCanvas(canvas: HTMLCanvasElement, width: number = 32, height: number = 32): void {
const blurHashValue = canvas.dataset.blurHash;
if (!blurHashValue) return;

const pixels = decode(blurHashValue, width, height);
const ctx = canvas.getContext('2d');
if (pixels && ctx) {
const imageData = new ImageData(new Uint8ClampedArray(pixels), width, height);
ctx.putImageData(imageData, 0, 0);
}
}

/**
* Decode and render all blurhash canvases on the page.
* Finds all canvas elements with data-blur-hash attribute and draws the decoded image.
*
* @param selector - CSS selector for canvas elements (default: 'canvas[data-blur-hash]')
* @param width - The width to decode at (default: 32)
* @param height - The height to decode at (default: 32)
*/
export function decodeAllBlurhashes(
selector: string = 'canvas[data-blur-hash]',
width: number = 32,
height: number = 32,
): void {
const canvases = document.querySelectorAll<HTMLCanvasElement>(selector);
for (const canvas of canvases) {
decodeBlurhashToCanvas(canvas, width, height);
}
}
71 changes: 71 additions & 0 deletions common/src/client/css-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* CSS utility functions for client-side theming and color manipulation.
* These utilities are browser-only and require DOM access.
*/

/**
* Normalizes hex color values to 6-digit format (e.g., #abc -> #aabbcc).
* Returns null if the hex value is invalid.
*
* @param hex - The hex color value to normalize (with or without #)
* @returns The normalized 6-digit hex color with # prefix, or null if invalid
*/
export function normalizeHex(hex: string): string | null {
hex = hex.replace('#', '');
if (hex.length === 3) hex = [...hex].map((c) => c + c).join('');
return hex.length === 6 && /^[0-9A-Fa-f]{6}$/.test(hex) ? `#${hex}` : null;
}

/**
* Parses and validates a color value.
* Supports CSS color names, hex values, rgb/rgba, and 'transparent'.
* Returns null if the color is invalid.
*
* @param colorParam - The color string to parse
* @returns The validated color string, or null if invalid
*/
export function parseColor(colorParam: string | null): string | null {
if (!colorParam) return null;
const normalized = colorParam.toLowerCase().trim();
if (normalized === 'transparent') return 'transparent';

const testEl = document.createElement('div');
testEl.style.color = normalized;
if (testEl.style.color) return normalized;

return normalizeHex(colorParam);
}

/**
* Sets or removes a CSS custom property (variable) on an element.
* Removes the property if value is null.
*
* @param element - The HTML element to modify
* @param name - The CSS variable name (e.g., '--my-color')
* @param value - The value to set, or null to remove
*/
export function setCSSVar(element: HTMLElement, name: string, value: string | null): void {
if (value) {
element.style.setProperty(name, value);
} else {
element.style.removeProperty(name);
}
}

/**
* Derives a color with adjusted opacity from an existing color.
* Converts rgb to rgba if needed, or adjusts existing rgba opacity.
*
* @param color - The source color (rgb, rgba, or other CSS color)
* @param opacity - The target opacity (0-1)
* @returns The color with adjusted opacity, or original if not rgb/rgba
*/
export function deriveOpacityColor(color: string, opacity: number): string {
if (color.startsWith('rgba')) {
return color.replace(/,\s*[\d.]+\)$/, `, ${opacity})`);
}
if (color.startsWith('rgb')) {
return color.replace('rgb', 'rgba').replace(')', `, ${opacity})`);
}
return color;
}
98 changes: 98 additions & 0 deletions common/src/client/hero-fallback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/** Options for the hero image fallback behavior */
export interface HeroImageFallbackOptions {
/** CSS selector for the picture element (default: '#hero-bg-picture') */
pictureSelector?: string;
/** CSS selector for the img element within picture (default: 'img.hero__bg-img') */
imgSelector?: string;
/** CSS selector for the blurhash canvas element (default: 'canvas[data-blur-hash]') */
canvasSelector?: string;
}

/**
* Initialize hero image fallback behavior.
* Handles:
* - Hiding blurhash canvas when image loads successfully
* - Removing source elements and retrying with fallback src on error
* - Keeping blurhash visible if final fallback also fails
*
* @param options - Configuration options for selectors
*
* @example
* ```typescript
* import { initHeroImageFallback } from '@simple-photo-gallery/common/client';
*
* // Use default selectors
* initHeroImageFallback();
*
* // Or with custom selectors
* initHeroImageFallback({
* pictureSelector: '#my-hero-picture',
* imgSelector: 'img.my-hero-img',
* canvasSelector: 'canvas.my-blurhash',
* });
* ```
*/
export function initHeroImageFallback(options: HeroImageFallbackOptions = {}): void {
const {
pictureSelector = '#hero-bg-picture',
imgSelector = 'img.hero__bg-img',
canvasSelector = 'canvas[data-blur-hash]',
} = options;

const picture = document.querySelector<HTMLPictureElement>(pictureSelector);
const img = picture?.querySelector<HTMLImageElement>(imgSelector);
const canvas = document.querySelector<HTMLCanvasElement>(canvasSelector);

if (!img) return;

const fallbackSrc = img.getAttribute('src') || '';
let didFallback = false;

const hideBlurhash = () => {
if (canvas) {
canvas.style.display = 'none';
}
};

const doFallback = () => {
if (didFallback) return;
didFallback = true;

if (picture) {
// Remove all <source> elements so the browser does not retry them
for (const sourceEl of picture.querySelectorAll('source')) {
sourceEl.remove();
}
}

// Force reload using the <img> src as the final fallback
const current = img.getAttribute('src') || '';
img.setAttribute('src', '');
img.setAttribute('src', fallbackSrc || current);

// If fallback also fails, keep blurhash visible
img.addEventListener(
'error',
() => {
// Final fallback failed, blurhash stays visible
},
{ once: true },
);

// If fallback succeeds, hide blurhash
img.addEventListener('load', hideBlurhash, { once: true });
};

// Check if image already loaded or failed before script runs
if (img.complete) {
if (img.naturalWidth === 0) {
doFallback();
} else {
hideBlurhash();
}
} else {
img.addEventListener('load', hideBlurhash, { once: true });
}

img.addEventListener('error', doFallback, { once: true });
}
Loading