diff --git a/src/common/traits/index.ts b/src/common/traits/index.ts index e2ca5df992..e92efba535 100644 --- a/src/common/traits/index.ts +++ b/src/common/traits/index.ts @@ -2,9 +2,11 @@ import { isString } from 'underscore'; import { Model } from '..'; import EditorModel from '../../editor/model/Editor'; import Trait, { TraitProperties } from './model/Trait'; +import TraitList from './model/TraitList'; import TraitButtonView, { TraitButtonViewOpts } from './view/TraitButtonView'; import TraitCheckboxView from './view/TraitCheckboxView'; import TraitColorView from './view/TraitColorView'; +import TraitListView from './view/TraitListView'; import { TraitNumberView, TraitNumberUnitView, @@ -22,12 +24,24 @@ export type InputViewProperties = | ({ type: 'select' } & TraitSelectViewOpts) | ({ type: 'checkbox' } & TraitViewOpts) | ({ type: 'color' } & TraitViewOpts) - | ({ type: 'button' } & TraitButtonViewOpts); + | ({ type: 'button' } & TraitButtonViewOpts) + | ({ type: 'list' } & TraitButtonViewOpts); + export type InputProperties = TraitProperties & { name: string }; + export default abstract class InputFactory { static build(model: Model, trait: string | (InputProperties & InputViewProperties) | Trait): Trait { if (!(trait instanceof Trait)) { - return isString(trait) ? new Trait(trait, model) : new Trait(trait.name, model, trait); + if (isString(trait)) { + return new Trait(trait, model); + } else { + switch (trait.type) { + case 'list': + return new TraitList(trait.name, model, trait); + default: + return new Trait(trait.name, model, trait); + } + } } else { return trait; } @@ -35,22 +49,18 @@ export default abstract class InputFactory { /** * Build props object by their name */ - static buildView( - target: Trait, - em: EditorModel, - opts?: InputViewProperties - ): TraitView { + static buildView>(target: T, em: EditorModel, opts?: InputViewProperties): TraitView { let type: string | undefined; let prop: any = { name: target.name, ...opts }; if (opts !== undefined) { type = opts.type; prop = opts; } - let view: TraitView; + let view: TraitView; switch (target.name) { case 'target': const options = em.Traits.config.optionsTarget; - view = new TraitSelectView(em, { name: target.name, ...prop, default: false, options }); + view = new TraitSelectView(em, { name: target.name, ...prop, default: false, options }) as any; break; default: const ViewClass = this.getView(type, prop); @@ -75,6 +85,8 @@ export default abstract class InputFactory { return TraitColorView; case 'button': return TraitButtonView; + case 'list': + return TraitListView; default: return TraitTextView; } diff --git a/src/common/traits/model/Trait.ts b/src/common/traits/model/Trait.ts index 537052eb50..36cfd2e8b3 100644 --- a/src/common/traits/model/Trait.ts +++ b/src/common/traits/model/Trait.ts @@ -10,13 +10,13 @@ export interface TraitProperties { changeProp?: boolean; } -export default class Trait { +export default class Trait { readonly name: string; opts: TraitProperties; - readonly model: Model; - private view?: OnUpdateView; + readonly model: TModel; + protected view?: OnUpdateView; - constructor(name: string, model: Model, opts?: TraitProperties) { + constructor(name: string, model: TModel, opts?: TraitProperties) { this.name = name; model.on('change:' + name, this.setValueFromModel, this); this.model = model; @@ -31,8 +31,8 @@ export default class Trait { const { changeProp, model, name } = this; const value = changeProp ? model.get(name) - : // @ts-ignore TODO update post component update - model.getAttributes()[name]; + : // TODO update post component update + model.get('attributes')[name]; return value ?? this.opts.default; } @@ -40,7 +40,7 @@ export default class Trait { return this.opts.changeProp ?? false; } - private updatingValue = false; + protected updatingValue = false; public set value(value: TraitValueType) { const { name, model, changeProp } = this; this.updatingValue = true; @@ -48,13 +48,12 @@ export default class Trait { if (changeProp) { model.set(name, value); } else { - //@ts-ignore - model.addAttributes({ [name]: value }); + model.set('attributes', { ...model.get('attributes'), [name]: value }); } this.updatingValue = false; } - private setValueFromModel() { + protected setValueFromModel() { if (!this.updatingValue) { this.view?.onUpdateEvent(this.value); } diff --git a/src/common/traits/model/TraitItem.ts b/src/common/traits/model/TraitItem.ts new file mode 100644 index 0000000000..f0fd5fb7c3 --- /dev/null +++ b/src/common/traits/model/TraitItem.ts @@ -0,0 +1,29 @@ +import { Model } from '../..'; +import Trait, { TraitProperties } from './Trait'; + +export interface TraitItemProperties extends TraitProperties { + prefix: string; +} + +export default class TraitItem extends Trait< + TModel, + TraitValueType +> { + readonly prefix: string; + constructor(name: string, model: TModel, opts: any) { + super(name, model, opts); + this.prefix = opts.prefix; + } + + public get value(): TraitValueType { + const { prefix, model, name } = this; + const value = model.get(prefix)[name]; + return value ?? this.opts.default; + } + public set value(value: TraitValueType) { + const { name, model, prefix } = this; + this.updatingValue = true; + model.set(prefix, { ...model.get(prefix), [name]: value }); + this.updatingValue = false; + } +} diff --git a/src/common/traits/model/TraitList.ts b/src/common/traits/model/TraitList.ts new file mode 100644 index 0000000000..395789c91b --- /dev/null +++ b/src/common/traits/model/TraitList.ts @@ -0,0 +1,66 @@ +import { InputProperties, InputViewProperties } from '..'; +import { Model } from '../..'; +import Trait, { TraitProperties } from './Trait'; +import TraitItem from './TraitItem'; + +export interface TraitListProperties extends TraitProperties { + default?: any; + value?: any; +} + +export default class TraitList extends Trait { + // traits: (InputViewProperties) [] + collection?: Trait[]; + constructor(name: string, model: TModel, opts?: TraitListProperties) { + super(name, model, { ...opts, type: 'list', changeProp: true } as any); + // this.model.on("all", (e) => console.log(e)) + // this.traits = opts?.traits ?? [{type: "list"}] + model.get(name) ?? model.set(name, {}); + } + + protected setValueFromModel() { + this.collection = undefined; + if (!this.updatingValue) { + this.view?.onUpdateEvent(this.value); + } + } + + public get value(): Trait[] { + const { model, name } = this; + if (!this.collection) { + const map = model.get(name); + this.collection = Object.keys(map).map( + key => new TraitItem(key, model, { type: 'text', name: key, prefix: name, value: map[key] }) + ); + } + return this.collection!; + } + + public set value(values: Trait[]) { + const { name, model } = this; + this.updatingValue = true; + + model.set( + name, + values.reduce((map: any, tr) => { + map[tr.name] = tr.value; + return map; + }, {}) + ); + this.updatingValue = false; + } + + public add(key: string) { + const { model, name } = this; + if (!this.collection?.find(tr => tr.name == key)) { + this.value.push(new TraitItem(key, model, { type: 'text', name: key, prefix: name })); + } + } + + public remove(key: string) { + const index = this.collection?.findIndex(tr => tr.name == key) ?? -1; + if (index > -1) { + this.value.splice(index, 1); + } + } +} diff --git a/src/common/traits/view/TraitButtonView.ts b/src/common/traits/view/TraitButtonView.ts index 14796790c3..6c9dde4963 100644 --- a/src/common/traits/view/TraitButtonView.ts +++ b/src/common/traits/view/TraitButtonView.ts @@ -2,6 +2,7 @@ import { isString } from 'underscore'; import { Model, $ } from '../..'; import Editor from '../../../editor'; import EditorModel from '../../../editor/model/Editor'; +import Trait from '../model/Trait'; import TraitView, { TraitViewOpts } from './TraitView'; export interface TraitButtonViewOpts extends TraitViewOpts { @@ -10,7 +11,7 @@ export interface TraitButtonViewOpts extends TraitViewOpts { full?: boolean; } -export default class TraitButtonView extends TraitView { +export default class TraitButtonView extends TraitView> { type = 'button'; command: string | ((e: Editor, m: TModel) => void); text?: string; diff --git a/src/common/traits/view/TraitCheckboxView.ts b/src/common/traits/view/TraitCheckboxView.ts index 19906d6185..687e61c4ba 100644 --- a/src/common/traits/view/TraitCheckboxView.ts +++ b/src/common/traits/view/TraitCheckboxView.ts @@ -1,7 +1,8 @@ import { Model } from 'backbone'; +import Trait from '../model/Trait'; import TraitView from './TraitView'; -export default class TraitCheckboxView extends TraitView { +export default class TraitCheckboxView extends TraitView> { type = 'checkbox'; appendInput = false; diff --git a/src/common/traits/view/TraitColorView.ts b/src/common/traits/view/TraitColorView.ts index 0aa908b430..8421a93071 100644 --- a/src/common/traits/view/TraitColorView.ts +++ b/src/common/traits/view/TraitColorView.ts @@ -2,6 +2,7 @@ import TraitView from './TraitView'; import { Model, $ } from '../..'; import { isUndefined } from 'underscore'; import ColorPicker from '../../../utils/ColorPicker'; +import Trait from '../model/Trait'; $ && ColorPicker($); @@ -11,7 +12,7 @@ const getColor = (color: any) => { return name || cl.replace(/ /g, ''); }; -export default class TraitColorView extends TraitView { +export default class TraitColorView extends TraitView> { type = 'text'; colorPicker?: any; templateInput() { diff --git a/src/common/traits/view/TraitListView.ts b/src/common/traits/view/TraitListView.ts new file mode 100644 index 0000000000..279dbf822d --- /dev/null +++ b/src/common/traits/view/TraitListView.ts @@ -0,0 +1,58 @@ +import { isString } from 'underscore'; +import TraitView from './TraitView'; +import { Model } from '../..'; +import EditorModel from '../../../editor/model/Editor'; +import TraitList, { TraitListProperties } from '../model/TraitList'; +import { TraitViewOpts } from './TraitView'; +import InputFactory from '..'; + +export interface TraitListViewOpts extends TraitViewOpts { + default?: any; + name?: string; + label?: string; + paceholder?: string; + noLabel?: boolean; +} + +export default class TraitListView extends TraitView> { + protected type = 'list'; + + get inputValue(): any { + return this.target.value; + } + set inputValue(value: any) {} + constructor(em: EditorModel, opts?: TraitListViewOpts) { + super(em, opts); + } + + setTarget(popertyName: string, model: TModel, opts?: TraitListProperties): this; + setTarget(target: TraitList): this; + setTarget(target: unknown, model?: TModel, opts?: TraitListProperties) { + if (isString(target) && model !== undefined) { + target = new TraitList(target, model, opts); + } + this.target = target as TraitList; + this.model = this.target.model as any; + this.name ?? (this.name = this.target.name); + this.target.registerForUpdateEvent(this); + return this; + } + + render() { + const { em } = this; + var frag = document.createDocumentFragment(); + this.$el.empty(); + + if (this.target.value.length) { + this.target.value.forEach(view => { + const rendered = InputFactory.buildView(view, em, view.opts).render().el; + console.log(rendered); + frag.appendChild(rendered); + }); + } + console.log(frag); + + this.$el.append(frag); + return this; + } +} diff --git a/src/common/traits/view/TraitNumberView.ts b/src/common/traits/view/TraitNumberView.ts index 10fa9c9828..9ce0cec5f5 100644 --- a/src/common/traits/view/TraitNumberView.ts +++ b/src/common/traits/view/TraitNumberView.ts @@ -2,6 +2,7 @@ import { bindAll, indexOf, isUndefined } from 'underscore'; import { Model, $ } from '../..'; import EditorModel from '../../../editor/model/Editor'; import { off, on } from '../../../utils/dom'; +import Trait from '../model/Trait'; import TraitView, { TraitViewOpts } from './TraitView'; export interface TraitNumberViewOpts extends TraitViewOpts { @@ -11,7 +12,9 @@ export interface TraitNumberViewOpts extends TraitViewOpts { fixedValues?: string[]; } -abstract class TraitNumberViewAbstract extends TraitView { +abstract class TraitNumberViewAbstract extends TraitView< + Trait +> { protected type = 'number'; moved?: boolean; prValue?: number; diff --git a/src/common/traits/view/TraitSelectView.ts b/src/common/traits/view/TraitSelectView.ts index 211f0e4a24..d3f4a98d59 100644 --- a/src/common/traits/view/TraitSelectView.ts +++ b/src/common/traits/view/TraitSelectView.ts @@ -1,6 +1,7 @@ import { isString, isUndefined } from 'underscore'; import { Model, $ } from '../..'; import EditorModel from '../../../editor/model/Editor'; +import Trait from '../model/Trait'; import TraitView, { TraitViewOpts } from './TraitView'; type SelectOption = @@ -15,7 +16,7 @@ export interface TraitSelectViewOpts extends TraitViewOpts { options: SelectOption[]; } -export default class TraitSelectView extends TraitView { +export default class TraitSelectView extends TraitView> { protected type = 'select'; options: SelectOption[]; diff --git a/src/common/traits/view/TraitTextView.ts b/src/common/traits/view/TraitTextView.ts index 47792a004a..2017b16ff5 100644 --- a/src/common/traits/view/TraitTextView.ts +++ b/src/common/traits/view/TraitTextView.ts @@ -1,7 +1,8 @@ import { Model } from '../..'; +import Trait from '../model/Trait'; import TraitView from './TraitView'; -export default class TraitTextView extends TraitView { +export default class TraitTextView extends TraitView> { protected type: string = 'text'; getInputElem() { diff --git a/src/common/traits/view/TraitView.ts b/src/common/traits/view/TraitView.ts index 79a726d0dd..edf8f7add5 100644 --- a/src/common/traits/view/TraitView.ts +++ b/src/common/traits/view/TraitView.ts @@ -15,9 +15,12 @@ export interface TraitViewOpts { noLabel?: boolean; } -export default abstract class TraitView - extends View - implements OnUpdateView +type ModelFromTrait = TTarget extends Trait ? M : unknown; +type ValueFromTrait = TTarget extends Trait ? M : unknown; + +export default abstract class TraitView + extends View> + implements OnUpdateView> { pfx: string; ppfx: string; @@ -36,7 +39,7 @@ export default abstract class TraitView; + target!: Target; events(): EventsHash { return { @@ -52,7 +55,7 @@ export default abstract class TraitView${label}`; } - templateInput(defaultValue: TraitValueType) { + templateInput(defaultValue: ValueFromTrait) { const { clsField } = this; return `
`; } @@ -67,24 +70,24 @@ export default abstract class TraitView): this; - setTarget(target: unknown, model?: TModel, opts?: TraitProperties) { + setTarget(popertyName: string, model: ModelFromTrait, opts?: TraitProperties): this; + setTarget(target: Target): this; + setTarget(target: unknown, model?: ModelFromTrait, opts?: TraitProperties) { if (isString(target) && model !== undefined) { target = new Trait(target, model, opts); } - this.target = target as Trait; + this.target = target as Target; this.model = this.target.model as any; this.name ?? (this.name = this.target.name); - this.listenTo(model, 'change:label', this.render); - this.listenTo(model, 'change:placeholder', this.rerender); + // this.listenTo(model, 'change:label', this.render); + // this.listenTo(model, 'change:placeholder', this.rerender); this.target.registerForUpdateEvent(this); return this; } - abstract get inputValue(): TraitValueType; + abstract get inputValue(): ValueFromTrait; - abstract set inputValue(value: TraitValueType); + abstract set inputValue(value: ValueFromTrait); /** * Fires when the input is changed @@ -94,7 +97,7 @@ export default abstract class TraitView) { this.inputValue = value; } diff --git a/src/common/traits/view/TraitViewList.ts b/src/common/traits/view/TraitsView.ts similarity index 81% rename from src/common/traits/view/TraitViewList.ts rename to src/common/traits/view/TraitsView.ts index b8ca00fa00..4c0ae20567 100644 --- a/src/common/traits/view/TraitViewList.ts +++ b/src/common/traits/view/TraitsView.ts @@ -1,12 +1,11 @@ import { TraitView } from '..'; import { View } from '../..'; -export default class TraitViewList extends View { +export default class TraitsView extends View { inputs: TraitView[]; constructor(inputs?: TraitView[], el?: any) { super({ el }); this.inputs = inputs ?? []; - console.log(this.inputs); } add(input: TraitView) { @@ -24,11 +23,9 @@ export default class TraitViewList extends View { if (this.inputs.length) { this.inputs.forEach(view => { const rendered = view.render().el; - console.log(rendered); frag.appendChild(rendered); }); } - console.log(frag); this.$el.append(frag); return this; diff --git a/src/trait_manager/index.ts b/src/trait_manager/index.ts index b7f557ec4c..2e26536572 100644 --- a/src/trait_manager/index.ts +++ b/src/trait_manager/index.ts @@ -2,7 +2,6 @@ import { debounce } from 'underscore'; import { Model } from '../common'; import { Module } from '../abstract'; import defaults, { TraitManagerConfig } from './config/config'; -import TraitsView from './view/TraitsView'; import TraitView from './view/TraitView'; import TraitSelectView from './view/TraitSelectView'; import TraitCheckboxView from './view/TraitCheckboxView'; @@ -12,7 +11,7 @@ import TraitButtonView from './view/TraitButtonView'; import EditorModel from '../editor/model/Editor'; import Component from '../dom_components/model/Component'; import Trait from '../common/traits/model/Trait'; -import TraitViewList from '../common/traits/view/TraitViewList'; +import TraitsView from '../common/traits/view/TraitsView'; import InputFactory from '../common/traits'; export const evAll = 'trait'; @@ -41,7 +40,7 @@ interface ITraitView { export type CustomTrait = ITraitView & T & ThisType; export default class TraitManager extends Module { - view?: TraitViewList; + view?: TraitsView; types: { [id: string]: { new (o: any): TraitView } }; model: Model; __ctn?: any; @@ -153,7 +152,7 @@ export default class TraitManager extends Module InputFactory.buildView(trait, em, trait.opts as any)); - this.view = new TraitViewList(traitViews, el).render(); + this.view = new TraitsView(traitViews, el).render(); return this.view.el; }