From cee3c14bd5fd9366926836749d2b4e0567fec246 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 26 Aug 2019 08:45:49 -0700 Subject: [PATCH] Improve types --- modern/src/Actions.ts | 18 +++++-- modern/src/Emitter.ts | 2 +- .../runtime/{MakiObject.js => MakiObject.ts} | 27 +++++++--- modern/src/types.ts | 8 +++ modern/src/{utils.js => utils.ts} | 51 ++++++++++++++----- 5 files changed, 82 insertions(+), 24 deletions(-) rename modern/src/runtime/{MakiObject.js => MakiObject.ts} (76%) rename modern/src/{utils.js => utils.ts} (82%) diff --git a/modern/src/Actions.ts b/modern/src/Actions.ts index a02c036a..eb20a086 100644 --- a/modern/src/Actions.ts +++ b/modern/src/Actions.ts @@ -1,4 +1,11 @@ -import { MakiTree, ModernAction, ModernStore, XmlTree } from "./types"; +import { + MakiTree, + ModernAction, + ModernStore, + XmlTree, + ResolvedXmlNode, + XmlNode, +} from "./types"; import JSZip from "jszip"; import * as Utils from "./utils"; import initialize from "./initialize"; @@ -39,9 +46,12 @@ export function gotSkinZip(zip: JSZip, store: ModernStore) { await Utils.readXml(zip, "skin.xml"), zip ); - const xmlTree = Utils.mapTree(rawXmlTree, node => { - return { ...node, uid: Utils.getId() }; - }); + const xmlTree: ResolvedXmlNode = Utils.mapTree( + rawXmlTree, + (node: XmlNode): ResolvedXmlNode => { + return { ...node, uid: Utils.getId() }; + } + ); dispatch(setXmlTree(xmlTree)); diff --git a/modern/src/Emitter.ts b/modern/src/Emitter.ts index fc773db2..5ed5d419 100644 --- a/modern/src/Emitter.ts +++ b/modern/src/Emitter.ts @@ -1,5 +1,5 @@ // TODO: Merge with the Webamp emitter -class Emitter { +export default class Emitter { _hooks: { [eventName: string]: Array<(...args: any[]) => void> }; _globalHooks: Array<(eventName: string, ...args: any[]) => void>; constructor() { diff --git a/modern/src/runtime/MakiObject.js b/modern/src/runtime/MakiObject.ts similarity index 76% rename from modern/src/runtime/MakiObject.js rename to modern/src/runtime/MakiObject.ts index 5e19aa56..e8537a30 100644 --- a/modern/src/runtime/MakiObject.js +++ b/modern/src/runtime/MakiObject.ts @@ -1,8 +1,23 @@ import Emitter from "../Emitter"; import * as Utils from "../utils"; +import { ModernStore, ResolvedXmlNode } from "../types"; class MakiObject { - constructor(node, parent, annotations = {}, store) { + name: string; + _uid: number; + _store: ModernStore; + attributes: Object; + parent: MakiObject; + _emitter: Emitter; + children: MakiObject[]; + js_annotations: Object; + + constructor( + node: ResolvedXmlNode, + parent: MakiObject, + annotations: Object = {}, + store: ModernStore + ) { this._store = store; if (node) { this._uid = node.uid; @@ -20,15 +35,15 @@ class MakiObject { this._emitter = new Emitter(); } - js_addChild(child) { + js_addChild(child: MakiObject) { this.children.push(child); } - js_addChildren(children) { + js_addChildren(children: MakiObject[]) { this.children = this.children.concat(children); } - js_removeChild(child) { + js_removeChild(child: MakiObject) { this.children = this.children.filter(item => item !== child); } @@ -53,7 +68,7 @@ class MakiObject { this._emitter.dispose(); } - js_imageLookup(id) { + js_imageLookup(id: string) { const element = Utils.findElementById(this, id); if (element) { return element.js_annotations; @@ -62,7 +77,7 @@ class MakiObject { return null; } - js_groupdefLookup(id) { + js_groupdefLookup(id: string) { const groupdef = Utils.findGroupDefById(this, id); if (groupdef) { return groupdef; diff --git a/modern/src/types.ts b/modern/src/types.ts index e51b9884..ea58d425 100644 --- a/modern/src/types.ts +++ b/modern/src/types.ts @@ -2,9 +2,17 @@ export type MakiTree = any; export type XmlNode = { + parent: XmlNode; children: XmlNode[]; + attributes: Object; + name: string; }; +export interface ResolvedXmlNode extends XmlNode { + uid: number; +} + +// What is a tree, but a single root node? export type XmlTree = XmlNode; export type ModernAppState = { diff --git a/modern/src/utils.js b/modern/src/utils.ts similarity index 82% rename from modern/src/utils.js rename to modern/src/utils.ts index 832cbbed..363681bb 100644 --- a/modern/src/utils.js +++ b/modern/src/utils.ts @@ -1,4 +1,6 @@ +import JSZip from "jszip"; import { xml2js } from "xml-js"; +import { XmlNode } from "./types"; let nextId = 0; export function getId() { @@ -20,22 +22,32 @@ function fixFilenameSlashes(filename) { return filename.replace(/\\/g, "/"); } -export function getCaseInsensitveFile(zip, filename) { +export function getCaseInsensitveFile( + zip: JSZip, + filename: string +): JSZip.JSZipObject { // TODO: Escape `file` for rejex characters return zip.file(new RegExp(fixFilenameSlashes(filename), "i"))[0]; } // Read a -export async function readXml(zip, filepath) { +export async function readXml(zip: JSZip, filepath: string): Promise { const file = await getCaseInsensitveFile(zip, filepath); if (file == null) { return null; } const text = await file.async("text"); + // @ts-ignore Due to the way it's config object interface works, xml2js is + // bascially impossible to type. For example, you can specify what key to use + // for `elements`. We choose `children` but the types assume the default + // `elements`. return xml2js(text, { compact: false, elementsKey: "children" }); } -export async function readUint8array(zip, filepath) { +export async function readUint8array( + zip: JSZip, + filepath: string +): Promise { const file = await getCaseInsensitveFile(zip, filepath); if (file == null) { return null; @@ -45,7 +57,7 @@ export async function readUint8array(zip, filepath) { // I any of the values in `arr` are themselves arrays, interpolate the nested // array into the top level array. -function flatten(arr) { +function flatten(arr: Array>): Array { const newArr = []; arr.forEach(item => { if (Array.isArray(item)) { @@ -60,7 +72,10 @@ function flatten(arr) { // Map an async function over an array. If the value returned from the mapper is // an array, it recursively maps the function over that array's values, and then // interpoates the resulting flat array into the top level array of results. -export async function asyncFlatMap(arr, mapper) { +export async function asyncFlatMap( + arr: Array, + mapper: (value: T) => R | Array +): Promise> { const mapped = await Promise.all(arr.map(mapper)); const childPromises = mapped.map(async item => { if (Array.isArray(item)) { @@ -97,7 +112,10 @@ export async function asyncTreeFlatMap(node, mapper) { // Given an XML file and a zip which it came from, replace all `` elements // with the contents of the file to be included. -export async function inlineIncludes(xml, zip) { +export async function inlineIncludes( + xml: XmlNode, + zip: JSZip +): Promise { return asyncTreeFlatMap(xml, async node => { if (node.name !== "include") { return node; @@ -116,7 +134,7 @@ export async function inlineIncludes(xml, zip) { }); } -export function unimplementedWarning(name) { +export function unimplementedWarning(name: string): void { console.warn(`Executing unimplemented MAKI function: ${name}`); } @@ -135,7 +153,10 @@ export function findInTree(node, predicate) { return null; } -export function findParent(node, predicate) { +export function findParent( + node: T, + predicate: (node: T) => boolean +): T | null { let n = node; while (n.parent) { n = n.parent; @@ -148,8 +169,8 @@ export function findParent(node, predicate) { } // Operations on trees -export function findParentNodeOfType(node, type) { - return findParent(node, node => type.has(node.name)); +export function findParentNodeOfType(node, type: Set) { + return findParent(node, n => type.has(n.name)); } export function findParentOrCurrentNodeOfType(node, type) { @@ -244,11 +265,13 @@ export function findGroupDefById(node, id) { // This is intentionally async since we may want to sub it out for an async // function in a node environment -export async function getUrlFromBlob(blob) { +export async function getUrlFromBlob(blob: Blob): Promise { return URL.createObjectURL(blob); } -async function loadImage(imgUrl) { +async function loadImage( + imgUrl: string +): Promise<{ width: number; height: number }> { return new Promise((resolve, reject) => { const img = new Image(); img.addEventListener("load", () => { @@ -261,7 +284,9 @@ async function loadImage(imgUrl) { }); } -export async function getSizeFromUrl(imgUrl) { +export async function getSizeFromUrl( + imgUrl: string +): Promise<{ width: number; height: number }> { const { width, height } = await loadImage(imgUrl); return { width, height }; }