From 877b104a61d2ab072e3b6a006168ec03e2c46365 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Tue, 30 Apr 2019 00:11:27 -0400 Subject: Mostly fixed lists --- src/new_fields/Doc.ts | 10 +-- src/new_fields/List.ts | 192 ++++++++++++++++++++++++++++++++++++++++--------- src/new_fields/util.ts | 8 +++ 3 files changed, 173 insertions(+), 37 deletions(-) (limited to 'src/new_fields') diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index d15b6309d..6ddb0df89 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -3,7 +3,7 @@ import { serializable, primitive, map, alias, list } from "serializr"; import { autoObject, SerializationHelper, Deserializable } from "../client/util/SerializationHelper"; import { Utils } from "../Utils"; import { DocServer } from "../client/DocServer"; -import { setter, getter, getField, updateFunction } from "./util"; +import { setter, getter, getField, updateFunction, deleteProperty } from "./util"; import { Cast, ToConstructor, PromiseValue, FieldValue } from "./Types"; import { UndoManager, undoBatch } from "../client/util/UndoManager"; import { listSpec } from "./Schema"; @@ -34,7 +34,7 @@ export class Doc extends RefField { set: setter, get: getter, ownKeys: target => Object.keys(target.__fields), - deleteProperty: () => { throw new Error("Currently properties can't be deleted from documents, assign to undefined instead"); }, + deleteProperty: deleteProperty, defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); }, }); if (!id || forceSave) { @@ -151,8 +151,8 @@ export namespace Doc { } export function MakeLink(source: Doc, target: Doc): Doc { - let linkDoc = new Doc; - UndoManager.RunInBatch(() => { + return UndoManager.RunInBatch(() => { + let linkDoc = new Doc; linkDoc.title = "New Link"; linkDoc.linkDescription = ""; linkDoc.linkTags = "Default"; @@ -171,8 +171,8 @@ export namespace Doc { source.linkedToDocs = linkedTo = new List(); } linkedTo.push(linkDoc); + return linkDoc; }, "make link"); - return linkDoc; } export function MakeDelegate(doc: Doc): Doc; diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts index e4a80f7a1..ec1bf44a9 100644 --- a/src/new_fields/List.ts +++ b/src/new_fields/List.ts @@ -1,21 +1,173 @@ import { Deserializable, autoObject } from "../client/util/SerializationHelper"; import { Field, Update, Self } from "./Doc"; -import { setter, getter } from "./util"; +import { setter, getter, deleteProperty } from "./util"; import { serializable, alias, list } from "serializr"; import { observable, observe, IArrayChange, IArraySplice, IObservableArray, Lambda, reaction } from "mobx"; import { ObjectField, OnUpdate } from "./ObjectField"; +import { RefField } from "./RefField"; +import { ProxyField } from "./Proxy"; const listHandlers: any = { - push(...items: any[]) { - // console.log("push"); - // console.log(...items); - return this[Self].__fields.push(...items); + /// Mutator methods + copyWithin() { + throw new Error("copyWithin not supported yet"); + }, + fill(value: any, start?: number, end?: number) { + if (value instanceof RefField) { + throw new Error("fill with RefFields not supported yet"); + } + const res = this[Self].__fields.fill(value, start, end); + this[Update](); + return res; }, pop(): any { - return this[Self].__fields.pop(); + const field = toRealField(this[Self].__fields.pop()); + this[Update](); + return field; + }, + push(...items: any[]) { + items = items.map(toObjectField); + const res = this[Self].__fields.push(...items); + this[Update](); + return res; + }, + reverse() { + const res = this[Self].__fields.reverse(); + this[Update](); + return res; + }, + shift() { + const res = toRealField(this[Self].__fields.shift()); + this[Update](); + return res; + }, + sort(cmpFunc: any) { + const res = this[Self].__fields.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined); + this[Update](); + return res; + }, + splice(start: number, deleteCount: number, ...items: any[]) { + items = items.map(toObjectField); + const res = this[Self].__fields.splice(start, deleteCount, ...items); + this[Update](); + return res.map(toRealField); + }, + unshift(...items: any[]) { + items = items.map(toObjectField); + const res = this[Self].__fields.unshift(...items); + this[Update](); + return res; + + }, + /// Accessor methods + concat(...items: any[]) { + return this[Self].__fields.map(toRealField).concat(...items); + }, + includes(valueToFind: any, fromIndex: number) { + const fields = this[Self].__fields; + if (valueToFind instanceof RefField) { + return fields.map(toRealField).includes(valueToFind, fromIndex); + } else { + return fields.includes(valueToFind, fromIndex); + } + }, + indexOf(valueToFind: any, fromIndex: number) { + const fields = this[Self].__fields; + if (valueToFind instanceof RefField) { + return fields.map(toRealField).indexOf(valueToFind, fromIndex); + } else { + return fields.indexOf(valueToFind, fromIndex); + } + }, + join(separator: any) { + return this[Self].__fields.map(toRealField).join(separator); + }, + lastIndexOf(valueToFind: any, fromIndex: number) { + const fields = this[Self].__fields; + if (valueToFind instanceof RefField) { + return fields.map(toRealField).lastIndexOf(valueToFind, fromIndex); + } else { + return fields.lastIndexOf(valueToFind, fromIndex); + } + }, + slice(begin: number, end: number) { + return this[Self].__fields.slice(begin, end).map(toRealField); + }, + + /// Iteration methods + entries() { + return this[Self].__fields.map(toRealField).entries(); + }, + every(callback: any, thisArg: any) { + return this[Self].__fields.map(toRealField).every(callback, thisArg); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + }, + filter(callback: any, thisArg: any) { + return this[Self].__fields.map(toRealField).filter(callback, thisArg); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + }, + find(callback: any, thisArg: any) { + return this[Self].__fields.map(toRealField).find(callback, thisArg); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + }, + findIndex(callback: any, thisArg: any) { + return this[Self].__fields.map(toRealField).findIndex(callback, thisArg); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + }, + forEach(callback: any, thisArg: any) { + return this[Self].__fields.map(toRealField).forEach(callback, thisArg); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + }, + map(callback: any, thisArg: any) { + return this[Self].__fields.map(toRealField).map(callback, thisArg); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + }, + reduce(callback: any, initialValue: any) { + return this[Self].__fields.map(toRealField).reduce(callback, initialValue); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue); + }, + reduceRight(callback: any, initialValue: any) { + return this[Self].__fields.map(toRealField).reduceRight(callback, initialValue); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue); + }, + some(callback: any, thisArg: any) { + return this[Self].__fields.map(toRealField).some(callback, thisArg); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + }, + values() { + return this[Self].__fields.map(toRealField).values(); + }, + [Symbol.iterator]() { + return this[Self].__fields.map(toRealField).values(); } }; +function toObjectField(field: Field) { + return field instanceof RefField ? new ProxyField(field) : field; +} + +function toRealField(field: Field) { + return field instanceof ProxyField ? field.value() : field; +} + function listGetter(target: any, prop: string | number | symbol, receiver: any): any { if (listHandlers.hasOwnProperty(prop)) { return listHandlers[prop]; @@ -38,36 +190,15 @@ interface ListIndexUpdate { type ListUpdate = ListSpliceUpdate | ListIndexUpdate; -const ObserveDisposer = Symbol("Observe Disposer"); - -function listObserver(this: ListImpl, change: IArrayChange | IArraySplice) { - if (change.type === "splice") { - this[Update]({ - index: change.index, - removedCount: change.removedCount, - added: change.added, - type: change.type - }); - } else { - //This should already be handled by the getter for the Proxy - // this[Update]({ - // index: change.index, - // newValue: change.newValue, - // type: change.type - // }); - } -} - @Deserializable("list") class ListImpl extends ObjectField { constructor(fields: T[] = []) { super(); this.___fields = fields; - this[ObserveDisposer] = observe(this.__fields as IObservableArray, listObserver.bind(this)); const list = new Proxy(this, { set: setter, - get: getter, - deleteProperty: () => { throw new Error("Currently properties can't be deleted from documents, assign to undefined instead"); }, + get: listGetter, + deleteProperty: deleteProperty, defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); }, }); return list; @@ -82,8 +213,6 @@ class ListImpl extends ObjectField { private set __fields(value) { this.___fields = value; - this[ObserveDisposer](); - this[ObserveDisposer] = observe(this.__fields as IObservableArray, listObserver.bind(this)); } // @serializable(alias("fields", list(autoObject()))) @@ -97,7 +226,6 @@ class ListImpl extends ObjectField { update && update(); } - private [ObserveDisposer]: Lambda; private [Self] = this; } export type List = ListImpl & T[]; diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts index 511820115..128817ab8 100644 --- a/src/new_fields/util.ts +++ b/src/new_fields/util.ts @@ -78,6 +78,14 @@ export function getField(target: any, prop: string | number, ignoreProto: boolea return field; } +export function deleteProperty(target: any, prop: string | number | symbol) { + if (typeof prop === "symbol") { + delete target[prop]; + return true; + } + throw new Error("Currently properties can't be deleted from documents, assign to undefined instead"); +} + export function updateFunction(target: any, prop: any, value: any) { return (diff?: any) => { if (!diff) diff = { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } }; -- cgit v1.2.3-70-g09d2 From e013b7b146f91b0ffbc26e3770f5f90f417da60b Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 30 Apr 2019 12:09:54 -0400 Subject: fixed inking --- src/client/views/InkingCanvas.tsx | 18 +++++++++++------- src/client/views/InkingStroke.scss | 3 +++ src/client/views/InkingStroke.tsx | 8 ++++++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 17 ++++++++++------- src/new_fields/Doc.ts | 6 +++++- 5 files changed, 35 insertions(+), 17 deletions(-) create mode 100644 src/client/views/InkingStroke.scss (limited to 'src/new_fields') diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx index 1e26893c5..1c0d13545 100644 --- a/src/client/views/InkingCanvas.tsx +++ b/src/client/views/InkingCanvas.tsx @@ -139,21 +139,25 @@ export class InkingCanvas extends React.Component { let curPage = NumCast(this.props.Document.curPage, -1); let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => { if (strokeData.page === -1 || strokeData.page === curPage) { - paths.push(); + color={strokeData.color} + width={strokeData.width} + tool={strokeData.tool} + deleteCallback={this.removeLine} />); } return paths; }, [] as JSX.Element[]); - return [ - {paths.filter(path => path.props.tool === InkTool.Highlighter)} + {paths.filter(path => path.props.tool !== InkTool.Highlighter)} , - - {paths.filter(path => path.props.tool !== InkTool.Highlighter)} + {paths.filter(path => path.props.tool === InkTool.Highlighter)} ]; } diff --git a/src/client/views/InkingStroke.scss b/src/client/views/InkingStroke.scss new file mode 100644 index 000000000..cdbfdcff3 --- /dev/null +++ b/src/client/views/InkingStroke.scss @@ -0,0 +1,3 @@ +.inkingStroke-marker { + mix-blend-mode: multiply +} \ No newline at end of file diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 616299146..37b1f5899 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -1,14 +1,16 @@ import { observer } from "mobx-react"; -import { observable } from "mobx"; +import { observable, trace } from "mobx"; import { InkingControl } from "./InkingControl"; import React = require("react"); import { InkTool } from "../../new_fields/InkField"; +import "./InkingStroke.scss"; interface StrokeProps { offsetX: number; offsetY: number; id: string; + count: number; line: Array<{ x: number, y: number }>; color: string; width: string; @@ -48,10 +50,12 @@ export class InkingStroke extends React.Component { render() { let pathStyle = this.createStyle(); let pathData = this.parseData(this.props.line); + let pathlength = this.props.count; // bcz: this is needed to force reactions to the line data changes + let marker = this.props.tool === InkTool.Highlighter ? "-marker" : ""; let pointerEvents: any = InkingControl.Instance.selectedTool === InkTool.Eraser ? "all" : "none"; return ( - ); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index d796bd8d5..2689f48cd 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,4 +1,4 @@ -import { action, computed } from "mobx"; +import { action, computed, trace } from "mobx"; import { observer } from "mobx-react"; import { emptyFunction, returnFalse, returnOne } from "../../../../Utils"; import { DocumentManager } from "../../../util/DocumentManager"; @@ -19,7 +19,7 @@ import { MarqueeView } from "./MarqueeView"; import React = require("react"); import v5 = require("uuid/v5"); import { createSchema, makeInterface, listSpec } from "../../../../new_fields/Schema"; -import { Doc } from "../../../../new_fields/Doc"; +import { Doc, WidthSym, HeightSym } from "../../../../new_fields/Doc"; import { FieldValue, Cast, NumCast } from "../../../../new_fields/Types"; import { pageSchema } from "../../nodes/ImageBox"; import { List } from "../../../../new_fields/List"; @@ -243,11 +243,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { focusDocument = (doc: Doc) => { this.setPan( - Cast(doc.x, "number", 0) + Cast(doc.width, "number", 0) / 2, - Cast(doc.y, "number", 0) + Cast(doc.height, "number", 0) / 2); + NumCast(doc.x) + NumCast(doc.width) / 2, + NumCast(doc.y) + NumCast(doc.height) / 2); this.props.focus(this.props.Document); } + getDocumentViewProps(document: Doc): DocumentViewProps { return { Document: document, @@ -258,8 +259,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ScreenToLocalTransform: this.getTransform, isTopMost: false, selectOnLoad: document[Id] === this._selectOnLoaded, - PanelWidth: () => Cast(document.width, "number", 0),//TODO Types These are inline functions - PanelHeight: () => Cast(document.height, "number", 0), + PanelWidth: document[WidthSym], + PanelHeight: document[HeightSym], ContentScaling: returnOne, ContainingCollectionView: this.props.CollectionView, focus: this.focusDocument, @@ -268,8 +269,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }; } - @computed + @computed.struct get views() { + trace(); let curPage = FieldValue(this.Document.curPage, -1); let docviews = (this.children || []).filter(doc => doc).reduce((prev, doc) => { if (!FieldValue(doc)) return prev; @@ -295,6 +297,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private childViews = () => [...this.views, ]; render() { + trace(); const containerName = `collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`; return (
= Opt | FieldWaiting { return NumCast(this.__fields.width); } // bcz: is this the right way to access width/height? it didn't work with : this.width + public [HeightSym] = () => { return NumCast(this.__fields.height); } } export namespace Doc { -- cgit v1.2.3-70-g09d2 From c95e1789da41fb63e27f1086e30c0ebd151009df Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 30 Apr 2019 15:58:57 -0400 Subject: fixed icons --- src/client/documents/Documents.ts | 1 + src/client/views/DocumentDecorations.tsx | 2 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 41 ++++++++++------------ src/client/views/nodes/IconBox.tsx | 2 +- src/new_fields/IconField.ts | 6 ++-- 5 files changed, 25 insertions(+), 27 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ff21dd5b0..964faa8db 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -125,6 +125,7 @@ export namespace Docs { function CreateIconPrototype(): Doc { let iconProto = setupPrototypeOptions(iconProtoId, "ICON_PROTO", IconBox.LayoutString(), { x: 0, y: 0, width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE) }); + console.log("iconpr" + iconProto.layout) return iconProto; } function CreateTextPrototype(): Doc { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 1dc7c8394..084220f76 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -231,9 +231,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> iconDoc.nativeHeight = 0; iconDoc.x = NumCast(doc.x); iconDoc.y = NumCast(doc.y) - 24; - iconDoc.proto = doc; iconDoc.maximizedDoc = doc; doc.minimizedDoc = iconDoc; + console.log("Layout " + iconDoc.layout) docView.props.addDocument && docView.props.addDocument(iconDoc, false); return iconDoc; } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index c8f0bca91..56c2a80fa 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -103,7 +103,7 @@ export class CollectionFreeFormDocumentView extends DocComponent d); docs.push(this.props.Document); - minimizedDocSet.map(async minimizedDoc => { - if (minimizedDoc instanceof Document) { - this.props.addDocument && this.props.addDocument(minimizedDoc, false); - let maximizedDoc = await Cast(minimizedDoc.maximizedDoc, Doc); - if (maximizedDoc && !(maximizedDoc as any).isIconAnimating) { - (maximizedDoc as any).isIconAnimating = true; - if (isMinimized === undefined) { - let maximizedDocMinimizedState = Cast(maximizedDoc.isMinimized, "boolean"); - isMinimized = (maximizedDocMinimizedState) ? true : false; - } - let minx = NumCast(minimizedDoc.x, undefined); - let miny = NumCast(minimizedDoc.y, undefined); - let maxx = NumCast(maximizedDoc.x, undefined); - let maxy = NumCast(maximizedDoc.y, undefined); - let maxw = NumCast(maximizedDoc.width, undefined); - let maxh = NumCast(maximizedDoc.height, undefined); - if (minx !== undefined && miny !== undefined && maxx !== undefined && maxy !== undefined && - maxw !== undefined && maxh !== undefined) { - this.animateBetweenIcon(true, [minx, miny], [maxx, maxy], maxw, maxh, Date.now(), maximizedDoc, isMinimized); - } + docs.map(async minimizedDoc => { + this.props.addDocument && this.props.addDocument(minimizedDoc, false); + let maximizedDoc = await Cast(minimizedDoc.maximizedDoc, Doc); + if (maximizedDoc && !maximizedDoc.isIconAnimating) { + maximizedDoc.isIconAnimating = true; + if (isMinimized === undefined) { + let maximizedDocMinimizedState = Cast(maximizedDoc.isMinimized, "boolean"); + isMinimized = (maximizedDocMinimizedState) ? true : false; + } + let minx = NumCast(minimizedDoc.x, undefined); + let miny = NumCast(minimizedDoc.y, undefined); + let maxx = NumCast(maximizedDoc.x, undefined); + let maxy = NumCast(maximizedDoc.y, undefined); + let maxw = NumCast(maximizedDoc.width, undefined); + let maxh = NumCast(maximizedDoc.height, undefined); + if (minx !== undefined && miny !== undefined && maxx !== undefined && maxy !== undefined && + maxw !== undefined && maxh !== undefined) { + this.animateBetweenIcon(true, [minx, miny], [maxx, maxy], maxw, maxh, Date.now(), maximizedDoc, isMinimized); } - } }) } diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx index f7cceb3d4..7a0c49735 100644 --- a/src/client/views/nodes/IconBox.tsx +++ b/src/client/views/nodes/IconBox.tsx @@ -23,7 +23,7 @@ export class IconBox extends React.Component { public static LayoutString() { return FieldView.LayoutString(IconBox); } @computed get maximized() { return Cast(this.props.Document.maximizedDoc, Doc); } - @computed get layout(): string { const field = Cast(this.props.Document[this.props.fieldKey], IconField); return field ? field.layout : "

Error loading layout data

"; } + @computed get layout(): string { const field = Cast(this.props.Document[this.props.fieldKey], IconField); return field ? field.icon : "

Error loading icon data

"; } @computed get minimizedIcon() { return IconBox.DocumentIcon(this.layout); } public static DocumentIcon(layout: string) { diff --git a/src/new_fields/IconField.ts b/src/new_fields/IconField.ts index 46f111f8e..c79a2f79a 100644 --- a/src/new_fields/IconField.ts +++ b/src/new_fields/IconField.ts @@ -5,10 +5,10 @@ import { ObjectField } from "./ObjectField"; @Deserializable("icon") export class IconField extends ObjectField { @serializable(primitive()) - readonly layout: string; + readonly icon: string; - constructor(layout: string) { + constructor(icon: string) { super(); - this.layout = layout; + this.icon = icon; } } -- cgit v1.2.3-70-g09d2