Skip to content

Commit

Permalink
Merge branch 'dev' into fix-page-clone
Browse files Browse the repository at this point in the history
  • Loading branch information
mohamedsalem401 authored Nov 4, 2024
2 parents f24ebc4 + ef26212 commit 66917aa
Show file tree
Hide file tree
Showing 20 changed files with 959 additions and 82 deletions.
7 changes: 2 additions & 5 deletions packages/core/src/data_sources/model/ComponentDataVariable.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Component from '../../dom_components/model/Component';
import { ToHTMLOptions } from '../../dom_components/model/types';
import { toLowerCase } from '../../utils/mixins';
import { DataVariableType } from './DataVariable';

Expand All @@ -19,10 +18,8 @@ export default class ComponentDataVariable extends Component {
return this.em.DataSources.getValue(path, defaultValue);
}

getInnerHTML(opts: ToHTMLOptions) {
const val = this.getDataValue();

return val;
getInnerHTML() {
return this.getDataValue();
}

static isComponent(el: HTMLElement) {
Expand Down
7 changes: 6 additions & 1 deletion packages/core/src/data_sources/model/DataVariable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import EditorModel from '../../editor/model/Editor';
import { stringToPath } from '../../utils/mixins';

export const DataVariableType = 'data-variable';
export type DataVariableDefinition = {
type: typeof DataVariableType;
path: string;
defaultValue?: string;
};

export default class DataVariable extends Model {
em?: EditorModel;
Expand All @@ -15,7 +20,7 @@ export default class DataVariable extends Model {
};
}

constructor(attrs: any, options: any) {
constructor(attrs: DataVariableDefinition, options: any) {
super(attrs, options);
this.em = options.em;
this.listenToDataSource();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@ import { Model } from '../../common';
import EditorModel from '../../editor/model/Editor';
import DataVariable, { DataVariableType } from './DataVariable';
import ComponentView from '../../dom_components/view/ComponentView';
import { DynamicValue } from '../types';
import { DataCondition, ConditionalVariableType } from './conditional_variables/DataCondition';
import ComponentDataVariable from './ComponentDataVariable';

export interface DynamicVariableListenerManagerOptions {
model: Model | ComponentView;
em: EditorModel;
dataVariable: DataVariable | ComponentDataVariable;
dataVariable: DynamicValue;
updateValueFromDataVariable: (value: any) => void;
}

export default class DynamicVariableListenerManager {
private dataListeners: DataVariableListener[] = [];
private em: EditorModel;
private model: Model | ComponentView;
private dynamicVariable: DataVariable | ComponentDataVariable;
private dynamicVariable: DynamicValue;
private updateValueFromDynamicVariable: (value: any) => void;

constructor(options: DynamicVariableListenerManagerOptions) {
Expand All @@ -42,14 +44,25 @@ export default class DynamicVariableListenerManager {
let dataListeners: DataVariableListener[] = [];
switch (type) {
case DataVariableType:
dataListeners = this.listenToDataVariable(dynamicVariable, em);
dataListeners = this.listenToDataVariable(dynamicVariable as DataVariable | ComponentDataVariable, em);
break;
case ConditionalVariableType:
dataListeners = this.listenToConditionalVariable(dynamicVariable as DataCondition, em);
break;
}
dataListeners.forEach((ls) => model.listenTo(ls.obj, ls.event, this.onChange));

this.dataListeners = dataListeners;
}

private listenToConditionalVariable(dataVariable: DataCondition, em: EditorModel) {
const dataListeners = dataVariable.getDependentDataVariables().flatMap((dataVariable) => {
return this.listenToDataVariable(new DataVariable(dataVariable, { em: this.em }), em);
});

return dataListeners;
}

private listenToDataVariable(dataVariable: DataVariable | ComponentDataVariable, em: EditorModel) {
const dataListeners: DataVariableListener[] = [];
const { path } = dataVariable.attributes;
Expand Down
7 changes: 5 additions & 2 deletions packages/core/src/data_sources/model/TraitDataVariable.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import DataVariable from './DataVariable';
import DataVariable, { DataVariableDefinition } from './DataVariable';
import Trait from '../../trait_manager/model/Trait';
import { TraitProperties } from '../../trait_manager/types';

export type TraitDataVariableDefinition = TraitProperties & DataVariableDefinition;

export default class TraitDataVariable extends DataVariable {
trait?: Trait;

constructor(attrs: any, options: any) {
constructor(attrs: TraitDataVariableDefinition, options: any) {
super(attrs, options);
this.trait = options.trait;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DataVariableDefinition, DataVariableType } from './../DataVariable';
import EditorModel from '../../../editor/model/Editor';
import DataVariable from '../DataVariable';
import { evaluateVariable, isDataVariable } from '../utils';
import { Expression, LogicGroup } from './DataCondition';
import { LogicalGroupStatement } from './LogicalGroupStatement';
Expand All @@ -8,12 +8,14 @@ import { GenericOperation, GenericOperator } from './operators/GenericOperator';
import { LogicalOperator } from './operators/LogicalOperator';
import { NumberOperator, NumberOperation } from './operators/NumberOperator';
import { StringOperator, StringOperation } from './operators/StringOperations';
import { Model } from '../../../common';

export class Condition {
export class Condition extends Model {
private condition: Expression | LogicGroup | boolean;
private em: EditorModel;

constructor(condition: Expression | LogicGroup | boolean, opts: { em: EditorModel }) {
super(condition);
this.condition = condition;
this.em = opts.em;
}
Expand Down Expand Up @@ -65,16 +67,16 @@ export class Condition {
/**
* Extracts all data variables from the condition, including nested ones.
*/
getDataVariables(): DataVariable[] {
const variables: DataVariable[] = [];
getDataVariables() {
const variables: DataVariableDefinition[] = [];
this.extractVariables(this.condition, variables);
return variables;
}

/**
* Recursively extracts variables from expressions or logic groups.
*/
private extractVariables(condition: boolean | LogicGroup | Expression, variables: DataVariable[]): void {
private extractVariables(condition: boolean | LogicGroup | Expression, variables: DataVariableDefinition[]): void {
if (this.isExpression(condition)) {
if (isDataVariable(condition.left)) variables.push(condition.left);
if (isDataVariable(condition.right)) variables.push(condition.right);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Component from '../../../dom_components/model/Component';
import Components from '../../../dom_components/model/Components';
import { ComponentDefinition, ComponentOptions } from '../../../dom_components/model/types';
import { toLowerCase } from '../../../utils/mixins';
import { DataCondition, ConditionalVariableType, Expression, LogicGroup } from './DataCondition';

type ConditionalComponentDefinition = {
condition: Expression | LogicGroup | boolean;
ifTrue: any;
ifFalse: any;
};

export default class ComponentConditionalVariable extends Component {
dataCondition: DataCondition;
componentDefinition: ConditionalComponentDefinition;

constructor(componentDefinition: ConditionalComponentDefinition, opt: ComponentOptions) {
const { condition, ifTrue, ifFalse } = componentDefinition;
const dataConditionInstance = new DataCondition(condition, ifTrue, ifFalse, { em: opt.em });
const initialComponentsProps = dataConditionInstance.getDataValue();
const conditionalCmptDef = {
type: ConditionalVariableType,
components: initialComponentsProps,
};
super(conditionalCmptDef, opt);

this.componentDefinition = componentDefinition;
this.dataCondition = dataConditionInstance;
this.dataCondition.onValueChange = this.handleConditionChange.bind(this);
}

private handleConditionChange() {
this.dataCondition.reevaluate();
const updatedComponents = this.dataCondition.getDataValue();
this.components().reset();
this.components().add(updatedComponents);
}

static isComponent(el: HTMLElement) {
return toLowerCase(el.tagName) === ConditionalVariableType;
}

toJSON(): ComponentDefinition {
return this.dataCondition.toJSON();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { LogicalOperation } from './operators/LogicalOperator';
import DynamicVariableListenerManager from '../DataVariableListenerManager';
import EditorModel from '../../../editor/model/Editor';
import { Condition } from './Condition';
import DataVariable from '../DataVariable';
import DataVariable, { DataVariableDefinition } from '../DataVariable';
import { evaluateVariable, isDataVariable } from '../utils';

export const DataConditionType = 'conditional-variable';
export const ConditionalVariableType = 'conditional-variable';
export type Expression = {
left: any;
operator: GenericOperation | StringOperation | NumberOperation;
Expand All @@ -21,50 +21,60 @@ export type LogicGroup = {
statements: (Expression | LogicGroup | boolean)[];
};

export type ConditionalVariableDefinition = {
type: typeof ConditionalVariableType;
condition: Expression | LogicGroup | boolean;
ifTrue: any;
ifFalse: any;
};

export class DataCondition extends Model {
private conditionResult: boolean;
lastEvaluationResult: boolean;
private condition: Condition;
private em: EditorModel;
private variableListeners: DynamicVariableListenerManager[] = [];
private _onValueChange?: () => void;

defaults() {
return {
type: DataConditionType,
type: ConditionalVariableType,
condition: false,
};
}

constructor(
condition: Expression | LogicGroup | boolean,
private ifTrue: any,
private ifFalse: any,
opts: { em: EditorModel },
public ifTrue: any,
public ifFalse: any,
opts: { em: EditorModel; onValueChange?: () => void },
) {
if (typeof condition === 'undefined') {
throw new MissingConditionError();
}

super();
this.condition = new Condition(condition, { em: opts.em });
this.em = opts.em;
this.conditionResult = this.evaluate();
this.lastEvaluationResult = this.evaluate();
this.listenToDataVariables();
this._onValueChange = opts.onValueChange;
}

evaluate() {
return this.condition.evaluate();
}

getDataValue(): any {
return this.conditionResult ? evaluateVariable(this.ifTrue, this.em) : evaluateVariable(this.ifFalse, this.em);
return this.lastEvaluationResult ? evaluateVariable(this.ifTrue, this.em) : evaluateVariable(this.ifFalse, this.em);
}

reevaluate(): void {
this.conditionResult = this.evaluate();
this.lastEvaluationResult = this.evaluate();
}

toJSON() {
return {
condition: this.condition,
ifTrue: this.ifTrue,
ifFalse: this.ifFalse,
};
set onValueChange(newFunction: () => void) {
this._onValueChange = newFunction;
this.listenToDataVariables();
}

private listenToDataVariables() {
Expand All @@ -73,25 +83,48 @@ export class DataCondition extends Model {
// Clear previous listeners to avoid memory leaks
this.cleanupListeners();

const dataVariables = this.condition.getDataVariables();
if (isDataVariable(this.ifTrue)) dataVariables.push(this.ifTrue);
if (isDataVariable(this.ifFalse)) dataVariables.push(this.ifFalse);
const dataVariables = this.getDependentDataVariables();

dataVariables.forEach((variable) => {
const variableInstance = new DataVariable(variable, { em: this.em });
const listener = new DynamicVariableListenerManager({
model: this as any,
em: this.em!,
dataVariable: variableInstance,
updateValueFromDataVariable: this.reevaluate.bind(this),
updateValueFromDataVariable: (() => {
this.reevaluate();
this._onValueChange?.();
}).bind(this),
});

this.variableListeners.push(listener);
});
}

getDependentDataVariables() {
const dataVariables: DataVariableDefinition[] = this.condition.getDataVariables();
if (isDataVariable(this.ifTrue)) dataVariables.push(this.ifTrue);
if (isDataVariable(this.ifFalse)) dataVariables.push(this.ifFalse);

return dataVariables;
}

private cleanupListeners() {
this.variableListeners.forEach((listener) => listener.destroy());
this.variableListeners = [];
}

toJSON() {
return {
type: ConditionalVariableType,
condition: this.condition,
ifTrue: this.ifTrue,
ifFalse: this.ifFalse,
};
}
}
export class MissingConditionError extends Error {
constructor() {
super('No condition was provided to a conditional component.');
}
}
13 changes: 11 additions & 2 deletions packages/core/src/data_sources/model/utils.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import EditorModel from '../../editor/model/Editor';
import { DataConditionType } from './conditional_variables/DataCondition';
import { DynamicValue, DynamicValueDefinition } from '../types';
import { ConditionalVariableType, DataCondition } from './conditional_variables/DataCondition';
import DataVariable, { DataVariableType } from './DataVariable';

export function isDynamicValueDefinition(value: any): value is DynamicValueDefinition {
return typeof value === 'object' && [DataVariableType, ConditionalVariableType].includes(value.type);
}

export function isDynamicValue(value: any): value is DynamicValue {
return value instanceof DataVariable || value instanceof DataCondition;
}

export function isDataVariable(variable: any) {
return variable?.type === DataVariableType;
}

export function isDataCondition(variable: any) {
return variable?.type === DataConditionType;
return variable?.type === ConditionalVariableType;
}

export function evaluateVariable(variable: any, em: EditorModel) {
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/data_sources/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { ObjectAny } from '../common';
import ComponentDataVariable from './model/ComponentDataVariable';
import DataRecord from './model/DataRecord';
import DataRecords from './model/DataRecords';
import DataVariable, { DataVariableDefinition } from './model/DataVariable';
import { ConditionalVariableDefinition, DataCondition } from './model/conditional_variables/DataCondition';

export type DynamicValue = DataVariable | ComponentDataVariable | DataCondition;
export type DynamicValueDefinition = DataVariableDefinition | ConditionalVariableDefinition;
export interface DataRecordProps extends ObjectAny {
/**
* Record id.
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/data_sources/view/ComponentDynamicView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import ComponentView from '../../dom_components/view/ComponentView';
import ConditionalComponent from '../model/conditional_variables/ConditionalComponent';

export default class ConditionalComponentView extends ComponentView<ConditionalComponent> {}
Loading

0 comments on commit 66917aa

Please sign in to comment.