the white saviours
This commit is contained in:
parent
6cd7d8cef0
commit
c3c16a145e
File diff suppressed because one or more lines are too long
@ -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
48
package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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]"
|
||||
}
|
||||
@ -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
|
||||
|
||||
|
||||
/////////////////////////////////////////////
|
||||
//
|
||||
|
||||
@ -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, []))
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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)
|
||||
@ -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}
|
||||
|
||||
@ -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=="
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
TEST,TEST
|
||||
|
@ -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=="
|
||||
}
|
||||
10
undefined/glossary/en/nl/index_glossary.json
Normal file
10
undefined/glossary/en/nl/index_glossary.json
Normal 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=="
|
||||
}
|
||||
Reference in New Issue
Block a user