ng2-json-editor
Version:
Angular2 component for editing large json documents
269 lines • 13.1 kB
JavaScript
/*
* 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