diff --git a/scripts/lib.js b/scripts/lib.js index c888f5c62..cc7619a22 100644 --- a/scripts/lib.js +++ b/scripts/lib.js @@ -152,13 +152,6 @@ export async function genDTS() { // check if global defs are being generated let globalGenerated = false - // contain the type data for doc gen - const types = {} - const sections = [{ - name: "Start", - entries: [ "kaboom" ], - }] - // generate global decls for KaboomCtx members let globalDts = "" @@ -166,51 +159,15 @@ export async function genDTS() { globalDts += "declare global {\n" for (const stmt of stmts) { - - if (!types[stmt.name]) { - types[stmt.name] = [] - } - - types[stmt.name].push(stmt) - if (stmt.name === "KaboomCtx") { - if (stmt.kind !== "InterfaceDeclaration") { throw new Error("KaboomCtx must be an interface.") } - for (const name in stmt.members) { - - const mem = stmt.members[name] - globalDts += `\tconst ${name}: KaboomCtx["${name}"]\n` - - const tags = mem[0].jsDoc?.tags ?? {} - - if (tags["section"]) { - const name = tags["section"][0] - const docPath = path.resolve(`doc/sections/${name}.md`) - sections.push({ - name: name, - entries: [], - doc: await isFile(docPath) - ? await fs.readFile(docPath, "utf8") - : null, - }) - } - - const curSection = sections[sections.length - 1] - - if (name && !curSection.entries.includes(name)) { - curSection.entries.push(name) - } - } - globalGenerated = true - } - } globalDts += "}\n" @@ -219,11 +176,6 @@ export async function genDTS() { throw new Error("KaboomCtx not found, failed to generate global defs.") } - writeFile("site/doc.json", JSON.stringify({ - types, - sections, - })) - writeFile(`${distDir}/kaboom.d.ts`, dts) writeFile(`${distDir}/global.d.ts`, globalDts) writeFile(`${distDir}/global.js`, "") diff --git a/site/.eslintrc.json b/site/.eslintrc.json deleted file mode 100644 index b74bf91d8..000000000 --- a/site/.eslintrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "next/core-web-vitals", - "rules": { - "@typescript-eslint/no-unused-vars": "warn" - } -} diff --git a/site/.gitignore b/site/.gitignore deleted file mode 100644 index e5672f311..000000000 --- a/site/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.DS_Store -node_modules/ -.next/ -build/ -doc.json diff --git a/site/README.md b/site/README.md deleted file mode 100644 index aaaf4d5fe..000000000 --- a/site/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Kaboom Website - -This is the kaboom website, it's written in nextjs. - -## Developing - -```sh -$ npm run dev -``` - -will start the nextjs server - -## Symlink on Windows - -This site uses unix symlinks, might not work on Windows by default - -On Windows 10+: - -- enable "developer mode" -- `git config --global core.symlinks true` -- reclone the repo diff --git a/site/comps/Background.tsx b/site/comps/Background.tsx deleted file mode 100644 index d25f7165b..000000000 --- a/site/comps/Background.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import * as React from "react" -import View, { ViewProps } from "comps/View" -import { themes } from "lib/ui" -import Ctx from "lib/Ctx" - -const Background: React.FC = ({ - children, - ...args -}) => { - const { theme } = React.useContext(Ctx) - const patColor = themes[theme]["bgpat"] - return ( - ') repeat var(--color-bg1)`, - }} - {...args} - > - {children} - - ) -} - -export default Background diff --git a/site/comps/Blockly.tsx b/site/comps/Blockly.tsx deleted file mode 100644 index 3789c74b4..000000000 --- a/site/comps/Blockly.tsx +++ /dev/null @@ -1,435 +0,0 @@ -import { - useEffect, - useRef, - useImperativeHandle, - forwardRef, -} from "react" -import Blockly, { Workspace } from "blockly" -import { javascriptGenerator } from "blockly/javascript" -import "lib/kaboomBlockly" - -export interface BlocklyEditorRef { - genCode: () => string, - save: () => any, - load: (data: any) => void, -} - -// https://developers.google.com/blockly/guides/configure/web/events -type WorkspaceEvent = { - type: string - isUiEvent: boolean - workspaceId: string - blockId: string - group: string -} - -const SAVE_EVENTS = new Set([ - Blockly.Events.BLOCK_CHANGE, - Blockly.Events.BLOCK_CREATE, - Blockly.Events.BLOCK_DELETE, - Blockly.Events.BLOCK_MOVE, - Blockly.Events.VAR_CREATE, - Blockly.Events.VAR_DELETE, - Blockly.Events.VAR_RENAME, -]) - -const specialBlocks: Record = {} - -specialBlocks["kaboom_pos"] = { - inputs: { - "X": { - shadow: { - type: "math_number", - fields: { "NUM": 0 }, - }, - }, - "Y": { - shadow: { - type: "math_number", - fields: { "NUM": 0 }, - }, - }, - }, -} - -specialBlocks["kaboom_rect"] = { - inputs: { - "WIDTH": { - shadow: { - type: "math_number", - fields: { "NUM": 40 }, - }, - }, - "HEIGHT": { - shadow: { - type: "math_number", - fields: { "NUM": 40 }, - }, - }, - }, -} - -specialBlocks["kaboom_text"] = { - inputs: { - "TEXT": { - shadow: { - type: "text", - fields: { "TEXT": "" }, - }, - }, - }, -} - -specialBlocks["kaboom_moveBy"] = { - inputs: { - "X": { - shadow: { - type: "math_number", - fields: { "NUM": 0 }, - }, - }, - "Y": { - shadow: { - type: "math_number", - fields: { "NUM": 0 }, - }, - }, - }, -} - -specialBlocks["kaboom_moveTo"] = { - inputs: { - "X": { - shadow: { - type: "math_number", - fields: { "NUM": 0 }, - }, - }, - "Y": { - shadow: { - type: "math_number", - fields: { "NUM": 0 }, - }, - }, - }, -} - -specialBlocks["kaboom_scaleTo"] = { - inputs: { - "X": { - shadow: { - type: "math_number", - fields: { "NUM": 1 }, - }, - }, - "Y": { - shadow: { - type: "math_number", - fields: { "NUM": 1 }, - }, - }, - }, -} - -specialBlocks["kaboom_rotateTo"] = { - inputs: { - "ANGLE": { - shadow: { - type: "math_number", - fields: { "NUM": 0 }, - }, - }, - }, -} - -specialBlocks["kaboom_jump"] = { - inputs: { - "FORCE": { - shadow: { - type: "math_number", - fields: { "NUM": 640 }, - }, - }, - }, -} - -specialBlocks["kaboom_setText"] = { - inputs: { - "TEXT": { - shadow: { - type: "text", - fields: { "TEXT": "" }, - }, - }, - }, -} - -specialBlocks["math_random_int"] = { - inputs: { - "FROM": { - shadow: { - type: "math_number", - fields: { "NUM": 0 }, - }, - }, - "TO": { - shadow: { - type: "math_number", - fields: { "NUM": 10 }, - }, - }, - }, -} - -const blocks = [ - { - name: "kaboom", - blocks: [ - "kaboom_kaboom", - "kaboom_burp", - "kaboom_shake", - "kaboom_gravity", - "kaboom_loadSprite", - "kaboom_loadSound", - "kaboom_add", - "kaboom_destroy", - "kaboom_mouseX", - "kaboom_mouseY", - "kaboom_width", - "kaboom_height", - "kaboom_dt", - ], - }, - { - name: "components", - blocks: [ - "kaboom_sprite", - "kaboom_rect", - "kaboom_text", - "kaboom_pos", - "kaboom_scale", - "kaboom_rotate", - "kaboom_color", - "kaboom_color2", - "kaboom_anchor", - "kaboom_area", - "kaboom_body", - "kaboom_outline", - "kaboom_offscreen", - ], - }, - { - name: "actions", - blocks: [ - "kaboom_getPosX", - "kaboom_getPosY", - "kaboom_moveTo", - "kaboom_moveBy", - "kaboom_scaleTo", - "kaboom_rotateTo", - "kaboom_setText", - "kaboom_jump", - "kaboom_isGrounded", - ], - }, - { - name: "events", - blocks: [ - "kaboom_loop", - "kaboom_wait", - "kaboom_onUpdate", - "kaboom_onUpdateTag", - "kaboom_onKey", - "kaboom_onMouse", - "kaboom_onObj", - "kaboom_onCollide", - ], - }, - { - name: "logic", - blocks: [ - "controls_if", - "logic_boolean", - "logic_compare", - "logic_operation", - "logic_negate", - "logic_null", - "logic_ternary", - ], - }, - { - name: "controls", - blocks: [ - "controls_repeat", - "controls_repeat_ext", - ], - }, - { - name: "math", - blocks: [ - "math_number", - "math_arithmetic", - "math_trig", - "math_constant", - "math_number_property", - "math_round", - "math_on_list", - "math_modulo", - "math_constrain", - "math_random_int", - "math_random_float", - "math_atan2", - ], - }, - { - name: "text", - blocks: [ - "text", - "text_multiline", - "text_create_join_container", - "text_length", - "text_isEmpty", - "text_indexOf", - "text_charAt", - "text_append", - "text_print", - ], - }, - { - name: "variables", - blocks: [ - "variables_get", - "variables_set", - "math_change", - ], - }, - { - name: "list", - blocks: [ - "lists_create_with", - "lists_create_empty", - "lists_repeat", - "lists_reverse", - "lists_isEmpty", - "lists_length", - "lists_getIndex", - "lists_setIndex", - ], - }, - { - name: "function", - blocks: [ - "procedures_defnoreturn", - "procedures_defreturn", - "procedures_callnoreturn", - "procedures_callreturn", - ], - }, -] - -const BlocklyEditor = forwardRef(({...props}, ref) => { - - const divRef = useRef(null) - const workspaceRef = useRef(null) - - useImperativeHandle(ref, () => ({ - genCode() { - if (!workspaceRef.current) throw new Error("Blockly workspace not initialized") - return javascriptGenerator.workspaceToCode(workspaceRef.current) - }, - save() { - if (!workspaceRef.current) throw new Error("Blockly workspace not initialized") - return Blockly.serialization.workspaces.save(workspaceRef.current) - }, - load(data) { - if (!workspaceRef.current) throw new Error("Blockly workspace not initialized") - Blockly.Events.disable() - Blockly.serialization.workspaces.load(data, workspaceRef.current) - Blockly.Events.enable() - }, - })) - - useEffect(() => { - if (!divRef.current) return - const div = divRef.current - Blockly.registry.unregister("theme", "kaboom") - const workspace = Blockly.inject(div, { - toolbox: { - kind: "categoryToolbox", - contents: blocks.map((c) => ({ - kind: "category", - name: c.name, - contents: c.blocks.map((b) => ({ - kind: "block", - type: b, - ...(specialBlocks[b] ?? {}), - })), - })), - }, - grid: { - spacing: 16, - }, - comments: true, - move: { - drag: true, - scrollbars: true, - wheel: true, - }, - zoom: { - controls: true, - pinch: true, - wheel: true, - }, - trashcan: true, - theme: { - name: "kaboom", - base: Blockly.Themes.Classic, - fontStyle: { - family: "IBM Plex Mono", - }, - componentStyles: { - toolboxBackgroundColour: "#eeeeee", - toolboxForegroundColour: "#888888", - flyoutBackgroundColour: "#eeeeee", - flyoutForegroundColour: "#888888", - flyoutOpacity: 0.5, - insertionMarkerColour: "#fff", - scrollbarOpacity: 0.5, - }, - }, - }) - - workspaceRef.current = workspace - - workspace.addChangeListener((e) => { - if (SAVE_EVENTS.has(e.type)) { - // TODO: auto save - } - }) - - let toolboxVisible = true - - const keyDownHandler = (e: KeyboardEvent) => { - if (e.key === "Tab") { - e.preventDefault() - toolboxVisible = !toolboxVisible - workspace.getToolbox()?.setVisible(toolboxVisible) - } - } - - div.addEventListener("keydown", keyDownHandler) - - return () => { - div.removeEventListener("keydown", keyDownHandler) - workspace.dispose() - } - - }, []) - - return ( -
- ) - -}) - -export default BlocklyEditor diff --git a/site/comps/BlogEntry.tsx b/site/comps/BlogEntry.tsx deleted file mode 100644 index 19214ded4..000000000 --- a/site/comps/BlogEntry.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import * as React from "react" -import View, { ViewProps } from "comps/View" -import { themes } from "lib/ui" -import Text from "comps/Text" -import Ctx from "lib/Ctx" - -interface BlogEntryProps { - title: string; - author: string; - date: string; - description: string; - image: string; -} - -const MOBILE = 640 - -const BlogEntry: React.FC = ({ - children, - title, - author, - date, - description, - image, - ...args -}) => { - return ( - - - - {title} - {author}, {date} - {description} - - - ) -} - -export default BlogEntry diff --git a/site/comps/Button.tsx b/site/comps/Button.tsx deleted file mode 100644 index fe0b5eb80..000000000 --- a/site/comps/Button.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { ReactNode } from "react" -import View, { ViewProps } from "comps/View" -import Text from "comps/Text" - -interface ButtonProps { - text?: string, - action?: () => void, - danger?: boolean, -} - -const Button: React.FC = ({ - text, - action, - danger, - children, - ...args -}) => ( - action && action()} - bg={danger ? "var(--color-danger)" : 3} - padX={1} - padY={0.5} - rounded - outlined - css={{ - fontSize: "var(--text-normal)", - cursor: "pointer", - userSelect: "none", - ":hover": { - background: "var(--color-bg4)", - }, - ":active": { - background: "var(--color-outline)", - }, - }} - {...args} - > - { text ? ( - {text} - ) : children } - -) - -export default Button diff --git a/site/comps/Doc.tsx b/site/comps/Doc.tsx deleted file mode 100644 index 26d65447c..000000000 --- a/site/comps/Doc.tsx +++ /dev/null @@ -1,346 +0,0 @@ -import * as React from "react" -import Link from "next/link" -import View, { ViewPropsAnd } from "comps/View" -import Text from "comps/Text" -import Markdown from "comps/Markdown" -// @ts-ignore -import doc from "doc.json" - -const TypeSig: React.FC = ({ data }) => ( - - {(() => { - switch (data?.kind) { - case "StringKeyword": return "string" - case "NumberKeyword": return "number" - case "BooleanKeyword": return "boolean" - case "VoidKeyword": return "void" - case "AnyKeyword": return "any" - case "NullKeyword": return "null" - case "StringLiteral": return `"${data.text}"` - case "LiteralType": return - case "ArrayType": return <>[] - case "ParenthesizedType": return <>() - case "FunctionType": return <> - () {"=>"} - - case "UnionType": return data.types.map((t: any, i: number) => ( - - - {i === data.types.length - 1 ? "" : " | "} - - )) - case "TypeReference": return (doc as any).types[data.typeName] - ? - - {(ctx) => ( - ctx.typeref && ctx.typeref(data.typeName)} - > - {data.typeName} - - )} - - : data.typeName - case "TypeLiteral": - return - { - Object.entries(data.members).map(([name, variants]: [string, any]) => - variants.map((mem: any) => - , - ), - ) - } - - case "IndexedAccessType": - return <>[] - default: - return "unknown" - } - })()} - { data?.typeArguments && - - {"<"}{data.typeArguments.map((arg: any, i: number) => ( - - - { i === data.typeArguments.length - 1 ? "" : ", " } - - ))}{">"} - - } - -) - -const FuncParams: React.FC = ({ data }) => data.parameters.map((p: any, i: number) => ( - - {p.name} - {p.questionToken ? "?" : ""} - : {p.dotDotDotToken ? "..." : } - {i === data.parameters.length - 1 ? "" : ", "} - -)) - -interface TagProps { - name: string, -} - -const Tag: React.FC = ({ name }) => ( - - - {name} - - -) - -function isType(entry: any): boolean { - return [ - "TypeAliasDeclaration", - "InterfaceDeclaration", - "ClassDeclaration", - ].includes(entry.kind) -} - -const Title: React.FC<{ - data: any, - small?: boolean, - children?: React.ReactNode, -}> = ({ data, small, children }) => ( - - { isType(data) && } - - { small ? ( - <>{data.name} - ) : ( - - {(ctx) => - {data.name} - } - - ) } - {children} - - -) - -interface MemberProps { - data: any, - small?: boolean, -} - -const MethodSignature: React.FC = ({ data, small }) => ( - - - (<FuncParams data={data} />) - { data.type?.kind !== "VoidKeyword" && <>{" => "}<TypeSig data={data.type}/ ></> } - - - -) - -const PropertySignature: React.FC = ({ data, small }) => ( - - {data.questionToken ? "?" : ""}: <TypeSig data={data.type} /> - - -) - -const FunctionDeclaration: React.FC = ({ data }) => ( - - - {data.name}() - - - -) - -const TypeAliasDeclaration: React.FC = ({ data }) => ( - - - {(() => { - switch (data.type.kind) { - case "TypeLiteral": - return Object.entries(data.type.members).map(([name, variants]: [string, any]) => - variants.map((mem: any) => - <Entry key={mem.name} data={mem} />, - ), - ) - case "TypeReference": - case "UnionType": - case "StringKeyword": - case "NumberKeyword": - case "BooleanKeyword": - case "VoidKeyword": - case "AnyKeyword": - case "NullKeyword": - case "FunctionType": - return <TypeSig data={data.type} /> - case "IntersectionType": - return data.type.types.map((t: any, i: number) => ( - <React.Fragment key={i}><TypeSig data={t} />{i === data.type.types.length - 1 ? "" : "&"}</React.Fragment> - )) - default: - return <></> - } - <JSDoc data={data} /> - })()} - </View> -) - -const InterfaceDeclaration: React.FC<EntryProps> = ({ data }) => { - return ( - <View gap={2} stretchX> - <View gap={1} stretchX> - <Title data={data} /> - <JSDoc data={data} /> - </View> - { Object.entries(data.members).map(([name, variants]: [string, any], i) => - variants.map((mem: any, j: number) => - <Member key={`${mem.name}-${i}-${j}`} data={mem} />, - ), - ) } - </View> - ) -} - -const ClassDeclaration = ({ data }: EntryProps) => { - return ( - <View gap={2} stretchX> - <View gap={1} stretchX> - <Title data={data} /> - <JSDoc data={data} /> - </View> - { Object.entries(data.members).map(([name, variants]: [string, any], i) => - variants.map((mem: any, j: number) => - <Member key={`${mem.name}-${i}-${j}`} data={mem} />, - ), - ) } - </View> - ) -} - -const Member: React.FC<MemberProps> = ({ data }) => { - switch (data.kind) { - case "MethodSignature": - return <MethodSignature data={data} small /> - case "PropertySignature": - return <PropertySignature data={data} small /> - case "MethodDeclaration": - return <MethodSignature data={data} small /> - case "PropertyDeclaration": - return <PropertySignature data={data} small /> - } - return <></> -} - -interface EntryProps { - data: any, -} - -const Entry: React.FC<EntryProps> = ({ data }) => { - switch (data.kind) { - case "MethodSignature": - return <MethodSignature data={data} /> - case "PropertySignature": - return <PropertySignature data={data} /> - case "FunctionDeclaration": - return <FunctionDeclaration data={data} /> - case "TypeAliasDeclaration": - return <TypeAliasDeclaration data={data} /> - case "InterfaceDeclaration": - return <InterfaceDeclaration data={data} /> - case "ClassDeclaration": - return <ClassDeclaration data={data} /> - } - return <></> -} - -const JSDoc: React.FC<EntryProps> = ({data}) => { - return data.jsDoc ? ( - <View gap={1} stretchX> - { data.jsDoc.doc && - <Text color={3}>{data.jsDoc.doc}</Text> - } - { Object.entries(data.jsDoc.tags).map(([name, items]) => { - return (items as string[]).map((content) => { - switch (name) { - case "section": return - case "example": return <Markdown padY={1} key={content} src={content} /> - default: return ( - <View key={content} gap={1} dir="row"> - <Tag name={name} /> - <Text color={3}>{content}</Text> - </View> - ) - } - }) - }) } - </View> - ) : <></> -} - -interface DocProps { - name: string, - anchor?: string, - onAnchor?: () => void, - typeref?: (name: string) => void, -} - -const Doc: React.FC<ViewPropsAnd<DocProps>> = ({ - name, - anchor, - onAnchor, - typeref, - ...args -}) => { - - const entries = (doc as any).types[name] - || (doc as any).types["KaboomCtx"][0].members[name] - - if (!entries) { - return <Text color={3}>Entry not found: {name}</Text> - } - - return ( - <DocCtx.Provider value={{ - typeref: typeref, - anchor: anchor, - onAnchor: onAnchor, - }}> - <View stretchX {...args} gap={3}> - {entries.map((e: any, i: number) => <Entry key={`${e.name}-${i}`} data={e} />)} - </View> - </DocCtx.Provider> - ) - -} - -interface DocCtx { - anchor?: string, - onAnchor?: () => void, - typeref?: (name: string) => void, -} - -const DocCtx = React.createContext<DocCtx>({ - typeref: () => {}, -}) - -export default Doc diff --git a/site/comps/Drawer.tsx b/site/comps/Drawer.tsx deleted file mode 100644 index 2dea6961d..000000000 --- a/site/comps/Drawer.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import * as React from "react" -import View, { ViewPropsAnd } from "comps/View" -import Ctx from "lib/Ctx" -import useClickOutside from "hooks/useClickOutside" - -type DrawerDir = - | "left" - | "right" - | "top" - | "bottom" - -interface DrawerProps { - paneWidth?: number, - handle?: boolean, - bigHandle?: boolean, - expanded: boolean, - dir?: DrawerDir, - setExpanded: React.Dispatch<React.SetStateAction<boolean>>, -} - -const Drawer = React.forwardRef<HTMLDivElement, ViewPropsAnd<DrawerProps>>(({ - handle, - bigHandle, - expanded, - setExpanded, - paneWidth, - height, - dir, - children, - ...args -}, ref) => { - - const localRef = React.useRef(null) - const curRef = ref ?? localRef - paneWidth = paneWidth ?? 240 - const handleWidth = handle ? (bigHandle ? 24 : 16) : 0 - - // @ts-ignore - useClickOutside(curRef, () => { - setExpanded(false) - }, []) - - return <> - <View - ref={curRef} - dir="row" - bg={1} - rounded - outlined - height={height ?? "90%"} - width={paneWidth + handleWidth} - css={{ - position: "fixed", - top: "50%", - transform: "translateY(-50%)", - [dir ?? "left"]: expanded ? -4 : (handle ? -paneWidth : -paneWidth - 4), - transition: "0.2s", - overflow: "hidden", - zIndex: 200, - }} - > - <View - gap={1} - stretchY - css={{ - flex: "1", - overflow: "auto", - order: dir === "right" || dir === "bottom" ? 2 : 1, - }} - {...args} - > - {children} - </View> - { handle && - <View - dir="row" - align="center" - justify="around" - padX={0.5} - width={handleWidth} - stretchY - onClick={() => setExpanded((e) => !e)} - css={{ - cursor: "pointer", - order: dir === "right" || dir === "bottom" ? 1 : 2, - }} - > - {[...Array(bigHandle ? 2 : 1)].map((_, i) => ( - <View key={i} height="calc(100% - 16px)" width={2} bg={2} /> - ))} - </View> - } - </View> - <View - css={{ - background: "black", - width: "100vw", - height: "100vh", - opacity: expanded ? 0.5 : 0, - transition: "0.2s opacity", - pointerEvents: "none", - position: "fixed", - zIndex: 199, - top: 0, - left: 0, - }} - /> - </> - -}) - -export default Drawer diff --git a/site/comps/Drop.tsx b/site/comps/Drop.tsx deleted file mode 100644 index ce7a3c980..000000000 --- a/site/comps/Drop.tsx +++ /dev/null @@ -1,191 +0,0 @@ -// TODO: accept DOM / dataTransfer drop - -import * as React from "react" -import View, { ViewPropsAnd } from "comps/View" - -type FileType = - | "arrayBuffer" - | "binaryString" - | "dataURL" - | "text" - ; - -type FileContent = - | ArrayBuffer - | string - | any - ; - -interface DropProps { - onEnter?: () => void, - onLeave?: () => void, - onLoad?: (file: File, content: FileContent) => void, - onErr?: (file: File) => void, - onAbort?: (file: File) => void, - readAs?: FileType | ((file: File) => FileType), - accept?: string | RegExp | (string | RegExp)[], - dragColor?: number | string, -} - -const Drop = React.forwardRef<HTMLDivElement, ViewPropsAnd<DropProps>>(({ - bg, - onLoad, - onErr, - onAbort, - onEnter, - onLeave, - readAs, - accept, - dragColor, - children, - ...args -}, ref) => { - - const [ counter, setCounter ] = React.useState(0) - const [ refuse, setRefuse ] = React.useState(false) - const draggin = counter > 0 - - const checkAccept = React.useCallback((mime: string) => { - - // accept anything if no accept is passed - if (!accept) { - return true - } - - const acceptList = Array.isArray(accept) ? accept : [accept] - - for (const pat of acceptList) { - if (!mime.match(pat)) { - return false - } - } - - return true - - }, [ accept ]) - - return ( - <View - bg={refuse ? "var(--color-errbg)" : (draggin ? (dragColor ?? 2) : bg)} - onDragEnter={(e) => { - - e.preventDefault() - - const items = e.dataTransfer.items - if (!items?.length) return - - for (let i = 0; i < items.length; i++) { - if (items[i].kind !== "file") { - return - } - if (!checkAccept(items[i].type)) { - setRefuse(true) - break - } - } - - setCounter((c) => { - if (c == 0) { - onEnter && onEnter() - } - return c + 1 - }) - - }} - onDragLeave={(e) => { - - e.preventDefault() - - const items = e.dataTransfer.items - if (!items?.length) return - - for (let i = 0; i < items.length; i++) { - if (items[i].kind !== "file") { - return - } - } - - setCounter((c) => { - if (c - 1 === 0) { - onLeave && onLeave() - setRefuse(false) - } - return c - 1 - }) - - }} - onDragOver={(e) => { - - const items = e.dataTransfer.items - if (!items?.length) return - - for (let i = 0; i < items.length; i++) { - if (items[i].kind !== "file") { - return - } - } - - e.preventDefault() - - }} - onDrop={(e) => { - - e.preventDefault() - setCounter(0) - setRefuse(false) - - if (refuse || !draggin || !onLoad || !readAs) return - - const items = e.dataTransfer.items - if (!items?.length) return - - for (let i = 0; i < items.length; i++) { - - if (items[i].kind !== "file") continue - const file = items[i].getAsFile() - if (!file) continue - - // get the desired read method of the file - const ty = typeof readAs === "string" ? readAs : readAs(file) - - // init reader - const reader = new FileReader() - - // register events - reader.onload = (e) => { - if (e.target?.result) { - onLoad(file, e.target.result) - } - } - - reader.onerror = (e) => onErr && onErr(file) - reader.onabort = (e) => onAbort && onAbort(file) - - // start the reading based on type - switch (ty) { - case "dataURL": - reader.readAsDataURL(file) - break - case "arrayBuffer": - reader.readAsArrayBuffer(file) - break - case "text": - reader.readAsText(file) - break - case "binaryString": - reader.readAsBinaryString(file) - break - } - - } - - }} - {...args} - > - {children} - </View> - ) - -}) - -export default Drop diff --git a/site/comps/Editor.tsx b/site/comps/Editor.tsx deleted file mode 100644 index b66e00353..000000000 --- a/site/comps/Editor.tsx +++ /dev/null @@ -1,560 +0,0 @@ -import * as React from "react" - -import { - EditorState, - Extension, -} from "@codemirror/state" - -import { - EditorView, - keymap, - highlightSpecialChars, - highlightActiveLine, - drawSelection, - placeholder as cmPlaceholder, - KeyBinding, - lineNumbers, - highlightActiveLineGutter, -} from "@codemirror/view" - -import { - tags as t, -} from "@lezer/highlight" - -import { - defaultKeymap, - indentWithTab, - history, - historyKeymap, -} from "@codemirror/commands" - -import { - indentUnit, - indentOnInput, - foldGutter, - defaultHighlightStyle, - HighlightStyle, - bracketMatching, - syntaxHighlighting, -} from "@codemirror/language" - -import { - searchKeymap, - highlightSelectionMatches, -} from "@codemirror/search" - -import { closeBrackets } from "@codemirror/autocomplete" -import { javascript } from "@codemirror/lang-javascript" - -import useUpdateEffect from "hooks/useUpdateEffect" -import View, { ViewPropsAnd } from "comps/View" -import Ctx from "lib/Ctx" -import { themes } from "lib/ui" -import { clamp, hex2rgb, rgb2hex } from "lib/math" - -import interact, { interactRule } from "lib/cm/interact" -import drop, { dropRule } from "lib/cm/drop" -import dropCursor from "lib/cm/dropCursor" -import img from "lib/cm/img" -import useCompartment from "lib/cm/useCompartment" - -// @ts-ignore -const cmThemes: Record<Theme, [ Extension, HighlightStyle ]> = {} - -Object.keys(themes).forEach((name) => { - - const theme = themes[name] - const yellow = "#e5c07b" - const red = "#e06c75" - const cyan = "#56b6c2" - const ivory = theme["fg2"] - const stone = theme["fg4"] - const invalid = theme["fg4"] - const blue = "#61afef" - const green = "#98d379" - const whiskey = "#d19a66" - const magenta = "#c678dd" - const darkBackground = theme["bg1"] - const highlightBackground = theme["bg3"] - const background = theme["bg2"] - const selection = theme["bg4"] - const cursor = theme["highlight"] - - cmThemes[name] = [ - EditorView.theme({ - "&": { - color: ivory, - backgroundColor: background, - }, - ".cm-content": { - caretColor: cursor, - }, - ".cm-cursor": { - borderLeftColor: cursor, - borderLeftWidth: "3px", - borderRadius: "3px", - }, - "&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": { - backgroundColor: selection, - }, - ".cm-panels": { - backgroundColor: darkBackground, - color: ivory, - }, - ".cm-panels.cm-panels-top": { - borderBottom: "2px solid black", - }, - ".cm-panels.cm-panels-bottom": { - borderTop: "2px solid black", - }, - ".cm-searchMatch": { - backgroundColor: "#72a1ff59", - outline: "1px solid #457dff", - }, - ".cm-searchMatch.cm-searchMatch-selected": { - backgroundColor: "#6199ff2f", - }, - ".cm-activeLine": { - backgroundColor: highlightBackground, - }, - ".cm-activeLineGutter": { - color: stone, - backgroundColor: highlightBackground, - }, - ".cm-selectionMatch": { - backgroundColor: "#aafe661a", - }, - ".cm-matchingBracket, .cm-nonmatchingBracket": { - backgroundColor: "#bad0f847", - outline: "1px solid #515a6b", - }, - ".cm-gutters": { - backgroundColor: background, - color: stone, - border: "none", - }, - ".cm-foldPlaceholder": { - backgroundColor: theme["fg4"], - border: "none", - color: theme["fg1"], - }, - }, { dark: true }), - HighlightStyle.define([ - { - tag: t.keyword, - color: magenta, - }, - { - tag: [ -// t.name, - t.deleted, - t.character, - t.macroName, -// t.propertyName, - ], - color: red, - }, - { - tag: [ - t.function(t.variableName), - t.labelName, - ], - color: blue, - }, - { - tag: [ - t.color, - t.constant(t.name), - t.standard(t.name), - ], - color: whiskey, - }, - { - tag: [ - t.definition(t.name), - t.separator, - ], - color: ivory, - }, - { - tag: [ - t.typeName, - t.className, - t.number, - t.changed, - t.annotation, - t.modifier, - t.self, - t.namespace, - ], - color: yellow, - }, - { - tag: [ - t.operator, - t.operatorKeyword, - t.url, - t.escape, - t.regexp, - t.link, - t.special(t.string), - ], - color: cyan, - }, - { - tag: [ - t.meta, - t.comment, - ], - color: stone, - }, - { - tag: t.strong, - fontWeight: "bold", - }, - { - tag: t.emphasis, - fontStyle: "italic", - }, - { - tag: t.strikethrough, - textDecoration: "line-through", - }, - { - tag: t.link, - color: stone, - textDecoration: "underline", - }, - { - tag: t.heading, - fontWeight: "bold", - color: red, - }, - { - tag: [ - t.atom, - t.bool, - t.special(t.variableName), - ], - color: whiskey, - }, - { - tag: [ - t.processingInstruction, - t.string, - t.inserted, - ], - color: green, - }, - { - tag: t.invalid, - color: invalid, - }, - ]), - ] - -}) - -export interface EditorRef { - getContent: () => string | null, - getSelection: () => string | null, - getWord: () => string | null, - setContent: (content: string) => void, - getView: () => EditorView | null, - focus: () => void, -} - -interface EditorProps { - content?: string | (() => string), - placeholder?: string, - onChange?: (code: string) => void, - onSelect?: (code: string) => void, - keys?: KeyBinding[], -} - -const Editor = React.forwardRef<EditorRef, ViewPropsAnd<EditorProps>>(({ - content, - placeholder, - keys, - onChange, - onSelect, - ...args -}, ref) => { - - const editorDOMRef = React.useRef(null) - const [ view, setView ] = React.useState<EditorView | null>(null) - const { theme } = React.useContext(Ctx) - const themeConf = useCompartment({view}) - const hlConf = useCompartment({view}) - - React.useImperativeHandle(ref, () => ({ - getContent() { - if (!view) return null - return view.state.doc.toString() - }, - getSelection() { - if (!view) return null - return view.state.sliceDoc( - view.state.selection.main.from, - view.state.selection.main.to, - ) - }, - getWord() { - if (!view) return null - const range = view.state.wordAt(view.state.selection.main.head) - if (range) { - return view.state.sliceDoc(range.from, range.to) - } - return null - }, - setContent(content: string) { - view?.dispatch({ - changes: { - from: 0, - to: view.state.doc.length, - insert: content, - }, - }) - }, - getView() { - return view - }, - focus() { - view?.focus() - }, - })) - - React.useEffect(() => { - - if (!editorDOMRef.current) { - throw new Error("Failed to start editor") - } - - const editorDOM = editorDOMRef.current - - const origins = [ - "topleft", "top", "topright", - "left", "center", "right", - "botleft", "bot", "botright", - ].map((o) => `"${o}"`) - - // TODO - const originState = { - x: 0, - y: 0, - idx: -1, - } - - const view = new EditorView({ - parent: editorDOM, - state: EditorState.create({ - doc: (typeof content === "function" ? content() : content) ?? "", - extensions: [ - themeConf.of(cmThemes[theme][0]), - hlConf.of(syntaxHighlighting(cmThemes[theme][1])), - EditorState.tabSize.of(4), - EditorState.allowMultipleSelections.of(true), - indentUnit.of("\t"), - javascript(), - lineNumbers(), - highlightSpecialChars(), - highlightActiveLine(), - highlightActiveLineGutter(), - highlightSelectionMatches(), - cmPlaceholder(placeholder ?? ""), - history(), - foldGutter(), - bracketMatching(), - closeBrackets(), - indentOnInput(), - drawSelection(), - dropCursor, - syntaxHighlighting(defaultHighlightStyle), - EditorView.updateListener.of((update) => { - const state = update.state - if (update.docChanged) { - onChange && onChange(state.doc.toString()) - } - if (update.selectionSet) { - const sel = state.sliceDoc( - state.selection.main.from, - state.selection.main.to, - ) - if (sel) { - onSelect && onSelect(sel) - } - } - }), - keymap.of([ - ...defaultKeymap, - ...historyKeymap, - ...searchKeymap, - indentWithTab, - ...(keys ?? []), - ]), - interact, - // number slider - interactRule.of({ - regexp: /-?\b\d+\.?\d*\b/g, - cursor: "ew-resize", - onDrag: (text, setText, e) => { - // TODO: size aware - // TODO: small interval with shift key? - const newVal = Number(text) + e.movementX - if (isNaN(newVal)) return - setText(newVal.toString()) - }, - }), - // bool toggler - interactRule.of({ - regexp: /true|false/g, - cursor: "pointer", - onClick: (text, setText) => { - switch (text) { - case "true": return setText("false") - case "false": return setText("true") - } - }, - }), - // kaboom vec2 slider - interactRule.of({ - regexp: /vec2\(-?\b\d+\.?\d*\b\s*(,\s*-?\b\d+\.?\d*\b)?\)/g, - cursor: "move", - onDrag: (text, setText, e) => { - const res = /vec2\((?<x>-?\b\d+\.?\d*\b)\s*(,\s*(?<y>-?\b\d+\.?\d*\b))?\)/.exec(text) - const x = Number(res?.groups?.x) - let y = Number(res?.groups?.y) - if (isNaN(x)) return - if (isNaN(y)) y = x - setText(`vec2(${x + e.movementX}, ${y + e.movementY})`) - }, - }), - // kaboom color picker - interactRule.of({ - regexp: /rgb\(.*\)/g, - cursor: "pointer", - onClick: (text, setText, e) => { - const res = /rgb\((?<r>\d+)\s*,\s*(?<g>\d+)\s*,\s*(?<b>\d+)\)/.exec(text) - const r = Number(res?.groups?.r) - const g = Number(res?.groups?.g) - const b = Number(res?.groups?.b) - const sel = document.createElement("input") - sel.type = "color" - if (!isNaN(r + g + b)) sel.value = rgb2hex(r, g, b) - sel.addEventListener("input", (e) => { - const el = e.target as HTMLInputElement - if (el.value) { - const [r, g, b] = hex2rgb(el.value) - setText(`rgb(${r}, ${g}, ${b})`) - } - }) - sel.click() - }, - }), - // kaboom origin slider - interactRule.of({ - regexp: new RegExp(`${origins.join("|")}`, "g"), - cursor: "move", - onClick: (text) => { - const idx = origins.indexOf(text) - originState.x = 0 - originState.y = 0 - originState.idx = idx - }, - onDrag: (text, setText, e) => { - originState.x += e.movementX - originState.y += e.movementY - const { idx, x, y } = originState - if (idx === -1) return - const s = 80 - const sx = clamp(idx % 3 + Math.round(x / s), 0, 2) - const sy = clamp(Math.floor(idx / 3) + Math.round(y / s), 0, 2) - setText(origins[sy * 3 + sx]) - }, - }), - // url clicker - interactRule.of({ - regexp: /https?:\/\/[^ "]+/g, - cursor: "pointer", - onClick: (text) => { - window.open(text) - }, - }), - drop, - dropRule.of({ - kind: "data", - format: "code", - }), - dropRule.of({ - kind: "file", - accept: /^image\//, - readAs: "dataURL", - process: (data) => { - if (typeof data === "string") { - return `"${data}"` - } - }, - }), - dropRule.of({ - kind: "file", - accept: /^text\//, - readAs: "text", - }), - img, - ].filter((ext) => ext), - }), - }) - - setView(view) - - return () => view.destroy() - - }, []) - - useUpdateEffect(() => { - if (!view) return - view.dispatch({ - changes: { - from: 0, - to: view.state.doc.length, - insert: (typeof content === "function" ? content() : content) ?? "", - }, - }) - }, [ content ]) - - useUpdateEffect(() => { - // TODO: not working - view?.dispatch({ - effects: themeConf.reconfigure(cmThemes[theme][0]), - }) - view?.dispatch({ - effects: hlConf.reconfigure(syntaxHighlighting(cmThemes[theme][1])), - }) - }, [ theme ]) - - return ( - <View - ref={editorDOMRef} - bg={2} - css={{ - fontFamily: "IBM Plex Mono", - overflow: "hidden", - fontSize: "var(--text-normal)", - ":focus-within": { - boxShadow: "0 0 0 2px var(--color-highlight)", - }, - ".cm-editor": { - userSelect: "auto", - width: "100% !important", - height: "100% !important", - }, - ".cm-editor > *": { - fontFamily: "inherit !important", - }, - }} - {...args} - /> - ) -}) - -export default Editor diff --git a/site/comps/GameView.tsx b/site/comps/GameView.tsx deleted file mode 100644 index 377af9037..000000000 --- a/site/comps/GameView.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import * as React from "react" -import { cssVars } from "lib/ui" -import View, { ViewProps } from "comps/View" -import Ctx from "lib/Ctx" -import { themes } from "lib/ui" -import useUpdateEffect from "hooks/useUpdateEffect" - -export interface GameViewRef { - run: (code: string) => void, - send: (msg: any, origin?: string) => void, -} - -const wrapGame = (code: string) => ` -<!DOCTYPE html> -<head> - <style> - ${cssVars} - * { - margin: 0; - padding: 0; - box-sizing: border-box; - } - body, - html { - width: 100%; - height: 100%; - } - body { - background: var(--color-bg2); - } - </style> -</head> -<body> - <script src="/dist/kaboom.js"></script> - <script> -${code} - </script> -</body> -` - -interface GameViewProps { - code: string, - onLoad?: () => void, -} - -const GameView = React.forwardRef<GameViewRef, GameViewProps & ViewProps>(({ - code, - onLoad, - ...args -}, ref) => { - - const iframeRef = React.useRef<HTMLIFrameElement>(null) - const { theme } = React.useContext(Ctx) - - React.useImperativeHandle(ref, () => ({ - run(code: string, msg?: any) { - if (!iframeRef.current) return - const iframe = iframeRef.current - iframe.srcdoc = wrapGame(code) - }, - send(msg: any, origin: string = "*") { - if (!iframeRef.current) return - const iframe = iframeRef.current - iframe.contentWindow?.postMessage(JSON.stringify(msg), origin) - }, - })) - - useUpdateEffect(() => { - if (!iframeRef.current) return - const iframe = iframeRef.current - iframe.srcdoc = wrapGame(code) - }, [ code ]) - - React.useEffect(() => { - const body = iframeRef.current?.contentWindow?.document?.body - if (body) { - body.className = theme - } - }, [ theme ]) - - return ( - <View {...args} css={{ - overflow: "hidden", - }}> - <iframe - ref={iframeRef} - tabIndex={0} - onLoad={onLoad} - css={{ - border: "none", - background: "var(--background-bg2)", - width: "100%", - height: "100%", - }} - srcDoc={wrapGame(code ?? "")} - /> - </View> - ) - -}) - -export default GameView diff --git a/site/comps/Head.tsx b/site/comps/Head.tsx deleted file mode 100644 index 5440ae521..000000000 --- a/site/comps/Head.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import NextHead from "next/head" - -interface TwitterPlayer { - width: number, - height: number, - url: string, -} - -interface HeadProps { - title?: string, - desc?: string, - icon?: string, - img?: string, - scale?: number, - twitterPlayer?: TwitterPlayer, -} - -const DEF_TITLE = "Kaboom" -const DEF_DESC = "JavaScript game programming library that helps you make games fast and fun" -const DEF_ICON = "/static/img/k.png" -const DEF_IMG = "https://kaboomjs.com/static/img/k.png" - -const Head: React.FC<HeadProps> = ({ - title, - desc, - icon, - img, - scale, - twitterPlayer, -}) => ( - <NextHead> - <title>{title ?? DEF_TITLE} - - - - - - - - - { twitterPlayer && <> - - - - } - -) - -export default Head diff --git a/site/comps/Input.tsx b/site/comps/Input.tsx deleted file mode 100644 index 77da5492a..000000000 --- a/site/comps/Input.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import * as React from "react" -import View from "comps/View" -import Text from "comps/Text" - -interface InputProps { - value?: string, - onChange?: (txt: string) => void, - placeholder?: string, -} - -const Input: React.FC = ({ - value, - onChange, - placeholder, - ...args -}) => { - const [ content, setContent ] = React.useState(value ?? "") - return ( - { - onChange && onChange(e.currentTarget.value) - setContent(e.currentTarget.value) - }} - placeholder={placeholder ?? ""} - css={{ - fontSize: "var(--text-normal)", - userSelect: "auto", - background: "var(--color-bg3)", - borderRadius: 8, - boxShadow: "0 0 0 2px var(--color-outline)", - border: "none", - padding: 8, - width: "100%", - color: "var(--color-fg1)", - "::placeholder": { - color: "var(--color-fg3)", - }, - ":focus": { - boxShadow: "0 0 0 2px var(--color-highlight)", - }, - }} - {...args} - /> - ) -} - -export default Input diff --git a/site/comps/Inspect.tsx b/site/comps/Inspect.tsx deleted file mode 100644 index 4310459f5..000000000 --- a/site/comps/Inspect.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import * as React from "react" -import View from "comps/View" -import Text from "comps/Text" -import Ctx from "lib/Ctx" - -const Inspect: React.FC = ({ - ...args -}) => { - - const { inspect, setInspect } = React.useContext(Ctx) - - return setInspect(!inspect)} - bg={3} - align="center" - justify="center" - outlined - width={32} - height={32} - css={{ - fontSize: "var(--text-normal)", - cursor: "pointer", - userSelect: "none", - borderRadius: 16, - ":hover": { - background: "var(--color-bg4)", - }, - ":active": { - background: "var(--color-outline)", - }, - }} - {...args} - > - ? - - -} - -export default Inspect diff --git a/site/comps/Markdown.tsx b/site/comps/Markdown.tsx deleted file mode 100644 index a37a40012..000000000 --- a/site/comps/Markdown.tsx +++ /dev/null @@ -1,193 +0,0 @@ -// TODO: use react-markdown - -import * as React from "react" -import { Marked } from "marked" -import { markedHighlight } from "marked-highlight" -import * as markedBaseUrl from "marked-base-url" -import hljs from "highlight.js/lib/core" -import javascript from "highlight.js/lib/languages/javascript" -import typescript from "highlight.js/lib/languages/typescript" -import xml from "highlight.js/lib/languages/xml" -import shell from "highlight.js/lib/languages/shell" -import bash from "highlight.js/lib/languages/bash" -import View, { ViewProps } from "comps/View" - -hljs.registerLanguage("javascript", javascript) -hljs.registerLanguage("typescript", typescript) -hljs.registerLanguage("xml", xml) -hljs.registerLanguage("shell", shell) -hljs.registerLanguage("bash", bash) - -interface MarkdownProps { - src: string, - baseUrl?: string, - dim?: boolean, -} - -const Markdown: React.FC = ({ - src, - baseUrl, - dim, - ...args -}) => { - const [marked] = React.useState(() => { - const marked = new Marked() - if (baseUrl) { - marked.use(markedBaseUrl.baseUrl(baseUrl)) - } - marked.use(markedHighlight({ - highlight(code, lang) { - const language = hljs.getLanguage(lang) ? lang : "plaintext" - return hljs.highlight(code, { language }).value - }, - })) - return marked - }) - const html = React.useMemo(() => { - const res = marked.parse(src) - if (res instanceof Promise) throw new Error("wtf???") - return res - }, [src, marked]) - return ( - code": { - padding: "2px 6px", - borderRadius: 8, - background: "var(--color-bg2)", - }, - "blockquote *": { - fontStyle: "italic", - color: "var(--color-fg3)", - }, - // dim - [[ - ".hljs-comment", - ".hljs-quote", - ].join(",")]: { - color: "var(--color-fg4)", - }, - // red - [[ - ".hljs-variable", - ".hljs-template-variable", - ".hljs-tag", - ".hljs-name", - ".hljs-selector-id", - ".hljs-selector-class", - ".hljs-regexp", - ".hljs-link", - ".hljs-meta", - ].join(",")]: { - color: "#ef6155", - }, - // orange - [[ - ".hljs-number", - ".hljs-built_in", - ".hljs-builtin-name", - ".hljs-literal", - ".hljs-type", - ".hljs-params", - ".hljs-deletion", - ].join(",")]: { - color: "#f99b15", - }, - // yellow - [[ - ".hljs-section", - ".hljs-attribute", - ].join(",")]: { - color: "#fec418", - }, - [[ - ".hljs-string", - ".hljs-symbol", - ".hljs-bullet", - ".hljs-addition", - ].join(",")]: { - color: "#48b685", - }, - // purple - [[ - ".hljs-keyword", - ".hljs-selector-tag", - ].join(",")]: { - color: "#815ba4", - }, - ".hljs-emphasis": { - fontStyle: "italic", - }, - ".hljs-strong": { - fontWeight: "bold", - }, - }} - {...args} - />) -} - -export default Markdown diff --git a/site/comps/Menu.tsx b/site/comps/Menu.tsx deleted file mode 100644 index e04e4147f..000000000 --- a/site/comps/Menu.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import * as React from "react" -import View from "comps/View" -import Text from "comps/Text" -import useClickOutside from "hooks/useClickOutside" -import useKey from "hooks/useKey" - -export interface MenuItem { - name: string, - action?: () => void, - danger?: boolean, -} - -interface MenuProps { - items: MenuItem[], - left?: boolean, -} - -// TODO: squeeze to left if no space -const Menu: React.FC = ({ - items, - left, -}) => { - - const domRef = React.useRef(null) - const [ expanded, setExpanded ] = React.useState(false) - - useClickOutside(domRef, () => setExpanded(false), [ setExpanded ]) - useKey("Escape", () => setExpanded(false), [ setExpanded ]) - - return ( - setExpanded(!expanded)} - > - - ... - - {expanded && - {items.length > 0 && items.map((item) => ( - item.action && item.action()} - > - {item.name} - - ))} - } - - ) -} - -export default Menu diff --git a/site/comps/Modal.tsx b/site/comps/Modal.tsx deleted file mode 100644 index dfa666979..000000000 --- a/site/comps/Modal.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import * as React from "react" -import View from "comps/View" - -const Modal = ({ - isOpen, - close, - children, -}: { - isOpen: boolean, - close: () => void, - children?: React.ReactNode, -}) => isOpen ? ( - <> - - - - {children} - - -) : (<>) - -export default Modal diff --git a/site/comps/Nav.tsx b/site/comps/Nav.tsx deleted file mode 100644 index 4e5a1bbba..000000000 --- a/site/comps/Nav.tsx +++ /dev/null @@ -1,273 +0,0 @@ -import * as React from "react" -import Link from "next/link" -import Image from "next/image" -import { keyframes } from "@emotion/react" - -import useMediaQuery from "hooks/useMediaQuery" -import useClickOutside from "hooks/useClickOutside" -import useUpdateEffect from "hooks/useUpdateEffect" -import Background from "comps/Background" -import View from "comps/View" -import Text from "comps/Text" -import Markdown from "comps/Markdown" -import Input from "comps/Input" -import Drawer from "comps/Drawer" -import ThemeSwitch from "comps/ThemeSwitch" -import doc from "doc.json" - -const popping = keyframes(` - 0% { - transform: scale(1); - } - 5% { - transform: scale(1.1); - } - 10% { - transform: scale(1); - } -`) - -const Logo: React.FC = () => ( - - - boom - ka - - -) - -interface NavLinkProps { - text: string, - link: string, -} - -const NavLink: React.FC = ({ - text, - link, -}) => ( - - *": { - color: "var(--color-fghl) !important", - }, - }, - }} - > - {text} - - -) - -const NARROW = 840 -const MOBILE = 640 - -const Index: React.FC = () => { - - const isNarrow = useMediaQuery(`(max-width: ${NARROW}px)`) - const [ expanded, setExpanded ] = React.useState(false) - - useUpdateEffect(() => { - setExpanded(!isNarrow) - }, [ isNarrow ]) - - return isNarrow ? ( - - setExpanded(false)} /> - - ) : ( - - setExpanded(false)} /> - - ) - -} - -type SectionTuple = [string, string[]] - -interface IndexContentProps { - shrink: () => void, -} - -const IndexContent: React.FC = ({ - shrink, -}) => { - - const [ query, setQuery ] = React.useState("") - - const filteredSections = doc.sections.reduce((acc: SectionTuple[], cur) => { - const filteredEntries = cur.entries - .filter(name => query ? name.match(new RegExp(query, "i")) : true) - - // Exclude sections that have no matching entries. - return filteredEntries.length > 0 - ? acc.concat([[cur.name, filteredEntries]]) - : acc - }, []) - - return <> - - - - - - - - - - - - - - - - - { filteredSections.map(([sectionName, entries]) => { - - return ( - - {sectionName} - - { entries.map((name) => { - - const mem = (doc as any).types["KaboomCtx"][0].members[name]?.[0] || (doc as any).types[name]?.[0] - - if (mem.jsDoc?.tags["deprecated"]) { - return - } - - const isFunc = mem.kind === "MethodSignature" || mem.kind === "FunctionDeclaration" - - return ( - - - {name}{isFunc ? "()" : ""} - - - ) - - }) } - - - ) - - }) } - - - -} - -const Nav = ({ - children, -}: { - children?: React.ReactNode, -}) => ( - - - - - {children} - - - -) - -export default Nav diff --git a/site/comps/Page.tsx b/site/comps/Page.tsx deleted file mode 100644 index f87a097c0..000000000 --- a/site/comps/Page.tsx +++ /dev/null @@ -1,211 +0,0 @@ -import * as React from "react" -import { Global, css } from "@emotion/react" -import Ctx from "lib/Ctx" -import { Tooltip } from "lib/tooltip" -import View from "comps/View" -import Button from "comps/Button" -import Text from "comps/Text" -import { DEF_THEME, themes, cssVars } from "lib/ui" -import useMousePos from "hooks/useMousePos" -import useSavedState from "hooks/useSavedState" -import useKey from "hooks/useKey" -import IDList from "lib/idlist" - -const Page = ({ - children, -}: { - children?: React.ReactNode, -}) => { - - const [ theme, setTheme ] = useSavedState("theme", DEF_THEME) - const [ inspect, setInspect ] = React.useState(false) - const [ tooltipStack, setTooltipStack ] = React.useState>(new IDList()) - - // make sure theme from local storage don't break anything - const curTheme = React.useMemo(() => { - if (!themes[theme]) { - setTheme(DEF_THEME) - return DEF_THEME - } - return theme - }, [ theme, setTheme ]) - - const curTooltip = React.useMemo( - () => tooltipStack.size === 0 - ? null - : Array.from(tooltipStack.values())[tooltipStack.size - 1], - [ tooltipStack ], - ) - - useKey("F1", () => setInspect(!inspect), [ setInspect, inspect ]) - useKey("Escape", () => setInspect(false), [ setInspect ]) - - // reset tooltip stack when exiting inspect mode - React.useEffect(() => { - if (!inspect) { - setTooltipStack(new IDList()) - } - }, [ inspect ]) - - // push a tooltip into tooltip stack, returning the id - const pushTooltip = React.useCallback((t: Tooltip) => { - return new Promise((resolve) => { - setTooltipStack((prevStack) => { - // if it's already the current tooltip, we just return that - const last = Array.from(prevStack)[prevStack.size - 1] - if (last && t.name === last[1].name && t.desc === last[1].desc) { - resolve(last[0]) - return prevStack - } - const newStack = prevStack.clone() - const id = newStack.push(t) - resolve(id) - return newStack - }) - }) - }, [ setTooltipStack ]) - - // pop a tooltip from tooltip stack with id - const popTooltip = React.useCallback((id: number) => { - setTooltipStack((prevStack) => { - const newStack = prevStack.clone() - newStack.delete(id) - return newStack - }) - }, [ setTooltipStack ]) - - return ( - - -
- {children} -
- { inspect && -