ember-material-icons
Version:
Google Material icons for your ember-cli app
352 lines (280 loc) • 10.4 kB
text/typescript
import { CLASS_META } from '@glimmer/object-reference';
import { Dict, dict, assign } from '@glimmer/util';
import GlimmerObject, {
GlimmerObjectFactory,
ClassMeta,
InstanceMeta,
turbocharge
} from './object';
import { ROOT } from './utils';
const { isArray } = Array;
export const DESCRIPTOR = "5d90f84f-908e-4a42-9749-3d0f523c262c";
export const BLUEPRINT = "8d97cf5f-db9e-48d8-a6b2-7a75b7170805";
export abstract class Descriptor {
"5d90f84f-908e-4a42-9749-3d0f523c262c" = true;
abstract define(prototype: Object, key: string, home: Object);
}
export abstract class Blueprint {
"8d97cf5f-db9e-48d8-a6b2-7a75b7170805" = true;
abstract descriptor(target: Object, key: string, classMeta: ClassMeta): Descriptor;
}
export interface Extensions {
concatenatedProperties?: string[] | string;
mergedProperties?: string[] | string;
_super?: Function;
[index: string]: any;
}
export class Mixin {
private extensions = null;
private concatenatedProperties: string[] = [];
private mergedProperties: string[] = [];
private dependencies: Mixin[] = [];
static create(...args: (Mixin | Extensions)[]) {
let extensions = args[args.length - 1];
if (args.length === 0) {
return new this({}, []);
} else if (extensions instanceof Mixin) {
return new this({}, <Mixin[]>args);
} else {
let deps = args.slice(0, -1).map(toMixin);
return new this(<Extensions>extensions, deps);
}
}
static mixins(obj: any): Mixin[] {
if (typeof obj !== 'object' || obj === null) return [];
let meta = ClassMeta.for(obj);
if (!meta) return [];
return meta.getAppliedMixins();
}
constructor(extensions: Extensions, mixins: Mixin[]) {
this.reopen(extensions);
this.dependencies.push(...mixins);
}
detect(obj: any): boolean {
if (typeof obj !== 'object' || obj === null) return false;
if (obj instanceof Mixin) {
return obj.dependencies.indexOf(this) !== -1;
}
let meta = ClassMeta.for(obj);
return !!meta && meta.hasAppliedMixin(this);
}
reopen(extensions: Extensions) {
if (this.extensions) {
this.dependencies.push(toMixin(this.extensions));
}
if (typeof extensions === 'object' && 'concatenatedProperties' in extensions) {
let concat: string[];
let rawConcat = extensions.concatenatedProperties;
if (isArray(rawConcat)) {
concat = (<string[]>rawConcat).slice();
} else if (rawConcat === null || rawConcat === undefined) {
concat = [];
} else {
concat = [<string>rawConcat];
}
delete extensions.concatenatedProperties;
this.concatenatedProperties = concat;
}
if (typeof extensions === 'object' && 'mergedProperties' in extensions) {
let merged: string[];
let rawMerged = extensions.mergedProperties;
if (isArray(rawMerged)) {
merged = (<string[]>rawMerged).slice();
} else if (rawMerged === null || rawMerged === undefined) {
merged = [];
} else {
merged = [<string>rawMerged];
}
delete extensions.mergedProperties;
this.mergedProperties = merged;
}
let normalized: Dict<Blueprint> = Object.keys(extensions).reduce((obj, key) => {
let value = extensions[key];
switch (typeof value) {
case 'function':
obj[key] = new MethodBlueprint({ value });
break;
case 'object':
if (value && BLUEPRINT in value) {
obj[key] = value;
break;
}
/* falls through */
default:
obj[key] = new DataBlueprint({ value });
}
return obj;
}, dict<Blueprint>());
this.extensions = dict<any>();
assign(this.extensions, turbocharge(normalized));
}
apply(target: any) {
let meta: ClassMeta = target[CLASS_META] = target[CLASS_META] || new ClassMeta();
this.dependencies.forEach(m => m.apply(target));
this.mergeProperties(target, target, meta);
meta.addMixin(this);
meta.seal();
meta.reseal(target);
return target;
}
extendPrototype(Original: GlimmerObjectFactory<any>) {
Original.prototype = Object.create(Original.prototype);
this.dependencies.forEach(m => m.extendPrototype(Original));
this.extendPrototypeOnto(Original, Original);
}
extendPrototypeOnto(Subclass: GlimmerObjectFactory<any>, Parent: GlimmerObjectFactory<any>) {
this.dependencies.forEach(m => m.extendPrototypeOnto(Subclass, Parent));
this.mergeProperties(Subclass.prototype, Parent.prototype, Subclass[CLASS_META]);
Subclass[CLASS_META].addMixin(this);
}
extendStatic(Target: GlimmerObjectFactory<any>) {
this.dependencies.forEach(m => m.extendStatic(Target));
this.mergeProperties(Target, Object.getPrototypeOf(Target), Target[CLASS_META][CLASS_META]);
Target[CLASS_META].addStaticMixin(this);
}
mergeProperties(target: Object, parent: Object, meta: ClassMeta) {
if (meta.hasAppliedMixin(this)) return;
meta.addAppliedMixin(this);
this.mergedProperties.forEach(k => meta.addMergedProperty(k, parent[k]));
this.concatenatedProperties.forEach(k => meta.addConcatenatedProperty(k, []));
new ValueDescriptor({ value: meta.getConcatenatedProperties() }).define(target, <string>'concatenatedProperties', null);
new ValueDescriptor({ value: meta.getMergedProperties() }).define(target, <string>'mergedProperties', null);
Object.keys(this.extensions).forEach(key => {
let extension: Blueprint = this.extensions[key];
let desc = extension.descriptor(target, <string>key, meta);
desc.define(target, <string>key, parent);
});
new ValueDescriptor({ value: ROOT }).define(target, <string>'_super', null);
}
}
export type Extension = Mixin | Extensions;
export function extend<T extends GlimmerObject>(Parent: GlimmerObjectFactory<T>, ...extensions: Extension[]): typeof GlimmerObject {
let Super = <typeof GlimmerObject>Parent;
let Subclass = class extends Super {};
Subclass[CLASS_META] = InstanceMeta.fromParent(Parent[CLASS_META]);
let mixins = extensions.map(toMixin);
Parent[CLASS_META].addSubclass(Subclass);
mixins.forEach(m => Subclass[CLASS_META].addMixin(m));
ClassMeta.applyAllMixins(Subclass, Parent);
return Subclass;
}
export function relinkSubclasses(Parent: GlimmerObjectFactory<any>) {
Parent[CLASS_META].getSubclasses().forEach((Subclass: GlimmerObjectFactory<any>) => {
Subclass[CLASS_META].reset(Parent[CLASS_META]);
Subclass.prototype = Object.create(Parent.prototype);
ClassMeta.applyAllMixins(Subclass, Parent);
// recurse into sub-subclasses
relinkSubclasses(Subclass);
});
}
export function toMixin(extension: Extension): Mixin {
if (extension instanceof Mixin) return extension;
else return new Mixin(<Object>extension, []);
}
class ValueDescriptor extends Descriptor {
public enumerable: boolean;
public configurable: boolean;
public writable: boolean;
public value: any;
constructor({ enumerable=true, configurable=true, writable=true, value }: PropertyDescriptor) {
super();
this.enumerable = enumerable;
this.configurable = configurable;
this.writable = writable;
this.value = value;
}
define(target: Object, key: string, home: Object) {
Object.defineProperty(target, key, {
enumerable: this.enumerable,
configurable: this.configurable,
writable: this.writable,
value: this.value
});
}
}
export class DataBlueprint extends Blueprint {
public enumerable: boolean;
public configurable: boolean;
public value: any;
public writable: boolean;
constructor({ enumerable=true, configurable=true, writable=true, value }: PropertyDescriptor) {
super();
this.enumerable = enumerable;
this.configurable = configurable;
this.value = value;
this.writable = writable;
}
descriptor(target: Object, key: string, classMeta: ClassMeta): Descriptor {
let { enumerable, configurable, writable, value } = this;
if (classMeta.hasConcatenatedProperty(<string>key)) {
classMeta.addConcatenatedProperty(<string>key, value);
value = classMeta.getConcatenatedProperty(<string>key);
} else if (classMeta.hasMergedProperty(<string>key)) {
classMeta.addMergedProperty(<string>key, value);
value = classMeta.getMergedProperty(<string>key);
}
return new ValueDescriptor({ enumerable, configurable, writable, value });
}
}
export abstract class AccessorBlueprint extends Blueprint {
public enumerable: boolean;
public configurable: boolean;
get: () => any;
set: (value: any) => void;
constructor({ enumerable=true, configurable=true, get, set }: PropertyDescriptor) {
super();
this.enumerable = enumerable;
this.configurable = configurable;
this.get = get;
this.set = set;
}
descriptor(target: Object, key: string, classMeta: ClassMeta): Descriptor {
return new ValueDescriptor({
enumerable: this.enumerable,
configurable: this.configurable,
get: this.get,
set: this.set
});
}
}
class MethodDescriptor extends ValueDescriptor {
define(target: Object, key: string, home: Object) {
this.value = wrapMethod(home, key, this.value);
super.define(target, key, home);
}
}
class MethodBlueprint extends DataBlueprint {
descriptor(target: Object, key: string, classMeta: ClassMeta): MethodDescriptor {
let desc = super.descriptor(target, key, classMeta);
return new MethodDescriptor(desc);
}
}
export function wrapMethod(home: Object, methodName: string, original: (...args) => any) {
if (!(<string>methodName in home)) return maybeWrap(original);
let superMethod = home[methodName];
let func = function(...args) {
if (!this) return original.apply(this, args);
let lastSuper = this._super;
this._super = superMethod;
try {
return original.apply(this, args);
} finally {
this._super = lastSuper;
}
};
(<any>func).__wrapped = true;
return func;
}
function maybeWrap(original: Function) {
if ('__wrapped' in original) return original;
return function(...args) {
if (!this) return original.apply(this, args);
let lastSuper = this._super;
this._super = ROOT;
try {
return original.apply(this, args);
} finally {
this._super = lastSuper;
}
};
}