From 82c6308b16f90ee7750ef71bf4e9448145130c16 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Sat, 24 Aug 2019 21:10:00 -0700 Subject: [PATCH] [WIP] Give Maki objects access to Redux state (#882) * Share uids between maki objects and state tree * Start to add xml tree --- modern/src/Actions.ts | 7 ++- modern/src/MakiSelectors.ts | 14 +++++ modern/src/initialize.js | 99 +++++++++++++++++++------------ modern/src/initializeStateTree.ts | 10 +--- modern/src/runtime/GuiObject.js | 14 ++++- modern/src/runtime/MakiObject.js | 14 ++--- modern/src/utils.js | 21 +++++++ 7 files changed, 120 insertions(+), 59 deletions(-) create mode 100644 modern/src/MakiSelectors.ts diff --git a/modern/src/Actions.ts b/modern/src/Actions.ts index ffd37db4..a02c036a 100644 --- a/modern/src/Actions.ts +++ b/modern/src/Actions.ts @@ -35,14 +35,17 @@ export function gotSkinBlob(blob: Blob, store: ModernStore) { export function gotSkinZip(zip: JSZip, store: ModernStore) { return async dispatch => { - const xmlTree = await Utils.inlineIncludes( + const rawXmlTree = await Utils.inlineIncludes( await Utils.readXml(zip, "skin.xml"), zip ); + const xmlTree = Utils.mapTree(rawXmlTree, node => { + return { ...node, uid: Utils.getId() }; + }); dispatch(setXmlTree(xmlTree)); - const makiTree = await initialize(zip, xmlTree); + const makiTree = await initialize(zip, xmlTree, store); // Execute scripts await Utils.asyncTreeFlatMap(makiTree, node => { switch (node.name) { diff --git a/modern/src/MakiSelectors.ts b/modern/src/MakiSelectors.ts new file mode 100644 index 00000000..60ee3e75 --- /dev/null +++ b/modern/src/MakiSelectors.ts @@ -0,0 +1,14 @@ +import * as Utils from "./utils"; + +function findNodeByUid(state, uid) { + return Utils.findInTree(state.xmlTree, node => { + return node.uid === uid; + }); +} + +export function getTop(uid) { + return state => { + const node = findNodeByUid(state, uid); + return Number(node.attributes.y) || 0; + }; +} diff --git a/modern/src/initialize.js b/modern/src/initialize.js index 472928fa..91557660 100644 --- a/modern/src/initialize.js +++ b/modern/src/initialize.js @@ -19,10 +19,12 @@ import EqVis from "./runtime/EqVis"; import AnimatedLayer from "./runtime/AnimatedLayer"; import Component from "./runtime/Component"; -const noop = (node, parent) => new GuiObject(node, parent); +const noop = (node, parent, zip, store) => + new GuiObject(node, parent, undefined, store); const parsers = { - groupdef: (node, parent) => new JsGroupDef(node, parent), + groupdef: (node, parent, zip, store) => + new JsGroupDef(node, parent, undefined, store), skininfo: noop, guiobject: noop, version: noop, @@ -33,19 +35,26 @@ const parsers = { email: noop, homepage: noop, screenshot: noop, - container: (node, parent) => new Container(node, parent), + container: (node, parent, zip, store) => + new Container(node, parent, undefined, store), scripts: noop, - gammaset: (node, parent) => new JsGammaSet(node, parent), + gammaset: (node, parent, zip, store) => + new JsGammaSet(node, parent, undefined, store), color: noop, - layer: (node, parent) => new Layer(node, parent), + layer: (node, parent, zip, store) => + new Layer(node, parent, undefined, store), layoutstatus: noop, hideobject: noop, - button: (node, parent) => new Button(node, parent), - group: (node, parent) => new Group(node, parent), - layout: (node, parent) => new Layout(node, parent), + button: (node, parent, zip, store) => + new Button(node, parent, undefined, store), + group: (node, parent, zip, store) => + new Group(node, parent, undefined, store), + layout: (node, parent, zip, store) => + new Layout(node, parent, undefined, store), sendparams: noop, - elements: (node, parent) => new JsElements(node, parent), - bitmap: async (node, parent, zip) => { + elements: (node, parent, zip, store) => + new JsElements(node, parent, undefined, store), + bitmap: async (node, parent, zip, store) => { let { h, w, x, y } = node.attributes; const { file, gammagroup, id } = node.attributes; // TODO: Escape file for regex @@ -63,27 +72,37 @@ const parsers = { y = y !== undefined ? y : 0; } - return new MakiObject(node, parent, { - id, - file, - gammagroup, - h, - w, - x, - y, - imgUrl, - }); + return new MakiObject( + node, + parent, + { + id, + file, + gammagroup, + h, + w, + x, + y, + imgUrl, + }, + store + ); }, - eqvis: (node, parent) => new EqVis(node, parent), - slider: (node, parent) => new Slider(node, parent), + eqvis: (node, parent, zip, store) => + new EqVis(node, parent, undefined, store), + slider: (node, parent, zip, store) => + new Slider(node, parent, undefined, store), gammagroup: noop, truetypefont: noop, - component: (node, parent) => new Component(node, parent), - text: (node, parent) => new Text(node, parent), - togglebutton: (node, parent) => new ToggleButton(node, parent), - status: (node, parent) => new Status(node, parent), + component: (node, parent, zip, store) => + new Component(node, parent, undefined, store), + text: (node, parent, zip, store) => new Text(node, parent, undefined, store), + togglebutton: (node, parent, zip, store) => + new ToggleButton(node, parent, undefined, store), + status: (node, parent, zip, store) => + new Status(node, parent, undefined, store), bitmapfont: noop, - vis: (node, parent) => new Vis(node, parent), + vis: (node, parent, zip, store) => new Vis(node, parent, undefined, store), "wasabi:titlebar": noop, "colorthemes:list": noop, "wasabi:standardframe:status": noop, @@ -95,19 +114,20 @@ const parsers = { elementalias: noop, grid: noop, rect: noop, - animatedlayer: (node, parent) => new AnimatedLayer(node, parent), + animatedlayer: (node, parent, zip, store) => + new AnimatedLayer(node, parent, undefined, store), nstatesbutton: noop, songticker: noop, menu: noop, albumart: noop, playlistplus: noop, - async script(node, parent, zip) { + async script(node, parent, zip, store) { const script = await Utils.readUint8array(zip, node.attributes.file); - return new MakiObject(node, parent, { script }); + return new MakiObject(node, parent, { script }, store); }, }; -async function parseChildren(node, children, zip) { +async function parseChildren(node, children, zip, store) { if (node.type === "comment") { return; } @@ -123,7 +143,7 @@ async function parseChildren(node, children, zip) { } if (child.type === "text") { // TODO: Handle text - return new MakiObject({ ...child }, node); + return new MakiObject({ ...child }, node, undefined, store); } if (child.name == null) { console.error(child); @@ -140,9 +160,9 @@ async function parseChildren(node, children, zip) { console.warn(`Missing parser in initialize for ${childName}`); childParser = noop; } - const parsedChild = await childParser(child, node, zip); + const parsedChild = await childParser(child, node, zip, store); if (child.children != null && child.children.length > 0) { - await parseChildren(parsedChild, child.children, zip); + await parseChildren(parsedChild, child.children, zip, store); } return parsedChild; }) @@ -185,10 +205,15 @@ async function applyGroupDefs(root) { }); } -async function initialize(zip, skinXml) { +async function initialize(zip, skinXml, store) { const xmlRoot = skinXml.children[0]; - const root = new JsWinampAbstractionLayer(skinXml.children[0], null); - await parseChildren(root, xmlRoot.children, zip); + const root = new JsWinampAbstractionLayer( + skinXml.children[0], + null, + undefined, + store + ); + await parseChildren(root, xmlRoot.children, zip, store); await applyGroupDefs(root); return root; } diff --git a/modern/src/initializeStateTree.ts b/modern/src/initializeStateTree.ts index e127f717..4b95f25d 100644 --- a/modern/src/initializeStateTree.ts +++ b/modern/src/initializeStateTree.ts @@ -1,15 +1,7 @@ import { XmlTree } from "./types"; -import * as Utils from "./utils"; - -function mapTree(node, cb) { - const children = node.children || []; - return cb({ ...node, children: children.map(child => mapTree(child, cb)) }); -} export default async function initializeStateTree( xmlTree: XmlTree ): Promise { - return mapTree(xmlTree, node => { - return { ...node, uid: Utils.getId() }; - }); + return xmlTree; } diff --git a/modern/src/runtime/GuiObject.js b/modern/src/runtime/GuiObject.js index c308e4ca..888d0afc 100644 --- a/modern/src/runtime/GuiObject.js +++ b/modern/src/runtime/GuiObject.js @@ -4,10 +4,11 @@ import { findParentNodeOfType, unimplementedWarning, } from "../utils"; +import * as MakiSelectors from "../MakiSelectors"; class GuiObject extends MakiObject { - constructor(node, parent) { - super(node, parent); + constructor(node, parent, annotations, store) { + super(node, parent, annotations, store); this.visible = true; } @@ -61,7 +62,14 @@ class GuiObject extends MakiObject { } gettop() { - return Number(this.attributes.y) || 0; + const stateTop = MakiSelectors.getTop(this._uid)(this._store.getState()); + const makiTop = Number(this.attributes.y) || 0; + if (stateTop !== makiTop) { + console.error( + `Maki state ${makiTop} is out of sync with tree state ${stateTop}` + ); + } + return makiTop; } getleft() { diff --git a/modern/src/runtime/MakiObject.js b/modern/src/runtime/MakiObject.js index 31f4a989..5e19aa56 100644 --- a/modern/src/runtime/MakiObject.js +++ b/modern/src/runtime/MakiObject.js @@ -1,17 +1,15 @@ import Emitter from "../Emitter"; -import { - findElementById, - findGroupDefById, - unimplementedWarning, -} from "../utils"; +import * as Utils from "../utils"; class MakiObject { constructor(node, parent, annotations = {}, store) { this._store = store; if (node) { + this._uid = node.uid; this.attributes = node.attributes || {}; this.name = node.name; } else { + this._uid = Utils.getId(); // When dynamically creating an object with `new` we have no underlying node this.attributes = {}; this.name = this.getclassname().toLowerCase(); @@ -56,7 +54,7 @@ class MakiObject { } js_imageLookup(id) { - const element = findElementById(this, id); + const element = Utils.findElementById(this, id); if (element) { return element.js_annotations; } @@ -65,7 +63,7 @@ class MakiObject { } js_groupdefLookup(id) { - const groupdef = findGroupDefById(this, id); + const groupdef = Utils.findGroupDefById(this, id); if (groupdef) { return groupdef; } @@ -91,7 +89,7 @@ class MakiObject { } onnotify(command, param, a, b) { - unimplementedWarning("onnotify"); + Utils.unimplementedWarning("onnotify"); return 0; } } diff --git a/modern/src/utils.js b/modern/src/utils.js index ae3f523a..6199d7a6 100644 --- a/modern/src/utils.js +++ b/modern/src/utils.js @@ -5,6 +5,12 @@ export function getId() { return i++; } +// Depth-first tree map +export function mapTree(node, cb) { + const children = node.children || []; + return cb({ ...node, children: children.map(child => mapTree(child, cb)) }); +} + export function isPromise(obj) { return obj && typeof obj.then === "function"; } @@ -114,6 +120,21 @@ export function unimplementedWarning(name) { console.warn(`Executing unimplemented MAKI function: ${name}`); } +// Bredth-first search in a tree +export function findInTree(node, predicate) { + if (predicate(node)) { + return node; + } + const children = node.children || []; + for (const child of children) { + const found = findInTree(child, predicate); + if (found != null) { + return found; + } + } + return null; +} + // Operations on trees export function findParentNodeOfType(node, type) { let n = node;