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
|
||||
.vscode
|
||||
/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"] }
|
||||
tracing = "0.1"
|
||||
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 { $el } from "../../vendor/domutil/domutil.js";
|
||||
import { $el } from "/static/vendor/domutil/domutil.js";
|
||||
|
||||
class Editor extends HTMLElement {
|
||||
editor;
|
||||
|
||||
/** @type {HTMLDivElement} Tab container */
|
||||
tabEl;
|
||||
|
||||
/** @type {Map<string, AceSession>} File name to file session */
|
||||
files;
|
||||
private editor: AceAjax.Editor;
|
||||
private tabEl: HTMLDivElement;
|
||||
private files: Record<string, AceAjax.IEditSession>;
|
||||
private root: ShadowRoot;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.files = {};
|
||||
this.attachShadow({ mode: "open" });
|
||||
this.root = this.attachShadow({ mode: "open" });
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.tabEl = $el("div", { className: "tab-container" });
|
||||
this.shadowRoot.append(this.tabEl);
|
||||
this.root.append(this.tabEl);
|
||||
|
||||
const style = $el("style");
|
||||
style.textContent = `
|
||||
|
@ -40,7 +37,7 @@ class Editor extends HTMLElement {
|
|||
}
|
||||
`;
|
||||
const slot = $el("slot", { name: "body" });
|
||||
this.shadowRoot.append(style, slot);
|
||||
this.root.append(style, slot);
|
||||
|
||||
const editorEl = $el("div", { slot: "body" });
|
||||
this.append(editorEl);
|
||||
|
@ -61,10 +58,10 @@ class Editor extends HTMLElement {
|
|||
|
||||
/**
|
||||
* Add a file editing session
|
||||
* @param {string} name File name
|
||||
* @param {string} content File contents
|
||||
* @param name File name
|
||||
* @param content File contents
|
||||
*/
|
||||
addFile(name, content) {
|
||||
addFile(name: string, content: string) {
|
||||
// Add to session list
|
||||
this.files[name] = ace.createEditSession(content);
|
||||
|
||||
|
@ -78,10 +75,10 @@ class Editor extends HTMLElement {
|
|||
|
||||
/**
|
||||
* Create a new tab
|
||||
* @param {string} name Tab name
|
||||
* @param name Tab name
|
||||
* @returns Tab element
|
||||
*/
|
||||
#addTab(name) {
|
||||
#addTab(name: string) {
|
||||
const tab = $el("div", {
|
||||
className: "tab",
|
||||
"@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/js/compose.mjs";
|
||||
import { check_compose } from "/static/scripts/compose.js";
|
||||
|
||||
/**
|
||||
* Makes the form require the stack to be checked before submitting
|
||||
* @param {HTMLFormElement} form
|
||||
* @param {Editor} editor
|
||||
* @param form
|
||||
* @param editor
|
||||
*/
|
||||
export function add_check(form, editor) {
|
||||
export function add_check(form: HTMLFormElement, editor: AceAjax.Editor) {
|
||||
form.addEventListener("submit", (ev) => {
|
||||
ev.preventDefault();
|
||||
check_stack(editor).then((result) => {
|
||||
|
@ -20,15 +19,15 @@ export function add_check(form, editor) {
|
|||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
export async function check_stack(editor) {
|
||||
const source = editor.editor.getValue();
|
||||
export async function check_stack(editor: AceAjax.Editor) {
|
||||
const source = editor.getValue();
|
||||
const check_result = await check_compose(source);
|
||||
|
||||
const editorEl = document.querySelector(".ace_editor");
|
||||
const editorErrorEl = document.querySelector("#editor-form .error");
|
||||
const editorEl = document.querySelector(".ace_editor")!;
|
||||
const editorErrorEl = document.querySelector<HTMLDivElement>("#editor-form div.error")!;
|
||||
editorEl.classList.remove("err", "checked");
|
||||
editorErrorEl.style.display = "block";
|
||||
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