the white saviours

This commit is contained in:
lovebird 2025-03-11 11:48:57 +01:00
parent 6cd7d8cef0
commit c3c16a145e
17 changed files with 484 additions and 397 deletions

File diff suppressed because one or more lines are too long

View File

@ -11,7 +11,7 @@ export default defineConfig({
enabled: false,
},
i18n: {
locales: ["es", "en", "de", "fr", "it", "ar", "ja", "zh"],
locales: ["es", "en", "de", "fr", "it", "ar", "ja", "zh", "nl"],
defaultLocale: "en",
},
vite: {

48
package-lock.json generated
View File

@ -30,6 +30,7 @@
"autoprefixer": "^10.4.20",
"axios": "^1.7.9",
"cacache": "^19.0.1",
"env-var": "^7.5.0",
"exifreader": "^4.26.1",
"file-type": "^20.0.0",
"find-cache-dir": "^5.0.0",
@ -65,6 +66,7 @@
"type-fest": "^4.34.1",
"vite": "^6.1.1",
"vite-plugin-compression": "^0.5.1",
"write-file-atomic": "^6.0.0",
"xlsx": "^0.18.5",
"yargs": "^17.7.2",
"zod": "^3.24.2"
@ -5133,6 +5135,24 @@
"node": ">=8"
}
},
"node_modules/configstore/node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC"
},
"node_modules/configstore/node_modules/write-file-atomic": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
"integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
"license": "ISC",
"dependencies": {
"imurmurhash": "^0.1.4",
"is-typedarray": "^1.0.0",
"signal-exit": "^3.0.2",
"typedarray-to-buffer": "^3.1.5"
}
},
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@ -5702,6 +5722,15 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/env-var": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/env-var/-/env-var-7.5.0.tgz",
"integrity": "sha512-mKZOzLRN0ETzau2W2QXefbFjo5EF4yWq28OyKb9ICdeNhHJlOE/pHHnz4hdYJ9cNZXcJHo5xN4OT4pzuSHSNvA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@ -16287,23 +16316,18 @@
"license": "ISC"
},
"node_modules/write-file-atomic": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
"integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-6.0.0.tgz",
"integrity": "sha512-GmqrO8WJ1NuzJ2DrziEI2o57jKAVIQNf8a18W3nCYU3H7PNWqCCVTeH6/NQE93CIllIgQS98rrmVkYgTX9fFJQ==",
"license": "ISC",
"dependencies": {
"imurmurhash": "^0.1.4",
"is-typedarray": "^1.0.0",
"signal-exit": "^3.0.2",
"typedarray-to-buffer": "^3.1.5"
"signal-exit": "^4.0.1"
},
"engines": {
"node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/write-file-atomic/node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC"
},
"node_modules/ws": {
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",

View File

@ -40,6 +40,7 @@
"autoprefixer": "^10.4.20",
"axios": "^1.7.9",
"cacache": "^19.0.1",
"env-var": "^7.5.0",
"exifreader": "^4.26.1",
"file-type": "^20.0.0",
"find-cache-dir": "^5.0.0",
@ -75,6 +76,7 @@
"type-fest": "^4.34.1",
"vite": "^6.1.1",
"vite-plugin-compression": "^0.5.1",
"write-file-atomic": "^6.0.0",
"xlsx": "^0.18.5",
"yargs": "^17.7.2",
"zod": "^3.24.2"

View File

@ -1,12 +1,12 @@
export default {
"environment": "dev",
"environment": "build",
"isSsrBuild": false,
"projectBase": "",
"publicDir": "C:\\Users\\zx\\Desktop\\polymech\\polymech-site\\public\\",
"rootDir": "C:\\Users\\zx\\Desktop\\polymech\\polymech-site\\",
"mode": "dev",
"outDir": "dist",
"assetsDir": "/_astro",
"mode": "production",
"outDir": "C:\\Users\\zx\\Desktop\\polymech\\polymech-site\\dist\\",
"assetsDir": "_astro",
"sourcemap": false,
"assetFileNames": "/_astro/[name]@[width].[hash][extname]"
}

View File

@ -10,7 +10,7 @@ export const LOGGING_NAMESPACE = 'polymech-site'
export const TRANSLATE_CONTENT = true
export const LANGUAGES = ['en', 'ar', 'de', 'ja', 'es', 'zh', 'fr']
//export const LANGUAGES_PROD = ['en']
export const LANGUAGES_PROD = ['en', 'es', 'ar', 'de', 'ja', 'zh', 'fr']
export const LANGUAGES_PROD = ['en', 'es', 'ar', 'de', 'ja', 'zh', 'fr', 'nl']
export const isRTL = (lang) => lang === 'ar'
// i18n constants
@ -117,6 +117,8 @@ export const SHOW_RESOURCES = true
export const SHOW_DEBUG = false
export const SHOW_SAMPLES = true
export const SHOW_README = false
export const SHOW_RELATED = true
/////////////////////////////////////////////
//

View File

@ -9,6 +9,7 @@ import { findUp } from 'find-up'
import { createLogger } from '@polymech/log'
import { parse, IProfile } from '@polymech/commons/profile'
import { renderMarkup } from "@/model/component.js";
import {
LOGGING_NAMESPACE,
OSRL_ENV,
@ -63,11 +64,10 @@ export async function markdownToHtml(markdown: string): Promise<string> {
return result.toString();
}
export async function createMarkdownComponent(markdown: string) {
export const createMarkdownComponent = async (markdown: string) => {
const html = unescapeHTML(await markdownToHtml(markdown));
return createComponent(() => renderTemplate(html as any, []));
}
export async function createHTMLComponent(html: string) {
return createComponent(() => renderTemplate(unescapeHTML(html) as any, []))
}
export const createHTMLComponent = async (html: string) =>
createComponent(() => renderTemplate(unescapeHTML(html) as any, []))

View File

@ -1,19 +1,19 @@
---
import { component, createMarkdownComponent } from "@/base/index.js";
import { createMarkdownComponent } from "@/base/index.js";
import Translation from "@/components/polymech/i18n.astro";
import Link from "./link.astro";
const { frontmatter: data, ...rest } = Astro.props;
const { frontmatter: data } = Astro.props;
const LINK_CLASSES = "text-blue-400 hover:text-blue-800 hover:underline";
const getHref = (key, data) => {
switch (key) {
case "cad":
return data.cad?.[0]?.[".html"];
return data.cad?.[0]?.[".html"]
default:
return data[key];
return data[key]
}
};
}
const checkCondition = (key, data) => {
switch (key) {

View File

@ -1,11 +1,11 @@
---
import { default_image } from "@/app/config.js"
const { title, url, price, model } = Astro.props
const thumbnail = model?.assets?.renderings[0]?.url || default_image()
import Img from "@/components/polymech/image.astro"
import { default_image } from "@/app/config.js";
const { title, url, price, model, selected = false } = Astro.props;
const thumbnail = model?.assets?.renderings[0]?.url || default_image();
import Img from "@/components/polymech/image.astro";
const classes = `group relative bg-white overflow-hidden group rounded-xl font-mono ${selected ? "ring-2 ring-orange-500" : ""}`
---
<div class="group relative bg-white overflow-hidden group rounded-xl font-mono">
<div class={classes}>
<div class="p-4 overflow-hidden group-hover:opacity-75 duration-300 transition-all">
<a href={url} title={title} aria-label={title}>
<Img

View File

@ -1,4 +1,5 @@
---
import { getCollection } from "astro:content";
import BaseLayout from "./BaseLayout.astro";
import { createMarkdownComponent } from "@/base/index.js";
import { translate } from "@/base/i18n.js";
@ -12,6 +13,13 @@ import Specs from "@/components/polymech/specs.astro";
import TabButton from "@/components/polymech/tab-button.astro";
import TabContent from "@/components/polymech/tab-content.astro";
import StoreEntries from "@/components/store/StoreEntries.astro";
import {
IComponentConfigEx,
group_by_path,
group_path,
} from "@/model/component.js";
import "flowbite";
import {
@ -29,6 +37,7 @@ import {
SHOW_RESOURCES,
SHOW_CHECKOUT,
SHOW_README,
SHOW_RELATED,
DEFAULT_LICENSE,
isRTL,
} from "config/config.js";
@ -54,6 +63,10 @@ const str_debug =
"\n```";
const Content_Debug = await createMarkdownComponent(str_debug);
const view = "store";
const items = (await getCollection(view));//.filter((i) => item.rel !== i.id);
//const group_id = group_path(item);
const others = await group_by_path(items, Astro.currentLocale);
---
<BaseLayout frontmatter={item} description={item.description} {...rest}>
@ -267,7 +280,10 @@ const Content_Debug = await createMarkdownComponent(str_debug);
)
}
</TabContent>
<TabContent title="Specs" class="bg-white rounded-xl dark:bg-gray-800 font-mono">
<TabContent
title="Specs"
class="bg-white rounded-xl dark:bg-gray-800 font-mono"
>
<Specs frontmatter={item} />
</TabContent>
<TabContent title="Gallery" class="p-0 md:p-4 rounded-lg bg-white">
@ -275,21 +291,30 @@ const Content_Debug = await createMarkdownComponent(str_debug);
</TabContent>
{
SHOW_SAMPLES && (
<TabContent title="Samples" class="p-4 bg-white rounded-xl dark:bg-gray-800">
<TabContent
title="Samples"
class="p-4 bg-white rounded-xl dark:bg-gray-800"
>
<GalleryK images={item.assets.samples} item={item} />
</TabContent>
)
}
{
SHOW_RESOURCES && (
<TabContent title="Resources" class="p-4 bg-white rounded-xl dark:bg-gray-800">
<TabContent
title="Resources"
class="p-4 bg-white rounded-xl dark:bg-gray-800"
>
<Resources frontmatter={item} />
</TabContent>
)
}
{
SHOW_DEBUG && (
<TabContent title="Debug" class="rounded-lg bg-white p-4 dark:bg-gray-800">
<TabContent
title="Debug"
class="rounded-lg bg-white p-4 dark:bg-gray-800"
>
<Content_Debug />
</TabContent>
)
@ -332,5 +357,40 @@ const Content_Debug = await createMarkdownComponent(str_debug);
});
</script>
</section>
<hr />
<h1 aria-hidden="true" class="p-4 text-2xl text-neutral-500 space-x-8">
<Translate>Related</Translate>
</h1>
{
SHOW_RELATED && (
<section id="item_related" class="bg-blue-50 p-4 rounded-2xl">
{Object.keys(others).map((relKey) => (
<section>
<h5
aria-hidden="true"
class="p-4 text-sm text-neutral-500 space-x-8"
>
{relKey}
</h5>
<div class="grid sm:grid-cols-4 lg:grid-cols-4 xl:grid-cols-4 gap-2">
{others[relKey].map((post) => (
<StoreEntries
key={post.id}
url={`/${Astro.currentLocale}/${view}/${post.id}`}
title={post.data.title}
price={post.data.price}
type={post.data.type}
alt={post.data.title}
model={post.data}
selected={post.id === item.rel}
/>
))}
</div>
</section>
))}
</section>
)
}
</Wrapper>
</BaseLayout>

View File

@ -1,311 +1,332 @@
import * as path from 'path'
import { findUp } from 'find-up'
import { } from 'astro:content'
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 type { Loader, LoaderContext } from 'astro/loaders'
import {
CAD_MAIN_MATCH, PRODUCT_BRANCHES,
CAD_EXTENSIONS, CAD_MODEL_EXT, PRODUCT_DIR, PRODUCT_GLOB,
PRODUCT_ROOT, RETAIL_PRODUCT_BRANCH, CAD_EXPORT_CONFIGURATIONS,
CAD_DEFAULT_CONFIGURATION,
CAD_URL,
parseBoolean
} from 'config/config.js'
import { env } from '../base/index.js'
import { gallery } from '../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<string, ContentEntryType>
}
interface IComponentConfigEx extends IComponentConfig {
content: string
extra_resources?: string
shared_resources?: string
readme?: string
rel: string
}
export interface IStoreItem extends DataEntry {
data: IComponentConfigEx
rendered?: RenderedContent
}
let loaderCtx: ILoaderContextEx
const renderFunctionByContentType = new WeakMap<ContentEntryType, ContentEntryRenderFunction>();
const filterBranch = (items: { rel: string, config, path }[],
branch: string = RETAIL_PRODUCT_BRANCH) => {
if (!PRODUCT_BRANCHES) {
return items
}
const branchItems = PRODUCT_BRANCHES[branch]
if (!branchItems) {
return items
}
return items.filter((item) => branchItems.includes(item.rel))
}
export const items = (opts: {}) => filterBranch(get(`${PRODUCT_ROOT()}/${PRODUCT_GLOB}`, PRODUCT_ROOT(), PFilterValid.marketplace_component))
const onComponent = async (item: IStoreItem, ctx: ILoaderContextEx) => {
/*
const onNode = async (data: INodeCallback, configuration: string) => {
if (!CAD_EXPORT_SUB_COMPONENTS || !data.target.endsWith('.json')) {
return
}
const modelPath = `${CAD_MODEL_FILE_PATH(data.target,configuration)}`
const model: IAssemblyData = read(modelPath, 'json') as IAssemblyData
if (!model) {
return
}
const configurations = Object.keys(model.Configurations).filter((c) => {
return c !== CAD_DEFAULT_CONFIGURATION &&
c !== 'Global' &&
model.Configurations[c].Hide !== '1'
})
if (!configurations.length ||
model.Configurations?.Global?.['Configurations'] !== '1') {
return
}
}
*/
}
const cad = async (item: IStoreItem, ctx: ILoaderContextEx): Promise<ICADNodeSchema[]> => {
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<string, string>))))
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 (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 cadMeta
}
export const getRenderFunction = async (fileName: string) => {
let entryType = loaderCtx.entryTypes.get(path.parse(fileName).ext) as ContentEntryType
let _render = renderFunctionByContentType.get(entryType) as ContentEntryRenderFunction
if (!_render) {
_render = await (entryType as any).getRenderFunction({})
renderFunctionByContentType.set(entryType, _render)
}
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)
}
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
const itemRel = data.rel
const itemRelMin = data.rel.replace('products/', '')
const itemDir = PRODUCT_DIR(itemRel)
const default_profile = env(itemRel)
data.product_rel = itemRelMin
data.assets = {
renderings: [],
gallery: []
}
//////////////////////////////////////////
//
// Body
//
let contentPath = path.join(itemDir, 'templates/shared', 'body.md')
await getRenderFunction(contentPath)
if (exists(contentPath)) {
data.content = read(contentPath) as string
item.filePath = contentPath
}
//////////////////////////////////////////
//
// Item Extra Resources
//
let resourcesPath = path.join(itemDir, 'templates/shared', 'resources.md')
exists(resourcesPath) && (data.extra_resources = read(resourcesPath) as string || "")
//////////////////////////////////////////
//
// Item Shared Resources
//
let resourcesDefaultPath = await findUp('resources.md', {
stopAt: PRODUCT_ROOT(),
cwd: itemDir
}) || ""
exists(resourcesDefaultPath) && (data.shared_resources = read(resourcesDefaultPath) as string || "")
//////////////////////////////////////////
//
// Readme
//
let readmePath = path.join(itemDir, 'Readme.md')
if (exists(readmePath)) {
data.readme = read(readmePath) as string
}
//////////////////////////////////////////
//
// Variables
//
let defaultsJSON = await findUp('defaults.json', {
stopAt: PRODUCT_ROOT(),
cwd: itemDir
})
try {
if (defaultsJSON) {
data = {
...read(defaultsJSON, 'json') as any,
...data,
}
}
} catch (error) {
logger.error(`Error reading defaults.json: ${error.message}`);
}
data = {
...data,
...default_profile.variables,
product_rel_min: itemRelMin.replace('products/', ''),
}
data = resolveConfig(data as Record<string, string>) as IComponentConfigEx
item.data = data
//////////////////////////////////////////
//
// 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 =
{
alt: '',
url: data.assets.renderings[0].thumb,
src: data.assets.renderings[0].thumb
})
data.assets.gallery = await gallery('media/gallery', data.rel) as []
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 []
}
export function loader(): Loader {
const load = async ({
config,
logger,
watcher,
parseData,
store,
generateDigest,
entryTypes }: ILoaderContextEx) => {
store.clear();
let products = items({})
for (const item of products) {
const product: any = item.config
const id = product.slug;
const data = {
rel: item.rel,
title: product.name,
slug: id,
type: 'product',
highlights: [],
components: [],
...product
}
//const parsedData = await parseData({ id, data: data });
const storeItem = {
digest: await generateDigest(data),
filePath: id,
assetImports: [],
id: `${item.rel}`,
data: data
}
await onItem(storeItem, {
logger,
watcher,
parseData,
store,
generateDigest,
entryTypes
} as any)
storeItem.data['config'] = JSON.stringify({
...storeItem.data
}, null, 2)
store.set(storeItem)
}
}
return {
name: "store-loader",
load
};
}
import * as path from 'path'
import { findUp } from 'find-up'
import { } from 'astro:content'
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 type { Loader, LoaderContext } from 'astro/loaders'
import {
CAD_MAIN_MATCH, PRODUCT_BRANCHES,
CAD_EXTENSIONS, CAD_MODEL_EXT, PRODUCT_DIR, PRODUCT_GLOB,
PRODUCT_ROOT, RETAIL_PRODUCT_BRANCH, CAD_EXPORT_CONFIGURATIONS,
CAD_DEFAULT_CONFIGURATION,
CAD_URL,
parseBoolean
} from 'config/config.js'
import { env } from '../base/index.js'
import { gallery } from '../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'
import { translate } from "@/base/i18n.js"
import { I18N_SOURCE_LANGUAGE } from "config/config.js"
import { slugify } from "@/base/strings.js"
export interface ILoaderContextEx extends LoaderContext {
entryTypes: Map<string, ContentEntryType>
}
export interface IComponentConfigEx extends IComponentConfig {
content: string
extra_resources?: string
shared_resources?: string
readme?: string
rel: string
}
export interface IStoreItem extends DataEntry {
data: IComponentConfigEx
rendered?: RenderedContent
}
let loaderCtx: ILoaderContextEx
const renderFunctionByContentType = new WeakMap<ContentEntryType, ContentEntryRenderFunction>();
const filterBranch = (items: { rel: string, config, path }[],
branch: string = RETAIL_PRODUCT_BRANCH) => {
if (!PRODUCT_BRANCHES) {
return items
}
const branchItems = PRODUCT_BRANCHES[branch]
if (!branchItems) {
return items
}
return items.filter((item) => branchItems.includes(item.rel))
}
export const items = (opts: {}) => filterBranch(get(`${PRODUCT_ROOT()}/${PRODUCT_GLOB}`, PRODUCT_ROOT(), PFilterValid.marketplace_component))
const onComponent = async (item: IStoreItem, ctx: ILoaderContextEx) => {
/*
const onNode = async (data: INodeCallback, configuration: string) => {
if (!CAD_EXPORT_SUB_COMPONENTS || !data.target.endsWith('.json')) {
return
}
const modelPath = `${CAD_MODEL_FILE_PATH(data.target,configuration)}`
const model: IAssemblyData = read(modelPath, 'json') as IAssemblyData
if (!model) {
return
}
const configurations = Object.keys(model.Configurations).filter((c) => {
return c !== CAD_DEFAULT_CONFIGURATION &&
c !== 'Global' &&
model.Configurations[c].Hide !== '1'
})
if (!configurations.length ||
model.Configurations?.Global?.['Configurations'] !== '1') {
return
}
}
*/
}
const cad = async (item: IStoreItem, ctx: ILoaderContextEx): Promise<ICADNodeSchema[]> => {
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<string, string>))))
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 (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 cadMeta
}
export const getRenderFunction = async (fileName: string) => {
let entryType = loaderCtx.entryTypes.get(path.parse(fileName).ext) as ContentEntryType
let _render = renderFunctionByContentType.get(entryType) as ContentEntryRenderFunction
if (!_render) {
_render = await (entryType as any).getRenderFunction({})
renderFunctionByContentType.set(entryType, _render)
}
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)
}
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
const itemRel = data.rel
const itemRelMin = data.rel.replace('products/', '')
const itemDir = PRODUCT_DIR(itemRel)
const default_profile = env(itemRel)
data.product_rel = itemRelMin
data.assets = {
renderings: [],
gallery: []
}
//////////////////////////////////////////
//
// Body
//
let contentPath = path.join(itemDir, 'templates/shared', 'body.md')
await getRenderFunction(contentPath)
if (exists(contentPath)) {
data.content = read(contentPath) as string
item.filePath = contentPath
}
//////////////////////////////////////////
//
// Item Extra Resources
//
let resourcesPath = path.join(itemDir, 'templates/shared', 'resources.md')
exists(resourcesPath) && (data.extra_resources = read(resourcesPath) as string || "")
//////////////////////////////////////////
//
// Item Shared Resources
//
let resourcesDefaultPath = await findUp('resources.md', {
stopAt: PRODUCT_ROOT(),
cwd: itemDir
}) || ""
exists(resourcesDefaultPath) && (data.shared_resources = read(resourcesDefaultPath) as string || "")
//////////////////////////////////////////
//
// Readme
//
let readmePath = path.join(itemDir, 'Readme.md')
if (exists(readmePath)) {
data.readme = read(readmePath) as string
}
//////////////////////////////////////////
//
// Variables
//
let defaultsJSON = await findUp('defaults.json', {
stopAt: PRODUCT_ROOT(),
cwd: itemDir
})
try {
if (defaultsJSON) {
data = {
...read(defaultsJSON, 'json') as any,
...data,
}
}
} catch (error) {
logger.error(`Error reading defaults.json: ${error.message}`);
}
data = {
...data,
...default_profile.variables,
product_rel_min: itemRelMin.replace('products/', ''),
}
data = resolveConfig(data as Record<string, string>) as IComponentConfigEx
item.data = data
//////////////////////////////////////////
//
// 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 =
{
alt: '',
url: data.assets.renderings[0].thumb,
src: data.assets.renderings[0].thumb
})
data.assets.gallery = await gallery('media/gallery', data.rel) as []
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 []
}
export function loader(): Loader {
const load = async ({
config,
logger,
watcher,
parseData,
store,
generateDigest,
entryTypes }: ILoaderContextEx) => {
store.clear();
let products = items({})
for (const item of products) {
const product: any = item.config
const id = product.slug;
const data = {
rel: item.rel,
title: product.name,
slug: id,
type: 'product',
highlights: [],
components: [],
...product
}
//const parsedData = await parseData({ id, data: data });
const storeItem = {
digest: await generateDigest(data),
filePath: id,
assetImports: [],
id: `${item.rel}`,
data: data
}
await onItem(storeItem, {
logger,
watcher,
parseData,
store,
generateDigest,
entryTypes
} as any)
storeItem.data['config'] = JSON.stringify({
...storeItem.data
}, null, 2)
store.set(storeItem)
}
}
return {
name: "store-loader",
load
};
}
export const group_path = (item) => item.id.split("/")[1]
const group_label = async (text: string, locale) => await translate(slugify(text), I18N_SOURCE_LANGUAGE, locale)
const group = async (items, locale) => {
return items.reduce(async (accPromise, item) => {
const acc = await accPromise
const id = group_path(item)
let key: string = await group_label(id, locale)
key = key.charAt(0).toUpperCase() + key.slice(1)
if (!acc[key]) {
acc[key] = []
}
acc[key].push(item)
return acc
}, {})
}
export const group_by_path = async (items, locale): Promise<IComponentConfigEx[]> => await group(items, locale)

