UNPKG

ember-material-icons

Version:

Google Material icons for your ember-cli app

390 lines (299 loc) 9.66 kB
import { Scope, DynamicScope, Environment } from '../environment'; import { DestroyableBounds, clear, move as moveBounds } from '../bounds'; import { ElementStack, Tracker, UpdatableTracker } from '../builder'; import { Option, Opaque, Stack, LinkedList, Dict, dict, expect } from '@glimmer/util'; import { ConstReference, PathReference, IterationArtifacts, IteratorSynchronizer, IteratorSynchronizerDelegate, // Tags combine, Revision, UpdatableTag, combineSlice, CONSTANT_TAG, INITIAL } from '@glimmer/reference'; import { EvaluatedArgs } from '../compiled/expressions/args'; import { Constants, OpcodeJSON, UpdatingOpcode, UpdatingOpSeq } from '../opcodes'; import { DOMChanges } from '../dom/helper'; import * as Simple from '../dom/interfaces'; import { CapturedFrame } from './frame'; import VM from './append'; export default class UpdatingVM { public env: Environment; public dom: DOMChanges; public alwaysRevalidate: boolean; public constants: Constants; private frameStack: Stack<UpdatingVMFrame> = new Stack<UpdatingVMFrame>(); constructor(env: Environment, { alwaysRevalidate = false }) { this.env = env; this.constants = env.constants; this.dom = env.getDOM(); this.alwaysRevalidate = alwaysRevalidate; } execute(opcodes: UpdatingOpSeq, handler: ExceptionHandler) { let { frameStack } = this; this.try(opcodes, handler); while (true) { if (frameStack.isEmpty()) break; let opcode = this.frame.nextStatement(); if (opcode === null) { this.frameStack.pop(); continue; } opcode.evaluate(this); } } private get frame() { return expect(this.frameStack.current, 'bug: expected a frame'); } goto(op: UpdatingOpcode) { this.frame.goto(op); } try(ops: UpdatingOpSeq, handler: Option<ExceptionHandler>) { this.frameStack.push(new UpdatingVMFrame(this, ops, handler)); } throw() { this.frame.handleException(); this.frameStack.pop(); } evaluateOpcode(opcode: UpdatingOpcode) { opcode.evaluate(this); } } export interface ExceptionHandler { handleException(): void; } export interface VMState { env: Environment; scope: Scope; dynamicScope: DynamicScope; frame: CapturedFrame; } export abstract class BlockOpcode extends UpdatingOpcode implements DestroyableBounds { public type = "block"; public next = null; public prev = null; protected env: Environment; protected scope: Scope; protected dynamicScope: DynamicScope; protected frame: CapturedFrame; protected children: LinkedList<UpdatingOpcode>; protected bounds: DestroyableBounds; constructor(public start: number, public end: number, state: VMState, bounds: DestroyableBounds, children: LinkedList<UpdatingOpcode>) { super(); let { env, scope, dynamicScope, frame } = state; this.children = children; this.env = env; this.scope = scope; this.dynamicScope = dynamicScope; this.frame = frame; this.bounds = bounds; } abstract didInitializeChildren(): void; parentElement() { return this.bounds.parentElement(); } firstNode() { return this.bounds.firstNode(); } lastNode() { return this.bounds.lastNode(); } evaluate(vm: UpdatingVM) { vm.try(this.children, null); } destroy() { this.bounds.destroy(); } didDestroy() { this.env.didDestroy(this.bounds); } toJSON() : OpcodeJSON { let details = dict<string>(); details["guid"] = `${this._guid}`; return { guid: this._guid, type: this.type, details, children: this.children.toArray().map(op => op.toJSON()) }; } } export class TryOpcode extends BlockOpcode implements ExceptionHandler { public type = "try"; private _tag: UpdatableTag; protected bounds: UpdatableTracker; constructor(start: number, end: number, state: VMState, bounds: UpdatableTracker, children: LinkedList<UpdatingOpcode>) { super(start, end, state, bounds, children); this.tag = this._tag = new UpdatableTag(CONSTANT_TAG); } didInitializeChildren() { this._tag.update(combineSlice(this.children)); } evaluate(vm: UpdatingVM) { vm.try(this.children, this); } handleException() { let { env, scope, start, end, dynamicScope, frame } = this; let elementStack = ElementStack.resume( this.env, this.bounds, this.bounds.reset(env) ); let vm = new VM(env, scope, dynamicScope, elementStack); let result = vm.resume(start, end, frame); this.children = result.opcodes(); this.didInitializeChildren(); } toJSON() : OpcodeJSON { let json = super.toJSON(); let details = json["details"]; if (!details) { details = json["details"] = {}; } return super.toJSON(); } } class ListRevalidationDelegate implements IteratorSynchronizerDelegate { private map: Dict<BlockOpcode>; private updating: LinkedList<UpdatingOpcode>; private didInsert = false; private didDelete = false; constructor(private opcode: ListBlockOpcode, private marker: Simple.Comment) { this.map = opcode.map; this.updating = opcode['children']; } insert(key: string, item: PathReference<Opaque>, memo: PathReference<Opaque>, before: string) { let { map, opcode, updating } = this; let nextSibling: Option<Simple.Node> = null; let reference: Option<BlockOpcode> = null; if (before) { reference = map[before]; nextSibling = reference['bounds'].firstNode(); } else { nextSibling = this.marker; } let vm = opcode.vmForInsertion(nextSibling); let tryOpcode: Option<TryOpcode> = null; vm.execute(opcode.start, opcode.end, vm => { vm.frame.setArgs(EvaluatedArgs.positional([item, memo])); vm.frame.setOperand(item); vm.frame.setCondition(new ConstReference(true)); vm.frame.setKey(key); let state = vm.capture(); let tracker = vm.stack().pushUpdatableBlock(); tryOpcode = new TryOpcode(opcode.start, opcode.end, state, tracker, vm.updating()); }); tryOpcode!.didInitializeChildren(); updating.insertBefore(tryOpcode!, reference); map[key] = tryOpcode!; this.didInsert = true; } retain(_key: string, _item: PathReference<Opaque>, _memo: PathReference<Opaque>) { } move(key: string, _item: PathReference<Opaque>, _memo: PathReference<Opaque>, before: string) { let { map, updating } = this; let entry = map[key]; let reference = map[before] || null; if (before) { moveBounds(entry, reference.firstNode()); } else { moveBounds(entry, this.marker); } updating.remove(entry); updating.insertBefore(entry, reference); } delete(key: string) { let { map } = this; let opcode = map[key]; opcode.didDestroy(); clear(opcode); this.updating.remove(opcode); delete map[key]; this.didDelete = true; } done() { this.opcode.didInitializeChildren(this.didInsert || this.didDelete); } } export class ListBlockOpcode extends BlockOpcode { public type = "list-block"; public map = dict<BlockOpcode>(); public artifacts: IterationArtifacts; private lastIterated: Revision = INITIAL; private _tag: UpdatableTag; constructor(start: number, end: number, state: VMState, bounds: Tracker, children: LinkedList<UpdatingOpcode>, artifacts: IterationArtifacts) { super(start, end, state, bounds, children); this.artifacts = artifacts; let _tag = this._tag = new UpdatableTag(CONSTANT_TAG); this.tag = combine([artifacts.tag, _tag]); } didInitializeChildren(listDidChange = true) { this.lastIterated = this.artifacts.tag.value(); if (listDidChange) { this._tag.update(combineSlice(this.children)); } } evaluate(vm: UpdatingVM) { let { artifacts, lastIterated } = this; if (!artifacts.tag.validate(lastIterated)) { let { bounds } = this; let { dom } = vm; let marker = dom.createComment(''); dom.insertAfter(bounds.parentElement(), marker, expect(bounds.lastNode(), "can't insert after an empty bounds")); let target = new ListRevalidationDelegate(this, marker); let synchronizer = new IteratorSynchronizer({ target, artifacts }); synchronizer.sync(); this.parentElement().removeChild(marker); } // Run now-updated updating opcodes super.evaluate(vm); } vmForInsertion(nextSibling: Option<Simple.Node>): VM { let { env, scope, dynamicScope } = this; let elementStack = ElementStack.forInitialRender( this.env, this.bounds.parentElement(), nextSibling ); return new VM(env, scope, dynamicScope, elementStack); } toJSON() : OpcodeJSON { let json = super.toJSON(); let map = this.map; let inner = Object.keys(map).map(key => { return `${JSON.stringify(key)}: ${map[key]._guid}`; }).join(", "); let details = json["details"]; if (!details) { details = json["details"] = {}; } details["map"] = `{${inner}}`; return json; } } class UpdatingVMFrame { private current: Option<UpdatingOpcode>; constructor(private vm: UpdatingVM, private ops: UpdatingOpSeq, private exceptionHandler: Option<ExceptionHandler>) { this.vm = vm; this.ops = ops; this.current = ops.head(); } goto(op: UpdatingOpcode) { this.current = op; } nextStatement(): Option<UpdatingOpcode> { let { current, ops } = this; if (current) this.current = ops.nextNode(current); return current; } handleException() { if (this.exceptionHandler) { this.exceptionHandler.handleException(); } } }