Merged in feature/model_to_typescript (pull request #22)

Feature/model to typescript

Approved-by: Matias Arriola
This commit is contained in:
Paulo Veiga 2022-01-03 14:13:38 +00:00 committed by Matias Arriola
commit b1809816b0
35 changed files with 908 additions and 972 deletions

View File

@ -59,7 +59,7 @@ class CommandContext {
/** */ /** */
createModel() { createModel() {
const mindmap = this._designer.getMindmap(); const mindmap = this._designer.getMindmap();
return mindmap.createNode(NodeModel.MAIN_TOPIC_TYPE); return mindmap.createNode('MainTopic');
} }
/** */ /** */

View File

@ -34,7 +34,7 @@ class ConnectionLine {
let line; let line;
const ctrlPoints = this._getCtrlPoints(sourceNode, targetNode); const ctrlPoints = this._getCtrlPoints(sourceNode, targetNode);
if (targetNode.getType() === INodeModel.CENTRAL_TOPIC_TYPE) { if (targetNode.getType() === 'CentralTopic') {
line = this._createLine(lineType, ConnectionLine.CURVED); line = this._createLine(lineType, ConnectionLine.CURVED);
line.setSrcControlPoint(ctrlPoints[0]); line.setSrcControlPoint(ctrlPoints[0]);
line.setDestControlPoint(ctrlPoints[1]); line.setDestControlPoint(ctrlPoints[1]);

View File

@ -243,7 +243,7 @@ class Designer extends Events {
}); });
// Register node listeners ... // Register node listeners ...
if (topic.getType() !== INodeModel.CENTRAL_TOPIC_TYPE) { if (topic.getType() !== 'CentralTopic') {
// Central Topic doesn't support to be dragged // Central Topic doesn't support to be dragged
this._dragManager.add(topic); this._dragManager.add(topic);
} }
@ -443,7 +443,7 @@ class Designer extends Events {
} }
// Execute event ... // Execute event ...
const topic = nodes[0]; const topic = nodes[0];
if (topic.getType() !== INodeModel.CENTRAL_TOPIC_TYPE) { if (topic.getType() !== 'CentralTopic') {
this._actionDispatcher.shrinkBranch([topic.getId()], !topic.areChildrenShrunken()); this._actionDispatcher.shrinkBranch([topic.getId()], !topic.areChildrenShrunken());
} }
} }
@ -476,7 +476,7 @@ class Designer extends Events {
*/ */
_copyNodeProps(sourceModel, targetModel) { _copyNodeProps(sourceModel, targetModel) {
// I don't copy the font size if the target is the source is the central topic. // I don't copy the font size if the target is the source is the central topic.
if (sourceModel.getType() !== INodeModel.CENTRAL_TOPIC_TYPE) { if (sourceModel.getType() !== 'CentralTopic') {
const fontSize = sourceModel.getFontSize(); const fontSize = sourceModel.getFontSize();
if (fontSize) { if (fontSize) {
targetModel.setFontSize(fontSize); targetModel.setFontSize(fontSize);
@ -586,7 +586,7 @@ class Designer extends Events {
// Hack: if parent is central topic, add node below not on opposite side. // Hack: if parent is central topic, add node below not on opposite side.
// This should be done in the layout // This should be done in the layout
if (parentTopic.getType() === INodeModel.CENTRAL_TOPIC_TYPE) { if (parentTopic.getType() === 'CentralTopic') {
siblingModel.setOrder(topic.getOrder() + 2); siblingModel.setOrder(topic.getOrder() + 2);
} }
@ -954,7 +954,7 @@ class Designer extends Events {
/** */ /** */
changeTopicShape(shape) { changeTopicShape(shape) {
const validateFunc = (topic) => !( const validateFunc = (topic) => !(
topic.getType() === INodeModel.CENTRAL_TOPIC_TYPE && shape === TopicShape.LINE topic.getType() === 'CentralTopic' && shape === TopicShape.LINE
); );
const validateError = 'Central Topic shape can not be changed to line figure.'; const validateError = 'Central Topic shape can not be changed to line figure.';

View File

@ -145,7 +145,7 @@ class DragPivot {
let result = null; let result = null;
const parentTopic = this._targetTopic; const parentTopic = this._targetTopic;
if (parentTopic) { if (parentTopic) {
if (parentTopic.getType() === INodeModel.CENTRAL_TOPIC_TYPE) { if (parentTopic.getType() === 'CentralTopic') {
result = this._straightLine; result = this._straightLine;
} else { } else {
result = this._curvedLine; result = this._curvedLine;

View File

@ -25,15 +25,12 @@ import {
} from '@wisemapping/web2d'; } from '@wisemapping/web2d';
import IconGroupRemoveTip from './IconGroupRemoveTip'; import IconGroupRemoveTip from './IconGroupRemoveTip';
import NoteModel from './model/NoteModel';
import LinkModel from './model/LinkModel';
import IconModel from './model/IconModel';
import Icon from './Icon'; import Icon from './Icon';
const ORDER_BY_TYPE = new Map(); const ORDER_BY_TYPE = new Map();
ORDER_BY_TYPE.set(IconModel.FEATURE_TYPE, 0); ORDER_BY_TYPE.set('icon', 0);
ORDER_BY_TYPE.set(NoteModel.FEATURE_TYPE, 1); ORDER_BY_TYPE.set('note', 1);
ORDER_BY_TYPE.set(LinkModel.FEATURE_TYPE, 2); ORDER_BY_TYPE.set('link', 2);
class IconGroup { class IconGroup {
constructor(topicId, iconSize) { constructor(topicId, iconSize) {

View File

@ -22,9 +22,9 @@ export const create = (nodeModel, options) => {
$assert(type, 'Node model type can not be null'); $assert(type, 'Node model type can not be null');
let result; let result;
if (type === INodeModel.CENTRAL_TOPIC_TYPE) { if (type === 'CentralTopic') {
result = new CentralTopic(nodeModel, options); result = new CentralTopic(nodeModel, options);
} else if (type === INodeModel.MAIN_TOPIC_TYPE) { } else if (type === 'MainTopic') {
result = new MainTopic(nodeModel, options); result = new MainTopic(nodeModel, options);
} else { } else {
$assert(false, `unsupported node type:${type}`); $assert(false, `unsupported node type:${type}`);

View File

@ -86,7 +86,7 @@ class Relationship extends ConnectionLine {
const targetTopic = this._targetTopic; const targetTopic = this._targetTopic;
let targetPosition = targetTopic.getPosition(); let targetPosition = targetTopic.getPosition();
if (targetTopic.getType() === INodeModel.CENTRAL_TOPIC_TYPE) { if (targetTopic.getType() === 'CentralTopic') {
targetPosition = Shape.workoutIncomingConnectionPoint(targetTopic, sourcePosition); targetPosition = Shape.workoutIncomingConnectionPoint(targetTopic, sourcePosition);
} }

View File

@ -131,7 +131,7 @@ class RelationshipPivot {
_calculateFromPosition(toPosition) { _calculateFromPosition(toPosition) {
// Calculate origin position ... // Calculate origin position ...
let sourcePosition = this._sourceTopic.getPosition(); let sourcePosition = this._sourceTopic.getPosition();
if (this._sourceTopic.getType() === INodeModel.CENTRAL_TOPIC_TYPE) { if (this._sourceTopic.getType() === 'CentralTopic') {
sourcePosition = Shape.workoutIncomingConnectionPoint(this._sourceTopic, toPosition); sourcePosition = Shape.workoutIncomingConnectionPoint(this._sourceTopic, toPosition);
} }
const controlPoint = Shape.calculateDefaultControlPoints(sourcePosition, toPosition); const controlPoint = Shape.calculateDefaultControlPoints(sourcePosition, toPosition);

View File

@ -1337,7 +1337,7 @@ class Topic extends NodeGraph {
/** @return {Boolean} true if the topic is the central topic of the map */ /** @return {Boolean} true if the topic is the central topic of the map */
isCentralTopic() { isCentralTopic() {
return this.getModel().getType() === INodeModel.CENTRAL_TOPIC_TYPE; return this.getModel().getType() === 'CentralTopic';
} }
} }

View File

@ -17,29 +17,26 @@
*/ */
import { $assert } from '@wisemapping/core-js'; import { $assert } from '@wisemapping/core-js';
import IconModel from './model/IconModel';
import ImageIcon from './ImageIcon'; import ImageIcon from './ImageIcon';
import LinkModel from './model/LinkModel';
import LinkIcon from './LinkIcon'; import LinkIcon from './LinkIcon';
import NoteModel from './model/NoteModel';
import NoteIcon from './NoteIcon'; import NoteIcon from './NoteIcon';
const TopicFeatureFactory = { const TopicFeatureFactory = {
/** the icon object */ /** the icon object */
Icon: { Icon: {
id: IconModel.FEATURE_TYPE, id: 'icon',
icon: ImageIcon, icon: ImageIcon,
}, },
/** the link object */ /** the link object */
Link: { Link: {
id: LinkModel.FEATURE_TYPE, id: 'link',
icon: LinkIcon, icon: LinkIcon,
}, },
/** the note object */ /** the note object */
Note: { Note: {
id: NoteModel.FEATURE_TYPE, id: 'note',
icon: NoteIcon, icon: NoteIcon,
}, },

View File

@ -16,6 +16,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Mindmap } from "../.."; import { Mindmap } from "../..";
import INodeModel from "../model/INodeModel";
import LinkModel from "../model/LinkModel";
import NodeModel from "../model/NodeModel"; import NodeModel from "../model/NodeModel";
import Exporter from "./Exporter"; import Exporter from "./Exporter";
@ -37,17 +39,20 @@ class TxtExporter implements Exporter {
return Promise.resolve(retult); return Promise.resolve(retult);
} }
private traverseBranch(prefix: string, branches: Array<NodeModel>) { private traverseBranch(prefix: string, branches: Array<INodeModel>) {
let result = ''; let result = '';
branches.forEach((node, index) => { branches.forEach((node, index) => {
result = result + `${prefix}${index+1} ${node.getText()}`; result = result + `${prefix}${index+1} ${node.getText()}`;
node.getFeatures().forEach((f)=>{ node.getFeatures().forEach((f)=>{
const type = f.getType();
if(type === 'link'){
result = result + ` [link: ${(f as LinkModel).getUrl()}]`
}
}); });
result = result + '\n'; result = result + '\n';
if (node.getChildren().length > 0) { if (node.getChildren().length > 0) {
result = result + this.traverseBranch(`${prefix}${index+1}.`, node.getChildren()); result = result + this.traverseBranch(`\t${prefix}${index+1}.`, node.getChildren());
} }
}); });
return result; return result;

View File

@ -15,16 +15,23 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { $assert, $defined } from '@wisemapping/core-js'; import { $assert } from '@wisemapping/core-js';
export type FeatureType = 'note' | 'link' | 'icon';
class FeatureModel { class FeatureModel {
static _next_id = 0;
private _id: number;
private _type: FeatureType;
private _attributes: {};
/** /**
* @constructs * @constructs
* @param type * @param type
* @throws will throw an exception if type is null or undefined * @throws will throw an exception if type is null or undefined
* assigns a unique id and the given type to the new model * assigns a unique id and the given type to the new model
*/ */
constructor(type) { constructor(type: FeatureType) {
$assert(type, 'type can not be null'); $assert(type, 'type can not be null');
this._id = FeatureModel._nextUUID(); this._id = FeatureModel._nextUUID();
@ -77,19 +84,15 @@ class FeatureModel {
this._id = id; this._id = id;
} }
/** */ getType(): FeatureType {
getType() {
return this._type; return this._type;
} }
}
FeatureModel._nextUUID = function _nextUUID() { static _nextUUID(): number {
if (!$defined(FeatureModel._uuid)) { const result = FeatureModel._next_id + 1;
FeatureModel._uuid = 0; FeatureModel._next_id = result;
return result;
}
} }
FeatureModel._uuid += 1;
return FeatureModel._uuid;
};
export default FeatureModel; export default FeatureModel;

View File

@ -2,20 +2,25 @@ import { $assert } from '@wisemapping/core-js';
import IconModel from './IconModel'; import IconModel from './IconModel';
import LinkModel from './LinkModel'; import LinkModel from './LinkModel';
import NoteModel from './NoteModel'; import NoteModel from './NoteModel';
import FeatureModel from './FeatureModel'; import FeatureModel, { FeatureType } from './FeatureModel';
interface NodeById {
id: FeatureType,
model: typeof FeatureModel;
}
class FeatureModelFactory { class FeatureModelFactory {
static modelById: Array<NodeById> = [{
private static modelById = [{ id: 'icon',
id: IconModel.FEATURE_TYPE,
model: IconModel, model: IconModel,
}, { }, {
id: LinkModel.FEATURE_TYPE, id: 'link',
model: LinkModel, model: LinkModel,
}, { }, {
id: NoteModel.FEATURE_TYPE, id: 'note',
model: NoteModel, model: NoteModel,
}] as const; }];
static createModel(type: string, attributes): FeatureModel { static createModel(type: string, attributes): FeatureModel {
$assert(type, 'type can not be null'); $assert(type, 'type can not be null');

View File

@ -18,35 +18,36 @@
* limitations under the License. * limitations under the License.
*/ */
import { $assert } from '@wisemapping/core-js'; import { $assert } from '@wisemapping/core-js';
import INodeModel, { NodeModelType as NodeType } from './INodeModel';
import NodeModel from './NodeModel'; import NodeModel from './NodeModel';
import RelationshipModel from './RelationshipModel'; import RelationshipModel from './RelationshipModel';
abstract class IMindmap { abstract class IMindmap {
getCentralTopic(): NodeModel { getCentralTopic(): INodeModel {
return this.getBranches()[0]; return this.getBranches()[0];
} }
abstract getDescription(): string; abstract getDescription(): string;
abstract setDescription(value: string); abstract setDescription(value: string): void;
abstract getId(): string abstract getId(): string
abstract setId(id: string); abstract setId(id: string): void;
abstract getVersion(): string; abstract getVersion(): string;
abstract setVersion(version: string): void; abstract setVersion(version: string): void;
abstract addBranch(nodeModel: NodeModel): void; abstract addBranch(nodeModel: INodeModel): void;
abstract getBranches(): Array<NodeModel>; abstract getBranches(): Array<INodeModel>;
abstract removeBranch(node: NodeModel): void; abstract removeBranch(node: INodeModel): void;
abstract getRelationships(): Array<RelationshipModel>; abstract getRelationships(): Array<RelationshipModel>;
connect(parent: NodeModel, child: NodeModel): void { connect(parent: INodeModel, child: INodeModel): void {
// Child already has a parent ? // Child already has a parent ?
$assert(!child.getParent(), 'Child model seems to be already connected'); $assert(!child.getParent(), 'Child model seems to be already connected');
@ -62,7 +63,7 @@ abstract class IMindmap {
* @throws will throw an error if child is null or undefined * @throws will throw an error if child is null or undefined
* @throws will throw an error if child's parent cannot be found * @throws will throw an error if child's parent cannot be found
*/ */
disconnect(child: NodeModel): void { disconnect(child: INodeModel): void {
const parent = child.getParent(); const parent = child.getParent();
$assert(child, 'Child can not be null.'); $assert(child, 'Child can not be null.');
$assert(parent, 'Child model seems to be already connected'); $assert(parent, 'Child model seems to be already connected');
@ -71,23 +72,15 @@ abstract class IMindmap {
this.addBranch(child); this.addBranch(child);
} }
/** @abstract */ abstract hasAlreadyAdded(node: INodeModel): boolean;
hasAlreadyAdded(node) {
throw new Error('Unsupported operation');
}
/** @abstract */ abstract createNode(type: NodeType, id: number):void;
createNode(type, id) {
throw new Error('Unsupported operation');
}
abstract createRelationship(fromNode: NodeModel, toNode: NodeModel): void; abstract createRelationship(fromNode: NodeModel, toNode: NodeModel): void;
abstract addRelationship(rel: RelationshipModel): void; abstract addRelationship(rel: RelationshipModel): void;
deleteRelationship(relationship: RelationshipModel): void { abstract deleteRelationship(relationship: RelationshipModel): void;
throw new Error('Unsupported operation');
}
/** */ /** */
inspect() { inspect() {

View File

@ -17,47 +17,53 @@
* limitations under the License. * limitations under the License.
*/ */
import { $assert, $defined } from '@wisemapping/core-js'; import { $assert, $defined } from '@wisemapping/core-js';
import FeatureModel from './FeatureModel';
import Mindmap from './Mindmap';
// regex taken from https://stackoverflow.com/a/34763398/58128 // regex taken from https://stackoverflow.com/a/34763398/58128
const parseJsObject = (str) => JSON.parse(str.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": ')); const parseJsObject = (str: string) => JSON.parse(str.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": '));
class INodeModel { abstract class INodeModel {
constructor(mindmap) { static MAIN_TOPIC_TO_MAIN_TOPIC_DISTANCE: number = 220;
static _next_uuid: number = 0;
protected _mindmap: Mindmap;
constructor(mindmap: Mindmap) {
$assert(mindmap && mindmap.getBranches, 'mindmap can not be null'); $assert(mindmap && mindmap.getBranches, 'mindmap can not be null');
this._mindmap = mindmap; this._mindmap = mindmap;
} }
/** */ getId(): number {
getId() {
return this.getProperty('id'); return this.getProperty('id');
} }
abstract getFeatures(): Array<FeatureModel>;
/** */ /** */
setId(id) { setId(id: number): void {
if (!$defined(id)) { if (!$defined(id)) {
const newId = INodeModel._nextUUID(); const newId = INodeModel._nextUUID();
this.putProperty('id', newId); this.putProperty('id', newId);
} else { } else {
if (id > INodeModel._uuid) { if (id > INodeModel._next_uuid) {
$assert(Number.isFinite(id)); $assert(Number.isFinite(id));
INodeModel._uuid = id; INodeModel._next_uuid = id;
} }
this.putProperty('id', id); this.putProperty('id', id);
} }
} }
/** */ getType(): NodeModelType {
getType() {
return this.getProperty('type'); return this.getProperty('type');
} }
/** */ /** */
setType(type) { setType(type: NodeModelType): void {
this.putProperty('type', type); this.putProperty('type', type);
} }
/** */ /** */
setText(text) { setText(text: string): void {
this.putProperty('text', text); this.putProperty('text', text);
} }
@ -315,39 +321,55 @@ class INodeModel {
// console.log("After:" + mindmap.inspect()); // console.log("After:" + mindmap.inspect());
} }
/** @abstract */ abstract getPropertiesKeys(): string[];
getPropertiesKeys() {
throw new Error('Unsupported operation'); abstract getProperty(key: string);
abstract putProperty(key: string, value: any): void;
abstract setParent(parent: INodeModel): void;
abstract getChildren(): INodeModel[];
abstract getParent(): INodeModel;
abstract clone(): INodeModel;
isChildNode(node: INodeModel): boolean {
let result = false;
if (node === this) {
result = true;
} else {
const children = this.getChildren();
for (let i = 0; i < children.length; i++) {
const child = children[i];
result = child.isChildNode(node);
if (result) {
break;
}
}
}
return result;
} }
/** @abstract */ findNodeById(id: number): INodeModel {
// eslint-disable-next-line no-unused-vars $assert(Number.isFinite(id));
putProperty(key, value) { let result = null;
throw new Error('Unsupported operation'); if (this.getId() === id) {
result = this;
} else {
const children = this.getChildren();
for (let i = 0; i < children.length; i++) {
const child = children[i];
result = child.findNodeById(id);
if (result) {
break;
}
}
}
return result;
} }
/** @abstract */
// eslint-disable-next-line no-unused-vars
setParent(parent) {
throw new Error('Unsupported operation');
}
/** @abstract */
getChildren() {
throw new Error('Unsupported operation');
}
/** @abstract */
getParent() {
throw new Error('Unsupported operation');
}
/** @abstract */
clone() {
throw new Error('Unsupported operation');
}
/** */
inspect() { inspect() {
let result = `{ type: ${this.getType()} , id: ${this.getId()} , text: ${this.getText()}`; let result = `{ type: ${this.getType()} , id: ${this.getId()} , text: ${this.getText()}`;
@ -369,16 +391,14 @@ class INodeModel {
return result; return result;
} }
/** @abstract */ abstract removeChild(child: INodeModel);
// eslint-disable-next-line no-unused-vars
removeChild(child) { static _nextUUID(): number {
throw new Error('Unsupported operation'); INodeModel._next_uuid += 1;
} return INodeModel._next_uuid;
};
} }
/**
* @enum {String}
*/
const TopicShape = { const TopicShape = {
RECTANGLE: 'rectagle', RECTANGLE: 'rectagle',
ROUNDED_RECT: 'rounded rectagle', ROUNDED_RECT: 'rounded rectagle',
@ -387,38 +407,10 @@ const TopicShape = {
IMAGE: 'image', IMAGE: 'image',
}; };
/** export type NodeModelType = 'CentralTopic' | 'MainTopic';
* @constant
* @type {String}
* @default
*/
INodeModel.CENTRAL_TOPIC_TYPE = 'CentralTopic';
/**
* @constant
* @type {String}
* @default
*/
INodeModel.MAIN_TOPIC_TYPE = 'MainTopic';
/**
* @constant
* @type {Number}
* @default
*/
INodeModel.MAIN_TOPIC_TO_MAIN_TOPIC_DISTANCE = 220;
/** /**
* @todo: This method must be implemented. (unascribed) * @todo: This method must be implemented. (unascribed)
*/ */
INodeModel._nextUUID = () => {
if (!$defined(INodeModel._uuid)) {
INodeModel._uuid = 0;
}
INodeModel._uuid += 1;
return INodeModel._uuid;
};
INodeModel._uuid = 0;
export { TopicShape }; export { TopicShape };
export default INodeModel; export default INodeModel;

View File

@ -20,27 +20,17 @@ import FeatureModel from './FeatureModel';
class IconModel extends FeatureModel { class IconModel extends FeatureModel {
constructor(attributes) { constructor(attributes) {
super(IconModel.FEATURE_TYPE); super('icon');
this.setIconType(attributes.id); this.setIconType(attributes.id);
} }
/** @return the icon type id */ getIconType(): string {
getIconType() {
return this.getAttribute('id'); return this.getAttribute('id');
} }
/** @param {String} iconType the icon type id */ setIconType(iconType: string) {
setIconType(iconType) {
$assert(iconType, 'iconType id can not be null'); $assert(iconType, 'iconType id can not be null');
this.setAttribute('id', iconType); this.setAttribute('id', iconType);
} }
} }
/**
* @constant
* @type {String}
* @default
*/
IconModel.FEATURE_TYPE = 'icon';
export default IconModel; export default IconModel;

View File

@ -20,12 +20,12 @@ import FeatureModel from './FeatureModel';
class LinkModel extends FeatureModel { class LinkModel extends FeatureModel {
constructor(attributes) { constructor(attributes) {
super(LinkModel.FEATURE_TYPE); super('link');
this.setUrl(attributes.url); this.setUrl(attributes.url);
} }
/** @return {String} the url attribute value */ /** @return {String} the url attribute value */
getUrl() { getUrl():string {
return this.getAttribute('url'); return this.getAttribute('url');
} }
@ -33,7 +33,7 @@ class LinkModel extends FeatureModel {
* @param {String} url a URL provided by the user to set the link to * @param {String} url a URL provided by the user to set the link to
* @throws will throw an error if url is null or undefined * @throws will throw an error if url is null or undefined
*/ */
setUrl(url) { setUrl(url: string): void {
$assert(url, 'url can not be null'); $assert(url, 'url can not be null');
const fixedUrl = LinkModel._fixUrl(url); const fixedUrl = LinkModel._fixUrl(url);
@ -44,7 +44,7 @@ class LinkModel extends FeatureModel {
} }
// url format is already checked in LinkEditor.checkUrl // url format is already checked in LinkEditor.checkUrl
static _fixUrl(url) { static _fixUrl(url: string): string {
let result = url; let result = url;
if (!result.includes('http://') && !result.includes('https://') && !result.includes('mailto://')) { if (!result.includes('http://') && !result.includes('https://') && !result.includes('mailto://')) {
result = `http://${result}`; result = `http://${result}`;
@ -61,12 +61,4 @@ class LinkModel extends FeatureModel {
this.setAttribute('urlType', urlType); this.setAttribute('urlType', urlType);
} }
} }
/**
* @constant
* @type {String}
* @default
*/
LinkModel.FEATURE_TYPE = 'link';
export default LinkModel; export default LinkModel;

View File

@ -17,17 +17,17 @@
*/ */
import { $assert, $defined } from '@wisemapping/core-js'; import { $assert, $defined } from '@wisemapping/core-js';
import IMindmap from './IMindmap'; import IMindmap from './IMindmap';
import INodeModel from './INodeModel'; import INodeModel, { NodeModelType } from './INodeModel';
import NodeModel from './NodeModel'; import NodeModel from './NodeModel';
import RelationshipModel from './RelationshipModel'; import RelationshipModel from './RelationshipModel';
import ModelCodeName from '../persistence/ModelCodeName'; import ModelCodeName from '../persistence/ModelCodeName';
class Mindmap extends IMindmap { class Mindmap extends IMindmap {
_description: string; private _description: string;
_version: string; private _version: string;
_id: string; private _id: string;
_branches: Array<NodeModel>; private _branches: Array<NodeModel>;
_relationships: Array<RelationshipModel>; private _relationships: Array<RelationshipModel>;
constructor(id: string, version: string = ModelCodeName.TANGO) { constructor(id: string, version: string = ModelCodeName.TANGO) {
super(); super();
@ -79,10 +79,10 @@ class Mindmap extends IMindmap {
$assert(nodeModel && nodeModel.isNodeModel(), 'Add node must be invoked with model objects'); $assert(nodeModel && nodeModel.isNodeModel(), 'Add node must be invoked with model objects');
const branches = this.getBranches(); const branches = this.getBranches();
if (branches.length === 0) { if (branches.length === 0) {
$assert(nodeModel.getType() === INodeModel.CENTRAL_TOPIC_TYPE, 'First element must be the central topic'); $assert(nodeModel.getType() === 'CentralTopic', 'First element must be the central topic');
nodeModel.setPosition(0, 0); nodeModel.setPosition(0, 0);
} else { } else {
$assert(nodeModel.getType() !== INodeModel.CENTRAL_TOPIC_TYPE, 'Mindmaps only have one cental topic'); $assert(nodeModel.getType() !== 'CentralTopic', 'Mindmaps only have one cental topic');
} }
this._branches.push(nodeModel); this._branches.push(nodeModel);
@ -91,7 +91,7 @@ class Mindmap extends IMindmap {
/** /**
* @param nodeModel * @param nodeModel
*/ */
removeBranch(nodeModel: NodeModel): void { removeBranch(nodeModel: INodeModel): void {
$assert(nodeModel && nodeModel.isNodeModel(), 'Remove node must be invoked with model objects'); $assert(nodeModel && nodeModel.isNodeModel(), 'Remove node must be invoked with model objects');
this._branches = this._branches.filter((b) => b !== nodeModel); this._branches = this._branches.filter((b) => b !== nodeModel);
} }
@ -104,29 +104,22 @@ class Mindmap extends IMindmap {
return this._relationships; return this._relationships;
} }
/**
* @param node hasAlreadyAdded(node: NodeModel): boolean {
* @return {Boolean} true if node already exists
*/
hasAlreadyAdded(node: any) {
let result = false; let result = false;
// Check in not connected nodes. // Check in not connected nodes.
const branches = this._branches; const branches = this._branches;
for (let i = 0; i < branches.length; i++) { for (let i = 0; i < branches.length; i++) {
result = branches[i]._isChildNode(node); result = branches[i].isChildNode(node);
if (result) { if (result) {
break; break;
} }
} }
return result;
} }
/** createNode(type: NodeModelType = 'MainTopic', id: number) {
* @param type
* @param id
* @return the node model created
*/
createNode(type = INodeModel.MAIN_TOPIC_TYPE, id: number) {
return new NodeModel(type, this, id); return new NodeModel(type, this, id);
} }
@ -172,7 +165,7 @@ class Mindmap extends IMindmap {
static buildEmpty = (mapId: string) => { static buildEmpty = (mapId: string) => {
const result = new Mindmap(mapId); const result = new Mindmap(mapId);
const node = result.createNode(INodeModel.CENTRAL_TOPIC_TYPE, 0); const node = result.createNode('CentralTopic', 0);
result.addBranch(node); result.addBranch(node);
return result; return result;
}; };

View File

@ -17,21 +17,28 @@
*/ */
import { $assert, $defined } from '@wisemapping/core-js'; import { $assert, $defined } from '@wisemapping/core-js';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import INodeModel from './INodeModel'; import INodeModel, { NodeModelType } from './INodeModel';
import FeatureModelFactory from './FeatureModelFactory'; import FeatureModelFactory from './FeatureModelFactory';
import FeatureModel from './FeatureModel';
import Mindmap from './Mindmap';
class NodeModel extends INodeModel { class NodeModel extends INodeModel {
constructor(type, mindmap, id) { private _properties: {};
private _children: INodeModel[];
private _features: FeatureModel[];
private _parent: INodeModel;
constructor(type: NodeModelType, mindmap: Mindmap, id: number) {
$assert(type, 'Node type can not be null'); $assert(type, 'Node type can not be null');
$assert(mindmap, 'mindmap can not be null'); $assert(mindmap, 'mindmap can not be null');
super(mindmap); super(mindmap);
this._properties = {}; this._properties = {};
this.setId(id); this.setId(id);
this.setType(type); this.setType(type);
this.areChildrenShrunken(false); this.areChildrenShrunken();
this._children = []; this._children = [];
this._feature = []; this._features = [];
} }
/** /**
@ -49,12 +56,12 @@ class NodeModel extends INodeModel {
*/ */
addFeature(feature) { addFeature(feature) {
$assert(feature, 'feature can not be null'); $assert(feature, 'feature can not be null');
this._feature.push(feature); this._features.push(feature);
} }
/** */ /** */
getFeatures() { getFeatures() {
return this._feature; return this._features;
} }
/** /**
@ -64,9 +71,9 @@ class NodeModel extends INodeModel {
*/ */
removeFeature(feature) { removeFeature(feature) {
$assert(feature, 'feature can not be null'); $assert(feature, 'feature can not be null');
const size = this._feature.length; const size = this._features.length;
this._feature = this._feature.filter((f) => feature.getId() !== f.getId()); this._features = this._features.filter((f) => feature.getId() !== f.getId());
$assert(size - 1 === this._feature.length, 'Could not be removed ...'); $assert(size - 1 === this._features.length, 'Could not be removed ...');
} }
/** /**
@ -75,7 +82,7 @@ class NodeModel extends INodeModel {
*/ */
findFeatureByType(type) { findFeatureByType(type) {
$assert(type, 'type can not be null'); $assert(type, 'type can not be null');
return this._feature.filter((feature) => feature.getType() === type); return this._features.filter((feature) => feature.getType() === type);
} }
/** /**
@ -86,7 +93,7 @@ class NodeModel extends INodeModel {
*/ */
findFeatureById(id) { findFeatureById(id) {
$assert($defined(id), 'id can not be null'); $assert($defined(id), 'id can not be null');
const result = this._feature.filter((feature) => feature.getId() === id); const result = this._features.filter((feature) => feature.getId() === id);
$assert(result.length === 1, `Feature could not be found:${id}`); $assert(result.length === 1, `Feature could not be found:${id}`);
return result[0]; return result[0];
} }
@ -112,7 +119,7 @@ class NodeModel extends INodeModel {
} }
/** */ /** */
getProperty(key) { getProperty(key: string) {
$defined(key, 'key can not be null'); $defined(key, 'key can not be null');
const result = this._properties[key]; const result = this._properties[key];
return !$defined(result) ? null : result; return !$defined(result) ? null : result;
@ -122,15 +129,15 @@ class NodeModel extends INodeModel {
* @return {mindplot.model.NodeModel} an identical clone of the NodeModel * @return {mindplot.model.NodeModel} an identical clone of the NodeModel
*/ */
clone() { clone() {
const result = new NodeModel(this.getType(), this._mindmap); const result = new NodeModel(this.getType(), this._mindmap, -1);
result._children = this._children.map((node) => { result._children = this._children.map((node) => {
const cnode = node.clone(); const cnode = node.clone() as NodeModel;
cnode._parent = result; cnode._parent = result;
return cnode; return cnode;
}); });
result._properties = cloneDeep(this._properties); result._properties = cloneDeep(this._properties);
result._feature = cloneDeep(this._feature); result._features = cloneDeep(this._features);
return result; return result;
} }
@ -138,19 +145,19 @@ class NodeModel extends INodeModel {
* Similar to clone, assign new id to the elements ... * Similar to clone, assign new id to the elements ...
* @return {mindplot.model.NodeModel} * @return {mindplot.model.NodeModel}
*/ */
deepCopy() { deepCopy(): NodeModel {
const result = new NodeModel(this.getType(), this._mindmap); const result = new NodeModel(this.getType(), this._mindmap, -1);
result._children = this._children.map((node) => { result._children = this._children.map((node) => {
const cnode = node.deepCopy(); const cnode = (node as NodeModel).deepCopy();
cnode._parent = result; cnode._parent = result;
return cnode; return cnode;
}); });
const id = result.getId(); const id = result.getId();
result._properties = Object.clone(this._properties); result._properties = Object.assign({}, this._properties);
result.setId(id); result.setId(id);
result._feature = this._feature.clone(); result._features = cloneDeep(this._features);
return result; return result;
} }
@ -158,7 +165,7 @@ class NodeModel extends INodeModel {
* @param {mindplot.model.NodeModel} child * @param {mindplot.model.NodeModel} child
* @throws will throw an error if child is null, undefined or not a NodeModel object * @throws will throw an error if child is null, undefined or not a NodeModel object
*/ */
append(child) { append(child: NodeModel) {
$assert(child && child.isNodeModel(), 'Only NodeModel can be appended to Mindmap object'); $assert(child && child.isNodeModel(), 'Only NodeModel can be appended to Mindmap object');
this._children.push(child); this._children.push(child);
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
@ -169,7 +176,7 @@ class NodeModel extends INodeModel {
* @param {mindplot.model.NodeModel} child * @param {mindplot.model.NodeModel} child
* @throws will throw an error if child is null, undefined or not a NodeModel object * @throws will throw an error if child is null, undefined or not a NodeModel object
*/ */
removeChild(child) { removeChild(child): void {
$assert(child && child.isNodeModel(), 'Only NodeModel can be appended to Mindmap object.'); $assert(child && child.isNodeModel(), 'Only NodeModel can be appended to Mindmap object.');
this._children = this._children.filter((c) => c !== child); this._children = this._children.filter((c) => c !== child);
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
@ -192,44 +199,6 @@ class NodeModel extends INodeModel {
this._parent = parent; this._parent = parent;
} }
_isChildNode(node) {
let result = false;
if (node === this) {
result = true;
} else {
const children = this.getChildren();
for (let i = 0; i < children.length; i++) {
const child = children[i];
result = child._isChildNode(node);
if (result) {
break;
}
}
}
return result;
}
/**
* @id
* @return {mindplot.model.NodeModel} the node with the respective id
*/
findNodeById(id) {
$assert(Number.isFinite(id));
let result = null;
if (this.getId() === id) {
result = this;
} else {
const children = this.getChildren();
for (let i = 0; i < children.length; i++) {
const child = children[i];
result = child.findNodeById(id);
if (result) {
break;
}
}
}
return result;
}
} }
export default NodeModel; export default NodeModel;

View File

@ -20,7 +20,7 @@ import FeatureModel from './FeatureModel';
class NoteModel extends FeatureModel { class NoteModel extends FeatureModel {
constructor(attributes) { constructor(attributes) {
super(NoteModel.FEATURE_TYPE); super('note');
const noteText = attributes.text ? attributes.text : ' '; const noteText = attributes.text ? attributes.text : ' ';
this.setText(noteText); this.setText(noteText);
} }
@ -37,11 +37,4 @@ class NoteModel extends FeatureModel {
} }
} }
/**
* @constant
* @type {String}
* @default
*/
NoteModel.FEATURE_TYPE = 'note';
export default NoteModel; export default NoteModel;

View File

@ -16,10 +16,21 @@
* limitations under the License. * limitations under the License.
*/ */
import { $assert, $defined } from '@wisemapping/core-js'; import { $assert, $defined } from '@wisemapping/core-js';
import Point from '@wisemapping/web2d';
import ConnectionLine from '../ConnectionLine'; import ConnectionLine from '../ConnectionLine';
class RelationshipModel { class RelationshipModel {
constructor(sourceTopicId, targetTopicId) { static _next_uuid: number = 0;
private _id: number;
private _sourceTargetId: number;
private _targetTopicId: number;
private _lineType: number;
private _srcCtrlPoint: Point;
private _destCtrlPoint: Point;
private _endArrow: boolean;
private _startArrow: boolean;
constructor(sourceTopicId: number, targetTopicId: number) {
$assert($defined(sourceTopicId), 'from node type can not be null'); $assert($defined(sourceTopicId), 'from node type can not be null');
$assert($defined(targetTopicId), 'to node type can not be null'); $assert($defined(targetTopicId), 'to node type can not be null');
$assert(Number.isFinite(sourceTopicId), 'sourceTopicId is not a number'); $assert(Number.isFinite(sourceTopicId), 'sourceTopicId is not a number');
@ -46,7 +57,7 @@ class RelationshipModel {
} }
/** */ /** */
getId() { getId():number {
$assert(this._id, 'id is null'); $assert(this._id, 'id is null');
return this._id; return this._id;
} }
@ -57,27 +68,27 @@ class RelationshipModel {
} }
/** */ /** */
setLineType(lineType) { setLineType(lineType: number) {
this._lineType = lineType; this._lineType = lineType;
} }
/** */ /** */
getSrcCtrlPoint() { getSrcCtrlPoint(): Point {
return this._srcCtrlPoint; return this._srcCtrlPoint;
} }
/** */ /** */
setSrcCtrlPoint(srcCtrlPoint) { setSrcCtrlPoint(srcCtrlPoint: Point) {
this._srcCtrlPoint = srcCtrlPoint; this._srcCtrlPoint = srcCtrlPoint;
} }
/** */ /** */
getDestCtrlPoint() { getDestCtrlPoint(): Point {
return this._destCtrlPoint; return this._destCtrlPoint;
} }
/** */ /** */
setDestCtrlPoint(destCtrlPoint) { setDestCtrlPoint(destCtrlPoint: Point) {
this._destCtrlPoint = destCtrlPoint; this._destCtrlPoint = destCtrlPoint;
} }
@ -87,7 +98,7 @@ class RelationshipModel {
} }
/** */ /** */
setEndArrow(endArrow) { setEndArrow(endArrow: boolean) {
this._endArrow = endArrow; this._endArrow = endArrow;
} }
@ -97,7 +108,7 @@ class RelationshipModel {
} }
/** */ /** */
setStartArrow(startArrow) { setStartArrow(startArrow: boolean) {
this._startArrow = startArrow; this._startArrow = startArrow;
} }
@ -118,23 +129,19 @@ class RelationshipModel {
/** /**
* @return {String} textual information about the relationship's source and target node * @return {String} textual information about the relationship's source and target node
*/ */
inspect() { inspect(): string {
return ( return (
`(fromNode:${this.getFromNode().getId() `(fromNode:${this.getFromNode()
} , toNode: ${this.getToNode().getId() } , toNode: ${this.getToNode()
})` })`
); );
} }
static _nextUUID() {
RelationshipModel._next_uuid += 1;
return RelationshipModel._next_uuid;
}
} }
function _nextUUID() {
if (!$defined(RelationshipModel._uuid)) {
RelationshipModel._uuid = 0;
}
RelationshipModel._uuid += 1;
return RelationshipModel._uuid;
}
RelationshipModel._nextUUID = _nextUUID;
export default RelationshipModel; export default RelationshipModel;

View File

@ -51,11 +51,11 @@ class XMLSerializerBeta {
const parentTopic = document.createElement('topic'); const parentTopic = document.createElement('topic');
// Set topic attributes... // Set topic attributes...
if (topic.getType() === INodeModel.CENTRAL_TOPIC_TYPE) { if (topic.getType() === 'CentralTopic') {
parentTopic.setAttribute('central', true); parentTopic.setAttribute('central', true);
} else { } else {
const parent = topic.getParent(); const parent = topic.getParent();
if (parent == null || parent.getType() === INodeModel.CENTRAL_TOPIC_TYPE) { if (parent == null || parent.getType() === 'CentralTopic') {
const pos = topic.getPosition(); const pos = topic.getPosition();
parentTopic.setAttribute('position', `${pos.x},${pos.y}`); parentTopic.setAttribute('position', `${pos.x},${pos.y}`);
} else { } else {
@ -206,8 +206,8 @@ class XMLSerializerBeta {
_deserializeNode(domElem, mindmap) { _deserializeNode(domElem, mindmap) {
const type = domElem.getAttribute('central') != null const type = domElem.getAttribute('central') != null
? INodeModel.CENTRAL_TOPIC_TYPE ? 'CentralTopic'
: INodeModel.MAIN_TOPIC_TYPE; : 'MainTopic';
const topic = mindmap.createNode(type); const topic = mindmap.createNode(type);
// Load attributes... // Load attributes...

View File

@ -68,7 +68,7 @@ class XMLSerializerPela {
const parentTopic = document.createElement('topic'); const parentTopic = document.createElement('topic');
// Set topic attributes... // Set topic attributes...
if (topic.getType() === INodeModel.CENTRAL_TOPIC_TYPE) { if (topic.getType() === 'CentralTopic') {
parentTopic.setAttribute('central', 'true'); parentTopic.setAttribute('central', 'true');
} else { } else {
const pos = topic.getPosition(); const pos = topic.getPosition();
@ -96,7 +96,7 @@ class XMLSerializerPela {
} }
} }
if (topic.areChildrenShrunken() && topic.getType() !== INodeModel.CENTRAL_TOPIC_TYPE) { if (topic.areChildrenShrunken() && topic.getType() !== 'CentralTopic') {
parentTopic.setAttribute('shrink', 'true'); parentTopic.setAttribute('shrink', 'true');
} }
@ -268,8 +268,8 @@ class XMLSerializerPela {
_deserializeNode(domElem, mindmap) { _deserializeNode(domElem, mindmap) {
const type = domElem.getAttribute('central') != null const type = domElem.getAttribute('central') != null
? INodeModel.CENTRAL_TOPIC_TYPE ? 'CentralTopic'
: INodeModel.MAIN_TOPIC_TYPE; : 'MainTopic';
// Load attributes... // Load attributes...
let id = domElem.getAttribute('id'); let id = domElem.getAttribute('id');
@ -349,7 +349,7 @@ class XMLSerializerPela {
const isShrink = domElem.getAttribute('shrink'); const isShrink = domElem.getAttribute('shrink');
// Hack: Some production maps has been stored with the central topic collapsed. This is a bug. // Hack: Some production maps has been stored with the central topic collapsed. This is a bug.
if ($defined(isShrink) && type !== INodeModel.CENTRAL_TOPIC_TYPE) { if ($defined(isShrink) && type !== 'CentralTopic') {
topic.setChildrenShrunken(isShrink); topic.setChildrenShrunken(isShrink);
} }

View File

@ -22,7 +22,7 @@ import fs from 'fs';
import { diff } from 'jest-diff'; import { diff } from 'jest-diff';
import { expect } from '@jest/globals'; import { expect } from '@jest/globals';
const saveOutputRecord = false; const saveOutputRecord = true;
export const setupBlob = () => { export const setupBlob = () => {
// Workaround for partial implementations on Jest: // Workaround for partial implementations on Jest:

View File

@ -1,6 +1,6 @@
1 PPM Plan 1 PPM Plan
1.1 Business Development 1.1 Business Development
1.2 Backlog Management 1.2 Backlog Management [link: https://docs.google.com/a/freeform.ca/drawings/d/1mrtkVAN3_XefJJCgfxw4Va6xk9TVDBKXDt_uzyIF4Us/edit]
1.3 Freeform IT 1.3 Freeform IT
1.4 Client Project Management 1.4 Client Project Management
1.5 Governance & Executive 1.5 Governance & Executive
@ -81,7 +81,7 @@
11.1.1 null 11.1.1 null
11.2 Strategic Priority 3b: Health Promotion 11.2 Strategic Priority 3b: Health Promotion
11.2.1 Health and Wellness Committee 11.2.1 Health and Wellness Committee
11.2.2 Work-life Balance Initiative 11.2.2 Work-life Balance Initiative [link: http://hrcouncil.ca/hr-toolkit/workplaces-health-safety.cfm]
11.3 So that... 11.3 So that...
12 Benefits 12 Benefits
12.1 As Freeform Staff 12.1 As Freeform Staff
@ -98,8 +98,8 @@
13.3 Drupal Community 13.3 Drupal Community
13.4 CiviCRM 13.4 CiviCRM
13.5 Other 13.5 Other
14 Backlog Plan 14 Backlog Plan [link: https://docs.google.com/a/freeform.ca/drawings/d/1mrtkVAN3_XefJJCgfxw4Va6xk9TVDBKXDt_uzyIF4Us/edit]
14.1 Go To Backlog Plan 14.1 Go To Backlog Plan [link: https://docs.google.com/a/freeform.ca/drawings/d/1mrtkVAN3_XefJJCgfxw4Va6xk9TVDBKXDt_uzyIF4Us/edit]
15 Strategy Prospecting 15 Strategy Prospecting
15.1 null 15.1 null
15.2 null 15.2 null

View File

@ -3,11 +3,11 @@
2.1 Normas aplicables a problemas de determinación de resultados 2.1 Normas aplicables a problemas de determinación de resultados
3 CIRCULANTES 3 CIRCULANTES
3.1 Adquisición temporal de acciones propias 3.1 Adquisición temporal de acciones propias
4 NIF A 4 NIF A [link: http://www.youtube.com/watch?v=7YN-sOlkQp0]
4.1 Marco conceptual 4.1 Marco conceptual
5 NIF C 5 NIF C [link: https://sites.google.com/site/contabilidadimcpnif/estructura-de-las-nif]
5.1 Normas aplicables a conceptos específicos de los estados financieros 5.1 Normas aplicables a conceptos específicos de los estados financieros
6 NIF E 6 NIF E
6.1 Normas aplicables alas actividades especializadas de distintos sectores 6.1 Normas aplicables alas actividades especializadas de distintos sectores
7 NIF B 7 NIF B [link: http://www.contaduria.uady.mx/files/cuerpo-acad/caef/aief/resumen_NIF_marco_conceptual.pdf]
7.1 Normas aplicables a los estados financieros en su conjunto 7.1 Normas aplicables a los estados financieros en su conjunto

View File

@ -29,7 +29,7 @@
1.5.1.1 Orange County Eye and Transplant Bank 1.5.1.1 Orange County Eye and Transplant Bank
1.5.1.2 Northern California Transplant Bank 1.5.1.2 Northern California Transplant Bank
1.5.1.2.1 In 2010, 2,500 referrals forwarded to OneLegacy 1.5.1.2.1 In 2010, 2,500 referrals forwarded to OneLegacy
1.5.1.3 Doheny Eye and Tissue Transplant Bank 1.5.1.3 Doheny Eye and Tissue Transplant Bank [link: http://www.dohenyeyebank.org/]
1.5.2 OneLegacy 1.5.2 OneLegacy
1.5.2.1 In 2010, 11,828 referrals 1.5.2.1 In 2010, 11,828 referrals
1.5.3 San Diego Eye Bank 1.5.3 San Diego Eye Bank

View File

@ -1,13 +1,13 @@
1 Welcome To WiseMapping 1 Welcome To WiseMapping
1.1 5 min tutorial video ? 1.1 5 min tutorial video ?
Follow the link ! Follow the link ! [link: https://www.youtube.com/tv?vq=medium#/watch?v=rKxZwNKs9cE]
1.2 Try it Now! 1.2 Try it Now!
1.2.1 Double Click 1.2.1 Double Click
1.2.2 Press "enter" to add a 1.2.2 Press "enter" to add a
Sibling Sibling
1.2.3 Drag map to move 1.2.3 Drag map to move
1.3 Features 1.3 Features
1.3.1 Links to Sites 1.3.1 Links to Sites [link: http://www.digg.com]
1.3.2 Styles 1.3.2 Styles
1.3.2.1 Fonts 1.3.2.1 Fonts
1.3.2.2 Topic Shapes 1.3.2.2 Topic Shapes
@ -24,8 +24,8 @@ Sibling
1.5.2 Brainstorming 1.5.2 Brainstorming
1.5.3 Visual 1.5.3 Visual
1.6 Install In Your Server 1.6 Install In Your Server
1.6.1 Open Source 1.6.1 Open Source [link: http://www.wisemapping.org/]
1.6.2 Download 1.6.2 Download [link: http://www.wisemapping.com/inyourserver.html]
1.7 Collaborate 1.7 Collaborate
1.7.1 Embed 1.7.1 Embed
1.7.2 Publish 1.7.2 Publish