UNPKG

terriajs

Version:

Geospatial data visualization platform.

167 lines 8.08 kB
import { computed } from "mobx"; import { computedFn } from "mobx-utils"; import Result from "../../Core/Result"; import TerriaError from "../../Core/TerriaError"; import createStratumInstance from "../../Models/Definition/createStratumInstance"; import saveStratumToJson from "../../Models/Definition/saveStratumToJson"; import StratumOrder from "../../Models/Definition/StratumOrder"; import ArrayNestedStrataMap, { getObjectId } from "../ArrayNestedStrataMap"; import Trait from "../Trait"; import traitsClassToModelClass from "../traitsClassToModelClass"; export var MergeStrategy; (function (MergeStrategy) { /** * Merge array elements across strata - this is the default. */ MergeStrategy["All"] = "all"; /** Similar to Merge.All, but elements that exist in the top-most strata will be merged with lower strata. Elements that only exist in lower strata will be removed. */ MergeStrategy["TopStratum"] = "topStratum"; /** Don't merge array elements across strata. */ MergeStrategy["None"] = "none"; })(MergeStrategy || (MergeStrategy = {})); export default function objectArrayTrait(options) { return function (target, propertyKey) { const constructor = target.constructor; if (!constructor.traits) { constructor.traits = {}; } constructor.traits[propertyKey] = new ObjectArrayTrait(propertyKey, options, constructor); }; } export class ObjectArrayTrait extends Trait { type; idProperty; decoratorForFlattened = computed.struct; modelClass; merge; constructor(id, options, parent) { super(id, options, parent); this.type = options.type; this.idProperty = options.idProperty; this.modelClass = options.modelClass || traitsClassToModelClass(this.type); this.merge = options.merge ?? MergeStrategy.All; } createObject = computedFn((model, objectId) => { return new this.modelClass(undefined, model.terria, undefined, new ArrayNestedStrataMap(model, this.id, this.type, this.idProperty, objectId, this.merge)); }); getIdsAcrossStrata(strata, ignoreRemovals = false) { const ids = new Set(); const removedIds = new Set(); // Find the unique objects and the strata that go into each. for (const stratumId of strata.keys()) { const stratum = strata.get(stratumId); const objectArray = stratum[this.id]; if (!objectArray) { continue; } objectArray.forEach((o, i) => { const id = getObjectId(this.idProperty, o, i); if (this.type.isRemoval !== undefined && this.type.isRemoval(o)) { // This ID is removed in this stratum. removedIds.add(id); } else if (removedIds.has(id) && !ignoreRemovals) { // This ID was removed by a stratum above this one, so ignore it. return; } else { ids.add(id); } }); } return ids; } getValue(model) { // Strata order is important here for two reasons: // Determining array order: // By default, we assume bottom strata order is "more" correct than top - with exception to default stratum - definition stratum is more correct // For example: // - In some LoadableStratum we set the objectArray to: [{item:"one", value:"a"}, {item:"two", value:"b"}] // - Then in the user stratum we set [{item:"two", value:"c"}] // - We want the order in LoadableStratum to stay static (item "one" is before item "two") // - If we were to use topToBottom strata, then the order would be flipped. // Higher level stratum are set more frequently than lower level, so using bottomToTop will minimise change in order of elements // Removing elements correctly if elements are removed by higher stratum: // Here we want higher stratum to remove elements of lower stratum // For example: // - In "definition" stratum, we set the objectArray to: [{item:"one", value:"a"}, {item:"two", value:"b"}] // - The in "user" stratum, we remove the {item:"two", value:"b"} element // - Then the correct model will only have {item:"one", value:"a"} // For more info see objectArrayTraitSpec.ts # allows strata to remove elements const idsInCorrectOrder = this.getIdsAcrossStrata(StratumOrder.sortBottomToTop(model.strata), true); const idsWithCorrectRemovals = this.getIdsAcrossStrata(StratumOrder.sortTopToBottom(model.strata)); // If merge strategy is topStratum, then we only want to keep the ids that exist in the top stratum if (this.merge === MergeStrategy.TopStratum) { const topStratum = model.strataTopToBottom.values().next().value; // topStratum will be undefined if a model has 0 strata if (topStratum !== undefined) { const topIds = this.getIdsAcrossStrata(new Map([["top", topStratum]])); // Remove ids that don't exist in the top stratum idsInCorrectOrder.forEach((id) => { if (!topIds.has(id)) { idsWithCorrectRemovals.delete(id); } }); } } // Correct ids are: // - Ids ordered by strata bottom to top combined with // - Ids removed by strata top to bottom const ids = Array.from(idsInCorrectOrder).filter((id) => idsWithCorrectRemovals.has(id)); // Create a model instance for each unique ID. Note that `createObject` is // memoized so we'll get the same model for the same ID each time, // at least when we're in a reactive context. const result = []; ids.forEach((value) => { result.push(this.createObject(model, value)); }); return result; } fromJson(model, stratumName, jsonValue) { // TODO: support removals if (!Array.isArray(jsonValue)) { return Result.error(new TerriaError({ title: "Invalid property", message: `Property ${this.id} is expected to be an array but instead it is of type ${typeof jsonValue}.` })); } const errors = []; const resultArray = jsonValue.map((jsonElement) => { const ResultType = this.type; const result = createStratumInstance(ResultType); Object.keys(jsonElement).forEach((propertyName) => { const trait = ResultType.traits[propertyName]; if (trait === undefined) { errors.push(new TerriaError({ title: "Unknown property", message: `${propertyName} is not a valid sub-property of elements of ${this.id}.` })); return; } const subJsonValue = jsonElement[propertyName]; if (subJsonValue === undefined) { result[propertyName] = subJsonValue; } else { result[propertyName] = trait .fromJson(model, stratumName, subJsonValue) .pushErrorTo(errors); } }); return result; }); return new Result(resultArray, TerriaError.combine(errors, `Error${errors.length !== 1 ? "s" : ""} occurred while updating objectArrayTrait model "${model.uniqueId}" from JSON`)); } toJson(value) { if (value === undefined) { return undefined; } return value.map((element) => saveStratumToJson(this.type.traits, element)); } isSameType(trait) { return (trait instanceof ObjectArrayTrait && trait.type === this.type && trait.idProperty === this.idProperty); } } //# sourceMappingURL=objectArrayTrait.js.map