image utils | cache | components
This commit is contained in:
parent
ecde90f89d
commit
c00cae6fde
@ -37,7 +37,7 @@ const NonProperties = {
|
||||
|
||||
const ImgProperties = NonGlobalSupportedConfigs.filter(
|
||||
(key) => !NonProperties.Img.includes(key)
|
||||
),
|
||||
),
|
||||
PictureProperties = NonGlobalSupportedConfigs.filter(
|
||||
(key) => !NonProperties.Picture.includes(key)
|
||||
),
|
||||
@ -65,14 +65,33 @@ export default function getFilteredProps(type, props) {
|
||||
|
||||
const { search, searchParams } = new URL(props.src, "file://");
|
||||
|
||||
const paramOptions = Object.fromEntries(searchParams);
|
||||
|
||||
// Separate supported config params from others (like cache busters)
|
||||
const supportedKeys = SupportedProperties[type] || [];
|
||||
const configParams = {};
|
||||
const otherParams = new URLSearchParams();
|
||||
|
||||
for (const [key, value] of searchParams) {
|
||||
if (supportedKeys.includes(key) || GlobalConfigOptions[key]) {
|
||||
configParams[key] = value;
|
||||
} else {
|
||||
otherParams.append(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove ALL params from src initially
|
||||
props.src = props.src.replace(search, "");
|
||||
|
||||
const paramOptions = Object.fromEntries(searchParams);
|
||||
// Re-append ONLY the non-config params to src
|
||||
if (otherParams.toString()) {
|
||||
props.src += `?${otherParams.toString()}`;
|
||||
}
|
||||
|
||||
const filteredLocalProps = filterConfigs(
|
||||
type,
|
||||
{
|
||||
...paramOptions,
|
||||
...configParams,
|
||||
...props,
|
||||
},
|
||||
SupportedProperties[type]
|
||||
|
||||
@ -26,8 +26,23 @@ export default async function getSrcset(
|
||||
.join("")
|
||||
: "";
|
||||
|
||||
const [cleanSrc] = src.split("?");
|
||||
const id = `${cleanSrc}?${params.slice(1)}`;
|
||||
// Extract existing search params from src
|
||||
const [cleanSrc, search] = src.split("?");
|
||||
|
||||
// Combine existing params (like s=...) with options
|
||||
// Existing params come first so they can be overridden by options if needed,
|
||||
// though 's' should typically be unique.
|
||||
const searchParams = new URLSearchParams(search);
|
||||
|
||||
// Add options to searchParams
|
||||
keys.forEach(key => {
|
||||
const value = Array.isArray(options[key])
|
||||
? options[key].join(";")
|
||||
: options[key];
|
||||
searchParams.set(key, value);
|
||||
});
|
||||
|
||||
const id = `${cleanSrc}?${searchParams.toString()}`;
|
||||
// @todo : remove this
|
||||
const fullPath = await getSrcPath(id);
|
||||
const { default: load } = await import("../../plugin/hooks/load.js");
|
||||
|
||||
@ -2,9 +2,18 @@
|
||||
import renderPicture from "../api/renderPicture.js";
|
||||
import type { PictureConfigOptions } from "../types.d";
|
||||
|
||||
declare interface Props extends PictureConfigOptions {}
|
||||
declare interface Props extends PictureConfigOptions {
|
||||
s?: string;
|
||||
}
|
||||
|
||||
const { link, style, picture } = await renderPicture(Astro.props as Props);
|
||||
const { s, ...rest } = Astro.props as Props;
|
||||
|
||||
if (s) {
|
||||
const separator = rest.src.includes("?") ? "&" : "?";
|
||||
rest.src = `${rest.src}${separator}s=${s}`;
|
||||
}
|
||||
|
||||
const { link, style, picture } = await renderPicture(rest);
|
||||
---
|
||||
|
||||
<Fragment set:html={link + style + picture} />
|
||||
|
||||
@ -88,7 +88,6 @@ export default {
|
||||
await pMap(
|
||||
[...allAssets.entries()],
|
||||
async ([assetPath, { hash, image, buffer }]) => {
|
||||
logger.info(`[imagetools] Processing image ${assetPath}...`);
|
||||
try {
|
||||
await saveAndCopyAsset(
|
||||
hash,
|
||||
|
||||
@ -42,9 +42,10 @@ export default async function load(id) {
|
||||
path.relative("", src).split(path.sep).join(path.posix.sep)
|
||||
);
|
||||
|
||||
// Include search params in the hash calculation to force re-processing on change
|
||||
const getHash = (width) =>
|
||||
objectHash(
|
||||
{ width, options, rootRelativePosixSrc },
|
||||
{ width, options, rootRelativePosixSrc, search },
|
||||
// @ts-ignore
|
||||
{ algorithm: "sha256" }
|
||||
);
|
||||
@ -56,8 +57,11 @@ export default async function load(id) {
|
||||
|
||||
const config = Object.fromEntries(searchParams);
|
||||
|
||||
// Use the full ID (including search params) + src for the store key
|
||||
// This ensures that the same file with different params is treated as different source
|
||||
const storeKey = src + search;
|
||||
const { image: loadedImage, width: imageWidth } =
|
||||
store.get(src) || store.set(src, await getLoadedImage(src, ext)).get(src);
|
||||
store.get(storeKey) || store.set(storeKey, await getLoadedImage(src, ext)).get(storeKey);
|
||||
|
||||
const { type, widths, options, extension, raw, inline } = getConfigOptions(
|
||||
config,
|
||||
|
||||
@ -13,16 +13,16 @@ export const items = async (opts: { locale: string }) => {
|
||||
"class": "hover:text-orange-600"
|
||||
},
|
||||
{
|
||||
"href": `/resources/home`,
|
||||
"href": `/${opts.locale}/resources/home`,
|
||||
"title": _T("Resources"),
|
||||
"ariaLabel": "Resources",
|
||||
"class": "hover:text-orange-600"
|
||||
}
|
||||
]
|
||||
}
|
||||
export const footer_left = async ( locale: string ) => {
|
||||
export const footer_left = async (locale: string) => {
|
||||
const _T = async (text: string) => await translate(text, I18N_SOURCE_LANGUAGE, locale)
|
||||
return await pMap(config.footer_left, async (item:any) => {
|
||||
return await pMap(config.footer_left, async (item: any) => {
|
||||
return {
|
||||
"href": `${item.href}`,
|
||||
"title": await _T(item.text),
|
||||
@ -31,9 +31,9 @@ export const footer_left = async ( locale: string ) => {
|
||||
}
|
||||
});
|
||||
}
|
||||
export const footer_right = async ( locale: string ) => {
|
||||
export const footer_right = async (locale: string) => {
|
||||
const _T = async (text: string) => await translate(text, I18N_SOURCE_LANGUAGE, locale)
|
||||
return await pMap(config.footer_right, async (item:any) => {
|
||||
return await pMap(config.footer_right, async (item: any) => {
|
||||
return {
|
||||
"href": `/${item.href}`,
|
||||
"title": await _T(item.text),
|
||||
|
||||
@ -9,33 +9,38 @@ interface Props {
|
||||
|
||||
const {
|
||||
currentPath,
|
||||
collection = '',
|
||||
title = '',
|
||||
separator = '/',
|
||||
showHome = true
|
||||
collection = "",
|
||||
title = "",
|
||||
separator = "/",
|
||||
showHome = true,
|
||||
} = Astro.props;
|
||||
|
||||
// Parse the current path to generate breadcrumb items
|
||||
function generateBreadcrumbs(path: string, collection: string, pageTitle?: string) {
|
||||
const segments = path.split('/').filter(segment => segment !== '');
|
||||
const breadcrumbs: Array<{ label: string; href?: string; isLast?: boolean }> = [];
|
||||
function generateBreadcrumbs(
|
||||
path: string,
|
||||
collection: string,
|
||||
pageTitle?: string,
|
||||
) {
|
||||
const segments = path.split("/").filter((segment) => segment !== "");
|
||||
const breadcrumbs: Array<{ label: string; href?: string; isLast?: boolean }> =
|
||||
[];
|
||||
|
||||
// Add home if enabled
|
||||
if (showHome) {
|
||||
breadcrumbs.push({ label: 'Home', href: '/' });
|
||||
breadcrumbs.push({ label: "Home", href: "/" });
|
||||
}
|
||||
|
||||
// Build path segments
|
||||
let currentHref = '';
|
||||
let currentHref = "";
|
||||
segments.forEach((segment, index) => {
|
||||
currentHref += `/${segment}`;
|
||||
const isLast = index === segments.length - 1;
|
||||
|
||||
// Format segment label
|
||||
let label = segment
|
||||
.split('-')
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||
.join(' ');
|
||||
.split("-")
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||
.join(" ");
|
||||
|
||||
// Use page title for the last segment if provided
|
||||
if (isLast && pageTitle) {
|
||||
@ -44,8 +49,8 @@ function generateBreadcrumbs(path: string, collection: string, pageTitle?: strin
|
||||
|
||||
breadcrumbs.push({
|
||||
label,
|
||||
href: isLast ? undefined : currentHref + '/',
|
||||
isLast
|
||||
href: isLast ? undefined : currentHref + "/",
|
||||
isLast,
|
||||
});
|
||||
});
|
||||
|
||||
@ -57,7 +62,8 @@ const breadcrumbs = generateBreadcrumbs(currentPath, collection, title);
|
||||
|
||||
<nav class="breadcrumb" aria-label="Breadcrumb navigation">
|
||||
<ol class="breadcrumb-list">
|
||||
{breadcrumbs.map((crumb, index) => (
|
||||
{
|
||||
breadcrumbs.map((crumb, index) => (
|
||||
<li class="breadcrumb-item">
|
||||
{crumb.href ? (
|
||||
<a
|
||||
@ -78,7 +84,8 @@ const breadcrumbs = generateBreadcrumbs(currentPath, collection, title);
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
))
|
||||
}
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
@ -103,10 +110,10 @@ const breadcrumbs = generateBreadcrumbs(currentPath, collection, title);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.breadcrumb-link {
|
||||
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
@ -126,7 +133,6 @@ const breadcrumbs = generateBreadcrumbs(currentPath, collection, title);
|
||||
}
|
||||
|
||||
.breadcrumb-current {
|
||||
|
||||
font-weight: 500;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
|
||||
@ -10,6 +10,7 @@ interface Image {
|
||||
src: string
|
||||
title?: string
|
||||
description?: string
|
||||
hash?: string
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
@ -29,9 +30,10 @@ export interface Props {
|
||||
SHOW_TITLE?: boolean;
|
||||
SHOW_DESCRIPTION?: boolean;
|
||||
};
|
||||
s?: string;
|
||||
}
|
||||
|
||||
const { images, gallerySettings = {}, lightboxSettings = {} } = Astro.props;
|
||||
const { images, gallerySettings = {}, lightboxSettings = {}, s } = Astro.props;
|
||||
|
||||
const mergedGallerySettings = {
|
||||
SIZES_REGULAR: gallerySettings.SIZES_REGULAR || IMAGE_SETTINGS.GALLERY.SIZES_REGULAR,
|
||||
@ -46,7 +48,7 @@ const mergedLightboxSettings = {
|
||||
SHOW_DESCRIPTION: lightboxSettings.SHOW_DESCRIPTION ?? IMAGE_SETTINGS.LIGHTBOX.SHOW_DESCRIPTION,
|
||||
};
|
||||
const locale = Astro.currentLocale || "en";
|
||||
console.log(`LGallery Images`, images)
|
||||
|
||||
---
|
||||
|
||||
<div
|
||||
@ -138,6 +140,8 @@ console.log(`LGallery Images`, images)
|
||||
format="avif"
|
||||
placeholder="blurred"
|
||||
sizes={mergedGallerySettings.SIZES_REGULAR}
|
||||
sizes={mergedGallerySettings.SIZES_REGULAR}
|
||||
s={s || image.hash}
|
||||
attributes={{
|
||||
img: { class: "main-image p-4 rounded-lg max-h-[60vh] aspect-square" }
|
||||
}}
|
||||
@ -180,6 +184,7 @@ console.log(`LGallery Images`, images)
|
||||
}
|
||||
}}
|
||||
loading="lazy"
|
||||
s={s || image.hash}
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
@ -208,6 +213,8 @@ console.log(`LGallery Images`, images)
|
||||
format="avif"
|
||||
objectFit="contain"
|
||||
sizes={IMAGE_SETTINGS.LIGHTBOX.SIZES_LARGE}
|
||||
sizes={IMAGE_SETTINGS.LIGHTBOX.SIZES_LARGE}
|
||||
s={s || image.hash}
|
||||
attributes={{
|
||||
img: { class: "max-w-[90vw] max-h-[90vh] object-contain rounded-lg lightbox-main" }
|
||||
}}
|
||||
|
||||
@ -2,10 +2,7 @@
|
||||
import Wrapper from "@/components/containers/Wrapper.astro";
|
||||
import { footer_left, footer_right } from "@/app/navigation.js";
|
||||
import { ISO_LANGUAGE_LABELS } from "@polymech/i18n";
|
||||
import {
|
||||
LANGUAGES_PROD,
|
||||
I18N_SOURCE_LANGUAGE,
|
||||
} from "config/config.js";
|
||||
import { LANGUAGES_PROD, I18N_SOURCE_LANGUAGE } from "config/config.js";
|
||||
|
||||
const locale = Astro.currentLocale || I18N_SOURCE_LANGUAGE;
|
||||
const currentUrl = new URL(Astro.url);
|
||||
@ -16,7 +13,7 @@ const currentUrl = new URL(Astro.url);
|
||||
* @returns {string[]} The URL path segments without the language code (if present).
|
||||
*/
|
||||
const getCleanPathSegments = (url) => {
|
||||
const segments = url.pathname.split('/').filter(Boolean);
|
||||
const segments = url.pathname.split("/").filter(Boolean);
|
||||
if (segments.length && LANGUAGES_PROD.includes(segments[0])) {
|
||||
segments.shift();
|
||||
}
|
||||
@ -32,31 +29,30 @@ const getCleanPathSegments = (url) => {
|
||||
const buildLocalizedUrl = (lang, segments) => {
|
||||
const newUrl = new URL(Astro.url);
|
||||
// Prepend the language code and join with existing segments, removing any trailing slash.
|
||||
newUrl.pathname = `/${lang}/${segments.join('/')}`.replace(/\/+$/, '');
|
||||
newUrl.pathname = `/${lang}/${segments.join("/")}`.replace(/\/+$/, "");
|
||||
return newUrl.toString();
|
||||
};
|
||||
|
||||
const cleanSegments = getCleanPathSegments(currentUrl);
|
||||
|
||||
const languages = LANGUAGES_PROD.filter(
|
||||
(lang) => lang !== locale,
|
||||
).map((lang) => ({
|
||||
const languages = LANGUAGES_PROD.filter((lang) => lang !== locale).map(
|
||||
(lang) => ({
|
||||
lang: ISO_LANGUAGE_LABELS[lang] || lang,
|
||||
url: buildLocalizedUrl(lang, cleanSegments),
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
const footerLeft = await footer_left(locale);
|
||||
const footerRight = await footer_right(locale);
|
||||
|
||||
---
|
||||
|
||||
<Wrapper variant="standard" class="py-12">
|
||||
<footer class="py-2">
|
||||
<div class="p-4 xl:pb-0 bg-white dark:bg-gray-800 overflow-hidden rounded-xl">
|
||||
<div class="grid md:grid-cols-3 gap-6">
|
||||
<div
|
||||
class="flex flex-col h-full justify-between xl:pb-2 order-last md:order-none"
|
||||
class="p-4 xl:pb-0 bg-white dark:bg-gray-800 overflow-hidden rounded-xl"
|
||||
>
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-6">
|
||||
<div class="flex flex-col gap-4 h-full justify-between xl:pb-2">
|
||||
<nav role="navigation">
|
||||
<ul class="text-xs space-y-1 uppercase dark:text-gray-400">
|
||||
{
|
||||
@ -69,20 +65,24 @@ const footerRight = await footer_right(locale);
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
<div class="mt-2">
|
||||
<div
|
||||
class="flex flex-col space-y-1 text-xs uppercase dark:text-gray-400"
|
||||
>
|
||||
{
|
||||
languages.map((link) => (
|
||||
<span>
|
||||
<a class=" hover:text-orange-500 p-2 dark:text-gray-400" href={link.url}>
|
||||
<a class="hover:text-orange-500 block" href={link.url}>
|
||||
{link.lang}
|
||||
</a>
|
||||
</span><br/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</nav>
|
||||
<div class="flex gap-4 mt-12 items-center">
|
||||
<img src="/logos/transparent.svg" alt="logo" class="size-4" />
|
||||
<div class="flex gap-4 mt-8 md:mt-12 items-center">
|
||||
<img
|
||||
src="/logos/transparent.svg"
|
||||
alt="logo"
|
||||
class="size-4 hidden md:block"
|
||||
/>
|
||||
<p
|
||||
class="text-xs leading-5 text-neutral-400 dark:text-gray-500 text-pretty uppercase"
|
||||
>
|
||||
@ -93,11 +93,10 @@ const footerRight = await footer_right(locale);
|
||||
<img
|
||||
src="/logos/transparent.svg"
|
||||
alt="logo"
|
||||
class="size-12 md:mx-auto fill-orange-600"
|
||||
class="size-12 md:mx-auto fill-orange-600 hidden md:block"
|
||||
/>
|
||||
|
||||
|
||||
<div class="flex flex-col h-full justify-between md:text-right xl:pb-2">
|
||||
<div class="flex flex-col h-full justify-between text-right xl:pb-2">
|
||||
<nav role="navigation">
|
||||
<ul class="text-xs space-y-1 uppercase dark:text-gray-400">
|
||||
{
|
||||
|
||||
@ -1,20 +1,25 @@
|
||||
---
|
||||
import * as path from "path"
|
||||
import { sync as fileExists } from "@polymech/fs/exists"
|
||||
import * as path from "path";
|
||||
import { sync as fileExists } from "@polymech/fs/exists";
|
||||
|
||||
import { specs } from "@/base/specs.js"
|
||||
import { specs } from "@/base/specs.js";
|
||||
|
||||
import { render, logger } from "@/base/index.js"
|
||||
import { translateSheets } from "@/base/i18n.js"
|
||||
import { I18N_SOURCE_LANGUAGE, LANGUAGES, PRODUCT_SPECS } from "config/config.js"
|
||||
import { createComponent } from "astro/runtime/server/astro-component.js";
|
||||
import { renderTemplate, unescapeHTML } from "astro/runtime/server/index.js";
|
||||
|
||||
import DefaultComponent from "./Default.astro"
|
||||
import { logger } from "@/base/index.js";
|
||||
import { translateSheets } from "@/base/i18n.js";
|
||||
import {
|
||||
I18N_SOURCE_LANGUAGE,
|
||||
LANGUAGES,
|
||||
PRODUCT_SPECS,
|
||||
} from "config/config.js";
|
||||
|
||||
const { frontmatter: data } = Astro.props
|
||||
const locale = Astro.currentLocale || I18N_SOURCE_LANGUAGE
|
||||
import DefaultComponent from "./Default.astro";
|
||||
const { frontmatter: data } = Astro.props;
|
||||
const locale = Astro.currentLocale || I18N_SOURCE_LANGUAGE;
|
||||
|
||||
const specs_table = async (relPath) =>
|
||||
{
|
||||
const specs_table = async (relPath) => {
|
||||
let specsPath = path.join(PRODUCT_SPECS(relPath));
|
||||
if (!fileExists(specsPath)) {
|
||||
logger.debug(`No specs found for ${specsPath}`);
|
||||
@ -25,19 +30,26 @@ const specs_table = async (relPath) =>
|
||||
if (!i18n) {
|
||||
logger.debug(`No i18n found for ${relPath} : ${locale}`);
|
||||
} else {
|
||||
specsPath = i18n
|
||||
specsPath = i18n;
|
||||
}
|
||||
}
|
||||
return specs(specsPath)
|
||||
}
|
||||
const tableHTML = await specs_table(data.rel)
|
||||
let SpecsComponent
|
||||
return specs(specsPath);
|
||||
};
|
||||
|
||||
const render = async (string) => {
|
||||
const html = `${unescapeHTML(string)}`;
|
||||
return createComponent(() => renderTemplate(html as any, []));
|
||||
};
|
||||
|
||||
const tableHTML = await specs_table(data.rel);
|
||||
let SpecsComponent;
|
||||
if (tableHTML) {
|
||||
SpecsComponent = await render(tableHTML)
|
||||
SpecsComponent = await render(tableHTML);
|
||||
} else {
|
||||
SpecsComponent = DefaultComponent
|
||||
SpecsComponent = DefaultComponent;
|
||||
}
|
||||
---
|
||||
|
||||
<div class="bg-white rounded-xl specs specs-table">
|
||||
<SpecsComponent />
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user