howto resources :)
@ -23,7 +23,7 @@
|
||||
"format": "unix-time"
|
||||
}
|
||||
],
|
||||
"default": "2025-03-20T23:45:29.153Z"
|
||||
"default": "2025-03-21T23:09:43.108Z"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
|
||||
@ -4,10 +4,7 @@
|
||||
"path": "."
|
||||
},
|
||||
{
|
||||
"path": "../astro-components"
|
||||
},
|
||||
{
|
||||
"path": "../polymech-mono/packages/commons"
|
||||
"path": "../polymech-mono/packages"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
|
||||
|
After Width: | Height: | Size: 356 KiB |
|
After Width: | Height: | Size: 392 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 167 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 99 KiB |
141
src/components/howtos/Detail.astro
Normal file
@ -0,0 +1,141 @@
|
||||
---
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { IHowto,asset_local_rel } from "@/model/howto";
|
||||
import { Img } from 'imagetools/components';
|
||||
import { i18n as Translate } from "@polymech/astro-base";
|
||||
import { files,forward_slash } from "@polymech/commons"
|
||||
import BaseLayout from "@/layouts/BaseLayout.astro";
|
||||
import Wrapper from "@/components/containers/Wrapper.astro";
|
||||
import GalleryK from "@/components/polymech/GalleryK.astro";
|
||||
import { HOWTO_FILES_WEB, HOWTO_FILES_ABS } from "config/config.js"
|
||||
import Sidebar from "@/components/howtos/sidebar2.astro";
|
||||
|
||||
interface Props {
|
||||
howto: IHowto;
|
||||
}
|
||||
const { howto } = Astro.props;
|
||||
const coverLocaleRel = await asset_local_rel(howto, howto.cover_image);
|
||||
const howto_abs = HOWTO_FILES_ABS(howto.slug)
|
||||
|
||||
let model_files: any = [ ...files(howto_abs, '**/**/*.(step|stp)')]
|
||||
model_files = model_files.map((f) => forward_slash(`${howto.slug}/${path.relative(path.resolve(howto_abs), f)}`))
|
||||
|
||||
---
|
||||
<BaseLayout class="markdown-content">
|
||||
<Wrapper>
|
||||
|
||||
<div class="howto-container max-w-4xl mx-auto p-4 ml-4 pl-8">
|
||||
<header class="mb-8">
|
||||
<h1 class="text-3xl font-bold mb-2">
|
||||
<Translate>{howto.title}</Translate>
|
||||
</h1>
|
||||
|
||||
<div class="bg-gray-50 p-4 rounded-lg m-4">
|
||||
{
|
||||
howto.tags.map((tag) => (
|
||||
<span class="inline-block text-gray-700 px-2 py-1 rounded-full text-sm mr-2">
|
||||
<Translate>{tag}</Translate>
|
||||
</span>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<!-- Cover image -->
|
||||
<div class="mb-4">
|
||||
<Img
|
||||
src={coverLocaleRel}
|
||||
alt={"none"}
|
||||
attributes={{
|
||||
img: { class: "w-full h-64 object-cover rounded-lg" }
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<!-- Metadata -->
|
||||
<div class="flex flex-wrap gap-4 mb-4 text-sm text-gray-600">
|
||||
<div>
|
||||
<span class="font-semibold">Difficulty:</span>
|
||||
<Translate>{howto.difficulty_level}</Translate>
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-semibold">Time:</span>
|
||||
<Translate>{howto.time}</Translate>
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-semibold">Views:</span> {howto.total_views}
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-semibold">Created by:</span> {howto._createdBy}
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-semibold">Country:</span> {howto.creatorCountry}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<p class="whitespace-pre-line white-space: pre-line;">
|
||||
<Translate><div set:html={howto.description.replace(/\n/g, '<br>')}></div></Translate>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<h3 class="font-semibold mb-4"> <Translate>Resources</Translate></h3>
|
||||
<div class="">
|
||||
{
|
||||
howto.files.map((file) => (
|
||||
<a href={`${file.downloadUrl}`} target="_blank" rel="noopener noreferrer">{file.name}</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
<a href={`${HOWTO_FILES_WEB(howto.slug)}`} target="_blank" rel="noopener noreferrer"><Translate>Browse Files</Translate></a>
|
||||
|
||||
</div>
|
||||
|
||||
</header>
|
||||
|
||||
<!-- Steps -->
|
||||
<div class="steps-container space-y-12">
|
||||
{howto.steps.map((step, index) => (
|
||||
<div class="step-item" id={`step-${index + 1}`}>
|
||||
<h2 class="text-2xl font-semibold mb-4">
|
||||
<span class="bg-orange-500/75 text-white w-8 h-8 rounded-full text-center leading-8 mr-2 inline-block float-left">
|
||||
{index + 1}
|
||||
</span>
|
||||
<Translate>{step.title}</Translate>
|
||||
</h2>
|
||||
|
||||
<div class="step-content mb-6">
|
||||
<p class="whitespace-pre-line mb-4">
|
||||
<Translate><div set:html={step.text.replace(/\n/g, '<br>')}></div></Translate>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{step.images && step.images.length > 0 && (
|
||||
<div class="step-images">
|
||||
<GalleryK images={step.images} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<!-- Footer information -->
|
||||
<footer class="mt-12 pt-6 border-t border-gray-200">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<span class="text-sm text-gray-500">
|
||||
<Translate>Created</Translate>: {new Date(howto._created).toLocaleDateString()}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
<span>
|
||||
<Translate>Found useful by</Translate>: {howto.votedUsefulBy.length} <Translate>people</Translate>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</Wrapper>
|
||||
</BaseLayout>
|
||||
@ -1,7 +1,7 @@
|
||||
---
|
||||
import BaseLayout from "@/layouts/BaseLayout.astro";
|
||||
import { getCollection } from "astro:content";
|
||||
import Item from "@/components/howtos/Item.astro";
|
||||
import Item from "@/components/howtos/ListItem.astro";
|
||||
import Wrapper from "@/components/containers/Wrapper.astro";
|
||||
|
||||
const all = await getCollection("howtos");
|
||||
|
||||
188
src/components/howtos/howto.json
Normal file
@ -0,0 +1,188 @@
|
||||
{
|
||||
"_createdBy": "gus-merckel",
|
||||
"mentions": [],
|
||||
"_deleted": false,
|
||||
"fileLink": "",
|
||||
"slug": "cut-out-shapes-out-of-plastic-sheets-with-a-cnc-",
|
||||
"_modified": "2023-10-27T18:09:36.519Z",
|
||||
"previousSlugs": [
|
||||
"cut-out-shapes-out-of-plastic-sheets-with-a-cnc-"
|
||||
],
|
||||
"_created": "2023-08-23T18:20:09.098Z",
|
||||
"description": "In this how to, I will show you our process to cut HDPE Sheets using a X-Carve CNC.\n\nHere is the full video in spanish with subtitles https://www.youtube.com/watch?v=4LrrFz802To ",
|
||||
"votedUsefulBy": [
|
||||
"sigolene",
|
||||
"mattia",
|
||||
"uillinoispreciousplastics"
|
||||
],
|
||||
"creatorCountry": "mx",
|
||||
"total_downloads": 0,
|
||||
"title": "Cut out shapes out of plastic sheets with a CNC ",
|
||||
"time": "< 5 hours",
|
||||
"files": [],
|
||||
"difficulty_level": "Medium",
|
||||
"_id": "038gjWgLjiyYknbEjDeI",
|
||||
"tags": {
|
||||
"RTCBJAFa05YBVVBy0KeO": true
|
||||
},
|
||||
"category":"machines",
|
||||
"total_views": 232,
|
||||
"_contentModifiedTimestamp": "2023-08-23T18:20:09.098Z",
|
||||
"cover_image": {
|
||||
"name": "IMG_20200605_142311.jpg",
|
||||
"downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2F038gjWgLjiyYknbEjDeI%2FIMG_20200605_142311.jpg?alt=media&token=c272c174-1adc-45af-967b-771adce7295d",
|
||||
"type": "image/jpeg",
|
||||
"fullPath": "uploads/howtos/038gjWgLjiyYknbEjDeI/IMG_20200605_142311.jpg",
|
||||
"updated": "2021-04-05T15:09:00.605Z",
|
||||
"size": 124661,
|
||||
"timeCreated": "2021-04-05T15:09:00.605Z",
|
||||
"contentType": "image/jpeg"
|
||||
},
|
||||
"comments": [],
|
||||
"moderatorFeedback": "",
|
||||
"steps": [
|
||||
{
|
||||
"title": "Measure the plastic sheet",
|
||||
"text": "For this step we need to measure our plastic sheet: Height, Width and Thickness. Our X-Carve machine works with the CAM Software EASEL, for me, the easiest software for CNC milling out there. \n\nThe cool thing about Easel (https://easel.inventables.com/) is that you can \"simulate\" your actual material and THEY EVEN HAVE HDPE 2-Colors in their cutting material lists!!\n\n\n",
|
||||
"images": [
|
||||
{
|
||||
"fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/1.jpg",
|
||||
"name": "1.jpg",
|
||||
"size": 74095,
|
||||
"type": "image/jpeg",
|
||||
"timeCreated": "2021-03-26T19:42:05.766Z",
|
||||
"contentType": "image/jpeg",
|
||||
"downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F1.jpg?alt=media&token=293d733d-05a5-494a-9340-47f4564f1939",
|
||||
"updated": "2021-03-26T19:42:05.766Z"
|
||||
},
|
||||
{
|
||||
"contentType": "image/jpeg",
|
||||
"timeCreated": "2021-03-26T19:42:05.669Z",
|
||||
"updated": "2021-03-26T19:42:05.669Z",
|
||||
"size": 69665,
|
||||
"downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F2.jpg?alt=media&token=004f50f1-97ac-4df4-9ba9-f463aa4cbca3",
|
||||
"fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/2.jpg",
|
||||
"name": "2.jpg",
|
||||
"type": "image/jpeg"
|
||||
}
|
||||
],
|
||||
"_animationKey": "unique1"
|
||||
},
|
||||
{
|
||||
"text": "Using the CNC clamps from the X-Carve, secure the sheet to the table, ",
|
||||
"_animationKey": "unique2",
|
||||
"images": [
|
||||
{
|
||||
"updated": "2021-03-26T19:42:06.249Z",
|
||||
"size": 55544,
|
||||
"fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/3.jpg",
|
||||
"timeCreated": "2021-03-26T19:42:06.249Z",
|
||||
"name": "3.jpg",
|
||||
"downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F3.jpg?alt=media&token=0b9c1914-1c75-429e-b34a-1e2b3706edef",
|
||||
"contentType": "image/jpeg",
|
||||
"type": "image/jpeg"
|
||||
}
|
||||
],
|
||||
"title": "Secure sheet "
|
||||
},
|
||||
{
|
||||
"title": "Choosing a file to cut ",
|
||||
"text": "Now we go to our illustrator, such as Inkscape to design a vector file or download and open source one frome https://thenounproject.com/.\n\nWe download the SVG file, which is an open source vector format and import it to Easel. \n",
|
||||
"images": [
|
||||
{
|
||||
"downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F4.jpg?alt=media&token=1cd2d49d-9335-4bb1-ac2a-e625322ca604",
|
||||
"contentType": "image/jpeg",
|
||||
"timeCreated": "2021-03-26T19:42:06.727Z",
|
||||
"updated": "2021-03-26T19:42:06.727Z",
|
||||
"name": "4.jpg",
|
||||
"size": 42952,
|
||||
"type": "image/jpeg",
|
||||
"fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/4.jpg"
|
||||
},
|
||||
{
|
||||
"size": 69255,
|
||||
"fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/5.jpg",
|
||||
"updated": "2021-03-26T19:42:06.833Z",
|
||||
"timeCreated": "2021-03-26T19:42:06.833Z",
|
||||
"downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F5.jpg?alt=media&token=7cca786a-7d47-43bb-900b-b8d101c276b4",
|
||||
"name": "5.jpg",
|
||||
"contentType": "image/jpeg",
|
||||
"type": "image/jpeg"
|
||||
}
|
||||
],
|
||||
"_animationKey": "unique3"
|
||||
},
|
||||
{
|
||||
"text": "Now with the file we can choose the width we want to carve/cut and then we go to cut and start the wizzard:\n- We check that the sheet is fixed.\n- We also specify the cutting bit, we are using a 1/8 flat flute bit. \n- We tell the machine where the coordinate 0-0 is, which we always choose as the down left corner.\n- We raise the bit, turn on the Router!!!\n\nAND PUM THE MAGIC BEGINS!!",
|
||||
"title": "Follow the cutting Wizzard",
|
||||
"images": [
|
||||
{
|
||||
"timeCreated": "2021-03-26T19:42:07.493Z",
|
||||
"size": 72226,
|
||||
"fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/6.jpg",
|
||||
"updated": "2021-03-26T19:42:07.493Z",
|
||||
"downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F6.jpg?alt=media&token=ba7195dd-7771-435f-a188-057457697332",
|
||||
"contentType": "image/jpeg",
|
||||
"type": "image/jpeg",
|
||||
"name": "6.jpg"
|
||||
},
|
||||
{
|
||||
"fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/7.jpg",
|
||||
"size": 52424,
|
||||
"downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F7.jpg?alt=media&token=a3d5820c-cfe2-484e-8f76-f861ab8b756d",
|
||||
"contentType": "image/jpeg",
|
||||
"type": "image/jpeg",
|
||||
"timeCreated": "2021-03-26T19:42:07.308Z",
|
||||
"updated": "2021-03-26T19:42:07.308Z",
|
||||
"name": "7.jpg"
|
||||
},
|
||||
{
|
||||
"fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/8.jpg",
|
||||
"name": "8.jpg",
|
||||
"type": "image/jpeg",
|
||||
"timeCreated": "2021-03-26T19:42:07.346Z",
|
||||
"size": 55264,
|
||||
"contentType": "image/jpeg",
|
||||
"downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F8.jpg?alt=media&token=1c9816d7-3a99-4f41-8d3c-acc2670240f6",
|
||||
"updated": "2021-03-26T19:42:07.346Z"
|
||||
}
|
||||
],
|
||||
"_animationKey": "uniquenisc2v"
|
||||
},
|
||||
{
|
||||
"text": "You take now your glasses or object and postprocess them and of course show it to your friends, family and so on.\n\n\n",
|
||||
"images": [
|
||||
{
|
||||
"fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/9.jpg",
|
||||
"contentType": "image/jpeg",
|
||||
"timeCreated": "2021-03-26T19:42:08.147Z",
|
||||
"downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F9.jpg?alt=media&token=4dcfe37d-e1ad-41e5-a590-40b4c37c5e1a",
|
||||
"name": "9.jpg",
|
||||
"updated": "2021-03-26T19:42:08.147Z",
|
||||
"type": "image/jpeg",
|
||||
"size": 82214
|
||||
}
|
||||
],
|
||||
"_animationKey": "uniquesgl34",
|
||||
"title": "Post-production and show case"
|
||||
},
|
||||
{
|
||||
"_animationKey": "uniquem4y0yi",
|
||||
"title": "Hack it and try it yourself",
|
||||
"text": "You can try this project with other types of CNC machines, even manual Routers or manual saw, as I did on this video: https://youtu.be/gxkcffQD3eQ, but the important thing is that you share what you do and help this community to grow!!!\n\nShare your ideas and comments!",
|
||||
"images": [
|
||||
{
|
||||
"contentType": "image/jpeg",
|
||||
"timeCreated": "2021-04-05T15:09:01.445Z",
|
||||
"fullPath": "uploads/howtos/038gjWgLjiyYknbEjDeI/IMG_20200605_142311.jpg",
|
||||
"type": "image/jpeg",
|
||||
"downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2F038gjWgLjiyYknbEjDeI%2FIMG_20200605_142311.jpg?alt=media&token=f94152ff-f923-4054-a3ad-d8ec588856fa",
|
||||
"size": 124661,
|
||||
"updated": "2021-04-05T15:09:01.445Z",
|
||||
"name": "IMG_20200605_142311.jpg"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"moderation": "accepted"
|
||||
}
|
||||
47
src/components/howtos/todos.md
Normal file
@ -0,0 +1,47 @@
|
||||
## Todos
|
||||
|
||||
- for Astro, tailwind
|
||||
- no react or additional dependencies
|
||||
- IHowto: import { IHowto } from "@/model/howto.js"
|
||||
- For text, use {text} (import { i18n as Translate } from "@polymech/astro-base")
|
||||
- data is provided via `import { getCollection } from 'astro:content'
|
||||
const items = await getCollection('howtos')`
|
||||
|
||||
### Example Tailwind Sidebar
|
||||
|
||||
<button data-drawer-target="default-sidebar" data-drawer-toggle="default-sidebar" aria-controls="default-sidebar" type="button" class="inline-flex items-center p-2 mt-2 ms-3 text-sm text-gray-500 rounded-lg sm:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600">
|
||||
<span class="sr-only">Open sidebar</span>
|
||||
<svg class="w-6 h-6" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path clip-rule="evenodd" fill-rule="evenodd" d="M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<aside id="default-sidebar" class="fixed top-0 left-0 z-40 w-64 h-screen transition-transform -translate-x-full sm:translate-x-0" aria-label="Sidebar">
|
||||
<div class="h-full px-3 py-4 overflow-y-auto bg-gray-50 dark:bg-gray-800">
|
||||
<ul class="space-y-2 font-medium">
|
||||
<li>
|
||||
<a href="#" class="flex items-center p-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
||||
<svg class="w-5 h-5 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 22 21">
|
||||
<path d="M16.975 11H10V4.025a1 1 0 0 0-1.066-.998 8.5 8.5 0 1 0 9.039 9.039.999.999 0 0 0-1-1.066h.002Z"/>
|
||||
<path d="M12.5 0c-.157 0-.311.01-.565.027A1 1 0 0 0 11 1.02V10h8.975a1 1 0 0 0 1-.935c.013-.188.028-.374.028-.565A8.51 8.51 0 0 0 12.5 0Z"/>
|
||||
</svg>
|
||||
<span class="ms-3">Dashboard</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="flex items-center p-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
||||
<svg class="shrink-0 w-5 h-5 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 18">
|
||||
<path d="M6.1"/>
|
||||
</svg>
|
||||
<span class="flex-1 ms-3 whitespace-nowrap">Kanban</span>
|
||||
<span class="inline-flex items-center justify-center px-2 ms-3 text-sm font-medium text-gray-800 bg-gray-100 rounded-full dark:bg-gray-700 dark:text-gray-300">Pro</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
## Todos
|
||||
|
||||
- [ ] create minimal Astro component, a sidebar using flowbit (see example) for the given interface ("IHowto") and sample data ../components/libary/howto.astro, use the 'category' field, save as ../components/howtos/sidebar2.astro
|
||||
7
src/components/howtos/todos.sh
Normal file
@ -0,0 +1,7 @@
|
||||
kbotd --preferences ./todos-howto.md \
|
||||
--include=./howto.ts \
|
||||
--include=./howto.json \
|
||||
--disable=terminal,git,npm,user,interact,search,email,web \
|
||||
--disableTools=read_file,read_files,list_files,file_exists,web \
|
||||
--model=anthropic/claude-3.7-sonnet
|
||||
|
||||
@ -20,9 +20,12 @@ export const I18N_ASSET_PATH = "${SRC_DIR}/${SRC_NAME}-${DST_LANG}${SRC_EXT}"
|
||||
|
||||
// Library - Howtos
|
||||
|
||||
// Products
|
||||
export const HOWTO_ROOT = () => path.resolve(resolve("./public/resources/howtos"))
|
||||
export const HOWTO_GLOB = '**/config.json'
|
||||
export const FILES_WEB = 'https://files.polymech.io/files/machines/howtos/'
|
||||
|
||||
export const HOWTO_ROOT = () => path.resolve(resolve("./public/resources/howtos"))
|
||||
export const HOWTO_FILES_ABS = (id) => `${HOWTO_ROOT()}/${id}`
|
||||
export const HOWTO_FILES_WEB = (id: string) => `${FILES_WEB}/${id}`
|
||||
|
||||
|
||||
// Products
|
||||
|
||||
@ -1,35 +1,45 @@
|
||||
---
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { IHowto,asset_local_rel } from "@/model/howto";
|
||||
import { Gallery } from "@polymech/astro-base";
|
||||
import { Img } from 'imagetools/components';
|
||||
import { i18n as Translate } from "@polymech/astro-base";
|
||||
import Item from "@/components/howtos/Item.astro";
|
||||
import { files,forward_slash } from "@polymech/commons"
|
||||
import BaseLayout from "@/layouts/BaseLayout.astro";
|
||||
import Wrapper from "@/components/containers/Wrapper.astro";
|
||||
import GalleryK from "@/components/polymech/GalleryK.astro";
|
||||
import { HOWTO_FILES_WEB, HOWTO_FILES_ABS } from "config/config.js"
|
||||
import Sidebar from "@/components/howtos/sidebar2.astro";
|
||||
|
||||
interface Props {
|
||||
howto: IHowto;
|
||||
}
|
||||
const { howto } = Astro.props;
|
||||
import BaseLayout from "@/layouts/BaseLayout.astro";
|
||||
import Wrapper from "@/components/containers/Wrapper.astro";
|
||||
const _url = Astro.url
|
||||
const canonicalUrl = _url.origin
|
||||
function getProxyUrl(imageUrl: string): string {
|
||||
const ret = `${canonicalUrl}/api/image-proxy?url=${encodeURIComponent(imageUrl)}`;
|
||||
return ret
|
||||
}
|
||||
const { howto } = Astro.props;
|
||||
const coverLocaleRel = await asset_local_rel(howto, howto.cover_image);
|
||||
import Sidebar from "@/components/howtos/sidebar2.astro";
|
||||
import GalleryK from "@/components/polymech/GalleryK.astro";
|
||||
const howto_abs = HOWTO_FILES_ABS(howto.slug)
|
||||
|
||||
let model_files: any = [ ...files(howto_abs, '**/**/*.(step|stp)')]
|
||||
model_files = model_files.map((f) => forward_slash(`${howto.slug}/${path.relative(path.resolve(howto_abs), f)}`))
|
||||
|
||||
---
|
||||
<BaseLayout class="markdown-content">
|
||||
<Wrapper>
|
||||
<Sidebar />
|
||||
|
||||
<div class="howto-container max-w-4xl mx-auto p-4 ml-4 pl-8">
|
||||
<header class="mb-8">
|
||||
<h1 class="text-3xl font-bold mb-2">
|
||||
<Translate>{howto.title}</Translate>
|
||||
</h1>
|
||||
|
||||
|
||||
<div class="bg-gray-50 p-4 rounded-lg m-4">
|
||||
{
|
||||
howto.tags.map((tag) => (
|
||||
<span class="inline-block text-gray-700 px-2 py-1 rounded-full text-sm mr-2">
|
||||
<Translate>{tag}</Translate>
|
||||
</span>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<!-- Cover image -->
|
||||
<div class="mb-4">
|
||||
<Img
|
||||
@ -39,8 +49,7 @@ import GalleryK from "@/components/polymech/GalleryK.astro";
|
||||
img: { class: "w-full h-64 object-cover rounded-lg" }
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- Metadata -->
|
||||
<div class="flex flex-wrap gap-4 mb-4 text-sm text-gray-600">
|
||||
<div>
|
||||
@ -61,13 +70,30 @@ import GalleryK from "@/components/polymech/GalleryK.astro";
|
||||
<span class="font-semibold">Country:</span> {howto.creatorCountry}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Description -->
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<p class="whitespace-pre-line">
|
||||
<Translate>{howto.description}</Translate>
|
||||
<p class="whitespace-pre-line white-space: pre-line;">
|
||||
<Translate><div set:html={howto.description.replace(/\n/g, '<br>')}></div></Translate>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<h3 class="font-semibold mb-4"> <Translate>Resources</Translate></h3>
|
||||
<div class="">
|
||||
{
|
||||
howto.files.map((file) => (
|
||||
<a href={`${file.downloadUrl}`} target="_blank" rel="noopener noreferrer">{file.name}</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
<a href={`${HOWTO_FILES_WEB(howto.slug)}`} target="_blank" rel="noopener noreferrer"><Translate>Browse Files</Translate></a>
|
||||
|
||||
</div>
|
||||
|
||||
</header>
|
||||
|
||||
<!-- Steps -->
|
||||
@ -75,16 +101,15 @@ import GalleryK from "@/components/polymech/GalleryK.astro";
|
||||
{howto.steps.map((step, index) => (
|
||||
<div class="step-item" id={`step-${index + 1}`}>
|
||||
<h2 class="text-2xl font-semibold mb-4">
|
||||
<span class="inline-block bg-blue-500 text-white w-8 h-8 rounded-full text-center leading-8 mr-2">
|
||||
<span class="bg-orange-500/75 text-white w-8 h-8 rounded-full text-center leading-8 mr-2 inline-block float-left">
|
||||
{index + 1}
|
||||
</span>
|
||||
<Translate>{step.title}</Translate>
|
||||
</h2>
|
||||
|
||||
<!-- Step content -->
|
||||
<div class="step-content mb-6">
|
||||
<p class="whitespace-pre-line mb-4">
|
||||
<Translate>{step.text}</Translate>
|
||||
<Translate><div set:html={step.text.replace(/\n/g, '<br>')}></div></Translate>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@ -63,7 +63,6 @@ export const items = (branch: string) =>{
|
||||
PRODUCT_ROOT(), PFilterValid.marketplace_component), branch)
|
||||
}
|
||||
|
||||
|
||||
const onComponent = async (item: IStoreItem, ctx: LoaderContext) => {
|
||||
/*
|
||||
const onNode = async (data: INodeCallback, configuration: string) => {
|
||||
|
||||
@ -6,9 +6,7 @@ import { filesEx, resolveConfig, resolve } from '@polymech/commons'
|
||||
import { ICADNodeSchema, IComponentConfig } from '@polymech/commons/component'
|
||||
import { RenderedContent, DataEntry } from "astro:content"
|
||||
import type { Loader, LoaderContext } from 'astro/loaders'
|
||||
import { get } from '@polymech/commons/component'
|
||||
import { forward_slash } from "@polymech/commons"
|
||||
import { sanitizeFilename, validateFilename } from "@polymech/fs/utils"
|
||||
import { sanitizeFilename } from "@polymech/fs/utils"
|
||||
|
||||
import {
|
||||
CAD_MAIN_MATCH, PRODUCT_BRANCHES,
|
||||
@ -32,18 +30,19 @@ 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"
|
||||
import { got } from 'got'
|
||||
import pMap from 'p-map'
|
||||
|
||||
import { HOWTO_MIGRATION } from '@/app/config.js'
|
||||
import { createWriteStream } from 'fs';
|
||||
|
||||
export const ITEM_TYPE = 'howto'
|
||||
|
||||
//export const load = () => get(`${HOWTO_ROOT()}/${HOWTO_GLOB}`, HOWTO_ROOT(), ITEM_TYPE)
|
||||
export const item_path = (item: any) => {
|
||||
return `${HOWTO_ROOT()}/${item.data.slug}`
|
||||
}
|
||||
export const item_path = (item: any) => `${HOWTO_ROOT()}/${item.data.slug}`
|
||||
|
||||
const download = async (url, outputPath) => {
|
||||
const stream = createWriteStream(outputPath);
|
||||
got.stream(url).pipe(stream);
|
||||
@ -60,8 +59,6 @@ export const asset_local_abs = async (item: IHowto, asset: Image) => {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
export const asset_local_rel = async (item: IHowto, asset: Image) => {
|
||||
const sanitizedFilename = sanitizeFilename(asset.name)
|
||||
const asset_path = path.join(HOWTO_ROOT(), item.slug, sanitizedFilename)
|
||||
@ -79,7 +76,6 @@ export const howtos = async () => {
|
||||
let howtos = data.v3_howtos as any[]
|
||||
howtos = howtos.filter((h) => h.moderation == 'accepted');
|
||||
const tags = data.v3_tags;
|
||||
|
||||
let output = {
|
||||
tags: {},
|
||||
howtows: {}
|
||||
@ -128,7 +124,6 @@ export const defaults = async (data: any, cwd: string, root: string) => {
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
const onItem = async (store: any, ctx: LoaderContext) => {
|
||||
const item = store.data.item as IHowto
|
||||
item.steps = item.steps || []
|
||||
@ -136,7 +131,7 @@ const onItem = async (store: any, ctx: LoaderContext) => {
|
||||
step.images = await pMap(step.images, async (image) => {
|
||||
return {
|
||||
...image,
|
||||
src: await asset_local_rel(item, image),
|
||||
src: await asset_local_rel(item, image) || default_image().src,
|
||||
alt: image.name || ''
|
||||
};
|
||||
}, {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
---
|
||||
import Layout from '@/layouts/Howto.astro'
|
||||
import Layout from '@/components/howtos/Detail.astro'
|
||||
import { getCollection } from 'astro:content'
|
||||
|
||||
import { LANGUAGES_PROD as LANGUAGES } from "config/config.js"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
---
|
||||
import BaseLayout from "@/layouts/BaseLayout.astro"
|
||||
import { getCollection } from "astro:content"
|
||||
import List from "@/components/howtos/Item.astro"
|
||||
import List from "@/components/howtos/ListItem.astro"
|
||||
const all = await getCollection("howtos")
|
||||
const locale = Astro.currentLocale || "en"
|
||||
|
||||
|
||||