ember-material-icons
Version:
Google Material icons for your ember-cli app
390 lines (299 loc) • 9.66 kB
text/typescript
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();
}
}
}