UNPKG

ng2-json-editor

Version:

Angular2 component for editing large json documents

269 lines 13.1 kB
/* * This file is part of ng2-json-editor. * Copyright (C) 2016 CERN. * * ng2-json-editor is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * ng2-json-editor is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ng2-json-editor; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * In applying this license, CERN does not * waive the privileges and immunities granted to it by virtue of its status * as an Intergovernmental Organization or submit itself to any jurisdiction. */ import { Injectable } from '@angular/core'; import { Map, List, Seq } from 'immutable'; import { ReplaySubject } from 'rxjs/ReplaySubject'; import { Subject } from 'rxjs/Subject'; import { PathUtilService } from './path-util.service'; import { JsonSchemaService } from './json-schema.service'; import { AppGlobalsService } from './app-globals.service'; import { CompareKeysBySchemaService } from './compare-keys-by-schema.service'; var KeysStoreService = (function () { function KeysStoreService(appGlobalsService, pathUtilService, jsonSchemaService, compareKeysBySchemaService) { this.appGlobalsService = appGlobalsService; this.pathUtilService = pathUtilService; this.jsonSchemaService = jsonSchemaService; this.compareKeysBySchemaService = compareKeysBySchemaService; this.onKeysChange = new Subject(); } KeysStoreService.prototype.forPath = function (path) { return this.keys$Map[path]; }; /** * Adds a key to the specified path. * * @param path path to add the key to * @param key key to be added * @param schema schema that belongs to path (schema.items for table-list) */ KeysStoreService.prototype.addKey = function (path, key, schema, value) { var _this = this; // FIXME: could do O(logn) insert instead of O(nlogn) since the set is already sorted. this.keysMap[path] = this.keysMap[path] .add(key) .sort(function (a, b) { return _this.compareKeysBySchemaService.compare(a, b, schema); }); this.keys$Map[path].next(this.keysMap[path]); this.onKeysChange.next({ path: path, keys: this.keysMap[path] }); var newKeyPath = "" + path + this.pathUtilService.separator + key; var keySchema = schema.properties[key]; if (keySchema.type === 'object' || keySchema.componentType === 'table-list') { this.buildKeysMapRecursivelyForPath(value || Map(), newKeyPath, keySchema); } return newKeyPath; }; KeysStoreService.prototype.deletePath = function (path) { var lastKey = path[path.length - 1]; var parentPath = this.pathUtilService.toPathString(path.slice(0, -1)); // don't invoke deleteKey if parentPath is primitive-list if (this.keysMap[parentPath]) { this.deleteKey(parentPath, lastKey); } }; /** * Sync keys in store for the given path or its parent, grand parent etc. if necessary * * @param path path to the (re)set field * @param json whole json */ KeysStoreService.prototype.syncKeysForPath = function (path, json) { // search from leaf to root, to find the first path with entry in keys map for (var i = path.length - 1; i >= 0; i--) { var currentPath = path.slice(0, i); var currentPathString = this.pathUtilService.toPathString(currentPath); if (this.keysMap[currentPathString]) { // path[i] is key that should be added to currentPat var key = path[i]; // if currentPath has the key if (this.keysMap[currentPathString].has(key)) { // just build the store keys map for that /current/path/key if it is object or array var keyPath = currentPath.concat(key); var keySchema = this.jsonSchemaService.forPathArray(keyPath); if (keySchema.type === 'object' || keySchema.type === 'array') { this.buildKeysMapRecursivelyForPath(json.getIn(keyPath), keyPath, keySchema); } // if currentPath doesn't have the key } else { var currentSchema = this.jsonSchemaService.forPathArray(currentPath); // if currentPath is to a table list if (currentSchema.componentType === 'table-list') { // have to rebuild keys map for it because key is here an index we don't know what to add this.buildKeysMapRecursivelyForPath(json.getIn(currentPath), currentPath, currentSchema); // if not to a table list. } else { // just add the key which will build keys map for /current/path/key as well if needed this.addKey(currentPathString, key, currentSchema, json.getIn(currentPath.concat(path[i]))); } } // break when a entry found for a path in keys map break; } } }; KeysStoreService.prototype.deleteKey = function (parentPath, key) { var _this = this; // remove deleted one from parent this.keysMap[parentPath] = this.keysMap[parentPath].delete(key); this.keys$Map[parentPath].next(this.keysMap[parentPath]); this.onKeysChange.next({ path: parentPath, keys: this.keysMap[parentPath] }); // delete keys for deleted one var deletedKeyPath = "" + parentPath + this.pathUtilService.separator + key; delete this.keysMap[deletedKeyPath]; delete this.keys$Map[deletedKeyPath]; // delete keys for all children of the deleted one var deletedKeyPathChildPrefix = deletedKeyPath + this.pathUtilService.separator; Object.keys(this.keysMap) .filter(function (path) { return path.startsWith(deletedKeyPathChildPrefix); }) .forEach(function (childPath) { delete _this.keysMap[childPath]; delete _this.keys$Map[childPath]; }); }; /** * Swaps keys of given two indices in object list recursively */ KeysStoreService.prototype.swapListElementKeys = function (listPath, index1, index2) { var _this = this; var listSchema = this.jsonSchemaService.forPathArray(listPath); if (listSchema.componentType !== 'complex-list') { return; } var listPathString = this.pathUtilService.toPathString(listPath); var ps1 = "" + listPathString + this.pathUtilService.separator + index1; var ps2 = "" + listPathString + this.pathUtilService.separator + index2; var keys1 = this.keysMap[ps1]; this.setKeys(ps1, this.keysMap[ps2]); this.setKeys(ps2, keys1); // swap children as well var ps1ChildPrefix = ps1 + this.pathUtilService.separator; var ps2ChildPrefix = ps2 + this.pathUtilService.separator; var childrenSwaps = []; Object.keys(this.keysMap) .forEach(function (path) { if (path.startsWith(ps1ChildPrefix)) { var toPath = path.replace(ps1ChildPrefix, ps2ChildPrefix); childrenSwaps.push({ from: path, to: toPath, keys: _this.keysMap[path] }); } else if (path.startsWith(ps2ChildPrefix)) { var toPath = path.replace(ps2ChildPrefix, ps1ChildPrefix); childrenSwaps.push({ from: path, to: toPath, keys: _this.keysMap[path] }); } }); childrenSwaps .forEach(function (swap) { _this.setKeys(swap.to, swap.keys); _this.onKeysChange.next({ path: swap.to, keys: _this.keysMap[swap.to] }); }); childrenSwaps .filter(function (swap) { return !childrenSwaps.some(function (otherSwap) { return swap.from === otherSwap.to; }); }) .forEach(function (swap) { delete _this.keysMap[swap.from]; delete _this.keys$Map[swap.from]; }); }; KeysStoreService.prototype.buildKeysMap = function (json, schema) { this.keys$Map = {}; this.keysMap = {}; this.buildKeysMapRecursivelyForPath(json, '', schema); }; KeysStoreService.prototype.buildKeysMapRecursivelyForPath = function (mapOrList, path, schema) { var _this = this; // TODO: remove this and unify typing when #330 is fixed var pathString = Array.isArray(path) ? this.pathUtilService.toPathString(path) : path; if (!schema) { schema = this.jsonSchemaService.forPathString(pathString); } if (schema.type === 'object') { var map_1 = mapOrList || Map(); var finalKeys = this.buildkeysForObject(pathString, map_1, schema); // recursive call finalKeys .filter(function (key) { return _this.isObjectOrArray(schema.properties[key]); }) .forEach(function (key) { var nextPath = "" + pathString + _this.pathUtilService.separator + key; _this.buildKeysMapRecursivelyForPath(map_1.get(key), nextPath, schema.properties[key]); }); } else if (schema.componentType === 'table-list') { var list = mapOrList || List(); this.buildKeysForTableList(pathString, list, schema); // there is no recursive call for table list items because they aren't expected to have object or object list as property. } else if (schema.componentType === 'complex-list') { var list = mapOrList || List(); list.forEach(function (element, index) { var elementPath = "" + pathString + _this.pathUtilService.separator + index; _this.buildKeysMapRecursivelyForPath(element, elementPath, schema.items); }); } }; // default value for `list`, if this is called for alwaysShow in which case `list` would be undefined KeysStoreService.prototype.buildKeysForTableList = function (path, list, schema) { if (list === void 0) { list = List(); } // get present unique keys in all items of table-list var keys = Seq.Set(list .map(function (object) { return object.keySeq().toArray(); }) .reduce(function (pre, cur) { return pre.concat(cur); }, [])); var itemSchema = schema.items; var finalKeys = this.schemafy(keys, itemSchema); this.setKeys(path, finalKeys); }; // default value for `map`, if this is called for alwaysShow in which case `map` would be undefined KeysStoreService.prototype.buildkeysForObject = function (path, map, schema) { if (map === void 0) { map = Map(); } var finalKeys = this.schemafy(map.keySeq(), schema); this.setKeys(path, finalKeys); return finalKeys; }; /** * Filters keys, add alwaysShow fields and sorts by schema. */ KeysStoreService.prototype.schemafy = function (keys, schema) { var _this = this; return keys .concat(schema.required || []) .filter(function (key) { return _this.isNotHidden(key, schema) || _this.appGlobalsService.adminMode; }) .concat(schema.alwaysShow || []) .sort(function (a, b) { return _this.compareKeysBySchemaService.compare(a, b, schema); }) .toOrderedSet(); }; KeysStoreService.prototype.isNotHidden = function (key, schema) { if (!schema.properties[key]) { throw new Error("\"" + key + "\" is not specified as property in \n" + JSON.stringify(schema.properties, undefined, 2)); } return !schema.properties[key].hidden; }; KeysStoreService.prototype.isObjectOrArray = function (schema) { return schema.type === 'object' || schema.type === 'array'; }; KeysStoreService.prototype.setKeys = function (path, keys) { this.keysMap[path] = keys; if (!this.keys$Map[path]) { this.keys$Map[path] = new ReplaySubject(1); } this.keys$Map[path].next(keys); }; return KeysStoreService; }()); export { KeysStoreService }; KeysStoreService.decorators = [ { type: Injectable }, ]; /** @nocollapse */ KeysStoreService.ctorParameters = function () { return [ { type: AppGlobalsService, }, { type: PathUtilService, }, { type: JsonSchemaService, }, { type: CompareKeysBySchemaService, }, ]; }; //# sourceMappingURL=keys-store.service.js.map