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]} -
- ))} -
- - { (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) => ( - - ))} -
-
- )} -
- - \ 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 +}