mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-22 01:29:22 +00:00
Improve types
This commit is contained in:
parent
3f7590817f
commit
cee3c14bd5
5 changed files with 82 additions and 24 deletions
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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<XmlNode> {
|
||||
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<Uint8Array> {
|
||||
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<T>(arr: Array<T | Array<T>>): Array<T> {
|
||||
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<T, R>(
|
||||
arr: Array<T>,
|
||||
mapper: (value: T) => R | Array<T>
|
||||
): Promise<Array<R>> {
|
||||
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 `<inline />` 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<XmlNode> {
|
||||
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<T extends { parent: T | null }>(
|
||||
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<string>) {
|
||||
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<string> {
|
||||
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 };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue