@virtualdisplay.io/client 3.6.0 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -64,8 +64,7 @@ const EVENT_NAMES = {
64
64
  STATE_CHANGED: "state:changed",
65
65
  INITIAL_STATE_RECEIVED: "state:initial-received",
66
66
  // Snapshot flow
67
- SNAPSHOT_MESSAGE: "snapshot:take",
68
- SNAPSHOT_DEVELOPED: "snapshot:developed"
67
+ SNAPSHOT_MESSAGE: "snapshot:take"
69
68
  };
70
69
  const g = Object.freeze({
71
70
  debug: 0,
@@ -532,24 +531,6 @@ class Attribute {
532
531
  addValue(attributeValue) {
533
532
  this.values.set(attributeValue.value, attributeValue);
534
533
  }
535
- /**
536
- * Select a value - returns mutations needed for the change
537
- * Pure function - no side effects
538
- */
539
- select(value) {
540
- const newValue = this.values.get(value);
541
- if (newValue === void 0) {
542
- return [];
543
- }
544
- const currentValue = this.getCurrentValue();
545
- if (currentValue === newValue) {
546
- return [];
547
- }
548
- return [
549
- ...currentValue?.getMutations().map((m2) => m2.inverse()) ?? [],
550
- ...newValue.getMutations().map((m2) => m2.inverse())
551
- ];
552
- }
553
534
  getDefaultMutations() {
554
535
  return Array.from(this.values.values()).flatMap((value) => value.getMutations());
555
536
  }
@@ -572,6 +553,9 @@ class Attribute {
572
553
  get currentSelection() {
573
554
  return this.getCurrentValue()?.value;
574
555
  }
556
+ clear() {
557
+ this.values.clear();
558
+ }
575
559
  }
576
560
  const MUTATION_TYPES = {
577
561
  SHOW: "show",
@@ -6925,11 +6909,54 @@ class MappingValidator {
6925
6909
  * @throws {VirtualdisplayError} When mapping configuration is invalid
6926
6910
  */
6927
6911
  static validate(config) {
6912
+ if (config === null || config === void 0) {
6913
+ throw VirtualdisplayError.invalidMapping("Mapping configuration cannot be null or undefined");
6914
+ }
6915
+ if (this.containsDynamicValues(config)) {
6916
+ this.validateBasicStructure(config);
6917
+ return;
6918
+ }
6928
6919
  if (!this.validateFn(config)) {
6929
6920
  const errors2 = this.validateFn.errors.map((err) => err.message).join(", ");
6930
6921
  throw VirtualdisplayError.invalidMapping(errors2);
6931
6922
  }
6932
6923
  }
6924
+ /**
6925
+ * Check if configuration contains dynamic values (functions)
6926
+ */
6927
+ static containsDynamicValues(config) {
6928
+ if (config.attributes === void 0 || !Array.isArray(config.attributes)) {
6929
+ return false;
6930
+ }
6931
+ return config.attributes.some((attr) => {
6932
+ if (typeof attr.values === "function") {
6933
+ return true;
6934
+ }
6935
+ if (Array.isArray(attr.values)) {
6936
+ return attr.values.some((val) => typeof val.value === "function" || typeof val.nodeIds === "function" || typeof val.isSelected === "function");
6937
+ }
6938
+ return false;
6939
+ });
6940
+ }
6941
+ /**
6942
+ * Basic structure validation for dynamic mappings
6943
+ */
6944
+ static validateBasicStructure(config) {
6945
+ if (config.attributes === void 0 || !Array.isArray(config.attributes)) {
6946
+ throw VirtualdisplayError.invalidMapping("attributes must be an array");
6947
+ }
6948
+ if (config.attributes.length === 0) {
6949
+ throw VirtualdisplayError.invalidMapping("attributes array must not be empty");
6950
+ }
6951
+ config.attributes.forEach((attr, index) => {
6952
+ if (attr.name === void 0 || attr.name === "" || typeof attr.name !== "string") {
6953
+ throw VirtualdisplayError.invalidMapping(`attribute at index ${index} must have a name`);
6954
+ }
6955
+ if (attr.values === void 0) {
6956
+ throw VirtualdisplayError.invalidMapping(`attribute '${attr.name}' must have values`);
6957
+ }
6958
+ });
6959
+ }
6933
6960
  }
6934
6961
  const MESSAGE_TYPES = {
6935
6962
  MUTATION: "mutation",
@@ -6968,6 +6995,12 @@ class AttributeService {
6968
6995
  attributes = /* @__PURE__ */ new Map();
6969
6996
  eventBus;
6970
6997
  isInitialized = false;
6998
+ // Store original config for re-evaluation
6999
+ mappingConfig = null;
7000
+ // Store current selections
7001
+ selections = /* @__PURE__ */ new Map();
7002
+ // Store current visible nodes per attribute
7003
+ currentState = /* @__PURE__ */ new Map();
6971
7004
  constructor(eventBus) {
6972
7005
  this.eventBus = eventBus;
6973
7006
  this.eventBus.on(EVENT_NAMES.STATE_CHANGED, (event) => {
@@ -6980,25 +7013,17 @@ class AttributeService {
6980
7013
  */
6981
7014
  loadMapping(config) {
6982
7015
  MappingValidator.validate(config);
7016
+ this.mappingConfig = config;
6983
7017
  if (this.isInitialized) {
6984
7018
  this.attributes.clear();
7019
+ this.selections.clear();
7020
+ this.currentState.clear();
6985
7021
  }
6986
- const mutations = [];
6987
- config.attributes.forEach((attrConfig) => {
6988
- const attribute = new Attribute(attrConfig.name);
6989
- attrConfig.values.forEach((valueConfig) => {
6990
- const attributeValue = new AttributeValue(
6991
- valueConfig.value,
6992
- valueConfig.nodeIds,
6993
- valueConfig.isSelected
6994
- );
6995
- attribute.addValue(attributeValue);
6996
- logger.debug("Created attribute value", attributeValue);
6997
- });
6998
- this.attributes.set(attribute.name, attribute);
6999
- mutations.push(...attribute.getDefaultMutations());
7000
- });
7022
+ this.attributes = this.evaluateMapping();
7023
+ this.initializeSelections();
7024
+ this.updateCurrentState();
7001
7025
  this.isInitialized = true;
7026
+ const mutations = this.getInitialMutations();
7002
7027
  if (mutations.length > 0) {
7003
7028
  const mutationMessage = createMutationMessage(mutations);
7004
7029
  this.eventBus.emit(EVENT_NAMES.MUTATION_MESSAGE, {
@@ -7009,15 +7034,25 @@ class AttributeService {
7009
7034
  selectAttributeValue(attributeName, value) {
7010
7035
  const attribute = this.attributes.get(attributeName);
7011
7036
  if (attribute === void 0) {
7037
+ logger.warn("Attribute not found", { attributeName });
7012
7038
  return;
7013
7039
  }
7014
- const mutations = attribute.select(value);
7015
- if (mutations.length === 0) {
7040
+ if (!attribute.hasValue(value)) {
7041
+ logger.warn("Value not found in attribute", { attributeName, value });
7016
7042
  return;
7017
7043
  }
7018
- this.eventBus.emit(EVENT_NAMES.MUTATION_MESSAGE, {
7019
- message: createMutationMessage(mutations)
7020
- });
7044
+ this.selections.set(attributeName, value);
7045
+ const oldState = new Map(this.currentState);
7046
+ if (this.hasDynamicMapping()) {
7047
+ this.attributes = this.evaluateMapping();
7048
+ }
7049
+ this.updateCurrentState();
7050
+ const mutations = this.calculateMutations(oldState, this.currentState);
7051
+ if (mutations.length > 0) {
7052
+ this.eventBus.emit(EVENT_NAMES.MUTATION_MESSAGE, {
7053
+ message: createMutationMessage(mutations)
7054
+ });
7055
+ }
7021
7056
  }
7022
7057
  getAttribute(name) {
7023
7058
  return this.attributes.get(name);
@@ -7032,26 +7067,157 @@ class AttributeService {
7032
7067
  if (!this.isInitialized) {
7033
7068
  return;
7034
7069
  }
7035
- nodes.forEach((node) => {
7036
- const attributeValue = this.getAttributeValueForNode(node.id);
7037
- if (attributeValue !== void 0) {
7038
- attributeValue.setSelected(node.isVisible);
7039
- }
7040
- });
7041
- }
7042
- getAttributeValueForNode(nodeId) {
7070
+ const nodeVisibility = /* @__PURE__ */ new Map();
7071
+ for (const node of nodes) {
7072
+ nodeVisibility.set(node.id, node.isVisible);
7073
+ }
7043
7074
  for (const attribute of this.attributes.values()) {
7044
- const value = attribute.getAllValues().find((v) => v.hasNode(nodeId));
7045
- if (value !== void 0) {
7046
- return value;
7075
+ for (const value of attribute.getAllValues()) {
7076
+ const allNodesVisible = value.nodeList.every(
7077
+ (nodeId) => nodeVisibility.get(nodeId) ?? false
7078
+ );
7079
+ value.setSelected(allNodesVisible);
7047
7080
  }
7048
7081
  }
7049
- return void 0;
7050
7082
  }
7051
7083
  clear() {
7052
7084
  this.attributes.clear();
7085
+ this.selections.clear();
7086
+ this.currentState.clear();
7087
+ this.mappingConfig = null;
7053
7088
  this.isInitialized = false;
7054
7089
  }
7090
+ /**
7091
+ * Create context for dynamic evaluation
7092
+ */
7093
+ createContext() {
7094
+ return {
7095
+ getValue: (attributeName) => this.selections.get(attributeName),
7096
+ getAllValues: () => new Map(this.selections)
7097
+ };
7098
+ }
7099
+ /**
7100
+ * Evaluate a property that might be static or dynamic
7101
+ */
7102
+ evaluateProperty(prop, context) {
7103
+ return typeof prop === "function" ? prop(context) : prop;
7104
+ }
7105
+ /**
7106
+ * Evaluate the entire mapping with current context
7107
+ */
7108
+ evaluateMapping() {
7109
+ if (this.mappingConfig === null) {
7110
+ return /* @__PURE__ */ new Map();
7111
+ }
7112
+ const context = this.createContext();
7113
+ const evaluatedAttributes = /* @__PURE__ */ new Map();
7114
+ for (const attrConfig of this.mappingConfig.attributes) {
7115
+ const attribute = this.createAttributeFromConfig(attrConfig, context);
7116
+ evaluatedAttributes.set(attribute.name, attribute);
7117
+ }
7118
+ return evaluatedAttributes;
7119
+ }
7120
+ /**
7121
+ * Create a single attribute from configuration
7122
+ */
7123
+ createAttributeFromConfig(attrConfig, context) {
7124
+ const attribute = new Attribute(attrConfig.name);
7125
+ const values = this.evaluateProperty(attrConfig.values, context);
7126
+ for (const valueConfig of values) {
7127
+ const value = this.evaluateProperty(valueConfig.value, context);
7128
+ const nodeIds = this.evaluateProperty(valueConfig.nodeIds, context);
7129
+ const isSelected = this.evaluateProperty(valueConfig.isSelected ?? false, context);
7130
+ const currentSelection = this.selections.get(attrConfig.name);
7131
+ const shouldBeSelected = currentSelection !== void 0 ? currentSelection === value : isSelected;
7132
+ const attributeValue = new AttributeValue(value, nodeIds, shouldBeSelected);
7133
+ attribute.addValue(attributeValue);
7134
+ logger.debug("Created attribute value", attributeValue);
7135
+ }
7136
+ return attribute;
7137
+ }
7138
+ /**
7139
+ * Initialize selections from default values
7140
+ */
7141
+ initializeSelections() {
7142
+ for (const [name, attribute] of this.attributes) {
7143
+ const selectedValue = attribute.getCurrentValue();
7144
+ if (selectedValue !== void 0) {
7145
+ this.selections.set(name, selectedValue.value);
7146
+ }
7147
+ }
7148
+ }
7149
+ /**
7150
+ * Check if the mapping configuration contains dynamic elements
7151
+ */
7152
+ hasDynamicMapping() {
7153
+ if (this.mappingConfig === null) {
7154
+ return false;
7155
+ }
7156
+ return MappingValidator.containsDynamicValues(this.mappingConfig);
7157
+ }
7158
+ /**
7159
+ * Update current state map with visible nodes per attribute
7160
+ */
7161
+ updateCurrentState() {
7162
+ this.currentState.clear();
7163
+ for (const [name, attribute] of this.attributes) {
7164
+ const visibleNodes = this.getVisibleNodesForAttribute(name, attribute);
7165
+ this.currentState.set(name, visibleNodes);
7166
+ }
7167
+ }
7168
+ getVisibleNodesForAttribute(name, attribute) {
7169
+ const visibleNodes = /* @__PURE__ */ new Set();
7170
+ const selectedValueName = this.selections.get(name);
7171
+ if (selectedValueName === void 0) {
7172
+ return visibleNodes;
7173
+ }
7174
+ const selectedValue = attribute.getValue(selectedValueName);
7175
+ if (selectedValue !== void 0) {
7176
+ selectedValue.nodeList.forEach((nodeId) => visibleNodes.add(nodeId));
7177
+ }
7178
+ return visibleNodes;
7179
+ }
7180
+ /**
7181
+ * Get initial mutations for default selections
7182
+ */
7183
+ getInitialMutations() {
7184
+ const mutations = [];
7185
+ for (const attribute of this.attributes.values()) {
7186
+ mutations.push(...attribute.getDefaultMutations());
7187
+ }
7188
+ return mutations;
7189
+ }
7190
+ /**
7191
+ * Calculate mutations by comparing old and new state
7192
+ */
7193
+ calculateMutations(oldState, newState) {
7194
+ const mutations = [];
7195
+ const allNodeIds = /* @__PURE__ */ new Set();
7196
+ oldState.forEach((nodes) => nodes.forEach((id2) => allNodeIds.add(id2)));
7197
+ newState.forEach((nodes) => nodes.forEach((id2) => allNodeIds.add(id2)));
7198
+ for (const nodeId of allNodeIds) {
7199
+ const wasVisible = this.isNodeVisible(nodeId, oldState);
7200
+ const isVisible = this.isNodeVisible(nodeId, newState);
7201
+ if (wasVisible && !isVisible) {
7202
+ mutations.push(Mutation.hide(nodeId));
7203
+ }
7204
+ if (!wasVisible && isVisible) {
7205
+ mutations.push(Mutation.show(nodeId));
7206
+ }
7207
+ }
7208
+ return mutations;
7209
+ }
7210
+ /**
7211
+ * Check if a node is visible in the given state
7212
+ */
7213
+ isNodeVisible(nodeId, state) {
7214
+ for (const nodes of state.values()) {
7215
+ if (nodes.has(nodeId)) {
7216
+ return true;
7217
+ }
7218
+ }
7219
+ return false;
7220
+ }
7055
7221
  }
7056
7222
  class Camera {
7057
7223
  constructor(eventBus) {