we typescript now
This commit is contained in:
parent
a8d2ed380e
commit
d7d814015f
14 changed files with 5147 additions and 106 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -3,3 +3,5 @@
|
||||||
.env
|
.env
|
||||||
.vscode
|
.vscode
|
||||||
/result
|
/result
|
||||||
|
/static/scripts/**/*.js*
|
||||||
|
/static/components/**/*.js*
|
12
.swcrc
Normal file
12
.swcrc
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"sourceMaps": true,
|
||||||
|
"jsc": {
|
||||||
|
"parser": {
|
||||||
|
"syntax": "typescript",
|
||||||
|
"tsx": false,
|
||||||
|
"decorators": false,
|
||||||
|
"dynamicImport": false
|
||||||
|
},
|
||||||
|
"target": "esnext"
|
||||||
|
}
|
||||||
|
}
|
1846
Cargo.lock
generated
1846
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -25,3 +25,8 @@ time = { version = "0.3", features = ["serde"] }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
swc = "0.269"
|
||||||
|
swc_common = { version = "0.33", features = ["tty-emitter"] }
|
||||||
|
glob = "0.3"
|
||||||
|
|
61
build.rs
Normal file
61
build.rs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
use std::{
|
||||||
|
fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use swc::config::{Options, SourceMapsConfig};
|
||||||
|
use swc_common::{
|
||||||
|
self,
|
||||||
|
errors::{ColorConfig, Handler},
|
||||||
|
sync::Lrc,
|
||||||
|
SourceMap, GLOBALS,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Find all Typescript files (that are not type declaration)
|
||||||
|
let ts_files: Vec<_> = glob::glob(&format!("{}/**/*.ts", "static/source"))
|
||||||
|
.expect("Failed to read glob pattern")
|
||||||
|
.filter_map(|entry| entry.ok())
|
||||||
|
.filter(|entry| !entry.to_string_lossy().ends_with(".d.ts"))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for ts_file in ts_files {
|
||||||
|
compile_file(ts_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_file(input: PathBuf) {
|
||||||
|
let cm: Lrc<SourceMap> = Default::default();
|
||||||
|
let handler = Handler::with_tty_emitter(ColorConfig::Auto, true, false, Some(cm.clone()));
|
||||||
|
|
||||||
|
let c = swc::Compiler::new(cm.clone());
|
||||||
|
|
||||||
|
let fm = cm
|
||||||
|
.load_file(Path::new(&input))
|
||||||
|
.expect("failed to load input typescript file");
|
||||||
|
|
||||||
|
GLOBALS.set(&Default::default(), || {
|
||||||
|
let output_path = input.with_extension("js");
|
||||||
|
let sourcemap_path = input.with_extension("js.map");
|
||||||
|
|
||||||
|
let res = c
|
||||||
|
.process_js_file(
|
||||||
|
fm,
|
||||||
|
&handler,
|
||||||
|
&Options {
|
||||||
|
swcrc: true,
|
||||||
|
filename: input.to_string_lossy().into_owned(),
|
||||||
|
output_path: Some(output_path.clone()),
|
||||||
|
source_maps: Some(SourceMapsConfig::Bool(true)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("parse error");
|
||||||
|
|
||||||
|
fs::create_dir_all(output_path.parent().unwrap()).expect("failed to create parent dir");
|
||||||
|
fs::write(output_path, res.code).expect("failed to write output file");
|
||||||
|
if let Some(map) = res.map {
|
||||||
|
fs::write(sourcemap_path, map).expect("failed to write output file");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,47 +0,0 @@
|
||||||
/**
|
|
||||||
* Checks if a specificied source is a valid arion-compose.nix file
|
|
||||||
* @param {string} source Source to check
|
|
||||||
* @returns {Promise<CheckResult>} Result of the check
|
|
||||||
*/
|
|
||||||
export async function check_compose(source) {
|
|
||||||
const response = await fetch("/stack/_/check", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Accept": "application/json",
|
|
||||||
"Content-Type": "text/plain",
|
|
||||||
},
|
|
||||||
body: source
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
return { ok: true };
|
|
||||||
} else {
|
|
||||||
return { ok: false, error: await response.json() };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} APIError
|
|
||||||
* @property {string} code - Error code
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} CheckSuccess
|
|
||||||
* @property { true } ok - Indicates that the check was successful
|
|
||||||
* /
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} APIError
|
|
||||||
* @property {string} code - Unique error code
|
|
||||||
* @property {string} message - Human readable error details
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} CheckFailure
|
|
||||||
* @property {false} ok - Indicates that the check has failed
|
|
||||||
* @property {APIError} error - Info about the encountered error
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {CheckSuccess | CheckFailure} CheckResult
|
|
||||||
*/
|
|
|
@ -1,17 +0,0 @@
|
||||||
|
|
||||||
/**
|
|
||||||
* Find nearest parent that satisfies a requirement
|
|
||||||
* @param {Node|Element} node
|
|
||||||
* @param {(el: Element) => boolean} findFn
|
|
||||||
* @returns {HTMLElement | null} Found element, or null if no parent matches
|
|
||||||
*/
|
|
||||||
export function findNearestParent(node, findFn) {
|
|
||||||
let parent = node.parentElement;
|
|
||||||
while (parent) {
|
|
||||||
if (findFn(parent)) {
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
parent = parent.parentElement;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
|
@ -1,24 +1,21 @@
|
||||||
import "/static/vendor/ace/ace.js";
|
import "/static/vendor/ace/ace.js";
|
||||||
import { $el } from "../../vendor/domutil/domutil.js";
|
import { $el } from "/static/vendor/domutil/domutil.js";
|
||||||
|
|
||||||
class Editor extends HTMLElement {
|
class Editor extends HTMLElement {
|
||||||
editor;
|
private editor: AceAjax.Editor;
|
||||||
|
private tabEl: HTMLDivElement;
|
||||||
/** @type {HTMLDivElement} Tab container */
|
private files: Record<string, AceAjax.IEditSession>;
|
||||||
tabEl;
|
private root: ShadowRoot;
|
||||||
|
|
||||||
/** @type {Map<string, AceSession>} File name to file session */
|
|
||||||
files;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.files = {};
|
this.files = {};
|
||||||
this.attachShadow({ mode: "open" });
|
this.root = this.attachShadow({ mode: "open" });
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this.tabEl = $el("div", { className: "tab-container" });
|
this.tabEl = $el("div", { className: "tab-container" });
|
||||||
this.shadowRoot.append(this.tabEl);
|
this.root.append(this.tabEl);
|
||||||
|
|
||||||
const style = $el("style");
|
const style = $el("style");
|
||||||
style.textContent = `
|
style.textContent = `
|
||||||
|
@ -40,7 +37,7 @@ class Editor extends HTMLElement {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
const slot = $el("slot", { name: "body" });
|
const slot = $el("slot", { name: "body" });
|
||||||
this.shadowRoot.append(style, slot);
|
this.root.append(style, slot);
|
||||||
|
|
||||||
const editorEl = $el("div", { slot: "body" });
|
const editorEl = $el("div", { slot: "body" });
|
||||||
this.append(editorEl);
|
this.append(editorEl);
|
||||||
|
@ -61,10 +58,10 @@ class Editor extends HTMLElement {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a file editing session
|
* Add a file editing session
|
||||||
* @param {string} name File name
|
* @param name File name
|
||||||
* @param {string} content File contents
|
* @param content File contents
|
||||||
*/
|
*/
|
||||||
addFile(name, content) {
|
addFile(name: string, content: string) {
|
||||||
// Add to session list
|
// Add to session list
|
||||||
this.files[name] = ace.createEditSession(content);
|
this.files[name] = ace.createEditSession(content);
|
||||||
|
|
||||||
|
@ -78,10 +75,10 @@ class Editor extends HTMLElement {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new tab
|
* Create a new tab
|
||||||
* @param {string} name Tab name
|
* @param name Tab name
|
||||||
* @returns Tab element
|
* @returns Tab element
|
||||||
*/
|
*/
|
||||||
#addTab(name) {
|
#addTab(name: string) {
|
||||||
const tab = $el("div", {
|
const tab = $el("div", {
|
||||||
className: "tab",
|
className: "tab",
|
||||||
"@click": () => {
|
"@click": () => {
|
36
static/scripts/compose.ts
Normal file
36
static/scripts/compose.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/**
|
||||||
|
* Checks if a specificied source is a valid arion-compose.nix file
|
||||||
|
* @param source Source to check
|
||||||
|
* @returns Result of the check
|
||||||
|
*/
|
||||||
|
export async function check_compose(source: string): Promise<CheckResult> {
|
||||||
|
const response = await fetch("/stack/_/check", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Content-Type": "text/plain",
|
||||||
|
},
|
||||||
|
body: source
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return { ok: true };
|
||||||
|
} else {
|
||||||
|
return { ok: false, error: await response.json() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface APIError {
|
||||||
|
/** Error code */
|
||||||
|
code: string;
|
||||||
|
|
||||||
|
/** Human readable error details */
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CheckResult = {
|
||||||
|
ok: true;
|
||||||
|
} | {
|
||||||
|
ok: false;
|
||||||
|
error: APIError;
|
||||||
|
}
|
|
@ -1,12 +1,11 @@
|
||||||
import Editor from "../ace.mjs";
|
import { check_compose } from "/static/scripts/compose.js";
|
||||||
import { check_compose } from "/static/js/compose.mjs";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes the form require the stack to be checked before submitting
|
* Makes the form require the stack to be checked before submitting
|
||||||
* @param {HTMLFormElement} form
|
* @param form
|
||||||
* @param {Editor} editor
|
* @param editor
|
||||||
*/
|
*/
|
||||||
export function add_check(form, editor) {
|
export function add_check(form: HTMLFormElement, editor: AceAjax.Editor) {
|
||||||
form.addEventListener("submit", (ev) => {
|
form.addEventListener("submit", (ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
check_stack(editor).then((result) => {
|
check_stack(editor).then((result) => {
|
||||||
|
@ -20,15 +19,15 @@ export function add_check(form, editor) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs the check function and updates some DOM elements
|
* Runs the check function and updates some DOM elements
|
||||||
* @param {Editor} editor Editor instance
|
* @param editor Editor instance
|
||||||
* @returns true if the stack is valid, false otherwise
|
* @returns true if the stack is valid, false otherwise
|
||||||
*/
|
*/
|
||||||
export async function check_stack(editor) {
|
export async function check_stack(editor: AceAjax.Editor) {
|
||||||
const source = editor.editor.getValue();
|
const source = editor.getValue();
|
||||||
const check_result = await check_compose(source);
|
const check_result = await check_compose(source);
|
||||||
|
|
||||||
const editorEl = document.querySelector(".ace_editor");
|
const editorEl = document.querySelector(".ace_editor")!;
|
||||||
const editorErrorEl = document.querySelector("#editor-form .error");
|
const editorErrorEl = document.querySelector<HTMLDivElement>("#editor-form div.error")!;
|
||||||
editorEl.classList.remove("err", "checked");
|
editorEl.classList.remove("err", "checked");
|
||||||
editorErrorEl.style.display = "block";
|
editorErrorEl.style.display = "block";
|
||||||
editorErrorEl.classList.add("pending");
|
editorErrorEl.classList.add("pending");
|
16
static/scripts/node-utils.ts
Normal file
16
static/scripts/node-utils.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
/**
|
||||||
|
* Find nearest parent that satisfies a requirement
|
||||||
|
* @param node Node to start from
|
||||||
|
* @param findFn Matching function
|
||||||
|
* @returns Found element, or null if no parent matches
|
||||||
|
*/
|
||||||
|
export function findNearestParent(node: Node | Element, findFn: (el: Element) => boolean): HTMLElement | null {
|
||||||
|
let parent = node.parentElement;
|
||||||
|
while (parent) {
|
||||||
|
if (findFn(parent)) {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
parent = parent.parentElement;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
3139
static/vendor/ace/ace.d.ts
vendored
Normal file
3139
static/vendor/ace/ace.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load diff
22
tsconfig.json
Normal file
22
tsconfig.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"paths": {
|
||||||
|
"/static/*": [
|
||||||
|
"./static/*"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"lib": [
|
||||||
|
"ESNext",
|
||||||
|
"DOM"
|
||||||
|
],
|
||||||
|
"rootDir": "./",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"declaration": true,
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./static/**/*.ts",
|
||||||
|
"./static/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
Reference in a new issue