mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-31 05:33:00 +00:00 
			
		
		
		
	Move website source/build logic to projects/web
Mostly useful as it moves some of our build logic out of the main project, as that's already pretty noisy!
This commit is contained in:
		
							
								
								
									
										46
									
								
								projects/web/src/components/Recipe.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								projects/web/src/components/Recipe.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| import type { FunctionComponent } from "react"; | ||||
| import { createElement as h } from "react"; | ||||
| import useExport from "./WithExport.js"; | ||||
|  | ||||
| const Item: FunctionComponent<{ item: string }> = ({ item }) => { | ||||
|     const data = useExport(); | ||||
|     const itemName = data.itemNames[item]; | ||||
|  | ||||
|     return <img | ||||
|         src={`/images/items/${item.replace(":", "/")}.png`} | ||||
|         alt={itemName} | ||||
|         title={itemName} | ||||
|         className="recipe-icon" | ||||
|     /> | ||||
| }; | ||||
|  | ||||
| const EmptyItem: FunctionComponent = () => <span className="recipe-icon " />; | ||||
|  | ||||
| const Arrow: FunctionComponent<JSX.IntrinsicElements["svg"]> = (props) => <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45.513 45.512" {...props}> | ||||
|     <g> | ||||
|         <path d="M44.275,19.739L30.211,5.675c-0.909-0.909-2.275-1.18-3.463-0.687c-1.188,0.493-1.959,1.654-1.956,2.938l0.015,5.903 | ||||
|    l-21.64,0.054C1.414,13.887-0.004,15.312,0,17.065l0.028,11.522c0.002,0.842,0.338,1.648,0.935,2.242s1.405,0.927,2.247,0.925 | ||||
|    l21.64-0.054l0.014,5.899c0.004,1.286,0.781,2.442,1.971,2.931c1.189,0.487,2.557,0.21,3.46-0.703L44.29,25.694 | ||||
|    C45.926,24.043,45.92,21.381,44.275,19.739z" fill="var(--recipe-hover)" /> | ||||
|     </g> | ||||
| </svg>; | ||||
|  | ||||
| const Recipe: FunctionComponent<{ recipe: string }> = ({ recipe }) => { | ||||
|     const data = useExport(); | ||||
|     const recipeInfo = data.recipes[recipe]; | ||||
|     if (!recipeInfo) throw Error("Cannot find recipe for " + recipe); | ||||
|  | ||||
|     return <div className="recipe"> | ||||
|         <strong className="recipe-title">{data.itemNames[recipeInfo.output]}</strong> | ||||
|         <div className="recipe-inputs"> | ||||
|             {recipeInfo.inputs.map((items, i) => <div className="recipe-item recipe-input" key={i}>{items ? <Item item={items[0]} /> : <EmptyItem />}</div>)} | ||||
|         </div> | ||||
|         <Arrow className="recipe-arrow" /> | ||||
|         <div className="recipe-item recipe-output"> | ||||
|             <Item item={recipeInfo.output} /> | ||||
|             {recipeInfo.count > 1 && <span className="recipe-count">{recipeInfo.count}</span>} | ||||
|         </div> | ||||
|     </div> | ||||
| }; | ||||
|  | ||||
| export default Recipe; | ||||
							
								
								
									
										23
									
								
								projects/web/src/components/WithExport.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								projects/web/src/components/WithExport.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| import { createElement as h, useContext, createContext, FunctionComponent, ReactNode } from "react"; | ||||
|  | ||||
| export type DataExport = { | ||||
|     readonly itemNames: Record<string, string>, | ||||
|     readonly recipes: Record<string, Recipe>, | ||||
| }; | ||||
|  | ||||
| export type Recipe = { | ||||
|     readonly inputs: Array<Array<string>>, | ||||
|     readonly output: string, | ||||
|     readonly count: number, | ||||
| }; | ||||
|  | ||||
| const DataExport = createContext<DataExport>({ | ||||
|     itemNames: {}, | ||||
|     recipes: {}, | ||||
| }); | ||||
|  | ||||
| export const useExport = () => useContext(DataExport); | ||||
| export default useExport; | ||||
|  | ||||
| export const WithExport: FunctionComponent<{ data: DataExport, children: ReactNode }> = | ||||
|     ({ data, children }) => <DataExport.Provider value={data}> {children}</DataExport.Provider >; | ||||
							
								
								
									
										25
									
								
								projects/web/src/components/support.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								projects/web/src/components/support.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| import type { FunctionComponent } from "react"; | ||||
