diff --git a/packages/polymech/src/base/images.ts b/packages/polymech/src/base/images.ts
index b71fc82..0f540bf 100644
--- a/packages/polymech/src/base/images.ts
+++ b/packages/polymech/src/base/images.ts
@@ -33,6 +33,7 @@ export interface GalleryImage {
height?: number
width?: number
gps?: { lon: number, lat: number }
+ hash?: string
}
export interface MetaJSON {
diff --git a/packages/polymech/src/base/media.ts b/packages/polymech/src/base/media.ts
index 5253824..00d66c6 100644
--- a/packages/polymech/src/base/media.ts
+++ b/packages/polymech/src/base/media.ts
@@ -1,11 +1,10 @@
+import { createHash } from 'node:crypto'
+import { readFileSync } from 'node:fs'
import * as path from 'node:path'
import pMap from 'p-map'
-
import { GlobOptions } from 'glob'
-
import { sanitizeUri } from 'micromark-util-sanitize-uri'
import ExifReader from 'exifreader'
-import fs from 'node:fs'
import { loadImage } from "imagetools/api"
@@ -15,162 +14,16 @@ import { sync as exists } from '@polymech/fs/exists'
import { sync as read } from '@polymech/fs/read'
import { logger } from '@/base/index.js'
-import { GalleryImage, MetaJSON } from '@/base/images.js'
import { removeArrayValues, removeArrays, removeBufferValues, removeEmptyObjects } from '@/base/objects.js'
-import { ITEM_ASSET_URL, PRODUCT_CONFIG, PRODUCT_ROOT, DEFAULT_IMAGE_URL } from '../app/config.js'
-
+import { ITEM_ASSET_URL, PRODUCT_CONFIG, PRODUCT_ROOT, DEFAULT_IMAGE_URL, FILE_SERVER_DEV } from '../app/config.js'
+import { GalleryImage, MetaJSON } from './images.js'
import { env } from './index.js'
-const IMAGES_GLOB = '*.+(JPG|jpg|png|PNG|gif)'
+const IMAGES_GLOB = '*.+(JPG|jpg|png|jpeg|PNG|gif)'
-// Extract date from EXIF data or file stats
-export async function extractImageDate(filePath: string): Promise<{ year: number, dateTaken: Date }> {
- let dateTaken: Date | null = null;
-
- try {
- // Try to get EXIF date
- const exifData = await ExifReader.load(filePath);
- const dateTimeOriginal = exifData?.['DateTimeOriginal']?.description;
- const dateTime = exifData?.['DateTime']?.description;
- const createDate = exifData?.['CreateDate']?.description;
-
- // Parse EXIF date (format: "YYYY:MM:DD HH:MM:SS")
- const exifDateString = dateTimeOriginal || dateTime || createDate;
- if (exifDateString) {
- const [datePart, timePart] = exifDateString.split(' ');
- const [year, month, day] = datePart.split(':').map(Number);
- const [hour, minute, second] = (timePart || '00:00:00').split(':').map(Number);
- dateTaken = new Date(year, month - 1, day, hour, minute, second);
- }
- } catch (e) {
- console.warn(`[extractImageDate] Could not read EXIF data for ${filePath}:`, e);
- }
-
- // Fallback to file modification date
- if (!dateTaken) {
- try {
- const stats = fs.statSync(filePath);
- dateTaken = stats.mtime;
- } catch (e) {
- console.warn(`[extractImageDate] Could not read file stats for ${filePath}:`, e);
- dateTaken = new Date(); // Ultimate fallback
- }
- }
-
- return {
- year: dateTaken.getFullYear(),
- dateTaken
- };
-}
-
-// Predefined grouping functions
-export const groupByYear = (image: { year?: number, dateTaken?: Date }) => {
- const year = image.year || (image.dateTaken ? image.dateTaken.getFullYear() : new Date().getFullYear());
- return {
- key: year.toString(),
- label: year.toString(),
- sortOrder: year
- };
-};
-
-export const groupByMonth = (image: { year?: number, dateTaken?: Date }) => {
- const date = image.dateTaken || new Date();
- const year = date.getFullYear();
- const month = date.getMonth();
- const monthNames = [
- 'January', 'February', 'March', 'April', 'May', 'June',
- 'July', 'August', 'September', 'October', 'November', 'December'
- ];
-
- return {
- key: `${year}-${month.toString().padStart(2, '0')}`,
- label: `${monthNames[month]} ${year}`,
- sortOrder: year * 100 + month
- };
-};
-
-// Type for grouping function result
-export interface GroupInfo {
- key: string;
- label: string;
- sortOrder: number;
-}
-
-// Extract comprehensive metadata from image file
-export async function extractImageMetadata(filePath: string): Promise<{
- year: number;
- dateTaken: Date;
- title: string;
- description: string;
- keywords: string[];
- width?: number;
- height?: number;
- exifRaw?: any;
-}> {
- const fileName = path.basename(filePath, path.extname(filePath));
- const baseDir = path.dirname(filePath);
-
- // Get date information
- const { year, dateTaken } = await extractImageDate(filePath);
-
- // Load companion metadata files
- const metaJsonPath = path.join(baseDir, `${fileName}.json`);
- const metaMarkdownPath = path.join(baseDir, `${fileName}.md`);
-
- let metaJson: any = { alt: "", keywords: "", title: "", description: "" };
- let metaMarkdown = "";
-
- try {
- if (fs.existsSync(metaJsonPath)) {
- metaJson = JSON.parse(fs.readFileSync(metaJsonPath, 'utf8'));
- }
-
- if (fs.existsSync(metaMarkdownPath)) {
- metaMarkdown = fs.readFileSync(metaMarkdownPath, 'utf8');
- }
- } catch (e) {
- console.warn(`Error loading metadata for ${fileName}:`, e);
- }
-
- // Load EXIF data
- let exifRaw: any = null;
- try {
- exifRaw = await ExifReader.load(filePath);
- } catch (e) {
- console.warn(`Error loading EXIF data for ${filePath}:`, e);
- exifRaw = {};
- }
-
- // Extract EXIF metadata following the same priority as media.ts gallery function
- const keywords = exifRaw?.['LastKeywordXMP']?.description || exifRaw?.iptc?.Keywords?.description || '';
- const exifDescription = exifRaw?.['ImageDescription']?.description || '';
- const width = exifRaw?.['Image Width']?.value;
- const height = exifRaw?.['Image Height']?.value;
-
- // Priority order: EXIF title -> JSON title -> generated from filename
- const title = exifRaw?.title?.description || metaJson.title ||
- fileName.replace(/[-_]/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
-
- // Priority order: JSON description -> Markdown -> EXIF ImageDescription -> EXIF IPTC Caption -> empty
- const description = metaJson.description || metaMarkdown || exifDescription ||
- exifRaw?.iptc?.['Caption/Abstract']?.description || '';
-
- return {
- year,
- dateTaken,
- title,
- description,
- keywords: keywords ? keywords.split(',').map((k: string) => k.trim()) : [],
- width,
- height,
- exifRaw
- };
-}
-
-export const default_sort = (files: string[]): string[] =>
-{
+export const default_sort = (files: string[]): string[] => {
const getSortableParts = (filename: string) => {
const baseName = path.parse(filename).name;
const match = baseName.match(/^(\d+)_?(.*)$/); // Match leading numbers
@@ -273,7 +126,6 @@ export const gallery = async (
}
galleryFiles = default_sort(galleryFiles)
return await pMap(galleryFiles, async (file: string) => {
-
const parts = path.parse(file)
const filePath = path.join(mediaPath, file)
const meta_path_json = `${mediaPath}/${parts.name}.json`
@@ -314,6 +166,7 @@ export const gallery = async (
const assetUrl = (filePath) => {
return sanitizeUri(ITEM_ASSET_URL(
{
+ FILE_SERVER_DEV,
assetPath,
filePath,
ITEM_REL: product,
@@ -321,6 +174,9 @@ export const gallery = async (
}
))
}
+
+ const fileBuffer = readFileSync(filePath);
+ const hash = createHash('md5').update(fileBuffer).digest('hex').substring(0, 8);
const ret: GalleryImage =
{
name: path.parse(file).name,
@@ -352,9 +208,9 @@ export const gallery = async (
width,
height,
title,
- gps: { lon, lat }
+ gps: { lon, lat },
+ hash
}
-
return ret
})
}
\ No newline at end of file
diff --git a/packages/polymech/src/base/objects.ts b/packages/polymech/src/base/objects.ts
index ba3148c..5c26304 100644
--- a/packages/polymech/src/base/objects.ts
+++ b/packages/polymech/src/base/objects.ts
@@ -27,7 +27,7 @@ export const removeArrayValues = (obj: any): any => {
try {
delete obj[key];
} catch (e) {
- debugger
+ //debugger
}
} else if (typeof obj[key] === 'object') {
diff --git a/packages/polymech/src/components/Gallery.astro b/packages/polymech/src/components/Gallery.astro
deleted file mode 100644
index cdcb520..0000000
--- a/packages/polymech/src/components/Gallery.astro
+++ /dev/null
@@ -1,277 +0,0 @@
----
-
-import { Img } from "imagetools/components";
-
-import Translate from "@/components/polymech/i18n.astro"
-
-import { translate } from "@/base/i18n"
-import { item_keywords } from '@/commons/seo.js'
-
-import pMap from 'p-map'
-import { toJsonLd } from '@/base/media.js'
-import { IComponentConfig } from "@polymech/commons"
-import { I18N_SOURCE_LANGUAGE, IMAGE_SETTINGS } from "config/config.js"
-
-interface Image {
- alt: string
- src: string
- title?: string
- description?: string
- item?:IComponentConfig
-}
-export interface Props {
- images: Image[];
- id?: string;
- siteKeywords?: string[];
- item?: IComponentConfig;
- gallerySettings?: {
- SIZES_REGULAR?: string;
- SIZES_THUMB?: string;
- SIZES_LARGE?: string;
- SHOW_TITLE?: boolean;
- SHOW_DESCRIPTION?: boolean;
- };
- lightboxSettings?: {
- SIZES_REGULAR?: string;
- SIZES_THUMB?: string;
- SIZES_LARGE?: string;
- SHOW_TITLE?: boolean;
- SHOW_DESCRIPTION?: boolean;
- };
-}
-const { images, gallerySettings = {}, lightboxSettings = {}, item } = Astro.props;
-const mergedGallerySettings = {
- SIZES_REGULAR: gallerySettings.SIZES_REGULAR || IMAGE_SETTINGS.GALLERY.SIZES_REGULAR,
- SIZES_THUMB: gallerySettings.SIZES_THUMB || IMAGE_SETTINGS.GALLERY.SIZES_THUMB,
- SHOW_TITLE: gallerySettings.SHOW_TITLE ?? IMAGE_SETTINGS.GALLERY.SHOW_TITLE,
- SHOW_DESCRIPTION: gallerySettings.SHOW_DESCRIPTION ?? IMAGE_SETTINGS.GALLERY.SHOW_DESCRIPTION,
-}
-const mergedLightboxSettings = {
- SIZES_LARGE: lightboxSettings.SIZES_LARGE || IMAGE_SETTINGS.LIGHTBOX.SIZES_LARGE,
- SHOW_TITLE: lightboxSettings.SHOW_TITLE ?? IMAGE_SETTINGS.LIGHTBOX.SHOW_TITLE,
- SHOW_DESCRIPTION: lightboxSettings.SHOW_DESCRIPTION ?? IMAGE_SETTINGS.LIGHTBOX.SHOW_DESCRIPTION,
-}
-const locale = Astro.currentLocale || I18N_SOURCE_LANGUAGE
-const canonicalURL = new URL(Astro.url.pathname, Astro.site)
-
-const alt = async (altText) => `${altText} - ${await item_keywords(item, locale)}`
-
-const alt_translated = await pMap(images, async (image) => {
- const text = await alt(image.alt)
- return await translate(text, I18N_SOURCE_LANGUAGE, locale)
-}, { concurrency: 1 })
-
-//const json_ld = await pMap(LANGUAGES, async (lang) => {
-const ld = await toJsonLd(images, Astro.currentLocale || I18N_SOURCE_LANGUAGE, canonicalURL.toString())
-//}, { concurrency: 1 })
-
----
-
-
= this.minSwipeDistance) {
- if (swipeDistance > 0 && this.currentIndex > 0) {
- // Swiped right, show previous image
- this.currentIndex--;
- } else if (swipeDistance < 0 && this.currentIndex < this.total - 1) {
- // Swiped left, show next image
- this.currentIndex++;
- }
- }
-
- this.isSwiping = false;
- this.mouseIsDown = false;
- },
- preloadAndOpen(index = null) {
- // If we're in the middle of a swipe gesture, don't open the lightbox
- if (this.isSwiping || this.mouseIsDown) return;
-
- // If an index is provided, update the current index
- if (index !== null) {
- this.currentIndex = index;
- }
-
- this.lightboxLoaded = false;
- let img = new Image();
- img.src = this.images[this.currentIndex].src;
- this.lightboxLoaded = true;
- this.open = true;
- img.onload = () => { };
- }
- }
- `}
- @keydown.escape.window="open = false"
- @keydown.window="if(open){
- if($event.key === 'ArrowRight' && currentIndex < total - 1){
- currentIndex++;
- lightboxLoaded = false;
- preloadAndOpen();
- } else if($event.key === 'ArrowLeft' && currentIndex > 0){
- currentIndex--;
- lightboxLoaded = false;
- preloadAndOpen();
- }
- }"
- class="product-gallery">
-
-
- {images.map((image, index) => (
-
-
![{alt_translated[index]}]({image.src})
-
- ))}
-
-
- { (mergedGallerySettings.SHOW_TITLE || mergedGallerySettings.SHOW_DESCRIPTION) && (
-
- {images.map((image, index) => (
-
- { mergedGallerySettings.SHOW_TITLE && (
{image.title}
)}
- { mergedGallerySettings.SHOW_DESCRIPTION && (
{ image.description}
)}
-
- ))}
-
- )}
-
- {images.length > 1 && (
-
- )}
-
-
-
-
- {images.map((image, index) => (
-
-
![{alt_translated[index]}]({image.src})
- { (mergedLightboxSettings.SHOW_TITLE || mergedLightboxSettings.SHOW_DESCRIPTION) &&
- (image.title || image.description) && (
-
- { mergedLightboxSettings.SHOW_TITLE && (
{image.title}
)}
- { mergedLightboxSettings.SHOW_DESCRIPTION && (
{image.description}
)}
-
- )}
-
- ))}
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/packages/polymech/src/components/RelativeGallery.astro b/packages/polymech/src/components/RelativeGallery.astro
index 1d5cda8..04a598c 100644
--- a/packages/polymech/src/components/RelativeGallery.astro
+++ b/packages/polymech/src/components/RelativeGallery.astro
@@ -2,11 +2,10 @@
import LGallery from "./GalleryK.astro";
import path from "node:path";
import fs from "node:fs";
-import { glob } from 'glob';
+import { glob } from "glob";
import { globBase, pathInfo } from "@polymech/commons";
import { GalleryImage } from "@/base/images.js";
-import { gallery } from "@/base/media.js";
-import { resolveImagePath } from '@/utils/path-resolution';
+import { resolveImagePath } from "@/utils/path-resolution";
export interface Props {
images?: GalleryImage[];
@@ -20,24 +19,28 @@ const { images, glob: globPattern, entryPath, ...props } = Astro.props;
// Get current content directory dynamically from URL
const currentUrl = Astro.url.pathname;
-const pathSegments = currentUrl.split('/').filter(Boolean);
+const pathSegments = currentUrl.split("/").filter(Boolean);
// Handle locale-aware URLs (e.g., /en/resources/test/ vs /resources/test/)
// Check if first segment is a locale (2-character language code)
-const isLocaleFirst = pathSegments.length > 0 && pathSegments[0].length === 2 && /^[a-z]{2}$/.test(pathSegments[0]);
+const isLocaleFirst =
+ pathSegments.length > 0 &&
+ pathSegments[0].length === 2 &&
+ /^[a-z]{2}$/.test(pathSegments[0]);
// Determine content subdirectory (e.g., 'resources', 'blog', etc.)
-let contentSubdir = 'resources'; // fallback
+let contentSubdir = "resources"; // fallback
if (pathSegments.length >= 1) {
- contentSubdir = isLocaleFirst && pathSegments.length > 1
- ? pathSegments[1] // Skip locale: /en/resources/test/ -> "resources"
- : pathSegments[0]; // No locale: /resources/test/ -> "resources"
+ contentSubdir =
+ isLocaleFirst && pathSegments.length > 1
+ ? pathSegments[1] // Skip locale: /en/resources/test/ -> "resources"
+ : pathSegments[0]; // No locale: /resources/test/ -> "resources"
}
// Get the nested content directory (e.g., 'cassandra' from /resources/cassandra/home/)
// We need to distinguish between:
// 1. /resources/test -> root-level file test.mdx -> contentPath = "resources"
-// 2. /es/resources/test -> root-level file test.mdx with locale -> contentPath = "resources"
+// 2. /es/resources/test -> root-level file test.mdx with locale -> contentPath = "resources"
// 3. /resources/cassandra/home -> nested file cassandra/home.mdx -> contentPath = "resources/cassandra"
// 4. /es/resources/cassandra/home -> nested file with locale -> contentPath = "resources/cassandra"
let contentPath = contentSubdir;
@@ -56,7 +59,7 @@ if (pathSegments.length >= minNestedSegments) {
}
}
-const contentDir = path.join(process.cwd(), 'src', 'content', contentPath);
+const contentDir = path.join(process.cwd(), "src", "content", contentPath);
let allImages: GalleryImage[] = [];
@@ -66,13 +69,20 @@ if (globPattern) {
// For content collections, we need to create a mock product structure
// that the gallery function can work with
const mockProductPath = `content/${contentSubdir}`;
-
+
// Since gallery expects a specific directory structure, let's use our custom approach
// but leverage the media processing logic where possible
- let matchedFiles = glob.sync(globPattern, { cwd: contentDir, absolute: true });
-
+ let matchedFiles = glob.sync(globPattern, {
+ cwd: contentDir,
+ absolute: true,
+ });
+
if (matchedFiles.length === 0) {
- const pathInfo2 = pathInfo(globPattern, false, path.join(contentDir, globBase(globPattern).base));
+ const pathInfo2 = pathInfo(
+ globPattern,
+ false,
+ path.join(contentDir, globBase(globPattern).base),
+ );
matchedFiles = pathInfo2.FILES;
}
@@ -81,8 +91,8 @@ if (globPattern) {
matchedFiles.map(async (filePath) => {
const relativePath = path.relative(process.cwd(), filePath);
const fileName = path.basename(filePath, path.extname(filePath));
- const webPath = `/${relativePath.replace(/\\/g, '/')}`;
-
+ const webPath = `/${relativePath.replace(/\\/g, "/")}`;
+
// Create basic image structure
const image: GalleryImage = {
name: fileName,
@@ -91,9 +101,11 @@ if (globPattern) {
thumb: webPath,
responsive: webPath,
alt: `Image: ${fileName}`,
- title: fileName.replace(/[-_]/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
+ title: fileName
+ .replace(/[-_]/g, " ")
+ .replace(/\b\w/g, (l) => l.toUpperCase()),
description: `Auto-loaded from ${globPattern}`,
- keywords: '',
+ keywords: "",
width: 0,
height: 0,
gps: { lon: 0, lat: 0 },
@@ -101,13 +113,13 @@ if (globPattern) {
format: path.extname(filePath).toLowerCase().slice(1),
width: 0,
height: 0,
- space: '',
+ space: "",
channels: 0,
- depth: '',
+ depth: "",
density: 0,
- chromaSubsampling: '',
+ chromaSubsampling: "",
isProgressive: false,
- resolutionUnit: '',
+ resolutionUnit: "",
hasProfile: false,
hasAlpha: false,
orientation: 0,
@@ -116,42 +128,42 @@ if (globPattern) {
alt: `Image: ${fileName}`,
keywords: "",
title: "",
- description: ""
+ description: "",
},
- markdown: ''
- }
+ markdown: "",
+ },
};
// Try to load companion markdown and JSON files (like media.ts does)
const baseDir = path.dirname(filePath);
const metaJsonPath = path.join(baseDir, `${fileName}.json`);
const metaMarkdownPath = path.join(baseDir, `${fileName}.md`);
-
+
try {
if (fs.existsSync(metaJsonPath)) {
- const metaJson = JSON.parse(fs.readFileSync(metaJsonPath, 'utf8'));
+ const metaJson = JSON.parse(fs.readFileSync(metaJsonPath, "utf8"));
image.meta!.json = metaJson;
image.alt = metaJson.alt || image.alt;
image.title = metaJson.title || image.title;
image.description = metaJson.description || image.description;
}
-
+
if (fs.existsSync(metaMarkdownPath)) {
- const markdown = fs.readFileSync(metaMarkdownPath, 'utf8');
+ const markdown = fs.readFileSync(metaMarkdownPath, "utf8");
image.meta!.markdown = markdown;
image.description = markdown || image.description;
}
} catch (e) {
console.warn(`Error loading metadata for ${fileName}:`, e);
}
-
+
return image;
- })
+ }),
);
-
+
allImages = [...globImages];
} catch (error) {
- console.warn('Glob pattern failed:', error);
+ console.warn("Glob pattern failed:", error);
allImages = [];
}
}
@@ -162,15 +174,15 @@ if (images) {
}
// Resolve relative paths in all image sources and convert to LGallery format
-const resolvedImages = allImages.map(image => {
+const resolvedImages = allImages.map((image) => {
const resolvedSrc = resolveImagePath(image.src, entryPath, Astro.url);
-
+
// Convert GalleryImage to LGallery's expected Image interface
return {
src: resolvedSrc,
- alt: image.alt || '',
+ alt: image.alt || "",
title: image.title,
- description: image.description
+ description: image.description,
};
});
---
diff --git a/packages/polymech/src/components/polymech/image.astro b/packages/polymech/src/components/polymech/image.astro
index 1649622..7dccc64 100644
--- a/packages/polymech/src/components/polymech/image.astro
+++ b/packages/polymech/src/components/polymech/image.astro
@@ -1,13 +1,13 @@
---
-import { DEFAULT_IMAGE_URL } from '@/app/config.js'
-import { Picture } from "imagetools/components"
-import { image_url } from "@/base/media.js"
+import { DEFAULT_IMAGE_URL } from "@/app/config.js";
+import { Picture } from "imagetools/components";
+import { image_url } from "../../base/media.js";
interface SafeImageProps {
- src: string
- alt: string
- fallback?: string
- [propName: string]: any
+ src: string;
+ alt: string;
+ fallback?: string;
+ [propName: string]: any;
}
const {
@@ -15,8 +15,9 @@ const {
alt,
fallback = DEFAULT_IMAGE_URL,
...rest
-} = Astro.props as SafeImageProps
+} = Astro.props as SafeImageProps;
-const srcSafe = await image_url(src, fallback)
+const srcSafe = await image_url(src, fallback);
---
+
diff --git a/packages/polymech/src/model/component.ts b/packages/polymech/src/model/component.ts
index 65e4e60..cb87033 100644
--- a/packages/polymech/src/model/component.ts
+++ b/packages/polymech/src/model/component.ts
@@ -1,18 +1,15 @@
import * as path from 'path'
import { findUp } from 'find-up'
-
-
-
import { sync as read } from '@polymech/fs/read'
import { sync as exists } from '@polymech/fs/exists'
import { filesEx, forward_slash, resolveConfig } from '@polymech/commons'
import { ICADNodeSchema, IComponentConfig } from '@polymech/commons/component'
-import { ContentEntryRenderFunction, ContentEntryType } from 'astro'
-import { RenderedContent, DataEntry } from "astro:content"
+import { DataEntry } from "astro:content"
import type { Loader, LoaderContext } from 'astro/loaders'
+
import {
CAD_MAIN_MATCH, PRODUCT_BRANCHES,
CAD_EXTENSIONS, CAD_MODEL_EXT, PRODUCT_DIR, PRODUCT_GLOB,
@@ -22,16 +19,16 @@ import {
parseBoolean
} from 'config/config.js'
-import { env } from '../base/index.js'
-import { gallery } from '@/base/media.js';
+import { env } from '@/base/index.js'
+import { gallery } from '@polymech/astro-base/base/media.js';
import { get } from '@polymech/commons/component'
import { PFilterValid } from '@polymech/commons/filter'
+import { IAssemblyData } from '@polymech/cad'
import { logger as log } from '@/base/index.js'
interface ILoaderContextEx extends LoaderContext {
- entryTypes: Map
}
interface IComponentConfigEx extends IComponentConfig {
content: string
@@ -42,12 +39,9 @@ interface IComponentConfigEx extends IComponentConfig {
}
export interface IStoreItem extends DataEntry {
data: IComponentConfigEx
- rendered?: RenderedContent
}
-let loaderCtx: ILoaderContextEx
-const renderFunctionByContentType = new WeakMap();
const filterBranch = (items: { rel: string, config, path }[],
branch: string = RETAIL_PRODUCT_BRANCH) => {
@@ -88,41 +82,56 @@ const onComponent = async (item: IStoreItem, ctx: ILoaderContextEx) => {
*/
}
-export const getRenderFunction = async (fileName: string) => {
- let entryType = loaderCtx.entryTypes.get(path.parse(fileName).ext) as ContentEntryType
- let _render = renderFunctionByContentType.get(entryType) as ContentEntryRenderFunction
+const cad = async (item: IStoreItem, ctx: ILoaderContextEx): Promise => {
+ const root = PRODUCT_ROOT()
+ const data: IComponentConfigEx = item.data
+ const itemRel = data.rel
+ const default_profile = env(itemRel)
+ const mainAssemblies = filesEx(root, CAD_MAIN_MATCH(itemRel), { absolute: false })
+ //log.debug(`Loading CAD/CAM for ${itemRel} in ${root} with ${CAD_MAIN_MATCH(itemRel)}`)
+ const cadMeta = mainAssemblies.map((file: string): ICADNodeSchema => {
+ const result: ICADNodeSchema = {
+ file,
+ name: path.basename(file)
+ }
+ CAD_EXTENSIONS.forEach(ext => result[ext] = decodeURIComponent(forward_slash(CAD_URL(file.replace('.SLDASM', ext), data as Record))))
+ result.model = path.join(root, file.replace('.SLDASM', CAD_MODEL_EXT))
+ return result
+ })
+ if (!cadMeta.length) {
+ return []
+ }
+ item.data.cad = cadMeta
+ item.data.preview3d = cadMeta[0].html
- if (!_render) {
- _render = await (entryType as any).getRenderFunction({})
- renderFunctionByContentType.set(entryType, _render)
+ if (CAD_EXPORT_CONFIGURATIONS) {
+ const assemblies = cadMeta.filter((assembly) => assembly.model && exists(assembly.model))
+ const components = cadMeta.map((assembly) => {
+ const modelPath = assembly.model as string
+ const model: IAssemblyData = read(modelPath, 'json') as IAssemblyData
+ if (!model) {
+ return
+ }
+ const configurations = Object.keys(model.Configurations).filter((c) => {
+ return c !== CAD_DEFAULT_CONFIGURATION &&
+ parseBoolean(model.Configurations[c].Hide || '')
+ })
+ if (!configurations.length ||
+ parseBoolean(model.Configurations?.Global?.Configurations || '')) {
+ return
+ }
+ log.debug(`Loading CAD/CAM for ${itemRel}`, configurations)
+ })
}
- return _render
-}
-export const renderMarkup = async (content, data: any, fileName: string = 'template.md') => {
- if (!loaderCtx) {
- debugger
- log.error('Loader context not set')
- return
- }
- const _render = await getRenderFunction(fileName)
- if (!_render) {
- log.error('No render function')
- return
- }
- return _render({
- body: content,
- data,
- filePath: fileName,
- } as any)
+ return cadMeta
}
+
+// Removed obsolete loader types and render functions
const onItem = async (item: IStoreItem, ctx: ILoaderContextEx) => {
if (!item || !item.data) {
ctx.logger.error(`Error completing ${''}: no data`);
return
}
- if(!loaderCtx){
- loaderCtx = ctx
- }
const { logger } = ctx
let data: IComponentConfigEx = item.data
@@ -134,17 +143,18 @@ const onItem = async (item: IStoreItem, ctx: ILoaderContextEx) => {
data.product_rel = itemRelMin
data.assets = {
renderings: [],
- gallery: []
+ gallery: [],
+ screenshots: []
}
//////////////////////////////////////////
//
// Body
//
let contentPath = path.join(itemDir, 'templates/shared', 'body.md')
- await getRenderFunction(contentPath)
+
if (exists(contentPath)) {
data.content = read(contentPath) as string
- (item as any).filePath = contentPath
+ item.filePath = contentPath
}
//////////////////////////////////////////
//
@@ -159,7 +169,7 @@ const onItem = async (item: IStoreItem, ctx: ILoaderContextEx) => {
let resourcesDefaultPath = await findUp('resources.md', {
stopAt: PRODUCT_ROOT(),
cwd: itemDir
- }) || ""
+ }) || ""
exists(resourcesDefaultPath) && (data.shared_resources = read(resourcesDefaultPath) as string || "")
//////////////////////////////////////////
//
@@ -199,7 +209,8 @@ const onItem = async (item: IStoreItem, ctx: ILoaderContextEx) => {
//
// Extensions, CAD, Media, etc.
//
-
+ data.cad = await cad(item, ctx)
+
data.assets.renderings = await gallery('renderings', data.rel) as []
data.assets.renderings.length && (data.thumbnail =
{
@@ -208,10 +219,12 @@ const onItem = async (item: IStoreItem, ctx: ILoaderContextEx) => {
src: data.assets.renderings[0].thumb
})
data.assets.gallery = await gallery('media/gallery', data.rel) as []
+ data.assets.main_image = (data.assets.gallery as any[]).find((item: any) => item.src.endsWith('latest.jpg'))
data.image = data.assets.renderings[0] || {}
data.assets.showcase = await gallery('media/showcase', data.rel) as []
data.assets.samples = await gallery('media/samples', data.rel) as []
+ data.assets.screenshots = await gallery('media/screenshots', data.rel) as []
}
export function loader(): Loader {
@@ -221,8 +234,8 @@ export function loader(): Loader {
watcher,
parseData,
store,
- generateDigest,
- entryTypes }: ILoaderContextEx) => {
+ generateDigest
+ }: ILoaderContextEx) => {
store.clear();
let products = items({})
@@ -251,8 +264,7 @@ export function loader(): Loader {
watcher,
parseData,
store,
- generateDigest,
- entryTypes
+ generateDigest
} as any)
storeItem.data['config'] = JSON.stringify({
...storeItem.data
@@ -264,4 +276,4 @@ export function loader(): Loader {
name: "store-loader",
load
};
-}
\ No newline at end of file
+}