@simple-photo-gallery/theme-modern 2.0.3 → 2.0.10-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simple-photo-gallery/theme-modern",
3
- "version": "2.0.3",
3
+ "version": "2.0.10-rc.2",
4
4
  "description": "Modern theme for Simple Photo Gallery",
5
5
  "license": "MIT",
6
6
  "author": "Vladimir Haltakov, Tomasz Rusin",
@@ -1,7 +1,8 @@
1
1
  ---
2
- import type { GallerySection as GallerySectionType } from '@simple-photo-gallery/common';
3
2
  import { renderMarkdown } from '@/lib/markdown';
4
3
 
4
+ import type { GallerySection as GallerySectionType } from '@simple-photo-gallery/common';
5
+
5
6
  interface Props {
6
7
  section: GallerySectionType;
7
8
  }
@@ -19,8 +19,8 @@ const captionHtml = parsedCaption ? `<div class="image-caption markdown-content"
19
19
 
20
20
  <a
21
21
  class="gallery-section__item"
22
- href={getPhotoPath(image.path, mediaBaseUrl)}
23
- data-pswp-src={getPhotoPath(image.path, mediaBaseUrl)}
22
+ href={getPhotoPath(image.filename, mediaBaseUrl)}
23
+ data-pswp-src={getPhotoPath(image.filename, mediaBaseUrl)}
24
24
  data-pswp-width={image.width}
25
25
  data-pswp-height={image.height}
26
26
  data-pswp-type={image.type}
@@ -1,69 +1,79 @@
1
1
  ---
2
+ import path from 'node:path';
3
+
2
4
  import HeroScrollToGalleryBtn from '@/features/themes/base-theme/components/hero/HeroScrollToGalleryBtn.astro';
3
5
  import { renderMarkdown } from '@/lib/markdown';
4
6
 
5
7
  interface Props {
6
8
  title: string;
7
9
  description?: string;
10
+ headerImage?: string;
11
+ headerImageBlurHash?: string;
8
12
  }
9
13
 
10
- const { title, description } = Astro.props;
14
+ const { title, description, headerImage, headerImageBlurHash } = Astro.props;
11
15
 
12
16
  // Parse description as Markdown if it exists
13
17
  const parsedDescription: string = description ? await renderMarkdown(description) : '';
18
+
19
+ // Extract basename from headerImage filename, fallback to generic name
20
+ const imgBasename = headerImage ? path.basename(headerImage, path.extname(headerImage)) : 'header';
14
21
  ---
15
22
 
16
23
  <section class="hero">
17
- <picture class="hero__bg">
18
- <!-- Portrait -->
19
- <source
20
- type="image/avif"
21
- media="(max-aspect-ratio: 3/4)"
22
- srcset="
23
- gallery/images/header_portrait_360.avif 360w,
24
- gallery/images/header_portrait_480.avif 480w,
25
- gallery/images/header_portrait_720.avif 720w,
26
- gallery/images/header_portrait_1080.avif 1080w"
27
- sizes="(max-aspect-ratio: 3/4) 160vw, 100vw"
28
- />
29
- <source
30
- type="image/jpeg"
31
- media="(max-aspect-ratio: 3/4)"
32
- srcset="
33
- gallery/images/header_portrait_360.jpg 360w,
34
- gallery/images/header_portrait_480.jpg 480w,
35
- gallery/images/header_portrait_720.jpg 720w,
36
- gallery/images/header_portrait_1080.jpg 1080w"
37
- sizes="(max-aspect-ratio: 3/4) 160vw, 100vw"
38
- />
39
-
40
- <!-- Landscape -->
41
- <source
42
- type="image/avif"
43
- srcset="
44
- gallery/images/header_landscape_640.avif 640w,
45
- gallery/images/header_landscape_960.avif 960w,
46
- gallery/images/header_landscape_1280.avif 1280w,
47
- gallery/images/header_landscape_1920.avif 1920w,
48
- gallery/images/header_landscape_2560.avif 2560w,
49
- gallery/images/header_landscape_3840.avif 3840w"
50
- sizes="100vw"
51
- />
52
- <source
53
- type="image/jpg"
54
- srcset="
55
- gallery/images/header_landscape_640.jpg 640w,
56
- gallery/images/header_landscape_960.jpg 960w,
57
- gallery/images/header_landscape_1280.jpg 1280w,
58
- gallery/images/header_landscape_1920.jpg 1920w,
59
- gallery/images/header_landscape_2560.jpg 2560w,
60
- gallery/images/header_landscape_3840.jpg 3840w"
61
- sizes="100vw"
62
- />
63
-
64
- <!-- Fallback -->
65
- <img src="gallery/images/header_landscape_1920.jpg" alt={title} />
66
- </picture>
24
+ <div class="hero__bg-wrapper">
25
+ {headerImageBlurHash && <canvas data-blur-hash={headerImageBlurHash} width={32} height={32} ></canvas>}
26
+ <picture class="hero__bg">
27
+ <!-- Portrait -->
28
+ <source
29
+ type="image/avif"
30
+ media="(max-aspect-ratio: 3/4)"
31
+ srcset={`
32
+ gallery/images/${imgBasename}_portrait_360.avif 360w,
33
+ gallery/images/${imgBasename}_portrait_480.avif 480w,
34
+ gallery/images/${imgBasename}_portrait_720.avif 720w,
35
+ gallery/images/${imgBasename}_portrait_1080.avif 1080w`}
36
+ sizes="(max-aspect-ratio: 3/4) 160vw, 100vw"
37
+ />
38
+ <source
39
+ type="image/jpeg"
40
+ media="(max-aspect-ratio: 3/4)"
41
+ srcset={`
42
+ gallery/images/${imgBasename}_portrait_360.jpg 360w,
43
+ gallery/images/${imgBasename}_portrait_480.jpg 480w,
44
+ gallery/images/${imgBasename}_portrait_720.jpg 720w,
45
+ gallery/images/${imgBasename}_portrait_1080.jpg 1080w`}
46
+ sizes="(max-aspect-ratio: 3/4) 160vw, 100vw"
47
+ />
48
+
49
+ <!-- Landscape -->
50
+ <source
51
+ type="image/avif"
52
+ srcset={`
53
+ gallery/images/${imgBasename}_landscape_640.avif 640w,
54
+ gallery/images/${imgBasename}_landscape_960.avif 960w,
55
+ gallery/images/${imgBasename}_landscape_1280.avif 1280w,
56
+ gallery/images/${imgBasename}_landscape_1920.avif 1920w,
57
+ gallery/images/${imgBasename}_landscape_2560.avif 2560w,
58
+ gallery/images/${imgBasename}_landscape_3840.avif 3840w`}
59
+ sizes="100vw"
60
+ />
61
+ <source
62
+ type="image/jpg"
63
+ srcset={`
64
+ gallery/images/${imgBasename}_landscape_640.jpg 640w,
65
+ gallery/images/${imgBasename}_landscape_960.jpg 960w,
66
+ gallery/images/${imgBasename}_landscape_1280.jpg 1280w,
67
+ gallery/images/${imgBasename}_landscape_1920.jpg 1920w,
68
+ gallery/images/${imgBasename}_landscape_2560.jpg 2560w,
69
+ gallery/images/${imgBasename}_landscape_3840.jpg 3840w`}
70
+ sizes="100vw"
71
+ />
72
+
73
+ <!-- Fallback -->
74
+ <img src={`gallery/images/${imgBasename}_landscape_1920.jpg`} class="hero__bg-img" />
75
+ </picture>
76
+ </div>
67
77
  <div class="hero__overlay"></div>
68
78
  <div class="hero__content">
69
79
  <h1 class="hero__title">{title}</h1>
@@ -83,6 +93,14 @@ const parsedDescription: string = description ? await renderMarkdown(description
83
93
  overflow: hidden;
84
94
  }
85
95
 
96
+ .hero__bg-wrapper {
97
+ position: absolute;
98
+ top: 0;
99
+ left: 0;
100
+ width: 100%;
101
+ height: 100%;
102
+ }
103
+
86
104
  .hero__bg {
87
105
  position: absolute;
88
106
  top: 0;
@@ -91,13 +109,23 @@ const parsedDescription: string = description ? await renderMarkdown(description
91
109
  height: 100%;
92
110
  }
93
111
 
94
- .hero__bg img {
112
+ .hero__bg-img {
95
113
  width: 100%;
96
114
  height: 100%;
97
115
  object-fit: cover;
98
116
  object-position: center;
99
117
  }
100
118
 
119
+ .hero__bg-wrapper canvas {
120
+ position: absolute;
121
+ top: 0;
122
+ left: 0;
123
+ width: 100%;
124
+ height: 100%;
125
+ display: block;
126
+ transition: transform 0.5s ease;
127
+ }
128
+
101
129
  .hero__overlay {
102
130
  position: absolute;
103
131
  top: 0;
@@ -6,9 +6,13 @@ interface Props {
6
6
  description?: string;
7
7
  url?: string;
8
8
  metadata?: GalleryMetadata;
9
+ headerImageBasename?: string;
9
10
  }
10
11
 
11
- const { title, description, url, metadata } = Astro.props;
12
+ const { title, description, url, metadata, headerImageBasename } = Astro.props;
13
+
14
+ // Use headerImageBasename for dynamic image paths, fallback to generic name
15
+ const imgBasename = headerImageBasename || 'header';
12
16
  ---
13
17
 
14
18
  <head>
@@ -74,7 +78,7 @@ const { title, description, url, metadata } = Astro.props;
74
78
  as="image"
75
79
  type="image/avif"
76
80
  media="(max-aspect-ratio: 3/4)"
77
- imagesrcset="gallery/images/header_portrait_360.avif 360w, gallery/images/header_portrait_480.avif 480w, gallery/images/header_portrait_720.avif 720w, gallery/images/header_portrait_1080.avif 1080w"
81
+ imagesrcset={`gallery/images/${imgBasename}_portrait_360.avif 360w, gallery/images/${imgBasename}_portrait_480.avif 480w, gallery/images/${imgBasename}_portrait_720.avif 720w, gallery/images/${imgBasename}_portrait_1080.avif 1080w`}
78
82
  imagesizes="(max-aspect-ratio: 3/4) 160vw, 100vw"
79
83
  fetchpriority="high"
80
84
  />
@@ -83,7 +87,7 @@ const { title, description, url, metadata } = Astro.props;
83
87
  as="image"
84
88
  type="image/avif"
85
89
  media="(min-aspect-ratio: 3/4)"
86
- imagesrcset="gallery/images/header_landscape_640.avif 640w, gallery/images/header_landscape_960.avif 960w, gallery/images/header_landscape_1280.avif 1280w, gallery/images/header_landscape_1920.avif 1920w, gallery/images/header_landscape_2560.avif 2560w, gallery/images/header_landscape_3840.avif 3840w"
90
+ imagesrcset={`gallery/images/${imgBasename}_landscape_640.avif 640w, gallery/images/${imgBasename}_landscape_960.avif 960w, gallery/images/${imgBasename}_landscape_1280.avif 1280w, gallery/images/${imgBasename}_landscape_1920.avif 1920w, gallery/images/${imgBasename}_landscape_2560.avif 2560w, gallery/images/${imgBasename}_landscape_3840.avif 3840w`}
87
91
  imagesizes="100vw"
88
92
  fetchpriority="high"
89
93
  />
@@ -1,4 +1,6 @@
1
1
  ---
2
+ import path from 'node:path';
3
+
2
4
  import MainHead from '@/features/themes/base-theme/layouts/MainHead.astro';
3
5
 
4
6
  import type { GalleryMetadata } from '@simple-photo-gallery/common';
@@ -8,16 +10,23 @@ interface Props {
8
10
  description?: string;
9
11
  url?: string;
10
12
  metadata?: GalleryMetadata;
13
+ analyticsScript?: string;
14
+ headerImage?: string;
11
15
  }
12
16
 
13
- const { title, description, metadata, url } = Astro.props;
17
+ const { title, description, metadata, url, analyticsScript, headerImage } = Astro.props;
18
+
19
+ // Extract basename from headerImage filename
20
+ const headerImageBasename = headerImage ? path.basename(headerImage, path.extname(headerImage)) : undefined;
14
21
  ---
15
22
 
16
23
  <!doctype html>
17
24
  <html lang={metadata?.language || 'en'}>
18
- <MainHead title={title} description={description} metadata={metadata} url={url} />
25
+ <MainHead title={title} description={description} metadata={metadata} url={url} headerImageBasename={headerImageBasename} />
19
26
  <body>
20
27
  <slot />
28
+
29
+ {analyticsScript && <Fragment set:html={analyticsScript} />}
21
30
  </body>
22
31
  </html>
23
32
 
@@ -16,7 +16,7 @@ const galleryData = JSON.parse(fs.readFileSync(galleryJsonPath, 'utf8'));
16
16
 
17
17
  const gallery = galleryData as GalleryData;
18
18
 
19
- const { title, description, metadata, sections, subGalleries, mediaBaseUrl, url } = gallery;
19
+ const { title, description, metadata, sections, subGalleries, mediaBaseUrl, url, analyticsScript, headerImage, headerImageBlurHash } = gallery;
20
20
  ---
21
21
 
22
22
  <script>
@@ -39,8 +39,8 @@ const { title, description, metadata, sections, subGalleries, mediaBaseUrl, url
39
39
  }
40
40
  </script>
41
41
 
42
- <MainLayout title={title} description={description} metadata={metadata} url={url}>
43
- <Hero title={title} description={description} />
42
+ <MainLayout title={title} description={description} metadata={metadata} url={url} analyticsScript={analyticsScript} headerImage={headerImage}>
43
+ <Hero title={title} description={description} headerImage={headerImage} headerImageBlurHash={headerImageBlurHash} />
44
44
 
45
45
  {
46
46
  subGalleries && subGalleries.galleries.length > 0 && (
@@ -19,13 +19,12 @@ export const getRelativePath = (resourcePath: string) => {
19
19
  /**
20
20
  * Get the path to a photo that is always in the gallery root directory.
21
21
  *
22
- * @param resourcePath - The path to the photo on the hard disk
22
+ * @param filename - The filename to get the path for
23
+ * @param mediaBaseUrl - The base URL for the media
23
24
  * @returns The normalized path relative to the gallery root directory
24
25
  */
25
- export const getPhotoPath = (photoPath: string, mediaBaseUrl?: string) => {
26
- const resourceBasename = path.basename(photoPath);
27
-
28
- return mediaBaseUrl ? `${mediaBaseUrl}/${resourceBasename}` : path.join('.', resourceBasename);
26
+ export const getPhotoPath = (filename: string, mediaBaseUrl?: string) => {
27
+ return mediaBaseUrl ? `${mediaBaseUrl}/${filename}` : filename;
29
28
  };
30
29
 
31
30
  /**