View File

@ -6,13 +6,12 @@ import { LANGUAGES_PROD } from "config/config.js";
import { getCollection } from "astro:content";
import StoreEntries from "@/components/store/StoreEntries.astro";
import CtaOne from "@/components/cta/CtaOne.astro";
import { translate } from "@/base/i18n.js"
import { I18N_SOURCE_LANGUAGE } from "config/config.js"
import { slugify } from "@/base/strings.js"
import { group_by_path } from "@/model/component.js";
const allProducts = await getCollection("store")
const locale = Astro.currentLocale
const store = `/${locale}/store/`;
const view = "store";
const items = await getCollection(view);
const locale = Astro.currentLocale;
const store = `/${locale}/${view}/`;
export function getStaticPaths() {
const all: unknown[] = [];
@ -26,38 +25,28 @@ export function getStaticPaths() {
});
return all;
}
const group_label = async (text: string) => await translate(slugify(text), I18N_SOURCE_LANGUAGE, locale)
const group = async (items) => {
return items.reduce(async (accPromise, item: any) => {
const acc = await accPromise
const id = item.id.split("/")[1]
let key:string = (await group_label(id))
key = key.charAt(0).toUpperCase() + key.slice(1)
if (!acc[key]) {
acc[key] = []
}
acc[key].push(item)
return acc
}, {})
}
const items = await group(allProducts)
const groups = await group_by_path(items, locale);
---
<BaseLayout>
<Wrapper variant="standard" class="py-4">
<CtaOne />
<section>
<div class="py-2 space-y-2">
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
</div>
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-4"></div>
</div>
</section>
{
Object.keys(items).map((relKey) => (
Object.keys(groups).map((relKey) => (
<section>
<h1 aria-hidden="true" class="p-4 text-2xl text-neutral-500 space-x-8"> {relKey} </h1>
<h1
aria-hidden="true"
class="p-4 text-2xl text-neutral-500 space-x-8"
>
{relKey}
</h1>
<div class="grid sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-2 gap-2">
{items[relKey].map((post) => (
{groups[relKey].map((post) => (
<StoreEntries
key={post.id}
url={store + post.id}

View File

@ -1,10 +0,0 @@
{
"glossaryId": "12816437-7e4f-4807-9137-eff9ce324a19",
"name": "OSR-en-fr",
"ready": true,
"sourceLang": "en",
"targetLang": "fr",
"creationTime": "2025-03-07T12:38:05.401Z",
"entryCount": 1,
"hash": "zDrlayXO43sB98wNoqof0g=="
}

View File

@ -1 +0,0 @@
TEST,TEST
1 TEST TEST

View File

@ -1,10 +0,0 @@
{
"glossaryId": "1eeeb1a8-19e6-4d86-b2dd-38fa3608ad85",
"name": "OSR-en-ja",
"ready": true,
"sourceLang": "en",
"targetLang": "ja",
"creationTime": "2025-03-08T18:37:42.658Z",
"entryCount": 1,
"hash": "zDrlayXO43sB98wNoqof0g=="
}

View File

@ -0,0 +1,10 @@
{
"glossaryId": "84421f37-6cff-40d1-8885-e85d7ff59c2f",
"name": "OSR-en-nl",
"ready": true,
"sourceLang": "en",
"targetLang": "nl",
"creationTime": "2025-03-11T10:43:47.516Z",
"entryCount": 1,
"hash": "zDrlayXO43sB98wNoqof0g=="
}