From 121b8390882fda7053a485914e0099060ee613cf Mon Sep 17 00:00:00 2001 From: danstarns Date: Sun, 4 Aug 2024 10:31:18 -0700 Subject: [PATCH] feat: init datasources into style --- src/data_sources/model/StyleDataVariable.ts | 14 +++ src/dom_components/model/Component.ts | 2 +- src/dom_components/model/types.ts | 2 +- src/domain_abstract/model/StyleableModel.ts | 93 +++++++++++++++++--- src/style_manager/index.ts | 2 + src/style_manager/model/PropertyComposite.ts | 28 +++++- test/specs/data_sources/index.ts | 40 +++++++++ 7 files changed, 165 insertions(+), 16 deletions(-) create mode 100644 src/data_sources/model/StyleDataVariable.ts diff --git a/src/data_sources/model/StyleDataVariable.ts b/src/data_sources/model/StyleDataVariable.ts new file mode 100644 index 000000000..03c927182 --- /dev/null +++ b/src/data_sources/model/StyleDataVariable.ts @@ -0,0 +1,14 @@ +import { Model, ObjectAny } from '../../common'; +export const type = 'data-variable-css'; + +interface StyleDataVariableProps extends ObjectAny { + type: string; + path: string; + value: string; +} + +export default class StyleDataVariable extends Model { + constructor(props: T, opts = {}) { + super(props, opts); + } +} diff --git a/src/dom_components/model/Component.ts b/src/dom_components/model/Component.ts index 6473fe6f9..c380172dc 100644 --- a/src/dom_components/model/Component.ts +++ b/src/dom_components/model/Component.ts @@ -698,7 +698,7 @@ export default class Component extends StyleableModel { if (avoidInline(em) && !opt.temporary && !opts.inline) { const style = this.get('style') || {}; prop = isString(prop) ? this.parseStyle(prop) : prop; - prop = { ...prop, ...style }; + prop = { ...prop, ...(style as any) }; const state = em.get('state'); const cc = em.Css; const propOrig = this.getStyle(opts); diff --git a/src/dom_components/model/types.ts b/src/dom_components/model/types.ts index 2075edaed..73039c2ef 100644 --- a/src/dom_components/model/types.ts +++ b/src/dom_components/model/types.ts @@ -175,7 +175,7 @@ export interface ComponentProperties { * Component default style, eg. `{ width: '100px', height: '100px', 'background-color': 'red' }` * @default {} */ - style?: any; + style?: string | Record; /** * Component related styles, eg. `.my-component-class { color: red }` * @default '' diff --git a/src/domain_abstract/model/StyleableModel.ts b/src/domain_abstract/model/StyleableModel.ts index 811c8466f..5fa1cd572 100644 --- a/src/domain_abstract/model/StyleableModel.ts +++ b/src/domain_abstract/model/StyleableModel.ts @@ -2,9 +2,20 @@ import { isArray, isString, keys } from 'underscore'; import { Model, ObjectAny, ObjectHash, SetOptions } from '../../common'; import ParserHtml from '../../parser/model/ParserHtml'; import Selectors from '../../selector_manager/model/Selectors'; -import { shallowDiff } from '../../utils/mixins'; - -export type StyleProps = Record; +import { shallowDiff, get, stringToPath } from '../../utils/mixins'; +import EditorModel from '../../editor/model/Editor'; +import StyleDataVariable from '../../data_sources/model/StyleDataVariable'; + +export type StyleProps = Record< + string, + | string + | string[] + | { + type: 'data-variable'; + value: string; + path: string; + } +>; export type UpdateStyleOptions = SetOptions & { partial?: boolean; @@ -20,6 +31,8 @@ export const getLastStyleValue = (value: string | string[]) => { }; export default class StyleableModel extends Model { + em?: EditorModel; + /** * Forward style string to `parseStyle` to be parse to an object * @param {string} str @@ -46,6 +59,11 @@ export default class StyleableModel extends Model getStyle(prop?: string | ObjectAny): StyleProps { const style = this.get('style') || {}; const result: ObjectAny = { ...style }; + if (this.em) { + const resolvedStyle = this.resolveDataVariables({ ...result }); + // @ts-ignore + return prop && isString(prop) ? resolvedStyle[prop] : resolvedStyle; + } return prop && isString(prop) ? result[prop] : result; } @@ -77,10 +95,14 @@ export default class StyleableModel extends Model delete newStyle[prop]; } }); + + this.convertToStyleDataVariable(newStyle); + this.set('style', newStyle, opts as any); - const diff = shallowDiff(propOrig, propNew); + const diff = shallowDiff(propOrig, newStyle); // Delete the property used for partial updates delete diff.__p; + keys(diff).forEach(pr => { // @ts-ignore const { em } = this; @@ -92,7 +114,61 @@ export default class StyleableModel extends Model } }); - return propNew; + this.processDataVariableStyles(newStyle); + + return newStyle; + } + + convertToStyleDataVariable(style: StyleProps) { + keys(style).forEach(key => { + const styleValue = style[key]; + // @ts-ignore + if (typeof styleValue === 'object' && styleValue.type === 'data-variable') { + // @ts-ignore + style[key] = new StyleDataVariable(styleValue, { em: this.em }); + } + }); + } + + processDataVariableStyles(style: StyleProps) { + keys(style).forEach(key => { + const styleValue = style[key]; + // @ts-ignore + if (styleValue instanceof StyleDataVariable) { + // @ts-ignore + this.listenToDataVariable(styleValue, key); + } + }); + } + + listenToDataVariable(dataVar: StyleDataVariable, styleProp: string) { + this.listenTo(dataVar, 'change:value', (model: any) => { + const newValue = model.get('value'); + this.updateStyleProp(styleProp, newValue); + }); + } + + updateStyleProp(prop: string, value: string) { + const style = this.getStyle(); + style[prop] = value; + // @ts-ignore + this.set('style', style, { noEvent: true }); + this.trigger(`change:style:${prop}`); + } + + resolveDataVariables(style: StyleProps): StyleProps { + const resolvedStyle = { ...style }; + keys(resolvedStyle).forEach(key => { + const styleValue = resolvedStyle[key]; + // @ts-ignore + if (styleValue instanceof StyleDataVariable) { + // @ts-ignore + const resolvedValue = this.em?.DataSources.getValue(styleValue.get('path'), styleValue.get('value')); + // @ts-ignore + resolvedStyle[key] = resolvedValue || styleValue.get('value'); + } + }); + return resolvedStyle; } /** @@ -147,7 +223,7 @@ export default class StyleableModel extends Model const value = style[prop]; const values = isArray(value) ? (value as string[]) : [value]; - values.forEach((val: string) => { + (values as string[]).forEach((val: string) => { const value = `${val}${important ? ' !important' : ''}`; value && result.push(`${prop}:${value};`); }); @@ -164,9 +240,4 @@ export default class StyleableModel extends Model // @ts-ignore return this.selectorsToString ? this.selectorsToString(opts) : this.getSelectors().getFullString(); } - - // @ts-ignore - // _validate(attr, opts) { - // return true; - // } } diff --git a/src/style_manager/index.ts b/src/style_manager/index.ts index 9bb5f2825..77f4b12e1 100644 --- a/src/style_manager/index.ts +++ b/src/style_manager/index.ts @@ -395,6 +395,7 @@ export default class StyleManager extends ItemManagerModule< if (isString(target)) { const rule = cssc.getRule(target) || cssc.setRule(target); !isUndefined(stylable) && rule.set({ stylable }); + // @ts-ignore model = rule; } @@ -652,6 +653,7 @@ export default class StyleManager extends ItemManagerModule< .reverse(); // Slice removes rules not related to the current device + // @ts-ignore result = all.slice(all.indexOf(target as CssRule) + 1); } diff --git a/src/style_manager/model/PropertyComposite.ts b/src/style_manager/model/PropertyComposite.ts index 74eb5c188..a9d5a0852 100644 --- a/src/style_manager/model/PropertyComposite.ts +++ b/src/style_manager/model/PropertyComposite.ts @@ -276,7 +276,13 @@ export default class PropertyComposite = PropertyC const result = this.getStyleFromProps()[this.getName()] || ''; - return getLastStyleValue(result); + if (result && typeof result !== 'string' && 'type' in result) { + if (result.type === 'data-variable') { + console.log('Datasources __getFullValue'); + } + } + + return getLastStyleValue(result as string); } __getJoin() { @@ -300,7 +306,15 @@ export default class PropertyComposite = PropertyC } __splitStyleName(style: StyleProps, name: string, sep: string | RegExp) { - return this.__splitValue(style[name] || '', sep); + const value = style[name]; + + if (value && typeof value !== 'string' && 'type' in value) { + if (value.type === 'data-variable') { + console.log('Datasources __splitStyleName'); + } + } + + return this.__splitValue((value as string) || '', sep); } __getSplitValue(value: string | string[] = '', { byName }: OptionByName = {}) { @@ -340,7 +354,15 @@ export default class PropertyComposite = PropertyC if (!fromStyle) { // Get props from the main property - result = this.__getSplitValue(style[name] || '', { byName }); + const value = style[name]; + + if (value && typeof value !== 'string' && 'type' in value) { + if (value.type === 'data-variable') { + console.log('Datasources __getPropsFromStyle'); + } + } + + result = this.__getSplitValue((value as string) || '', { byName }); // Get props from the inner properties props.forEach(prop => { diff --git a/test/specs/data_sources/index.ts b/test/specs/data_sources/index.ts index 98f133176..fbd4a2f25 100644 --- a/test/specs/data_sources/index.ts +++ b/test/specs/data_sources/index.ts @@ -34,6 +34,46 @@ describe('DataSourceManager', () => { expect(dsm).toBeTruthy(); }); + describe.only('Style', () => { + let fixtures: HTMLElement; + let cmpRoot: ComponentWrapper; + + beforeEach(() => { + document.body.innerHTML = '
'; + const { Pages, Components } = em; + Pages.onLoad(); + cmpRoot = Components.getWrapper()!; + const View = Components.getType('wrapper')!.view; + const wrapperEl = new View({ + model: cmpRoot, + config: { ...cmpRoot.config, em }, + }); + wrapperEl.render(); + fixtures = document.body.querySelector('#fixtures')!; + fixtures.appendChild(wrapperEl.el); + }); + + test('todo', () => { + const styleDataSource: DataSourceProps = { + id: 'colors-data', + records: [{ id: 'id1', color: 'red' }], + }; + + const cmp = cmpRoot.append({ + tagName: 'h1', + type: 'text', + content: 'Hello World', + style: { + color: { + type: 'data-variable', + default: 'black', + path: 'colors-data.id1.color', + }, + }, + })[0]; + }); + }); + test('add DataSource with records', () => { const eventAdd = jest.fn(); em.on(dsm.events.add, eventAdd);