import { BasicType, PrimitiveData, Variable, copyVar, equalsVar, ObjectData, VariableListener, InstructionType, Tag, Scope, Instruction, XmlError, Expression } from '@cyklang/core' import loglevel from 'loglevel' import { structure } from './cykLang' import { ComponentModel } from './cykLang' import { computed, ref, Ref, UnwrapRef, WritableComputedRef } from 'vue' import { WindowManager } from './WindowManager' // import { getDBManager } from './cyklang' const logger = loglevel.getLogger('VariableReact.ts') logger.setLevel('debug') class VarListener { variable: Variable initialValue: BasicType initialVariable: Variable label: string constructor(variable: Variable, label: string) { this.variable = variable // if (variable.dataType.isPrimitive() === false) throw 'variable of type ' + variable.dataType.name this.initialVariable = new Variable(this.variable.dataType) copyVar(variable, this.initialVariable) this.label = label } } /** * */ type DirtyListener = () => void /** * */ export class DirtyManager { isNewRecord: boolean formData: ObjectData varDirty: Variable varListeners: VarListener[] = [] dirtyRefs: Ref[] = [] dirtyListeners: DirtyListener[] = [] constructor(formData: ObjectData, isNewRecord?: boolean) { this.formData = formData this.isNewRecord = isNewRecord || false const varDirty = formData.variables.getVariable('__dirty__') if (varDirty) { this.varDirty = varDirty } else { this.varDirty = new Variable(structure.booleanDataType, structure.falseData) formData.variables.push('__dirty__',this.varDirty) } if (this.isNewRecord) { this.varDirty.data = structure.trueData } else { this.varDirty.data = structure.falseData; } } registerVariable(variable: Variable, label: string) { const varListener = this.varListeners.find((varListener) => varListener.variable === variable) if (varListener === undefined) { this.varListeners.push(new VarListener(variable, label)) // logger.debug('registerVariable ' + label) } } variableChanged(variable?: Variable) { if (this.isNewRecord) { return } // logger.debug('...... variableChanged : ', variable ) // what is the new state let newDirty = false this.varListeners.forEach((varListener) => { if (!equalsVar(varListener.variable, varListener.initialVariable) && varListener.variable.getString() !== varListener.initialVariable.getString()) { // logger.debug('--> dirty', varListener.variable, 'est différent de ', varListener.initialVariable, 'newString',varListener.variable.getString(), 'oldString',varListener.initialVariable.getString()) newDirty = true } // logger.debug('variableChanged from ' + variable2json(varListener.initialVariable) + ' to ' + (varListener.variable) + ' => newDirty ' + newDirty + ', this.dirty ' + this.dirty, varListener.variable) }) this.dirtyRefs.forEach( (dirtyRef) => { if (dirtyRef.value) { newDirty = true } }) let notify = false if (newDirty !== (this.varDirty.data as PrimitiveData).value) { // we have to notify the dirtyListeners // logger.debug('DirtyManager.variableChanged() -> newDirty ' + newDirty) notify = true } if (newDirty) { this.varDirty.data = structure.trueData } else { this.varDirty.data = structure.falseData } // this.dirty = newDirty // logger.debug('variableChanged dirty ' + (this.varDirty.data as PrimitiveData).value + ', notify = ' + notify) if (notify == true) { this.dirtyListeners.forEach((dirtyListener) => { dirtyListener() }) } } registerDirtyRef(dirtyRef: Ref) { const foundIndex = this.dirtyRefs.findIndex( ( element ) => element === dirtyRef) if (foundIndex === -1) { this.dirtyRefs.push(dirtyRef) } } unregisterDirtyRef(dirtyRef: Ref) { const foundIndex = this.dirtyRefs.findIndex( ( element ) => element === dirtyRef) if (foundIndex !== -1) { this.dirtyRefs.splice(foundIndex, 1) } } resetDirty() { logger.debug('resetDirty') this.isNewRecord = false this.varListeners.forEach((varListener) => { copyVar(varListener.variable, varListener.initialVariable) //varListener.initialValue = varListener.getValue() }) this.varDirty.data = structure.falseData this.dirtyListeners.forEach((dirtyListener) => { dirtyListener() }) } addDirtyListener(dirtyListener: DirtyListener) { if (this.dirtyListeners.indexOf(dirtyListener) === -1) { this.dirtyListeners.push(dirtyListener) // logger.debug('!!!!!!!!! add DirtyListener !!!!!!!!') } } removeDirtyListener(dirtyListener: DirtyListener) { for (let ind = 0; ind < this.dirtyListeners.length; ind++) { if (this.dirtyListeners[ind] === dirtyListener) { this.dirtyListeners.splice(ind, 1) ind-- // logger.debug('!!!!!!!!! remove DirtyListener !!!!!!!!') } } } isDirty(): boolean { return (this.varDirty.data as PrimitiveData).value as boolean } } // class VariableReact // instance for each Form export class VariableReact { namedComponents: NamedComponents constructor() { this.namedComponents = new NamedComponents() } addNamedComponent(namedComponent: NamedComponent) { // if (this.namedComponents.hasNamedComponent(namedComponent.name)) { // throw Error('NamedComponent ' + namedComponent.name + ' already exists') // } this.namedComponents.addNamedComponent(namedComponent) } removeNamedComponent(namedComponent: NamedComponent) { if (this.namedComponents.hasNamedComponent(namedComponent.name)) { this.namedComponents.removeNamedComponent(namedComponent) } } sendEvent(componentName: string, event: string) { if (this.namedComponents.hasNamedComponent(componentName)) { this.namedComponents.sendEvent(componentName, event) } else { throw Error('No component named ' + componentName + ', cannot sendEvent ' + event) } } } // class NamedComponent // child of a form / dialog to which his siblings can send events export class NamedComponent { name: string sendEvent: (event: string) => void constructor(name: string, sendEvent: (event: string) => void) { this.name = name this.sendEvent = sendEvent } } class NamedComponents { stack: NamedComponent[] = [] constructor() { this.stack = [] } hasNamedComponent(name: string): boolean { // Recherche depuis la fin de la pile for (let i = this.stack.length - 1; i >= 0; i--) { if (this.stack[i].name === name) { return true } } return false } addNamedComponent(namedComponent: NamedComponent) { this.stack.push(namedComponent) } removeNamedComponent(namedComponent: NamedComponent) { const index = this.stack.lastIndexOf(namedComponent) if (index !== -1) { this.stack.splice(index, 1) } } sendEvent(componentName: string, event: string) { // Recherche depuis la fin de la pile for (let i = this.stack.length - 1; i >= 0; i--) { if (this.stack[i].name === componentName) { this.stack[i].sendEvent(event) return } } } } /** * * @param dataType * @param componentArg * @param defaultValue */ export function componentModelParameter(componentArg: ComponentModel | undefined, name: string, defaultValue: type): WritableComputedRef { const computedRef = computed({ get() { let result = defaultValue; if (componentArg && componentArg.objectData) { const variable = componentArg.objectData.variables.getVariable(name) if (variable && variable.data) { if (!variable.dataType.isPrimitive()) throw 'componentParameter is not primitive: ' + name result = (variable.data as PrimitiveData).value as type } } return result; }, set() { throw 'visible.set not supported'; }, }); return computedRef } /** * */ export class ComponentModelParameter { model: Ref private variable: Variable | undefined private variableListener: VariableListener | undefined constructor(componentArg: ComponentModel | undefined, name: string, defaultValue: type) { let result = defaultValue; this.variable = componentArg?.objectData?.variables.getVariable(name) if (this.variable && this.variable.data) { if (!this.variable.dataType.isPrimitive()) throw 'componentParameter is not primitive: ' result = (this.variable.data as PrimitiveData).value as type } this.model = ref(result) as Ref if (this.variable?.formula) { // logger.debug('ComponentModelParameter.formula: ' + this.variable.formula) this.variableListener = async () => { // logger.debug('variableListener called for ' + this.variable?.formula) if (this.variable) { this.model.value = (this.variable.data as PrimitiveData).value as type } } this.variable.addVariableListener(this.variableListener) } } /** * */ destroy() { if (this.variableListener) { this.variable?.removeVariableListener(this.variableListener) } } } /** * */ export class ComponentParameter { model: Ref private variable: Variable | undefined private variableListener: VariableListener | undefined constructor(variable: Variable, defaultValue: type) { let result = defaultValue; this.variable = variable if (this.variable && this.variable.data) { if (!this.variable.dataType.isPrimitive()) throw 'componentParameter is not primitive: ' result = (this.variable.data as PrimitiveData).value as type } this.model = ref(result) as Ref if (this.variable?.formula) { logger.debug('ComponentParameter.formula: ' + this.variable.formula) this.variableListener = async () => { logger.debug('variableListener called for ' + this.variable?.formula) if (this.variable) { this.model.value = (this.variable.data as PrimitiveData).value as type } } this.variable.addVariableListener(this.variableListener) } } /** * */ destroy() { if (this.variableListener) { this.variable?.removeVariableListener(this.variableListener) } } } /** * */ export class NotifyChangeInstructionType extends InstructionType { windowManager: WindowManager constructor(windowManager: WindowManager) { super('notify_change') this.windowManager = windowManager } async parseInstruction(tag: Tag, scope: Scope): Promise { const inst = new NotifyChangeInstruction(tag, scope, this.windowManager) return inst } } /** * */ class NotifyChangeInstruction extends Instruction { outerScope: Scope windowManager: WindowManager constructor(tag: Tag, outerScope: Scope, windowManager: WindowManager) { super(tag) this.outerScope = outerScope; this.windowManager = windowManager } async execute(scope: Scope): Promise { try { const express = new Expression(this.outerScope) const objData = await express.evaluate(this.tag.getText()) if (!objData) throw ` requires an object but got ${String(objData)}` if (!objData.type.isObject()) throw ` requires an object but got ${objData.type.name}` let foundComponent: ComponentModel | undefined // rech Dialog this.windowManager.dialogStack.forEach((dialogComponent) => { if (dialogComponent.objectData === objData) { foundComponent = dialogComponent } }) if (!foundComponent) { // rech Form if (this.windowManager.currentformComponent) { if (this.windowManager.currentformComponent.objectData === objData) { foundComponent = this.windowManager.currentformComponent } else { // throw ` has not found formComponent. Nested forms ?` } } } if (foundComponent) { logger.debug(` foundComponent and before dirtyManager.variableChanged()`) foundComponent.dirtyManager?.variableChanged() } } catch (err) { if (err instanceof XmlError) throw err throw new XmlError(String(err), this.tag) } } }