From a4c9e89370ddb80ec4cf8f9560c585b5b61cc7dc Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Thu, 12 Nov 2020 19:01:50 +0000 Subject: [PATCH] Runnable examples (#576) Provides a basic interface for running examples on tweaked.cc. This is probably janky as anything, but it works on my machine. This is the culmination of 18 months of me building far too much infrastructure (copy-cat, illuaminate), so that's nice I guess. I should probably get out more. --- .editorconfig | 1 - .github/workflows/make-doc.sh | 2 +- .github/workflows/make-doc.yml | 18 ++-- .gitignore | 10 +- build.gradle | 52 +++++++++- doc/styles.css | 14 --- illuaminate.sexp | 24 +++-- package-lock.json | 172 ++++++++++++++++++++++++++++++++ package.json | 18 ++++ rollup.config.js | 49 +++++++++ src/web/copy-cat.d.ts | 21 ++++ src/web/index.tsx | 155 ++++++++++++++++++++++++++++ src/web/mount/.settings | 3 + src/web/mount/expr_template.lua | 14 +++ src/web/mount/startup.lua | 5 + src/web/styles.css | 85 ++++++++++++++++ tools/check-lines.py | 2 +- tsconfig.json | 34 +++++++ 18 files changed, 639 insertions(+), 40 deletions(-) delete mode 100644 doc/styles.css create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 rollup.config.js create mode 100644 src/web/copy-cat.d.ts create mode 100644 src/web/index.tsx create mode 100644 src/web/mount/.settings create mode 100644 src/web/mount/expr_template.lua create mode 100644 src/web/mount/startup.lua create mode 100644 src/web/styles.css create mode 100644 tsconfig.json diff --git a/.editorconfig b/.editorconfig index d57b269a4..f5f23d8d4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -17,6 +17,5 @@ indent_size = 2 [*.yml] indent_size = 2 - [*.properties] insert_final_newline = false diff --git a/.github/workflows/make-doc.sh b/.github/workflows/make-doc.sh index f7446f84c..f86ef60f0 100755 --- a/.github/workflows/make-doc.sh +++ b/.github/workflows/make-doc.sh @@ -12,5 +12,5 @@ chmod 600 "$HOME/.ssh/key" # And upload rsync -avc -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no -p $SSH_PORT" \ - "$GITHUB_WORKSPACE/doc/out/" \ + "$GITHUB_WORKSPACE/build/docs/lua/" \ "$SSH_USER@$SSH_HOST:/var/www/tweaked.cc/$DEST" diff --git a/.github/workflows/make-doc.yml b/.github/workflows/make-doc.yml index 3abf5d265..18ce815a5 100644 --- a/.github/workflows/make-doc.yml +++ b/.github/workflows/make-doc.yml @@ -30,18 +30,20 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- - - name: Build with Gradle - run: ./gradlew compileJava --no-daemon || ./gradlew compileJava --no-daemon - - - name: Generate Java documentation stubs - run: ./gradlew luaJavadoc --no-daemon - - - name: Build documentation + - name: Setup illuaminate run: | test -d bin || mkdir bin test -f bin/illuaminate || wget -q -Obin/illuaminate https://squiddev.cc/illuaminate/linux-x86-64/illuaminate chmod +x bin/illuaminate - bin/illuaminate doc-gen + + - name: Setup node + run: npm ci + + - name: Build with Gradle + run: ./gradlew compileJava --no-daemon || ./gradlew compileJava --no-daemon + + - name: Generate documentation + run: ./gradlew docWebsite --no-daemon - name: Upload documentation run: .github/workflows/make-doc.sh 2> /dev/null diff --git a/.gitignore b/.gitignore index 84f17aee8..808032e6d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ /build /out /doc/out/ -/doc/javadoc/ +/node_modules # Runtime directories /run @@ -18,10 +18,12 @@ .gradle *.DS_Store -.classpath -.project -.settings/ +/.classpath +/.project +/.settings +/.vscode bin/ *.launch /src/generated/resources/.cache +/src/web/mount/*.d.ts diff --git a/build.gradle b/build.gradle index 33a414ab9..5c36a17fd 100644 --- a/build.gradle +++ b/build.gradle @@ -136,7 +136,7 @@ task luaJavadoc(type: Javadoc) { group "documentation" source = sourceSets.main.allJava - destinationDir = file("doc/javadoc") + destinationDir = file("${project.docsDir}/luaJavadoc") classpath = sourceSets.main.compileClasspath options.docletpath = configurations.cctJavadoc.files as List @@ -306,6 +306,56 @@ task compressJson(dependsOn: jar) { assemble.dependsOn compressJson +// Web tasks + +import org.apache.tools.ant.taskdefs.condition.Os + +List mkCommand(String command) { + return Os.isFamily(Os.FAMILY_WINDOWS) ? ["cmd", "/c", command] : ["sh", "-c", command] +} + +task rollup(type: Exec) { + group = "build" + description = "Bundles JS into rollup" + + inputs.files(fileTree("src/web")).withPropertyName("sources") + inputs.file("package-lock.json").withPropertyName("package-lock.json") + inputs.file("tsconfig.json").withPropertyName("Typescript config") + inputs.file("rollup.config.js").withPropertyName("Rollup config") + outputs.file("$buildDir/rollup/index.js").withPropertyName("output") + + commandLine mkCommand('"node_modules/.bin/rollup" --config rollup.config.js') +} + +task minifyWeb(type: Exec, dependsOn: rollup) { + group = "build" + description = "Bundles JS into rollup" + + inputs.file("$buildDir/rollup/index.js").withPropertyName("sources") + inputs.file("package-lock.json").withPropertyName("package-lock.json") + outputs.file("$buildDir/rollup/index.min.js").withPropertyName("output") + + commandLine mkCommand('"node_modules/.bin/terser"' + " -o $buildDir/rollup/index.min.js $buildDir/rollup/index.js") +} + +task illuaminateDocs(type: Exec, dependsOn: [minifyWeb, luaJavadoc]) { + group = "build" + description = "Bundles JS into rollup" + + inputs.files(fileTree("doc")).withPropertyName("sources") + inputs.file("illuaminate.sexp").withPropertyName("illuaminate.sexp") + inputs.file("$buildDir/rollup/index.min.js").withPropertyName("scripts") + inputs.file("src/web/styles.css").withPropertyName("styles") + outputs.dir("$buildDir/docs/lua") + + commandLine mkCommand('"bin/illuaminate" doc-gen') +} + +task docWebsite(type: Copy, dependsOn: [illuaminateDocs]) { + from 'doc/logo.png' + into "${project.docsDir}/lua" +} + // Check tasks test { diff --git a/doc/styles.css b/doc/styles.css deleted file mode 100644 index a21b9ac34..000000000 --- a/doc/styles.css +++ /dev/null @@ -1,14 +0,0 @@ -/* Pretty tables, mostly inherited from table.definition-list */ -table.pretty-table { - border-collapse: collapse; - width: 100%; -} - -table.pretty-table td, table.pretty-table th { - border: 1px solid #cccccc; - padding: 2px 4px; -} - -table.pretty-table th { - background-color: #f0f0f0; -} diff --git a/illuaminate.sexp b/illuaminate.sexp index 558028975..80a481a64 100644 --- a/illuaminate.sexp +++ b/illuaminate.sexp @@ -2,18 +2,20 @@ (sources /doc/stub/ - /doc/javadoc/ + /build/docs/luaJavadoc/ /src/main/resources/*/computercraft/lua/bios.lua /src/main/resources/*/computercraft/lua/rom/ - /src/test/resources/test-rom) + /src/test/resources/test-rom + /src/web/mount) (doc (title "CC: Tweaked") - (destination doc/out) + (destination build/docs/lua) (logo src/main/resources/pack.png) (index doc/index.md) - (styles doc/styles.css) + (styles src/web/styles.css) + (scripts build/rollup/index.js) (source-link https://github.com/SquidDev-CC/CC-Tweaked/blob/${commit}/${path}#L${line}) (module-kinds @@ -21,7 +23,7 @@ (library-path /doc/stub/ - /doc/javadoc/ + /build/docs/luaJavadoc/ /src/main/resources/*/computercraft/lua/rom/apis /src/main/resources/*/computercraft/lua/rom/apis/command @@ -72,7 +74,7 @@ (lint (allow-toplevel-global true))) ;; Silence some variable warnings in documentation stubs. -(at (/doc/stub/ /doc/javadoc/) +(at (/doc/stub/ /build/docs/luaJavadoc/) (linters -var:unused-global) (lint (allow-toplevel-global true))) @@ -84,11 +86,11 @@ /doc/stub/turtle.lua /doc/stub/global.lua ; Java generated APIs - /doc/javadoc/turtle.lua + /build/docs/luaJavadoc/turtle.lua ; Peripherals - /doc/javadoc/drive.lua - /doc/javadoc/speaker.lua - /doc/javadoc/printer.lua + /build/docs/luaJavadoc/drive.lua + /build/docs/luaJavadoc/speaker.lua + /build/docs/luaJavadoc/printer.lua ; Lua APIs /src/main/resources/*/computercraft/lua/rom/apis/io.lua /src/main/resources/*/computercraft/lua/rom/apis/window.lua) @@ -116,3 +118,5 @@ (globals :max sleep write cct_test describe expect howlci fail it pending stub))) + +(at /src/web/mount/expr_template.lua (lint (globals :max __expr__))) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..b12022dac --- /dev/null +++ b/package-lock.json @@ -0,0 +1,172 @@ +{ + "name": "tweaked.cc", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@rollup/plugin-typescript": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-6.1.0.tgz", + "integrity": "sha512-hJxaiE6WyNOsK+fZpbFh9CUijZYqPQuAOWO5khaGTUkM8DYNNyA2TDlgamecE+qLOG1G1+CwbWMAx3rbqpp6xQ==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.1.0", + "resolve": "^1.17.0" + } + }, + "@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "requires": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + } + }, + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "is-core-module": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.1.0.tgz", + "integrity": "sha512-YcV7BgVMRFRua2FqQzKtTDMz8iCuLEyGKjr70q8Zm1yy2qKcurbFEd79PAdHV77oL3NrAaOVQIbMmiHQCHB7ZA==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "preact": { + "version": "10.5.5", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.5.5.tgz", + "integrity": "sha512-5ONLNH1SXMzzbQoExZX4TELemNt+TEDb622xXFNfZngjjM9qtrzseJt+EfiUu4TZ6EJ95X5sE1ES4yqHFSIdhg==" + }, + "requirejs": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", + "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==", + "dev": true + }, + "resolve": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", + "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", + "dev": true, + "requires": { + "is-core-module": "^2.0.0", + "path-parse": "^1.0.6" + } + }, + "rollup": { + "version": "2.33.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.33.1.tgz", + "integrity": "sha512-uY4O/IoL9oNW8MMcbA5hcOaz6tZTMIh7qJHx/tzIJm+n1wLoY38BLn6fuy7DhR57oNFLMbDQtDeJoFURt5933w==", + "dev": true, + "requires": { + "fsevents": "~2.1.2" + } + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "terser": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.3.8.tgz", + "integrity": "sha512-zVotuHoIfnYjtlurOouTazciEfL7V38QMAOhGqpXDEg6yT13cF4+fEP9b0rrCEQTn+tT46uxgFsTZzhygk+CzQ==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.19" + } + }, + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + }, + "typescript": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz", + "integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..682f93de0 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "tweaked.cc", + "version": "1.0.0", + "description": "Website additions for tweaked.cc", + "author": "SquidDev", + "license": "BSD-3-Clause", + "dependencies": { + "preact": "^10.5.5", + "tslib": "^2.0.3" + }, + "devDependencies": { + "@rollup/plugin-typescript": "^6.1.0", + "requirejs": "^2.3.6", + "rollup": "^2.33.1", + "terser": "^5.3.8", + "typescript": "^4.0.5" + } +} diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 000000000..1e0342fea --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,49 @@ +import { readFileSync, promises as fs } from "fs"; +import path from "path"; + +import typescript from "@rollup/plugin-typescript"; + +const input = "src/web"; +const requirejs = readFileSync("node_modules/requirejs/require.js"); + +export default { + input: [`${input}/index.tsx`], + output: { + file: "build/rollup/index.js", + // We bundle requirejs (and config) into the header. It's rather gross + // but also works reasonably well. + banner: `${requirejs}\nrequire.config({ paths: { copycat: "https://copy-cat.squiddev.cc" } });`, + format: "amd", + preferConst: true, + amd: { + define: "require", + } + }, + context: "window", + external: ["copycat/embed"], + + plugins: [ + typescript(), + + { + name: "cc-tweaked", + async options(options) { + // Generate .d.ts files for all /mount files. This is the worst way to do it, + // but we need to run before the TS pass. + const template = "declare const contents : string;\nexport default contents;\n"; + const files = await fs.readdir(`${input}/mount`); + + await Promise.all(files + .filter(x => path.extname(x) !== ".ts") + .map(file => fs.writeFile(`${input}/mount/${file}.d.ts`, template)) + ); + return options; + }, + async transform(code, file) { + // Allow loading files in /mount. + if (path.extname(file) != ".lua" && path.basename(file) != ".settings") return null; + return `export default ${JSON.stringify(code)};\n`; + }, + } + ], +}; diff --git a/src/web/copy-cat.d.ts b/src/web/copy-cat.d.ts new file mode 100644 index 000000000..b4a85d267 --- /dev/null +++ b/src/web/copy-cat.d.ts @@ -0,0 +1,21 @@ +import { h, Component, render, ComponentChild } from "preact"; + +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, +} + +declare class Computer extends Component { + public render(props: MainProps, state: unknown): ComponentChild; +} + +export { Computer }; diff --git a/src/web/index.tsx b/src/web/index.tsx new file mode 100644 index 000000000..0bf476a86 --- /dev/null +++ b/src/web/index.tsx @@ -0,0 +1,155 @@ +import { render, h, Component, Computer } 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"; + +const defaultFiles: { [filename: string]: string } = { + ".settings": settingsFile, + "startup.lua": startupFile, +}; + +const clamp = (value: number, min: number, max: number): number => { + if (value < min) return min; + if (value > max) return max; + return value; +} + +const Click = (options: { run: () => void }) => + + +type WindowProps = {}; + +type WindowState = { + visible: boolean, + + example: string, + exampleIdx: number, +} + +type Touch = { clientX: number, clientY: number }; + +class Window extends Component { + private positioned: boolean = false; + private left: number = 0; + private top: number = 0; + private dragging?: { downX: number, downY: number, initialX: number, initialY: number }; + + constructor(props: WindowProps, context: unknown) { + super(props, context); + + this.state = { + visible: false, + example: "", + 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; + if (element.getAttribute("data-lua-kind") == "expr") { + example = exprTemplate.replace("__expr__", example); + } + render(, 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 ?
+
+
+ +
+
+ +
+
:
; + } + + private runExample(example: string): () => void { + return () => { + if (!this.positioned) { + this.positioned = true; + this.left = 20; + this.top = 20; + } + + this.setState(({ exampleIdx }: WindowState) => ({ + visible: true, + example: example, + 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(, document.body, root); diff --git a/src/web/mount/.settings b/src/web/mount/.settings new file mode 100644 index 000000000..5b9849575 --- /dev/null +++ b/src/web/mount/.settings @@ -0,0 +1,3 @@ +{ + [ "motd.enable" ] = false, +} diff --git a/src/web/mount/expr_template.lua b/src/web/mount/expr_template.lua new file mode 100644 index 000000000..37faf4180 --- /dev/null +++ b/src/web/mount/expr_template.lua @@ -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))) diff --git a/src/web/mount/startup.lua b/src/web/mount/startup.lua new file mode 100644 index 000000000..91bbf535f --- /dev/null +++ b/src/web/mount/startup.lua @@ -0,0 +1,5 @@ +-- 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) diff --git a/src/web/styles.css b/src/web/styles.css new file mode 100644 index 000000000..e60e670ee --- /dev/null +++ b/src/web/styles.css @@ -0,0 +1,85 @@ +/* Pretty tables, mostly inherited from table.definition-list */ +table.pretty-table { + border-collapse: collapse; + width: 100%; +} + +table.pretty-table td, table.pretty-table th { + border: 1px solid #cccccc; + padding: 2px 4px; +} + +table.pretty-table th { + background-color: #f0f0f0; +} + +.highlight.highlight-lua { + position: relative; + background: #eee; + padding: 2px; +} + +.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; } +} diff --git a/tools/check-lines.py b/tools/check-lines.py index 3659924ae..47ff15fc9 100644 --- a/tools/check-lines.py +++ b/tools/check-lines.py @@ -18,7 +18,7 @@ for path in pathlib.Path("src").glob("**/*"): if line.strip() == "": print("%s has empty first line" % path) - if len(line) >= 2 and line[-2] == "\r" and line[-1] == "\n" and not has_line: + if len(line) >= 2 and line[-2] == "\r" and line[-1] == "\n" and not has_dos: print("%s has contains '\\r\\n' on line %d" % (path, i + 1)) problems = has_dos = True diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..b6dc1da3f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "module": "esNext", + "moduleResolution": "node", + "target": "es6", + "lib": [ + "es2015", + "dom" + ], + "newLine": "LF", + "baseUrl": ".", + // Additional compile options + "noEmitOnError": true, + "preserveWatchOutput": true, + "jsx": "react", + "jsxFactory": "h", + // Strict Type-Checking Options + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "importsNotUsedAsValues": "error", + "forceConsistentCasingInFileNames": true, + "paths": { + "copycat/embed": [ + "src/web/copy-cat.d.ts" + ], + } + }, + "include": [ + "src/web", + ] +}