@things-factory/figure-service 10.1.70 → 10.1.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-server/index.js +6 -11
- package/dist-server/index.js.map +1 -1
- package/dist-server/routers/figure-thumbnail-router.js +22 -0
- package/dist-server/routers/figure-thumbnail-router.js.map +1 -1
- package/dist-server/service/figure-recipe/figure-recipe-resolver.d.ts +4 -6
- package/dist-server/service/figure-recipe/figure-recipe-resolver.js +36 -51
- package/dist-server/service/figure-recipe/figure-recipe-resolver.js.map +1 -1
- package/dist-server/service/figure-recipe/figure-recipe-templates.d.ts +28 -0
- package/dist-server/service/figure-recipe/figure-recipe-templates.js +57 -0
- package/dist-server/service/figure-recipe/figure-recipe-templates.js.map +1 -0
- package/dist-server/service/figure-recipe/figure-recipe-type.d.ts +11 -0
- package/dist-server/service/figure-recipe/figure-recipe-type.js +48 -0
- package/dist-server/service/figure-recipe/figure-recipe-type.js.map +1 -0
- package/dist-server/service/figure-recipe/index.d.ts +2 -4
- package/dist-server/service/figure-recipe/index.js +2 -4
- package/dist-server/service/figure-recipe/index.js.map +1 -1
- package/dist-server/service/index.d.ts +4 -4
- package/dist-server/service/index.js +7 -8
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -4
- package/recipes/_lib/model.mts +71 -0
- package/recipes/air-cylinder/build.mts +14 -0
- package/recipes/air-cylinder/facts.md +3 -0
- package/recipes/air-cylinder/guide.en.md +18 -0
- package/recipes/air-cylinder/guide.ko.md +18 -0
- package/recipes/air-cylinder/models/default.json +1 -0
- package/recipes/air-cylinder/recipe.json +129 -0
- package/recipes/air-cylinder/spec.json +45 -0
- package/recipes/air-cylinder/thumbnail.png +0 -0
- package/recipes/air-shower/build.mts +14 -0
- package/recipes/air-shower/facts.md +3 -0
- package/recipes/air-shower/guide.en.md +17 -0
- package/recipes/air-shower/guide.ko.md +17 -0
- package/recipes/air-shower/models/default.json +1 -0
- package/recipes/air-shower/recipe.json +111 -0
- package/recipes/air-shower/spec.json +18 -0
- package/recipes/air-shower/thumbnail.png +0 -0
- package/recipes/barrier-gate/build.mts +11 -0
- package/recipes/barrier-gate/facts.md +3 -0
- package/recipes/barrier-gate/guide.en.md +18 -0
- package/recipes/barrier-gate/guide.ko.md +18 -0
- package/recipes/barrier-gate/models/default.json +1 -0
- package/recipes/barrier-gate/recipe.json +87 -0
- package/recipes/barrier-gate/spec.json +45 -0
- package/recipes/barrier-gate/thumbnail.png +0 -0
- package/recipes/base-frame/build.mts +11 -0
- package/recipes/base-frame/facts.md +3 -0
- package/recipes/base-frame/guide.en.md +18 -0
- package/recipes/base-frame/guide.ko.md +18 -0
- package/recipes/base-frame/models/default.json +1 -0
- package/recipes/base-frame/recipe.json +259 -0
- package/recipes/base-frame/spec.json +72 -0
- package/recipes/base-frame/thumbnail.png +0 -0
- package/recipes/belt-surface/build.mts +12 -0
- package/recipes/belt-surface/facts.md +3 -0
- package/recipes/belt-surface/guide.en.md +18 -0
- package/recipes/belt-surface/guide.ko.md +18 -0
- package/recipes/belt-surface/models/default.json +1 -0
- package/recipes/belt-surface/recipe.json +116 -0
- package/recipes/belt-surface/spec.json +69 -0
- package/recipes/belt-surface/thumbnail.png +0 -0
- package/recipes/bin-row/build.mts +11 -0
- package/recipes/bin-row/facts.md +3 -0
- package/recipes/bin-row/guide.en.md +18 -0
- package/recipes/bin-row/guide.ko.md +18 -0
- package/recipes/bin-row/models/default.json +1 -0
- package/recipes/bin-row/recipe.json +87 -0
- package/recipes/bin-row/spec.json +45 -0
- package/recipes/bin-row/thumbnail.png +0 -0
- package/recipes/bollard-row/build.mts +11 -0
- package/recipes/bollard-row/facts.md +3 -0
- package/recipes/bollard-row/guide.en.md +18 -0
- package/recipes/bollard-row/guide.ko.md +18 -0
- package/recipes/bollard-row/models/default.json +1 -0
- package/recipes/bollard-row/recipe.json +107 -0
- package/recipes/bollard-row/spec.json +71 -0
- package/recipes/bollard-row/thumbnail.png +0 -0
- package/recipes/buzzer/build.mts +10 -0
- package/recipes/buzzer/facts.md +3 -0
- package/recipes/buzzer/guide.en.md +17 -0
- package/recipes/buzzer/guide.ko.md +17 -0
- package/recipes/buzzer/models/default.json +1 -0
- package/recipes/buzzer/recipe.json +68 -0
- package/recipes/buzzer/spec.json +18 -0
- package/recipes/buzzer/thumbnail.png +0 -0
- package/recipes/cabinet-doors/build.mts +21 -0
- package/recipes/cabinet-doors/facts.md +3 -0
- package/recipes/cabinet-doors/guide.en.md +19 -0
- package/recipes/cabinet-doors/guide.ko.md +19 -0
- package/recipes/cabinet-doors/models/default.json +1 -0
- package/recipes/cabinet-doors/models/doors=1.json +1 -0
- package/recipes/cabinet-doors/models/doors=2.json +1 -0
- package/recipes/cabinet-doors/recipe.json +240 -0
- package/recipes/cabinet-doors/spec.json +36 -0
- package/recipes/cabinet-doors/thumbnail.png +0 -0
- package/recipes/cable-tray/build.mts +16 -0
- package/recipes/cable-tray/facts.md +3 -0
- package/recipes/cable-tray/guide.en.md +18 -0
- package/recipes/cable-tray/guide.ko.md +18 -0
- package/recipes/cable-tray/models/default.json +1 -0
- package/recipes/cable-tray/recipe.json +198 -0
- package/recipes/cable-tray/spec.json +45 -0
- package/recipes/cable-tray/thumbnail.png +0 -0
- package/recipes/camera-mount/build.mts +10 -0
- package/recipes/camera-mount/facts.md +3 -0
- package/recipes/camera-mount/guide.en.md +16 -0
- package/recipes/camera-mount/guide.ko.md +16 -0
- package/recipes/camera-mount/models/default.json +1 -0
- package/recipes/camera-mount/recipe.json +108 -0
- package/recipes/camera-mount/spec.json +18 -0
- package/recipes/camera-mount/thumbnail.png +0 -0
- package/recipes/canopy/build.mts +17 -0
- package/recipes/canopy/facts.md +3 -0
- package/recipes/canopy/guide.en.md +18 -0
- package/recipes/canopy/guide.ko.md +18 -0
- package/recipes/canopy/models/default.json +1 -0
- package/recipes/canopy/recipe.json +281 -0
- package/recipes/canopy/spec.json +81 -0
- package/recipes/canopy/thumbnail.png +0 -0
- package/recipes/cart/build.mts +24 -0
- package/recipes/cart/facts.md +3 -0
- package/recipes/cart/guide.en.md +16 -0
- package/recipes/cart/guide.ko.md +16 -0
- package/recipes/cart/models/default.json +1 -0
- package/recipes/cart/recipe.json +555 -0
- package/recipes/cart/spec.json +11 -0
- package/recipes/cart/thumbnail.png +0 -0
- package/recipes/cart-handle/build.mts +13 -0
- package/recipes/cart-handle/facts.md +3 -0
- package/recipes/cart-handle/guide.en.md +16 -0
- package/recipes/cart-handle/guide.ko.md +16 -0
- package/recipes/cart-handle/models/default.json +1 -0
- package/recipes/cart-handle/recipe.json +161 -0
- package/recipes/cart-handle/spec.json +18 -0
- package/recipes/cart-handle/thumbnail.png +0 -0
- package/recipes/casters-four/build.mts +16 -0
- package/recipes/casters-four/facts.md +3 -0
- package/recipes/casters-four/guide.en.md +19 -0
- package/recipes/casters-four/guide.ko.md +19 -0
- package/recipes/casters-four/models/default.json +1 -0
- package/recipes/casters-four/recipe.json +409 -0
- package/recipes/casters-four/spec.json +72 -0
- package/recipes/casters-four/thumbnail.png +0 -0
- package/recipes/ceiling-rail-carriage/build.mts +15 -0
- package/recipes/ceiling-rail-carriage/facts.md +3 -0
- package/recipes/ceiling-rail-carriage/guide.en.md +17 -0
- package/recipes/ceiling-rail-carriage/guide.ko.md +17 -0
- package/recipes/ceiling-rail-carriage/models/default.json +1 -0
- package/recipes/ceiling-rail-carriage/recipe.json +127 -0
- package/recipes/ceiling-rail-carriage/spec.json +19 -0
- package/recipes/ceiling-rail-carriage/thumbnail.png +0 -0
- package/recipes/chain-rails/build.mts +15 -0
- package/recipes/chain-rails/facts.md +3 -0
- package/recipes/chain-rails/guide.en.md +18 -0
- package/recipes/chain-rails/guide.ko.md +18 -0
- package/recipes/chain-rails/models/default.json +1 -0
- package/recipes/chain-rails/recipe.json +166 -0
- package/recipes/chain-rails/spec.json +54 -0
- package/recipes/chain-rails/thumbnail.png +0 -0
- package/recipes/charging-station/build.mts +9 -0
- package/recipes/charging-station/facts.md +3 -0
- package/recipes/charging-station/guide.en.md +16 -0
- package/recipes/charging-station/guide.ko.md +16 -0
- package/recipes/charging-station/models/default.json +1 -0
- package/recipes/charging-station/recipe.json +105 -0
- package/recipes/charging-station/spec.json +18 -0
- package/recipes/charging-station/thumbnail.png +0 -0
- package/recipes/column-row/build.mts +11 -0
- package/recipes/column-row/facts.md +3 -0
- package/recipes/column-row/guide.en.md +18 -0
- package/recipes/column-row/guide.ko.md +18 -0
- package/recipes/column-row/models/default.json +1 -0
- package/recipes/column-row/recipe.json +103 -0
- package/recipes/column-row/spec.json +71 -0
- package/recipes/column-row/thumbnail.png +0 -0
- package/recipes/control-cabinet/build.mts +14 -0
- package/recipes/control-cabinet/facts.md +3 -0
- package/recipes/control-cabinet/guide.en.md +17 -0
- package/recipes/control-cabinet/guide.ko.md +17 -0
- package/recipes/control-cabinet/models/default.json +1 -0
- package/recipes/control-cabinet/recipe.json +111 -0
- package/recipes/control-cabinet/spec.json +18 -0
- package/recipes/control-cabinet/thumbnail.png +0 -0
- package/recipes/conveyor-skirts/build.mts +9 -0
- package/recipes/conveyor-skirts/facts.md +3 -0
- package/recipes/conveyor-skirts/guide.en.md +16 -0
- package/recipes/conveyor-skirts/guide.ko.md +16 -0
- package/recipes/conveyor-skirts/models/default.json +1 -0
- package/recipes/conveyor-skirts/recipe.json +141 -0
- package/recipes/conveyor-skirts/spec.json +18 -0
- package/recipes/conveyor-skirts/thumbnail.png +0 -0
- package/recipes/counter-display/build.mts +13 -0
- package/recipes/counter-display/facts.md +3 -0
- package/recipes/counter-display/guide.en.md +17 -0
- package/recipes/counter-display/guide.ko.md +17 -0
- package/recipes/counter-display/models/default.json +1 -0
- package/recipes/counter-display/recipe.json +106 -0
- package/recipes/counter-display/spec.json +18 -0
- package/recipes/counter-display/thumbnail.png +0 -0
- package/recipes/dock-bumpers/build.mts +9 -0
- package/recipes/dock-bumpers/facts.md +3 -0
- package/recipes/dock-bumpers/guide.en.md +16 -0
- package/recipes/dock-bumpers/guide.ko.md +16 -0
- package/recipes/dock-bumpers/models/default.json +1 -0
- package/recipes/dock-bumpers/recipe.json +107 -0
- package/recipes/dock-bumpers/spec.json +18 -0
- package/recipes/dock-bumpers/thumbnail.png +0 -0
- package/recipes/dock-leveler/build.mts +11 -0
- package/recipes/dock-leveler/facts.md +3 -0
- package/recipes/dock-leveler/guide.en.md +16 -0
- package/recipes/dock-leveler/guide.ko.md +16 -0
- package/recipes/dock-leveler/models/default.json +1 -0
- package/recipes/dock-leveler/recipe.json +69 -0
- package/recipes/dock-leveler/spec.json +18 -0
- package/recipes/dock-leveler/thumbnail.png +0 -0
- package/recipes/double-hinged-door/build.mts +19 -0
- package/recipes/double-hinged-door/facts.md +3 -0
- package/recipes/double-hinged-door/guide.en.md +19 -0
- package/recipes/double-hinged-door/guide.ko.md +19 -0
- package/recipes/double-hinged-door/models/default.json +1 -0
- package/recipes/double-hinged-door/recipe.json +273 -0
- package/recipes/double-hinged-door/spec.json +69 -0
- package/recipes/double-hinged-door/thumbnail.png +0 -0
- package/recipes/drawer-stack/build.mts +17 -0
- package/recipes/drawer-stack/facts.md +3 -0
- package/recipes/drawer-stack/guide.en.md +18 -0
- package/recipes/drawer-stack/guide.ko.md +18 -0
- package/recipes/drawer-stack/models/default.json +1 -0
- package/recipes/drawer-stack/models/drawers=2.json +1 -0
- package/recipes/drawer-stack/models/drawers=3.json +1 -0
- package/recipes/drawer-stack/models/drawers=4.json +1 -0
- package/recipes/drawer-stack/recipe.json +789 -0
- package/recipes/drawer-stack/spec.json +37 -0
- package/recipes/drawer-stack/thumbnail.png +0 -0
- package/recipes/drip-tray/build.mts +12 -0
- package/recipes/drip-tray/facts.md +3 -0
- package/recipes/drip-tray/guide.en.md +16 -0
- package/recipes/drip-tray/guide.ko.md +16 -0
- package/recipes/drip-tray/models/default.json +1 -0
- package/recipes/drip-tray/recipe.json +86 -0
- package/recipes/drip-tray/spec.json +18 -0
- package/recipes/drip-tray/thumbnail.png +0 -0
- package/recipes/duct-fan/build.mts +14 -0
- package/recipes/duct-fan/facts.md +3 -0
- package/recipes/duct-fan/guide.en.md +16 -0
- package/recipes/duct-fan/guide.ko.md +16 -0
- package/recipes/duct-fan/models/default.json +1 -0
- package/recipes/duct-fan/recipe.json +122 -0
- package/recipes/duct-fan/spec.json +18 -0
- package/recipes/duct-fan/thumbnail.png +0 -0
- package/recipes/emergency-stop/build.mts +14 -0
- package/recipes/emergency-stop/facts.md +3 -0
- package/recipes/emergency-stop/guide.en.md +17 -0
- package/recipes/emergency-stop/guide.ko.md +17 -0
- package/recipes/emergency-stop/models/default.json +1 -0
- package/recipes/emergency-stop/recipe.json +109 -0
- package/recipes/emergency-stop/spec.json +18 -0
- package/recipes/emergency-stop/thumbnail.png +0 -0
- package/recipes/end-covers/build.mts +13 -0
- package/recipes/end-covers/facts.md +3 -0
- package/recipes/end-covers/guide.en.md +17 -0
- package/recipes/end-covers/guide.ko.md +17 -0
- package/recipes/end-covers/models/default.json +1 -0
- package/recipes/end-covers/recipe.json +141 -0
- package/recipes/end-covers/spec.json +18 -0
- package/recipes/end-covers/thumbnail.png +0 -0
- package/recipes/ffu-row/build.mts +11 -0
- package/recipes/ffu-row/facts.md +3 -0
- package/recipes/ffu-row/guide.en.md +18 -0
- package/recipes/ffu-row/guide.ko.md +18 -0
- package/recipes/ffu-row/models/default.json +1 -0
- package/recipes/ffu-row/recipe.json +82 -0
- package/recipes/ffu-row/spec.json +45 -0
- package/recipes/ffu-row/thumbnail.png +0 -0
- package/recipes/fire-extinguisher/build.mts +13 -0
- package/recipes/fire-extinguisher/facts.md +3 -0
- package/recipes/fire-extinguisher/guide.en.md +16 -0
- package/recipes/fire-extinguisher/guide.ko.md +16 -0
- package/recipes/fire-extinguisher/models/default.json +1 -0
- package/recipes/fire-extinguisher/recipe.json +114 -0
- package/recipes/fire-extinguisher/spec.json +18 -0
- package/recipes/fire-extinguisher/thumbnail.png +0 -0
- package/recipes/first-aid-box/build.mts +9 -0
- package/recipes/first-aid-box/facts.md +3 -0
- package/recipes/first-aid-box/guide.en.md +16 -0
- package/recipes/first-aid-box/guide.ko.md +16 -0
- package/recipes/first-aid-box/models/default.json +1 -0
- package/recipes/first-aid-box/recipe.json +97 -0
- package/recipes/first-aid-box/spec.json +18 -0
- package/recipes/first-aid-box/thumbnail.png +0 -0
- package/recipes/floor-zone/build.mts +17 -0
- package/recipes/floor-zone/facts.md +3 -0
- package/recipes/floor-zone/guide.en.md +18 -0
- package/recipes/floor-zone/guide.ko.md +18 -0
- package/recipes/floor-zone/models/default.json +1 -0
- package/recipes/floor-zone/recipe.json +257 -0
- package/recipes/floor-zone/spec.json +72 -0
- package/recipes/floor-zone/thumbnail.png +0 -0
- package/recipes/folding-door/build.mts +20 -0
- package/recipes/folding-door/facts.md +3 -0
- package/recipes/folding-door/guide.en.md +18 -0
- package/recipes/folding-door/guide.ko.md +18 -0
- package/recipes/folding-door/models/default.json +1 -0
- package/recipes/folding-door/recipe.json +217 -0
- package/recipes/folding-door/spec.json +44 -0
- package/recipes/folding-door/thumbnail.png +0 -0
- package/recipes/fork/build.mts +13 -0
- package/recipes/fork/facts.md +3 -0
- package/recipes/fork/guide.en.md +18 -0
- package/recipes/fork/guide.ko.md +18 -0
- package/recipes/fork/models/default.json +1 -0
- package/recipes/fork/recipe.json +136 -0
- package/recipes/fork/spec.json +45 -0
- package/recipes/fork/thumbnail.png +0 -0
- package/recipes/gantry-frame/build.mts +13 -0
- package/recipes/gantry-frame/facts.md +3 -0
- package/recipes/gantry-frame/guide.en.md +18 -0
- package/recipes/gantry-frame/guide.ko.md +18 -0
- package/recipes/gantry-frame/models/default.json +1 -0
- package/recipes/gantry-frame/recipe.json +178 -0
- package/recipes/gantry-frame/spec.json +63 -0
- package/recipes/gantry-frame/thumbnail.png +0 -0
- package/recipes/gripper-2f/build.mts +16 -0
- package/recipes/gripper-2f/facts.md +3 -0
- package/recipes/gripper-2f/guide.en.md +18 -0
- package/recipes/gripper-2f/guide.ko.md +18 -0
- package/recipes/gripper-2f/models/default.json +1 -0
- package/recipes/gripper-2f/recipe.json +172 -0
- package/recipes/gripper-2f/spec.json +45 -0
- package/recipes/gripper-2f/thumbnail.png +0 -0
- package/recipes/guard-cover/build.mts +13 -0
- package/recipes/guard-cover/facts.md +3 -0
- package/recipes/guard-cover/guide.en.md +18 -0
- package/recipes/guard-cover/guide.ko.md +18 -0
- package/recipes/guard-cover/models/default.json +1 -0
- package/recipes/guard-cover/recipe.json +92 -0
- package/recipes/guard-cover/spec.json +18 -0
- package/recipes/guard-cover/thumbnail.png +0 -0
- package/recipes/handrail/build.mts +15 -0
- package/recipes/handrail/facts.md +3 -0
- package/recipes/handrail/guide.en.md +18 -0
- package/recipes/handrail/guide.ko.md +18 -0
- package/recipes/handrail/models/default.json +1 -0
- package/recipes/handrail/recipe.json +140 -0
- package/recipes/handrail/spec.json +45 -0
- package/recipes/handrail/thumbnail.png +0 -0
- package/recipes/hatch/build.mts +17 -0
- package/recipes/hatch/facts.md +3 -0
- package/recipes/hatch/guide.en.md +18 -0
- package/recipes/hatch/guide.ko.md +18 -0
- package/recipes/hatch/models/default.json +1 -0
- package/recipes/hatch/recipe.json +194 -0
- package/recipes/hatch/spec.json +69 -0
- package/recipes/hatch/thumbnail.png +0 -0
- package/recipes/hinged-door/build.mts +26 -0
- package/recipes/hinged-door/facts.md +21 -0
- package/recipes/hinged-door/guide.en.md +19 -0
- package/recipes/hinged-door/guide.ko.md +19 -0
- package/recipes/hinged-door/models/default.json +1 -0
- package/recipes/hinged-door/models/hinge=left.json +1 -0
- package/recipes/hinged-door/models/hinge=right.json +1 -0
- package/recipes/hinged-door/recipe.json +252 -0
- package/recipes/hinged-door/spec.json +23 -0
- package/recipes/hinged-door/thumbnail.png +0 -0
- package/recipes/hmi-monitor/build.mts +14 -0
- package/recipes/hmi-monitor/facts.md +3 -0
- package/recipes/hmi-monitor/guide.en.md +17 -0
- package/recipes/hmi-monitor/guide.ko.md +17 -0
- package/recipes/hmi-monitor/models/default.json +1 -0
- package/recipes/hmi-monitor/recipe.json +108 -0
- package/recipes/hmi-monitor/spec.json +18 -0
- package/recipes/hmi-monitor/thumbnail.png +0 -0
- package/recipes/hopper/build.mts +14 -0
- package/recipes/hopper/facts.md +3 -0
- package/recipes/hopper/guide.en.md +18 -0
- package/recipes/hopper/guide.ko.md +18 -0
- package/recipes/hopper/models/default.json +1 -0
- package/recipes/hopper/recipe.json +137 -0
- package/recipes/hopper/spec.json +22 -0
- package/recipes/hopper/thumbnail.png +0 -0
- package/recipes/interlock-switch/build.mts +8 -0
- package/recipes/interlock-switch/facts.md +3 -0
- package/recipes/interlock-switch/guide.en.md +17 -0
- package/recipes/interlock-switch/guide.ko.md +17 -0
- package/recipes/interlock-switch/models/default.json +1 -0
- package/recipes/interlock-switch/recipe.json +63 -0
- package/recipes/interlock-switch/spec.json +18 -0
- package/recipes/interlock-switch/thumbnail.png +0 -0
- package/recipes/junction-box/build.mts +8 -0
- package/recipes/junction-box/facts.md +3 -0
- package/recipes/junction-box/guide.en.md +16 -0
- package/recipes/junction-box/guide.ko.md +16 -0
- package/recipes/junction-box/models/default.json +1 -0
- package/recipes/junction-box/recipe.json +64 -0
- package/recipes/junction-box/spec.json +18 -0
- package/recipes/junction-box/thumbnail.png +0 -0
- package/recipes/ladder/build.mts +13 -0
- package/recipes/ladder/facts.md +3 -0
- package/recipes/ladder/guide.en.md +18 -0
- package/recipes/ladder/guide.ko.md +18 -0
- package/recipes/ladder/models/default.json +1 -0
- package/recipes/ladder/recipe.json +191 -0
- package/recipes/ladder/spec.json +44 -0
- package/recipes/ladder/thumbnail.png +0 -0
- package/recipes/legs-four/build.mts +12 -0
- package/recipes/legs-four/facts.md +3 -0
- package/recipes/legs-four/guide.en.md +18 -0
- package/recipes/legs-four/guide.ko.md +18 -0
- package/recipes/legs-four/models/default.json +1 -0
- package/recipes/legs-four/recipe.json +217 -0
- package/recipes/legs-four/spec.json +72 -0
- package/recipes/legs-four/thumbnail.png +0 -0
- package/recipes/leveling-feet/build.mts +11 -0
- package/recipes/leveling-feet/facts.md +3 -0
- package/recipes/leveling-feet/guide.en.md +18 -0
- package/recipes/leveling-feet/guide.ko.md +18 -0
- package/recipes/leveling-feet/models/default.json +1 -0
- package/recipes/leveling-feet/recipe.json +233 -0
- package/recipes/leveling-feet/spec.json +72 -0
- package/recipes/leveling-feet/thumbnail.png +0 -0
- package/recipes/lift-updown/build.mts +13 -0
- package/recipes/lift-updown/facts.md +3 -0
- package/recipes/lift-updown/guide.en.md +18 -0
- package/recipes/lift-updown/guide.ko.md +18 -0
- package/recipes/lift-updown/models/default.json +1 -0
- package/recipes/lift-updown/recipe.json +107 -0
- package/recipes/lift-updown/spec.json +45 -0
- package/recipes/lift-updown/thumbnail.png +0 -0
- package/recipes/light-curtain/build.mts +11 -0
- package/recipes/light-curtain/facts.md +3 -0
- package/recipes/light-curtain/guide.en.md +18 -0
- package/recipes/light-curtain/guide.ko.md +18 -0
- package/recipes/light-curtain/models/default.json +1 -0
- package/recipes/light-curtain/recipe.json +127 -0
- package/recipes/light-curtain/spec.json +54 -0
- package/recipes/light-curtain/thumbnail.png +0 -0
- package/recipes/limit-switch/build.mts +14 -0
- package/recipes/limit-switch/facts.md +3 -0
- package/recipes/limit-switch/guide.en.md +17 -0
- package/recipes/limit-switch/guide.ko.md +17 -0
- package/recipes/limit-switch/models/default.json +1 -0
- package/recipes/limit-switch/recipe.json +107 -0
- package/recipes/limit-switch/spec.json +17 -0
- package/recipes/limit-switch/thumbnail.png +0 -0
- package/recipes/linear-guide/build.mts +15 -0
- package/recipes/linear-guide/facts.md +3 -0
- package/recipes/linear-guide/guide.en.md +18 -0
- package/recipes/linear-guide/guide.ko.md +18 -0
- package/recipes/linear-guide/models/default.json +1 -0
- package/recipes/linear-guide/recipe.json +145 -0
- package/recipes/linear-guide/spec.json +44 -0
- package/recipes/linear-guide/thumbnail.png +0 -0
- package/recipes/locker-row/build.mts +11 -0
- package/recipes/locker-row/facts.md +3 -0
- package/recipes/locker-row/guide.en.md +18 -0
- package/recipes/locker-row/guide.ko.md +18 -0
- package/recipes/locker-row/models/default.json +1 -0
- package/recipes/locker-row/recipe.json +82 -0
- package/recipes/locker-row/spec.json +45 -0
- package/recipes/locker-row/thumbnail.png +0 -0
- package/recipes/motor-box/build.mts +10 -0
- package/recipes/motor-box/facts.md +3 -0
- package/recipes/motor-box/guide.en.md +17 -0
- package/recipes/motor-box/guide.ko.md +17 -0
- package/recipes/motor-box/models/default.json +1 -0
- package/recipes/motor-box/recipe.json +64 -0
- package/recipes/motor-box/spec.json +18 -0
- package/recipes/motor-box/thumbnail.png +0 -0
- package/recipes/observation-window/build.mts +16 -0
- package/recipes/observation-window/facts.md +3 -0
- package/recipes/observation-window/guide.en.md +19 -0
- package/recipes/observation-window/guide.ko.md +19 -0
- package/recipes/observation-window/models/default.json +1 -0
- package/recipes/observation-window/recipe.json +185 -0
- package/recipes/observation-window/spec.json +68 -0
- package/recipes/observation-window/thumbnail.png +0 -0
- package/recipes/operator-panel/build.mts +14 -0
- package/recipes/operator-panel/facts.md +3 -0
- package/recipes/operator-panel/guide.en.md +18 -0
- package/recipes/operator-panel/guide.ko.md +18 -0
- package/recipes/operator-panel/models/buttons=2.json +1 -0
- package/recipes/operator-panel/models/buttons=3.json +1 -0
- package/recipes/operator-panel/models/buttons=4.json +1 -0
- package/recipes/operator-panel/models/buttons=5.json +1 -0
- package/recipes/operator-panel/models/default.json +1 -0
- package/recipes/operator-panel/recipe.json +210 -0
- package/recipes/operator-panel/spec.json +38 -0
- package/recipes/operator-panel/thumbnail.png +0 -0
- package/recipes/pallet/build.mts +12 -0
- package/recipes/pallet/facts.md +3 -0
- package/recipes/pallet/guide.en.md +17 -0
- package/recipes/pallet/guide.ko.md +17 -0
- package/recipes/pallet/models/default.json +1 -0
- package/recipes/pallet/recipe.json +181 -0
- package/recipes/pallet/spec.json +18 -0
- package/recipes/pallet/thumbnail.png +0 -0
- package/recipes/partition-wall/build.mts +16 -0
- package/recipes/partition-wall/facts.md +3 -0
- package/recipes/partition-wall/guide.en.md +18 -0
- package/recipes/partition-wall/guide.ko.md +18 -0
- package/recipes/partition-wall/models/default.json +1 -0
- package/recipes/partition-wall/recipe.json +203 -0
- package/recipes/partition-wall/spec.json +54 -0
- package/recipes/partition-wall/thumbnail.png +0 -0
- package/recipes/pass-box/build.mts +14 -0
- package/recipes/pass-box/facts.md +3 -0
- package/recipes/pass-box/guide.en.md +17 -0
- package/recipes/pass-box/guide.ko.md +17 -0
- package/recipes/pass-box/models/default.json +1 -0
- package/recipes/pass-box/recipe.json +107 -0
- package/recipes/pass-box/spec.json +18 -0
- package/recipes/pass-box/thumbnail.png +0 -0
- package/recipes/peg-board/build.mts +14 -0
- package/recipes/peg-board/facts.md +3 -0
- package/recipes/peg-board/guide.en.md +17 -0
- package/recipes/peg-board/guide.ko.md +17 -0
- package/recipes/peg-board/models/default.json +1 -0
- package/recipes/peg-board/recipe.json +120 -0
- package/recipes/peg-board/spec.json +45 -0
- package/recipes/peg-board/thumbnail.png +0 -0
- package/recipes/photo-sensor-pair/build.mts +11 -0
- package/recipes/photo-sensor-pair/facts.md +3 -0
- package/recipes/photo-sensor-pair/guide.en.md +18 -0
- package/recipes/photo-sensor-pair/guide.ko.md +18 -0
- package/recipes/photo-sensor-pair/models/default.json +1 -0
- package/recipes/photo-sensor-pair/recipe.json +145 -0
- package/recipes/photo-sensor-pair/spec.json +54 -0
- package/recipes/photo-sensor-pair/thumbnail.png +0 -0
- package/recipes/pipe-run/build.mts +11 -0
- package/recipes/pipe-run/facts.md +3 -0
- package/recipes/pipe-run/guide.en.md +18 -0
- package/recipes/pipe-run/guide.ko.md +18 -0
- package/recipes/pipe-run/models/default.json +1 -0
- package/recipes/pipe-run/recipe.json +100 -0
- package/recipes/pipe-run/spec.json +44 -0
- package/recipes/pipe-run/thumbnail.png +0 -0
- package/recipes/platform/build.mts +14 -0
- package/recipes/platform/facts.md +3 -0
- package/recipes/platform/guide.en.md +18 -0
- package/recipes/platform/guide.ko.md +18 -0
- package/recipes/platform/models/default.json +1 -0
- package/recipes/platform/recipe.json +281 -0
- package/recipes/platform/spec.json +81 -0
- package/recipes/platform/thumbnail.png +0 -0
- package/recipes/power-box/build.mts +10 -0
- package/recipes/power-box/facts.md +3 -0
- package/recipes/power-box/guide.en.md +17 -0
- package/recipes/power-box/guide.ko.md +17 -0
- package/recipes/power-box/models/default.json +1 -0
- package/recipes/power-box/recipe.json +64 -0
- package/recipes/power-box/spec.json +18 -0
- package/recipes/power-box/thumbnail.png +0 -0
- package/recipes/pressure-gauge/build.mts +10 -0
- package/recipes/pressure-gauge/facts.md +3 -0
- package/recipes/pressure-gauge/guide.en.md +17 -0
- package/recipes/pressure-gauge/guide.ko.md +17 -0
- package/recipes/pressure-gauge/models/default.json +1 -0
- package/recipes/pressure-gauge/recipe.json +65 -0
- package/recipes/pressure-gauge/spec.json +18 -0
- package/recipes/pressure-gauge/thumbnail.png +0 -0
- package/recipes/pull-cord/build.mts +11 -0
- package/recipes/pull-cord/facts.md +3 -0
- package/recipes/pull-cord/guide.en.md +17 -0
- package/recipes/pull-cord/guide.ko.md +17 -0
- package/recipes/pull-cord/models/default.json +1 -0
- package/recipes/pull-cord/recipe.json +75 -0
- package/recipes/pull-cord/spec.json +18 -0
- package/recipes/pull-cord/thumbnail.png +0 -0
- package/recipes/pump/build.mts +13 -0
- package/recipes/pump/facts.md +3 -0
- package/recipes/pump/guide.en.md +17 -0
- package/recipes/pump/guide.ko.md +17 -0
- package/recipes/pump/models/default.json +1 -0
- package/recipes/pump/recipe.json +109 -0
- package/recipes/pump/spec.json +18 -0
- package/recipes/pump/thumbnail.png +0 -0
- package/recipes/pusher/build.mts +11 -0
- package/recipes/pusher/facts.md +3 -0
- package/recipes/pusher/guide.en.md +18 -0
- package/recipes/pusher/guide.ko.md +18 -0
- package/recipes/pusher/models/default.json +1 -0
- package/recipes/pusher/recipe.json +105 -0
- package/recipes/pusher/spec.json +45 -0
- package/recipes/pusher/thumbnail.png +0 -0
- package/recipes/robot-plinth/build.mts +9 -0
- package/recipes/robot-plinth/facts.md +3 -0
- package/recipes/robot-plinth/guide.en.md +17 -0
- package/recipes/robot-plinth/guide.ko.md +17 -0
- package/recipes/robot-plinth/models/default.json +1 -0
- package/recipes/robot-plinth/recipe.json +115 -0
- package/recipes/robot-plinth/spec.json +45 -0
- package/recipes/robot-plinth/thumbnail.png +0 -0
- package/recipes/roll-up-shutter/build.mts +17 -0
- package/recipes/roll-up-shutter/facts.md +3 -0
- package/recipes/roll-up-shutter/guide.en.md +18 -0
- package/recipes/roll-up-shutter/guide.ko.md +18 -0
- package/recipes/roll-up-shutter/models/default.json +1 -0
- package/recipes/roll-up-shutter/recipe.json +200 -0
- package/recipes/roll-up-shutter/spec.json +69 -0
- package/recipes/roll-up-shutter/thumbnail.png +0 -0
- package/recipes/roller-row/build.mts +18 -0
- package/recipes/roller-row/facts.md +20 -0
- package/recipes/roller-row/guide.en.md +21 -0
- package/recipes/roller-row/guide.ko.md +21 -0
- package/recipes/roller-row/models/default.json +1 -0
- package/recipes/roller-row/recipe.json +179 -0
- package/recipes/roller-row/spec.json +80 -0
- package/recipes/roller-row/thumbnail.png +0 -0
- package/recipes/room-shell/build.mts +16 -0
- package/recipes/room-shell/facts.md +3 -0
- package/recipes/room-shell/guide.en.md +18 -0
- package/recipes/room-shell/guide.ko.md +18 -0
- package/recipes/room-shell/models/default.json +1 -0
- package/recipes/room-shell/recipe.json +324 -0
- package/recipes/room-shell/spec.json +81 -0
- package/recipes/room-shell/thumbnail.png +0 -0
- package/recipes/rotating-beacon/build.mts +17 -0
- package/recipes/rotating-beacon/facts.md +3 -0
- package/recipes/rotating-beacon/guide.en.md +17 -0
- package/recipes/rotating-beacon/guide.ko.md +17 -0
- package/recipes/rotating-beacon/models/default.json +1 -0
- package/recipes/rotating-beacon/recipe.json +160 -0
- package/recipes/rotating-beacon/spec.json +18 -0
- package/recipes/rotating-beacon/thumbnail.png +0 -0
- package/recipes/safety-fence/build.mts +15 -0
- package/recipes/safety-fence/facts.md +3 -0
- package/recipes/safety-fence/guide.en.md +19 -0
- package/recipes/safety-fence/guide.ko.md +19 -0
- package/recipes/safety-fence/models/default.json +1 -0
- package/recipes/safety-fence/recipe.json +161 -0
- package/recipes/safety-fence/spec.json +80 -0
- package/recipes/safety-fence/thumbnail.png +0 -0
- package/recipes/safety-mat/build.mts +11 -0
- package/recipes/safety-mat/facts.md +3 -0
- package/recipes/safety-mat/guide.en.md +18 -0
- package/recipes/safety-mat/guide.ko.md +18 -0
- package/recipes/safety-mat/models/default.json +1 -0
- package/recipes/safety-mat/recipe.json +93 -0
- package/recipes/safety-mat/spec.json +44 -0
- package/recipes/safety-mat/thumbnail.png +0 -0
- package/recipes/safety-sign-stand/build.mts +16 -0
- package/recipes/safety-sign-stand/facts.md +3 -0
- package/recipes/safety-sign-stand/guide.en.md +16 -0
- package/recipes/safety-sign-stand/guide.ko.md +16 -0
- package/recipes/safety-sign-stand/models/default.json +1 -0
- package/recipes/safety-sign-stand/recipe.json +144 -0
- package/recipes/safety-sign-stand/spec.json +11 -0
- package/recipes/safety-sign-stand/thumbnail.png +0 -0
- package/recipes/scanner-mount/build.mts +10 -0
- package/recipes/scanner-mount/facts.md +3 -0
- package/recipes/scanner-mount/guide.en.md +16 -0
- package/recipes/scanner-mount/guide.ko.md +16 -0
- package/recipes/scanner-mount/models/default.json +1 -0
- package/recipes/scanner-mount/recipe.json +147 -0
- package/recipes/scanner-mount/spec.json +18 -0
- package/recipes/scanner-mount/thumbnail.png +0 -0
- package/recipes/selector-switch/build.mts +14 -0
- package/recipes/selector-switch/facts.md +3 -0
- package/recipes/selector-switch/guide.en.md +17 -0
- package/recipes/selector-switch/guide.ko.md +17 -0
- package/recipes/selector-switch/models/default.json +1 -0
- package/recipes/selector-switch/recipe.json +104 -0
- package/recipes/selector-switch/spec.json +11 -0
- package/recipes/selector-switch/thumbnail.png +0 -0
- package/recipes/shelf-tiers/build.mts +12 -0
- package/recipes/shelf-tiers/facts.md +3 -0
- package/recipes/shelf-tiers/guide.en.md +18 -0
- package/recipes/shelf-tiers/guide.ko.md +18 -0
- package/recipes/shelf-tiers/models/default.json +1 -0
- package/recipes/shelf-tiers/models/tiers=2.json +1 -0
- package/recipes/shelf-tiers/models/tiers=3.json +1 -0
- package/recipes/shelf-tiers/models/tiers=4.json +1 -0
- package/recipes/shelf-tiers/models/tiers=5.json +1 -0
- package/recipes/shelf-tiers/recipe.json +196 -0
- package/recipes/shelf-tiers/spec.json +64 -0
- package/recipes/shelf-tiers/thumbnail.png +0 -0
- package/recipes/side-guides/build.mts +12 -0
- package/recipes/side-guides/facts.md +3 -0
- package/recipes/side-guides/guide.en.md +18 -0
- package/recipes/side-guides/guide.ko.md +18 -0
- package/recipes/side-guides/models/default.json +1 -0
- package/recipes/side-guides/recipe.json +147 -0
- package/recipes/side-guides/spec.json +54 -0
- package/recipes/side-guides/thumbnail.png +0 -0
- package/recipes/sign-board/build.mts +11 -0
- package/recipes/sign-board/facts.md +3 -0
- package/recipes/sign-board/guide.en.md +18 -0
- package/recipes/sign-board/guide.ko.md +18 -0
- package/recipes/sign-board/models/default.json +1 -0
- package/recipes/sign-board/recipe.json +93 -0
- package/recipes/sign-board/spec.json +44 -0
- package/recipes/sign-board/thumbnail.png +0 -0
- package/recipes/signal-tower-3/build.mts +19 -0
- package/recipes/signal-tower-3/facts.md +3 -0
- package/recipes/signal-tower-3/guide.en.md +17 -0
- package/recipes/signal-tower-3/guide.ko.md +17 -0
- package/recipes/signal-tower-3/models/default.json +1 -0
- package/recipes/signal-tower-3/recipe.json +216 -0
- package/recipes/signal-tower-3/spec.json +19 -0
- package/recipes/signal-tower-3/thumbnail.png +0 -0
- package/recipes/sliding-door/build.mts +47 -0
- package/recipes/sliding-door/facts.md +26 -0
- package/recipes/sliding-door/guide.en.md +22 -0
- package/recipes/sliding-door/guide.ko.md +22 -0
- package/recipes/sliding-door/models/default.json +1 -0
- package/recipes/sliding-door/models/leaves=1,lamp=false.json +1 -0
- package/recipes/sliding-door/models/leaves=1,lamp=true.json +1 -0
- package/recipes/sliding-door/models/leaves=2,lamp=false.json +1 -0
- package/recipes/sliding-door/models/leaves=2,lamp=true.json +1 -0
- package/recipes/sliding-door/recipe.json +506 -0
- package/recipes/sliding-door/spec.json +26 -0
- package/recipes/sliding-door/thumbnail.png +0 -0
- package/recipes/stairs/build.mts +11 -0
- package/recipes/stairs/facts.md +3 -0
- package/recipes/stairs/guide.en.md +19 -0
- package/recipes/stairs/guide.ko.md +19 -0
- package/recipes/stairs/models/default.json +1 -0
- package/recipes/stairs/recipe.json +110 -0
- package/recipes/stairs/spec.json +94 -0
- package/recipes/stairs/thumbnail.png +0 -0
- package/recipes/status-lamp/build.mts +17 -0
- package/recipes/status-lamp/facts.md +3 -0
- package/recipes/status-lamp/guide.en.md +17 -0
- package/recipes/status-lamp/guide.ko.md +17 -0
- package/recipes/status-lamp/models/default.json +1 -0
- package/recipes/status-lamp/recipe.json +172 -0
- package/recipes/status-lamp/spec.json +18 -0
- package/recipes/status-lamp/thumbnail.png +0 -0
- package/recipes/stopper/build.mts +12 -0
- package/recipes/stopper/facts.md +3 -0
- package/recipes/stopper/guide.en.md +18 -0
- package/recipes/stopper/guide.ko.md +18 -0
- package/recipes/stopper/models/default.json +1 -0
- package/recipes/stopper/recipe.json +108 -0
- package/recipes/stopper/spec.json +45 -0
- package/recipes/stopper/thumbnail.png +0 -0
- package/recipes/suction-pads/build.mts +11 -0
- package/recipes/suction-pads/facts.md +3 -0
- package/recipes/suction-pads/guide.en.md +18 -0
- package/recipes/suction-pads/guide.ko.md +18 -0
- package/recipes/suction-pads/models/default.json +1 -0
- package/recipes/suction-pads/recipe.json +86 -0
- package/recipes/suction-pads/spec.json +45 -0
- package/recipes/suction-pads/thumbnail.png +0 -0
- package/recipes/tank/build.mts +12 -0
- package/recipes/tank/facts.md +3 -0
- package/recipes/tank/guide.en.md +18 -0
- package/recipes/tank/guide.ko.md +18 -0
- package/recipes/tank/models/default.json +1 -0
- package/recipes/tank/recipe.json +105 -0
- package/recipes/tank/spec.json +44 -0
- package/recipes/tank/thumbnail.png +0 -0
- package/recipes/telescopic-arm/build.mts +15 -0
- package/recipes/telescopic-arm/facts.md +3 -0
- package/recipes/telescopic-arm/guide.en.md +17 -0
- package/recipes/telescopic-arm/guide.ko.md +17 -0
- package/recipes/telescopic-arm/models/default.json +1 -0
- package/recipes/telescopic-arm/recipe.json +108 -0
- package/recipes/telescopic-arm/spec.json +18 -0
- package/recipes/telescopic-arm/thumbnail.png +0 -0
- package/recipes/toe-board/build.mts +11 -0
- package/recipes/toe-board/facts.md +3 -0
- package/recipes/toe-board/guide.en.md +18 -0
- package/recipes/toe-board/guide.ko.md +18 -0
- package/recipes/toe-board/models/default.json +1 -0
- package/recipes/toe-board/recipe.json +92 -0
- package/recipes/toe-board/spec.json +45 -0
- package/recipes/toe-board/thumbnail.png +0 -0
- package/recipes/tote/build.mts +10 -0
- package/recipes/tote/facts.md +3 -0
- package/recipes/tote/guide.en.md +18 -0
- package/recipes/tote/guide.ko.md +18 -0
- package/recipes/tote/models/default.json +1 -0
- package/recipes/tote/recipe.json +82 -0
- package/recipes/tote/spec.json +45 -0
- package/recipes/tote/thumbnail.png +0 -0
- package/recipes/traffic-cones/build.mts +15 -0
- package/recipes/traffic-cones/facts.md +3 -0
- package/recipes/traffic-cones/guide.en.md +18 -0
- package/recipes/traffic-cones/guide.ko.md +18 -0
- package/recipes/traffic-cones/models/default.json +1 -0
- package/recipes/traffic-cones/recipe.json +130 -0
- package/recipes/traffic-cones/spec.json +22 -0
- package/recipes/traffic-cones/thumbnail.png +0 -0
- package/recipes/transfer-belts/build.mts +14 -0
- package/recipes/transfer-belts/facts.md +3 -0
- package/recipes/transfer-belts/guide.en.md +18 -0
- package/recipes/transfer-belts/guide.ko.md +18 -0
- package/recipes/transfer-belts/models/default.json +1 -0
- package/recipes/transfer-belts/recipe.json +174 -0
- package/recipes/transfer-belts/spec.json +45 -0
- package/recipes/transfer-belts/thumbnail.png +0 -0
- package/recipes/tray-stack/build.mts +13 -0
- package/recipes/tray-stack/facts.md +3 -0
- package/recipes/tray-stack/guide.en.md +18 -0
- package/recipes/tray-stack/guide.ko.md +18 -0
- package/recipes/tray-stack/models/default.json +1 -0
- package/recipes/tray-stack/models/trays=3.json +1 -0
- package/recipes/tray-stack/models/trays=4.json +1 -0
- package/recipes/tray-stack/models/trays=5.json +1 -0
- package/recipes/tray-stack/recipe.json +289 -0
- package/recipes/tray-stack/spec.json +20 -0
- package/recipes/tray-stack/thumbnail.png +0 -0
- package/recipes/trough/build.mts +11 -0
- package/recipes/trough/facts.md +3 -0
- package/recipes/trough/guide.en.md +17 -0
- package/recipes/trough/guide.ko.md +17 -0
- package/recipes/trough/models/default.json +1 -0
- package/recipes/trough/recipe.json +75 -0
- package/recipes/trough/spec.json +18 -0
- package/recipes/trough/thumbnail.png +0 -0
- package/recipes/turntable/build.mts +15 -0
- package/recipes/turntable/facts.md +3 -0
- package/recipes/turntable/guide.en.md +18 -0
- package/recipes/turntable/guide.ko.md +18 -0
- package/recipes/turntable/models/default.json +1 -0
- package/recipes/turntable/recipe.json +140 -0
- package/recipes/turntable/spec.json +43 -0
- package/recipes/turntable/thumbnail.png +0 -0
- package/recipes/valve-handle/build.mts +17 -0
- package/recipes/valve-handle/facts.md +3 -0
- package/recipes/valve-handle/guide.en.md +17 -0
- package/recipes/valve-handle/guide.ko.md +17 -0
- package/recipes/valve-handle/models/default.json +1 -0
- package/recipes/valve-handle/recipe.json +149 -0
- package/recipes/valve-handle/spec.json +18 -0
- package/recipes/valve-handle/thumbnail.png +0 -0
- package/recipes/waste-bin/build.mts +10 -0
- package/recipes/waste-bin/facts.md +3 -0
- package/recipes/waste-bin/guide.en.md +17 -0
- package/recipes/waste-bin/guide.ko.md +17 -0
- package/recipes/waste-bin/models/default.json +1 -0
- package/recipes/waste-bin/recipe.json +82 -0
- package/recipes/waste-bin/spec.json +45 -0
- package/recipes/waste-bin/thumbnail.png +0 -0
- package/recipes/work-light/build.mts +18 -0
- package/recipes/work-light/facts.md +3 -0
- package/recipes/work-light/guide.en.md +17 -0
- package/recipes/work-light/guide.ko.md +17 -0
- package/recipes/work-light/models/default.json +1 -0
- package/recipes/work-light/recipe.json +162 -0
- package/recipes/work-light/spec.json +18 -0
- package/recipes/work-light/thumbnail.png +0 -0
- package/recipes/workbench-top/build.mts +12 -0
- package/recipes/workbench-top/facts.md +3 -0
- package/recipes/workbench-top/guide.en.md +18 -0
- package/recipes/workbench-top/guide.ko.md +18 -0
- package/recipes/workbench-top/models/default.json +1 -0
- package/recipes/workbench-top/recipe.json +99 -0
- package/recipes/workbench-top/spec.json +45 -0
- package/recipes/workbench-top/thumbnail.png +0 -0
- package/scripts/build-templates.mts +64 -0
- package/scripts/templates.test.mts +89 -0
- package/server/index.ts +6 -10
- package/server/routers/figure-thumbnail-router.ts +24 -0
- package/server/service/figure-recipe/figure-recipe-resolver.ts +28 -32
- package/server/service/figure-recipe/figure-recipe-templates.test.ts +47 -0
- package/server/service/figure-recipe/figure-recipe-templates.ts +70 -0
- package/server/service/figure-recipe/figure-recipe-type.ts +31 -0
- package/server/service/figure-recipe/index.ts +2 -4
- package/server/service/index.ts +4 -4
- package/dist-server/service/figure-recipe/figure-recipe-store.d.ts +0 -41
- package/dist-server/service/figure-recipe/figure-recipe-store.js +0 -105
- package/dist-server/service/figure-recipe/figure-recipe-store.js.map +0 -1
- package/dist-server/service/figure-recipe/figure-recipe.d.ts +0 -19
- package/dist-server/service/figure-recipe/figure-recipe.js +0 -67
- package/dist-server/service/figure-recipe/figure-recipe.js.map +0 -1
- package/server/service/figure-recipe/figure-recipe-db.test.ts +0 -123
- package/server/service/figure-recipe/figure-recipe-store.ts +0 -101
- package/server/service/figure-recipe/figure-recipe.ts +0 -51
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Every recipe template, verified (ADR-0095 amended: model first, verified, then the recipe).
|
|
3
|
+
*
|
|
4
|
+
* node --test scripts/templates.test.mts
|
|
5
|
+
*
|
|
6
|
+
* For each template: its files are what its models build; each model passes the release gate, holds at half and twice
|
|
7
|
+
* its host, and moves through every control's range; the recipe put into a new host shows exactly each model; and the
|
|
8
|
+
* words and the picture are there.
|
|
9
|
+
*/
|
|
10
|
+
import { test } from 'node:test'
|
|
11
|
+
import assert from 'node:assert/strict'
|
|
12
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
13
|
+
import { join } from 'node:path'
|
|
14
|
+
|
|
15
|
+
import { applyV3Proposal, applyV3Recipe, checkV3Recipe, compileV3Asset, inspectV3Asset, serializeV3Asset } from '@hatiolab/figure-model'
|
|
16
|
+
import type { V3Asset } from '@hatiolab/figure-model'
|
|
17
|
+
|
|
18
|
+
import { buildTemplate, modelFileOf, RECIPES } from './build-templates.mts'
|
|
19
|
+
import { host } from '../recipes/_lib/model.mts'
|
|
20
|
+
|
|
21
|
+
const names = readdirSync(RECIPES).filter(n => !n.startsWith('_') && existsSync(join(RECIPES, n, 'build.mts'))).sort()
|
|
22
|
+
|
|
23
|
+
/** The bodies of a figure, rounded: what a person sees, to compare two figures by. */
|
|
24
|
+
const bodies = (asset: V3Asset, state: Record<string, number> = {}) =>
|
|
25
|
+
(compileV3Asset(asset).evaluate(state).geometry as unknown as Array<{ id: string; visible?: boolean; pose: { t: number[] }; shape: { dimensions: Record<string, number> } }>)
|
|
26
|
+
.map(g => ({ id: g.id, shown: g.visible !== false, t: g.pose.t.map(v => Math.round(v)), d: Object.fromEntries(Object.entries(g.shape.dimensions).map(([k, v]) => [k, Math.round(v)])) }))
|
|
27
|
+
.sort((a, b) => a.id.localeCompare(b.id))
|
|
28
|
+
/** A model's parts named as a recipe names them, so the two can be laid side by side. */
|
|
29
|
+
const renamed = (list: ReturnType<typeof bodies>, id: string, parts: string[]) =>
|
|
30
|
+
list
|
|
31
|
+
.map(b => ({ ...b, id: parts.reduce((text, p) => text.replace(new RegExp(`(^|[^\\w-])${p.replace(/[-]/g, '\\-')}(?=[^\\w-]|$)`, 'g'), `$1${id}-${p}`), b.id) }))
|
|
32
|
+
.sort((a, b) => a.id.localeCompare(b.id))
|
|
33
|
+
const partsOf = (asset: V3Asset) => asset.appearance.map(a => a.target).filter(t => t !== 'host')
|
|
34
|
+
/** How high the model's host stands off the floor: the new host stands the same. */
|
|
35
|
+
const hostGap = (asset: V3Asset) => {
|
|
36
|
+
const gap = asset.joints?.find(j => j.body1.part === 'host')?.origin?.gap
|
|
37
|
+
return typeof gap === 'number' ? gap : 0
|
|
38
|
+
}
|
|
39
|
+
const hostSize = (asset: V3Asset) => ({ x: asset.designInputs['host.size.x']!, y: asset.designInputs['host.size.y']!, z: asset.designInputs['host.size.z']! })
|
|
40
|
+
const withHost = (asset: V3Asset, k: number) => ({ ...asset, designInputs: { ...asset.designInputs, ...Object.fromEntries(['x', 'y', 'z'].map(a => [`host.size.${a}`, asset.designInputs[`host.size.${a}`]! * k])) } })
|
|
41
|
+
|
|
42
|
+
for (const name of names) {
|
|
43
|
+
test(`${name}: the files are what the models build`, async () => {
|
|
44
|
+
const { models, recipe } = await buildTemplate(name)
|
|
45
|
+
assert.deepEqual(JSON.parse(readFileSync(join(RECIPES, name, 'recipe.json'), 'utf-8')), JSON.parse(JSON.stringify(recipe)), 'recipe.json is up to date -- run scripts/build-templates.mts')
|
|
46
|
+
for (const { values, asset } of models) assert.equal(readFileSync(join(RECIPES, name, 'models', modelFileOf(values)), 'utf-8'), serializeV3Asset(asset), `models/${modelFileOf(values)} is up to date`)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test(`${name}: each model passes the gate, holds at half and twice its host, and moves through its controls`, async () => {
|
|
50
|
+
const { models } = await buildTemplate(name)
|
|
51
|
+
for (const { values, asset } of models) {
|
|
52
|
+
const at = JSON.stringify(values)
|
|
53
|
+
const declared = applyV3Proposal(asset, [{ kind: 'fit-room', placement: 'floor' } as never])
|
|
54
|
+
assert.deepEqual(inspectV3Asset(declared).violations, [], `${at}: the release gate`)
|
|
55
|
+
for (const k of [0.5, 2]) assert.doesNotThrow(() => compileV3Asset(withHost(asset, k)).evaluate(), `${at}: host × ${k}`)
|
|
56
|
+
for (const input of asset.document.model.inputs.filter(i => i.role === 'state'))
|
|
57
|
+
for (const v of [input.min!, input.max!]) assert.doesNotThrow(() => compileV3Asset(asset).evaluate({ [input.id]: v }), `${at}: ${input.id} at ${v}`)
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test(`${name}: the recipe put into a new host shows each model, shut and at every control's end`, async () => {
|
|
62
|
+
const { models, recipe } = await buildTemplate(name)
|
|
63
|
+
assert.doesNotThrow(() => checkV3Recipe(structuredClone(recipe)))
|
|
64
|
+
for (const { values, asset } of models) {
|
|
65
|
+
const put = applyV3Recipe(host(hostSize(asset), 'Template check', hostGap(asset)), recipe, { target: 'host', values })
|
|
66
|
+
assert.deepEqual(bodies(put.asset), renamed(bodies(asset), put.id, partsOf(asset)), `${JSON.stringify(values)}: as the model`)
|
|
67
|
+
for (const input of asset.document.model.inputs.filter(i => i.role === 'state'))
|
|
68
|
+
assert.deepEqual(bodies(put.asset, { [`${put.id}-${input.id}`]: input.max! }), renamed(bodies(asset, { [input.id]: input.max! }), put.id, partsOf(asset)), `${JSON.stringify(values)}: ${input.id} at its end`)
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test(`${name}: the words and the picture are there`, () => {
|
|
73
|
+
for (const file of ['guide.ko.md', 'guide.en.md', 'facts.md', 'thumbnail.png']) assert.ok(existsSync(join(RECIPES, name, file)), `${file}`)
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* The palette is the figure's, shared by every template put into it: one colour name, one colour, across all of them. */
|
|
78
|
+
test('no two templates give one colour name two colours', async () => {
|
|
79
|
+
const seen = new Map<string, { value: string; by: string }>()
|
|
80
|
+
for (const name of names) {
|
|
81
|
+
const { recipe } = await buildTemplate(name)
|
|
82
|
+
for (const step of recipe.steps.filter(s => s.kind === 'set-material'))
|
|
83
|
+
for (const [token, value] of Object.entries((step.palette ?? {}) as Record<string, string>)) {
|
|
84
|
+
const before = seen.get(token)
|
|
85
|
+
assert.ok(!before || before.value === value, `${token}: ${before?.by} says ${before?.value}, ${name} says ${value}`)
|
|
86
|
+
seen.set(token, { value, by: name })
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
})
|
package/server/index.ts
CHANGED
|
@@ -11,17 +11,13 @@ import { registerFigureToolCategories } from './service/figure/figure-tools.js'
|
|
|
11
11
|
registerFigureToolCategories()
|
|
12
12
|
|
|
13
13
|
/*
|
|
14
|
-
* The
|
|
15
|
-
*
|
|
14
|
+
* The recipe templates are read once at start (ADR-0095, amended): what was served, and any file whose form is wrong,
|
|
15
|
+
* said here rather than found by a person as a missing template.
|
|
16
16
|
*/
|
|
17
|
-
import {
|
|
18
|
-
process.on('bootstrap-module-start' as any,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
console.log(`[figure-service:bootstrap] recipes added ${added.length}, already there ${skipped.length}${refused.length ? `, refused: ${refused.join(' | ')}` : ''}`)
|
|
22
|
-
} catch (error) {
|
|
23
|
-
console.error('[figure-service:bootstrap] recipes were not seeded:', error)
|
|
24
|
-
}
|
|
17
|
+
import { recipeTemplates } from './service/figure-recipe/figure-recipe-templates.js'
|
|
18
|
+
process.on('bootstrap-module-start' as any, () => {
|
|
19
|
+
const { templates, refused } = recipeTemplates()
|
|
20
|
+
console.log(`[figure-service:bootstrap] recipe templates ${templates.length}${refused.length ? `, left out: ${refused.join(' | ')}` : ''}`)
|
|
25
21
|
})
|
|
26
22
|
|
|
27
23
|
export * from './service/index.js'
|
|
@@ -4,7 +4,10 @@ import { In } from 'typeorm'
|
|
|
4
4
|
import { checkPermission } from '@things-factory/auth-base'
|
|
5
5
|
import { getRepository } from '@things-factory/shell'
|
|
6
6
|
|
|
7
|
+
import { readFileSync } from 'fs'
|
|
8
|
+
|
|
7
9
|
import { Figure } from '../service/figure/figure.js'
|
|
10
|
+
import { recipeTemplates } from '../service/figure-recipe/figure-recipe-templates.js'
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
13
|
* 썸네일을 **그림으로** 내보내는 길.
|
|
@@ -108,3 +111,24 @@ figureThumbnailRouter.get('/figure-thumbnail/:id', async (context, next) => {
|
|
|
108
111
|
|
|
109
112
|
context.body = decoded.buffer
|
|
110
113
|
})
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* A recipe template's thumbnail (ADR-0095 amended): the file drawn from its model, served behind the same door as the
|
|
117
|
+
* templates themselves. The file changes only with a release, so a day's private cache is enough.
|
|
118
|
+
*/
|
|
119
|
+
figureThumbnailRouter.get('/figure-recipe-thumbnail/:name', async context => {
|
|
120
|
+
const { domain, user, unsafeIP, prohibitedPrivileges } = context.state
|
|
121
|
+
const allowed = await checkPermission({ category: 'figure', privilege: 'query', owner: true }, user, domain, unsafeIP, prohibitedPrivileges)
|
|
122
|
+
if (!allowed) {
|
|
123
|
+
context.status = 403
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
const template = recipeTemplates().templates.find(t => t.recipe.name === context.params.name)
|
|
127
|
+
if (!template?.thumbnail) {
|
|
128
|
+
context.status = 404
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
context.type = 'image/png'
|
|
132
|
+
context.set('Cache-Control', 'private, max-age=86400')
|
|
133
|
+
context.body = readFileSync(template.thumbnail)
|
|
134
|
+
})
|
|
@@ -1,42 +1,38 @@
|
|
|
1
|
-
import { Arg,
|
|
1
|
+
import { Arg, Directive, Query, Resolver, UseMiddleware } from 'type-graphql'
|
|
2
2
|
|
|
3
3
|
import { SpeakRefusals } from '@things-factory/shell'
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { FigureRecipeTemplate } from './figure-recipe-type.js'
|
|
6
|
+
import { inLanguage, recipeTemplates, type RecipeTemplate } from './figure-recipe-templates.js'
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const view = (t: RecipeTemplate, lang: string | undefined, withGuide: boolean): FigureRecipeTemplate => ({
|
|
9
|
+
name: t.recipe.name,
|
|
10
|
+
category: t.category,
|
|
11
|
+
tags: t.tags,
|
|
12
|
+
target: t.recipe.target,
|
|
13
|
+
title: inLanguage(t.recipe.title, lang),
|
|
14
|
+
description: inLanguage(t.recipe.description, lang),
|
|
15
|
+
source: JSON.stringify(t.recipe),
|
|
16
|
+
...(withGuide ? { guide: inLanguage(t.guides, lang) } : {}),
|
|
17
|
+
hasThumbnail: !!t.thumbnail
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
@Resolver(FigureRecipeTemplate)
|
|
9
21
|
@UseMiddleware(SpeakRefusals)
|
|
10
|
-
export class
|
|
22
|
+
export class FigureRecipeTemplateQuery {
|
|
11
23
|
@Directive('@privilege(category: "figure", privilege: "query", domainOwnerGranted: true)')
|
|
12
|
-
@Query(returns => [
|
|
13
|
-
|
|
14
|
-
return
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
@Directive('@privilege(category: "figure", privilege: "mutation", domainOwnerGranted: true)')
|
|
18
|
-
@Mutation(returns => FigureRecipe, { description: 'Saves a recipe into this domain by its name, after putting it into an empty room with its defaults (ADR-0095).' })
|
|
19
|
-
async saveFigureRecipe(
|
|
20
|
-
@Arg('source', { description: 'The recipe JSON (figure-model V3Recipe).' }) source: string,
|
|
21
|
-
@Arg('state', { nullable: true, description: "'draft' or 'released' (the default)." }) state: string,
|
|
22
|
-
@Ctx() context: ResolverContext
|
|
23
|
-
): Promise<FigureRecipe> {
|
|
24
|
-
return await saveRecipe(context.state.domain, source, state === 'draft' ? 'draft' : 'released')
|
|
24
|
+
@Query(returns => [FigureRecipeTemplate], { description: 'The recipe templates the product ships, in the language asked. Guides are left out of the list.' })
|
|
25
|
+
figureRecipeTemplates(@Arg('lang', { nullable: true, description: 'The language of the titles (ko, en, …).' }) lang: string): FigureRecipeTemplate[] {
|
|
26
|
+
return recipeTemplates().templates.map(t => view(t, lang, false))
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
@Directive('@privilege(category: "figure", privilege: "
|
|
28
|
-
@
|
|
29
|
-
|
|
30
|
-
@Arg('name', { description: 'The
|
|
31
|
-
@Arg('
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
@Directive('@privilege(category: "figure", privilege: "mutation", domainOwnerGranted: true)')
|
|
38
|
-
@Mutation(returns => Boolean, { description: "Deletes a recipe of this domain's own. Figures it was put into keep their parts." })
|
|
39
|
-
async deleteFigureRecipe(@Arg('name', { description: 'The recipe name.' }) name: string, @Ctx() context: ResolverContext): Promise<boolean> {
|
|
40
|
-
return await deleteRecipe(context.state.domain, name)
|
|
29
|
+
@Directive('@privilege(category: "figure", privilege: "query", domainOwnerGranted: true)')
|
|
30
|
+
@Query(returns => FigureRecipeTemplate, { nullable: true, description: 'One recipe template with its guide, in the language asked.' })
|
|
31
|
+
figureRecipeTemplate(
|
|
32
|
+
@Arg('name', { description: 'The template name.' }) name: string,
|
|
33
|
+
@Arg('lang', { nullable: true, description: 'The language of the words (ko, en, …).' }) lang: string
|
|
34
|
+
): FigureRecipeTemplate | null {
|
|
35
|
+
const t = recipeTemplates().templates.find(one => one.recipe.name === name)
|
|
36
|
+
return t ? view(t, lang, true) : null
|
|
41
37
|
}
|
|
42
38
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 조각 템플릿 — 제품이 싣는 정적 파일(ADR-0095 고침). 기동 때는 형식만 읽으므로, 빈 방에 실제로 넣어 보는 것은 여기서 한다.
|
|
3
|
+
* 템플릿이 100개가 되어도 이 시험 하나가 전부를 넣어 본다.
|
|
4
|
+
*/
|
|
5
|
+
import { mkdtempSync, mkdirSync, writeFileSync } from 'fs'
|
|
6
|
+
import { tmpdir } from 'os'
|
|
7
|
+
import { join } from 'path'
|
|
8
|
+
|
|
9
|
+
import { checkV3Recipe } from '@hatiolab/figure-model'
|
|
10
|
+
|
|
11
|
+
import { inLanguage, recipeTemplates, RECIPE_DIR } from './figure-recipe-templates'
|
|
12
|
+
|
|
13
|
+
describe('조각 템플릿 — 정적 파일', () => {
|
|
14
|
+
it('★ 실린 템플릿은 전부 형식이 맞고, 폴더 이름이 곧 이름이며, 기본값으로 빈 방에 들어가 발행 검사를 지난다', () => {
|
|
15
|
+
const { templates, refused } = recipeTemplates(RECIPE_DIR)
|
|
16
|
+
expect(refused).toEqual([])
|
|
17
|
+
const folders = require('fs').readdirSync(RECIPE_DIR, { withFileTypes: true }).filter((e: any) => e.isDirectory() && !e.name.startsWith('_')).map((e: any) => e.name).sort()
|
|
18
|
+
expect(templates.map(t => t.recipe.name)).toEqual(folders)
|
|
19
|
+
expect(templates.length).toBeGreaterThanOrEqual(22)
|
|
20
|
+
for (const t of templates) {
|
|
21
|
+
expect(() => checkV3Recipe(structuredClone(t.recipe))).not.toThrow()
|
|
22
|
+
expect(t.category).not.toBe('other')
|
|
23
|
+
for (const p of t.recipe.params) expect(Object.keys(p.hint ?? {}).sort()).toEqual(['en', 'ko'])
|
|
24
|
+
expect(Object.keys(t.guides).sort()).toEqual(['en', 'ko'])
|
|
25
|
+
expect(t.thumbnail).not.toBeNull()
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('★ 형식이 틀린 파일은 빠지고 이름과 까닭이 남는다 — 반쯤 내주지 않는다', () => {
|
|
30
|
+
const dir = mkdtempSync(join(tmpdir(), 'recipes-'))
|
|
31
|
+
mkdirSync(join(dir, 'broken'))
|
|
32
|
+
writeFileSync(join(dir, 'broken', 'recipe.json'), JSON.stringify({ name: 'broken', title: { en: 'x' }, target: 'part', params: [], steps: [] }))
|
|
33
|
+
mkdirSync(join(dir, 'misnamed'))
|
|
34
|
+
writeFileSync(join(dir, 'misnamed', 'recipe.json'), JSON.stringify({ name: 'other', title: { en: 'x' }, target: 'part', params: [], steps: [{ kind: 'add-part' }] }))
|
|
35
|
+
const { templates, refused } = recipeTemplates(dir)
|
|
36
|
+
expect(templates).toEqual([])
|
|
37
|
+
expect(refused).toEqual([expect.stringMatching(/^broken: .*one step or more/), expect.stringMatching(/^misnamed: .*named other/)])
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('★ 말은 물은 언어로 — 없으면 그 언어의 기본, 그다음 영어', () => {
|
|
41
|
+
const words = { ko: '미닫이 문', en: 'sliding door' }
|
|
42
|
+
expect(inLanguage(words, 'ko')).toBe('미닫이 문')
|
|
43
|
+
expect(inLanguage(words, 'ko-KR')).toBe('미닫이 문')
|
|
44
|
+
expect(inLanguage(words, 'ja')).toBe('sliding door')
|
|
45
|
+
expect(inLanguage(undefined, 'ko')).toBe('')
|
|
46
|
+
})
|
|
47
|
+
})
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'fs'
|
|
2
|
+
import { join, resolve } from 'path'
|
|
3
|
+
|
|
4
|
+
import { parseV3Recipe } from '@hatiolab/figure-model'
|
|
5
|
+
import type { V3Recipe } from '@hatiolab/figure-model'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The recipe templates the product ships (ADR-0095, amended 2026-09-26): static files, served, not managed.
|
|
9
|
+
*
|
|
10
|
+
* recipes/<name>/recipe.json the command list, its parameters, category, tags, titles
|
|
11
|
+
* recipes/<name>/guide.<lang>.md how to use it, per language (English stands in for a missing one)
|
|
12
|
+
* recipes/<name>/thumbnail.png drawn by a script from the recipe put into the test room
|
|
13
|
+
*
|
|
14
|
+
* There is no table and no screen that changes them: a template is added or changed as a file, with the code. The
|
|
15
|
+
* start reads their form only; putting each into the test room is the tests' work (`figure-recipe-templates.test.ts`).
|
|
16
|
+
*/
|
|
17
|
+
export interface RecipeTemplate {
|
|
18
|
+
recipe: V3Recipe
|
|
19
|
+
category: string
|
|
20
|
+
tags: string[]
|
|
21
|
+
/** Guides by language code, as written. */
|
|
22
|
+
guides: Record<string, string>
|
|
23
|
+
thumbnail: string | null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The folder the templates are in: `recipes/` at the package root, beside `server/` and `dist-server/`. */
|
|
27
|
+
export const RECIPE_DIR = resolve(__dirname, '..', '..', '..', 'recipes')
|
|
28
|
+
|
|
29
|
+
let loaded: { templates: RecipeTemplate[]; refused: string[] } | null = null
|
|
30
|
+
|
|
31
|
+
/** Every template, read once. One whose form is wrong is left out and named, not half served. */
|
|
32
|
+
export function recipeTemplates(dir: string = RECIPE_DIR): { templates: RecipeTemplate[]; refused: string[] } {
|
|
33
|
+
if (loaded && dir === RECIPE_DIR) return loaded
|
|
34
|
+
const templates: RecipeTemplate[] = []
|
|
35
|
+
const refused: string[] = []
|
|
36
|
+
/* A folder starting with _ holds what the models are built with, not a template. */
|
|
37
|
+
const folders = existsSync(dir) ? readdirSync(dir, { withFileTypes: true }).filter(e => e.isDirectory() && !e.name.startsWith('_')).map(e => e.name).sort() : []
|
|
38
|
+
for (const folder of folders) {
|
|
39
|
+
const at = join(dir, folder)
|
|
40
|
+
try {
|
|
41
|
+
const raw = JSON.parse(readFileSync(join(at, 'recipe.json'), 'utf-8'))
|
|
42
|
+
const recipe = parseV3Recipe(raw)
|
|
43
|
+
if (recipe.name !== folder) throw new Error(`the recipe is named ${recipe.name}, its folder ${folder}`)
|
|
44
|
+
const guides: Record<string, string> = {}
|
|
45
|
+
for (const file of readdirSync(at)) {
|
|
46
|
+
const lang = /^guide\.([a-zA-Z-]+)\.md$/.exec(file)?.[1]
|
|
47
|
+
if (lang) guides[lang] = readFileSync(join(at, file), 'utf-8')
|
|
48
|
+
}
|
|
49
|
+
templates.push({
|
|
50
|
+
recipe,
|
|
51
|
+
category: typeof raw.category === 'string' ? raw.category : 'other',
|
|
52
|
+
tags: Array.isArray(raw.tags) ? raw.tags.filter((t: unknown) => typeof t === 'string') : [],
|
|
53
|
+
guides,
|
|
54
|
+
thumbnail: existsSync(join(at, 'thumbnail.png')) ? join(at, 'thumbnail.png') : null
|
|
55
|
+
})
|
|
56
|
+
} catch (error) {
|
|
57
|
+
refused.push(`${folder}: ${error instanceof Error ? error.message : String(error)}`)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const result = { templates, refused }
|
|
61
|
+
if (dir === RECIPE_DIR) loaded = result
|
|
62
|
+
return result
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** A word in the language asked: that language, its base (`ko` for `ko-KR`), English, then the first there is. */
|
|
66
|
+
export function inLanguage(words: Record<string, string> | undefined, lang: string | undefined): string {
|
|
67
|
+
if (!words) return ''
|
|
68
|
+
const asked = lang || 'en'
|
|
69
|
+
return words[asked] ?? words[asked.split('-')[0]!] ?? words.en ?? Object.values(words)[0] ?? ''
|
|
70
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Field, ObjectType } from 'type-graphql'
|
|
2
|
+
|
|
3
|
+
@ObjectType({ description: 'A recipe template the product ships: parts, their fastenings and motion, put onto a part in one step (ADR-0095).' })
|
|
4
|
+
export class FigureRecipeTemplate {
|
|
5
|
+
@Field({ description: 'The template name: lower case, digits and hyphens.' })
|
|
6
|
+
name: string
|
|
7
|
+
|
|
8
|
+
@Field({ description: 'The category it is listed under (door, conveyor, …).' })
|
|
9
|
+
category: string
|
|
10
|
+
|
|
11
|
+
@Field(type => [String], { description: 'Words it is found by.' })
|
|
12
|
+
tags: string[]
|
|
13
|
+
|
|
14
|
+
@Field({ description: "'part' when it is put onto a chosen part, 'figure' when onto the whole figure." })
|
|
15
|
+
target: string
|
|
16
|
+
|
|
17
|
+
@Field({ description: 'Its title in the language asked (English stands in).' })
|
|
18
|
+
title: string
|
|
19
|
+
|
|
20
|
+
@Field({ description: 'Its one-line description in the language asked (English stands in).' })
|
|
21
|
+
description: string
|
|
22
|
+
|
|
23
|
+
@Field({ description: 'The recipe JSON (figure-model V3Recipe), as the modeller applies it.' })
|
|
24
|
+
source: string
|
|
25
|
+
|
|
26
|
+
@Field({ nullable: true, description: 'How to use it, as Markdown, in the language asked (English stands in). Only when one template is asked for.' })
|
|
27
|
+
guide?: string
|
|
28
|
+
|
|
29
|
+
@Field({ description: 'Whether it has a thumbnail image.' })
|
|
30
|
+
hasThumbnail: boolean
|
|
31
|
+
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { FigureRecipeResolver } from './figure-recipe-resolver.js'
|
|
1
|
+
import { FigureRecipeTemplateQuery } from './figure-recipe-resolver.js'
|
|
3
2
|
|
|
4
|
-
export const
|
|
5
|
-
export const resolvers = [FigureRecipeResolver]
|
|
3
|
+
export const resolvers = [FigureRecipeTemplateQuery]
|
package/server/service/index.ts
CHANGED
|
@@ -3,8 +3,8 @@ export * from './figure/figure.js'
|
|
|
3
3
|
export * from './figure/figure-type.js'
|
|
4
4
|
export * from './figure/figure-format.js'
|
|
5
5
|
export * from './figure/figure-history.js'
|
|
6
|
-
export * from './figure-recipe/figure-recipe.js'
|
|
7
|
-
export {
|
|
6
|
+
export * from './figure-recipe/figure-recipe-type.js'
|
|
7
|
+
export { recipeTemplates, inLanguage, RECIPE_DIR } from './figure-recipe/figure-recipe-templates.js'
|
|
8
8
|
|
|
9
9
|
// Entities, resolvers
|
|
10
10
|
import {
|
|
@@ -12,9 +12,9 @@ import {
|
|
|
12
12
|
resolvers as FigureResolvers,
|
|
13
13
|
subscribers as FigureSubscribers
|
|
14
14
|
} from './figure/index.js'
|
|
15
|
-
import {
|
|
15
|
+
import { resolvers as RecipeResolvers } from './figure-recipe/index.js'
|
|
16
16
|
|
|
17
|
-
export const entities = [...FigureEntities
|
|
17
|
+
export const entities = [...FigureEntities]
|
|
18
18
|
|
|
19
19
|
/* 판본 스냅샷을 뜨는 구독자. 셸이 이 이름으로 모아 TypeORM 에 등록한다(board-service 와 같다). */
|
|
20
20
|
export const subscribers = [...FigureSubscribers]
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { Domain } from '@things-factory/shell';
|
|
2
|
-
import { FigureRecipe } from './figure-recipe.js';
|
|
3
|
-
/**
|
|
4
|
-
* The recipes a domain sees (ADR-0095 condition 2): its own and its parent's, as figures are read. A recipe of the
|
|
5
|
-
* domain's own stands in for the parent's of the same name.
|
|
6
|
-
*/
|
|
7
|
-
export declare function recipesFor(domain: {
|
|
8
|
-
id: string;
|
|
9
|
-
parentId?: string | null;
|
|
10
|
-
}, state?: 'released'): Promise<FigureRecipe[]>;
|
|
11
|
-
/**
|
|
12
|
-
* A recipe saved into a domain, by its name: checked first by putting it into the test room with its defaults
|
|
13
|
-
* (figure-model `checkV3Recipe`). Refused, it is not saved and the kernel's sentence goes back as it is.
|
|
14
|
-
*/
|
|
15
|
-
export declare function saveRecipe(domain: Domain, source: unknown, state?: 'draft' | 'released'): Promise<FigureRecipe>;
|
|
16
|
-
/** Released or back to draft: only released recipes are offered in the modeller. */
|
|
17
|
-
export declare function setRecipeState(domain: {
|
|
18
|
-
id: string;
|
|
19
|
-
}, name: string, state: 'draft' | 'released'): Promise<FigureRecipe>;
|
|
20
|
-
/**
|
|
21
|
-
* A recipe of the domain's own taken away. Figures it was put into keep their parts -- nothing ties them to it
|
|
22
|
-
* (ADR-0095). Where the parent has one by that name, the parent's is seen again.
|
|
23
|
-
*/
|
|
24
|
-
export declare function deleteRecipe(domain: {
|
|
25
|
-
id: string;
|
|
26
|
-
}, name: string): Promise<boolean>;
|
|
27
|
-
/** The recipes figure-model ships (`recipes/*.json`), as they are. */
|
|
28
|
-
export declare function builtInRecipes(): Array<{
|
|
29
|
-
name: string;
|
|
30
|
-
source: string;
|
|
31
|
-
}>;
|
|
32
|
-
/**
|
|
33
|
-
* The built-in recipes put into the system domain (ADR-0095 condition 2): once, and again gives the same result -- a
|
|
34
|
-
* recipe already there by name is skipped, not overwritten, so a system recipe someone changed stays as they left it.
|
|
35
|
-
* Each is checked as a saved one is; one that fails is left out and said, not half put in.
|
|
36
|
-
*/
|
|
37
|
-
export declare function seedBuiltInRecipes(): Promise<{
|
|
38
|
-
added: string[];
|
|
39
|
-
skipped: string[];
|
|
40
|
-
refused: string[];
|
|
41
|
-
}>;
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.recipesFor = recipesFor;
|
|
4
|
-
exports.saveRecipe = saveRecipe;
|
|
5
|
-
exports.setRecipeState = setRecipeState;
|
|
6
|
-
exports.deleteRecipe = deleteRecipe;
|
|
7
|
-
exports.builtInRecipes = builtInRecipes;
|
|
8
|
-
exports.seedBuiltInRecipes = seedBuiltInRecipes;
|
|
9
|
-
const fs_1 = require("fs");
|
|
10
|
-
const path_1 = require("path");
|
|
11
|
-
const typeorm_1 = require("typeorm");
|
|
12
|
-
const figure_model_1 = require("@hatiolab/figure-model");
|
|
13
|
-
const shell_1 = require("@things-factory/shell");
|
|
14
|
-
const figure_recipe_js_1 = require("./figure-recipe.js");
|
|
15
|
-
/**
|
|
16
|
-
* The recipes a domain sees (ADR-0095 condition 2): its own and its parent's, as figures are read. A recipe of the
|
|
17
|
-
* domain's own stands in for the parent's of the same name.
|
|
18
|
-
*/
|
|
19
|
-
async function recipesFor(domain, state) {
|
|
20
|
-
const ids = [domain.id, domain.parentId].filter(Boolean);
|
|
21
|
-
const rows = await (0, shell_1.getRepository)(figure_recipe_js_1.FigureRecipe).find({
|
|
22
|
-
where: { domain: { id: (0, typeorm_1.In)(ids) }, ...(state ? { state } : {}) },
|
|
23
|
-
relations: ['domain'],
|
|
24
|
-
order: { name: 'ASC' }
|
|
25
|
-
});
|
|
26
|
-
const byName = new Map();
|
|
27
|
-
for (const row of rows) {
|
|
28
|
-
const mine = row.domainId === domain.id || row.domain?.id === domain.id;
|
|
29
|
-
if (mine || !byName.has(row.name))
|
|
30
|
-
byName.set(row.name, row);
|
|
31
|
-
}
|
|
32
|
-
return [...byName.values()];
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* A recipe saved into a domain, by its name: checked first by putting it into the test room with its defaults
|
|
36
|
-
* (figure-model `checkV3Recipe`). Refused, it is not saved and the kernel's sentence goes back as it is.
|
|
37
|
-
*/
|
|
38
|
-
async function saveRecipe(domain, source, state = 'released') {
|
|
39
|
-
const recipe = (0, figure_model_1.checkV3Recipe)(typeof source === 'string' ? JSON.parse(source) : source);
|
|
40
|
-
const repository = (0, shell_1.getRepository)(figure_recipe_js_1.FigureRecipe);
|
|
41
|
-
const existing = await repository.findOne({ where: { domain: { id: domain.id }, name: recipe.name } });
|
|
42
|
-
return await repository.save({ ...(existing ?? {}), domain, name: recipe.name, source: JSON.stringify(recipe), state });
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* A recipe of the domain's own, by name. A parent's recipe is not the domain's to change: it is copied in under the same
|
|
46
|
-
* name (`saveRecipe`), and the copy stands in for it.
|
|
47
|
-
*/
|
|
48
|
-
async function ownRecipe(domain, name) {
|
|
49
|
-
const row = await (0, shell_1.getRepository)(figure_recipe_js_1.FigureRecipe).findOne({ where: { domain: { id: domain.id }, name } });
|
|
50
|
-
if (!row)
|
|
51
|
-
throw new Error(`${name} is not a recipe of this domain; a parent's recipe is copied here to be changed`);
|
|
52
|
-
return row;
|
|
53
|
-
}
|
|
54
|
-
/** Released or back to draft: only released recipes are offered in the modeller. */
|
|
55
|
-
async function setRecipeState(domain, name, state) {
|
|
56
|
-
const row = await ownRecipe(domain, name);
|
|
57
|
-
return await (0, shell_1.getRepository)(figure_recipe_js_1.FigureRecipe).save({ ...row, state });
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* A recipe of the domain's own taken away. Figures it was put into keep their parts -- nothing ties them to it
|
|
61
|
-
* (ADR-0095). Where the parent has one by that name, the parent's is seen again.
|
|
62
|
-
*/
|
|
63
|
-
async function deleteRecipe(domain, name) {
|
|
64
|
-
const row = await ownRecipe(domain, name);
|
|
65
|
-
await (0, shell_1.getRepository)(figure_recipe_js_1.FigureRecipe).softRemove(row);
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
/** The recipes figure-model ships (`recipes/*.json`), as they are. */
|
|
69
|
-
function builtInRecipes() {
|
|
70
|
-
const dir = (0, path_1.join)((0, path_1.dirname)(require.resolve('@hatiolab/figure-model/package.json')), 'recipes');
|
|
71
|
-
return (0, fs_1.readdirSync)(dir)
|
|
72
|
-
.filter(file => file.endsWith('.json'))
|
|
73
|
-
.sort()
|
|
74
|
-
.map(file => {
|
|
75
|
-
const source = (0, fs_1.readFileSync)((0, path_1.join)(dir, file), 'utf-8');
|
|
76
|
-
return { name: JSON.parse(source).name, source };
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* The built-in recipes put into the system domain (ADR-0095 condition 2): once, and again gives the same result -- a
|
|
81
|
-
* recipe already there by name is skipped, not overwritten, so a system recipe someone changed stays as they left it.
|
|
82
|
-
* Each is checked as a saved one is; one that fails is left out and said, not half put in.
|
|
83
|
-
*/
|
|
84
|
-
async function seedBuiltInRecipes() {
|
|
85
|
-
const system = await (0, shell_1.getRepository)(shell_1.Domain).findOne({ where: { systemFlag: true } });
|
|
86
|
-
const result = { added: [], skipped: [], refused: [] };
|
|
87
|
-
if (!system)
|
|
88
|
-
return result;
|
|
89
|
-
const repository = (0, shell_1.getRepository)(figure_recipe_js_1.FigureRecipe);
|
|
90
|
-
for (const { name, source } of builtInRecipes()) {
|
|
91
|
-
if (await repository.count({ where: { domain: { id: system.id }, name } })) {
|
|
92
|
-
result.skipped.push(name);
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
try {
|
|
96
|
-
await saveRecipe(system, source);
|
|
97
|
-
result.added.push(name);
|
|
98
|
-
}
|
|
99
|
-
catch (error) {
|
|
100
|
-
result.refused.push(`${name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return result;
|
|
104
|
-
}
|
|
105
|
-
//# sourceMappingURL=figure-recipe-store.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"figure-recipe-store.js","sourceRoot":"","sources":["../../../server/service/figure-recipe/figure-recipe-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { readdirSync, readFileSync } from 'fs'\nimport { dirname, join } from 'path'\nimport { In } from 'typeorm'\n\nimport { checkV3Recipe } from '@hatiolab/figure-model'\nimport { Domain, getRepository } from '@things-factory/shell'\n\nimport { FigureRecipe } from './figure-recipe.js'\n\n/**\n * The recipes a domain sees (ADR-0095 condition 2): its own and its parent's, as figures are read. A recipe of the\n * domain's own stands in for the parent's of the same name.\n */\nexport async function recipesFor(domain: { id: string; parentId?: string | null }, state?: 'released'): Promise<FigureRecipe[]> {\n const ids = [domain.id, domain.parentId].filter(Boolean) as string[]\n const rows = await getRepository(FigureRecipe).find({\n where: { domain: { id: In(ids) }, ...(state ? { state } : {}) },\n relations: ['domain'],\n order: { name: 'ASC' }\n })\n const byName = new Map<string, FigureRecipe>()\n for (const row of rows) {\n const mine = row.domainId === domain.id || (row.domain as Domain | undefined)?.id === domain.id\n if (mine || !byName.has(row.name!)) byName.set(row.name!, row)\n }\n return [...byName.values()]\n}\n\n/**\n * A recipe saved into a domain, by its name: checked first by putting it into the test room with its defaults\n * (figure-model `checkV3Recipe`). Refused, it is not saved and the kernel's sentence goes back as it is.\n */\nexport async function saveRecipe(domain: Domain, source: unknown, state: 'draft' | 'released' = 'released'): Promise<FigureRecipe> {\n const recipe = checkV3Recipe(typeof source === 'string' ? JSON.parse(source) : source)\n const repository = getRepository(FigureRecipe)\n const existing = await repository.findOne({ where: { domain: { id: domain.id }, name: recipe.name } })\n return await repository.save({ ...(existing ?? {}), domain, name: recipe.name, source: JSON.stringify(recipe), state })\n}\n\n/**\n * A recipe of the domain's own, by name. A parent's recipe is not the domain's to change: it is copied in under the same\n * name (`saveRecipe`), and the copy stands in for it.\n */\nasync function ownRecipe(domain: { id: string }, name: string): Promise<FigureRecipe> {\n const row = await getRepository(FigureRecipe).findOne({ where: { domain: { id: domain.id }, name } })\n if (!row) throw new Error(`${name} is not a recipe of this domain; a parent's recipe is copied here to be changed`)\n return row\n}\n\n/** Released or back to draft: only released recipes are offered in the modeller. */\nexport async function setRecipeState(domain: { id: string }, name: string, state: 'draft' | 'released'): Promise<FigureRecipe> {\n const row = await ownRecipe(domain, name)\n return await getRepository(FigureRecipe).save({ ...row, state })\n}\n\n/**\n * A recipe of the domain's own taken away. Figures it was put into keep their parts -- nothing ties them to it\n * (ADR-0095). Where the parent has one by that name, the parent's is seen again.\n */\nexport async function deleteRecipe(domain: { id: string }, name: string): Promise<boolean> {\n const row = await ownRecipe(domain, name)\n await getRepository(FigureRecipe).softRemove(row)\n return true\n}\n\n/** The recipes figure-model ships (`recipes/*.json`), as they are. */\nexport function builtInRecipes(): Array<{ name: string; source: string }> {\n const dir = join(dirname(require.resolve('@hatiolab/figure-model/package.json')), 'recipes')\n return readdirSync(dir)\n .filter(file => file.endsWith('.json'))\n .sort()\n .map(file => {\n const source = readFileSync(join(dir, file), 'utf-8')\n return { name: JSON.parse(source).name, source }\n })\n}\n\n/**\n * The built-in recipes put into the system domain (ADR-0095 condition 2): once, and again gives the same result -- a\n * recipe already there by name is skipped, not overwritten, so a system recipe someone changed stays as they left it.\n * Each is checked as a saved one is; one that fails is left out and said, not half put in.\n */\nexport async function seedBuiltInRecipes(): Promise<{ added: string[]; skipped: string[]; refused: string[] }> {\n const system = await getRepository(Domain).findOne({ where: { systemFlag: true } })\n const result = { added: [] as string[], skipped: [] as string[], refused: [] as string[] }\n if (!system) return result\n const repository = getRepository(FigureRecipe)\n for (const { name, source } of builtInRecipes()) {\n if (await repository.count({ where: { domain: { id: system.id }, name } })) {\n result.skipped.push(name)\n continue\n }\n try {\n await saveRecipe(system, source)\n result.added.push(name)\n } catch (error) {\n result.refused.push(`${name}: ${error instanceof Error ? error.message : String(error)}`)\n }\n }\n return result\n}\n"]}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Domain } from '@things-factory/shell';
|
|
2
|
-
/**
|
|
3
|
-
* FigureRecipe — 조각 하나(ADR-0095).
|
|
4
|
-
*
|
|
5
|
-
* 조각은 매개변수가 붙은 제안 명령 목록이다. 형식은 `@hatiolab/figure-model` 의 `V3Recipe` 가 정하고, 여기에는
|
|
6
|
-
* 그 원본을 통째로 담는다 — Figure 가 자산을 `source` 로 담는 것과 같다. 매개변수 · 명령을 컬럼으로 두지 않는다.
|
|
7
|
-
* 넣은 뒤의 부품은 조각과 이어지지 않으므로, 이 행을 고치거나 지워도 도형은 바뀌지 않는다.
|
|
8
|
-
*/
|
|
9
|
-
export declare class FigureRecipe {
|
|
10
|
-
readonly id?: string;
|
|
11
|
-
domain?: Domain;
|
|
12
|
-
domainId?: string;
|
|
13
|
-
name?: string;
|
|
14
|
-
source?: string;
|
|
15
|
-
state?: 'draft' | 'released';
|
|
16
|
-
createdAt?: Date;
|
|
17
|
-
updatedAt?: Date;
|
|
18
|
-
deletedAt?: Date;
|
|
19
|
-
}
|