|  | ||||
| /** | ||||
|  * Wrap a component and ensure that no children are passed to it. | ||||
|  * | ||||
|  * Our custom tags *must* be explicitly closed, as <foo /> will be parsed as | ||||
|  * <foo>(rest of the document)</foo>. This ensures you've not forgotten to do | ||||
|  * that. | ||||
|  * | ||||
|  * @param component The component to wrap | ||||
|  * @returns A new functional component identical to the previous one | ||||
|  */ | ||||
| export const noChildren = function <T>(component: FunctionComponent<T>): FunctionComponent<T> { | ||||
|     // I hope that our few remaining friends | ||||
|     // Give up on trying to save us | ||||
|  | ||||
|     const name = component.displayName ?? component.name; | ||||
|     const wrapped: FunctionComponent<T> = props => { | ||||
|         if ((props as any).children) throw Error("Unexpected children in " + name); | ||||
|  | ||||
|         return component(props); | ||||
|     }; | ||||
|     wrapped.displayName = name; | ||||
|     return wrapped; | ||||
| } | ||||
							
								
								
									
										206
									
								
								projects/web/src/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								projects/web/src/index.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,206 @@ | ||||
| import { render, h, Component, Computer, PeripheralKind } from "copycat/embed"; | ||||
| import type { ComponentChild } from "preact"; | ||||
|  | ||||
| import settingsFile from "./mount/.settings"; | ||||
| import startupFile from "./mount/startup.lua"; | ||||
| import exprTemplate from "./mount/expr_template.lua"; | ||||
| import exampleNfp from "./mount/example.nfp"; | ||||
| import exampleNft from "./mount/example.nft"; | ||||
| import exampleAudioLicense from "./mount/example.dfpwm.LICENSE"; | ||||
| import exampleAudioUrl from "./mount/example.dfpwm"; | ||||
|  | ||||
| const defaultFiles: { [filename: string]: string } = { | ||||
|     ".settings": settingsFile, | ||||
|     "startup.lua": startupFile, | ||||
|  | ||||
|     "data/example.nfp": exampleNfp, | ||||
|     "data/example.nft": exampleNft, | ||||
| }; | ||||
|  | ||||
| const clamp = (value: number, min: number, max: number): number => { | ||||
|     if (value < min) return min; | ||||
|     if (value > max) return max; | ||||
|     return value; | ||||
| } | ||||
|  | ||||
| const download = async (url: string): Promise<Uint8Array> => { | ||||
|     const result = await fetch(url); | ||||
|     if (result.status != 200) throw new Error(`${url} responded with ${result.status} ${result.statusText}`); | ||||
|  | ||||
|     return new Uint8Array(await result.arrayBuffer()); | ||||
| }; | ||||
|  | ||||
| let dfpwmAudio: Promise<Uint8Array> | null = null; | ||||
|  | ||||
| const Click = (options: { run: () => void }) => | ||||
|     <button type="button" class="example-run" onClick={options.run}>Run ᐅ</button> | ||||
|  | ||||
| type WindowProps = {}; | ||||
|  | ||||
| type Example = { | ||||
|     files: { [file: string]: string | Uint8Array }, | ||||
|     peripheral: PeripheralKind | null, | ||||
| } | ||||
|  | ||||
| type WindowState = { | ||||
|     exampleIdx: number, | ||||
| } & ({ visible: false, example: null } | { visible: true, example: Example }) | ||||
|  | ||||
| type Touch = { clientX: number, clientY: number }; | ||||
|  | ||||
| class Window extends Component<WindowProps, WindowState> { | ||||
|     private positioned: boolean = false; | ||||
|     private left: number = 0; | ||||
|     private top: number = 0; | ||||
|     private dragging?: { downX: number, downY: number, initialX: number, initialY: number }; | ||||
|  | ||||
|     private snippets: { [file: string]: string } = {}; | ||||
|  | ||||
|     constructor(props: WindowProps, context: unknown) { | ||||
|         super(props, context); | ||||
|  | ||||
|         this.state = { | ||||
|             visible: false, | ||||
|             example: null, | ||||
|             exampleIdx: 0, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     componentDidMount() { | ||||
|         const elements = document.querySelectorAll("pre[data-lua-kind]"); | ||||
|         for (let i = 0; i < elements.length; i++) { | ||||
|             const element = elements[i] as HTMLElement; | ||||
|  | ||||
|             let example = element.innerText; | ||||
|  | ||||
|             const snippet = element.getAttribute("data-snippet"); | ||||
|             if (snippet) this.snippets[snippet] = example; | ||||
|  | ||||
|             // We attempt to pretty-print the result of a function _except_ when the function | ||||
|             // is print. This is pretty ugly, but prevents the confusing trailing "1". | ||||
|             if (element.getAttribute("data-lua-kind") == "expr" && !example.startsWith("print(")) { | ||||
|                 example = exprTemplate.replace("__expr__", example); | ||||
|             } | ||||
|  | ||||
|             const mount = element.getAttribute("data-mount"); | ||||
|             const peripheral = element.getAttribute("data-peripheral"); | ||||
|             render(<Click run={this.runExample(example, mount, peripheral)} />, element); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     componentDidUpdate(_: WindowProps, { visible }: WindowState) { | ||||
|         if (!visible && this.state.visible) this.setPosition(this.left, this.top); | ||||
|     } | ||||
|  | ||||
|     public render(_: WindowProps, { visible, example, exampleIdx }: WindowState): ComponentChild { | ||||
|         return visible ? <div class="example-window" style={`transform: translate(${this.left}px, ${this.top}px);`}> | ||||
|             <div class="titlebar"> | ||||
|                 <div class="titlebar-drag" onMouseDown={this.onMouseDown} onTouchStart={this.onTouchDown} /> | ||||
|                 <button type="button" class="titlebar-close" onClick={this.close}>{"\u2715"}</button> | ||||
|             </div> | ||||
|             <div class="computer-container"> | ||||
|                 <Computer key={exampleIdx} files={{ | ||||
|                     ...defaultFiles, ...example!.files, | ||||
|                 }} peripherals={{ back: example!.peripheral }} /> | ||||
|             </div> | ||||
|         </div> : <div class="example-window example-window-hidden" />; | ||||
|     } | ||||
|  | ||||
|     private runExample(example: string, mount: string | null, peripheral: string | null): () => void { | ||||
|         return async () => { | ||||
|             if (!this.positioned) { | ||||
|                 this.positioned = true; | ||||
|                 this.left = 20; | ||||
|                 this.top = 20; | ||||
|             } | ||||
|  | ||||
|             const files: { [file: string]: string | Uint8Array } = { "example.lua": example }; | ||||
|             if (mount !== null) { | ||||
|                 for (const toMount of mount.split(",")) { | ||||
|                     const [name, path] = toMount.split(":", 2); | ||||
|                     files[path] = this.snippets[name] || ""; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (example.includes("data/example.dfpwm")) { | ||||
|                 files["data/example.dfpwm.LICENSE"] = exampleAudioLicense; | ||||
|  | ||||
|                 try { | ||||
|                     if (dfpwmAudio === null) dfpwmAudio = download(exampleAudioUrl); | ||||
|                     files["data/example.dfpwm"] = await dfpwmAudio; | ||||
|                 } catch (e) { | ||||
|                     console.error("Cannot download example dfpwm", e); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             this.setState(({ exampleIdx }: WindowState) => ({ | ||||
|                 visible: true, | ||||
|                 example: { | ||||
|                     files, | ||||
|                     peripheral: peripheral as PeripheralKind | null, | ||||
|                 }, | ||||
|                 exampleIdx: exampleIdx + 1, | ||||
|             })); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private readonly close = () => this.setState({ visible: false }); | ||||
|  | ||||
|     // All the dragging code is terrible. However, I've had massive performance | ||||
|     // issues doing it other ways, so this'll have to do. | ||||
|     private onDown(e: Event, touch: Touch) { | ||||
|         e.stopPropagation(); | ||||
|         e.preventDefault(); | ||||
|  | ||||
|         this.dragging = { | ||||
|             initialX: this.left, initialY: this.top, | ||||
|             downX: touch.clientX, downY: touch.clientY | ||||
|         }; | ||||
|  | ||||
|         window.addEventListener("mousemove", this.onMouseDrag, true); | ||||
|         window.addEventListener("touchmove", this.onTouchDrag, true); | ||||
|         window.addEventListener("mouseup", this.onUp, true); | ||||
|         window.addEventListener("touchend", this.onUp, true); | ||||
|     } | ||||
|     private readonly onMouseDown = (e: MouseEvent) => this.onDown(e, e); | ||||
|     private readonly onTouchDown = (e: TouchEvent) => this.onDown(e, e.touches[0]); | ||||
|  | ||||
|     private onDrag(e: Event, touch: Touch) { | ||||
|         e.stopPropagation(); | ||||
|         e.preventDefault(); | ||||
|  | ||||
|         const dragging = this.dragging; | ||||
|         if (!dragging) return; | ||||
|  | ||||
|         this.setPosition( | ||||
|             dragging.initialX + (touch.clientX - dragging.downX), | ||||
|             dragging.initialY + (touch.clientY - dragging.downY), | ||||
|         ); | ||||
|     }; | ||||
|     private readonly onMouseDrag = (e: MouseEvent) => this.onDrag(e, e); | ||||
|     private readonly onTouchDrag = (e: TouchEvent) => this.onDrag(e, e.touches[0]); | ||||
|  | ||||
|     private readonly onUp = (e: Event) => { | ||||
|         e.stopPropagation(); | ||||
|  | ||||
|         this.dragging = undefined; | ||||
|  | ||||
|         window.removeEventListener("mousemove", this.onMouseDrag, true); | ||||
|         window.removeEventListener("touchmove", this.onTouchDrag, true); | ||||
|         window.removeEventListener("mouseup", this.onUp, true); | ||||
|         window.removeEventListener("touchend", this.onUp, true); | ||||
|     } | ||||
|  | ||||
|     private readonly setPosition = (left: number, top: number): void => { | ||||
|         const root = this.base as HTMLElement; | ||||
|  | ||||
|         left = this.left = clamp(left, 0, window.innerWidth - root.offsetWidth); | ||||
|         top = this.top = clamp(top, 0, window.innerHeight - root.offsetHeight); | ||||
|         root.style.transform = `translate(${left}px, ${top}px)`; | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| const root = document.createElement("div"); | ||||
| document.body.appendChild(root); | ||||
| render(<Window />, document.body, root); | ||||
							
								
								
									
										3
									
								
								projects/web/src/mount/.settings
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								projects/web/src/mount/.settings
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| { | ||||
|   [ "motd.enable" ] = false, | ||||
| } | ||||
							
								
								
									
										2982
									
								
								projects/web/src/mount/example.dfpwm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2982
									
								
								projects/web/src/mount/example.dfpwm
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										3
									
								
								projects/web/src/mount/example.dfpwm.LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								projects/web/src/mount/example.dfpwm.LICENSE
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| Playing Soliloquy [Remake] by Alcakight | ||||
| Source: https://soundcloud.com/alcaknight/soliloquy-remake | ||||
| License: under CC BY 3.0 | ||||
							
								
								
									
										16
									
								
								projects/web/src/mount/example.nfp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								projects/web/src/mount/example.nfp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| fffffffffffffffffffffff | ||||
| f444444444444444444444f | ||||
| f444444444444444444444f | ||||
| f44fffffffffffffffff44f | ||||
| f44ff0ffffffffffffff44f | ||||
| f44fff0fffffffffffff44f | ||||
| f44ff0ffffffffffffff44f | ||||
| f44fffffffffffffffff44f | ||||
| f44fffffffffffffffff44f | ||||
| f44fffffffffffffffff44f | ||||
| f44fffffffffffffffff44f | ||||
| f44fffffffffffffffff44f | ||||
| f444444444444444444444f | ||||
| f4444444444444444fff44f | ||||
| f444444444444444444444f | ||||
| fffffffffffffffffffffff | ||||
							
								
								
									
										15
									
								
								projects/web/src/mount/example.nft
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								projects/web/src/mount/example.nft
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
|                        | ||||
|  4                      | ||||
|  4                      | ||||
|  4                    4   | ||||
| 0 4     4>0 ls           4   | ||||
| 0 4     drom/            4 0  | ||||
| 0 4     startup.lua    4   | ||||
| 0 4     4> 0hello        4   | ||||
| 0 4     aHello World!  0  4   | ||||
| 0 4                    4   | ||||
| 0 4                    4   | ||||
| 0 4                    4   | ||||
| 0 4                      | ||||
| 0 4                    4   | ||||
| 0 4                      | ||||
							
								
								
									
										14
									
								
								projects/web/src/mount/expr_template.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								projects/web/src/mount/expr_template.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| local result = table.pack(__expr__ | ||||
| ) | ||||
|  | ||||
| if result.n == 0 then return end | ||||
|  | ||||
| local pp = require "cc.pretty" | ||||
|  | ||||
| local line = {} | ||||
| for i = 1, result.n do | ||||
|     if i > 1 then line[#line + 1] = pp.text(", ") end | ||||
|     line[#line + 1] = pp.pretty(result[i]) | ||||
| end | ||||
|  | ||||
| pp.print(pp.concat(table.unpack(line))) | ||||
							
								
								
									
										14
									
								
								projects/web/src/mount/startup.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								projects/web/src/mount/startup.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| -- Print out license information if needed | ||||
| if fs.exists("data/example.dfpwm") then | ||||
|     local h = io.open("data/example.dfpwm.LICENSE") | ||||
|     local contents = h:read("*a") | ||||
|     h:close() | ||||
|  | ||||
|     write(contents) | ||||
| end | ||||
|  | ||||
| -- Make the startup file invisible, then run the file. We could use | ||||
| -- shell.run, but this ensures the program is in shell history, etc... | ||||
| fs.delete("startup.lua") | ||||
| os.queueEvent("paste", "example.lua") | ||||
| os.queueEvent("key", keys.enter, false) | ||||
							
								
								
									
										180
									
								
								projects/web/src/styles.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								projects/web/src/styles.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,180 @@ | ||||
| :root { | ||||
|     --nav-width: 250px; | ||||
| } | ||||
| /* Some misc styles */ | ||||
|  | ||||
| .big-image { | ||||
|     display: block; | ||||
|     margin: 0 auto; | ||||
|     max-height: 400px; | ||||
| } | ||||
|  | ||||
| /* Pretty tables, mostly inherited from table.definition-list */ | ||||
| table { | ||||
|     border-collapse: collapse; | ||||
|     width: 100%; | ||||
| } | ||||
|  | ||||
| table td, | ||||
| table th { | ||||
|     border: 1px solid #cccccc; | ||||
|     padding: 2px 4px; | ||||
| } | ||||
|  | ||||
| table th { | ||||
|     background-color: var(--background-2); | ||||
| } | ||||
|  | ||||
| pre.highlight { | ||||
|     position: relative; | ||||
| } | ||||
|  | ||||
| .example-run { | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|     right: 0; | ||||
|     background: #058e05; | ||||
|     color: #fff; | ||||
|     padding: 2px 5px; | ||||
| } | ||||
|  | ||||
| .example-window { | ||||
|     position: fixed; | ||||
|     z-index: 200; | ||||
|     top: 0px; | ||||
|     top: 0px; | ||||
| } | ||||
|  | ||||
| /* Behold, the most cursed CSS! copy-cat's resizing algorithm is a weird, in | ||||
|    that it basically does "wrapper height - 40px" to determine the effective | ||||
|    size. But the footer is actually 1em+6px high, so we need to do very weird | ||||
|    things. | ||||
|  | ||||
|    Yes, it should probably be fixed on the copy-cat side. | ||||
|  */ | ||||
| .computer-container { | ||||
|     width: 620px; | ||||
|     height: calc(350px + 40px); | ||||
|     margin-top: calc((1em + 6px - 40px) / 2); | ||||
| } | ||||
|  | ||||
| .example-window-hidden { | ||||
|     display: none; | ||||
| } | ||||
|  | ||||
| .titlebar { | ||||
|     display: flex; | ||||
|     background: #dede6c; | ||||
|     height: 30px; | ||||
| } | ||||
|  | ||||
| .titlebar-drag { | ||||
|     flex-grow: 1; | ||||
|     cursor: grab; | ||||
| } | ||||
|  | ||||
| .titlebar-close { | ||||
|     background: none; | ||||
|     padding: 0px 5px; | ||||
|     border: none; | ||||
|     border-radius: 0px; | ||||
|     margin: 0px; | ||||
|     font-size: 15px; | ||||
| } | ||||
|  | ||||
| .titlebar-close:hover { | ||||
|     background: #cc4c4c; | ||||
| } | ||||
|  | ||||
| @media (max-width: 700px) { | ||||
|     .computer-container { | ||||
|         width: 314px; | ||||
|         height: calc(179px + 40px); | ||||
|     } | ||||
|  | ||||
|     .titlebar { | ||||
|         height: 20px; | ||||
|     } | ||||
|  | ||||
|     .titlebar-close { | ||||
|         font-size: 7px; | ||||
|     } | ||||
| } | ||||
|  | ||||
| :root { | ||||
|     --recipe-bg: #ddd; | ||||
|     --recipe-fg: #bbb; | ||||
|     --recipe-hover: #aaa; | ||||
|  | ||||
|     --recipe-padding: 4px; | ||||
|     --recipe-size: 32px; | ||||
| } | ||||
|  | ||||
| .recipe-container { | ||||
|     display: flex; | ||||
|     justify-content: center; | ||||
|     margin: 1em 0; | ||||
|     gap: 1em; | ||||
|     flex-wrap: wrap; | ||||
| } | ||||
|  | ||||
| .recipe { | ||||
|     display: inline-grid; | ||||
|     padding: var(--recipe-padding); | ||||
|  | ||||
|     background: var(--recipe-bg); | ||||
|     column-gap: var(--recipe-padding); | ||||
|     row-gap: var(--recipe-padding); | ||||
|     grid-template-rows: auto auto; | ||||
|     grid-template-columns: max-content 1fr max-content; | ||||
| } | ||||
|  | ||||
| .recipe-title { | ||||
|     color: #222; /* Same as --foreground in light theme. Ugly, but too lazy to style in dark for now. */ | ||||
|     grid-column-start: span 3; | ||||
| } | ||||
|  | ||||
| .recipe-inputs { | ||||
|     display: inline-grid; | ||||
|     column-gap: var(--recipe-padding); | ||||
|     row-gap: var(--recipe-padding); | ||||
|     align-items: center; | ||||
|     grid-template-rows: 1fr 1fr 1fr; | ||||
|     grid-template-columns: 1fr 1fr 1fr; | ||||
| } | ||||
|  | ||||
| .recipe-item { | ||||
|     padding: var(--recipe-padding); | ||||
|     background-color: var(--recipe-fg); | ||||
| } | ||||
|  | ||||
| .recipe-item:hover { | ||||
|     background-color: var(--recipe-hover); | ||||
| } | ||||
|  | ||||
| .recipe-icon { | ||||
|     display: block; | ||||
|     width: var(--recipe-size); | ||||
|     height: var(--recipe-size); | ||||
|     max-width: 10vw; | ||||
|     max-height: 10vw; | ||||
| } | ||||
|  | ||||
| .recipe-arrow { | ||||
|     width: var(--recipe-size); | ||||
|     max-width: 10vw; | ||||
|     align-self: center; | ||||
| } | ||||
|  | ||||
| .recipe-output { | ||||
|     align-self: center; | ||||
|     position: relative; | ||||
| } | ||||
|  | ||||
| .recipe-count { | ||||
|     position: absolute; | ||||
|     bottom: 0; | ||||
|     right: var(--recipe-padding); | ||||
|     color: #fff; | ||||
|     text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000 | ||||
| } | ||||
							
								
								
									
										53
									
								
								projects/web/src/transform.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								projects/web/src/transform.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| /** | ||||
|  * Find all HTML files generated by illuaminate and pipe them through a remark. | ||||
|  * | ||||
|  * This performs compile-time syntax highlighting and expands our custom | ||||
|  * components using React SSR. | ||||
|  * | ||||
|  * Yes, this would be so much nicer with next.js. | ||||
|  */ | ||||
| import * as fs from "fs/promises"; | ||||
| import globModule from "glob"; | ||||
| import * as path from "path"; | ||||
| import { createElement, createElement as h, Fragment } from 'react'; | ||||
| import { renderToStaticMarkup } from "react-dom/server"; | ||||
| import rehypeHighlight from "rehype-highlight"; | ||||
| import rehypeParse from 'rehype-parse'; | ||||
| import rehypeReact from 'rehype-react'; | ||||
| import { unified } from 'unified'; | ||||
| import { promisify } from "util"; | ||||
| // Our components | ||||
| import Recipe from "./components/Recipe.js"; | ||||
| import { noChildren } from "./components/support.js"; | ||||
| import { DataExport, WithExport } from "./components/WithExport.js"; | ||||
|  | ||||
| const glob = promisify(globModule); | ||||
|  | ||||
| (async () => { | ||||
|     const base = "build/illuaminate"; | ||||
|  | ||||
|     const processor = unified() | ||||
|         .use(rehypeParse, { emitParseErrors: true }) | ||||
|         .use(rehypeHighlight, { prefix: "" }) | ||||
|         .use(rehypeReact, { | ||||
|             createElement, | ||||
|             Fragment, | ||||
|             passNode: false, | ||||
|             components: { | ||||
|                 ['mc-recipe']: noChildren(Recipe), | ||||
|                 ['mcrecipe']: noChildren(Recipe), | ||||
|             } as any | ||||
|         }); | ||||
|  | ||||
|     const dataExport = JSON.parse(await fs.readFile("../../src/generated/export/index.json", "utf-8")) as DataExport; | ||||
|  | ||||
|     for (const file of await glob(base + "/**/*.html")) { | ||||
|         const contents = await fs.readFile(file, "utf-8"); | ||||
|  | ||||
|         const { result } = await processor.process(contents); | ||||
|  | ||||
|         const outputPath = path.resolve("build/jsxDocs", path.relative(base, file)); | ||||
|         await fs.mkdir(path.dirname(outputPath), { recursive: true }); | ||||
|         await fs.writeFile(outputPath, "<!doctype HTML>" + renderToStaticMarkup(<WithExport data={dataExport}>{result}</WithExport>)); | ||||
|     } | ||||
| })(); | ||||
							
								
								
									
										61
									
								
								projects/web/src/typings.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								projects/web/src/typings.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| declare module "*.lua" { | ||||
|     const contents: string; | ||||
|     export default contents; | ||||
| } | ||||
|  | ||||
| declare module "*.nfp" { | ||||
|     const contents: string; | ||||
|     export default contents; | ||||
| } | ||||
|  | ||||
| declare module "*.nft" { | ||||
|     const contents: string; | ||||
|     export default contents; | ||||
| } | ||||
|  | ||||
|  | ||||
| declare module "*.settings" { | ||||
|     const contents: string; | ||||
|     export default contents; | ||||
| } | ||||
|  | ||||
| declare module "*.LICENSE" { | ||||
|     const contents: string; | ||||
|     export default contents; | ||||
| } | ||||
|  | ||||
| declare module "*.dfpwm" { | ||||
|     const contents: string; | ||||
|     export default contents; | ||||
| } | ||||
|  | ||||
|  | ||||
| declare module "copycat/embed" { | ||||
|     import { h, Component, render, ComponentChild } from "preact"; | ||||
|  | ||||
|     export type Side = "up" | "down" | "left" | "right" | "front" | "back"; | ||||
|     export type PeripheralKind = "speaker"; | ||||
|  | ||||
|     export { h, Component, render }; | ||||
|  | ||||
|     export type ComputerAccess = unknown; | ||||
|  | ||||
|     export type MainProps = { | ||||
|         hdFont?: boolean | string, | ||||
|         persistId?: number, | ||||
|         files?: { [filename: string]: string | ArrayBuffer }, | ||||
|         label?: string, | ||||
|         width?: number, | ||||
|         height?: number, | ||||
|         resolve?: (computer: ComputerAccess) => void, | ||||
|         peripherals?: { | ||||
|             [side in Side]?: PeripheralKind | null | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     class Computer extends Component<MainProps, unknown> { | ||||
|         public render(props: MainProps, state: unknown): ComponentChild; | ||||
|     } | ||||
|  | ||||
|     export { Computer }; | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Jonathan Coates
					Jonathan Coates