feat: 后端全量更新 - 含所有本次需求
- Contract.php: 返回合约账户余额(balance_contract) - My.php: 地址管理增加BTC/ETH - AppContract.php: 一键平仓(closeall) - AppProxy.php: 代理专属注册链接 + 分级权限(L1/L2) - site.php: 手续费减半(0.018→0.009) - agent_permission_setup.sql: 代理权限SQL - crypto_news_crawler.py: 新闻自动采集脚本
@@ -0,0 +1,14 @@
|
||||
As of GoJS 2.0 many of these projects have moved to their own GitHub repositories.
|
||||
|
||||
There is a new package for Angular, named gojs-angular, which is at: https://github.com/NorthwoodsSoftware/gojs-angular
|
||||
It is also available at npm: https://www.npmjs.com/package/gojs-angular
|
||||
That package is used by a sample: https://github.com/NorthwoodsSoftware/gojs-angular-basic
|
||||
|
||||
There is a new package for React, named gojs-react, which is at: https://github.com/NorthwoodsSoftware/gojs-react
|
||||
It is also available at npm: https://www.npmjs.com/package/gojs-react
|
||||
That package is used by a sample: https://github.com/NorthwoodsSoftware/gojs-react-basic
|
||||
|
||||
The Electron sample is now at: https://github.com/NorthwoodsSoftware/GoJS-projects/tree/master/electron-circuit
|
||||
The Cordova sample is now at: https://github.com/NorthwoodsSoftware/GoJS-projects/tree/master/cordova-circuit
|
||||
The NW sample is now at: https://github.com/NorthwoodsSoftware/GoJS-projects/tree/master/nw-circuit
|
||||
The Vue Webpack sample is now at: https://github.com/NorthwoodsSoftware/GoJS-projects/tree/master/vue-webpack
|
||||
@@ -0,0 +1,133 @@
|
||||
/* for BPMN.html */
|
||||
|
||||
/* Diagram and palette area */
|
||||
|
||||
#myDiagramDiv {
|
||||
float: left;
|
||||
border: solid 1px gray;
|
||||
border-left: none;
|
||||
width: 84%;
|
||||
height: 700px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#description {
|
||||
float: left;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
#PaletteAndDiagram {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#sideBar {
|
||||
float: left;
|
||||
width: 15%;
|
||||
min-height: 500px;
|
||||
text-align: center;
|
||||
border: 1px solid gray;
|
||||
}
|
||||
|
||||
.myPaletteDiv {
|
||||
width: 100%;
|
||||
min-height: 625px;
|
||||
}
|
||||
|
||||
#accordion {
|
||||
margin: 0px;
|
||||
width: 97%;
|
||||
min-height: 625px;
|
||||
}
|
||||
|
||||
#myOverviewDiv {
|
||||
width: 100%;
|
||||
height: 225px;
|
||||
}
|
||||
|
||||
/* top bar area */
|
||||
|
||||
.ui-accordion .ui-accordion-content {
|
||||
padding: 1px;
|
||||
}
|
||||
.ui-menu {
|
||||
width: 120px;
|
||||
}
|
||||
#menuui {
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
}
|
||||
#menuui > li {
|
||||
float: left;
|
||||
}
|
||||
|
||||
|
||||
input {
|
||||
text-align: center;
|
||||
font-size: large;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#myOverviewDiv {
|
||||
background-color: lightgray;
|
||||
}
|
||||
|
||||
#currentFile {
|
||||
background: #1874CD;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-family: Arial;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
padding: 3px 0px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.draggable {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
border: 1px solid gray;
|
||||
background-color: #e2e2e2;
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
width: 300px;
|
||||
height: 200px;
|
||||
z-index: 500;
|
||||
}
|
||||
|
||||
.handle {
|
||||
background-color: #9CCB19;
|
||||
text-align: center;
|
||||
font: bold 12px sans-serif;
|
||||
}
|
||||
|
||||
.elementText {
|
||||
font-family: Arial;
|
||||
font-size: medium;
|
||||
margin-left: 10px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.mySavedFiles {
|
||||
font-family: Arial;
|
||||
font-size: medium;
|
||||
width: 250px;
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
.elementBtn {
|
||||
margin-top: 20px;
|
||||
font-family: Arial;
|
||||
font-size: medium;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
/* sample thumbnails bottom area */
|
||||
|
||||
figure {
|
||||
display: inline-block;
|
||||
margin: 10px;
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>BPMN Editor</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="../../assets/css/jquery-ui.min.css" />
|
||||
<script src="../../assets/js/jquery.min.js"></script>
|
||||
<script src="../../assets/js/jquery-ui.min.js"></script>
|
||||
<link href="BPMN.css" rel="stylesheet" type="text/css" />
|
||||
<script src="../../samples/assets/require.js"></script>
|
||||
<script>
|
||||
function init() {
|
||||
require(["BPMNScript"], function (app) {
|
||||
app.init();
|
||||
document.getElementById("newDocument").onclick = app.newDocument;
|
||||
document.getElementById("openDocuments").onclick = app.openDocument;
|
||||
document.getElementById("saveDocument").onclick = app.saveDocument;
|
||||
document.getElementById("saveDocumentAs").onclick = app.saveDocumentAs;
|
||||
document.getElementById("removeDocuments").onclick = app.removeDocument;
|
||||
|
||||
document.getElementById("undo").onclick = app.undo;
|
||||
document.getElementById("redo").onclick = app.redo;
|
||||
document.getElementById("cutSelection").onclick = app.cutSelection;
|
||||
document.getElementById("copySelection").onclick = app.copySelection;
|
||||
document.getElementById("pasteSelection").onclick = app.pasteSelection;
|
||||
document.getElementById("deleteSelection").onclick = app.deleteSelection;
|
||||
document.getElementById("selectAll").onclick = app.selectAll;
|
||||
|
||||
document.getElementById("alignLeft").onclick = app.alignLeft;
|
||||
document.getElementById("alignRight").onclick = app.alignRight;
|
||||
document.getElementById("alignTop").onclick = app.alignTop;
|
||||
document.getElementById("alignBottom").onclick = app.alignBottom;
|
||||
document.getElementById("alignCenterX").onclick = app.alignCenterX;
|
||||
document.getElementById("alignCenterY").onclick = app.alignCenterY;
|
||||
|
||||
document.getElementById("alignRows").onclick = app.alignRows;
|
||||
document.getElementById("alignColumns").onclick = app.alignColumns;
|
||||
|
||||
document.getElementById("grid").onclick = app.updateGridOption;
|
||||
document.getElementById("snap").onclick = app.updateSnapOption;
|
||||
|
||||
document.getElementById("basicOrderProcess").onclick = app.basicOrderProcess;
|
||||
document.getElementById("5.1").onclick = app.BPMNdata51;
|
||||
document.getElementById("5.2").onclick = app.BPMNdata52;
|
||||
document.getElementById("5.3").onclick = app.BPMNdata53;
|
||||
|
||||
document.getElementById("openBtn").onclick = app.loadFile;
|
||||
document.getElementById("cancelBtn").onclick = app.cancel1;
|
||||
|
||||
document.getElementById("removeBtn").onclick = app.removeFile;
|
||||
document.getElementById("cancelBtn2").onclick = app.cancel2;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div>
|
||||
<div id="currentFile">(Unsaved File)</div>
|
||||
<ul id="menuui">
|
||||
<li><a href="#">File</a>
|
||||
<ul>
|
||||
<li><a href="#" id="newDocument">New</a></li>
|
||||
<li><a href="#" id="openDocuments">Open...</a></li>
|
||||
<li><a href="#" id="saveDocument">Save</a></li>
|
||||
<li><a href="#" id="saveDocumentAs">Save As...</a></li>
|
||||
<li><a href="#" id="removeDocuments">Delete...</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#">Edit</a>
|
||||
<ul>
|
||||
<li><a href="#" id="undo">Undo</a></li>
|
||||
<li><a href="#" id="redo">Redo</a></li>
|
||||
<li><a href="#" id="cutSelection">Cut</a></li>
|
||||
<li><a href="#" id="copySelection">Copy</a></li>
|
||||
<li><a href="#" id="pasteSelection">Paste</a></li>
|
||||
<li><a href="#" id="deleteSelection">Delete</a></li>
|
||||
<li><a href="#" id="selectAll">Select All</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#">Align</a>
|
||||
<ul>
|
||||
<li><a href="#" id="alignLeft">Left Sides</a></li>
|
||||
<li><a href="#" id="alignRight">Right Sides</a></li>
|
||||
<li><a href="#" id="alignTop">Tops</a></li>
|
||||
<li><a href="#" id="alignBottom">Bottoms</a></li>
|
||||
<li><a href="#" id="alignCenterX">Center X</a></li>
|
||||
<li><a href="#" id="alignCenterY">Center Y</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#">Space</a>
|
||||
<ul>
|
||||
<li><a href="#" id="alignRows">In Row...</a></li>
|
||||
<li><a href="#" id="alignColumns">In Column...</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#">Options</a>
|
||||
<ul>
|
||||
<li><a href="#">
|
||||
<input id="grid" type="checkbox" name="options" value="grid">Grid</a></li>
|
||||
<li><a href="#">
|
||||
<input id="snap" type="checkbox" name="options" value="0">Snapping</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!--END menu bar -->
|
||||
|
||||
<!-- Styling for this portion is in BPMN.css -->
|
||||
<div id="PaletteAndDiagram">
|
||||
<div id="sideBar">
|
||||
<span style="display: inline-block; vertical-align: top; padding: 5px; width:100%">
|
||||
<div id="accordion">
|
||||
<h4>Level 1 items</h4>
|
||||
<div>
|
||||
<div id="myPaletteLevel1" class="myPaletteDiv" ></div>
|
||||
</div>
|
||||
<h4>Level 2 items</h4>
|
||||
<div>
|
||||
<div id="myPaletteLevel2" class="myPaletteDiv"></div>
|
||||
</div>
|
||||
<h4>Other items</h4>
|
||||
<div>
|
||||
<div id="myPaletteLevel3" class="myPaletteDiv"></div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
<div class="handle">Overview:</div>
|
||||
<div id="myOverviewDiv"></div>
|
||||
</div>
|
||||
<div id="myDiagramDiv"></div>
|
||||
<div id="description">
|
||||
<p>
|
||||
This sample describes templates and relationships typically used in <a href="https://en.wikipedia.org/wiki/Business_Process_Model_and_Notation">Business Process Model and Notation (BPMN)</a>,
|
||||
to be used as a starting point for developing BPMN or flow-like web applications.
|
||||
</p>
|
||||
<p>In addition to this HTML file, this sample uses the files:</p>
|
||||
<ul>
|
||||
<li><a href="BPMN.css">BPMN.css</a> for styling
|
||||
<li><a href="BPMNScript.ts">BPMN.ts</a> for the Diagram logic
|
||||
<li><a href="BPMNClasses.ts">BPMNClasses.ts</a> - Custom PoolLink and BPMNLinkingTool classes
|
||||
<li><a href="../../extensionsTS/DrawCommandHandler.ts">DrawCommandHandler.ts</a> - Custom CommandHandler class
|
||||
</ul>
|
||||
<span>
|
||||
<div>
|
||||
<p>Additional sample data files:</p>
|
||||
<figure>
|
||||
<img src="BPMNdata/BasicOrderProcess.png" id='basicOrderProcess' title="Basic Order Process"></img>
|
||||
<figcaption>Basic Order Process</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="BPMNdata/OMG BPMN by Example Figure 5.1.png" id='5.1' title="OMG BPMN by Example Figure 5.1"></img>
|
||||
<figcaption>OMG BPMN by Example Figure 5.1</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="BPMNdata/OMG BPMN by Example Figure 5.2.png" id='5.2' title="OMG BPMN by Example Figure 5.2"></img>
|
||||
<figcaption>OMG BPMN by Example Figure 5.2</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="BPMNdata/OMG BPMN by Example Figure 5.3.png" id='5.3' title="OMG BPMN by Example Figure 5.3"></img>
|
||||
<figcaption>OMG BPMN by Example Figure 5.3</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="openDocument" class="draggable">
|
||||
<div id="openDraggableHandle" class="handle">Open File</div>
|
||||
<div id="openText" class="elementText">Choose file to open...</div>
|
||||
<select id="mySavedFiles" class="mySavedFiles"></select>
|
||||
<br />
|
||||
<button id="openBtn" class="elementBtn" type="button"style="margin-left: 70px">Open</button>
|
||||
<button id="cancelBtn" class="elementBtn" type="button">Cancel</button>
|
||||
</div>
|
||||
|
||||
<div id="removeDocument" class="draggable">
|
||||
<div id="removeDraggableHandle" class="handle">Delete File</div>
|
||||
<div id="removeText" class="elementText">Choose file to remove...</div>
|
||||
<select id="mySavedFiles2" class="mySavedFiles"></select>
|
||||
<br />
|
||||
<button id="removeBtn" class="elementBtn" type="button" style="margin-left: 70px">Remove</button>
|
||||
<button id="cancelBtn2" class="elementBtn" type="button">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.BPMNRelinkingTool = exports.BPMNLinkingTool = exports.PoolLink = void 0;
|
||||
var go = require("../../release/go.js");
|
||||
// Contains PoolLink and BPMNLinkingTool classes for the BPMN sample
|
||||
/**
|
||||
* PoolLink, a special Link class for message flows from edges of pools
|
||||
*
|
||||
* Used in the <a href="../../projects/bpmn/BPMN.html">BPMN extension</a>.
|
||||
*/
|
||||
var PoolLink = /** @class */ (function (_super) {
|
||||
__extends(PoolLink, _super);
|
||||
function PoolLink() {
|
||||
return _super !== null && _super.apply(this, arguments) || this;
|
||||
}
|
||||
/**
|
||||
* @hidden @internal
|
||||
*/
|
||||
PoolLink.prototype.getLinkPoint = function (node, port, spot, from, ortho, othernode, otherport) {
|
||||
var r = new go.Rect(port.getDocumentPoint(go.Spot.TopLeft), port.getDocumentPoint(go.Spot.BottomRight));
|
||||
var op = _super.prototype.getLinkPoint.call(this, othernode, otherport, spot, from, ortho, node, port);
|
||||
var below = op.y > r.centerY;
|
||||
var y = below ? r.bottom : r.top;
|
||||
if (node.category === 'privateProcess') {
|
||||
if (op.x < r.left)
|
||||
return new go.Point(r.left, y);
|
||||
if (op.x > r.right)
|
||||
return new go.Point(r.right, y);
|
||||
return new go.Point(op.x, y);
|
||||
}
|
||||
else { // otherwise get the standard link point by calling the base class method
|
||||
return _super.prototype.getLinkPoint.call(this, node, port, spot, from, ortho, othernode, otherport);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @hidden @internal
|
||||
* If there are two links from & to same node... and pool is offset in X from node... the link toPoints collide on pool
|
||||
*/
|
||||
PoolLink.prototype.computeOtherPoint = function (othernode, otherport) {
|
||||
var op = _super.prototype.computeOtherPoint.call(this, othernode, otherport);
|
||||
var node = this.toNode;
|
||||
if (node === othernode)
|
||||
node = this.fromNode;
|
||||
if (node !== null) {
|
||||
if (othernode.category === 'privateProcess') {
|
||||
op.x = node.getDocumentPoint(go.Spot.MiddleBottom).x;
|
||||
}
|
||||
else {
|
||||
if ((node === this.fromNode) !== (node.actualBounds.centerY < othernode.actualBounds.centerY)) {
|
||||
op.x -= 1;
|
||||
}
|
||||
else {
|
||||
op.x += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return op;
|
||||
};
|
||||
/**
|
||||
* @hidden @internal
|
||||
*/
|
||||
PoolLink.prototype.getLinkDirection = function (node, port, linkpoint, spot, from, ortho, othernode, otherport) {
|
||||
if (node.category === 'privateProcess') {
|
||||
var p = port.getDocumentPoint(go.Spot.Center);
|
||||
var op = otherport.getDocumentPoint(go.Spot.Center);
|
||||
var below = op.y > p.y;
|
||||
return below ? 90 : 270;
|
||||
}
|
||||
else {
|
||||
return _super.prototype.getLinkDirection.call(this, node, port, linkpoint, spot, from, ortho, othernode, otherport);
|
||||
}
|
||||
};
|
||||
return PoolLink;
|
||||
}(go.Link));
|
||||
exports.PoolLink = PoolLink;
|
||||
/**
|
||||
* BPMNLinkingTool, a custom linking tool to switch the class of the link created.
|
||||
*
|
||||
* Used in the <a href="../../projects/bpmn/BPMN.html">BPMN extension</a>.
|
||||
* @category Extension
|
||||
*/
|
||||
var BPMNLinkingTool = /** @class */ (function (_super) {
|
||||
__extends(BPMNLinkingTool, _super);
|
||||
function BPMNLinkingTool() {
|
||||
var _this = _super.call(this) || this;
|
||||
// don't allow user to create link starting on the To node
|
||||
_this.direction = go.LinkingTool.ForwardsOnly;
|
||||
// orthogonal routing during linking
|
||||
_this.temporaryLink.routing = go.Link.Orthogonal;
|
||||
// link validation using the validate methods defined below
|
||||
_this.linkValidation = function (fromnode, fromport, tonode, toport) {
|
||||
return BPMNLinkingTool.validateSequenceLinkConnection(fromnode, fromport, tonode, toport) ||
|
||||
BPMNLinkingTool.validateMessageLinkConnection(fromnode, fromport, tonode, toport);
|
||||
};
|
||||
return _this;
|
||||
}
|
||||
/**
|
||||
* Override {@link LinkingTool#insertLink} to do some extra BPMN-specific processing.
|
||||
*/
|
||||
BPMNLinkingTool.prototype.insertLink = function (fromnode, fromport, tonode, toport) {
|
||||
var lsave = null;
|
||||
// maybe temporarily change the link data that is copied to create the new link
|
||||
if (BPMNLinkingTool.validateMessageLinkConnection(fromnode, fromport, tonode, toport)) {
|
||||
lsave = this.archetypeLinkData;
|
||||
this.archetypeLinkData = { category: 'msg' };
|
||||
}
|
||||
// create the link in the standard manner by calling the base method
|
||||
var newlink = _super.prototype.insertLink.call(this, fromnode, fromport, tonode, toport);
|
||||
// maybe make the label visible
|
||||
if (newlink !== null && fromnode.category === 'gateway') {
|
||||
var label = newlink.findObject('Label');
|
||||
if (label !== null)
|
||||
label.visible = true;
|
||||
}
|
||||
// maybe restore the original archetype link data
|
||||
if (lsave !== null)
|
||||
this.archetypeLinkData = lsave;
|
||||
return newlink;
|
||||
};
|
||||
// static utility validation routines for linking & relinking as well as insert link logic
|
||||
/**
|
||||
* Validate that sequence links don't cross subprocess or pool boundaries.
|
||||
*/
|
||||
BPMNLinkingTool.validateSequenceLinkConnection = function (fromnode, fromport, tonode, toport) {
|
||||
if (fromnode.category === null || tonode.category === null)
|
||||
return true;
|
||||
// if either node is in a subprocess, both nodes must be in same subprocess (even for Message Flows)
|
||||
if ((fromnode.containingGroup !== null && fromnode.containingGroup.category === 'subprocess') ||
|
||||
(tonode.containingGroup !== null && tonode.containingGroup.category === 'subprocess')) {
|
||||
if (fromnode.containingGroup !== tonode.containingGroup)
|
||||
return false;
|
||||
}
|
||||
if (fromnode.containingGroup === tonode.containingGroup)
|
||||
return true; // a valid Sequence Flow
|
||||
// also check for children in common pool
|
||||
var common = fromnode.findCommonContainingGroup(tonode);
|
||||
return common != null;
|
||||
};
|
||||
/**
|
||||
* Validate that message links cross pool boundaries.
|
||||
*/
|
||||
BPMNLinkingTool.validateMessageLinkConnection = function (fromnode, fromport, tonode, toport) {
|
||||
if (fromnode.category === null || tonode.category === null)
|
||||
return true;
|
||||
if (fromnode.category === 'privateProcess' || tonode.category === 'privateProcess')
|
||||
return true;
|
||||
// if either node is in a subprocess, both nodes must be in same subprocess (even for Message Flows)
|
||||
if ((fromnode.containingGroup !== null && fromnode.containingGroup.category === 'subprocess') ||
|
||||
(tonode.containingGroup !== null && tonode.containingGroup.category === 'subprocess')) {
|
||||
if (fromnode.containingGroup !== tonode.containingGroup)
|
||||
return false;
|
||||
}
|
||||
if (fromnode.containingGroup === tonode.containingGroup)
|
||||
return false; // an invalid Message Flow
|
||||
// also check if fromnode and tonode are in same pool
|
||||
var common = fromnode.findCommonContainingGroup(tonode);
|
||||
return common === null;
|
||||
};
|
||||
return BPMNLinkingTool;
|
||||
}(go.LinkingTool));
|
||||
exports.BPMNLinkingTool = BPMNLinkingTool;
|
||||
/**
|
||||
* BPMNRelinkingTool, a custom relinking tool to switch the class of the link reconnected.
|
||||
*
|
||||
* Used in the <a href="../../projects/bpmn/BPMN.html">BPMN extension</a>.
|
||||
*/
|
||||
var BPMNRelinkingTool = /** @class */ (function (_super) {
|
||||
__extends(BPMNRelinkingTool, _super);
|
||||
function BPMNRelinkingTool() {
|
||||
var _this = _super.call(this) || this;
|
||||
// orthogonal routing during linking
|
||||
_this.temporaryLink.routing = go.Link.Orthogonal;
|
||||
// link validation using the validate methods defined below
|
||||
_this.linkValidation = function (fromnode, fromport, tonode, toport) {
|
||||
return BPMNLinkingTool.validateSequenceLinkConnection(fromnode, fromport, tonode, toport) ||
|
||||
BPMNLinkingTool.validateMessageLinkConnection(fromnode, fromport, tonode, toport);
|
||||
};
|
||||
return _this;
|
||||
}
|
||||
/**
|
||||
* Override {@link RelinkingTool#reconnectLink} to do some extra BPMN-specific processing.
|
||||
*/
|
||||
BPMNRelinkingTool.prototype.reconnectLink = function (existinglink, newnode, newport, toend) {
|
||||
var diagram = existinglink.diagram;
|
||||
if (diagram === null)
|
||||
return false;
|
||||
var model = diagram.model;
|
||||
if (model === null)
|
||||
return false;
|
||||
function recreateLinkData(data, cat) {
|
||||
// Copy existing data, then set from, to, and category
|
||||
var copy = model.copyLinkData(data);
|
||||
copy.from = data.from;
|
||||
copy.to = data.to;
|
||||
copy.category = cat;
|
||||
copy.points = undefined; // don't keep points from existing link
|
||||
model.removeLinkData(data);
|
||||
model.addLinkData(copy);
|
||||
}
|
||||
if (_super.prototype.reconnectLink.call(this, existinglink, newnode, newport, toend)) {
|
||||
var data = existinglink.data;
|
||||
var fromnode = existinglink.fromNode;
|
||||
var fromport = existinglink.fromPort;
|
||||
var tonode = existinglink.toNode;
|
||||
var toport = existinglink.toPort;
|
||||
if (fromnode !== null && fromport !== null && tonode !== null && toport !== null) {
|
||||
diagram.startTransaction('Relink updates');
|
||||
if (BPMNLinkingTool.validateMessageLinkConnection(fromnode, fromport, tonode, toport)) {
|
||||
// Recreate the link if the category changed, since it is a different class
|
||||
if (existinglink.category !== 'msg') {
|
||||
recreateLinkData(data, 'msg');
|
||||
}
|
||||
}
|
||||
// maybe make the label visible
|
||||
if (fromnode.category === 'gateway') {
|
||||
var label = existinglink.findObject('Label');
|
||||
if (label !== null)
|
||||
label.visible = true;
|
||||
}
|
||||
diagram.commitTransaction('Relink updates');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return BPMNRelinkingTool;
|
||||
}(go.RelinkingTool));
|
||||
exports.BPMNRelinkingTool = BPMNRelinkingTool;
|
||||
});
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
import * as go from '../../release/go.js';
|
||||
|
||||
// Contains PoolLink and BPMNLinkingTool classes for the BPMN sample
|
||||
|
||||
/**
|
||||
* PoolLink, a special Link class for message flows from edges of pools
|
||||
*
|
||||
* Used in the <a href="../../projects/bpmn/BPMN.html">BPMN extension</a>.
|
||||
*/
|
||||
export class PoolLink extends go.Link {
|
||||
/**
|
||||
* @hidden @internal
|
||||
*/
|
||||
public getLinkPoint(node: go.Node, port: go.GraphObject, spot: go.Spot, from: boolean, ortho: boolean, othernode: go.Node, otherport: go.GraphObject): go.Point {
|
||||
const r = new go.Rect(port.getDocumentPoint(go.Spot.TopLeft), port.getDocumentPoint(go.Spot.BottomRight));
|
||||
const op = super.getLinkPoint(othernode, otherport, spot, from, ortho, node, port);
|
||||
|
||||
const below = op.y > r.centerY;
|
||||
const y = below ? r.bottom : r.top;
|
||||
if (node.category === 'privateProcess') {
|
||||
if (op.x < r.left) return new go.Point(r.left, y);
|
||||
if (op.x > r.right) return new go.Point(r.right, y);
|
||||
return new go.Point(op.x, y);
|
||||
} else { // otherwise get the standard link point by calling the base class method
|
||||
return super.getLinkPoint(node, port, spot, from, ortho, othernode, otherport);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden @internal
|
||||
* If there are two links from & to same node... and pool is offset in X from node... the link toPoints collide on pool
|
||||
*/
|
||||
public computeOtherPoint(othernode: go.Node, otherport: go.GraphObject): go.Point {
|
||||
const op = super.computeOtherPoint(othernode, otherport);
|
||||
let node = this.toNode;
|
||||
if (node === othernode) node = this.fromNode;
|
||||
if (node !== null) {
|
||||
if (othernode.category === 'privateProcess') {
|
||||
op.x = node.getDocumentPoint(go.Spot.MiddleBottom).x;
|
||||
} else {
|
||||
if ((node === this.fromNode) !== (node.actualBounds.centerY < othernode.actualBounds.centerY)) {
|
||||
op.x -= 1;
|
||||
} else {
|
||||
op.x += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return op;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden @internal
|
||||
*/
|
||||
public getLinkDirection(node: go.Node, port: go.GraphObject, linkpoint: go.Point, spot: go.Spot,
|
||||
from: boolean, ortho: boolean, othernode: go.Node, otherport: go.GraphObject): number {
|
||||
if (node.category === 'privateProcess') {
|
||||
const p = port.getDocumentPoint(go.Spot.Center);
|
||||
const op = otherport.getDocumentPoint(go.Spot.Center);
|
||||
const below = op.y > p.y;
|
||||
return below ? 90 : 270;
|
||||
} else {
|
||||
return super.getLinkDirection.call(this, node, port, linkpoint, spot, from, ortho, othernode, otherport);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* BPMNLinkingTool, a custom linking tool to switch the class of the link created.
|
||||
*
|
||||
* Used in the <a href="../../projects/bpmn/BPMN.html">BPMN extension</a>.
|
||||
* @category Extension
|
||||
*/
|
||||
export class BPMNLinkingTool extends go.LinkingTool {
|
||||
constructor() {
|
||||
super();
|
||||
// don't allow user to create link starting on the To node
|
||||
this.direction = go.LinkingTool.ForwardsOnly;
|
||||
// orthogonal routing during linking
|
||||
this.temporaryLink.routing = go.Link.Orthogonal;
|
||||
// link validation using the validate methods defined below
|
||||
this.linkValidation = (fromnode: go.Node, fromport: go.GraphObject, tonode: go.Node, toport: go.GraphObject) => {
|
||||
return BPMNLinkingTool.validateSequenceLinkConnection(fromnode, fromport, tonode, toport) ||
|
||||
BPMNLinkingTool.validateMessageLinkConnection(fromnode, fromport, tonode, toport);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Override {@link LinkingTool#insertLink} to do some extra BPMN-specific processing.
|
||||
*/
|
||||
public insertLink(fromnode: go.Node, fromport: go.GraphObject, tonode: go.Node, toport: go.GraphObject): go.Link | null {
|
||||
let lsave = null;
|
||||
// maybe temporarily change the link data that is copied to create the new link
|
||||
if (BPMNLinkingTool.validateMessageLinkConnection(fromnode, fromport, tonode, toport)) {
|
||||
lsave = this.archetypeLinkData;
|
||||
this.archetypeLinkData = { category: 'msg' };
|
||||
}
|
||||
|
||||
// create the link in the standard manner by calling the base method
|
||||
const newlink = super.insertLink(fromnode, fromport, tonode, toport);
|
||||
|
||||
// maybe make the label visible
|
||||
if (newlink !== null && fromnode.category === 'gateway') {
|
||||
const label = newlink.findObject('Label');
|
||||
if (label !== null) label.visible = true;
|
||||
}
|
||||
|
||||
// maybe restore the original archetype link data
|
||||
if (lsave !== null) this.archetypeLinkData = lsave;
|
||||
return newlink;
|
||||
}
|
||||
|
||||
// static utility validation routines for linking & relinking as well as insert link logic
|
||||
|
||||
/**
|
||||
* Validate that sequence links don't cross subprocess or pool boundaries.
|
||||
*/
|
||||
public static validateSequenceLinkConnection(fromnode: go.Node, fromport: go.GraphObject, tonode: go.Node, toport: go.GraphObject): boolean {
|
||||
if (fromnode.category === null || tonode.category === null) return true;
|
||||
|
||||
// if either node is in a subprocess, both nodes must be in same subprocess (even for Message Flows)
|
||||
if ((fromnode.containingGroup !== null && fromnode.containingGroup.category === 'subprocess') ||
|
||||
(tonode.containingGroup !== null && tonode.containingGroup.category === 'subprocess')) {
|
||||
if (fromnode.containingGroup !== tonode.containingGroup) return false;
|
||||
}
|
||||
|
||||
if (fromnode.containingGroup === tonode.containingGroup) return true; // a valid Sequence Flow
|
||||
// also check for children in common pool
|
||||
const common = fromnode.findCommonContainingGroup(tonode);
|
||||
return common != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that message links cross pool boundaries.
|
||||
*/
|
||||
public static validateMessageLinkConnection(fromnode: go.Node, fromport: go.GraphObject, tonode: go.Node, toport: go.GraphObject): boolean {
|
||||
if (fromnode.category === null || tonode.category === null) return true;
|
||||
|
||||
if (fromnode.category === 'privateProcess' || tonode.category === 'privateProcess') return true;
|
||||
|
||||
// if either node is in a subprocess, both nodes must be in same subprocess (even for Message Flows)
|
||||
if ((fromnode.containingGroup !== null && fromnode.containingGroup.category === 'subprocess') ||
|
||||
(tonode.containingGroup !== null && tonode.containingGroup.category === 'subprocess')) {
|
||||
if (fromnode.containingGroup !== tonode.containingGroup) return false;
|
||||
}
|
||||
|
||||
if (fromnode.containingGroup === tonode.containingGroup) return false; // an invalid Message Flow
|
||||
|
||||
// also check if fromnode and tonode are in same pool
|
||||
const common = fromnode.findCommonContainingGroup(tonode);
|
||||
return common === null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BPMNRelinkingTool, a custom relinking tool to switch the class of the link reconnected.
|
||||
*
|
||||
* Used in the <a href="../../projects/bpmn/BPMN.html">BPMN extension</a>.
|
||||
*/
|
||||
export class BPMNRelinkingTool extends go.RelinkingTool {
|
||||
constructor() {
|
||||
super();
|
||||
// orthogonal routing during linking
|
||||
this.temporaryLink.routing = go.Link.Orthogonal;
|
||||
// link validation using the validate methods defined below
|
||||
this.linkValidation = (fromnode: go.Node, fromport: go.GraphObject, tonode: go.Node, toport: go.GraphObject) => {
|
||||
return BPMNLinkingTool.validateSequenceLinkConnection(fromnode, fromport, tonode, toport) ||
|
||||
BPMNLinkingTool.validateMessageLinkConnection(fromnode, fromport, tonode, toport);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Override {@link RelinkingTool#reconnectLink} to do some extra BPMN-specific processing.
|
||||
*/
|
||||
public reconnectLink(existinglink: go.Link, newnode: go.Node | null, newport: go.GraphObject | null, toend: boolean): boolean {
|
||||
const diagram = existinglink.diagram;
|
||||
if (diagram === null) return false;
|
||||
const model = diagram.model as go.GraphLinksModel;
|
||||
if (model === null) return false;
|
||||
|
||||
function recreateLinkData(data: any, cat: string) {
|
||||
// Copy existing data, then set from, to, and category
|
||||
const copy = model.copyLinkData(data) as any;
|
||||
copy.from = data.from;
|
||||
copy.to = data.to;
|
||||
copy.category = cat;
|
||||
copy.points = undefined; // don't keep points from existing link
|
||||
model.removeLinkData(data);
|
||||
model.addLinkData(copy);
|
||||
}
|
||||
|
||||
if (super.reconnectLink(existinglink, newnode, newport, toend)) {
|
||||
const data = existinglink.data;
|
||||
const fromnode = existinglink.fromNode;
|
||||
const fromport = existinglink.fromPort;
|
||||
const tonode = existinglink.toNode;
|
||||
const toport = existinglink.toPort;
|
||||
if (fromnode !== null && fromport !== null && tonode !== null && toport !== null) {
|
||||
diagram.startTransaction('Relink updates');
|
||||
if (BPMNLinkingTool.validateMessageLinkConnection(fromnode, fromport, tonode, toport)) {
|
||||
// Recreate the link if the category changed, since it is a different class
|
||||
if (existinglink.category !== 'msg') {
|
||||
recreateLinkData(data, 'msg');
|
||||
}
|
||||
}
|
||||
|
||||
// maybe make the label visible
|
||||
if (fromnode.category === 'gateway') {
|
||||
const label = existinglink.findObject('Label');
|
||||
if (label !== null) label.visible = true;
|
||||
}
|
||||
diagram.commitTransaction('Relink updates');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{ "class": "go.GraphLinksModel",
|
||||
"linkFromPortIdProperty": "fromPort",
|
||||
"linkToPortIdProperty": "toPort",
|
||||
"modelData": {"position":"230 200"},
|
||||
"nodeDataArray": [
|
||||
{"category":"event", "item":"start", "key":101, "loc":"450 540", "text":"Start", "eventType":1, "eventDimension":1},
|
||||
{"category":"event", "item":"End", "key":104, "loc":"1130 540", "text":"End", "eventType":1, "eventDimension":8},
|
||||
{"category":"activity", "item":"generic task", "key":5, "loc":"1010 540", "text":"Send Invoice", "taskType":0, "boundaryEventArray":[ ]},
|
||||
{"category":"activity", "item":"generic task", "key":-4, "loc":"860 540", "text":"Fulfill Order", "taskType":0, "boundaryEventArray":[ ]},
|
||||
{"category":"activity", "item":"generic task", "key":-5, "loc":"710 540", "text":"Credit Check", "taskType":0, "boundaryEventArray":[ ]},
|
||||
{"category":"activity", "item":"generic task", "key":-6, "loc":"560 540", "text":"Receive Order", "taskType":0, "boundaryEventArray":[ ]},
|
||||
{"category":"annotation", "key":701, "loc":"494 434", "text":"Basic Order Process"}
|
||||
|
||||
],
|
||||
"linkDataArray": [
|
||||
{"from":101, "to":-6, "fromPort":"", "toPort":""},
|
||||
{"from":-6, "to":-5, "fromPort":"", "toPort":""},
|
||||
{"from":-5, "to":-4, "fromPort":"", "toPort":""},
|
||||
{"from":-4, "to":5, "fromPort":"", "toPort":""},
|
||||
{"from":5, "to":104, "fromPort":"", "toPort":""}
|
||||
]}
|
||||
|
After Width: | Height: | Size: 56 KiB |
@@ -0,0 +1,618 @@
|
||||
{
|
||||
"class":"go.GraphLinksModel",
|
||||
"copiesArrays":true,
|
||||
"copiesArrayObjects":true,
|
||||
"linkFromPortIdProperty":"fromPort",
|
||||
"linkToPortIdProperty":"toPort",
|
||||
"modelData":{
|
||||
"position":"186.6328613281251 -77.5"
|
||||
},
|
||||
"nodeDataArray":[
|
||||
{
|
||||
"category":"event",
|
||||
"item":"start",
|
||||
"key":101,
|
||||
"loc":"270 270",
|
||||
"text":"Goods\nto Ship",
|
||||
"eventType":1,
|
||||
"eventDimension":1,
|
||||
"group":-12
|
||||
},
|
||||
{
|
||||
"category":"event",
|
||||
"item":"End",
|
||||
"key":104,
|
||||
"loc":"1420 470",
|
||||
"text":"End",
|
||||
"eventType":1,
|
||||
"eventDimension":8,
|
||||
"group":-13
|
||||
},
|
||||
{
|
||||
"category":"activity",
|
||||
"item":"generic task",
|
||||
"key":5,
|
||||
"loc":"1290 470",
|
||||
"text":"Add paperwork\nand move\npackage to\npick area",
|
||||
"taskType":0,
|
||||
"boundaryEventArray":[
|
||||
|
||||
],
|
||||
"group":-13
|
||||
},
|
||||
{
|
||||
"category":"activity",
|
||||
"item":"generic task",
|
||||
"key":-4,
|
||||
"loc":"720 140",
|
||||
"text":"Check if extra insurance is necessary",
|
||||
"taskType":0,
|
||||
"boundaryEventArray":[
|
||||
|
||||
],
|
||||
"group":-12
|
||||
},
|
||||
{
|
||||
"category":"activity",
|
||||
"item":"generic task",
|
||||
"key":-5,
|
||||
"loc":"490 270",
|
||||
"text":"Decide if \nnormal post or\nspecial\nshipment",
|
||||
"taskType":0,
|
||||
"boundaryEventArray":[
|
||||
|
||||
],
|
||||
"group":-12
|
||||
},
|
||||
{
|
||||
"category":"activity",
|
||||
"item":"generic task",
|
||||
"key":-6,
|
||||
"loc":"540 470",
|
||||
"text":"Package Goods",
|
||||
"taskType":0,
|
||||
"boundaryEventArray":[
|
||||
|
||||
],
|
||||
"group":-13
|
||||
},
|
||||
{
|
||||
"category":"annotation",
|
||||
"key":701,
|
||||
"loc":"556.9999999999995 -37.00000000000042",
|
||||
"text":"OMG BPMN 2.0 by Example. \nFigure 5.1 Shipment Process of a Hardware Retailer",
|
||||
"group":-11
|
||||
},
|
||||
{
|
||||
"key":"501",
|
||||
"text":"Hardware Retailer",
|
||||
"isGroup":"true",
|
||||
"category":"Pool",
|
||||
"loc":"217.2106933593751 -72"
|
||||
},
|
||||
{
|
||||
"category":"Lane",
|
||||
"text":"Logistics\nManager",
|
||||
"color":"white",
|
||||
"isGroup":true,
|
||||
"loc":"217.2106933593751 -72",
|
||||
"group":"501",
|
||||
"key":-11,
|
||||
"size":"1299.992431640625 129"
|
||||
},
|
||||
{
|
||||
"category":"Lane",
|
||||
"text":"Clerk",
|
||||
"color":"white",
|
||||
"isGroup":true,
|
||||
"loc":"217.2106933593751 58",
|
||||
"group":"501",
|
||||
"key":-12,
|
||||
"size":"1299.992431640625 334"
|
||||
},
|
||||
{
|
||||
"category":"Lane",
|
||||
"text":"Warehouse\nWorker",
|
||||
"color":"white",
|
||||
"isGroup":true,
|
||||
"loc":"217.2106933593751 393",
|
||||
"group":"501",
|
||||
"key":-13,
|
||||
"size":"1299.992431640625 153.57783203124995"
|
||||
},
|
||||
{
|
||||
"key":201,
|
||||
"category":"gateway",
|
||||
"text":"",
|
||||
"gatewayType":1,
|
||||
"group":-12,
|
||||
"loc":"360 270"
|
||||
},
|
||||
{
|
||||
"key":204,
|
||||
"category":"gateway",
|
||||
"text":"Mode of\nDelivery",
|
||||
"gatewayType":4,
|
||||
"group":-12,
|
||||
"loc":"630 270"
|
||||
},
|
||||
{
|
||||
"key":131,
|
||||
"category":"activity",
|
||||
"text":"Request Quotes from carriers",
|
||||
"item":"generic task",
|
||||
"taskType":0,
|
||||
"group":-12,
|
||||
"loc":"830 270"
|
||||
},
|
||||
{
|
||||
"key":301,
|
||||
"category":"gateway",
|
||||
"gatewayType":2,
|
||||
"text":"",
|
||||
"group":-12,
|
||||
"loc":"850 140"
|
||||
},
|
||||
{
|
||||
"key":-16,
|
||||
"category":"activity",
|
||||
"text":"Take out extra\ninsurance",
|
||||
"item":"generic task",
|
||||
"taskType":0,
|
||||
"group":-11,
|
||||
"loc":"1000 -20"
|
||||
},
|
||||
{
|
||||
"key":-17,
|
||||
"category":"activity",
|
||||
"text":"Fill in \nPost Label",
|
||||
"item":"generic task",
|
||||
"taskType":0,
|
||||
"group":-12,
|
||||
"loc":"1010 140"
|
||||
},
|
||||
{
|
||||
"key":-19,
|
||||
"category":"gateway",
|
||||
"gatewayType":2,
|
||||
"text":"",
|
||||
"group":-12,
|
||||
"loc":"1150 140"
|
||||
},
|
||||
{
|
||||
"key":-20,
|
||||
"category":"activity",
|
||||
"text":"Assign a \ncarrier & prepare papperwork",
|
||||
"item":"generic task",
|
||||
"taskType":0,
|
||||
"group":-12,
|
||||
"loc":"1010 270"
|
||||
},
|
||||
{
|
||||
"key":-21,
|
||||
"category":"gateway",
|
||||
"text":"",
|
||||
"gatewayType":4,
|
||||
"group":-12,
|
||||
"loc":"1160 270"
|
||||
},
|
||||
{
|
||||
"key":-22,
|
||||
"category":"annotation",
|
||||
"text":"insurance included",
|
||||
"loc":"956.2031249999999 356.0389160156248",
|
||||
"group":-12
|
||||
},
|
||||
{
|
||||
"key":-23,
|
||||
"category":"gateway",
|
||||
"text":"",
|
||||
"gatewayType":1,
|
||||
"group":-13,
|
||||
"loc":"1160 470"
|
||||
}
|
||||
],
|
||||
"linkDataArray":[
|
||||
{
|
||||
"from":5,
|
||||
"to":104,
|
||||
"fromPort":"",
|
||||
"toPort":"",
|
||||
"points":[
|
||||
1349.9999999999998,
|
||||
470.0000000000001,
|
||||
1359.9999999999998,
|
||||
470.0000000000001,
|
||||
1368.5,
|
||||
470.0000000000001,
|
||||
1368.5,
|
||||
469.99999999999994,
|
||||
1377,
|
||||
469.99999999999994,
|
||||
1397,
|
||||
469.99999999999994
|
||||
]
|
||||
},
|
||||
{
|
||||
"from":101,
|
||||
"to":201,
|
||||
"fromPort":"",
|
||||
"toPort":"",
|
||||
"points":[
|
||||
291.5,
|
||||
270,
|
||||
301.5,
|
||||
270,
|
||||
301.5,
|
||||
270,
|
||||
299.5,
|
||||
270,
|
||||
299.5,
|
||||
270,
|
||||
319.5,
|
||||
270
|
||||
]
|
||||
},
|
||||
{
|
||||
"from":201,
|
||||
"to":-6,
|
||||
"fromPort":"",
|
||||
"toPort":"",
|
||||
"visible":true,
|
||||
"points":[
|
||||
360,
|
||||
310.5,
|
||||
360,
|
||||
320.5,
|
||||
360,
|
||||
332,
|
||||
360,
|
||||
332,
|
||||
360,
|
||||
470,
|
||||
460.0000000000002,
|
||||
470,
|
||||
480.0000000000002,
|
||||
470
|
||||
],
|
||||
"text":""
|
||||
},
|
||||
{
|
||||
"from":201,
|
||||
"to":-5,
|
||||
"fromPort":"",
|
||||
"toPort":"",
|
||||
"visible":true,
|
||||
"points":[
|
||||
400.5,
|
||||
270,
|
||||
410.5,
|
||||
270,
|
||||
410.5,
|
||||
270,
|
||||
409.99999999999983,
|
||||
270,
|
||||
409.99999999999983,
|
||||
270,
|
||||
429.99999999999983,
|
||||
270
|
||||
],
|
||||
"text":""
|
||||
},
|
||||
{
|
||||
"from":-5,
|
||||
"to":204,
|
||||
"fromPort":"",
|
||||
"toPort":"",
|
||||
"points":[
|
||||
549.9999999999998,
|
||||
270,
|
||||
559.9999999999998,
|
||||
270,
|
||||
564.7499999999998,
|
||||
270,
|
||||
564.7499999999998,
|
||||
270,
|
||||
569.4999999999998,
|
||||
270,
|
||||
589.4999999999998,
|
||||
270
|
||||
]
|
||||
},
|
||||
{
|
||||
"from":204,
|
||||
"to":-4,
|
||||
"fromPort":"",
|
||||
"toPort":"",
|
||||
"visible":true,
|
||||
"points":[
|
||||
629.9999999999998,
|
||||
229.5,
|
||||
629.9999999999998,
|
||||
219.5,
|
||||
629.9999999999998,
|
||||
140.00000000000006,
|
||||
635,
|
||||
140.00000000000006,
|
||||
640.0000000000001,
|
||||
140.00000000000006,
|
||||
660.0000000000001,
|
||||
140.00000000000006
|
||||
],
|
||||
"text":"Normal\nPost"
|
||||
},
|
||||
{
|
||||
"from":204,
|
||||
"to":131,
|
||||
"fromPort":"",
|
||||
"toPort":"",
|
||||
"visible":true,
|
||||
"points":[
|
||||
670.4999999999998,
|
||||
270,
|
||||
680.4999999999998,
|
||||
270,
|
||||
715.25,
|
||||
270,
|
||||
715.25,
|
||||
270.00000000000006,
|
||||
750.0000000000001,
|
||||
270.00000000000006,
|
||||
770.0000000000001,
|
||||
270.00000000000006
|
||||
],
|
||||
"text":"Special\nCarrier"
|
||||
},
|
||||
{
|
||||
"from":-4,
|
||||
"to":301,
|
||||
"fromPort":"",
|
||||
"toPort":"",
|
||||
"points":[
|
||||
780.0000000000001,
|
||||
140.00000000000006,
|
||||
790.0000000000001,
|
||||
140.00000000000006,
|
||||
790.0000000000001,
|
||||
140,
|
||||
789.5,
|
||||
140,
|
||||
789.5,
|
||||
139.99999999999997,
|
||||
809.5,
|
||||
139.99999999999997
|
||||
]
|
||||
},
|
||||
{
|
||||
"from":301,
|
||||
"to":-16,
|
||||
"fromPort":"",
|
||||
"toPort":"",
|
||||
"visible":true,
|
||||
"points":[
|
||||
850,
|
||||
99.49999999999997,
|
||||
850,
|
||||
89.49999999999997,
|
||||
850,
|
||||
-20,
|
||||
885,
|
||||
-20,
|
||||
919.9999999999999,
|
||||
-20,
|
||||
939.9999999999999,
|
||||
-20
|
||||
],
|
||||
"text":"extra insurance\nrequired"
|
||||
},
|
||||
{
|
||||
"from":301,
|
||||
"to":-17,
|
||||
"fromPort":"",
|
||||
"toPort":"",
|
||||
"visible":true,
|
||||
"points":[
|
||||
890.5,
|
||||
139.99999999999997,
|
||||
900.5,
|
||||
139.99999999999997,
|
||||
915.2499999999998,
|
||||
139.99999999999997,
|
||||
915.2499999999998,
|
||||
140.00000000000006,
|
||||
929.9999999999995,
|
||||
140.00000000000006,
|
||||
949.9999999999995,
|
||||
140.00000000000006
|
||||
],
|
||||
"text":"Always"
|
||||
},
|
||||
{
|
||||
"from":-17,
|
||||
"to":-19,
|
||||
"fromPort":"",
|
||||
"toPort":"",
|
||||
"points":[
|
||||
1069.9999999999995,
|
||||
140.00000000000006,
|
||||
1079.9999999999995,
|
||||
140.00000000000006,
|
||||
1084.7499999999995,
|
||||
140.00000000000006,
|
||||
1084.7499999999995,
|
||||
139.99999999999997,
|
||||
1089.4999999999998,
|
||||
139.99999999999997,
|
||||
1109.4999999999998,
|
||||
139.99999999999997
|
||||
]
|
||||
},
|
||||
{
|
||||
"from":-16,
|
||||
"to":-19,
|
||||
"fromPort":"",
|
||||
"toPort":"",
|
||||
"points":[
|
||||
1060,
|
||||
-20,
|
||||
1070,
|
||||
-20,
|
||||
1149.9999999999998,
|
||||
-20,
|
||||
1149.9999999999998,
|
||||
29.749999999999986,
|
||||
1149.9999999999998,
|
||||
79.49999999999997,
|
||||
1149.9999999999998,
|
||||
99.49999999999997
|
||||
]
|
||||
},
|
||||
{
|
||||
"from":131,
|
||||
"to":-20,
|
||||
"fromPort":"",
|
||||
"toPort":"",
|
||||
"points":[
|
||||
890.0000000000001,
|
||||
270.00000000000006,
|
||||
900.0000000000001,
|
||||
270.00000000000006,
|
||||
914.9999999999999,
|
||||
270.00000000000006,
|
||||
914.9999999999999,
|
||||
270,
|
||||
929.9999999999997,
|
||||
270,
|
||||
949.9999999999997,
|
||||
270
|
||||
]
|
||||
},
|
||||
{
|
||||
"from":-20,
|
||||
"to":-21,
|
||||
"fromPort":"",
|
||||
"toPort":"",
|
||||
"points":[
|
||||
1069.9999999999995,
|
||||
270,
|
||||
1079.9999999999995,
|
||||
270,
|
||||
1089.7499999999998,
|
||||
270,
|
||||
1089.7499999999998,
|
||||
270,
|
||||
1099.5,
|
||||
270,
|
||||
1119.5,
|
||||
270
|
||||
]
|
||||
},
|
||||
{
|
||||
"from":-19,
|
||||
"to":-21,
|
||||
"fromPort":"",
|
||||
"toPort":"",
|
||||
"visible":true,
|
||||
"points":[
|
||||
1149.9999999999998,
|
||||
180.49999999999997,
|
||||
1149.9999999999998,
|
||||
190.49999999999997,
|
||||
1149.9999999999998,
|
||||
200,
|
||||
1160,
|
||||
200,
|
||||
1160,
|
||||
209.5,
|
||||
1160,
|
||||
229.5
|
||||
],
|
||||
"text":""
|
||||
},
|
||||
{
|
||||
"from":-22,
|
||||
"to":131,
|
||||
"fromPort":"",
|
||||
"toPort":"",
|
||||
"category":"annotation",
|
||||
"points":[
|
||||
896.0004882812499,
|
||||
356.0389160156248,
|
||||
856.0004882812499,
|
||||
356.0389160156248,
|
||||
830.0000000000001,
|
||||
330.00000000000006,
|
||||
830.0000000000001,
|
||||
310.00000000000006
|
||||
]
|
||||
},
|
||||
{
|
||||
"from":-23,
|
||||
"to":5,
|
||||
"fromPort":"",
|
||||
"toPort":"",
|
||||
"visible":true,
|
||||
"points":[
|
||||
1200.4999999999998,
|
||||
469.99999999999994,
|
||||
1210.4999999999998,
|
||||
469.99999999999994,
|
||||
1210.4999999999998,
|
||||
470,
|
||||
1209.9999999999998,
|
||||
470,
|
||||
1209.9999999999998,
|
||||
470.0000000000001,
|
||||
1229.9999999999998,
|
||||
470.0000000000001
|
||||
],
|
||||
"text":""
|
||||
},
|
||||
{
|
||||
"from":-6,
|
||||
"to":-23,
|
||||
"fromPort":"",
|
||||
"toPort":"",
|
||||
"points":[
|
||||
600.0000000000002,
|
||||
470,
|
||||
610.0000000000002,
|
||||
470,
|
||||
854.75,
|
||||
470,
|
||||
854.75,
|
||||
469.99999999999994,
|
||||
1099.4999999999998,
|
||||
469.99999999999994,
|
||||
1119.4999999999998,
|
||||
469.99999999999994
|
||||
]
|
||||
},
|
||||
{
|
||||
"from":-21,
|
||||
"to":-23,
|
||||
"fromPort":"",
|
||||
"toPort":"",
|
||||
"visible":true,
|
||||
"points":[
|
||||
1160,
|
||||
310.5,
|
||||
1160,
|
||||
320.5,
|
||||
1160,
|
||||
332,
|
||||
1160,
|
||||
332,
|
||||
1160,
|
||||
348,
|
||||
1159.9999999999998,
|
||||
348,
|
||||
1159.9999999999998,
|
||||
409.49999999999994,
|
||||
1159.9999999999998,
|
||||
429.49999999999994
|
||||
],
|
||||
"text":""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 80 KiB |
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "umd",
|
||||
"target": "es5",
|
||||
"strict": true
|
||||
}
|
||||
// For ES6 modules you may wish to use a configuration like this instead:
|
||||
// "compilerOptions": {
|
||||
// "target": "es6",
|
||||
// "strict": true
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* FLOOR PLANNER CODE: TEMPLATES - FURNITURE
|
||||
* GraphObject templates for interactional furniture nodes (and their dependecies) used in the Floor Planner sample
|
||||
* Includes Default Node (Furniture), MultiPurpose Node
|
||||
*/
|
||||
|
||||
/*
|
||||
* Furniture Node Dependencies:
|
||||
* Node Tool Tip, Furniture Resize Adornment Template, Furniture Rotate Adornment Template, Invert Color
|
||||
*/
|
||||
|
||||
// Node Tool Tip
|
||||
function makeNodeToolTip() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Adornment, "Auto",
|
||||
$(go.Shape, { fill: "#FFFFCC" }),
|
||||
$(go.TextBlock, { margin: 4 },
|
||||
new go.Binding("text", "", function (text, obj) {
|
||||
var data = obj.part.adornedObject.data;
|
||||
var name = (obj.part.adornedObject.category === "MultiPurposeNode") ? data.text : data.caption;
|
||||
return "Name: " + name + "\nNotes: " + data.notes;
|
||||
}).ofObject())
|
||||
)
|
||||
}
|
||||
|
||||
// Furniture Resize Adornment
|
||||
function makeFurnitureResizeAdornmentTemplate() {
|
||||
var $ = go.GraphObject.make;
|
||||
function makeHandle(alignment, cursor) {
|
||||
return $(go.Shape, { alignment: alignment, cursor: cursor, figure: "Rectangle", desiredSize: new go.Size(7, 7), fill: "#ffffff", stroke: "#808080" },
|
||||
new go.Binding("fill", "color"),
|
||||
new go.Binding("stroke", "stroke"));
|
||||
}
|
||||
|
||||
return $(go.Adornment, "Spot",
|
||||
$(go.Placeholder),
|
||||
makeHandle(go.Spot.Top, "n-resize"),
|
||||
makeHandle(go.Spot.TopRight, "n-resize"),
|
||||
makeHandle(go.Spot.BottomRight, "se-resize"),
|
||||
makeHandle(go.Spot.Right, "e-resize"),
|
||||
makeHandle(go.Spot.Bottom, "s-resize"),
|
||||
makeHandle(go.Spot.BottomLeft, "sw-resize"),
|
||||
makeHandle(go.Spot.Left, "w-resize"),
|
||||
makeHandle(go.Spot.TopLeft, "nw-resize")
|
||||
);
|
||||
}
|
||||
|
||||
// Furniture Rotate Adornment
|
||||
function makeFurnitureRotateAdornmentTemplate() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Adornment,
|
||||
$(go.Shape, "Circle", { cursor: "pointer", desiredSize: new go.Size(7, 7), fill: "#ffffff", stroke: "#808080" },
|
||||
new go.Binding("fill", "", function (obj) { return (obj.adornedPart === null) ? "#ffffff" : obj.adornedPart.data.color; }).ofObject(),
|
||||
new go.Binding("stroke", "", function (obj) { return (obj.adornedPart === null) ? "#000000" : obj.adornedPart.data.stroke; }).ofObject())
|
||||
);
|
||||
}
|
||||
|
||||
// Return inverted color (in hex) of a given hex code color; used to determine furniture node stroke color
|
||||
function invertColor(hexnum) {
|
||||
if (hexnum.includes('#')) hexnum = hexnum.substring(1);
|
||||
if (hexnum.length != 6) {
|
||||
console.error("Hex color must be six hex numbers in length.");
|
||||
return false;
|
||||
}
|
||||
|
||||
hexnum = hexnum.toUpperCase();
|
||||
var splitnum = hexnum.split("");
|
||||
var resultnum = "";
|
||||
var simplenum = "FEDCBA9876".split("");
|
||||
var complexnum = new Array();
|
||||
complexnum.A = "5";
|
||||
complexnum.B = "4";
|
||||
complexnum.C = "3";
|
||||
complexnum.D = "2";
|
||||
complexnum.E = "1";
|
||||
complexnum.F = "0";
|
||||
|
||||
for (i = 0; i < 6; i++) {
|
||||
if (!isNaN(splitnum[i])) {
|
||||
resultnum += simplenum[splitnum[i]];
|
||||
} else if (complexnum[splitnum[i]]) {
|
||||
resultnum += complexnum[splitnum[i]];
|
||||
} else {
|
||||
console.error("Hex colors must only include hex numbers 0-9, and A-F");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return '#' + resultnum;
|
||||
}
|
||||
|
||||
/*
|
||||
* Furniture Node Templates:
|
||||
* Default Node, MultiPurpose Node
|
||||
*/
|
||||
|
||||
// Default Node
|
||||
function makeDefaultNode() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Node, "Spot",
|
||||
{
|
||||
resizable: true,
|
||||
rotatable: true,
|
||||
toolTip: makeNodeToolTip(),
|
||||
resizeAdornmentTemplate: makeFurnitureResizeAdornmentTemplate(),
|
||||
rotateAdornmentTemplate: makeFurnitureRotateAdornmentTemplate(),
|
||||
contextMenu: makeContextMenu(),
|
||||
locationObjectName: "SHAPE",
|
||||
resizeObjectName: "SHAPE",
|
||||
rotateObjectName: "SHAPE",
|
||||
minSize: new go.Size(5, 5),
|
||||
locationSpot: go.Spot.Center,
|
||||
selectionAdorned: false, // use a Binding on the Shape.stroke to show selection
|
||||
doubleClick: function (e) {
|
||||
if (e.diagram.floorplanUI) e.diagram.floorplanUI.hideShow("selectionInfoWindow")
|
||||
}
|
||||
},
|
||||
// remember Node location
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
// move selected Node to Foreground layer so it's not obscuerd by non-selected Parts
|
||||
new go.Binding("layerName", "isSelected", function (s) {
|
||||
return s ? "Foreground" : "";
|
||||
}).ofObject(),
|
||||
$(go.Shape,
|
||||
{
|
||||
name: "SHAPE", stroke: "#000000",
|
||||
fill: "rgba(128, 128, 128, 0.5)"
|
||||
},
|
||||
new go.Binding("figure", "shape"),
|
||||
new go.Binding("geometryString", "geo"),
|
||||
new go.Binding("width").makeTwoWay(),
|
||||
new go.Binding("height").makeTwoWay(),
|
||||
new go.Binding("angle").makeTwoWay(),
|
||||
new go.Binding("fill", "color"),
|
||||
new go.Binding("stroke", "isSelected", function (s, obj) {
|
||||
return s ? go.Brush.lightenBy(obj.stroke, .5) : invertColor(obj.part.data.color);
|
||||
}).ofObject())
|
||||
)
|
||||
}
|
||||
|
||||
// MultiPurpose Node
|
||||
function makeMultiPurposeNode() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Node, "Spot",
|
||||
{
|
||||
contextMenu: makeContextMenu(),
|
||||
toolTip: makeNodeToolTip(),
|
||||
locationSpot: go.Spot.Center,
|
||||
resizeAdornmentTemplate: makeFurnitureResizeAdornmentTemplate(),
|
||||
rotateAdornmentTemplate: makeFurnitureRotateAdornmentTemplate(),
|
||||
locationObjectName: "SHAPE",
|
||||
resizable: true,
|
||||
rotatable: true,
|
||||
resizeObjectName: "SHAPE",
|
||||
rotateObjectName: "SHAPE",
|
||||
minSize: new go.Size(5, 5),
|
||||
selectionAdorned: false,
|
||||
doubleClick: function (e) {
|
||||
if (e.diagram.floorplanUI) e.diagram.floorplanUI.hideShow("selectionInfoWindow")
|
||||
}
|
||||
},
|
||||
// remember location, angle, height, and width of the node
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
// move a selected part into the Foreground layer so it's not obscuerd by non-selected Parts
|
||||
new go.Binding("layerName", "isSelected", function (s) { return s ? "Foreground" : ""; }).ofObject(),
|
||||
$(go.Shape,
|
||||
{ strokeWidth: 1, name: "SHAPE", fill: "rgba(128, 128, 128, 0.5)", },
|
||||
new go.Binding("angle").makeTwoWay(),
|
||||
new go.Binding("width").makeTwoWay(),
|
||||
new go.Binding("height").makeTwoWay(),
|
||||
new go.Binding("fill", "color"),
|
||||
new go.Binding("stroke", "isSelected", function (s, obj) {
|
||||
return s ? go.Brush.lightenBy(obj.stroke, .5) : invertColor(obj.part.data.color);
|
||||
}).ofObject()
|
||||
),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
margin: 5,
|
||||
wrap: go.TextBlock.WrapFit,
|
||||
textAlign: "center",
|
||||
editable: true,
|
||||
isMultiline: false,
|
||||
stroke: '#454545',
|
||||
font: "10pt sans-serif"
|
||||
},
|
||||
new go.Binding("text").makeTwoWay(),
|
||||
new go.Binding("angle", "angle").makeTwoWay(),
|
||||
new go.Binding("font", "height", function (height) {
|
||||
if (height > 25) return "10pt sans-serif";
|
||||
if (height < 25 && height > 15) return "8pt sans-serif";
|
||||
else return "6pt sans-serif";
|
||||
}),
|
||||
new go.Binding("stroke", "color", function (color) { return invertColor(color); })
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* FLOOR PLANNER CODE: TEMPLATES - GENERAL
|
||||
* General GraphObject templates used in the Floor Planner sample
|
||||
* Includes Context Menu, Diagram, Default Group, AngleNode, DimensionLink, PointNode
|
||||
*/
|
||||
|
||||
/*
|
||||
* Dependencies for Context Menu:
|
||||
* Make Selection Group, Ungroup Selection, Clear Empty Groups
|
||||
*/
|
||||
|
||||
// Make the selection a group
|
||||
function makeSelectionGroup(floorplan) {
|
||||
floorplan.startTransaction("group selection");
|
||||
// ungroup all selected nodes; then group them; if one of the selected nodes is a group, ungroup all its nodes
|
||||
var sel = floorplan.selection; var nodes = [];
|
||||
sel.iterator.each(function (n) {
|
||||
if (n instanceof go.Group) n.memberParts.iterator.each(function (part) { nodes.push(part); })
|
||||
else nodes.push(n);
|
||||
});
|
||||
for (var i = 0; i < nodes.length; i++) nodes[i].isSelected = true;
|
||||
ungroupSelection(floorplan);
|
||||
floorplan.commandHandler.groupSelection();
|
||||
var group = floorplan.selection.first(); // after grouping, the new group will be the only thing selected
|
||||
floorplan.model.setDataProperty(group.data, "caption", "Group");
|
||||
floorplan.model.setDataProperty(group.data, "notes", "");
|
||||
clearEmptyGroups(floorplan);
|
||||
// unselect / reselect group so data appears properly in Selection Info Window
|
||||
floorplan.clearSelection();
|
||||
floorplan.select(group);
|
||||
floorplan.commitTransaction("group selection");
|
||||
}
|
||||
|
||||
// Ungroup selected nodes; if the selection is a group, ungroup all it's memberParts
|
||||
function ungroupSelection(floorplan) {
|
||||
floorplan.startTransaction('ungroup selection');
|
||||
// helper function to ungroup nodes
|
||||
function ungroupNode(node) {
|
||||
var group = node.containingGroup;
|
||||
node.containingGroup = null;
|
||||
if (group != null) {
|
||||
if (group.memberParts.count === 0) floorplan.remove(group);
|
||||
else if (group.memberParts.count === 1) group.memberParts.first().containingGroup = null;
|
||||
}
|
||||
}
|
||||
// ungroup any selected nodes; remember groups that are selected
|
||||
var sel = floorplan.selection; var groups = [];
|
||||
sel.iterator.each(function (n) {
|
||||
if (!(n instanceof go.Group)) ungroupNode(n);
|
||||
else groups.push(n);
|
||||
});
|
||||
// go through selected groups, and ungroup their memberparts too
|
||||
var nodes = [];
|
||||
for (var i = 0; i < groups.length; i++) groups[i].memberParts.iterator.each(function (n) { nodes.push(n); });
|
||||
for (var i = 0; i < nodes.length; i++) ungroupNode(nodes[i]);
|
||||
clearEmptyGroups(floorplan);
|
||||
floorplan.commitTransaction('ungroup selection');
|
||||
}
|
||||
|
||||
// Clear all the groups that have no nodes
|
||||
function clearEmptyGroups(floorplan) {
|
||||
var nodes = floorplan.nodes; var arr = [];
|
||||
nodes.iterator.each(function (node) { if (node instanceof go.Group && node.memberParts.count === 0 && node.category !== "WallGroup") { arr.push(node); } });
|
||||
for (i = 0; i < arr.length; i++) { floorplan.remove(arr[i]); }
|
||||
}
|
||||
|
||||
/*
|
||||
* General Group Dependencies:
|
||||
* Group Tool Tip
|
||||
*/
|
||||
|
||||
// Group Tool Tip
|
||||
function makeGroupToolTip() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Adornment, "Auto",
|
||||
$(go.Shape, { fill: "#FFFFCC" }),
|
||||
$(go.TextBlock, { margin: 4 },
|
||||
new go.Binding("text", "", function (text, obj) {
|
||||
var data = obj.part.adornedObject.data;
|
||||
var name = (obj.part.adornedObject.category === "MultiPurposeNode") ? data.text : data.caption;
|
||||
return "Name: " + name + "\nNotes: " + data.notes + '\nMembers: ' + obj.part.adornedObject.memberParts.count;
|
||||
}).ofObject())
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* General Templates:
|
||||
* Context Menu, Default Group
|
||||
*/
|
||||
|
||||
// Context Menu -- referenced by Node, Diagram and Group Templates
|
||||
function makeContextMenu() {
|
||||
var $ = go.GraphObject.make
|
||||
return $(go.Adornment, "Vertical",
|
||||
// Make Selection Group Button
|
||||
$("ContextMenuButton",
|
||||
$(go.TextBlock, "Make Group"),
|
||||
{ click: function (e, obj) { makeSelectionGroup(obj.part.diagram); } },
|
||||
new go.Binding("visible", "visible", function (v, obj) {
|
||||
var floorplan = obj.part.diagram;
|
||||
if (floorplan.selection.count <= 1) return false;
|
||||
var flag = true;
|
||||
floorplan.selection.iterator.each(function (node) {
|
||||
if (node.category === "WallGroup" || node.category === "WindowNode" || node.category === "DoorNode") flag = false;
|
||||
});
|
||||
return flag;
|
||||
}).ofObject()
|
||||
),
|
||||
// Ungroup Selection Button
|
||||
$("ContextMenuButton",
|
||||
$(go.TextBlock, "Ungroup"),
|
||||
{ click: function (e, obj) { ungroupSelection(obj.part.diagram); } },
|
||||
new go.Binding("visible", "", function (v, obj) {
|
||||
var floorplan = obj.part.diagram;
|
||||
if (floorplan !== null) {
|
||||
var node = floorplan.selection.first();
|
||||
return ((node instanceof go.Node && node.containingGroup != null && node.containingGroup.category != 'WallGroup') ||
|
||||
(node instanceof go.Group && node.category === ''));
|
||||
} return false;
|
||||
}).ofObject()
|
||||
),
|
||||
// Copy Button
|
||||
$("ContextMenuButton",
|
||||
$(go.TextBlock, "Copy"),
|
||||
{ click: function (e, obj) { obj.part.diagram.commandHandler.copySelection() } },
|
||||
new go.Binding("visible", "", function (v, obj) {
|
||||
if (obj.part.diagram !== null) {
|
||||
return obj.part.diagram.selection.count > 0;
|
||||
} return false;
|
||||
}).ofObject()
|
||||
),
|
||||
// Cut Button
|
||||
$("ContextMenuButton",
|
||||
$(go.TextBlock, "Cut"),
|
||||
{ click: function (e, obj) { obj.part.diagram.commandHandler.cutSelection() } },
|
||||
new go.Binding("visible", "", function (v, obj) {
|
||||
if (obj.part.diagram !== null) {
|
||||
return obj.part.diagram.selection.count > 0;
|
||||
} return false;
|
||||
}).ofObject()
|
||||
),
|
||||
// Delete Button
|
||||
$("ContextMenuButton",
|
||||
$(go.TextBlock, "Delete"),
|
||||
{ click: function (e, obj) { obj.part.diagram.commandHandler.deleteSelection() } },
|
||||
new go.Binding("visible", "", function (v, obj) {
|
||||
if (obj.part.diagram !== null) {
|
||||
return obj.part.diagram.selection.count > 0;
|
||||
} return false;
|
||||
}).ofObject()
|
||||
),
|
||||
// Paste Button
|
||||
$("ContextMenuButton",
|
||||
$(go.TextBlock, "Paste"),
|
||||
{ click: function (e, obj) { obj.part.diagram.commandHandler.pasteSelection(obj.part.diagram.toolManager.contextMenuTool.mouseDownPoint) } }
|
||||
),
|
||||
// Show Selection Info Button (only available when selection count > 0)
|
||||
$("ContextMenuButton",
|
||||
$(go.TextBlock, "Show Selection Info"),
|
||||
{
|
||||
click: function (e, obj) {
|
||||
if (e.diagram.floorplanUI) {
|
||||
var selectionInfoWindow = document.getElementById(e.diagram.floorplanUI.state.windows.selectionInfoWindow.id);
|
||||
if (selectionInfoWindow.style.visibility !== 'visible') e.diagram.floorplanUI.hideShow('selectionInfoWindow');
|
||||
}
|
||||
}
|
||||
},
|
||||
new go.Binding("visible", "", function (v, obj) {
|
||||
if (obj.part.diagram !== null) {
|
||||
return obj.part.diagram.selection.count > 0;
|
||||
} return false;
|
||||
}).ofObject()
|
||||
),
|
||||
// Flip Dimension Side Button (only available when selection contains Wall Group(s))
|
||||
$("ContextMenuButton",
|
||||
$(go.TextBlock, "Flip Dimension Side"),
|
||||
{
|
||||
click: function (e, obj) {
|
||||
var floorplan = obj.part.diagram;
|
||||
if (floorplan !== null) {
|
||||
floorplan.startTransaction("flip dimension link side");
|
||||
var walls = [];
|
||||
floorplan.selection.iterator.each(function (part) {
|
||||
if (part.category === "WallGroup") walls.push(part);
|
||||
});
|
||||
for (var i = 0; i < walls.length; i++) {
|
||||
var wall = walls[i];
|
||||
var sPt = wall.data.startpoint.copy();
|
||||
var ePt = wall.data.endpoint.copy();
|
||||
floorplan.model.setDataProperty(wall.data, "startpoint", ePt);
|
||||
floorplan.model.setDataProperty(wall.data, "endpoint", sPt);
|
||||
floorplan.updateWall(wall);
|
||||
}
|
||||
floorplan.commitTransaction("flip dimension link side");
|
||||
}
|
||||
}
|
||||
},
|
||||
new go.Binding("visible", "", function (v, obj) {
|
||||
if (obj.part.diagram !== null) {
|
||||
var sel = obj.part.diagram.selection;
|
||||
if (sel.count === 0) return false;
|
||||
var flag = false;
|
||||
sel.iterator.each(function (part) {
|
||||
if (part.category === "WallGroup") flag = true;
|
||||
});
|
||||
return flag;
|
||||
} return false;
|
||||
}).ofObject()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Default Group
|
||||
function makeDefaultGroup() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Group, "Vertical",
|
||||
{
|
||||
contextMenu: makeContextMenu(),
|
||||
doubleClick: function (e) {
|
||||
if (e.diagram.floorplanUI) e.diagram.floorplanUI.hideShow("selectionInfoWindow");
|
||||
},
|
||||
toolTip: makeGroupToolTip()
|
||||
},
|
||||
new go.Binding("location", "loc"),
|
||||
$(go.Panel, "Auto",
|
||||
$(go.Shape, "RoundedRectangle", { fill: "rgba(128,128,128,0.15)", stroke: 'rgba(128, 128, 128, .05)', name: 'SHAPE', strokeCap: 'square' },
|
||||
new go.Binding("fill", "isSelected", function (s, obj) {
|
||||
return s ? "rgba(128, 128, 128, .15)" : "rgba(128, 128, 128, 0.10)";
|
||||
}).ofObject()
|
||||
),
|
||||
$(go.Placeholder, { padding: 5 }) // extra padding around group members
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
* Dependencies for Angle Nodes:
|
||||
* Make Arc
|
||||
*/
|
||||
|
||||
// Return arc geometry for Angle Nodes
|
||||
function makeArc(node) {
|
||||
var ang = node.data.angle;
|
||||
var sweep = node.data.sweep;
|
||||
var rad = Math.min(30, node.data.maxRadius);
|
||||
if (typeof sweep === "number" && sweep > 0) {
|
||||
var start = new go.Point(rad, 0).rotate(ang);
|
||||
// this is much more efficient than calling go.GraphObject.make:
|
||||
return new go.Geometry()
|
||||
.add(new go.PathFigure(start.x + rad, start.y + rad) // start point
|
||||
.add(new go.PathSegment(go.PathSegment.Arc,
|
||||
ang, sweep, // angles
|
||||
rad, rad, // center
|
||||
rad, rad) // radius
|
||||
))
|
||||
.add(new go.PathFigure(0, 0))
|
||||
.add(new go.PathFigure(2 * rad, 2 * rad));
|
||||
} else { // make sure this arc always occupies the same circular area of RAD radius
|
||||
return new go.Geometry()
|
||||
.add(new go.PathFigure(0, 0))
|
||||
.add(new go.PathFigure(2 * rad, 2 * rad));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Dependencies for Dimension Links
|
||||
* Make Point Node
|
||||
*/
|
||||
|
||||
// Return a Point Node (used for Dimension Links)
|
||||
function makePointNode() {
|
||||
var $ = go.GraphObject.make
|
||||
return $(go.Node, "Position", new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify));
|
||||
}
|
||||
|
||||
/*
|
||||
* Dynamically appearing parts:
|
||||
* Angle Node, Dimension Link
|
||||
*/
|
||||
|
||||
// Return an Angle Node (for each angle ndeeded in the diagram, one angle node is made)
|
||||
function makeAngleNode() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Node, "Spot",
|
||||
{ locationSpot: go.Spot.Center, locationObjectName: "SHAPE", selectionAdorned: false },
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
$(go.Shape, "Circle", // placed where walls intersect, is invisible
|
||||
{ name: "SHAPE", height: 0, width: 0 }),
|
||||
$(go.Shape, // arc
|
||||
{ strokeWidth: 1.5, fill: null },
|
||||
new go.Binding("geometry", "", makeArc).ofObject(),
|
||||
new go.Binding("stroke", "sweep", function (sweep) {
|
||||
return (sweep % 45 < 1 || sweep % 45 > 44) ? "dodgerblue" : "lightblue";
|
||||
})),
|
||||
// Arc label panel
|
||||
$(go.Panel, "Auto",
|
||||
{ name: "ARCLABEL" },
|
||||
// position the label in the center of the arc
|
||||
new go.Binding("alignment", "sweep", function (sweep, panel) {
|
||||
var rad = Math.min(30, panel.part.data.maxRadius);
|
||||
var angle = panel.part.data.angle;
|
||||
var cntr = new go.Point(rad, 0).rotate(angle + sweep / 2);
|
||||
return new go.Spot(0.5, 0.5, cntr.x, cntr.y);
|
||||
}),
|
||||
// rectangle containing angle text
|
||||
$(go.Shape,
|
||||
{ fill: "white" },
|
||||
new go.Binding("stroke", "sweep", function (sweep) {
|
||||
return (sweep % 45 < 1 || sweep % 45 > 44) ? "dodgerblue" : "lightblue";
|
||||
})),
|
||||
// angle text
|
||||
$(go.TextBlock,
|
||||
{ font: "7pt sans-serif", margin: 2 },
|
||||
new go.Binding("text", "sweep", function (sweep) {
|
||||
return sweep.toFixed(2) + String.fromCharCode(176);
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Returns a Dimension Link
|
||||
function makeDimensionLink() {
|
||||
var $ = go.GraphObject.make
|
||||
return $(go.Link,
|
||||
// link itself
|
||||
$(go.Shape,
|
||||
{ stroke: "gray", strokeWidth: 2, name: 'SHAPE' }),
|
||||
// to arrow shape
|
||||
$(go.Shape,
|
||||
{ toArrow: "OpenTriangle", stroke: "gray", strokeWidth: 2 }),
|
||||
$(go.Shape,
|
||||
// from arrow shape
|
||||
{ fromArrow: "BackwardOpenTriangle", stroke: "gray", strokeWidth: 2 }),
|
||||
// dimension link text
|
||||
$(go.TextBlock,
|
||||
{ text: 'sometext', segmentOffset: new go.Point(0, -10), font: "13px sans-serif" },
|
||||
new go.Binding("text", "", function (link) {
|
||||
var floorplan = link.diagram;
|
||||
if (floorplan) {
|
||||
var fromPtNode = null; var toPtNode = null;
|
||||
floorplan.pointNodes.iterator.each(function (node) {
|
||||
if (node.data.key === link.data.from) fromPtNode = node;
|
||||
if (node.data.key === link.data.to) toPtNode = node;
|
||||
});
|
||||
if (fromPtNode !== null) {
|
||||
var fromPt = fromPtNode.location;
|
||||
var toPt = toPtNode.location;
|
||||
return floorplan.convertPixelsToUnits(Math.sqrt(fromPt.distanceSquaredPoint(toPt))).toFixed(2) + floorplan.model.modelData.unitsAbbreviation;
|
||||
} return null;
|
||||
} return null;
|
||||
}).ofObject(),
|
||||
// bind angle of textblock to angle of link -- always make text rightside up and readable
|
||||
new go.Binding("angle", "angle", function (angle, link) {
|
||||
if (angle > 90 && angle < 270) return (angle + 180) % 360;
|
||||
return angle;
|
||||
}),
|
||||
// default poisiton text above / below dimension link based on angle
|
||||
new go.Binding("segmentOffset", "angle", function (angle, textblock) {
|
||||
var floorplan = textblock.part.diagram;
|
||||
if (floorplan) {
|
||||
var wall = floorplan.findPartForKey(textblock.part.data.wall);
|
||||
if (wall.rotateObject.angle > 135 && wall.rotateObject.angle < 315) return new go.Point(0, 10);
|
||||
return new go.Point(0, -10);
|
||||
} return new go.Point(0, 0);
|
||||
}).ofObject(),
|
||||
// scale font size according to the length of the link
|
||||
new go.Binding("font", "", function (link) {
|
||||
var floorplan = link.diagram;
|
||||
var fromPtNode = null; var toPtNode = null;
|
||||
floorplan.pointNodes.iterator.each(function (node) {
|
||||
if (node.data.key === link.data.from) fromPtNode = node;
|
||||
if (node.data.key === link.data.to) toPtNode = node;
|
||||
});
|
||||
if (fromPtNode !== null) {
|
||||
var fromPt = fromPtNode.location;
|
||||
var toPt = toPtNode.location;
|
||||
var distance = Math.sqrt(fromPt.distanceSquaredPoint(toPt));
|
||||
if (distance > 40) return "13px sans-serif";
|
||||
if (distance <= 40 && distance >= 20) return "11px sans-serif";
|
||||
else return "9px sans-serif";
|
||||
} return "13px sans-serif";
|
||||
}).ofObject()
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,499 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* FLOOR PLANNER CODE: TEMPLATES - WALLS
|
||||
* GraphObject templates for Wall Groups, Wall Part Nodes (and their dependecies) used in the Floor Planner sample
|
||||
* Includes Wall Group, Palette Wall Node, Window Node, Door Node
|
||||
*/
|
||||
|
||||
/*
|
||||
* Wall Group Dependencies:
|
||||
* Snap Walls, Find Closest Loc on Wall, Add Wall Part, Wall Part Drag Over, Wall Part Drag Away
|
||||
*/
|
||||
|
||||
/*
|
||||
* Drag computation function to snap walls to the grid properly while dragging
|
||||
* @param {Node} part A reference to dragged Part
|
||||
* @param {Point} pt The Point describing the proposed location
|
||||
* @param {Point} gridPt Snapped location
|
||||
*/
|
||||
var snapWalls = function (part, pt, gridPt) {
|
||||
var floorplan = part.diagram;
|
||||
floorplan.updateWallDimensions();
|
||||
floorplan.updateWallAngles();
|
||||
floorplan.updateWall(part);
|
||||
var grid = part.diagram.grid;
|
||||
var sPt = part.data.startpoint.copy();
|
||||
var ePt = part.data.endpoint.copy();
|
||||
var dx = pt.x - part.location.x;
|
||||
var dy = pt.y - part.location.y;
|
||||
var newSpt = sPt.offset(dx, dy);
|
||||
var newEpt = ePt.offset(dx, dy);
|
||||
if (floorplan.toolManager.draggingTool.isGridSnapEnabled) {
|
||||
newSpt = newSpt.snapToGridPoint(grid.gridOrigin, grid.gridCellSize);
|
||||
newEpt = newEpt.snapToGridPoint(grid.gridOrigin, grid.gridCellSize);
|
||||
}
|
||||
floorplan.model.setDataProperty(part.data, "startpoint", newSpt);
|
||||
floorplan.model.setDataProperty(part.data, "endpoint", newEpt);
|
||||
return new go.Point((newSpt.x + newEpt.x) / 2, (newSpt.y + newEpt.y) / 2);
|
||||
}
|
||||
|
||||
/*
|
||||
* Find closest loc (to mouse point) on wall a wallPart can be dropped onto without extending beyond wall endpoints or intruding into another wallPart
|
||||
* @param {Group} wall A reference to a Wall Group
|
||||
* @param {Node} part A reference to a Wall Part Node -- i.e. Door Node, Window Node
|
||||
*/
|
||||
function findClosestLocOnWall(wall, part) {
|
||||
var orderedConstrainingPts = []; // wall endpoints and wallPart endpoints
|
||||
var startpoint = wall.data.startpoint.copy();
|
||||
var endpoint = wall.data.endpoint.copy();
|
||||
// store all possible constraining endpoints (wall endpoints and wallPart endpoints) in the order in which they appear (left/top to right/bottom)
|
||||
var firstWallPt = ((startpoint.x + startpoint.y) <= (endpoint.x + endpoint.y)) ? startpoint : endpoint;
|
||||
var lastWallPt = ((startpoint.x + startpoint.y) > (endpoint.x + endpoint.y)) ? startpoint : endpoint;
|
||||
var wallPartEndpoints = [];
|
||||
wall.memberParts.iterator.each(function (wallPart) {
|
||||
var endpoints = getWallPartEndpoints(wallPart);
|
||||
wallPartEndpoints.push(endpoints[0]);
|
||||
wallPartEndpoints.push(endpoints[1]);
|
||||
});
|
||||
// sort all wallPartEndpoints by x coordinate left to right
|
||||
wallPartEndpoints.sort(function (a, b) {
|
||||
if ((a.x + a.y) > (b.x + b.y)) return 1;
|
||||
if ((a.x + a.y) < (b.x + b.y)) return -1;
|
||||
else return 0;
|
||||
});
|
||||
orderedConstrainingPts.push(firstWallPt);
|
||||
orderedConstrainingPts = orderedConstrainingPts.concat(wallPartEndpoints);
|
||||
orderedConstrainingPts.push(lastWallPt);
|
||||
|
||||
// go through all constraining points; if there's a free stretch along the wall "part" could fit in, remember it
|
||||
var possibleStretches = [];
|
||||
for (var i = 0; i < orderedConstrainingPts.length; i += 2) {
|
||||
var point1 = orderedConstrainingPts[i];
|
||||
var point2 = orderedConstrainingPts[i + 1];
|
||||
var distanceBetween = Math.sqrt(point1.distanceSquaredPoint(point2));
|
||||
if (distanceBetween >= part.data.length) possibleStretches.push({ pt1: point1, pt2: point2 });
|
||||
}
|
||||
|
||||
// go through all possible stretches along the wall the part *could* fit in; find the one closest to the part's current location
|
||||
var closestDist = Number.MAX_VALUE; var closestStretch = null;
|
||||
for (var i = 0; i < possibleStretches.length; i++) {
|
||||
var testStretch = possibleStretches[i];
|
||||
var testPoint1 = testStretch.pt1;
|
||||
var testPoint2 = testStretch.pt2;
|
||||
var testDistance1 = Math.sqrt(testPoint1.distanceSquaredPoint(part.location));
|
||||
var testDistance2 = Math.sqrt(testPoint2.distanceSquaredPoint(part.location));
|
||||
if (testDistance1 < closestDist) {
|
||||
closestDist = testDistance1;
|
||||
closestStretch = testStretch;
|
||||
}
|
||||
if (testDistance2 < closestDist) {
|
||||
closestDist = testDistance2;
|
||||
closestStretch = testStretch;
|
||||
}
|
||||
}
|
||||
|
||||
// Edge Case: If there's no space for the wallPart, return null
|
||||
if (closestStretch === null) return null;
|
||||
|
||||
// using the closest free stretch along the wall, calculate endpoints that make the stretch's line segment, then project part.location onto the segment
|
||||
var closestStretchLength = Math.sqrt(closestStretch.pt1.distanceSquaredPoint(closestStretch.pt2));
|
||||
var offset = part.data.length / 2;
|
||||
var point1 = new go.Point(closestStretch.pt1.x + ((offset / closestStretchLength) * (closestStretch.pt2.x - closestStretch.pt1.x)),
|
||||
closestStretch.pt1.y + ((offset / closestStretchLength) * (closestStretch.pt2.y - closestStretch.pt1.y)));
|
||||
var point2 = new go.Point(closestStretch.pt2.x + ((offset / closestStretchLength) * (closestStretch.pt1.x - closestStretch.pt2.x)),
|
||||
closestStretch.pt2.y + ((offset / closestStretchLength) * (closestStretch.pt1.y - closestStretch.pt2.y)));
|
||||
var newLoc = part.location.copy().projectOntoLineSegmentPoint(point1, point2);
|
||||
return newLoc;
|
||||
}
|
||||
|
||||
// MouseDrop event for wall groups; if a door or window is dropped on a wall, add it to the wall group
|
||||
// Do not allow dropping wallParts that would extend beyond wall endpoints or intrude into another wallPart
|
||||
var addWallPart = function (e, wall) {
|
||||
var floorplan = e.diagram;
|
||||
var wallPart = floorplan.selection.first();
|
||||
if ((wallPart && (wallPart.category === "WindowNode" || wallPart.category === "DoorNode") && wallPart.containingGroup === null)) {
|
||||
var newLoc = findClosestLocOnWall(wall, wallPart);
|
||||
if (newLoc !== null) {
|
||||
wall.findObject("SHAPE").stroke = makeBrush(wall.data);
|
||||
floorplan.model.setDataProperty(wallPart.data, "group", wall.data.key);
|
||||
wallPart.location = newLoc.projectOntoLineSegmentPoint(wall.data.startpoint, wall.data.endpoint);
|
||||
wallPart.angle = wall.rotateObject.angle;
|
||||
if (wallPart.category === "WindowNode") floorplan.model.setDataProperty(wallPart.data, "height", wall.data.thickness);
|
||||
if (wallPart.category === "DoorNode") floorplan.model.setDataProperty(wallPart.data, "doorOpeningHeight", wall.data.thickness);
|
||||
} else {
|
||||
floorplan.remove(wallPart);
|
||||
alert("There's not enough room on the wall!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (floorplan.floorplanUI) floorplan.floorplanUI.setSelectionInfo(floorplan.selection.first(), floorplan);
|
||||
floorplan.updateWallDimensions();
|
||||
}
|
||||
|
||||
// MouseDragEnter event for walls; if a door or window is dragged over a wall, highlight the wall and change its angle
|
||||
var wallPartDragOver = function (e, wall) {
|
||||
var floorplan = e.diagram;
|
||||
var parts = floorplan.toolManager.draggingTool.draggingParts;
|
||||
parts.iterator.each(function (part) {
|
||||
if ((part.category === "WindowNode" || part.category === "DoorNode") && part.containingGroup === null) {
|
||||
wall.findObject("SHAPE").stroke = "lightblue";
|
||||
part.angle = wall.rotateObject.angle;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// MouseDragLeave event for walls; if a wall part is dragged past a wall, unhighlight the wall and change back the wall part's angle to 0
|
||||
var wallPartDragAway = function (e, wall) {
|
||||
var floorplan = e.diagram;
|
||||
wall.findObject("SHAPE").stroke = makeBrush(wall.data);
|
||||
var parts = floorplan.toolManager.draggingTool.draggingParts;
|
||||
parts.iterator.each(function (part) {
|
||||
if ((part.category === "WindowNode" || part.category === "DoorNode") && part.containingGroup === null) part.angle = 0
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Wall Group Template
|
||||
*/
|
||||
|
||||
// Wall Group
|
||||
function makeWallGroup() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Group, "Spot",
|
||||
{
|
||||
contextMenu: makeContextMenu(),
|
||||
toolTip: makeGroupToolTip(),
|
||||
selectionObjectName: "SHAPE",
|
||||
rotateObjectName: "SHAPE",
|
||||
locationSpot: go.Spot.Center,
|
||||
reshapable: true,
|
||||
minSize: new go.Size(1, 1),
|
||||
dragComputation: snapWalls,
|
||||
selectionAdorned: false,
|
||||
mouseDrop: addWallPart,
|
||||
mouseDragEnter: wallPartDragOver,
|
||||
mouseDragLeave: wallPartDragAway,
|
||||
doubleClick: function (e) { if (e.diagram.floorplanUI) e.diagram.floorplanUI.hideShow("selectionInfoWindow"); }
|
||||
},
|
||||
$(go.Shape,
|
||||
{ name: "SHAPE" },
|
||||
new go.Binding("strokeWidth", "thickness"),
|
||||
new go.Binding("stroke", "isSelected", function (s, obj) {
|
||||
if (obj.part.containingGroup != null) {
|
||||
var group = obj.part.containingGroup;
|
||||
if (s) { group.data.isSelected = true; }
|
||||
}
|
||||
return s ? "dodgerblue" : makeBrush(obj.part.data);
|
||||
}).ofObject()
|
||||
))
|
||||
}
|
||||
|
||||
function makeBrush(data) {
|
||||
return "black";
|
||||
}
|
||||
|
||||
/*
|
||||
* Wall Part Node Dependencies:
|
||||
* Get Wall Part Endpoints, Get Wall Part Stretch, Drag Wall Parts (Drag Computation Function),
|
||||
* Wall Part Resize Adornment, Door Selection Adornment (Door Nodes only)
|
||||
*/
|
||||
|
||||
/*
|
||||
* Find and return an array of the endpoints of a given wallpart (window or door)
|
||||
* @param {Node} wallPart A Wall Part Node -- i.e. Door Node, Window Node
|
||||
*/
|
||||
function getWallPartEndpoints(wallPart) {
|
||||
var loc = wallPart.location;
|
||||
var partLength = wallPart.data.length;
|
||||
if (wallPart.containingGroup !== null) var angle = wallPart.containingGroup.rotateObject.angle;
|
||||
else var angle = 180;
|
||||
var point1 = new go.Point((loc.x + (partLength / 2)), loc.y);
|
||||
var point2 = new go.Point((loc.x - (partLength / 2)), loc.y);
|
||||
point1.offset(-loc.x, -loc.y).rotate(angle).offset(loc.x, loc.y);
|
||||
point2.offset(-loc.x, -loc.y).rotate(angle).offset(loc.x, loc.y);
|
||||
var arr = []; arr.push(point1); arr.push(point2);
|
||||
return arr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a "stretch" (2 Points) that constrains a wallPart (door or window), comprised of "part"'s containing wall endpoints or other wallPart endpoints
|
||||
* @param {Node} part A Wall Part Node -- i.e. Door Node, Window Node, that is attached to a wall
|
||||
*/
|
||||
function getWallPartStretch(part) {
|
||||
var wall = part.containingGroup;
|
||||
var startpoint = wall.data.startpoint.copy();
|
||||
var endpoint = wall.data.endpoint.copy();
|
||||
|
||||
// sort all possible endpoints into either left/above or right/below
|
||||
var leftOrAbove = new go.Set(/*go.Point*/); var rightOrBelow = new go.Set(/*go.Point*/);
|
||||
wall.memberParts.iterator.each(function (wallPart) {
|
||||
if (wallPart.data.key !== part.data.key) {
|
||||
var endpoints = getWallPartEndpoints(wallPart);
|
||||
for (var i = 0; i < endpoints.length; i++) {
|
||||
if (endpoints[i].x < part.location.x || (endpoints[i].y > part.location.y && endpoints[i].x === part.location.x)) leftOrAbove.add(endpoints[i]);
|
||||
else rightOrBelow.add(endpoints[i]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// do the same with the startpoint and endpoint of the dragging part's wall
|
||||
if (parseFloat(startpoint.x.toFixed(2)) < parseFloat(part.location.x.toFixed(2)) || (startpoint.y > part.location.y && parseFloat(startpoint.x.toFixed(2)) === parseFloat(part.location.x.toFixed(2)))) leftOrAbove.add(startpoint);
|
||||
else rightOrBelow.add(startpoint);
|
||||
if (parseFloat(endpoint.x.toFixed(2)) < parseFloat(part.location.x.toFixed(2)) || (endpoint.y > part.location.y && parseFloat(endpoint.x.toFixed(2)) === parseFloat(part.location.x.toFixed(2)))) leftOrAbove.add(endpoint);
|
||||
else rightOrBelow.add(endpoint);
|
||||
|
||||
// of each set, find the closest point to the dragging part
|
||||
var leftOrAbovePt; var closestDistLeftOrAbove = Number.MAX_VALUE;
|
||||
leftOrAbove.iterator.each(function (point) {
|
||||
var distance = Math.sqrt(point.distanceSquaredPoint(part.location));
|
||||
if (distance < closestDistLeftOrAbove) {
|
||||
closestDistLeftOrAbove = distance;
|
||||
leftOrAbovePt = point;
|
||||
}
|
||||
});
|
||||
var rightOrBelowPt; var closestDistRightOrBelow = Number.MAX_VALUE;
|
||||
rightOrBelow.iterator.each(function (point) {
|
||||
var distance = Math.sqrt(point.distanceSquaredPoint(part.location));
|
||||
if (distance < closestDistRightOrBelow) {
|
||||
closestDistRightOrBelow = distance;
|
||||
rightOrBelowPt = point;
|
||||
}
|
||||
});
|
||||
|
||||
var stretch = { point1: leftOrAbovePt, point2: rightOrBelowPt };
|
||||
return stretch;
|
||||
}
|
||||
|
||||
/*
|
||||
* Drag computation function for WindowNodes and DoorNodes; ensure wall parts stay in walls when dragged
|
||||
* @param {Node} part A reference to dragged Part
|
||||
* @param {Point} pt The Point describing the proposed location
|
||||
* @param {Point} gridPt Snapped location
|
||||
*/
|
||||
var dragWallParts = function (part, pt, gridPt) {
|
||||
if (part.containingGroup !== null && part.containingGroup.category === 'WallGroup') {
|
||||
var floorplan = part.diagram;
|
||||
// Edge Case: if part is not on its wall (due to incorrect load) snap part.loc onto its wall immediately; ideally this is never called
|
||||
var wall = part.containingGroup;
|
||||
var wStart = wall.data.startpoint;
|
||||
var wEnd = wall.data.endpoint;
|
||||
var dist1 = Math.sqrt(wStart.distanceSquaredPoint(part.location));
|
||||
var dist2 = Math.sqrt(part.location.distanceSquaredPoint(wEnd));
|
||||
var totalDist = Math.sqrt(wStart.distanceSquaredPoint(wEnd));
|
||||
if (dist1 + dist2 !== totalDist) part.location = part.location.copy().projectOntoLineSegmentPoint(wStart, wEnd);
|
||||
|
||||
// main behavior
|
||||
var stretch = getWallPartStretch(part);
|
||||
var leftOrAbovePt = stretch.point1;
|
||||
var rightOrBelowPt = stretch.point2;
|
||||
|
||||
// calc points along line created by the endpoints that are half the width of the moving window/door
|
||||
var totalLength = Math.sqrt(leftOrAbovePt.distanceSquaredPoint(rightOrBelowPt));
|
||||
var distance = (part.data.length / 2);
|
||||
var point1 = new go.Point(leftOrAbovePt.x + ((distance / totalLength) * (rightOrBelowPt.x - leftOrAbovePt.x)),
|
||||
leftOrAbovePt.y + ((distance / totalLength) * (rightOrBelowPt.y - leftOrAbovePt.y)));
|
||||
var point2 = new go.Point(rightOrBelowPt.x + ((distance / totalLength) * (leftOrAbovePt.x - rightOrBelowPt.x)),
|
||||
rightOrBelowPt.y + ((distance / totalLength) * (leftOrAbovePt.y - rightOrBelowPt.y)));
|
||||
|
||||
// calc distance from pt to line (part's wall) - use point to 2pt line segment distance formula
|
||||
var distFromWall = Math.abs(((wEnd.y - wStart.y) * pt.x) - ((wEnd.x - wStart.x) * pt.y) + (wEnd.x * wStart.y) - (wEnd.y * wStart.x)) /
|
||||
Math.sqrt(Math.pow((wEnd.y - wStart.y), 2) + Math.pow((wEnd.x - wStart.x), 2));
|
||||
var tolerance = (20 * wall.data.thickness < 100) ? (20 * wall.data.thickness) : 100;
|
||||
|
||||
// if distance from pt to line > some tolerance, detach the wallPart from the wall
|
||||
if (distFromWall > tolerance) {
|
||||
part.containingGroup = null;
|
||||
delete part.data.group;
|
||||
part.angle = 0;
|
||||
floorplan.pointNodes.iterator.each(function (node) { floorplan.remove(node) });
|
||||
floorplan.dimensionLinks.iterator.each(function (link) { floorplan.remove(link) });
|
||||
floorplan.pointNodes.clear();
|
||||
floorplan.dimensionLinks.clear();
|
||||
floorplan.updateWallDimensions();
|
||||
if (floorplan.floorplanUI) floorplan.floorplanUI.setSelectionInfo(part);
|
||||
}
|
||||
|
||||
// project the proposed location onto the line segment created by the new points (ensures wall parts are constrained properly when dragged)
|
||||
pt = pt.copy().projectOntoLineSegmentPoint(point1, point2);
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.startTransaction("set loc");
|
||||
floorplan.model.setDataProperty(part.data, "loc", go.Point.stringify(pt));
|
||||
floorplan.commitTransaction("set loc");
|
||||
floorplan.skipsUndoManager = false;
|
||||
|
||||
floorplan.updateWallDimensions(); // update the dimension links created by having this wall part selected
|
||||
} return pt;
|
||||
}
|
||||
|
||||
// Resize Adornment for Wall Part Nodes
|
||||
function makeWallPartResizeAdornment() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Adornment, "Spot",
|
||||
{ name: "WallPartResizeAdornment" },
|
||||
$(go.Placeholder),
|
||||
$(go.Shape, { alignment: go.Spot.Left, cursor: "w-resize", figure: "Diamond", desiredSize: new go.Size(7, 7), fill: "#ffffff", stroke: "#808080" }),
|
||||
$(go.Shape, { alignment: go.Spot.Right, cursor: "e-resize", figure: "Diamond", desiredSize: new go.Size(7, 7), fill: "#ffffff", stroke: "#808080" })
|
||||
);
|
||||
}
|
||||
|
||||
// Selection Adornment for Door Nodes
|
||||
function makeDoorSelectionAdornment() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Adornment, "Vertical",
|
||||
{ name: "DoorSelectionAdornment" },
|
||||
$(go.Panel, "Auto",
|
||||
$(go.Shape, { fill: null, stroke: null }),
|
||||
$(go.Placeholder)),
|
||||
$(go.Panel, "Horizontal", { defaultStretch: go.GraphObject.Vertical },
|
||||
$("Button",
|
||||
$(go.Picture, { source: "icons/flipDoorOpeningLeft.png", column: 0, desiredSize: new go.Size(12, 12) },
|
||||
new go.Binding("source", "", function (obj) {
|
||||
if (obj.adornedPart === null) return "icons/flipDoorOpeningRight.png";
|
||||
else if (obj.adornedPart.data.swing === "left") return "icons/flipDoorOpeningRight.png";
|
||||
else return "icons/flipDoorOpeningLeft.png";
|
||||
}).ofObject()
|
||||
),
|
||||
{
|
||||
click: function (e, obj) {
|
||||
var floorplan = obj.part.diagram;
|
||||
floorplan.startTransaction("flip door");
|
||||
var door = obj.part.adornedPart;
|
||||
if (door.data.swing === "left") floorplan.model.setDataProperty(door.data, "swing", "right");
|
||||
else floorplan.model.setDataProperty(door.data, "swing", "left");
|
||||
floorplan.commitTransaction("flip door");
|
||||
},
|
||||
toolTip: $(go.Adornment, "Auto",
|
||||
$(go.Shape, { fill: "#FFFFCC" }),
|
||||
$(go.TextBlock, { margin: 4, text: "Flip Door Opening" }
|
||||
))
|
||||
},
|
||||
new go.Binding("visible", "", function (obj) { return (obj.adornedPart === null) ? false : (obj.adornedPart.containingGroup !== null); }).ofObject()
|
||||
),
|
||||
$("Button",
|
||||
$(go.Picture, { source: "icons/flipDoorSide.png", column: 0, desiredSize: new go.Size(12, 12) }),
|
||||
{
|
||||
click: function (e, obj) {
|
||||
var floorplan = obj.part.diagram;
|
||||
floorplan.startTransaction("rotate door");
|
||||
var door = obj.part.adornedPart;
|
||||
door.angle = (door.angle + 180) % 360;
|
||||
floorplan.commitTransaction("rotate door");
|
||||
},
|
||||
toolTip: $(go.Adornment, "Auto",
|
||||
$(go.Shape, { fill: "#FFFFCC" }),
|
||||
$(go.TextBlock, { margin: 4, text: "Flip Door Side" }
|
||||
))
|
||||
}
|
||||
),
|
||||
new go.Binding("visible", "", function (obj) { return (obj.adornedPart === null) ? false : (obj.adornedPart.containingGroup !== null); }).ofObject()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Wall Part Nodes:
|
||||
* Window Node, Door Node, Palette Wall Node
|
||||
*/
|
||||
|
||||
// Window Node
|
||||
function makeWindowNode() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Node, "Spot",
|
||||
{
|
||||
contextMenu: makeContextMenu(),
|
||||
selectionObjectName: "SHAPE",
|
||||
selectionAdorned: false,
|
||||
locationSpot: go.Spot.Center,
|
||||
toolTip: makeNodeToolTip(),
|
||||
minSize: new go.Size(5, 5),
|
||||
resizable: true,
|
||||
resizeAdornmentTemplate: makeWallPartResizeAdornment(),
|
||||
resizeObjectName: "SHAPE",
|
||||
rotatable: false,
|
||||
doubleClick: function (e) { if (e.diagram.floorplanUI) e.diagram.floorplanUI.hideShow("selectionInfoWindow"); },
|
||||
dragComputation: dragWallParts,
|
||||
layerName: 'Foreground' // make sure windows are always in front of walls
|
||||
},
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
new go.Binding("angle").makeTwoWay(),
|
||||
$(go.Shape,
|
||||
{ name: "SHAPE", fill: "white", strokeWidth: 0 },
|
||||
new go.Binding("width", "length").makeTwoWay(),
|
||||
new go.Binding("height").makeTwoWay(),
|
||||
new go.Binding("stroke", "isSelected", function (s, obj) { return s ? "dodgerblue" : "black"; }).ofObject(),
|
||||
new go.Binding("fill", "isSelected", function (s, obj) { return s ? "lightgray" : "white"; }).ofObject()
|
||||
),
|
||||
$(go.Shape,
|
||||
{ name: "LINESHAPE", fill: "darkgray", strokeWidth: 0, height: 10 },
|
||||
new go.Binding("width", "length", function (width, obj) { return width - 10; }), // 5px padding each side
|
||||
new go.Binding("height", "height", function (height, obj) { return (height / 5); }),
|
||||
new go.Binding("stroke", "isSelected", function (s, obj) { return s ? "dodgerblue" : "black"; }).ofObject()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Door Node
|
||||
function makeDoorNode() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Node, "Spot",
|
||||
{
|
||||
contextMenu: makeContextMenu(),
|
||||
selectionObjectName: "SHAPE",
|
||||
selectionAdornmentTemplate: makeDoorSelectionAdornment(),
|
||||
locationSpot: go.Spot.BottomCenter,
|
||||
resizable: true,
|
||||
resizeObjectName: "OPENING_SHAPE",
|
||||
toolTip: makeNodeToolTip(),
|
||||
minSize: new go.Size(10, 10),
|
||||
doubleClick: function (e) { if (e.diagram.floorplanUI) e.diagram.floorplanUI.hideShow("selectionInfoWindow"); },
|
||||
dragComputation: dragWallParts,
|
||||
resizeAdornmentTemplate: makeWallPartResizeAdornment(),
|
||||
layerName: 'Foreground' // make sure windows are always in front of walls
|
||||
},
|
||||
// remember location of the Node
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
new go.Binding("angle").makeTwoWay(),
|
||||
// the door's locationSpot is affected by it's openingHeight, which is affected by the thickness of its containing wall
|
||||
new go.Binding("locationSpot", "doorOpeningHeight", function (doh, obj) { return new go.Spot(0.5, 1, 0, -(doh / 2)); }),
|
||||
// this is the shape that reprents the door itself and its swing
|
||||
$(go.Shape,
|
||||
{ name: "SHAPE" },
|
||||
new go.Binding("width", "length"),
|
||||
new go.Binding("height", "length").makeTwoWay(),
|
||||
new go.Binding("stroke", "isSelected", function (s, obj) { return s ? "dodgerblue" : "black"; }).ofObject(),
|
||||
new go.Binding("fill", "color"),
|
||||
new go.Binding("geometryString", "swing", function (swing) {
|
||||
if (swing === "left") return "F1 M0,0 v-150 a150,150 0 0,1 150,150 ";
|
||||
else return "F1 M275,175 v-150 a150,150 0 0,0 -150,150 ";
|
||||
})
|
||||
),
|
||||
// door opening shape
|
||||
$(go.Shape,
|
||||
{
|
||||
name: "OPENING_SHAPE", fill: "white",
|
||||
strokeWidth: 0, height: 5, width: 40,
|
||||
alignment: go.Spot.BottomCenter, alignmentFocus: go.Spot.Center
|
||||
},
|
||||
new go.Binding("height", "doorOpeningHeight").makeTwoWay(),
|
||||
new go.Binding("stroke", "isSelected", function (s, obj) { return s ? "dodgerblue" : "black"; }).ofObject(),
|
||||
new go.Binding("fill", "isSelected", function (s, obj) { return s ? "lightgray" : "white"; }).ofObject(),
|
||||
new go.Binding("width", "length").makeTwoWay()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Palette Wall Node (becomes WallGroup when dropped from Palette onto diagram)
|
||||
function makePaletteWallNode() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Node, "Spot",
|
||||
{ selectionAdorned: false },
|
||||
$(go.Shape,
|
||||
{ name: "SHAPE", fill: "black", strokeWidth: 0, height: 10, figure: "Rectangle" },
|
||||
new go.Binding("width", "length").makeTwoWay(),
|
||||
new go.Binding("height").makeTwoWay(),
|
||||
new go.Binding("fill", "isSelected", function (s, obj) { return s ? "dodgerblue" : "black"; }).ofObject(),
|
||||
new go.Binding("stroke", "isSelected", function (s, obj) { return s ? "dodgerblue" : "black"; }).ofObject())
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* FLOOR PLANNER - WALL BUILDING TOOL
|
||||
* Used to construct new Walls in a Floorplan with mouse clicking / mouse point
|
||||
* Depends on functionality in Floorplan.js
|
||||
*/
|
||||
|
||||
// Constructor
|
||||
function WallBuildingTool() {
|
||||
go.Tool.call(this);
|
||||
this.name = "WallBuilding";
|
||||
this._startPoint = null;
|
||||
this._endPoint = null;
|
||||
this._wallReshapingTool = null;
|
||||
} go.Diagram.inherit(WallBuildingTool, go.Tool);
|
||||
|
||||
// Get / set the current startPoint
|
||||
Object.defineProperty(WallBuildingTool.prototype, "startPoint", {
|
||||
get: function () { return this._startPoint; },
|
||||
set: function (val) { this._startPoint = val; }
|
||||
});
|
||||
|
||||
// Get / set the current endPoint
|
||||
Object.defineProperty(WallBuildingTool.prototype, "endPoint", {
|
||||
get: function () { return this._endPoint; },
|
||||
set: function (val) { this._endPoint = val; }
|
||||
});
|
||||
|
||||
// Get / set the diagram's wallReshapingTool
|
||||
Object.defineProperty(WallBuildingTool.prototype, "wallReshapingTool", {
|
||||
get: function () { return this._wallReshapingTool; },
|
||||
set: function (val) { this._wallReshapingTool = val; }
|
||||
});
|
||||
|
||||
// Tool can start iff diagram exists, is editable, and tool is enabled
|
||||
WallBuildingTool.prototype.canStart = function () {
|
||||
var diagram = this.diagram;
|
||||
if (diagram !== null && !diagram.isReadOnly && this.isEnabled) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start transaction, capture the mouse, use a crosshair cursor
|
||||
WallBuildingTool.prototype.doActivate = function () {
|
||||
this.endPoint = null;
|
||||
this.startTransaction(this.name);
|
||||
this.isMouseCaptured = true;
|
||||
var diagram = this.diagram;
|
||||
var tool = this;
|
||||
|
||||
// update wallThickness, based on the current value of the HTML input element
|
||||
// pre-condition: diagram.floorplanUI exists
|
||||
if (diagram.floorplanUI) {
|
||||
var el = document.getElementById(diagram.floorplanUI.state.wallThicknessInputId);
|
||||
if (isNaN(el.value) || el.value === null || el.value === '' || el.value === undefined) el.value = diagram.convertPixelsToUnits(5);
|
||||
diagram.model.setDataProperty(diagram.model.modelData, "wallThickness", diagram.convertUnitsToPixels(parseFloat(el.value)));
|
||||
}
|
||||
else diagram.model.setDataProperty(diagram.model.modelData, "wallThickness", diagram.convertUnitsToPixels(parseFloat(5)));
|
||||
|
||||
// assign startpoint based on grid
|
||||
var point1 = tool.diagram.lastInput.documentPoint;
|
||||
var gs = diagram.model.modelData.gridSize;
|
||||
if (!(tool.diagram.toolManager.draggingTool.isGridSnapEnabled)) gs = 1;
|
||||
var newx = gs * Math.round(point1.x / gs);
|
||||
var newy = gs * Math.round(point1.y / gs);
|
||||
var newPoint1 = new go.Point(newx, newy);
|
||||
this.startPoint = newPoint1;
|
||||
|
||||
this.wallReshapingTool = tool.diagram.toolManager.mouseDownTools.elt(3);
|
||||
// Default functionality:
|
||||
this.isActive = true;
|
||||
}
|
||||
|
||||
// When user clicks, add wall model data to model and initialize a Wall Reshaping Tool to handle the shaping of its construction
|
||||
WallBuildingTool.prototype.doMouseDown = function () {
|
||||
var diagram = this.diagram;
|
||||
var tool = this;
|
||||
tool.diagram.currentCursor = 'crosshair';
|
||||
var data = { key: "wall", category: "WallGroup", caption: "Wall", type: "Wall", startpoint: tool.startPoint, endpoint: tool.startPoint, thickness: parseFloat(diagram.model.modelData.wallThickness), isGroup: true, notes: "" };
|
||||
this.diagram.model.addNodeData(data);
|
||||
var wall = diagram.findPartForKey(data.key);
|
||||
diagram.updateWall(wall);
|
||||
var part = diagram.findPartForData(data);
|
||||
// set the TransactionResult before raising event, in case it changes the result or cancels the tool
|
||||
tool.transactionResult = tool.name;
|
||||
diagram.raiseDiagramEvent('PartCreated', part);
|
||||
|
||||
// start the wallReshapingTool, tell it what wall it's reshaping (more accurately, the shape that will have the reshape handle)
|
||||
tool.wallReshapingTool.isEnabled = true;
|
||||
diagram.select(part);
|
||||
tool.wallReshapingTool.isBuilding = true;
|
||||
tool.wallReshapingTool.adornedShape = part.findObject("SHAPE");
|
||||
tool.wallReshapingTool.doActivate();
|
||||
}
|
||||
|
||||
// If user presses Esc key, cancel the wall building
|
||||
WallBuildingTool.prototype.doKeyDown = function () {
|
||||
var diagram = this.diagram;
|
||||
var e = diagram.lastInput;
|
||||
if (e.key === "Esc") {
|
||||
var wall = diagram.selection.first();
|
||||
diagram.remove(wall);
|
||||
diagram.pointNodes.iterator.each(function (node) { diagram.remove(node); });
|
||||
diagram.dimensionLinks.iterator.each(function (link) { diagram.remove(link); });
|
||||
diagram.pointNodes.clear();
|
||||
diagram.dimensionLinks.clear();
|
||||
this.doDeactivate();
|
||||
}
|
||||
go.Tool.prototype.doKeyDown.call(this);
|
||||
}
|
||||
|
||||
// When the mouse moves, reshape the wall
|
||||
WallBuildingTool.prototype.doMouseMove = function () {
|
||||
this.wallReshapingTool.doMouseMove();
|
||||
}
|
||||
|
||||
// End transaction
|
||||
WallBuildingTool.prototype.doDeactivate = function () {
|
||||
var diagram = this.diagram;
|
||||
this.diagram.currentCursor = "";
|
||||
this.diagram.isMouseCaptured = false;
|
||||
|
||||
this.wallReshapingTool.isEnabled = false;
|
||||
this.wallReshapingTool.adornedShape = null;
|
||||
this.wallReshapingTool.doDeactivate();
|
||||
this.wallReshapingTool.isBuilding = false;
|
||||
|
||||
diagram.updateWallDimensions();
|
||||
|
||||
this.stopTransaction(this.name);
|
||||
|
||||
this.isActive = false; // Default functionality
|
||||
}
|
||||
@@ -0,0 +1,595 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* FLOOR PLANNER: WALL RESHAPING TOOL
|
||||
* Used to reshape walls via their endpoints in a Floorplan
|
||||
* Depends on functionality in Floorplan.js
|
||||
*/
|
||||
|
||||
// Constructor
|
||||
function WallReshapingTool() {
|
||||
go.Tool.call(this);
|
||||
this.name = "WallReshaping";
|
||||
|
||||
var h = new go.Shape();
|
||||
h.figure = "Diamond";
|
||||
h.desiredSize = new go.Size(7, 7);
|
||||
h.fill = "lightblue";
|
||||
h.stroke = "dodgerblue";
|
||||
h.cursor = "move";
|
||||
this._handleArchetype = h;
|
||||
|
||||
this._handle = null;
|
||||
this._adornedShape = null;
|
||||
this._reshapeObjectName = 'SHAPE';
|
||||
this._angle = 0;
|
||||
this._length;
|
||||
this._isBuilding = false; // only true when a wall is first being constructed, set in WallBuildingTool's doMouseUp function
|
||||
|
||||
this._returnPoint = null; // used if reshape is cancelled; return reshaping wall endpoint to its previous location
|
||||
this._returnData = null; // used if reshape is cancelled; return all windows/doors of a reshaped wall to their old place
|
||||
} go.Diagram.inherit(WallReshapingTool, go.Tool);
|
||||
|
||||
// Get the archetype for the handle (a Shape)
|
||||
Object.defineProperty(WallReshapingTool.prototype, "handleArchetype", {
|
||||
get: function () { return this._handleArchetype; }
|
||||
});
|
||||
|
||||
// Get / set current handle being used to reshape the wall
|
||||
Object.defineProperty(WallReshapingTool.prototype, "handle", {
|
||||
get: function () { return this._handle; },
|
||||
set: function (val) { this._handle = val; }
|
||||
});
|
||||
|
||||
// Get / set adorned shape (shape of the Wall Group being reshaped)
|
||||
Object.defineProperty(WallReshapingTool.prototype, "adornedShape", {
|
||||
get: function () { return this._adornedShape; },
|
||||
set: function (val) { this._adornedShape = val; }
|
||||
});
|
||||
|
||||
// Get / set current angle
|
||||
Object.defineProperty(WallReshapingTool.prototype, "angle", {
|
||||
get: function () { return this._angle; },
|
||||
set: function (val) { this._angle = val; }
|
||||
});
|
||||
|
||||
// Get / set length of the wall being reshaped (used only with SHIFT + drag)
|
||||
Object.defineProperty(WallReshapingTool.prototype, "length", {
|
||||
get: function () { return this._length; },
|
||||
set: function (val) { this._length = val; }
|
||||
});
|
||||
|
||||
// Get / set the name of the object being reshaped
|
||||
Object.defineProperty(WallReshapingTool.prototype, "reshapeObjectName", {
|
||||
get: function () { return this._reshapeObjectName; },
|
||||
set: function (val) { this._reshapeObjectName = val; }
|
||||
});
|
||||
|
||||
// Get / set flag telling tool whether it's reshaping a new wall (isBuilding = true) or reshaping an old wall (isBuilding = false)
|
||||
Object.defineProperty(WallReshapingTool.prototype, "isBuilding", {
|
||||
get: function () { return this._isBuilding; },
|
||||
set: function (val) { this._isBuilding = val; }
|
||||
});
|
||||
|
||||
// Get set loc data for wallParts to return to if reshape is cancelled
|
||||
Object.defineProperty(WallReshapingTool.prototype, "returnData", {
|
||||
get: function () { return this._returnData; },
|
||||
set: function (val) { this._returnData = val; }
|
||||
});
|
||||
|
||||
// Get / set the point to return the reshaping wall endpoint to if reshape is cancelled
|
||||
Object.defineProperty(WallReshapingTool.prototype, "returnPoint", {
|
||||
get: function () { return this._returnPoint; },
|
||||
set: function (val) { this._returnPoint = val; }
|
||||
});
|
||||
|
||||
/*
|
||||
* Places reshape handles on either end of a wall node
|
||||
* @param {part} The wall to adorn
|
||||
*/
|
||||
WallReshapingTool.prototype.updateAdornments = function (part) {
|
||||
if (part === null || part instanceof go.Link) return;
|
||||
if (part.isSelected && !this.diagram.isReadOnly) {
|
||||
var selelt = part.findObject(this.reshapeObjectName);
|
||||
if (selelt.part.data.category === "WallGroup") {
|
||||
var adornment = part.findAdornment(this.name);
|
||||
if (adornment === null) {
|
||||
adornment = this.makeAdornment(selelt);
|
||||
}
|
||||
if (adornment !== null && selelt.geometry != null) {
|
||||
// update the position/alignment of each handle
|
||||
var geo = selelt.geometry;
|
||||
var b = geo.bounds;
|
||||
// update the size of the adornment
|
||||
adornment.findObject("BODY").desiredSize = b.size;
|
||||
adornment.elements.each(function (h) {
|
||||
if (h.name === undefined) return;
|
||||
var x = 0;
|
||||
var y = 0;
|
||||
switch (h.name) {
|
||||
case 'sPt': x = geo.startX; y = geo.startY; break;
|
||||
case 'ePt': x = geo.endX; y = geo.endY; break;
|
||||
}
|
||||
var xCheck = Math.min((x - b.x) / b.width, 1);
|
||||
var yCheck = Math.min((y - b.y) / b.height, 1);
|
||||
if (xCheck < 0) xCheck = 0;
|
||||
if (yCheck < 0) yCheck = 0;
|
||||
if (xCheck > 1) xCheck = 1;
|
||||
if (yCheck > 1) yCheck = 1;
|
||||
if (isNaN(xCheck)) xCheck = 0;
|
||||
if (isNaN(yCheck)) yCheck = 0;
|
||||
h.alignment = new go.Spot(Math.max(0, xCheck), Math.max(0, yCheck));
|
||||
});
|
||||
|
||||
part.addAdornment(this.name, adornment);
|
||||
adornment.location = selelt.getDocumentPoint(go.Spot.Center);
|
||||
adornment.angle = selelt.getDocumentAngle();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
part.removeAdornment(this.name);
|
||||
}
|
||||
|
||||
// If the user has clicked down at a visible handle on a wall node, then the tool may start
|
||||
WallReshapingTool.prototype.canStart = function () {
|
||||
if (!this.isEnabled) return false;
|
||||
var diagram = this.diagram;
|
||||
if (diagram === null || diagram.isReadOnly) return false;
|
||||
if (!diagram.allowReshape) return false;
|
||||
if (!diagram.lastInput.left) return false;
|
||||
var h = this.findToolHandleAt(diagram.firstInput.documentPoint, this.name);
|
||||
return (h !== null || this.isBuilding);
|
||||
}
|
||||
|
||||
// Start a new transaction for the wall reshaping
|
||||
WallReshapingTool.prototype.doActivate = function () {
|
||||
var diagram = this.diagram;
|
||||
if (diagram === null) return;
|
||||
if (this.isBuilding) {
|
||||
// this.adornedShape has already been set in WallBuildingTool's doMouseDown function
|
||||
var wall = this.adornedShape.part;
|
||||
this.handle = this.findToolHandleAt(wall.data.endpoint, this.name);
|
||||
} else {
|
||||
this.handle = this.findToolHandleAt(diagram.firstInput.documentPoint, this.name);
|
||||
if (this.handle === null) return;
|
||||
var shape = this.handle.part.adornedObject;
|
||||
var wall = shape.part;
|
||||
if (!shape) return;
|
||||
this.adornedShape = shape;
|
||||
|
||||
// store pre-reshape location of wall's reshaping endpoint
|
||||
this.returnPoint = this.snapPointToGrid(diagram.firstInput.documentPoint);
|
||||
|
||||
// store pre-reshape locations of all wall's members (windows / doors)
|
||||
var wallParts = wall.memberParts;
|
||||
if (wallParts.count != 0) {
|
||||
var locationsMap = new go.Map(/*"string", go.Point*/);
|
||||
wallParts.iterator.each(function (wallPart) {
|
||||
locationsMap.add(wallPart.data.key, wallPart.location);
|
||||
});
|
||||
this.returnData = locationsMap;
|
||||
}
|
||||
}
|
||||
|
||||
this.calcAngleAndLengthFromHandle(diagram.firstInput.documentPoint);
|
||||
this.startTransaction(this.name);
|
||||
this.isActive = true;
|
||||
}
|
||||
|
||||
// Adjust the handle's coordinates, along with the wall's points
|
||||
WallReshapingTool.prototype.doMouseMove = function () {
|
||||
var diagram = this.diagram;
|
||||
var tool = this;
|
||||
var wall = this.handle.part.adornedPart;
|
||||
|
||||
if (this.isActive && diagram !== null) {
|
||||
var mousePt = diagram.lastInput.documentPoint;
|
||||
tool.calcAngleAndLengthFromHandle(mousePt); // sets this.angle and this.length (useful for when SHIFT is held)
|
||||
var newpt = diagram.lastInput.documentPoint;
|
||||
if (diagram.floorplanUI) diagram.floorplanUI.setSelectionInfo(this.adornedShape.part, diagram); // update selection info window
|
||||
this.reshape(newpt);
|
||||
}
|
||||
diagram.updateWallAngles();
|
||||
}
|
||||
|
||||
// Does one final reshape, commits the transaction, then stops the tool
|
||||
WallReshapingTool.prototype.doMouseUp = function () {
|
||||
var diagram = this.diagram;
|
||||
if (this.isActive && diagram !== null) {
|
||||
var newpt = diagram.lastInput.documentPoint;
|
||||
this.reshape(newpt);
|
||||
this.transactionResult = this.name; // success
|
||||
}
|
||||
this.stopTool();
|
||||
}
|
||||
|
||||
// End the wall reshaping transaction
|
||||
WallReshapingTool.prototype.doDeactivate = function () {
|
||||
var diagram = this.diagram;
|
||||
var returnData = this.returnData;
|
||||
// if a wall reshaped to length < 1 px, remove it
|
||||
var wall = this.handle.part.adornedPart;
|
||||
var sPt = wall.data.startpoint;
|
||||
var ePt = wall.data.endpoint;
|
||||
var length = Math.sqrt(sPt.distanceSquared(ePt.x, ePt.y));
|
||||
if (length < 1) {
|
||||
diagram.remove(wall); // remove wall
|
||||
wall.memberParts.iterator.each(function (member) { diagram.remove(member); }) // remove wall's parts
|
||||
var wallDimensionLinkPointNodes = [];
|
||||
diagram.pointNodes.iterator.each(function (node) { if (node.data.key.indexOf(wall.data.key) !== -1) wallDimensionLinkPointNodes.push(node); });
|
||||
diagram.remove(wallDimensionLinkPointNodes[0]);
|
||||
diagram.remove(wallDimensionLinkPointNodes[1]);
|
||||
}
|
||||
|
||||
// remove wall's dimension links if tool cancelled via esc key
|
||||
if (diagram.lastInput.key === "Esc" && !this.isBuilding) {
|
||||
diagram.skipsUndoManager = true;
|
||||
diagram.startTransaction("reset to old data");
|
||||
if (this.handle.name === "sPt") wall.data.startpoint = this.returnPoint;
|
||||
else wall.data.endpoint = this.returnPoint;
|
||||
|
||||
diagram.updateWall(wall);
|
||||
|
||||
if (this.returnData) {
|
||||
this.returnData.iterator.each(function (kvp) {
|
||||
var key = kvp.key;
|
||||
var loc = kvp.value;
|
||||
var wallPart = diagram.findPartForKey(key);
|
||||
wallPart.location = loc;
|
||||
wallPart.rotateObject.angle = wall.rotateObject.angle;
|
||||
});
|
||||
}
|
||||
diagram.commitTransaction("reset to old data");
|
||||
diagram.skipsUndoManager = false;
|
||||
}
|
||||
|
||||
// remove guide line points
|
||||
var glPoints = this.diagram.findNodesByExample({ category: 'GLPointNode' });
|
||||
diagram.removeParts(glPoints, true);
|
||||
|
||||
diagram.updateWallDimensions();
|
||||
// commit transaction, deactivate tool
|
||||
diagram.commitTransaction(this.name);
|
||||
this.isActive = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates an adornment with 2 handles
|
||||
* @param {Shape} The adorned wall's Shape element
|
||||
*/
|
||||
WallReshapingTool.prototype.makeAdornment = function (selelt) {
|
||||
var adornment = new go.Adornment;
|
||||
adornment.type = go.Panel.Spot;
|
||||
adornment.locationObjectName = "BODY";
|
||||
adornment.locationSpot = go.Spot.Center;
|
||||
var h = new go.Shape();
|
||||
h.name = "BODY"
|
||||
h.fill = null;
|
||||
h.stroke = null;
|
||||
h.strokeWidth = 0;
|
||||
adornment.add(h);
|
||||
|
||||
h = this.makeHandle();
|
||||
h.name = 'sPt';
|
||||
adornment.add(h);
|
||||
h = this.makeHandle();
|
||||
h.name = 'ePt';
|
||||
adornment.add(h);
|
||||
|
||||
adornment.category = this.name;
|
||||
adornment.adornedObject = selelt;
|
||||
return adornment;
|
||||
}
|
||||
|
||||
// Creates a basic handle archetype
|
||||
WallReshapingTool.prototype.makeHandle = function () {
|
||||
var h = this.handleArchetype;
|
||||
return h.copy();
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the angle and length made from the mousepoint and the non-moving handle; used to reshape wall when holding SHIFT
|
||||
* @param {Point} mousePt The mouse cursors coordinate position
|
||||
*/
|
||||
WallReshapingTool.prototype.calcAngleAndLengthFromHandle = function (mousePt) {
|
||||
var tool = this;
|
||||
var diagram = this.diagram;
|
||||
var h = this.handle;
|
||||
var otherH;
|
||||
var node = this.adornedShape.part;
|
||||
var adornments = node.adornments.iterator;
|
||||
var adornment;
|
||||
adornments.each(function (a) { if (a.category === tool.name) adornment = a; })
|
||||
adornment.elements.each(function (e) {
|
||||
if (e.name != undefined && e.name != h.name) otherH = e;
|
||||
});
|
||||
|
||||
// calc angle from otherH against the horizontal
|
||||
var otherHandlePt = otherH.getDocumentPoint(go.Spot.Center);
|
||||
|
||||
deltaY = mousePt.y - otherHandlePt.y
|
||||
deltaX = mousePt.x - otherHandlePt.x
|
||||
var angle = Math.atan2(deltaY, deltaX) * (180 / Math.PI);
|
||||
// because atan2 goes from -180 to +180 and we want it to be 0-360
|
||||
// so -90 becomes 270, etc.
|
||||
if (angle < 0) angle += 360;
|
||||
tool.angle = angle;
|
||||
|
||||
var distanceBetween = Math.sqrt(mousePt.distanceSquared(otherHandlePt.x, otherHandlePt.y));
|
||||
tool.length = distanceBetween;
|
||||
}
|
||||
|
||||
/*
|
||||
* Takes a point -- returns a new point that is closest to the original point that conforms to the grid snap
|
||||
* @param {Point} point The point to snap to grid
|
||||
*/
|
||||
WallReshapingTool.prototype.snapPointToGrid = function (point) {
|
||||
var diagram = this.diagram;
|
||||
var newx = diagram.model.modelData.gridSize * Math.round(point.x / diagram.model.modelData.gridSize);
|
||||
var newy = diagram.model.modelData.gridSize * Math.round(point.y / diagram.model.modelData.gridSize);
|
||||
var newPt = new go.Point(newx, newy);
|
||||
return newPt;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reshapes the shape's geometry, updates model data
|
||||
* @param {Point} newPoint The point to move the reshaping wall's reshaping endpoint to
|
||||
*/
|
||||
WallReshapingTool.prototype.reshape = function (newPoint) {
|
||||
var diagram = this.diagram;
|
||||
var tool = this;
|
||||
var shape = this.adornedShape;
|
||||
var node = shape.part;
|
||||
|
||||
// if user holds SHIFT, make angle between startPoint / endPoint and the horizontal line a multiple of 45
|
||||
if (this.diagram.lastInput.shift) {
|
||||
|
||||
var sPt; // the stationary point -- the point at the handle that is not being adjusted
|
||||
if (tool.handle.name === 'sPt') sPt = node.data.endpoint;
|
||||
else sPt = node.data.startpoint;
|
||||
|
||||
var oldGridSize = diagram.model.modelData.gridSize;
|
||||
var gridSize = diagram.model.modelData.gridSize;
|
||||
//if gridSnapping is disabled, just set 'gridSize' var to 1 so it doesn't affect endPoint calculations
|
||||
if (!(this.diagram.toolManager.draggingTool.isGridSnapEnabled)) gridSize = 1;
|
||||
|
||||
// these are set in mouseMove's call to calcAngleAndLengthFromHandle()
|
||||
var angle = tool.angle;
|
||||
var length = tool.length;
|
||||
|
||||
// snap to 90 degrees
|
||||
if (angle > 67.5 && angle < 112.5) {
|
||||
var newy = sPt.y + length;
|
||||
newy = gridSize * Math.round(newy / gridSize);
|
||||
newPoint = new go.Point(sPt.x, newy);
|
||||
}
|
||||
// snap to 180 degrees
|
||||
if (angle > 112.5 && angle < 202.5) {
|
||||
var newx = sPt.x - length;
|
||||
newx = gridSize * Math.round(newx / gridSize);
|
||||
newPoint = new go.Point(newx, sPt.y);
|
||||
}
|
||||
// snap to 270 degrees
|
||||
if (angle > 247.5 && angle < 292.5) {
|
||||
var newy = sPt.y - length;
|
||||
newy = gridSize * Math.round(newy / gridSize);
|
||||
newPoint = new go.Point(sPt.x, newy);
|
||||
}
|
||||
// snap to 360 degrees
|
||||
if (angle > 337.5 || angle < 22.5) {
|
||||
var newx = sPt.x + length;
|
||||
newx = gridSize * Math.round(newx / gridSize);
|
||||
newPoint = new go.Point(newx, sPt.y);
|
||||
}
|
||||
// snap to 45 degrees
|
||||
if (angle > 22.5 && angle < 67.5) {
|
||||
var newx = (Math.sin(.785) * length);
|
||||
newx = gridSize * Math.round(newx / gridSize) + sPt.x;
|
||||
var newy = (Math.cos(.785) * length);
|
||||
newy = gridSize * Math.round(newy / gridSize) + sPt.y;
|
||||
newPoint = new go.Point(newx, newy);
|
||||
}
|
||||
// snap to 135 degrees
|
||||
if (angle > 112.5 && angle < 157.5) {
|
||||
var newx = (Math.sin(.785) * length);
|
||||
newx = sPt.x - (gridSize * Math.round(newx / gridSize));
|
||||
var newy = (Math.cos(.785) * length);
|
||||
newy = gridSize * Math.round(newy / gridSize) + sPt.y;
|
||||
newPoint = new go.Point(newx, newy);
|
||||
}
|
||||
// snap to 225 degrees
|
||||
if (angle > 202.5 && angle < 247.5) {
|
||||
var newx = (Math.sin(.785) * length);
|
||||
newx = sPt.x - (gridSize * Math.round(newx / gridSize));
|
||||
var newy = (Math.cos(.785) * length);
|
||||
newy = sPt.y - (gridSize * Math.round(newy / gridSize));
|
||||
newPoint = new go.Point(newx, newy);
|
||||
}
|
||||
// snap to 315 degrees
|
||||
if (angle > 292.5 && angle < 337.5) {
|
||||
var newx = (Math.sin(.785) * length);
|
||||
newx = sPt.x + (gridSize * Math.round(newx / gridSize));
|
||||
var newy = (Math.cos(.785) * length);
|
||||
newy = sPt.y - (gridSize * Math.round(newy / gridSize));
|
||||
newPoint = new go.Point(newx, newy);
|
||||
}
|
||||
gridSize = oldGridSize; //set gridSize back to what it used to be in case gridSnap is enabled again
|
||||
}
|
||||
if (this.diagram.toolManager.draggingTool.isGridSnapEnabled) newPoint = this.snapPointToGrid(newPoint);
|
||||
else newPoint = new go.Point(newPoint.x, newPoint.y);
|
||||
|
||||
var type = this.handle.name;
|
||||
if (type === undefined) return;
|
||||
// set the appropriate point in the node's data to the newPoint value
|
||||
switch (type) {
|
||||
case 'sPt':
|
||||
reshapeWall(node, node.data.endpoint, node.data.startpoint, newPoint, diagram, tool);
|
||||
break;
|
||||
case 'ePt':
|
||||
reshapeWall(node, node.data.startpoint, node.data.endpoint, newPoint, diagram, tool);
|
||||
break;
|
||||
}
|
||||
this.updateAdornments(shape.part);
|
||||
this.showMatches();
|
||||
diagram.updateWallDimensions();
|
||||
}
|
||||
|
||||
/*Maintain position of all wallParts as best as possible when a wall is being reshaped
|
||||
* Position is relative to the distance a wallPart's location is from the stationaryPoint of the wall
|
||||
* This is called during WallReshapingTool's reshape function
|
||||
* @param {Group} wall The wall being reshaped
|
||||
* @param {Point} stationaryPoint The endpoint of the wall not being reshaped
|
||||
* @param {Point} movingPoint The endpoint of the wall being reshaped
|
||||
* @param {Point} newPoint The point that movingPoint is going to
|
||||
* @param {Diagram} diagram The diagram belonging WallReshapingTool belongs to
|
||||
* @param {WallReshapingTool} tool
|
||||
*/
|
||||
function reshapeWall(wall, stationaryPoint, movingPoint, newPoint, diagram, tool) {
|
||||
var wallParts = wall.memberParts;
|
||||
var arr = [];
|
||||
var oldAngle = wall.rotateObject.angle;
|
||||
wallParts.iterator.each(function (part) { arr.push(part); });
|
||||
// remember the distance each wall part's location was from the stationary point; store these in a Map
|
||||
var distancesMap = new go.Map(/*"string", "number"*/);
|
||||
var closestPart = null; var closestDistance = Number.MAX_VALUE;
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var part = arr[i];
|
||||
var distanceToStationaryPt = Math.sqrt(part.location.distanceSquaredPoint(stationaryPoint));
|
||||
distancesMap.add(part.data.key, distanceToStationaryPt);
|
||||
// distanceToMovingPt is determined by whichever endpoint of the wallpart is closest to movingPoint
|
||||
var endpoints = getWallPartEndpoints(part);
|
||||
var distanceToMovingPt = Math.min(Math.sqrt(endpoints[0].distanceSquaredPoint(movingPoint)),
|
||||
Math.sqrt(endpoints[1].distanceSquaredPoint(movingPoint)));
|
||||
// find and store the closest wallPart to the movingPt
|
||||
if (distanceToMovingPt < closestDistance) {
|
||||
closestDistance = distanceToMovingPt;
|
||||
closestPart = part;
|
||||
}
|
||||
}
|
||||
// if the proposed newPoint would make it so the wall would reshape past closestPart, set newPoint to the edge point of closest part
|
||||
if (closestPart !== null) {
|
||||
var loc = closestPart.location;
|
||||
var partLength = closestPart.data.length;
|
||||
var angle = oldAngle;
|
||||
var point1 = new go.Point((loc.x + (partLength / 2)), loc.y);
|
||||
var point2 = new go.Point((loc.x - (partLength / 2)), loc.y);
|
||||
point1.offset(-loc.x, -loc.y).rotate(angle).offset(loc.x, loc.y);
|
||||
point2.offset(-loc.x, -loc.y).rotate(angle).offset(loc.x, loc.y);
|
||||
var distance1 = Math.sqrt(stationaryPoint.distanceSquaredPoint(point1));
|
||||
var distance2 = Math.sqrt(stationaryPoint.distanceSquaredPoint(point2));
|
||||
|
||||
var minLength; var newLoc;
|
||||
if (distance1 > distance2) {
|
||||
minLength = distance1;
|
||||
newLoc = point1;
|
||||
} else {
|
||||
minLength = distance2;
|
||||
newLoc = point2;
|
||||
}
|
||||
|
||||
var testDistance = Math.sqrt(stationaryPoint.distanceSquaredPoint(newPoint));
|
||||
if (testDistance < minLength) newPoint = newLoc;
|
||||
}
|
||||
// reshape the wall
|
||||
if (movingPoint === wall.data.endpoint) diagram.model.setDataProperty(wall.data, "endpoint", newPoint);
|
||||
else diagram.model.setDataProperty(wall.data, "startpoint", newPoint);
|
||||
diagram.updateWall(wall);
|
||||
// calculate the new angle offset
|
||||
var newAngle = wall.rotateObject.angle;
|
||||
var angleOffset = newAngle - oldAngle;
|
||||
// for each wallPart, maintain relative distance from the stationaryPoint
|
||||
distancesMap.iterator.each(function (kvp) {
|
||||
var wallPart = diagram.findPartForKey(kvp.key);
|
||||
var distance = kvp.value;
|
||||
var wallLength = Math.sqrt(stationaryPoint.distanceSquaredPoint(movingPoint));
|
||||
var newLoc = new go.Point(stationaryPoint.x + ((distance / wallLength) * (movingPoint.x - stationaryPoint.x)),
|
||||
stationaryPoint.y + ((distance / wallLength) * (movingPoint.y - stationaryPoint.y)));
|
||||
wallPart.location = newLoc;
|
||||
wallPart.angle = (wallPart.angle + angleOffset) % 360;
|
||||
});
|
||||
}
|
||||
|
||||
// Show if the wall (at the adjustment handle being moved) lines up with other wall edges
|
||||
WallReshapingTool.prototype.showMatches = function () {
|
||||
//if (!(document.getElementById('wallGuidelinesCheckbox').checked)) return;
|
||||
var diagram = this.diagram;
|
||||
if (!diagram.model.modelData.preferences.showWallGuidelines) return;
|
||||
var tool = this;
|
||||
var wall = this.adornedShape.part;
|
||||
var comparePt;
|
||||
if (this.handle.name === 'sPt') comparePt = wall.data.startpoint;
|
||||
else comparePt = wall.data.endpoint;
|
||||
|
||||
// the wall attached to the handle being manipulated
|
||||
var hWall = this.adornedShape.part;
|
||||
|
||||
// delete any old guideline points (these are used to show guidelines, must be cleared before a new guideline can be shown)
|
||||
var glPoints = diagram.findNodesByExample({ category: 'GLPointNode' });
|
||||
diagram.removeParts(glPoints, true);
|
||||
|
||||
var walls = this.diagram.findNodesByExample({ category: 'WallGroup' });
|
||||
walls.iterator.each(function (w) {
|
||||
if (w.data.key != hWall.data.key) {
|
||||
var shape = w.findObject('SHAPE');
|
||||
var geo = shape.geometry;
|
||||
|
||||
var pt1 = w.data.startpoint;
|
||||
var pt2 = w.data.endpoint;
|
||||
|
||||
tool.checkPtLinedUp(pt1, comparePt.x, pt1.x, comparePt);
|
||||
tool.checkPtLinedUp(pt1, comparePt.y, pt1.y, comparePt);
|
||||
tool.checkPtLinedUp(pt2, comparePt.x, pt2.x, comparePt);
|
||||
tool.checkPtLinedUp(pt2, comparePt.y, pt2.y, comparePt);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* Static function -- checks if there exists a horiontal or vertical line (decided by 'coord' parameter) between pt and compare pt
|
||||
* if so, draws a link between the two, letting the user know the wall they're reshaping lines up with another's edge
|
||||
* @param {Point} pt
|
||||
* @param {Number} comparePtCoord
|
||||
* @param {Number} ptCoord
|
||||
* @param {Point} comparePt
|
||||
*/
|
||||
WallReshapingTool.prototype.checkPtLinedUp = function (pt, comparePtCoord, ptCoord, comparePt) {
|
||||
function makeGuideLinePoint() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Node, "Spot", { locationSpot: go.Spot.TopLeft, locationObjectName: "SHAPE", desiredSize: new go.Size(1, 1), },
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
$(go.Shape, { stroke: null, strokeWidth: 1, name: "SHAPE", fill: "black", })
|
||||
);
|
||||
}
|
||||
|
||||
function makeGuideLineLink() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Link,
|
||||
$(go.Shape, { stroke: "black", strokeWidth: 2, name: 'SHAPE', },
|
||||
new go.Binding("strokeWidth", "width"),
|
||||
new go.Binding('stroke', 'stroke')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
var diagram = this.diagram;
|
||||
var errorMargin = Math.abs(comparePtCoord - ptCoord);
|
||||
if (errorMargin < 2) {
|
||||
|
||||
var data = { category: "GLPointNode", loc: go.Point.stringify(pt) };
|
||||
var data2 = { key: 'movingPt', category: "GLPointNode", loc: go.Point.stringify(comparePt) };
|
||||
var data3 = { key: 'guideline', category: 'guideLine', from: 'movingPt', to: data.key, stroke: 'blue' };
|
||||
var GLPoint1 = makeGuideLinePoint();
|
||||
var GLPoint2 = makeGuideLinePoint();
|
||||
var GLLink = makeGuideLineLink();
|
||||
diagram.add(GLPoint1);
|
||||
diagram.add(GLPoint2);
|
||||
diagram.add(GLLink);
|
||||
|
||||
GLPoint1.data = data;
|
||||
GLPoint2.data = data2;
|
||||
GLLink.data = data3;
|
||||
GLLink.fromNode = GLPoint1;
|
||||
GLLink.toNode = GLPoint2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
/********************************************** MAIN CONTENT STYLING ***********************************************/
|
||||
body {font-family: Arial; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none;}
|
||||
canvas:focus {outline:0;} /*don't outline palette and diagram when they are focused*/
|
||||
p, h1,h2,h3,h4,h5,h6{cursor: default}
|
||||
/*Style placeholder text to be black on all browsers*/
|
||||
::-webkit-input-placeholder { color: black; }
|
||||
::-moz-placeholder { color: black; }
|
||||
:-ms-input-placeholder { color: black; }
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input{
|
||||
padding: 0px;
|
||||
border: 1px solid gray;
|
||||
}
|
||||
.unitsBox{ /*A special input that displays units*/
|
||||
text-align: right;
|
||||
width: 20px;
|
||||
border: 1px solid gray;
|
||||
border-left: 0px;
|
||||
}
|
||||
|
||||
#notesTextarea {
|
||||
resize: none;
|
||||
font-family: Arial;
|
||||
}
|
||||
|
||||
label{ font-size: 9pt; color: #757575;}
|
||||
|
||||
.paletteLabel {
|
||||
text-align: center;
|
||||
font: bold 12px sans-serif;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
#furnitureSearchBar{ width: 100%; border: none; }
|
||||
#furnitureSearchBar::-webkit-input-placeholder { color: gray; }
|
||||
#furnitureSearchBar::-moz-placeholder { color: gray; }
|
||||
#furnitureSearchBar:-ms-input-placeholder { color: gray; }
|
||||
#furniturePaletteDiv {width: inherit; height: 300px; background: #e2e2e2;}
|
||||
#wallPartsPaletteDiv {width: inherit; height: 150px; background: #e2e2e2;}
|
||||
|
||||
#myPaletteWindow{
|
||||
height: inherit;
|
||||
top:12%;
|
||||
left:.5%;
|
||||
}
|
||||
|
||||
#palettes {
|
||||
height: 100%;
|
||||
width: 300px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#myFloorplanDiv {
|
||||
width: 100%;
|
||||
background-color: #DAE4E4;
|
||||
border: 1px solid gray;
|
||||
height: 85vh;
|
||||
}
|
||||
|
||||
#currentFile {
|
||||
background-color: #4b545f;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#diagramHelpDiv {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
border: 1px solid gray;
|
||||
background-color: #4b545f;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/****************************************************************** GENERAL DRAGGABLE WINDOWS STYLING ********************************************************/
|
||||
|
||||
.draggable {
|
||||
border: 1px solid gray;
|
||||
background-color: #e2e2e2;
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
width: 300px;
|
||||
height: 200px;
|
||||
z-index: 10;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#optionsWindow {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.windowButtons{
|
||||
float: right;
|
||||
border: none;
|
||||
font: bold 12px sans-serif;
|
||||
}
|
||||
|
||||
.handle {
|
||||
background-color: #4b545f;
|
||||
text-align: center;
|
||||
font: bold 12px sans-serif;
|
||||
color: white;
|
||||
cursor: move;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mySavedFiles {width:75%;}
|
||||
|
||||
/*Selection Info Window specific styling*/
|
||||
|
||||
#selectionInfoTextDiv p {margin: 0; padding: 0;} /*used only when no node is selected*/
|
||||
#selectionInfoTextDiv #name {margin: 3px;}
|
||||
#selectionInfoTextDiv .nameNotesInput {
|
||||
width: 80%;
|
||||
}
|
||||
#selectionInfoTextDiv .dimensionsInput {width: 85%; border-right: 0px;}
|
||||
.data {font-size: 10pt;}
|
||||
.clickable {cursor: pointer;}
|
||||
.selectedKey {color: dodgerblue;}
|
||||
|
||||
/*-- set border box on all elements inside the grid*/
|
||||
.grid-container * {box-sizing: border-box;}
|
||||
|
||||
.row:before, .row::after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
[class*='col-'] {
|
||||
float: left;
|
||||
min-height: 1px;
|
||||
width: 16.66%;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.col-1 {width: 100%;}
|
||||
.col-2 {width: 50%;}
|
||||
.col-3 {width: 33.33%;}
|
||||
.col-4 {width: 25%;}
|
||||
.col-5 {width: 20%;}
|
||||
.col-6 {width: 16.66%;}
|
||||
/************************************************************* NAV BAR STYLING *************************************************/
|
||||
|
||||
nav {background: linear-gradient(#efefef ,#bbbbbb );}
|
||||
|
||||
nav ul { /*file menus and tool menus*/
|
||||
list-style: none;
|
||||
position: relative;
|
||||
display: flex;
|
||||
margin: 0; padding: 0;
|
||||
}
|
||||
|
||||
nav ul li { /*menu tabs*/
|
||||
float: left;
|
||||
transition-property: background;
|
||||
transition-duration: 0.3s;
|
||||
}
|
||||
nav ul li:hover {
|
||||
transition-delay: 0.1s, 0.1s;
|
||||
background: #4f5964;
|
||||
}
|
||||
|
||||
nav ul li a { /*menu tabs text*/
|
||||
display: block;
|
||||
padding-left: 25px; padding-right: 25px; padding-top: 2px;
|
||||
color: #757575; text-decoration: none;
|
||||
transition-property: background, color;
|
||||
transition-duration: 0.4s, 0.4s;
|
||||
}
|
||||
nav ul li:hover a {color: #fff;}
|
||||
|
||||
nav ul ul { /*drop down menus, general*/
|
||||
display: block;
|
||||
z-index: 20; /*in front of everything, even .draggables*/
|
||||
visibility: hidden; opacity: 0;
|
||||
transition-property: opacity;
|
||||
transition-duration: 0.3s;
|
||||
background: #5f6975; padding: 0;
|
||||
position: absolute; top: 100%;
|
||||
/* width: 220%; /*band aid fix for menu width -- TODO*/
|
||||
}
|
||||
nav ul li:hover > ul { /*drop down menus, visible*/
|
||||
visibility: visible; opacity: 1;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
nav ul ul li { /*drop down menu items*/
|
||||
border-top: 1px solid #6b727c;
|
||||
border-bottom: 1px solid #575f6a;
|
||||
position: relative; float: none;
|
||||
}
|
||||
nav ul ul li a { /*drown down menu items text*/
|
||||
padding: 5px;
|
||||
transition-property: background;
|
||||
transition-duration: 0.1s;
|
||||
}
|
||||
nav ul ul li a:hover {
|
||||
background: #4b545f;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
nav ul ul ul { /*drop down submenu (units menu)*/
|
||||
left: 100%; top:0; width: 100px;
|
||||
}
|
||||
|
||||
nav p { /* grid input area, wall width area, and 'Tools' all wrapped in <p> tag*/
|
||||
float: left;
|
||||
margin-bottom: 0px;
|
||||
margin-top: 3px;
|
||||
padding-left: 25px;
|
||||
padding-right: 25px;
|
||||
color: #4b545f;
|
||||
}
|
||||
nav p.shortcut {
|
||||
font-size: 9pt;
|
||||
float: right;
|
||||
color: #efefef;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.scaleItems{ /*special styling for the 'Scale' item of the 'View' menu*/
|
||||
float: left;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
width: 12.5%;
|
||||
}
|
||||
#scaleDisplay{width: 75%;}
|
||||
|
||||
#wallThicknessInput, #gridSizeInput {
|
||||
width: 50px;
|
||||
float: left;
|
||||
border-right: 0px;
|
||||
}
|
||||
#gridSizeInput { margin-left: 10%; }
|
||||
#wallThicknesshUnitsInput, #gridSizeUnitsInput{float: left;}
|
||||
|
||||
#wallThicknessInputLabel{ float: left; }
|
||||
#setGridButton { padding: 0; }
|
||||
|
||||
|
||||
/* Scaling for small screens, TODO*/
|
||||
@media screen and (max-device-width: 480px) {
|
||||
nav ul li ul li{ font-size: 9pt; }
|
||||
}
|
||||
|
||||
/*Icons*/
|
||||
|
||||
#wallBuildingButton {background: url(icons/wallBuildingTool.png);}
|
||||
#draggingButton {background: url(icons/selectionTool.png);}
|
||||
#wallWidthBox { visibility: hidden; display: none;} /*box for setting wall width; invisible when Wall Tool not active*/
|
||||
|
||||
/* jQuery UI specific stylings */
|
||||
|
||||
.ui-accordion .ui-accordion-content, .ui-accordion .ui-accordion-icons { padding: 0px; }
|
||||
.ui-widget * { outline: none; }
|
||||
.ui-widget-content { border: none; }
|
||||
@@ -0,0 +1,279 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta charset="utf-8" />
|
||||
<title>Floor Planner</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation -->
|
||||
<script src="../../release/go.js"></script>
|
||||
<script src="FloorPlanner-WallBuildingTool.js"></script>
|
||||
<script src="FloorPlanner-WallReshapingTool.js"></script>
|
||||
<script src="FloorPlanner-Templates-General.js"></script>
|
||||
<script src="FloorPlanner-Templates-Furniture.js"></script>
|
||||
<script src="FloorPlanner-Templates-Walls.js"></script>
|
||||
<script src="Floorplan.js"></script>
|
||||
<script src="FloorplanFilesystem.js"></script>
|
||||
<script src="FloorplanUI.js"></script>
|
||||
<script src="Floorplanner-Constants.js"></script>
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/jquery-ui.min.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/themes/smoothness/jquery-ui.css" />
|
||||
<link rel="stylesheet" type="text/css" href="FloorPlanner.css" />
|
||||
</head>
|
||||
<body id="body" onload="init()">
|
||||
<div id="currentFile">(Unsaved File)</div>
|
||||
<!-- File menus-->
|
||||
<!-- The Nav Bar / Windows are specific to a certain floorplan and its classes -- unlike most of the Floorplanner code, which is generic -->
|
||||
<nav>
|
||||
<ul id="fileMenus">
|
||||
<li>
|
||||
<a href="#">File</a>
|
||||
<ul>
|
||||
<li><a href="#" onclick="filesystem.newFloorplan()">New <p class="shortcut">(Ctrl + D)</p></a></li>
|
||||
<li><a href="#" onclick="filesystem.showOpenWindow()">Open... <p class="shortcut">(Ctrl + O)</p></a></li>
|
||||
<li><a href="#" onclick="filesystem.saveFloorplan()">Save <p class="shortcut">(Ctrl + S)</p></a></li>
|
||||
<li><a href="#" onclick="filesystem.saveFloorplanAs()">Save As...</a></li>
|
||||
<li><a href="#" onclick="filesystem.showRemoveWindow()">Remove... <p class="shortcut">(Ctrl + R)</p></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">Edit</a>
|
||||
<ul>
|
||||
<li><a href="#" onclick="myFloorplan.commandHandler.undo()">Undo <p class="shortcut">(Ctrl + Z)</p></a></li>
|
||||
<li><a href="#" onclick="myFloorplan.commandHandler.redo()">Redo <p class="shortcut">(Ctrl + Y)</p></a></li>
|
||||
<li><a href="#" onclick="myFloorplan.commandHandler.copySelection()">Copy <p class="shortcut">(Ctrl + C)</p></a></li>
|
||||
<li><a href="#" onclick="myFloorplan.commandHandler.cutSelection()">Cut <p class="shortcut">(Ctrl + X)</p></a></li>
|
||||
<li><a href="#" onclick="myFloorplan.commandHandler.pasteSelection()">Paste <p class="shortcut">(Ctrl + V)</p></a></li>
|
||||
<li><a href="#" onclick="myFloorplan.commandHandler.deleteSelection()">Delete <p class="shortcut">(Del)</p></a></li>
|
||||
<li><a href="#" onclick="myFloorplan.commandHandler.selectAll()">Select All <p class="shortcut">(Ctrl + A)</p></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">View</a>
|
||||
<ul>
|
||||
<li><a href="#" onclick="ui.hideShow('diagramHelpDiv')" id="diagramHelpDivButton">Hide Diagram Help <p class="shortcut"> (Ctrl + H)</p></a></li>
|
||||
<li><a href="#" onclick="ui.hideShow('selectionInfoWindow')" id="selectionInfoWindowButton">Show Selection Help <p class="shortcut"> (Ctrl + I)</p></a></li>
|
||||
<li><a href="#" onclick="ui.hideShow('myPaletteWindow')" id="myPaletteWindowButton">Hide Palettes <p class="shortcut"> (Ctrl + P)</p></a></li>
|
||||
<li><a href="#" onclick="ui.hideShow('myOverviewWindow')" id="myOverviewWindowButton">Show Overview <p class="shortcut"> (Ctrl + E)</p></a></li>
|
||||
<li><a href="#" onclick="ui.hideShow('statisticsWindow')" id="statisticsWindowButton">Show Statistics <p class="shortcut"> (Ctrl + G)</p></a></li>
|
||||
<li>
|
||||
<a href="#" id="optionsWindowButton" onclick="ui.hideShow('optionsWindow')">Show Options <p class="shortcut"> (Ctrl + B)</p> </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="scaleItems" onclick="ui.adjustScale('-')">-</a>
|
||||
<a href="#" class="scaleItems" id="scaleDisplay">Scale: 100%</a>
|
||||
<a href="#" class="scaleItems" onclick="ui.adjustScale('+')">+</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><button class="setBehavior toolIcons" id="wallBuildingButton" title="Wall Building Tool (Ctrl + 1)" onclick="ui.setBehavior('wallBuilding')"> </button></li>
|
||||
<li><button class="setBehavior toolIcons" id="draggingButton" title="Select/Move Tool (Ctrl + 2)" onclick="ui.setBehavior('dragging')"> </button></li>
|
||||
<p id="wallThicknessBox">
|
||||
<label for="wallThicknessInput" id="wallThicknessInputLabel">Wall Thickness:</label>
|
||||
<input id="wallThicknessInput" class="unitsInput" placeholder="width" />
|
||||
<input id="wallThicknessUnitsInput" class="unitsBox" value="cm" disabled="disabled" />
|
||||
</p>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- Floorplan / Help bar -->
|
||||
<div id="myFloorplanDiv"></div>
|
||||
<div id="diagramHelpDiv" style="visibility: visible">
|
||||
<div id="diagramHelpTextDiv">
|
||||
<p>Drag a node to the Diagram or select the Wall Drawing Tool (Ctrl + 1) to begin</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Floating windows-->
|
||||
<div id="myPaletteWindow" style="visibility: visible" class="draggable ui-draggable">
|
||||
<div id="myPaletteWindowHandle" class="handle ui-draggable-handle">Palettes<button id="myPaletteClose" class="windowButtons clickable" onclick="ui.hideShow('myPaletteWindow')">X</button></div>
|
||||
<div id="palettes">
|
||||
<!-- jQuery accordion -->
|
||||
<h3 class="paletteLabel">Furniture</h3>
|
||||
<div>
|
||||
<input id="furnitureSearchBar" placeholder="Search Furniture" oninput="ui.searchFurniture()" />
|
||||
<div id="furniturePaletteDiv" class="paletteClass"></div>
|
||||
</div>
|
||||
<h3 class="paletteLabel">Wall Parts</h3>
|
||||
<div id="wallPartsPaletteDiv" class="paletteClass"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="openDocument" style="visibility: hidden;" class="draggable ui-draggable">
|
||||
<div id="openDocumentHandle" class="handle ui-draggable-handle">Open File<button id="openDocumentClose" class="windowButtons clickable" onclick="ui.hideShow('openDocument')">X</button></div>
|
||||
<div id="openText" class="elementText">Choose file to open...</div>
|
||||
<select id="filesToOpen" class="mySavedFiles"></select>
|
||||
<br />
|
||||
<button id="openBtn" class="elementBtn" type="button" onclick="filesystem.loadFloorplan()">Open</button>
|
||||
</div>
|
||||
|
||||
<div id="removeDocument" style="visibility: hidden;" class="draggable ui-draggable">
|
||||
<div id="removeDocumentHandle" class="handle">Delete File <button id="removeDocumentClose" class="windowButtons clickable" onclick="ui.hideShow('removeDocument')">X</button></div>
|
||||
<div id="removeText" class="elementText">Choose file to remove...</div>
|
||||
<select id="filesToRemove" class="mySavedFiles"></select>
|
||||
<br />
|
||||
<button id="removeBtn" class="elementBtn" type="button" onclick="filesystem.removeFloorplan()" style="margin-left:70px">Remove</button>
|
||||
</div>
|
||||
|
||||
<div id="myOverviewWindow" style="visibility: hidden;" class="draggable ui-draggable">
|
||||
<div id="myOverviewWindowHandle" class="handle ui-draggable-handle">Overview<button id="myOverviewClose" title="Close" class="windowButtons clickable" onclick="ui.hideShow('myOverviewWindow')">X</button></div>
|
||||
<div id="myOverviewDiv" style="height:187px; width: 300px;"></div>
|
||||
</div>
|
||||
|
||||
<div id="selectionInfoWindow" style="visibility: hidden" class="draggable ui-draggable">
|
||||
<div id="selectionInfoWindowHandle" class="handle ui-draggable-handle">Selection Info <button id="selectionInfoClose" class="windowButtons clickable" onclick="ui.hideShow('selectionInfoWindow')">X</button></div>
|
||||
<div id="selectionInfoTextDiv" class="grid-container">Nothing selected</div>
|
||||
</div>
|
||||
|
||||
<div id="optionsWindow" style="visibility: hidden" class="draggable ui-draggable">
|
||||
<div id="optionsWindowHandle" class="handle ui-draggable-handle">Options <button id="optionsWindowClose" class="windowButtons clickable" onclick="ui.hideShow('optionsWindow')">X</button></div>
|
||||
Units
|
||||
<div id="unitsRow" class="row data">
|
||||
<form id="unitsForm" onchange="ui.changeUnits()">
|
||||
<div class="col-4">
|
||||
<input type="radio" name="units" id="centimeters" checked />cm
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<input type="radio" name="units" id="meters" /> m
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<input type="radio" name="units" id="inches" />in
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<input type="radio" name="units" id="feet" />ft
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
Grid
|
||||
<div id="gridRow" class="row">
|
||||
<div class="col-2">
|
||||
<input id="gridSizeInput" placeholder="grid size" class="unitsInput" onchange="ui.changeGridSize()" />
|
||||
<input id="gridSizeUnitsInput" class="unitsBox" value="cm" disabled />
|
||||
<!--<button id="setGridButton" onclick="ui.changeGridSize()">Set Grid</button>-->
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<input type="checkbox" id="showGridCheckbox" onchange="ui.checkboxChanged('showGridCheckbox', myFloorplan)" checked />Show Grid
|
||||
</div>
|
||||
</div>
|
||||
<div id="gridRow" class="row">
|
||||
<div class="col-1">
|
||||
<label for="unitsConversionFactorInput">Units/1px (at scale 100%)</label>
|
||||
<input id="unitsConversionFactorInput" placeholder="2" onchange="ui.changeUnitsConversionFactor()" />
|
||||
</div>
|
||||
</div>
|
||||
Preferences
|
||||
<div id="miscRow" class="row data">
|
||||
<div class="col-1">
|
||||
<input type="checkbox" id="gridSnapCheckbox" onchange="ui.checkboxChanged('gridSnapCheckbox', myFloorplan)" checked />Grid Snap
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<input type="checkbox" id="wallGuidelinesCheckbox" onchange="ui.checkboxChanged('wallGuidelinesCheckbox', myFloorplan)" checked /> Show Wall Guidelines
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<input type="checkbox" id="wallLengthsCheckbox" onchange="ui.checkboxChanged('wallLengthsCheckbox', myFloorplan)" checked /> Show Wall Lengths
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<input type="checkbox" id="wallAnglesCheckbox" onchange="ui.checkboxChanged('wallAnglesCheckbox', myFloorplan)" checked /> Show Wall Angles
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<input type="checkbox" id="smallWallAnglesCheckbox" onchange="ui.checkboxChanged('smallWallAnglesCheckbox', myFloorplan)" unchecked /> Show Only Small Wall Angles
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="statisticsWindow" style="visibility: hidden" class="draggable ui-draggable">
|
||||
<div id="statisticsWindowHandle" class="handle ui-draggable-handle">Floor Plan Statistics <button id="statisticsWindowClose" class="windowButtons clickable" onclick="ui.hideShow('statisticsWindow')">X</button></div>
|
||||
Stats
|
||||
<div id="statisticsWindowTextDiv" class="grid-container"></div>
|
||||
</div>
|
||||
|
||||
<p>This Floorplanner samples makes use of multiple classes to allow for users to build, edit, save, and load feature-rich Floorplans. To start, build walls with the Wall Building Tool (Ctrl + 1), or drag furniture from Palettes onto the Floorplan area. The help at the bottom of the Floorplan area is context-specific and should aid in providing tips on how to better use this software.</p>
|
||||
<p>This sample uses the following files.</p>
|
||||
<ul>
|
||||
<li><a href="Floorplan.js">Floorplan.js</a> - A special kind of <a href="https://gojs.net/latest/api/symbols/Diagram.html">Diagram</a> with rules and listeners that help with floorplanning</li>
|
||||
<li><a href="FloorplanFilesystem.js">FloorplanFilesystem.js</a> - A class to handle saving and loading floorplans (through localstorage). Linked to a Floorplan instance</li>
|
||||
<li><a href="Floorplanner-WallBuildingTool.js">WallBuildingTool.js</a> - For constructing new walls</li>
|
||||
<li><a href="Floorplanner-WallReshapingTool.js">WallReshapingTool.js</a> - For reshaping walls from their endpoints</li>
|
||||
<li><a href="FloorplanUI.js">FloorplanUI.js</a> - A class to handle GUI interaction as events on the Floorplan take place. Linked to a Floorplan instance</li>
|
||||
</ul>
|
||||
<p> In addition, three files are used to store Flooplanner-specific <a href="https://gojs.net/latest/api/symbols/GraphObject.html">Graph Object</a> templates.</p>
|
||||
<ul>
|
||||
<li><a href="Floorplanner-Templates-General.js">Floorplanner-Templates-General.js</a> - Contains templates for Context Menu, Diagram, Default Group, AngleNode, DimensionLink, PointNode, all all their dependencies</li>
|
||||
<li><a href="Floorplanner-Templates-Furniture.js">Floorplanner-Templates-Furniture.js</a> - Contains templates for Default Node (Furniture), MultiPurpose Node, and all their dependencies</li>
|
||||
<li><a href="Floorplanner-Templates-Walls.js">Floorplanner-Templates-Walls.js</a> - Contains templates for Wall Group, Palette Wall Node, Window Node, Door Node, and all their dependencies</li>
|
||||
</ul>
|
||||
|
||||
<script>
|
||||
|
||||
// enables draggable windows (jQuery), defining their handles and behavior (most recently dragged window stacks over other windows)
|
||||
$(function () {
|
||||
$("#palettes").accordion({
|
||||
activate: function (event, ui) {
|
||||
furniturePalette.requestUpdate();
|
||||
wallPartsPalette.requestUpdate();
|
||||
}
|
||||
});
|
||||
$("#openDocument").draggable({ handle: "#openDocumentHandle", stack: ".draggable", containment: 'window', scroll: false });
|
||||
$('#optionsWindow').draggable({ handle: "#optionsWindowHandle", stack: ".draggable", containment: 'window', scroll: false });
|
||||
$("#removeDocument").draggable({ handle: "#removeDocumentHandle", stack: ".draggable", containment: 'window', scroll: false });
|
||||
$("#myOverviewWindow").draggable({ handle: "#myOverviewWindowHandle", stack: ".draggable", containment: 'window', scroll: false });
|
||||
$('#statisticsWindow').draggable({ handle: "#statisticsWindowHandle", stack: ".draggable", containment: 'window', scroll: false });
|
||||
$("#selectionInfoWindow").draggable({ handle: "#selectionInfoWindowHandle", stack: ".draggable", containment: 'window', scroll: false });
|
||||
$("#myPaletteWindow").draggable({ handle: "#myPaletteWindowHandle", stack: ".draggable", containment: 'window', scroll: false });
|
||||
$("#myPaletteWindow").resize(function () {
|
||||
furniturePalette.requestUpdate();
|
||||
wallPartsPalette.requestUpdate();
|
||||
});
|
||||
});
|
||||
|
||||
function init() {
|
||||
// Floorplan
|
||||
myFloorplan = new Floorplan("myFloorplanDiv");
|
||||
|
||||
// 'FILESYSTEM_UI_STATE', 'GUI_STATE' defined in Floorplanner-Constants.js
|
||||
filesystem = new FloorplanFilesystem(myFloorplan, FILESYSTEM_UI_STATE);
|
||||
ui = new FloorplanUI(myFloorplan, "ui", "myFloorplan", GUI_STATE);
|
||||
|
||||
// Overview
|
||||
var $ = go.GraphObject.make;
|
||||
myOverview = $(go.Overview, "myOverviewDiv", { observed: myFloorplan, maxScale: 0.5 });
|
||||
|
||||
furniturePalette = $(go.Palette, "furniturePaletteDiv");
|
||||
furniturePalette.nodeTemplateMap = myFloorplan.nodeTemplateMap;
|
||||
furniturePalette.model = new go.GraphLinksModel(FURNITURE_NODE_DATA_ARRAY);
|
||||
wallPartsPalette = $(go.Palette, "wallPartsPaletteDiv");
|
||||
wallPartsPalette.nodeTemplateMap = myFloorplan.nodeTemplateMap;
|
||||
wallPartsPalette.model = new go.GraphLinksModel(WALLPARTS_NODE_DATA_ARRAY);
|
||||
|
||||
// enable hotkeys
|
||||
var body = document.getElementById('body');
|
||||
body.addEventListener("keydown", function (e) {
|
||||
var keynum = e.which;
|
||||
if (e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
switch (keynum) {
|
||||
case 83: filesystem.saveFloorplan(); break; // ctrl + s
|
||||
case 79: filesystem.showOpenWindow(); break; // ctrl + o
|
||||
case 68: e.preventDefault(); filesystem.newFloorplan(); break; // ctrl + d
|
||||
case 82: filesystem.showRemoveWindow(); break; // ctrl + r
|
||||
case 49: ui.setBehavior('wallBuilding', myFloorplan); break; // ctrl + 1
|
||||
case 50: ui.setBehavior('dragging', myFloorplan); break; // ctrl + 2
|
||||
case 72: ui.hideShow('diagramHelpDiv'); break; // ctrl + h
|
||||
case 73: ui.hideShow('selectionInfoWindow'); break; // ctrl + i
|
||||
case 80: ui.hideShow('myPaletteWindow'); break; // ctrl + p
|
||||
case 69: ui.hideShow('myOverviewWindow'); break; // ctrl + e
|
||||
case 66: ui.hideShow('optionsWindow'); break; // ctrl + b
|
||||
case 71: ui.hideShow('statisticsWindow'); break; // ctrl + g
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// default model data stored in Floorplanner-Constants.js
|
||||
myFloorplan.floorplanFilesystem.loadFloorplanFromModel(DEFAULT_MODEL_DATA);
|
||||
ui.setBehavior("dragging");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,813 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Floorplan Class
|
||||
* A Floorplan is a Diagram with special rules
|
||||
* Dependencies: Floorplanner-Templates-General.js, Floorplanner-Templates-Furniture.js, Floorplanner-Templates-Walls.js
|
||||
*/
|
||||
|
||||
/*
|
||||
* Floorplan Constructor
|
||||
* @param {HTMLDivElement|string} div A reference to a div or its ID as a string
|
||||
*/
|
||||
function Floorplan(div) {
|
||||
|
||||
/*
|
||||
* Floor Plan Setup:
|
||||
* Initialize Floor Plan, Floor Plan Listeners, Floor Plan Overview
|
||||
*/
|
||||
|
||||
go.Diagram.call(this, div);
|
||||
// By default there is no filesystem / UI control for a floorplan, though they can be added
|
||||
this._floorplanFilesystem = null;
|
||||
this._floorplanUI = null;
|
||||
|
||||
// When a FloorplanPalette instance is made, it is automatically added to a Floorplan's "palettes" property
|
||||
this._palettes = [];
|
||||
|
||||
// Point Nodes, Dimension Links, Angle Nodes on the Floorplan (never in model data)
|
||||
this._pointNodes = new go.Set(/*go.Node*/);
|
||||
this._dimensionLinks = new go.Set(/*go.Link*/);
|
||||
this._angleNodes = new go.Set(/*go.Node*/);
|
||||
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
this.allowLink = false;
|
||||
this.undoManager.isEnabled = true;
|
||||
this.layout.isOngoing = false;
|
||||
this.model = $(go.GraphLinksModel, {
|
||||
modelData: {
|
||||
"units": "centimeters",
|
||||
"unitsAbbreviation": "cm",
|
||||
"unitsConversionFactor": 2,
|
||||
"gridSize": 10,
|
||||
"wallThickness": 5,
|
||||
"preferences": {
|
||||
showWallGuidelines: true,
|
||||
showWallLengths: true,
|
||||
showWallAngles: true,
|
||||
showOnlySmallWallAngles: true,
|
||||
showGrid: true,
|
||||
gridSnap: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.grid = $(go.Panel, "Grid",
|
||||
{ gridCellSize: new go.Size(this.model.modelData.gridSize, this.model.modelData.gridSize), visible: true },
|
||||
$(go.Shape, "LineH", { stroke: "lightgray" }),
|
||||
$(go.Shape, "LineV", { stroke: "lightgray" }));
|
||||
this.contextMenu = makeContextMenu();
|
||||
this.commandHandler.canGroupSelection = true;
|
||||
this.commandHandler.canUngroupSelection = true;
|
||||
this.commandHandler.archetypeGroupData = { isGroup: true };
|
||||
|
||||
// When floorplan model is changed, update stats in Statistics Window
|
||||
this.addModelChangedListener(function (e) {
|
||||
if (e.isTransactionFinished) {
|
||||
// find floorplan changed
|
||||
var floorplan = null;
|
||||
if (e.object !== null) {
|
||||
e.object.changes.each(function (change) {
|
||||
if (change.diagram instanceof Floorplan) floorplan = change.diagram;
|
||||
});
|
||||
}
|
||||
if (floorplan) {
|
||||
if (floorplan.floorplanUI) floorplan.floorplanUI.updateStatistics();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// When floorplan is modified, change document title to include a *
|
||||
this.addDiagramListener("Modified", function (e) {
|
||||
var floorplan = e.diagram;
|
||||
if (floorplan.floorplanFilesystem) {
|
||||
var currentFile = document.getElementById(floorplan.floorplanFilesystem.state.currentFileId);
|
||||
if (currentFile) {
|
||||
var idx = currentFile.textContent.indexOf("*");
|
||||
if (floorplan.isModified) {
|
||||
if (idx < 0) currentFile.textContent = currentFile.textContent + "*";
|
||||
}
|
||||
else {
|
||||
if (idx >= 0) currentFile.textContent = currentFile.textContent.substr(0, idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// if a wall is copied, update its geometry
|
||||
this.addDiagramListener("SelectionCopied", function (e) {
|
||||
e.diagram.selection.iterator.each(function(part){
|
||||
if (part.category == "WallGroup") {
|
||||
e.diagram.updateWall(part);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// If floorplan scale has been changed update the 'Scale' item in the View menu
|
||||
this.addDiagramListener("ViewportBoundsChanged", function (e) {
|
||||
var floorplan = e.diagram;
|
||||
if (floorplan.floorplanUI) {
|
||||
var scaleEl = document.getElementById(floorplan.floorplanUI.state.scaleDisplayId);
|
||||
if (scaleEl) scaleEl.innerHTML = "Scale: " + (e.diagram.scale * 100).toFixed(2) + "%";
|
||||
}
|
||||
});
|
||||
|
||||
// If a node has been dropped onto the Floorplan from a Palette...
|
||||
this.addDiagramListener("ExternalObjectsDropped", function (e) {
|
||||
var garbage = [];
|
||||
var paletteWallNodes = [];
|
||||
var otherNodes = [];
|
||||
e.diagram.selection.iterator.each(function(node){
|
||||
// Event 1: handle a drag / drop of a wall node from the Palette (as opposed to wall construction via WallBuildingTool)
|
||||
if (node.category === "PaletteWallNode") {
|
||||
paletteWallNodes.push(node);
|
||||
}
|
||||
if (e.diagram.floorplanUI) {
|
||||
otherNodes.push(node);
|
||||
}
|
||||
});
|
||||
for (var i in paletteWallNodes) {
|
||||
var node = paletteWallNodes[i];
|
||||
var paletteWallNode = node;
|
||||
var endpoints = getWallPartEndpoints(paletteWallNode);
|
||||
var data = { key: "wall", category: "WallGroup", caption: "Wall", startpoint: endpoints[0], endpoint: endpoints[1], thickness: parseFloat(e.diagram.model.modelData.wallThickness), isGroup: true, notes: "" };
|
||||
e.diagram.model.addNodeData(data);
|
||||
var wall = e.diagram.findPartForKey(data.key);
|
||||
e.diagram.updateWall(wall);
|
||||
garbage.push(paletteWallNode);
|
||||
}
|
||||
for (var i in otherNodes) {
|
||||
var node = otherNodes[i];
|
||||
var floorplanUI = e.diagram.floorplanUI;
|
||||
// Event 2: Update the text of the Diagram Helper
|
||||
if (node.category === "WindowNode" || node.category === "DoorNode") floorplanUI.setDiagramHelper("Drag part so the cursor is over a wall to add this part to a wall");
|
||||
else floorplanUI.setDiagramHelper("Drag, resize, or rotate your selection (hold SHIFT for no grid-snapping)");
|
||||
// Event 3: If the select tool is not active, make it active
|
||||
if (e.diagram.toolManager.mouseDownTools.elt(0).isEnabled) floorplanUI.setBehavior('dragging', e.diagram);
|
||||
}
|
||||
for (var i in garbage) {
|
||||
e.diagram.remove(garbage[i]);
|
||||
}
|
||||
});
|
||||
|
||||
// When a wall is copied / pasted, update the wall geometry, angle, etc
|
||||
this.addDiagramListener("ClipboardPasted", function (e) {
|
||||
e.diagram.selection.iterator.each(function (node) { if (node.category === "WallGroup") e.diagram.updateWall(node); });
|
||||
});
|
||||
|
||||
// Display different help depending on selection context
|
||||
this.addDiagramListener("ChangedSelection", function (e) {
|
||||
var floorplan = e.diagram;
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.startTransaction("remove dimension links and angle nodes");
|
||||
floorplan.pointNodes.iterator.each(function (node) { e.diagram.remove(node) });
|
||||
floorplan.dimensionLinks.iterator.each(function (link) { e.diagram.remove(link) });
|
||||
|
||||
var missedDimensionLinks = []; // used only in undo situations
|
||||
floorplan.links.iterator.each(function (link) { if (link.data.category == "DimensionLink") missedDimensionLinks.push(link); });
|
||||
for (var i = 0; i < missedDimensionLinks.length; i++) {
|
||||
e.diagram.remove(missedDimensionLinks[i]);
|
||||
}
|
||||
|
||||
floorplan.pointNodes.clear();
|
||||
floorplan.dimensionLinks.clear();
|
||||
floorplan.angleNodes.iterator.each(function (node) { e.diagram.remove(node); });
|
||||
floorplan.angleNodes.clear();
|
||||
|
||||
floorplan.commitTransaction("remove dimension links and angle nodes");
|
||||
floorplan.skipsUndoManager = false;
|
||||
floorplan.updateWallDimensions();
|
||||
floorplan.updateWallAngles();
|
||||
if (floorplan.floorplanUI) {
|
||||
var floorplanUI = floorplan.floorplanUI;
|
||||
var selection = floorplan.selection;
|
||||
var node = floorplan.selection.first(); // only used if selection.count === 1
|
||||
|
||||
if (selection.count === 0) floorplan.floorplanUI.setSelectionInfo('Nothing selected');
|
||||
else if (selection.count === 1) floorplan.floorplanUI.setSelectionInfo(floorplan.selection.first());
|
||||
else floorplan.floorplanUI.setSelectionInfo('Selection: ');
|
||||
|
||||
if (selection.count === 0) floorplanUI.setDiagramHelper("Click to select a part, drag one from a Palette, or draw a wall with the Wall Tool (Ctr + 1)");
|
||||
else if (selection.count > 1) {
|
||||
var ungroupable = false;
|
||||
selection.iterator.each(function (node) { if (node.category === "WindowNode" || node.category === "DoorNode" || node.category === "WallGroup") ungroupable = true; });
|
||||
if (!ungroupable) floorplanUI.setDiagramHelper("You may group your selection with the context menu (Right Click anywhere)");
|
||||
}
|
||||
else if (node.category === "WallGroup") floorplanUI.setDiagramHelper("Drag wall endpoints or add doors and windows to the wall from the Wall Parts Palette");
|
||||
else if (selection.first().category === "WindowNode" || selection.first().category === "DoorNode") {
|
||||
if (node.containingGroup !== null) floorplanUI.setDiagramHelper("Drag and resize wall part along the wall; drag away from wall to detach");
|
||||
else floorplanUI.setDiagramHelper("Drag part so the cursor is over a wall to add this part to a wall");
|
||||
}
|
||||
else if (selection.first().category === "MultiPurposeNode") floorplanUI.setDiagramHelper("Double click on part text to revise it");
|
||||
else floorplanUI.setDiagramHelper("Drag, resize, or rotate (hold SHIFT for no snap) your selection");
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Node Templates
|
||||
* Add Default Node, Multi-Purpose Node, Window Node, Palette Wall Node, and Door Node to the Node Template Map
|
||||
* Template functions defined in FloorPlanner-Templates-* js files
|
||||
*/
|
||||
|
||||
this.nodeTemplateMap.add("", makeDefaultNode()); // Default Node (furniture)
|
||||
this.nodeTemplateMap.add("MultiPurposeNode", makeMultiPurposeNode()); // Multi-Purpose Node
|
||||
this.nodeTemplateMap.add("WindowNode", makeWindowNode()); // Window Node
|
||||
this.nodeTemplateMap.add("PaletteWallNode", makePaletteWallNode()); // Palette Wall Node
|
||||
this.nodeTemplateMap.add("DoorNode", makeDoorNode()); // Door Node
|
||||
|
||||
/*
|
||||
* Group Templates
|
||||
* Add Default Group, Wall Group to Group Template Map
|
||||
* Template functions defined in FloorPlanner-Templates-* js files
|
||||
*/
|
||||
|
||||
this.groupTemplateMap.add("", makeDefaultGroup()); // Default Group
|
||||
this.groupTemplateMap.add("WallGroup", makeWallGroup()); // Wall Group
|
||||
|
||||
/*
|
||||
* Install Custom Tools
|
||||
* Wall Building Tool, Wall Reshaping Tool
|
||||
* Tools are defined in their own FloorPlanner-<Tool>.js files
|
||||
*/
|
||||
|
||||
var wallBuildingTool = new WallBuildingTool();
|
||||
this.toolManager.mouseDownTools.insertAt(0, wallBuildingTool);
|
||||
|
||||
var wallReshapingTool = new WallReshapingTool();
|
||||
this.toolManager.mouseDownTools.insertAt(3, wallReshapingTool);
|
||||
wallBuildingTool.isEnabled = false;
|
||||
|
||||
/*
|
||||
* Tool Overrides
|
||||
*/
|
||||
|
||||
// If a wall was dragged to intersect another wall, update angle displays
|
||||
this.toolManager.draggingTool.doMouseUp = function () {
|
||||
go.DraggingTool.prototype.doMouseUp.call(this);
|
||||
this.diagram.updateWallAngles();
|
||||
this.isGridSnapEnabled = this.diagram.model.modelData.preferences.gridSnap;
|
||||
}
|
||||
|
||||
// If user holds SHIFT while dragging, do not use grid snap
|
||||
this.toolManager.draggingTool.doMouseMove = function () {
|
||||
if (this.diagram.lastInput.shift) {
|
||||
this.isGridSnapEnabled = false;
|
||||
} else this.isGridSnapEnabled = this.diagram.model.modelData.preferences.gridSnap;
|
||||
go.DraggingTool.prototype.doMouseMove.call(this);
|
||||
}
|
||||
|
||||
// When resizing, constantly update the node info box with updated size info; constantly update Dimension Links
|
||||
this.toolManager.resizingTool.doMouseMove = function () {
|
||||
var floorplan = this.diagram;
|
||||
var node = this.adornedObject;
|
||||
// if node is the only thing selected, display its info as its resized
|
||||
if (floorplan.selection.count === 1 && floorplan.floorplanUI) floorplan.floorplanUI.setSelectionInfo(node);
|
||||
this.diagram.updateWallDimensions();
|
||||
go.ResizingTool.prototype.doMouseMove.call(this);
|
||||
}
|
||||
|
||||
// When resizing a wallPart, do not allow it to be resized past the nearest wallPart / wall endpoints
|
||||
this.toolManager.resizingTool.computeMaxSize = function () {
|
||||
var tool = this;
|
||||
var obj = tool.adornedObject.part;
|
||||
var wall = this.diagram.findPartForKey(obj.data.group);
|
||||
if ((obj.category === 'DoorNode' || obj.category === 'WindowNode') && wall !== null) {
|
||||
var stationaryPt; var movingPt;
|
||||
var resizeAdornment = null;
|
||||
obj.adornments.iterator.each(function (adorn) { if (adorn.name === "WallPartResizeAdornment") resizeAdornment = adorn; });
|
||||
resizeAdornment.elements.iterator.each(function (el) {
|
||||
if (el instanceof go.Shape && el.alignment === tool.handle.alignment) movingPt = el.getDocumentPoint(go.Spot.Center);
|
||||
if (el instanceof go.Shape && el.alignment !== tool.handle.alignment) stationaryPt = el.getDocumentPoint(go.Spot.Center);
|
||||
});
|
||||
// find the constrainingPt; that is, the endpoint (wallPart endpoint or wall endpoint) that is the one closest to movingPt but still farther from stationaryPt than movingPt
|
||||
// this loop checks all other wallPart endpoints of the wall that the resizing wallPart is a part of
|
||||
var constrainingPt; var closestDist = Number.MAX_VALUE;
|
||||
wall.memberParts.iterator.each(function (part) {
|
||||
if (part.data.key !== obj.data.key) {
|
||||
var endpoints = getWallPartEndpoints(part);
|
||||
for (var i = 0; i < endpoints.length; i++) {
|
||||
var point = endpoints[i];
|
||||
var distanceToMovingPt = Math.sqrt(point.distanceSquaredPoint(movingPt));
|
||||
if (distanceToMovingPt < closestDist) {
|
||||
var distanceToStationaryPt = Math.sqrt(point.distanceSquaredPoint(stationaryPt));
|
||||
if (distanceToStationaryPt > distanceToMovingPt) {
|
||||
closestDist = distanceToMovingPt;
|
||||
constrainingPt = point;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// if we're not constrained by a wallPart endpoint, the constraint will come from a wall endpoint; figure out which one
|
||||
if (constrainingPt === undefined || constrainingPt === null) {
|
||||
if (wall.data.startpoint.distanceSquaredPoint(movingPt) > wall.data.startpoint.distanceSquaredPoint(stationaryPt)) constrainingPt = wall.data.endpoint;
|
||||
else constrainingPt = wall.data.startpoint;
|
||||
}
|
||||
// set the new max size of the wallPart according to the constrainingPt
|
||||
var maxLength = Math.sqrt(stationaryPt.distanceSquaredPoint(constrainingPt));
|
||||
return new go.Size(maxLength, wall.data.thickness);
|
||||
}
|
||||
return go.ResizingTool.prototype.computeMaxSize.call(tool);
|
||||
}
|
||||
|
||||
this.toolManager.draggingTool.isGridSnapEnabled = true;
|
||||
} go.Diagram.inherit(Floorplan, go.Diagram);
|
||||
|
||||
// Get/set the Floorplan Filesystem instance associated with this Floorplan
|
||||
Object.defineProperty(Floorplan.prototype, "floorplanFilesystem", {
|
||||
get: function () { return this._floorplanFilesystem; },
|
||||
set: function (val) {
|
||||
val instanceof FloorplanFilesystem ? this._floorplanFilesystem = val : this._floorplanFilesystem = null;
|
||||
}
|
||||
});
|
||||
|
||||
// Get/set the FloorplanUI instance associated with this Floorplan
|
||||
Object.defineProperty(Floorplan.prototype, "floorplanUI", {
|
||||
get: function () { return this._floorplanUI; },
|
||||
set: function (val) {
|
||||
val instanceof FloorplanUI ? this._floorplanUI = val : this._floorplanUI = null;
|
||||
}
|
||||
});
|
||||
|
||||
// Get array of all FloorplanPalettes associated with this Floorplan
|
||||
Object.defineProperty(Floorplan.prototype, "palettes", {
|
||||
get: function () { return this._palettes; }
|
||||
});
|
||||
|
||||
// Get / set Set of all Point Nodes in the Floorplan
|
||||
Object.defineProperty(Floorplan.prototype, "pointNodes", {
|
||||
get: function () { return this._pointNodes; },
|
||||
set: function (val) { this._pointNodes = val; }
|
||||
});
|
||||
|
||||
// Get / set Set of all Dimension Links in the Floorplan
|
||||
Object.defineProperty(Floorplan.prototype, "dimensionLinks", {
|
||||
get: function () { return this._dimensionLinks; },
|
||||
set: function () { this._dimensionLinks = val; }
|
||||
});
|
||||
|
||||
// Get / set Set of all Angle Nodes in the Floorplan
|
||||
Object.defineProperty(Floorplan.prototype, "angleNodes", {
|
||||
get: function () { return this._angleNodes; },
|
||||
set: function () { this._angleNodes = val; }
|
||||
});
|
||||
|
||||
|
||||
// Check what units are being used, convert to cm then multiply by 2, (1px = 2cm, change this if you want to use a different paradigm)
|
||||
Floorplan.prototype.convertPixelsToUnits = function (num) {
|
||||
var units = this.model.modelData.units;
|
||||
var factor = this.model.modelData.unitsConversionFactor;
|
||||
if (units === 'meters') return (num / 100) * factor;
|
||||
if (units === 'feet') return (num / 30.48) * factor;
|
||||
if (units === 'inches') return (num / 2.54) * factor;
|
||||
return num * factor;
|
||||
}
|
||||
|
||||
// Take a number of units, convert to cm, then divide by 2, (1px = 2cm, change this if you want to use a different paradigm)
|
||||
Floorplan.prototype.convertUnitsToPixels = function (num) {
|
||||
var units = this.model.modelData.units;
|
||||
var factor = this.model.modelData.unitsConversionFactor;
|
||||
if (units === 'meters') return (num * 100) / factor;
|
||||
if (units === 'feet') return (num * 30.48) / factor;
|
||||
if (units === 'inches') return (num * 2.54) / factor;
|
||||
return num / factor;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update the geometry, angle, and location of a given wall
|
||||
* @param {Wall} wall A reference to a valid Wall Group (defined in Templates-Walls)
|
||||
*/
|
||||
Floorplan.prototype.updateWall = function (wall) {
|
||||
if (wall.data.startpoint && wall.data.endpoint) {
|
||||
var shape = wall.findObject("SHAPE");
|
||||
var geo = new go.Geometry(go.Geometry.Line);
|
||||
var sPt = wall.data.startpoint;
|
||||
var ePt = wall.data.endpoint;
|
||||
var mPt = new go.Point((sPt.x + ePt.x) / 2, (sPt.y + ePt.y) / 2);
|
||||
// define a wall's geometry as a simple horizontal line, then rotate it
|
||||
geo.startX = 0;
|
||||
geo.startY = 0;
|
||||
geo.endX = Math.sqrt(sPt.distanceSquaredPoint(ePt));
|
||||
geo.endY = 0;
|
||||
shape.geometry = geo;
|
||||
wall.location = mPt; // a wall's location is the midpoint between it's startpoint and endpoint
|
||||
var angle = sPt.directionPoint(ePt);
|
||||
wall.rotateObject.angle = angle;
|
||||
this.updateWallDimensions();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function for Build Dimension Link: get a to/from point for a Dimension Link
|
||||
* @param {Wall} wall The Wall Group being given a Dimension Link
|
||||
* @param {Number} angle The angle of "wall"
|
||||
* @param {Number} wallOffset The distance the Dimension Link will be from wall (in pixels)
|
||||
*/
|
||||
Floorplan.prototype.getAdjustedPoint = function (point, wall, angle, wallOffset) {
|
||||
var oldPoint = point.copy();
|
||||
point.offset(0, -(wall.data.thickness * .5) - wallOffset);
|
||||
point.offset(-oldPoint.x, -oldPoint.y).rotate(angle).offset(oldPoint.x, oldPoint.y);
|
||||
return point;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function for Update Wall Dimensions; used to build Dimension Links
|
||||
* @param {Wall} wall The wall the Link runs along (either describing the wall itself or some wallPart on "wall")
|
||||
* @param {Number} index A number appended to PointNode keys; used for finding PointNodes of Dimension Links later
|
||||
* @param {Point} point1 The first point of the wallPart being described by the Link
|
||||
* @param {Point} point2 The second point of the wallPart being described by the Link
|
||||
* @param {Number} angle The angle of the wallPart
|
||||
* @param {Number} wallOffset How far from the wall (in px) the Link should be
|
||||
* @param {Boolean} soloWallFlag If this Link is the only Dimension Link for "wall" (no other wallParts on "wall" selected) this is true; else, false
|
||||
* @param {Floorplan} floorplan A reference to a valid Floorplan
|
||||
*/
|
||||
Floorplan.prototype.buildDimensionLink = function (wall, index, point1, point2, angle, wallOffset, soloWallFlag, floorplan) {
|
||||
point1 = floorplan.getAdjustedPoint(point1, wall, angle, wallOffset);
|
||||
point2 = floorplan.getAdjustedPoint(point2, wall, angle, wallOffset);
|
||||
var data1 = { key: wall.data.key + "PointNode" + index, category: "PointNode", loc: go.Point.stringify(point1) };
|
||||
var data2 = { key: wall.data.key + "PointNode" + (index + 1), category: "PointNode", loc: go.Point.stringify(point2) };
|
||||
var data3 = { key: wall.data.key + "DimensionLink", category: 'DimensionLink', from: data1.key, to: data2.key, stroke: 'gray', angle: angle, wall: wall.data.key, soloWallFlag: soloWallFlag };
|
||||
var pointNode1 = makePointNode();
|
||||
var pointNode2 = makePointNode();
|
||||
var link = makeDimensionLink();
|
||||
|
||||
floorplan.pointNodes.add(pointNode1);
|
||||
floorplan.pointNodes.add(pointNode2);
|
||||
floorplan.dimensionLinks.add(link);
|
||||
floorplan.add(pointNode1);
|
||||
floorplan.add(pointNode2);
|
||||
floorplan.add(link);
|
||||
|
||||
pointNode1.data = data1;
|
||||
pointNode2.data = data2;
|
||||
link.data = data3;
|
||||
link.fromNode = pointNode1;
|
||||
link.toNode = pointNode2;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update Dimension Links shown along walls, based on which walls and wallParts are selected
|
||||
*/
|
||||
Floorplan.prototype.updateWallDimensions = function () {
|
||||
var floorplan = this;
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.startTransaction("update wall dimensions");
|
||||
// if showWallLengths === false, remove all pointNodes (used to build wall dimensions)
|
||||
if (!floorplan.model.modelData.preferences.showWallLengths) {
|
||||
floorplan.pointNodes.iterator.each(function (node) { floorplan.remove(node); });
|
||||
floorplan.dimensionLinks.iterator.each(function (link) { floorplan.remove(link); });
|
||||
floorplan.pointNodes.clear();
|
||||
floorplan.dimensionLinks.clear();
|
||||
floorplan.commitTransaction("update wall dimensions");
|
||||
floorplan.skipsUndoManager = false;
|
||||
return;
|
||||
}
|
||||
// make visible all dimension links (zero-length dimension links are set to invisible at the end of the function)
|
||||
floorplan.dimensionLinks.iterator.each(function (link) { link.visible = true; });
|
||||
|
||||
var selection = floorplan.selection;
|
||||
// gather all selected walls, including walls of selected DoorNodes and WindowNodes
|
||||
var walls = new go.Set(/*go.Group*/);
|
||||
selection.iterator.each(function (part) {
|
||||
if ((part.category === 'WindowNode' || part.category === 'DoorNode') && part.containingGroup !== null) walls.add(part.containingGroup);
|
||||
if (part.category === 'WallGroup' && part.data && part.data.startpoint && part.data.endpoint) {
|
||||
var soloWallLink = null;
|
||||
floorplan.dimensionLinks.iterator.each(function (link) { if (link.data.soloWallFlag && link.data.wall === part.data.key) soloWallLink = link; });
|
||||
// if there's 1 Dimension Link for this wall (link has soloWallFlag), adjust to/from pointNodes of link, rather than deleting / redrawing
|
||||
if (soloWallLink !== null) {
|
||||
// since this is the only Dimension Link for this wall, keys of its pointNodes will be (wall.data.key) + 1 / (wall.data.key) + 2
|
||||
var linkPoint1 = null; var linkPoint2 = null;
|
||||
floorplan.pointNodes.iterator.each(function (node) {
|
||||
if (node.data.key === part.data.key + "PointNode1") linkPoint1 = node;
|
||||
if (node.data.key === part.data.key + "PointNode2") linkPoint2 = node;
|
||||
});
|
||||
var startpoint = part.data.startpoint; var endpoint = part.data.endpoint;
|
||||
// adjust left/top-most / right/bottom-most wall endpoints so link angle is correct (else text appears on wrong side of Link)
|
||||
var firstWallPt = ((startpoint.x + startpoint.y) <= (endpoint.x + endpoint.y)) ? startpoint : endpoint;
|
||||
var lastWallPt = ((startpoint.x + startpoint.y) > (endpoint.x + endpoint.y)) ? startpoint : endpoint;
|
||||
var newLoc1 = floorplan.getAdjustedPoint(firstWallPt.copy(), part, part.rotateObject.angle, 10);
|
||||
var newLoc2 = floorplan.getAdjustedPoint(lastWallPt.copy(), part, part.rotateObject.angle, 10);
|
||||
// cannot use model.setDataProperty, since pointNodes and dimensionLinks are not stored in the model
|
||||
linkPoint1.data.loc = go.Point.stringify(newLoc1);
|
||||
linkPoint2.data.loc = go.Point.stringify(newLoc2);
|
||||
soloWallLink.data.angle = part.rotateObject.angle;
|
||||
linkPoint1.updateTargetBindings();
|
||||
linkPoint2.updateTargetBindings();
|
||||
soloWallLink.updateTargetBindings();
|
||||
}
|
||||
// else build a Dimension Link for this wall; this is removed / replaced if Dimension Links for wallParts this wall are built
|
||||
else {
|
||||
var startpoint = part.data.startpoint;
|
||||
var endpoint = part.data.endpoint;
|
||||
var firstWallPt = ((startpoint.x + startpoint.y) <= (endpoint.x + endpoint.y)) ? startpoint : endpoint;
|
||||
var lastWallPt = ((startpoint.x + startpoint.y) > (endpoint.x + endpoint.y)) ? startpoint : endpoint;
|
||||
floorplan.buildDimensionLink(part, 1, firstWallPt.copy(), lastWallPt.copy(), part.rotateObject.angle, 10, true, floorplan);
|
||||
}
|
||||
}
|
||||
});
|
||||
// create array of selected wall endpoints and selected wallPart endpoints along the wall that represent measured stretches
|
||||
walls.iterator.each(function (wall) {
|
||||
var startpoint = wall.data.startpoint;
|
||||
var endpoint = wall.data.endpoint;
|
||||
var firstWallPt = ((startpoint.x + startpoint.y) <= (endpoint.x + endpoint.y)) ? startpoint : endpoint;
|
||||
var lastWallPt = ((startpoint.x + startpoint.y) > (endpoint.x + endpoint.y)) ? startpoint : endpoint;
|
||||
|
||||
// store all endpoints along with the part they correspond to (used later to either create DimensionLinks or simply adjust them)
|
||||
var wallPartEndpoints = [];
|
||||
wall.memberParts.iterator.each(function (wallPart) {
|
||||
if (wallPart.isSelected) {
|
||||
var endpoints = getWallPartEndpoints(wallPart);
|
||||
wallPartEndpoints.push(endpoints[0]);
|
||||
wallPartEndpoints.push(endpoints[1]);
|
||||
}
|
||||
});
|
||||
// sort all wallPartEndpoints by x coordinate left to right/ up to down
|
||||
wallPartEndpoints.sort(function (a, b) {
|
||||
if ((a.x + a.y) > (b.x + b.y)) return 1;
|
||||
if ((a.x + a.y) < (b.x + b.y)) return -1;
|
||||
else return 0;
|
||||
});
|
||||
wallPartEndpoints.unshift(firstWallPt);
|
||||
wallPartEndpoints.push(lastWallPt);
|
||||
|
||||
var angle = wall.rotateObject.angle;
|
||||
var k = 1; // k is a counter for the indices of PointNodes
|
||||
// build / edit dimension links for each stretch, defined by pairs of points in wallPartEndpoints
|
||||
for (var j = 0; j < wallPartEndpoints.length - 1; j++) {
|
||||
var linkPoint1 = null; linkPoint2 = null;
|
||||
floorplan.pointNodes.iterator.each(function (node) {
|
||||
if (node.data.key === wall.data.key + "PointNode" + k) linkPoint1 = node;
|
||||
if (node.data.key === wall.data.key + "PointNode" + (k + 1)) linkPoint2 = node;
|
||||
});
|
||||
if (linkPoint1 !== null) {
|
||||
var newLoc1 = floorplan.getAdjustedPoint(wallPartEndpoints[j].copy(), wall, angle, 5);
|
||||
var newLoc2 = floorplan.getAdjustedPoint(wallPartEndpoints[j + 1].copy(), wall, angle, 5);
|
||||
linkPoint1.data.loc = go.Point.stringify(newLoc1);
|
||||
linkPoint2.data.loc = go.Point.stringify(newLoc2);
|
||||
linkPoint1.updateTargetBindings();
|
||||
linkPoint2.updateTargetBindings();
|
||||
}
|
||||
// only build new links if needed -- normally simply change pointNode locations
|
||||
else floorplan.buildDimensionLink(wall, k, wallPartEndpoints[j].copy(), wallPartEndpoints[j + 1].copy(), angle, 5, false, floorplan);
|
||||
k += 2;
|
||||
}
|
||||
// total wall Dimension Link constructed of a kth and k+1st pointNode
|
||||
var totalWallDimensionLink = null;
|
||||
floorplan.dimensionLinks.iterator.each(function (link) {
|
||||
if ((link.fromNode.data.key === wall.data.key + "PointNode" + k) &&
|
||||
(link.toNode.data.key === wall.data.key + "PointNode" + (k + 1))) totalWallDimensionLink = link;
|
||||
});
|
||||
// if a total wall Dimension Link already exists, adjust its constituent point nodes
|
||||
if (totalWallDimensionLink !== null) {
|
||||
var linkPoint1 = null; var linkPoint2 = null;
|
||||
floorplan.pointNodes.iterator.each(function (node) {
|
||||
if (node.data.key === wall.data.key + "PointNode" + k) linkPoint1 = node;
|
||||
if (node.data.key === wall.data.key + "PointNode" + (k + 1)) linkPoint2 = node;
|
||||
});
|
||||
var newLoc1 = floorplan.getAdjustedPoint(wallPartEndpoints[0].copy(), wall, angle, 25);
|
||||
var newLoc2 = floorplan.getAdjustedPoint(wallPartEndpoints[wallPartEndpoints.length - 1].copy(), wall, angle, 25);
|
||||
linkPoint1.data.loc = go.Point.stringify(newLoc1);
|
||||
linkPoint2.data.loc = go.Point.stringify(newLoc2);
|
||||
linkPoint1.updateTargetBindings();
|
||||
linkPoint2.updateTargetBindings();
|
||||
}
|
||||
// only build total wall Dimension Link (far out from wall to accomodate wallPart Dimension Links) if one does not already exist
|
||||
else floorplan.buildDimensionLink(wall, k, wallPartEndpoints[0].copy(), wallPartEndpoints[wallPartEndpoints.length - 1].copy(), angle, 25, false, floorplan);
|
||||
});
|
||||
|
||||
// Cleanup: hide zero-length Dimension Links, DimensionLinks with null wall points
|
||||
floorplan.dimensionLinks.iterator.each(function (link) {
|
||||
var canStay = false;
|
||||
floorplan.pointNodes.iterator.each(function (node) {
|
||||
if (node.data.key == link.data.to) canStay = true;
|
||||
});
|
||||
if (!canStay) floorplan.remove(link);
|
||||
else {
|
||||
var length = Math.sqrt(link.toNode.location.distanceSquaredPoint(link.fromNode.location));
|
||||
if (length < 1 && !link.data.soloWallFlag) link.visible = false;
|
||||
}
|
||||
});
|
||||
|
||||
floorplan.commitTransaction("update wall dimensions");
|
||||
floorplan.skipsUndoManager = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function for updateWallAngles(); returns the Point where two walls intersect; if they do not intersect, return null
|
||||
* @param {Wall} wall1
|
||||
* @param {Wall} wall2
|
||||
*/
|
||||
Floorplan.prototype.getWallsIntersection = function (wall1, wall2) {
|
||||
if (wall1 === null || wall2 === null) return null;
|
||||
// treat walls as lines; get lines in formula of ax + by = c
|
||||
var a1 = wall1.data.endpoint.y - wall1.data.startpoint.y;
|
||||
var b1 = wall1.data.startpoint.x - wall1.data.endpoint.x;
|
||||
var c1 = (a1 * wall1.data.startpoint.x) + (b1 * wall1.data.startpoint.y);
|
||||
var a2 = wall2.data.endpoint.y - wall2.data.startpoint.y;
|
||||
var b2 = wall2.data.startpoint.x - wall2.data.endpoint.x;
|
||||
var c2 = (a2 * wall2.data.startpoint.x) + (b2 * wall2.data.startpoint.y);
|
||||
// Solve the system of equations, finding where the lines (not segments) would intersect
|
||||
/** Algebra Explanation:
|
||||
Line 1: a1x + b1y = c1
|
||||
Line 2: a2x + b2y = c2
|
||||
|
||||
Multiply Line1 equation by b2, Line2 equation by b1, get:
|
||||
a1b1x + b1b2y = b2c1
|
||||
a2b1x + b1b2y = b1c2
|
||||
|
||||
Subtract bottom from top:
|
||||
a1b2x - a2b1x = b2c1 - b1c2
|
||||
|
||||
Divide both sides by a1b2 - a2b1, get equation for x. Equation for y is analogous
|
||||
**/
|
||||
var det = a1 * b2 - a2 * b1;
|
||||
var x = null; var y = null;
|
||||
// Edge Case: Lines are paralell
|
||||
if (det === 0) {
|
||||
// Edge Case: wall1 and wall2 have an endpoint to endpoint intersection (the only instance in which paralell walls could intersect at a specific point)
|
||||
if (wall1.data.startpoint.equals(wall2.data.startpoint) || wall1.data.startpoint.equals(wall2.data.endpoint)) return wall1.data.startpoint;
|
||||
if (wall1.data.endpoint.equals(wall2.data.startpoint) || wall1.data.endpoint.equals(wall2.data.endpoint)) return wall1.data.endpoint;
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
x = (b2 * c1 - b1 * c2) / det;
|
||||
y = (a1 * c2 - a2 * c1) / det;
|
||||
}
|
||||
// ensure proposed intersection is contained in both line segments (walls)
|
||||
var inWall1 = ((Math.min(wall1.data.startpoint.x, wall1.data.endpoint.x) <= x) && (Math.max(wall1.data.startpoint.x, wall1.data.endpoint.x) >= x)
|
||||
&& (Math.min(wall1.data.startpoint.y, wall1.data.endpoint.y) <= y) && (Math.max(wall1.data.startpoint.y, wall1.data.endpoint.y) >= y));
|
||||
var inWall2 = ((Math.min(wall2.data.startpoint.x, wall2.data.endpoint.x) <= x) && (Math.max(wall2.data.startpoint.x, wall2.data.endpoint.x) >= x)
|
||||
&& (Math.min(wall2.data.startpoint.y, wall2.data.endpoint.y) <= y) && (Math.max(wall2.data.startpoint.y, wall2.data.endpoint.y) >= y));
|
||||
if (inWall1 && inWall2) return new go.Point(x, y);
|
||||
else return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update Angle Nodes shown along a wall, based on which wall(s) is/are selected
|
||||
*/
|
||||
Floorplan.prototype.updateWallAngles = function () {
|
||||
var floorplan = this;
|
||||
floorplan.skipsUndoManager = true; // do not store displaying angles as a transaction
|
||||
floorplan.startTransaction("display angles");
|
||||
if (floorplan.model.modelData.preferences.showWallAngles) {
|
||||
floorplan.angleNodes.iterator.each(function (node) { node.visible = true; });
|
||||
var selectedWalls = [];
|
||||
floorplan.selection.iterator.each(function (part) { if (part.category === "WallGroup") selectedWalls.push(part); });
|
||||
for (var i = 0; i < selectedWalls.length; i++) {
|
||||
var seen = new go.Set(/*"string"*/); // Set of all walls "seen" thus far for "wall"
|
||||
var wall = selectedWalls[i];
|
||||
var possibleWalls = floorplan.findNodesByExample({ category: "WallGroup" });
|
||||
|
||||
// go through all other walls; if the other wall intersects this wall, make angles
|
||||
possibleWalls.iterator.each(function (otherWall) {
|
||||
if (otherWall.data === null || wall.data === null || seen.contains(otherWall.data.key)) return;
|
||||
if ((otherWall.data.key !== wall.data.key) && (floorplan.getWallsIntersection(wall, otherWall) !== null) && (!seen.contains(otherWall.data.key))) {
|
||||
|
||||
seen.add(otherWall.data.key);
|
||||
// "otherWall" intersects "wall"; make or update angle nodes
|
||||
var intersectionPoint = floorplan.getWallsIntersection(wall, otherWall);
|
||||
var wallsInvolved = floorplan.findObjectsNear(intersectionPoint,
|
||||
1,
|
||||
function (x) { if (x.part !== null) return x.part; },
|
||||
function (p) { return p.category === "WallGroup"; },
|
||||
false);
|
||||
|
||||
var endpoints = []; // store endpoints and their corresponding walls here
|
||||
// gather endpoints of each wall in wallsInvolved; discard endpoints within a tolerance distance of intersectionPoint
|
||||
wallsInvolved.iterator.each(function (w) {
|
||||
var tolerance = (floorplan.model.modelData.gridSize >= 10) ? floorplan.model.modelData.gridSize : 10;
|
||||
if (Math.sqrt(w.data.startpoint.distanceSquaredPoint(intersectionPoint)) > tolerance) endpoints.push({ point: w.data.startpoint, wall: w.data.key });
|
||||
if (Math.sqrt(w.data.endpoint.distanceSquaredPoint(intersectionPoint)) > tolerance) endpoints.push({ point: w.data.endpoint, wall: w.data.key });
|
||||
});
|
||||
|
||||
// find maxRadius (shortest distance from an involved wall's endpoint to intersectionPoint or 30, whichever is smaller)
|
||||
var maxRadius = 30;
|
||||
for (var i = 0; i < endpoints.length; i++) {
|
||||
var distance = Math.sqrt(endpoints[i].point.distanceSquaredPoint(intersectionPoint));
|
||||
if (distance < maxRadius) maxRadius = distance;
|
||||
}
|
||||
|
||||
// sort endpoints in a clockwise fashion around the intersectionPoint
|
||||
endpoints.sort(function (a, b) {
|
||||
a = a.point; b = b.point;
|
||||
if (a.x - intersectionPoint.x >= 0 && b.x - intersectionPoint.x < 0) return true;
|
||||
if (a.x - intersectionPoint.x < 0 && b.x - intersectionPoint.x >= 0) return false;
|
||||
if (a.x - intersectionPoint.x == 0 && b.x - intersectionPoint.x == 0) {
|
||||
if (a.y - intersectionPoint.y >= 0 || b.y - intersectionPoint.y >= 0) return a.y > b.y;
|
||||
return b.y > a.y;
|
||||
}
|
||||
|
||||
// compute the cross product of vectors (center -> a) x (center -> b)
|
||||
var det = (a.x - intersectionPoint.x) * (b.y - intersectionPoint.y) - (b.x - intersectionPoint.x) * (a.y - intersectionPoint.y);
|
||||
if (det < 0) return true;
|
||||
if (det > 0) return false;
|
||||
|
||||
// points a and b are on the same line from the center; check which point is closer to the center
|
||||
var d1 = (a.x - intersectionPoint.x) * (a.x - intersectionPoint.x) + (a.y - intersectionPoint.y) * (a.y - intersectionPoint.y);
|
||||
var d2 = (b.x - intersectionPoint.x) * (b.x - intersectionPoint.x) + (b.y - intersectionPoint.y) * (b.y - intersectionPoint.y);
|
||||
return d1 > d2;
|
||||
}); // end endpoints sort
|
||||
|
||||
// for each pair of endpoints, construct or modify an angleNode
|
||||
for (var i = 0; i < endpoints.length; i++) {
|
||||
var p1 = endpoints[i];
|
||||
if (endpoints[i + 1] != null) var p2 = endpoints[i + 1];
|
||||
else var p2 = endpoints[0];
|
||||
var a1 = intersectionPoint.directionPoint(p1.point);
|
||||
var a2 = intersectionPoint.directionPoint(p2.point);
|
||||
var sweep = Math.abs(a2 - a1 + 360) % 360;
|
||||
var angle = a1;
|
||||
|
||||
/*
|
||||
construct proper key for angleNode
|
||||
proper angleNode key syntax is "wallWwallX...wallYangleNodeZ" such that W < Y < Y; angleNodes are sorted clockwise around the intersectionPoint by Z
|
||||
*/
|
||||
var keyArray = []; // used to construct proper key
|
||||
wallsInvolved.iterator.each(function (wall) { keyArray.push(wall); });
|
||||
keyArray.sort(function (a, b) {
|
||||
var aIndex = a.data.key.match(/\d+/g);
|
||||
var bIndex = b.data.key.match(/\d+/g);
|
||||
if (isNaN(aIndex)) return true;
|
||||
if (isNaN(bIndex)) return false;
|
||||
else return aIndex > bIndex;
|
||||
});
|
||||
|
||||
var key = "";
|
||||
for (var j = 0; j < keyArray.length; j++) key += keyArray[j].data.key;
|
||||
key += "angle" + i;
|
||||
|
||||
// check if this angleNode already exists -- if it does, adjust data (instead of deleting/redrawing)
|
||||
var angleNode = null;
|
||||
floorplan.angleNodes.iterator.each(function (aNode) { if (aNode.data.key === key) angleNode = aNode; });
|
||||
if (angleNode !== null) {
|
||||
angleNode.data.angle = angle;
|
||||
angleNode.data.sweep = sweep;
|
||||
angleNode.data.loc = go.Point.stringify(intersectionPoint);
|
||||
angleNode.data.maxRadius = maxRadius;
|
||||
angleNode.updateTargetBindings();
|
||||
}
|
||||
// if this angleNode does not already exist, create it and add it to the diagram
|
||||
else {
|
||||
var data = { key: key, category: "AngleNode", loc: go.Point.stringify(intersectionPoint), stroke: "dodgerblue", angle: angle, sweep: sweep, maxRadius: maxRadius };
|
||||
var newAngleNode = makeAngleNode();
|
||||
newAngleNode.data = data;
|
||||
floorplan.add(newAngleNode);
|
||||
newAngleNode.updateTargetBindings();
|
||||
floorplan.angleNodes.add(newAngleNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// garbage collection (angleNodes that should not exist any more)
|
||||
var garbage = [];
|
||||
floorplan.angleNodes.iterator.each(function (node) {
|
||||
var keyNums = node.data.key.match(/\d+/g); // values X for all wall keys involved, given key "wallX"
|
||||
var numWalls = (node.data.key.match(/wall/g) || []).length; // # of walls involved in in "node"'s construction
|
||||
var wallsInvolved = [];
|
||||
// add all walls involved in angleNode's construction to wallsInvolved
|
||||
for (var i = 0; i < keyNums.length - 1; i++) wallsInvolved.push("wall" + keyNums[i]);
|
||||
// edge case: if the numWalls != keyNums.length, that means the wall with key "wall" (no number in key) is involved
|
||||
if (numWalls !== keyNums.length - 1) wallsInvolved.push("wall");
|
||||
|
||||
// Case 1: if any wall pairs involved in this angleNode are no longer intersecting, add this angleNode to "garbage"
|
||||
for (var i = 0; i < wallsInvolved.length - 1; i++) {
|
||||
var wall1 = floorplan.findPartForKey(wallsInvolved[i]);
|
||||
var wall2 = floorplan.findPartForKey(wallsInvolved[i + 1]);
|
||||
var intersectionPoint = floorplan.getWallsIntersection(wall1, wall2);
|
||||
if (intersectionPoint === null) garbage.push(node);
|
||||
}
|
||||
// Case 2: if there are angleNode clusters with the same walls in their keys as "node" but different locations, destroy and rebuild
|
||||
// collect all angleNodes with same walls in their construction as "node"
|
||||
var possibleAngleNodes = new go.Set(/*go.Node*/);
|
||||
var allWalls = node.data.key.slice(0, node.data.key.indexOf("angle"));
|
||||
floorplan.angleNodes.iterator.each(function (other) { if (other.data.key.indexOf(allWalls) !== -1) possibleAngleNodes.add(other); });
|
||||
possibleAngleNodes.iterator.each(function (pNode) {
|
||||
if (pNode.data.loc !== node.data.loc) {
|
||||
garbage.push(pNode);
|
||||
}
|
||||
});
|
||||
|
||||
// Case 3: put any angleNodes with sweep === 0 in garbage
|
||||
if (node.data.sweep === 0) garbage.push(node);
|
||||
});
|
||||
|
||||
for (var i = 0; i < garbage.length; i++) {
|
||||
floorplan.remove(garbage[i]); // remove garbage
|
||||
floorplan.angleNodes.remove(garbage[i]);
|
||||
}
|
||||
}
|
||||
// hide all angles > 180 if show only small angles == true in preferences
|
||||
if (floorplan.model.modelData.preferences.showOnlySmallWallAngles) {
|
||||
floorplan.angleNodes.iterator.each(function (node) { if (node.data.sweep >= 180) node.visible = false; });
|
||||
}
|
||||
// hide all angles if show wall angles == false in preferences
|
||||
if (!floorplan.model.modelData.preferences.showWallAngles) {
|
||||
floorplan.angleNodes.iterator.each(function (node) { node.visible = false; });
|
||||
}
|
||||
floorplan.commitTransaction("display angles");
|
||||
floorplan.skipsUndoManager = false;
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Floorplan Filesystem Class
|
||||
* Handles Floorplan-specific saving / loading model data events
|
||||
* Attached to a specific instance of Floorplan (via constructor); can be assigned to Floorplan.floorplanFilesystem
|
||||
* Currently only supports saving / loading from localstorage
|
||||
*/
|
||||
|
||||
/*
|
||||
* Floorplan Filesystem Constructor
|
||||
* @param {Floorplan} floorplan A reference to a valid instance of Floorplan
|
||||
* @param {Object} state A JSON object with string ids for UI objects (windows, listboxes, HTML elements)
|
||||
* {
|
||||
* openWindowId: {String} the id of the HTML window to open a file
|
||||
* removeWindowId: {String} the id of the HTML window to remove files
|
||||
* currentFileId: {String} the id of the HTML element containing the name of the currently open file
|
||||
* filesToOpenListId: {String} the id of the HTML listbox in the openWindow
|
||||
* filesToRemoveListId: {String} the id of the HTML listbox of the removeWindow
|
||||
* }
|
||||
*/
|
||||
function FloorplanFilesystem(floorplan, state) {
|
||||
this._floorplan = floorplan;
|
||||
this._floorplan.floorplanFilesystem = this;
|
||||
this._UNSAVED_FILENAME = "(Unsaved File)";
|
||||
this._DEFAULT_MODELDATA = {
|
||||
"units": "centimeters",
|
||||
"unitsAbbreviation": "cm",
|
||||
"unitsConversionFactor": 2,
|
||||
"gridSize": 10,
|
||||
"wallWidth": 5,
|
||||
"preferences": {
|
||||
showWallGuidelines: true,
|
||||
showWallLengths: true,
|
||||
showWallAngles: true,
|
||||
showOnlySmallWallAngles: true,
|
||||
showGrid: true,
|
||||
gridSnap: true
|
||||
}
|
||||
};
|
||||
this._state = state;
|
||||
}
|
||||
|
||||
// Get the Floorplan associated with this Floorplan Filesystem
|
||||
Object.defineProperty(FloorplanFilesystem.prototype, "floorplan", {
|
||||
get: function () { return this._floorplan; }
|
||||
});
|
||||
|
||||
// Get constant name for an unsaved Floorplan
|
||||
Object.defineProperty(FloorplanFilesystem.prototype, "UNSAVED_FILENAME", {
|
||||
get: function () { return this._UNSAVED_FILENAME; }
|
||||
});
|
||||
|
||||
// Get constant default model data (default Floorplan model data for a new Floorplan)
|
||||
Object.defineProperty(FloorplanFilesystem.prototype, "DEFAULT_MODELDATA", {
|
||||
get: function () { return this._DEFAULT_MODELDATA; }
|
||||
});
|
||||
|
||||
// Get state information about this app's UI
|
||||
Object.defineProperty(FloorplanFilesystem.prototype, "state", {
|
||||
get: function () { return this._state; }
|
||||
});
|
||||
|
||||
/*
|
||||
* Helper functions
|
||||
* Check Local Storage, Update File List, Open Window
|
||||
*/
|
||||
|
||||
// Ensure local storage works in browser (not supported by MS IE/Edge)
|
||||
function checkLocalStorage() {
|
||||
try {
|
||||
window.localStorage.setItem('item', 'item');
|
||||
window.localStorage.removeItem('item');
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Update listbox of files in window (for loading / removing files)
|
||||
function updateFileList(id) {
|
||||
// displays cached floor plan files in the listboxes
|
||||
var listbox = document.getElementById(id);
|
||||
// remove any old listing of files
|
||||
var last;
|
||||
while (last = listbox.lastChild) listbox.removeChild(last);
|
||||
// now add all saved files to the listbox
|
||||
for (key in window.localStorage) {
|
||||
var storedFile = window.localStorage.getItem(key);
|
||||
if (!storedFile) continue;
|
||||
var option = document.createElement("option");
|
||||
option.value = key;
|
||||
option.text = key;
|
||||
listbox.add(option, null)
|
||||
}
|
||||
}
|
||||
|
||||
// Open a specifed window element -- used for Remove / Open file windows
|
||||
function openWindow(id, listid) {
|
||||
var panel = document.getElementById(id);
|
||||
if (panel.style.visibility === "hidden") {
|
||||
updateFileList(listid);
|
||||
panel.style.visibility = "visible";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Instance methods
|
||||
* New Floorplan, Save Floorplan, Save Floorplan As, Load Floorplan, Remove Floorplan
|
||||
* Show Open Window, Show Remove Window
|
||||
* Set Current File Name, Get Current File Name
|
||||
*/
|
||||
|
||||
// Create new floorplan (Ctrl + D or File -> New)
|
||||
FloorplanFilesystem.prototype.newFloorplan = function () {
|
||||
var floorplan = this.floorplan;
|
||||
// checks to see if all changes have been saved
|
||||
if (floorplan.isModified) {
|
||||
var save = confirm("Would you like to save changes to " + this.getCurrentFileName() + "?");
|
||||
if (save) {
|
||||
this.saveFloorplan();
|
||||
}
|
||||
}
|
||||
this.setCurrentFileName(this.UNSAVED_FILENAME);
|
||||
// loads an empty diagram
|
||||
var model = new go.GraphLinksModel;
|
||||
// initialize all modelData
|
||||
model.modelData = this.DEFAULT_MODELDATA;
|
||||
floorplan.model = model;
|
||||
floorplan.undoManager.isEnabled = true;
|
||||
floorplan.isModified = false;
|
||||
if (floorplan.floorplanUI) {
|
||||
floorplan.floorplanUI.updateUI();
|
||||
floorplan.floorplanUI.updateStatistics();
|
||||
}
|
||||
}
|
||||
|
||||
// Save current floor plan to local storage (Ctrl + S or File -> Save)
|
||||
FloorplanFilesystem.prototype.saveFloorplan = function () {
|
||||
if (checkLocalStorage()) {
|
||||
var saveName = this.getCurrentFileName();
|
||||
if (saveName === this.UNSAVED_FILENAME || saveName == null || saveName == undefined) {
|
||||
this.saveFloorplanAs();
|
||||
} else {
|
||||
window.localStorage.setItem(saveName, this.floorplan.model.toJson());
|
||||
this.floorplan.isModified = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save floor plan to local storage with a new name (File -> Save As)
|
||||
FloorplanFilesystem.prototype.saveFloorplanAs = function () {
|
||||
if (checkLocalStorage()) {
|
||||
var saveName = prompt("Save file as...", this.getCurrentFileName());
|
||||
// if saveName is already in list of files, ask if overwrite is ok
|
||||
if (saveName && saveName !== this.UNSAVED_FILENAME) {
|
||||
var override = true;
|
||||
if (window.localStorage.getItem(saveName) !== null) {
|
||||
override = confirm("Do you want to overwrite " + saveName + "?");
|
||||
}
|
||||
if (override) {
|
||||
this.setCurrentFileName(saveName);
|
||||
window.localStorage.setItem(saveName, this.floorplan.model.toJson());
|
||||
this.floorplan.isModified = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load floorplan model data in "Open" window (Ctrl + O or File -> Open)
|
||||
FloorplanFilesystem.prototype.loadFloorplan = function () {
|
||||
var floorplan = this.floorplan;
|
||||
var listbox = document.getElementById(this.state.filesToOpenListId);
|
||||
var fileName = undefined; // get selected filename
|
||||
for (var i = 0; i < listbox.options.length; i++) {
|
||||
if (listbox.options[i].selected) fileName = listbox.options[i].text; // selected file
|
||||
}
|
||||
if (fileName !== undefined) {
|
||||
var savedFile = window.localStorage.getItem(fileName);
|
||||
this.loadFloorplanFromModel(savedFile);
|
||||
floorplan.isModified = false;
|
||||
this.setCurrentFileName(fileName);
|
||||
}
|
||||
if (floorplan.floorplanUI) floorplan.floorplanUI.hideShow(this.state.openWindowId);
|
||||
}
|
||||
|
||||
FloorplanFilesystem.prototype.loadFloorplanFromModel = function (str) {
|
||||
var floorplan = this.floorplan;
|
||||
floorplan.model = go.Model.fromJson(str);
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.startTransaction("generate walls");
|
||||
floorplan.nodes.each(function (node) {
|
||||
if (node.category === "WallGroup") floorplan.updateWall(node);
|
||||
});
|
||||
if (floorplan.floorplanUI) {
|
||||
floorplan.floorplanUI.updateUI();
|
||||
floorplan.floorplanUI.updateStatistics();
|
||||
}
|
||||
|
||||
floorplan.commitTransaction("generate walls");
|
||||
floorplan.undoManager.isEnabled = true;
|
||||
|
||||
}
|
||||
|
||||
// Delete selected floorplan from local storage
|
||||
FloorplanFilesystem.prototype.removeFloorplan = function () {
|
||||
var floorplan = this.floorplan;
|
||||
var listbox = document.getElementById(this.state.filesToRemoveListId);
|
||||
var fileName = undefined; // get selected filename
|
||||
for (var i = 0; i < listbox.options.length; i++) {
|
||||
if (listbox.options[i].selected) fileName = listbox.options[i].text; // selected file
|
||||
}
|
||||
if (fileName !== undefined) {
|
||||
// removes file from local storage
|
||||
window.localStorage.removeItem(fileName);
|
||||
}
|
||||
if (floorplan.floorplanUI) floorplan.floorplanUI.hideShow(this.state.removeWindowId);
|
||||
}
|
||||
|
||||
// Check to see if all changes have been saved -> show the "Open" window
|
||||
FloorplanFilesystem.prototype.showOpenWindow = function () {
|
||||
if (checkLocalStorage()) {
|
||||
if (this.floorplan.isModified) {
|
||||
var save = confirm("Would you like to save changes to " + this.getCurrentFileName() + "?");
|
||||
if (save) {
|
||||
this.saveFloorplan();
|
||||
}
|
||||
}
|
||||
openWindow(this.state.openWindowId, this.state.filesToOpenListId);
|
||||
}
|
||||
}
|
||||
|
||||
// Show the Remove File window
|
||||
FloorplanFilesystem.prototype.showRemoveWindow = function () {
|
||||
if (checkLocalStorage()) {
|
||||
openWindow(this.state.removeWindowId, this.state.filesToRemoveListId);
|
||||
}
|
||||
}
|
||||
|
||||
// Add * to current file element if diagram has been modified
|
||||
FloorplanFilesystem.prototype.setCurrentFileName = function (name) {
|
||||
var currentFile = document.getElementById(this.state.currentFileId);
|
||||
if (currentFile) {
|
||||
if (this.floorplan.isModified) name += "*";
|
||||
currentFile.textContent = name;
|
||||
}
|
||||
}
|
||||
|
||||
// Get current file name from the current file element
|
||||
FloorplanFilesystem.prototype.getCurrentFileName = function () {
|
||||
var currentFile = document.getElementById(this.state.currentFileId);
|
||||
if (currentFile) {
|
||||
var name = currentFile.textContent;
|
||||
if (name[name.length - 1] === "*") return name.substr(0, name.length - 1);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
@@ -0,0 +1,773 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* FLOOR PLANN UI CLASS
|
||||
* Handle GUI manipulation (showing/changing data, populating windows, etc) for Floorplanner.html
|
||||
*/
|
||||
|
||||
/*
|
||||
* Floorplan UI Constructor
|
||||
* @param {Floorplan} floorplan A reference to a valid instance of Floorplan
|
||||
* @param {String} name The name of this FloorplanUI instance known to the DOM
|
||||
* @param {String} The name of this UI's floorplan known to the DOM
|
||||
* @param {Object} state A JSON object with string ids for UI HTML elements. Format is as follows:
|
||||
menuButtons: {
|
||||
selectionInfoWindowButtonId:
|
||||
palettesWindowButtonId:
|
||||
overviewWindowButtonId:
|
||||
optionsWindowButtonId:
|
||||
statisticsWindowButtonId:
|
||||
}
|
||||
windows: {
|
||||
diagramHelpDiv: {
|
||||
id:
|
||||
}
|
||||
selectionInfoWindow: {
|
||||
id:
|
||||
textDivId:
|
||||
handleId:
|
||||
colorPickerId:
|
||||
heightLabelId:
|
||||
heightInputId:
|
||||
widthInputId:
|
||||
nodeGroupInfoId:
|
||||
nameInputId:
|
||||
notesTextareaId:
|
||||
}
|
||||
palettesWindow:{
|
||||
id:
|
||||
furnitureSearchInputId:
|
||||
furniturePaletteId:
|
||||
}
|
||||
overviewWindow: {
|
||||
id:
|
||||
}
|
||||
optionsWindow: {
|
||||
id:
|
||||
gridSizeInputId:
|
||||
unitsConversionFactorInputId:
|
||||
unitsFormId:
|
||||
unitsFormName:
|
||||
checkboxes: {
|
||||
showGridCheckboxId:
|
||||
gridSnapCheckboxId:
|
||||
wallGuidelinesCheckboxId:
|
||||
wallLengthsCheckboxId:
|
||||
wallAnglesCheckboxId:
|
||||
smallWallAnglesCheckboxId:
|
||||
}
|
||||
}
|
||||
statisticsWindow: {
|
||||
id:
|
||||
textDivId:
|
||||
numsTableId:
|
||||
totalsTableId:
|
||||
}
|
||||
}
|
||||
scaleDisplayId:
|
||||
setBehaviorClass:
|
||||
wallThicknessInputId:
|
||||
wallThicknessBoxId:
|
||||
unitsBoxId:
|
||||
unitsInputId:
|
||||
*/
|
||||
function FloorplanUI(floorplan, name, floorplanName, state) {
|
||||
this._floorplan = floorplan;
|
||||
this._name = name;
|
||||
this._floorplanName = floorplanName;
|
||||
this._state = state;
|
||||
this._furnitureNodeData = null; // used for searchFurniture function. set only once
|
||||
this.floorplan.floorplanUI = this;
|
||||
}
|
||||
|
||||
// Get Floorplan associated with this UI
|
||||
Object.defineProperty(FloorplanUI.prototype, "floorplan", {
|
||||
get: function () { return this._floorplan; }
|
||||
});
|
||||
|
||||
// Get state object containing many ids of various UI elements
|
||||
Object.defineProperty(FloorplanUI.prototype, "state", {
|
||||
get: function () { return this._state; }
|
||||
});
|
||||
|
||||
// Get name of this FloorplanUI instance known to the DOM
|
||||
Object.defineProperty(FloorplanUI.prototype, "name", {
|
||||
get: function () { return this._name; }
|
||||
});
|
||||
|
||||
// Get name of the Floorplan associated with this FloorplanUI instance known to the DOM
|
||||
Object.defineProperty(FloorplanUI.prototype, "floorplanName", {
|
||||
get: function () { return this._floorplanName; }
|
||||
});
|
||||
|
||||
Object.defineProperty(FloorplanUI.prototype, "furnitureData", {
|
||||
get: function () { return this._furnitureData; },
|
||||
set: function (val) { this._furnitureData = val; }
|
||||
});
|
||||
|
||||
/*
|
||||
* UI manipulation:
|
||||
* Hide/Show Element, Adjust Scale, ChangeGridSize, Change Units Conversion Factor
|
||||
* Search Furniture, Checkbox Changed, Change Units, Set Behavior, Update UI
|
||||
*/
|
||||
|
||||
/*
|
||||
* Hide or show specific help/windows (used mainly with hotkeys)
|
||||
* @param {String} id The ID of the window to show / hide
|
||||
*/
|
||||
FloorplanUI.prototype.hideShow = function(id) {
|
||||
var element = document.getElementById(id); var str;
|
||||
var windows = this.state.windows;
|
||||
switch (id) {
|
||||
case windows.diagramHelpDiv.id: str = 'Diagram Help'; char = 'H'; break;
|
||||
case windows.selectionInfoWindow.id: str = 'Selection Help'; char = 'I'; break;
|
||||
case windows.overviewWindow.id: str = 'Overview'; char = 'E'; break;
|
||||
case windows.optionsWindow.id: str = 'Options'; char = 'B'; break;
|
||||
case windows.statisticsWindow.id: str = 'Statistics'; char = 'G'; break;
|
||||
case windows.palettesWindow.id: str = 'Palettes'; char = 'P'; {
|
||||
furniturePalette.layoutDiagram(true);
|
||||
wallPartsPalette.layoutDiagram(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
var button = document.getElementById(id + 'Button');
|
||||
element.style.visibility = element.style.visibility === "visible" ? "hidden" : "visible";
|
||||
var verb = element.style.visibility === "visible" ? "Hide " : "Show ";
|
||||
if (button) button.innerHTML = verb + str + "<p class='shortcut'> (Ctrl + " + char + " )</p>";
|
||||
}
|
||||
|
||||
/*
|
||||
* Set text under Diagram to suggest most common functions user could perform
|
||||
* @param {String} str The text to display in the Diagram Help div
|
||||
*/
|
||||
FloorplanUI.prototype.setDiagramHelper = function(str) {
|
||||
var helper = document.getElementById(this.state.windows.diagramHelpDiv.id);
|
||||
if (helper) helper.innerHTML = '<p>' + str + '</p>';
|
||||
}
|
||||
|
||||
/*
|
||||
* Increase / decrease diagram scale to the nearest 10%
|
||||
* @param {String} sign Accepted values are "+" and "-"
|
||||
*/
|
||||
FloorplanUI.prototype.adjustScale = function(sign) {
|
||||
var floorplan = this.floorplan;
|
||||
var el = document.getElementById(this.state.scaleDisplayId);
|
||||
floorplan.startTransaction('Change Scale');
|
||||
switch (sign) {
|
||||
case '-': floorplan.scale -= .1; break;
|
||||
case '+': floorplan.scale += .1; break;
|
||||
}
|
||||
floorplan.scale = parseFloat((Math.round(floorplan.scale / .1) * .1).toFixed(2));
|
||||
var scale = (floorplan.scale * 100).toFixed(2);
|
||||
el.innerHTML = 'Scale: ' + scale + '%';
|
||||
floorplan.commitTransaction('Change Scale');
|
||||
}
|
||||
|
||||
// Change edge length of the grid based on input
|
||||
FloorplanUI.prototype.changeGridSize = function () {
|
||||
var floorplan = this.floorplan;
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.startTransaction("change grid size");
|
||||
var el = document.getElementById(this.state.windows.optionsWindow.gridSizeInputId); var input;
|
||||
if (!isNaN(el.value) && el.value != null && el.value != '' && el.value != undefined && el.value > 1) input = parseFloat(el.value);
|
||||
else {
|
||||
el.value = floorplan.convertPixelsToUnits(10); // if bad input given, revert to 20cm (10px) or unit equivalent
|
||||
input = parseFloat(el.value);
|
||||
}
|
||||
input = floorplan.convertUnitsToPixels(input);
|
||||
floorplan.grid.gridCellSize = new go.Size(input, input);
|
||||
floorplan.toolManager.draggingTool.gridCellSize = new go.Size(input, input);
|
||||
floorplan.model.setDataProperty(floorplan.model.modelData, "gridSize", input);
|
||||
floorplan.commitTransaction("change grid size");
|
||||
floorplan.skipsUndoManager = false;
|
||||
}
|
||||
|
||||
FloorplanUI.prototype.changeUnitsConversionFactor = function () {
|
||||
var floorplan = this.floorplan;
|
||||
var val = document.getElementById(this.state.windows.optionsWindow.unitsConversionFactorInputId).value;
|
||||
if (isNaN(val) || !val || val == undefined) return;
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.model.set(floorplan.model.modelData, "unitsConversionFactor", val);
|
||||
floorplan.skipsUndoManager = false;
|
||||
}
|
||||
|
||||
// Search through all elements in the furniture palette (useful for a palette with many furniture nodes)
|
||||
FloorplanUI.prototype.searchFurniture = function () {
|
||||
var ui = this;
|
||||
var floorplan = this.floorplan;
|
||||
var furniturePaletteId = ui.state.windows.palettesWindow.furniturePaletteId;
|
||||
var str = document.getElementById(ui.state.windows.palettesWindow.furnitureSearchInputId).value;
|
||||
var furniturePalette = window.furniturePalette;
|
||||
/*var furniturePalette = null;
|
||||
for (var i = 0; i < floorplan.palettes.length; i++) {
|
||||
var palette = floorplan.palettes[i];
|
||||
if (palette.div.id == furniturePaletteId) {
|
||||
furniturePalette = floorplan.palettes[i];
|
||||
}
|
||||
}*/
|
||||
if (ui.furnitureData == null) ui.furnitureData = furniturePalette.model.nodeDataArray;
|
||||
var items = furniturePalette.model.nodeDataArray.slice();
|
||||
if (str !== null && str !== undefined && str !== "") {
|
||||
for (var i = 0; i < items.length; i += 0) {
|
||||
var item = items[i];
|
||||
if (item.type.toLowerCase().indexOf(str.toLowerCase()) === -1) {
|
||||
items.splice(i, 1);
|
||||
}
|
||||
else i++;
|
||||
}
|
||||
furniturePalette.model.nodeDataArray = items;
|
||||
}
|
||||
else furniturePalette.model.nodeDataArray = ui.furnitureData;
|
||||
furniturePalette.updateAllRelationshipsFromData();
|
||||
}
|
||||
|
||||
/*
|
||||
* Change the "checked" value of checkboxes in the Options Menu, and to have those changes reflected in app behavior / model data
|
||||
* @param {String} id The ID of the changed checkbox
|
||||
*/
|
||||
FloorplanUI.prototype.checkboxChanged = function (id) {
|
||||
var floorplan = this.floorplan;
|
||||
var checkboxes = this.state.windows.optionsWindow.checkboxes;
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.startTransaction("change preference");
|
||||
var element = document.getElementById(id);
|
||||
switch (id) {
|
||||
case checkboxes.showGridCheckboxId: {
|
||||
floorplan.grid.visible = element.checked;
|
||||
floorplan.model.modelData.preferences.showGrid = element.checked;
|
||||
break;
|
||||
}
|
||||
case checkboxes.gridSnapCheckboxId: {
|
||||
floorplan.toolManager.draggingTool.isGridSnapEnabled = element.checked;
|
||||
floorplan.model.modelData.preferences.gridSnap = element.checked;
|
||||
break;
|
||||
}
|
||||
case checkboxes.wallGuidelinesCheckboxId: floorplan.model.modelData.preferences.showWallGuidelines = element.checked; break;
|
||||
case checkboxes.wallLengthsCheckboxId: floorplan.model.modelData.preferences.showWallLengths = element.checked; floorplan.updateWallDimensions(); break;
|
||||
case checkboxes.wallAnglesCheckboxId: floorplan.model.modelData.preferences.showWallAngles = element.checked; floorplan.updateWallAngles(); break;
|
||||
case checkboxes.smallWallAnglesCheckboxId: floorplan.model.modelData.preferences.showOnlySmallWallAngles = element.checked; floorplan.updateWallAngles(); break;
|
||||
}
|
||||
floorplan.commitTransaction("change preference");
|
||||
floorplan.skipsUndoManager = false;
|
||||
}
|
||||
|
||||
// Adjust units based on the selected radio button in the Options Menu
|
||||
FloorplanUI.prototype.changeUnits = function() {
|
||||
var floorplan = this.floorplan;
|
||||
floorplan.startTransaction("set units");
|
||||
var prevUnits = floorplan.model.modelData.units;
|
||||
var radios = document.forms[this.state.windows.optionsWindow.unitsFormId].elements[this.state.windows.optionsWindow.unitsFormName];
|
||||
for (var i = 0; i < radios.length; i++) {
|
||||
if (radios[i].checked) {
|
||||
floorplan.model.setDataProperty(floorplan.model.modelData, "units", radios[i].id);
|
||||
}
|
||||
}
|
||||
var units = floorplan.model.modelData.units;
|
||||
switch (units) {
|
||||
case 'centimeters': floorplan.model.setDataProperty(floorplan.model.modelData, "unitsAbbreviation", 'cm'); break;
|
||||
case 'meters': floorplan.model.setDataProperty(floorplan.model.modelData, "unitsAbbreviation", 'm'); break;
|
||||
case 'feet': floorplan.model.setDataProperty(floorplan.model.modelData, "unitsAbbreviation", 'ft'); break;
|
||||
case 'inches': floorplan.model.setDataProperty(floorplan.model.modelData, "unitsAbbreviation", 'in'); break;
|
||||
}
|
||||
var unitsAbbreviation = floorplan.model.modelData.unitsAbbreviation;
|
||||
// update all units boxes with new units
|
||||
var unitAbbrevInputs = document.getElementsByClassName(this.state.unitsBoxClass);
|
||||
for (var i = 0; i < unitAbbrevInputs.length; i++) {
|
||||
unitAbbrevInputs[i].value = unitsAbbreviation;
|
||||
}
|
||||
var unitInputs = document.getElementsByClassName(this.state.unitsInputClass);
|
||||
for (var i = 0; i < unitInputs.length; i++) {
|
||||
var input = unitInputs[i];
|
||||
floorplan.model.setDataProperty(floorplan.model.modelData, "units", prevUnits);
|
||||
var value = floorplan.convertUnitsToPixels(input.value);
|
||||
floorplan.model.setDataProperty(floorplan.model.modelData, "units", units)
|
||||
value = floorplan.convertPixelsToUnits(value);
|
||||
input.value = value;
|
||||
}
|
||||
if (floorplan.selection.count === 1) this.setSelectionInfo(floorplan.selection.first()); // reload node info measurements according to new units
|
||||
floorplan.commitTransaction("set units");
|
||||
}
|
||||
|
||||
/*
|
||||
* Set current tool (selecting/dragging or wallbuilding/reshaping)
|
||||
* @param {String} string Informs what behavior to switch to. Accepted values: "dragging", "wallbuilding"
|
||||
*/
|
||||
FloorplanUI.prototype.setBehavior = function (string) {
|
||||
var floorplan = this.floorplan;
|
||||
var ui = this;
|
||||
var wallBuildingTool = floorplan.toolManager.mouseDownTools.elt(0);
|
||||
var wallReshapingTool = floorplan.toolManager.mouseDownTools.elt(3);
|
||||
// style the current tool HTML button accordingly
|
||||
var elements = document.getElementsByClassName(this.state.setBehaviorClass);
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
var el = elements[i];
|
||||
if (el.id === string + "Button") el.style.backgroundColor = '#4b545f';
|
||||
else el.style.backgroundColor = '#bbbbbb';
|
||||
}
|
||||
var wallThicknessBox = document.getElementById(this.state.wallThicknessBoxId)
|
||||
if (string === 'wallBuilding') {
|
||||
wallBuildingTool.isEnabled = true;
|
||||
wallReshapingTool.isEnabled = false;
|
||||
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.startTransaction("change wallThickness");
|
||||
// create walls with wallThickness in input box
|
||||
floorplan.model.setDataProperty(floorplan.model.modelData, 'wallThickness', parseFloat(document.getElementById(ui.state.wallThicknessInputId).value));
|
||||
var wallThickness = floorplan.model.modelData.wallThickness;
|
||||
if (isNaN(wallThickness)) floorplan.model.setDataProperty(floorplan.model.modelData, 'wallThickness', 5);
|
||||
else {
|
||||
var width = floorplan.convertUnitsToPixels(wallThickness);
|
||||
floorplan.model.setDataProperty(floorplan.model.modelData, 'wallThickness', width);
|
||||
}
|
||||
floorplan.commitTransaction("change wallThickness");
|
||||
floorplan.skipsUndoManager = false;
|
||||
wallThicknessBox.style.visibility = 'visible';
|
||||
wallThicknessBox.style.display = 'inline-block';
|
||||
ui.setDiagramHelper("Click and drag on the diagram to draw a wall (hold SHIFT for 45 degree angles)");
|
||||
}
|
||||
if (string === 'dragging') {
|
||||
wallBuildingTool.isEnabled = false;
|
||||
wallReshapingTool.isEnabled = true;
|
||||
wallThicknessBox.style.visibility = 'hidden';
|
||||
wallThicknessBox.style.display = 'none';
|
||||
}
|
||||
// clear resize adornments on walls/windows, if there are any
|
||||
floorplan.nodes.iterator.each(function (n) { n.clearAdornments(); })
|
||||
floorplan.clearSelection();
|
||||
}
|
||||
|
||||
/*
|
||||
* Populating UI Windows from Floorplan data:
|
||||
* Update UI, Update Statistics, Fill Rows With Nodes, Set Selection Info, Set Color, Set Height, Set Width, Apply Selection Changes
|
||||
*/
|
||||
|
||||
// Update the UI properly in accordance with model.modelData (called only when a new floorplan is loaded or created)
|
||||
FloorplanUI.prototype.updateUI = function () {
|
||||
var floorplan = this.floorplan;
|
||||
var modelData = floorplan.model.modelData;
|
||||
var checkboxes = this.state.windows.optionsWindow.checkboxes;
|
||||
if (floorplan.floorplanUI) floorplan.floorplanUI.changeUnits();
|
||||
document.getElementById(this.state.wallThicknessInputId).value = floorplan.convertPixelsToUnits(modelData.wallThickness);
|
||||
// update options GUI based on floorplan.model.modelData.preferences
|
||||
var preferences = modelData.preferences;
|
||||
document.getElementById(checkboxes.showGridCheckboxId).checked = preferences.showGrid;
|
||||
document.getElementById(checkboxes.gridSnapCheckboxId).checked = preferences.gridSnap;
|
||||
document.getElementById(checkboxes.wallGuidelinesCheckboxId).checked = preferences.showWallGuidelines;
|
||||
document.getElementById(checkboxes.wallLengthsCheckboxId).checked = preferences.showWallLengths;
|
||||
document.getElementById(checkboxes.wallAnglesCheckboxId).checked = preferences.showWallAngles;
|
||||
document.getElementById(checkboxes.smallWallAnglesCheckboxId).checked = preferences.showOnlySmallWallAngles;
|
||||
}
|
||||
|
||||
// Update all statistics in Statistics Window - called when a Floorplan's model is changed
|
||||
FloorplanUI.prototype.updateStatistics = function () {
|
||||
var floorplan = this.floorplan;
|
||||
var statsWindow = this.state.windows.statisticsWindow;
|
||||
var element = document.getElementById(statsWindow.textDivId);
|
||||
if (element) {
|
||||
element.innerHTML = "<div class='row'><div class='col-2' style='height: 165px; overflow: auto;'> Item Types <table id='" + statsWindow.numsTableId + "'></table></div><div class='col-2'> Totals <table id='totalsTable'></table></div></div>";
|
||||
// fill Item Types table with node type/count of all nodes in diagram
|
||||
var numsTable = document.getElementById(statsWindow.numsTableId);
|
||||
|
||||
// get all palette nodes associated with this Floorplan
|
||||
var palettes = floorplan.palettes;
|
||||
var allPaletteNodes = [];
|
||||
for (var i = 0; i < palettes.length; i++) {
|
||||
allPaletteNodes = allPaletteNodes.concat(palettes[i].model.nodeDataArray);
|
||||
}
|
||||
|
||||
for (var i = 0; i < allPaletteNodes.length; i++) {
|
||||
var type = allPaletteNodes[i].type;
|
||||
var num = floorplan.findNodesByExample({ type: type }).count;
|
||||
if (num > 0) // only display data for nodes that exist on the diagram
|
||||
numsTable.innerHTML += "<tr class='data'> <td style='float: left;'>" + type + "</td> <td style='float: right;'> " + num + "</td></tr>";
|
||||
}
|
||||
// fill Totals table with lengths of all walls
|
||||
totalsTable = document.getElementById('totalsTable');
|
||||
var walls = floorplan.findNodesByExample({ category: "WallGroup" });
|
||||
var totalLength = 0;
|
||||
walls.iterator.each(function (wall) {
|
||||
var wallLength = Math.sqrt(wall.data.startpoint.distanceSquaredPoint(wall.data.endpoint));
|
||||
totalLength += wallLength;
|
||||
});
|
||||
totalLength = floorplan.convertPixelsToUnits(totalLength).toFixed(2);
|
||||
var unitsAbbreviation = floorplan.model.modelData.unitsAbbreviation;
|
||||
totalsTable.innerHTML += "<tr class='data'><td style='float: left;'>Wall Lengths</td><td style='float: right;'>" + totalLength + unitsAbbreviation + "</td></tr>";
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper function for setSelectionInfo(); displays all nodes in a given set in a given 1 or 2 rows in a given HTML element
|
||||
* @param {Iterable | Array} iterator A iterable collection of nodes to display in rows
|
||||
* @param {String} element The ID of the HTML element to fill with this data
|
||||
* @param {String} selectedKey The key of the currently selected node -- this node's name will be styled differently in the rows
|
||||
*/
|
||||
// TODO some repetitive code here
|
||||
FloorplanUI.prototype.fillRowsWithNodes = function(iterator, element, selectedKey) {
|
||||
var floorplan = this.floorplan;
|
||||
var ui = this;
|
||||
var arr = [];
|
||||
if (iterator.constructor !== Array) iterator.each(function (p) { arr.push(p); });
|
||||
else arr = iterator;
|
||||
|
||||
// helper
|
||||
function makeOnClick(key) {
|
||||
return ui.name + '.setSelectionInfo(' + ui.floorplanName + '.findPartForKey(' + "'" + key + "'" + '))';
|
||||
}
|
||||
|
||||
for (var i = 0; i < arr.length; i += 2) {
|
||||
if (arr[i].data === null) { this.setSelectionInfo('Nothing selected'); return; }
|
||||
var key1 = arr[i].data.key; // keys used to locate the node if clicked on...
|
||||
var name1 = (arr[i].data.caption !== "MultiPurposeNode") ? arr[i].data.caption : arr[i].data.text; // ... names are editable, so users can distinguish between nodes
|
||||
// if there are two nodes for this row...
|
||||
if (arr[i + 1] != undefined && arr[i + 1] != null) {
|
||||
var key2 = arr[i + 1].data.key;
|
||||
var name2 = (arr[i + 1].data.caption !== "MultiPurposeNode") ? arr[i + 1].data.caption : arr[i + 1].data.text;
|
||||
// if there's a non-null selectedKey, highlight the selected node in the list
|
||||
if (key1 === selectedKey) element.innerHTML += '<div class="row"><div class="col-2"><p class="data clickable selectedKey" onclick="' + makeOnClick(key1) + '">' + name1 +
|
||||
'</p></div><div class="col-2"><p class="data clickable" onclick="' + makeOnClick(key2) + '">' + name2 + '</p></div></div>';
|
||||
|
||||
else if (key2 === selectedKey) element.innerHTML += '<div class="row"><div class="col-2"><p class="data clickable" onclick="' + makeOnClick(key1) + '">' + name1 +
|
||||
'</p></div><div class="col-2"><p class="data clickable selectedKey" onclick="' + makeOnClick(key2) + '">' + name2 + '</p></div></div>';
|
||||
|
||||
else element.innerHTML += '<div class="row"><div class="col-2"><p class="data clickable"' + 'onclick="' + makeOnClick(key1) + '">' + name1 +
|
||||
'</p></div><div class="col-2"><p class="data clickable"' + 'onclick="' + makeOnClick(key2) + '">' + name2 + '</p></div></div>';
|
||||
}
|
||||
// if there's only one node for this row...
|
||||
else {
|
||||
if (key1 === selectedKey) element.innerHTML += '<div class="row"><div class="col-2"><p class="data clickable selectedKey" onclick="' + makeOnClick(key1) + '">' + name1 + '</p></div></div>';
|
||||
else element.innerHTML += '<div class="row"><div class="col-2"><p class="data clickable" onclick="' + makeOnClick(key1) + '">' + name1 + '</p></div></div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Displays dynamic, editable info about selection in Selection Info window (height/length, width, name, group info, color, etc.)
|
||||
* @param {Node | String} node Can be: Reference to Node / Group, go.Node/go.Group key, "Selection" (indicating multi-selection), "Nothing selected"
|
||||
*/
|
||||
FloorplanUI.prototype.setSelectionInfo = function(node) {
|
||||
var floorplan = this.floorplan;
|
||||
var ui = this;
|
||||
if (node instanceof go.GraphObject) node = node.part;
|
||||
var selectionInfoWindow = this.state.windows.selectionInfoWindow;
|
||||
var element = document.getElementById(selectionInfoWindow.textDivId);
|
||||
var state = this.state;
|
||||
var infoWindow = document.getElementById(selectionInfoWindow.id);
|
||||
if (element === null || infoWindow === null) return;
|
||||
if (node === 'Nothing selected' || node.layer === null || node === null) { element.innerHTML = '<p>' + node + '</p>'; return; }
|
||||
|
||||
// if there are multiple nodes selected, show all their names, allowing user to click on the node they want
|
||||
if (node === 'Selection: ') {
|
||||
var selectionIterator = floorplan.selection.iterator; var arr = [];
|
||||
element.innerHTML = '<p id="name"> Selection (' + selectionIterator.count + ' items selected): </p>';
|
||||
this.fillRowsWithNodes(selectionIterator, element, null);
|
||||
infoWindow.style.height = document.getElementById(selectionInfoWindow.textDivId).offsetHeight + document.getElementById(selectionInfoWindow.handleId).offsetHeight + 5 + 'px';
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO clean this to be usable by a more general template scheme
|
||||
// if we have one node selected, gather pertinent information for that node....
|
||||
floorplan.select(node);
|
||||
var name = ''; var length; var width; var nodeGroupCount = 0; var notes = node.data.notes;
|
||||
// get node name
|
||||
if (node.category === 'MultiPurposeNode') name = node.data.text;
|
||||
else name = node.data.caption;
|
||||
// get node height / width / length (dependent on node category)
|
||||
// Wall Groups
|
||||
if (node.category === 'WallGroup') {
|
||||
length = floorplan.convertPixelsToUnits(Math.sqrt(node.data.startpoint.distanceSquared(node.data.endpoint.x, node.data.endpoint.y))).toFixed(2); // wall length
|
||||
thickness = floorplan.convertPixelsToUnits(node.data.thickness).toFixed(2); // wall thickness
|
||||
}
|
||||
// Wall Parts (i.e. Door / Window Nodes)
|
||||
else if (node.category === 'DoorNode' || node.category === "WindowNode") {
|
||||
length = floorplan.convertPixelsToUnits(node.data.length).toFixed(2);
|
||||
}
|
||||
// Generic Groups
|
||||
else if (node.data.isGroup && node.category !== "WallGroup") {
|
||||
height = floorplan.convertPixelsToUnits(node.actualBounds.height).toFixed(2);
|
||||
width = floorplan.convertPixelsToUnits(node.actualBounds.width).toFixed(2);
|
||||
}
|
||||
// Furniture Nodes
|
||||
else {
|
||||
height = floorplan.convertPixelsToUnits(node.data.height).toFixed(2);
|
||||
width = floorplan.convertPixelsToUnits(node.data.width).toFixed(2);
|
||||
}
|
||||
// get node group info
|
||||
if (node.containingGroup != null && node.containingGroup != undefined) {
|
||||
var nodeGroupParts = node.containingGroup.memberParts;
|
||||
nodeGroupCount = nodeGroupParts.count;
|
||||
}
|
||||
if (node.data.isGroup) {
|
||||
var nodeGroupParts = node.memberParts;
|
||||
nodeGroupCount = nodeGroupParts.count;
|
||||
}
|
||||
var unitsAbbreviation = floorplan.model.modelData.unitsAbbreviation;
|
||||
|
||||
// display information in the selection info window
|
||||
element.innerHTML = '<p id="name" >Name: ' + '<input id="nameInput" class="nameNotesInput" value="' + name + '"/>' + '</p>'; // name
|
||||
element.innerHTML += "<p>Notes: <textarea id='"+ selectionInfoWindow.notesTextareaId +"' class='nameNotesInput' >" + notes + "</textarea></p>"; // notes
|
||||
// display color as a color picker element (furniture nodes only)
|
||||
if (!node.data.isGroup && node.data.category !== "DoorNode" && node.data.category !== "WindowNode") {
|
||||
element.innerHTML += '<p>Color: <input type="color" id="'+ selectionInfoWindow.colorPickerId +'" value="' + node.data.color + '" name="selectionColor"></input></p>';
|
||||
}
|
||||
|
||||
// display ONLY "Length" input (Door / Window Nodes only) (this is still technically the "width input" as far as IDs are concerned)
|
||||
if (node.category === "DoorNode" || node.category === "WindowNode") element.innerHTML += '<div class="row"><p id="'+ selectionInfoWindow.widthLabelId +'" class="data">Length: <br/><input id = "' + selectionInfoWindow.widthInputId + '" class = "dimensionsInput" name = "width" value = "' + length + '"/>'
|
||||
+ '<input id="widthUnits" class="' + state.unitsBoxClass + '" value=' + unitsAbbreviation + ' disabled/></p>';
|
||||
// for walls "width" is displayed as "length", "height" is displayed as "Thickness"
|
||||
else if (node.category === 'WallGroup') {
|
||||
element.innerHTML += '<div class="row"><div class="col-2"><p id="' + selectionInfoWindow.heightLabelId + '" class="data">Thickness: <br/><input id ="' + selectionInfoWindow.heightInputId + '" class = "dimensionsInput" name = "height" value = "' + thickness
|
||||
+ '"/><input id="heightUnits" class="' + state.unitsBoxClass + '" value=' + unitsAbbreviation + ' disabled/></p> ' + '</div><div class="col-2"><p class="data">Length: <br/><input id="' + selectionInfoWindow.widthInputId + '" class="dimensionsInput" value = "'
|
||||
+ length + '"/><input id="widthUnits" class="' + state.unitsBoxClass + '" value="' + unitsAbbreviation + '" disabled/></p>' + '</p></div></div>';
|
||||
}
|
||||
// display editable properties height and width (non Door / Window nodes)
|
||||
else element.innerHTML += '<div class="row"><div class="col-2"><p id="' + selectionInfoWindow.heightLabelId + '" class="data">Height: <br/><input id ="' + selectionInfoWindow.heightInputId + '" class = "dimensionsInput" name = "height" value = "' + height
|
||||
+ '"/><input id="heightUnits" class="' + state.unitsBoxClass + '" value=' + unitsAbbreviation + ' disabled/></p> ' + '</div><div class="col-2"><p class="data">Width: <br/><input id="' + selectionInfoWindow.widthInputId + '" class="dimensionsInput" value = "'
|
||||
+ width + '"/><input id="widthUnits" class="' + state.unitsBoxClass + '" value="' + unitsAbbreviation + '" disabled/></p>' + '</p></div></div>';
|
||||
|
||||
// do not allow height or width adjustment for group info
|
||||
if (node.data.isGroup && node.category !== "WallGroup") {
|
||||
document.getElementById(selectionInfoWindow.heightInputId).disabled = true;
|
||||
document.getElementById(selectionInfoWindow.widthInputId).disabled = true;
|
||||
}
|
||||
|
||||
// "Apply Changes" button
|
||||
element.innerHTML += '<div class="row"> <button id="applySelectionChanges" onClick="' + this.name + '.applySelectionChanges()">Apply Changes</button></div>';
|
||||
|
||||
// display group info for standard groups, wallParts for walls
|
||||
var groupName = null; var groupKey = null; var selectedKey = "";
|
||||
if (node.data.isGroup === true) {
|
||||
groupName = node.data.caption;
|
||||
groupKey = node.data.key;
|
||||
selectedKey = "selectedKey"; // the 'group' node is selected; make it blue to show this
|
||||
}
|
||||
if (node.containingGroup !== null) {
|
||||
groupName = node.containingGroup.data.caption;
|
||||
groupKey = node.containingGroup.data.key;
|
||||
}
|
||||
if (groupName !== null) {
|
||||
groupKey = "'" + groupKey + "'";
|
||||
element.innerHTML += '<div class="row data" id="' + selectionInfoWindow.nodeGroupInfoId +'"> <span class="clickable ' + selectedKey + '" onclick="' + ui.name + '.setSelectionInfo(' + ui.floorplanName + '.findPartForKey(' + groupKey + '))">' +
|
||||
groupName + '</span> Info (' + nodeGroupCount + ' member(s) in <span class="clickable ' + selectedKey + '" onclick="' + ui.name + '.setSelectionInfo(' + ui.floorplanName + '.findPartForKey(' + groupKey + '))">' + groupName + '</span>) </div>';
|
||||
if (nodeGroupCount != 0) ui.fillRowsWithNodes(nodeGroupParts, document.getElementById(selectionInfoWindow.nodeGroupInfoId), node.data.key);
|
||||
}
|
||||
|
||||
var nameInput = document.getElementById(selectionInfoWindow.nameInputId);
|
||||
if (!floorplan.isReadOnly) {
|
||||
// dynamically adjust name of selected node based on user input
|
||||
nameInput.addEventListener('input', function (e) {
|
||||
var value = nameInput.value;
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.startTransaction("rename node");
|
||||
if (value === null || value === "" || value === undefined) { floorplan.commitTransaction("rename node"); return; }
|
||||
floorplan.model.setDataProperty(node.data, "caption", value);
|
||||
floorplan.model.setDataProperty(node.data, "text", value); // if node is a multi purpose node, update the text on it
|
||||
floorplan.commitTransaction("rename node");
|
||||
floorplan.skipsUndoManager = false;
|
||||
});
|
||||
|
||||
// dynamically adjust notes of selected node based on user input
|
||||
var notesTextarea = document.getElementById(selectionInfoWindow.notesTextareaId);
|
||||
notesTextarea.addEventListener('input', function (e) {
|
||||
var value = notesTextarea.value;
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.startTransaction("edit node notes");
|
||||
if (value === null || value === undefined) return;
|
||||
floorplan.model.setDataProperty(node.data, "notes", value);
|
||||
floorplan.commitTransaction("edit node notes");
|
||||
floorplan.skipsUndoManager = false;
|
||||
});
|
||||
infoWindow.style.height = document.getElementById(selectionInfoWindow.textDivId).offsetHeight + document.getElementById(selectionInfoWindow.handleId).offsetHeight + 5 + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
// Triggered by "Apply Changes"; set model data for fill color of the current selection
|
||||
FloorplanUI.prototype.setColor = function () {
|
||||
var floorplan = this.floorplan;
|
||||
var node = floorplan.selection.first();
|
||||
var colorPicker = document.getElementById(this.state.windows.selectionInfoWindow.colorPickerId);
|
||||
if (colorPicker !== null) {
|
||||
floorplan.startTransaction("recolor node");
|
||||
floorplan.model.setDataProperty(node.data, "color", colorPicker.value);
|
||||
floorplan.model.setDataProperty(node.data, "stroke", invertColor(colorPicker.value))
|
||||
floorplan.commitTransaction("recolor node");
|
||||
}
|
||||
}
|
||||
|
||||
// Triggered by "Apply Changes"; set model data for height of the currently selected node (also handles door length for doors, wall length for walls)
|
||||
FloorplanUI.prototype.setHeight = function () {
|
||||
var heightInput = document.getElementById(this.state.windows.selectionInfoWindow.heightInputId);
|
||||
if (heightInput) {
|
||||
var floorplan = this.floorplan;
|
||||
var ui = this;
|
||||
var node = floorplan.selection.first();
|
||||
var value = parseFloat(floorplan.convertUnitsToPixels(heightInput.value));
|
||||
if (isNaN(value)) {
|
||||
alert("Please enter a valid number");
|
||||
ui.setSelectionInfo(node, floorplan);
|
||||
return;
|
||||
}
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.startTransaction("resize node");
|
||||
if (!floorplan.isReadOnly) {
|
||||
// Case: Furniture Nodes and Window Nodes; basic height adjustment
|
||||
if (node.category !== 'WallGroup' && node.category !== 'DoorNode') {
|
||||
floorplan.model.setDataProperty(node.data, "height", value);
|
||||
}
|
||||
// Case: Wall Groups; set wall's data.strokeWidth
|
||||
else if (node.category === 'WallGroup') {
|
||||
floorplan.model.setDataProperty(node.data, "thickness", value);
|
||||
node.memberParts.iterator.each(function (part) {
|
||||
if (part.category === 'DoorNode') floorplan.model.setDataProperty(part.data, "doorOpeningHeight", value);
|
||||
if (part.category === 'WindowNode') floorplan.model.setDataProperty(part.data, "height", value);
|
||||
});
|
||||
}
|
||||
// Note: Door Nodes are purposefully unaccounted for, as width (length) adjustment (in setWidth()) also adjusts node height
|
||||
}
|
||||
floorplan.commitTransaction("resize node");
|
||||
floorplan.updateWallDimensions(floorplan);
|
||||
floorplan.skipsUndoManager = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Triggered by "Apply Changes"; set model data for width of the currently selected node
|
||||
FloorplanUI.prototype.setWidth = function() {
|
||||
var floorplan = this.floorplan;
|
||||
var ui = this;
|
||||
var node = floorplan.selection.first();
|
||||
var widthInput = document.getElementById(this.state.windows.selectionInfoWindow.widthInputId);
|
||||
if (widthInput === null) return;
|
||||
var value = parseFloat(floorplan.convertUnitsToPixels(widthInput.value));
|
||||
if (isNaN(value)) {
|
||||
alert("Please enter a valid number");
|
||||
ui.setSelectionInfo(node, floorplan);
|
||||
return;
|
||||
}
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.startTransaction("resize node");
|
||||
if (!floorplan.isReadOnly) {
|
||||
// Case: Window / Door Nodes (part.width is part.data.length), keeps windows and doors within wall boundaries (and surrounding wall part boundaries)
|
||||
if (node.category === 'WindowNode' || node.category === "DoorNode") {
|
||||
var wall = floorplan.findPartForKey(node.data.group);
|
||||
var loc = node.location.copy();
|
||||
// constrain max width "value" by the free stretch on the wall "node" is in
|
||||
if (wall !== null) {
|
||||
var containingStretch = getWallPartStretch(node);
|
||||
var stretchLength = Math.sqrt(containingStretch.point1.distanceSquaredPoint(containingStretch.point2));
|
||||
if (stretchLength < value) {
|
||||
value = stretchLength;
|
||||
loc = new go.Point((containingStretch.point1.x + containingStretch.point2.x) / 2,
|
||||
(containingStretch.point1.y + containingStretch.point2.y) / 2);
|
||||
}
|
||||
}
|
||||
floorplan.model.setDataProperty(node.data, "length", value);
|
||||
node.location = loc;
|
||||
floorplan.updateWallDimensions();
|
||||
}
|
||||
// Case: Wall Groups; wall length adjustment; do not allow walls to be shorter than the distance between their fathest apart wallParts
|
||||
else if (node.category === "WallGroup") {
|
||||
var sPt = node.data.startpoint.copy();
|
||||
var ePt = node.data.endpoint.copy();
|
||||
var angle = sPt.directionPoint(ePt);
|
||||
|
||||
var midPoint = new go.Point(((sPt.x + ePt.x) / 2), ((sPt.y + ePt.y) / 2));
|
||||
var newEpt = new go.Point((midPoint.x + (value / 2)), midPoint.y);
|
||||
var newSpt = new go.Point((midPoint.x - (value / 2)), midPoint.y);
|
||||
newEpt.offset(-midPoint.x, -midPoint.y).rotate(angle).offset(midPoint.x, midPoint.y);
|
||||
newSpt.offset(-midPoint.x, -midPoint.y).rotate(angle).offset(midPoint.x, midPoint.y);
|
||||
|
||||
// Edge Case 1: The user has input a length shorter than the edge wallPart's endpoints allow
|
||||
// find the endpoints of the wallparts closest to the endpoints of the wall
|
||||
var closestPtToSpt = null; var farthestPtFromSpt;
|
||||
var closestDistToSpt = Number.MAX_VALUE; var farthestDistFromSpt = 0;
|
||||
node.memberParts.iterator.each(function (wallPart) {
|
||||
var endpoints = getWallPartEndpoints(wallPart);
|
||||
var endpoint1 = endpoints[0];
|
||||
var endpoint2 = endpoints[1];
|
||||
var distance1 = Math.sqrt(endpoint1.distanceSquaredPoint(sPt));
|
||||
var distance2 = Math.sqrt(endpoint2.distanceSquaredPoint(sPt));
|
||||
|
||||
if (distance1 < closestDistToSpt) {
|
||||
closestDistToSpt = distance1;
|
||||
closestPtToSpt = endpoint1;
|
||||
} if (distance1 > farthestDistFromSpt) {
|
||||
farthestDistFromSpt = distance1;
|
||||
farthestPtFromSpt = endpoint1;
|
||||
} if (distance2 < closestDistToSpt) {
|
||||
closestDistToSpt = distance2;
|
||||
closestPtToSpt = endpoint2;
|
||||
} if (distance2 > farthestDistFromSpt) {
|
||||
farthestDistFromSpt = distance2;
|
||||
farthestPtFromSpt = endpoint2;
|
||||
}
|
||||
});
|
||||
|
||||
if (closestPtToSpt !== null) {
|
||||
// if the proposed length is smaller than the minDistance, set wall length to minDistance
|
||||
var proposedDistance = Math.sqrt(newSpt.distanceSquaredPoint(newEpt));
|
||||
var minDistance = Math.sqrt(closestPtToSpt.distanceSquaredPoint(farthestPtFromSpt));
|
||||
if (proposedDistance < minDistance) {
|
||||
newSpt = closestPtToSpt;
|
||||
newEpt = farthestPtFromSpt;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Edge Case 2: The new wall endpoints constructed based on user input do not generate a wall too short for the wall's edge wallPart's endpoints;
|
||||
* however, there is/are a/some wallPart(s) that do not fit along the new wall endpoints (due to midpoint construction)
|
||||
* if a wallPart endpoint is outside the line created by newSpt and newEpt, adjust the endpoints accordingly
|
||||
*/
|
||||
var farthestPtFromWallPt = null; var farthestFromWallPtDist = 0;
|
||||
node.memberParts.iterator.each(function (part) {
|
||||
var endpoints = getWallPartEndpoints(part);
|
||||
// check for endpoints of wallParts not along the line segment made by newSpt and newEpt
|
||||
for (var i = 0; i < endpoints.length; i++) {
|
||||
var point = endpoints[i];
|
||||
var distanceToStartPoint = parseFloat(Math.sqrt(point.distanceSquaredPoint(newSpt)).toFixed(2));
|
||||
var distanceToEndPoint = parseFloat(Math.sqrt(point.distanceSquaredPoint(newEpt)).toFixed(2));
|
||||
var wallLength = parseFloat(Math.sqrt(newSpt.distanceSquaredPoint(newEpt)).toFixed(2));
|
||||
if ((distanceToStartPoint + distanceToEndPoint).toFixed(2) !== wallLength.toFixed(2)) {
|
||||
var testDistance = Math.sqrt(point.distanceSquaredPoint(newSpt));
|
||||
if (testDistance > farthestFromWallPtDist) {
|
||||
farthestFromWallPtDist = testDistance;
|
||||
farthestPtFromWallPt = point;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// if a wallPart endpoint is outside the wall, adjust the endpoints of the wall to accomodate it
|
||||
if (farthestPtFromWallPt !== null) {
|
||||
var distance = Math.sqrt(newSpt.distanceSquaredPoint(newEpt));
|
||||
if (farthestPtFromWallPt.distanceSquaredPoint(newSpt) < farthestPtFromWallPt.distanceSquaredPoint(newEpt)) {
|
||||
newSpt = farthestPtFromWallPt;
|
||||
var totalLength = Math.sqrt(newSpt.distanceSquaredPoint(newEpt));
|
||||
newEpt = new go.Point(newSpt.x + ((distance / totalLength) * (newEpt.x - newSpt.x)),
|
||||
newSpt.y + ((distance / totalLength) * (newEpt.y - newSpt.y)));
|
||||
} else {
|
||||
newEpt = farthestPtFromWallPt;
|
||||
var totalLength = Math.sqrt(newSpt.distanceSquaredPoint(newEpt));
|
||||
newSpt = new go.Point(newEpt.x + ((distance / totalLength) * (newSpt.x - newEpt.x)),
|
||||
newEpt.y + ((distance / totalLength) * (newSpt.y - newEpt.y)));
|
||||
}
|
||||
}
|
||||
|
||||
floorplan.model.setDataProperty(node.data, "startpoint", newSpt);
|
||||
floorplan.model.setDataProperty(node.data, "endpoint", newEpt);
|
||||
floorplan.updateWall(node);
|
||||
}
|
||||
// Case: Standard / Multi-Purpose Nodes; basic width ajustment
|
||||
else floorplan.model.setDataProperty(node.data, "width", value);
|
||||
}
|
||||
floorplan.commitTransaction("resize node");
|
||||
floorplan.skipsUndoManager = false;
|
||||
}
|
||||
|
||||
// Set height, width, and color of the selection based on user input in the Selection Info Window
|
||||
FloorplanUI.prototype.applySelectionChanges = function() {
|
||||
var floorplan = this.floorplan;
|
||||
var ui = this;
|
||||
this.setHeight();
|
||||
this.setWidth();
|
||||
this.setColor();
|
||||
ui.setSelectionInfo(floorplan.selection.first(), floorplan);
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Floorplanner Constants
|
||||
*/
|
||||
|
||||
// The diagram model data for the default floorplanner
|
||||
DEFAULT_MODEL_DATA = {
|
||||
"class": "go.GraphLinksModel",
|
||||
"modelData": { "units": "centimeters", "unitsAbbreviation": "cm", "unitsConversionFactor": 2, "gridSize": 10, "wallThickness": 5, "preferences": { "showWallGuidelines": true, "showWallLengths": true, "showWallAngles": true, "showOnlySmallWallAngles": true, "showGrid": true, "gridSnap": true } },
|
||||
"nodeDataArray": [
|
||||
{ "key": "wall", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -430, "y": 240 }, "endpoint": { "class": "go.Point", "x": -430, "y": -240 }, "thickness": 10, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall3", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -430, "y": -240 }, "endpoint": { "class": "go.Point", "x": 260, "y": -240 }, "thickness": 10, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall4", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 260, "y": -240 }, "endpoint": { "class": "go.Point", "x": 260, "y": 240 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall5", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 260, "y": 140 }, "endpoint": { "class": "go.Point", "x": 550, "y": 140 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall6", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 260, "y": 240 }, "endpoint": { "class": "go.Point", "x": 390, "y": 240 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall7", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 550, "y": 140 }, "endpoint": { "class": "go.Point", "x": 550, "y": 400 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall8", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 390, "y": 240 }, "endpoint": { "class": "go.Point", "x": 390, "y": 400 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall9", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 390, "y": 400 }, "endpoint": { "class": "go.Point", "x": 550, "y": 400 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "staircase", "color": "#ffffff", "stroke": "#000000", "caption": "Staircase", "type": "Staircase", "geo": "F1 M0 0 L 0 100 250 100 250 0 0 0 M25 100 L 25 0 M 50 100 L 50 0 M 75 100 L 75 0 M 100 100 L 100 0 M 125 100 L 125 0 M 150 100 L 150 0 M 175 100 L 175 0 M 200 100 L 200 0 M 225 100 L 225 0", "width": 125, "height": 50, "notes": "", "loc": "430 330", "angle": 270 },
|
||||
{ "key": "staircase2", "color": "#ffffff", "stroke": "#000000", "caption": "Staircase", "type": "Staircase", "geo": "F1 M0 0 L 0 100 250 100 250 0 0 0 M25 100 L 25 0 M 50 100 L 50 0 M 75 100 L 75 0 M 100 100 L 100 0 M 125 100 L 125 0 M 150 100 L 150 0 M 175 100 L 175 0 M 200 100 L 200 0 M 225 100 L 225 0", "width": 125, "height": 50, "notes": "", "loc": "500 330", "angle": 270 },
|
||||
{ "key": "wall10", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -230, "y": -130 }, "endpoint": { "class": "go.Point", "x": 70, "y": -130 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall11", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 70, "y": -130 }, "endpoint": { "class": "go.Point", "x": 130, "y": -70 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall12", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 130, "y": -70 }, "endpoint": { "class": "go.Point", "x": 130, "y": 40 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall13", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 130, "y": 40 }, "endpoint": { "class": "go.Point", "x": 70, "y": 100 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall14", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 70, "y": 100 }, "endpoint": { "class": "go.Point", "x": -230, "y": 100 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall15", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -230, "y": -130 }, "endpoint": { "class": "go.Point", "x": -290, "y": -70 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall16", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -290, "y": -70 }, "endpoint": { "class": "go.Point", "x": -290, "y": 40 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall17", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -290, "y": 40 }, "endpoint": { "class": "go.Point", "x": -230, "y": 100 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "door", "category": "DoorNode", "color": "rgba(0, 0, 0, 0)", "caption": "Door", "type": "Door", "length": 56, "doorOpeningHeight": 5, "swing": "left", "notes": "", "loc": "260 188.20000076293945", "group": "wall4", "angle": 90 },
|
||||
{ "key": "diningTable", "color": "#704332", "stroke": "#8FBCCD", "caption": "Dining Table", "type": "Dining Table", "geo": "F1 M 0 0 L 0 100 200 100 200 0 0 0 M 25 0 L 25 -10 75 -10 75 0 M 125 0 L 125 -10 175 -10 175 0 M 200 25 L 210 25 210 75 200 75 M 125 100 L 125 110 L 175 110 L 175 100 M 25 100 L 25 110 75 110 75 100 M 0 75 -10 75 -10 25 0 25", "width": 205, "height": 70.5, "notes": "", "loc": "-80 -20" },
|
||||
{ "key": "wall18", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 550, "y": -240 }, "endpoint": { "class": "go.Point", "x": 550, "y": 140 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall19", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 260, "y": -240 }, "endpoint": { "class": "go.Point", "x": 310, "y": -310 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall20", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 310, "y": -310 }, "endpoint": { "class": "go.Point", "x": 500, "y": -310 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall21", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 500, "y": -310 }, "endpoint": { "class": "go.Point", "x": 550, "y": -240 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "door2", "category": "DoorNode", "color": "rgba(0, 0, 0, 0)", "caption": "Door", "type": "Door", "length": 40, "doorOpeningHeight": 5, "swing": "left", "notes": "", "loc": "-290 5.200000762939453", "group": "wall16", "angle": 90 },
|
||||
{ "category": "WindowNode", "key": "window", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 5, "length": 230, "notes": "", "loc": "-80.80000019073492 -130.00000000000006", "group": "wall10" },
|
||||
{ "category": "WindowNode", "key": "window2", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 5, "length": 230, "notes": "", "loc": "-80.80000019073492 100.00000000000003", "group": "wall14", "angle": 180 },
|
||||
{ "category": "WindowNode", "key": "window3", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 60, "notes": "", "loc": "-400 -240", "group": "wall3" },
|
||||
{ "category": "WindowNode", "key": "window32", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 60, "notes": "", "loc": "-234.80000019073486 -240", "group": "wall3" },
|
||||
{ "category": "WindowNode", "key": "window4", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 60, "notes": "", "loc": "-89.80000019073486 -240", "group": "wall3" },
|
||||
{ "category": "WindowNode", "key": "window5", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 60, "notes": "", "loc": "80.19999980926514 -240", "group": "wall3" },
|
||||
{ "category": "WindowNode", "key": "window6", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 60, "notes": "", "loc": "201.19999980926514 -240", "group": "wall3" },
|
||||
{ "category": "WindowNode", "key": "window7", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 175, "notes": "", "loc": "-430 -152.5", "group": "wall", "angle": 90 },
|
||||
{ "category": "WindowNode", "key": "window8", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 233, "notes": "", "loc": "-430 123.49999999999999", "group": "wall", "angle": 270 },
|
||||
{ "key": "wall32", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -430, "y": 240 }, "endpoint": { "class": "go.Point", "x": 260, "y": 240 }, "thickness": 10, "isGroup": true, "notes": "" },
|
||||
{ "category": "WindowNode", "key": "window33", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 60, "notes": "", "loc": "-400 240", "group": "wall32" },
|
||||
{ "category": "WindowNode", "key": "window322", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 60, "notes": "", "loc": "-234.80000019073486 240", "group": "wall32" },
|
||||
{ "category": "WindowNode", "key": "window42", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 60, "notes": "", "loc": "-89.80000019073486 240", "group": "wall32" },
|
||||
{ "category": "WindowNode", "key": "window52", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 60, "notes": "", "loc": "80.19999980926514 240", "group": "wall32" },
|
||||
{ "category": "WindowNode", "key": "window62", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 60, "notes": "", "loc": "201.19999980926514 240", "group": "wall32" },
|
||||
{ "key": "wall2", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 260, "y": 0 }, "endpoint": { "class": "go.Point", "x": 380, "y": 0 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "door5", "category": "DoorNode", "color": "rgba(0, 0, 0, 0)", "caption": "Door", "type": "Door", "length": 40, "doorOpeningHeight": 5, "swing": "right", "notes": "", "loc": "-290 -34.79999923706055", "angle": 90, "group": "wall16" },
|
||||
{ "key": "door52", "category": "DoorNode", "color": "rgba(0, 0, 0, 0)", "caption": "Door", "type": "Door", "length": 40, "doorOpeningHeight": 5, "swing": "left", "notes": "", "loc": "130 -37.79999923706055", "angle": 270, "group": "wall12" },
|
||||
{ "key": "door22", "category": "DoorNode", "color": "rgba(0, 0, 0, 0)", "caption": "Door", "type": "Door", "length": 40, "doorOpeningHeight": 5, "swing": "right", "notes": "", "loc": "130 2.200000762939453", "group": "wall12", "angle": 270 },
|
||||
{ "key": "sink", "color": "#c0c0c0", "stroke": "#3F3F3F", "caption": "Sink", "type": "Sink", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0z M5 7.5 L18.5 7.5 M 21.5 7.5 L35 7.5 35 35 5 35 5 7.5 M 15 21.25 A 5 5 180 1 0 15 21.24 M23 3.75 A 3 3 180 1 1 23 3.74 M21.5 6.25 L 21.5 12.5 18.5 12.5 18.5 6.25 M15 3.75 A 1 1 180 1 1 15 3.74 M 10 4.25 L 10 3.25 13 3.25 M 13 4.25 L 10 4.25 M27 3.75 A 1 1 180 1 1 27 3.74 M 26.85 3.25 L 30 3.25 30 4.25 M 26.85 4.25 L 30 4.25", "width": 27, "height": 27, "notes": "", "loc": "361.5 110", "angle": 180, "group": -52 },
|
||||
{ "key": "shower", "color": "#b9cece", "stroke": "#463131", "caption": "Shower/Tub", "type": "Shower/Tub", "geo": "F1 M0 0 L40 0 40 60 0 60 0 0 M35 15 L35 55 5 55 5 15 Q5 5 20 5 Q35 5 35 15 M22.5 20 A2.5 2.5 180 1 1 22.5 19.99", "width": 57, "height": 109, "notes": "", "loc": "296 67", "group": -52 },
|
||||
{ "key": "toilet", "color": "#f7f9e3", "stroke": "#08061C", "caption": "Toilet", "type": "Toilet", "geo": "F1 M0 0 L25 0 25 10 0 10 0 0 M20 10 L20 15 5 15 5 10 20 10 M5 15 Q0 15 0 25 Q0 40 12.5 40 Q25 40 25 25 Q25 15 20 15", "width": 25, "height": 35, "notes": "", "loc": "350 30", "group": -52 },
|
||||
{ "key": "wall22", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 380, "y": 0 }, "endpoint": { "class": "go.Point", "x": 380, "y": 140 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "door3", "category": "DoorNode", "color": "rgba(0, 0, 0, 0)", "caption": "Door", "type": "Door", "length": 36, "doorOpeningHeight": 5, "swing": "left", "notes": "", "loc": "380 65.20000076293945", "group": "wall22", "angle": 270 },
|
||||
{ "key": "wall23", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 430, "y": 0 }, "endpoint": { "class": "go.Point", "x": 550, "y": 0 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall24", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 430, "y": 0 }, "endpoint": { "class": "go.Point", "x": 430, "y": 140 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "isGroup": true, "key": -52, "caption": "Group", "notes": "" },
|
||||
{ "isGroup": true, "key": -53, "caption": "Group", "notes": "" },
|
||||
{ "key": "shower2", "color": "#b9cece", "stroke": "#463131", "caption": "Shower/Tub", "type": "Shower/Tub", "geo": "F1 M0 0 L40 0 40 60 0 60 0 0 M35 15 L35 55 5 55 5 15 Q5 5 20 5 Q35 5 35 15 M22.5 20 A2.5 2.5 180 1 1 22.5 19.99", "width": 57, "height": 109, "notes": "", "loc": "510 70", "group": -53 },
|
||||
{ "key": "toilet2", "color": "#f7f9e3", "stroke": "#08061C", "caption": "Toilet", "type": "Toilet", "geo": "F1 M0 0 L25 0 25 10 0 10 0 0 M20 10 L20 15 5 15 5 10 20 10 M5 15 Q0 15 0 25 Q0 40 12.5 40 Q25 40 25 25 Q25 15 20 15", "width": 25, "height": 35, "notes": "", "loc": "460 30", "group": -53 },
|
||||
{ "key": "sink2", "color": "#c0c0c0", "stroke": "#3F3F3F", "caption": "Sink", "type": "Sink", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0z M5 7.5 L18.5 7.5 M 21.5 7.5 L35 7.5 35 35 5 35 5 7.5 M 15 21.25 A 5 5 180 1 0 15 21.24 M23 3.75 A 3 3 180 1 1 23 3.74 M21.5 6.25 L 21.5 12.5 18.5 12.5 18.5 6.25 M15 3.75 A 1 1 180 1 1 15 3.74 M 10 4.25 L 10 3.25 13 3.25 M 13 4.25 L 10 4.25 M27 3.75 A 1 1 180 1 1 27 3.74 M 26.85 3.25 L 30 3.25 30 4.25 M 26.85 4.25 L 30 4.25", "width": 27, "height": 27, "notes": "", "loc": "460 112", "angle": 180, "group": -53 },
|
||||
{ "key": "door32", "category": "DoorNode", "color": "rgba(0, 0, 0, 0)", "caption": "Door", "type": "Door", "length": 36, "doorOpeningHeight": 5, "swing": "left", "notes": "", "loc": "430 69.20000076293945", "group": "wall24", "angle": 90 },
|
||||
{ "key": "sofaMedium", "color": "#c6a8c5", "stroke": "#39573A", "caption": "Sofa", "type": "Sofa", "geo": "F1 M0 0 L80 0 80 40 0 40 0 0 M10 35 L10 10 M0 0 Q8 0 10 10 M0 40 Q40 15 80 40 M70 10 Q72 0 80 0 M70 10 L70 35", "height": 45, "width": 90, "notes": "", "loc": "320 -40" },
|
||||
{ "key": "sofaMedium2", "color": "#c6a8c5", "stroke": "#39573A", "caption": "Sofa", "type": "Sofa", "geo": "F1 M0 0 L80 0 80 40 0 40 0 0 M10 35 L10 10 M0 0 Q8 0 10 10 M0 40 Q40 15 80 40 M70 10 Q72 0 80 0 M70 10 L70 35", "height": 45, "width": 90, "notes": "", "loc": "490 -40" },
|
||||
{ "key": "roundTable", "color": "#dadada", "stroke": "#252525", "caption": "Round Table", "type": "Round Table", "shape": "Ellipse", "width": 61, "height": 61, "notes": "", "loc": "410 -170", "group": -74 },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode", "caption": "Multi Purpose Node", "color": "#ffffff", "stroke": "#000000", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Fridge", "width": 55, "height": 40, "notes": "", "loc": "342.5 -287.5" },
|
||||
{ "key": "doubleSink", "color": "#d9d9d9", "stroke": "#262626", "caption": "Double Sink", "type": "Double Sink", "geo": "F1 M0 0 L75 0 75 40 0 40 0 0 M5 7.5 L35 7.5 35 35 5 35 5 7.5 M44 7.5 L70 7.5 70 35 40 35 40 9 M15 21.25 A5 5 180 1 0 15 21.24 M50 21.25 A 5 5 180 1 0 50 21.24 M40.5 3.75 A3 3 180 1 1 40.5 3.74 M40.5 3.75 L50.5 13.75 47.5 16.5 37.5 6.75 M32.5 3.75 A 1 1 180 1 1 32.5 3.74 M 27.5 4.25 L 27.5 3.25 30.5 3.25 M 30.5 4.25 L 27.5 4.25 M44.5 3.75 A 1 1 180 1 1 44.5 3.74 M 44.35 3.25 L 47.5 3.25 47.5 4.25 M 44.35 4.25 L 47.5 4.25", "height": 27, "width": 52, "notes": "", "loc": "510 -260", "angle": 53.07333893129521 },
|
||||
{ "category": "WindowNode", "key": "window9", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 5, "length": 60, "notes": "", "loc": "284.9054049801182 -274.8675669721655", "group": "wall19", "angle": 305.5376777919744 },
|
||||
{ "category": "WindowNode", "key": "window10", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 5, "length": 60, "notes": "", "loc": "522.9324327288447 -277.8945941796174", "group": "wall21", "angle": 234.46232220802563 },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode3", "caption": "Multi Purpose Node", "color": "#f7f9e3", "stroke": "#08061C", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Fridge", "width": 55, "height": 40, "notes": "", "loc": "342.5 -287.5" },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode32", "caption": "Multi Purpose Node", "color": "#f7f9e3", "stroke": "#08061C", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Counter", "width": 55, "height": 40, "notes": "", "loc": "395 -287" },
|
||||
{ "category": "WindowNode", "key": "window11", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 5, "length": 60, "notes": "", "loc": "397.19999980926514 -310", "group": "wall20" },
|
||||
{ "key": "door4", "category": "DoorNode", "color": "rgba(0, 0, 0, 0)", "caption": "Door", "type": "Door", "length": 56, "doorOpeningHeight": 5, "swing": "left", "notes": "", "loc": "260 -179.79999923706055", "group": "wall4", "angle": 90 },
|
||||
{ "key": "stove", "color": "#f7f9e3", "stroke": "#08061C", "caption": "Stove", "type": "Stove", "geo": "F1 M 0 0 L 0 100 100 100 100 0 0 0 M 30 15 A 15 15 180 1 0 30.01 15 M 70 15 A 15 15 180 1 0 70.01 15M 30 55 A 15 15 180 1 0 30.01 55 M 70 55 A 15 15 180 1 0 70.01 55", "width": 55, "height": 40, "notes": "", "loc": "450.22782650708155 -288" },
|
||||
{ "key": "armChair", "color": "#c0c0c0", "stroke": "#3F3F3F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 32.865, "height": 32, "notes": "", "loc": "410 -120", "group": -74 },
|
||||
{ "key": "armChair2", "color": "#c0c0c0", "stroke": "#3F3F3F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 32.865, "height": 32, "notes": "", "loc": "460 -170", "angle": 270, "group": -74 },
|
||||
{ "key": "armChair22", "color": "#c0c0c0", "stroke": "#3F3F3F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 32.865, "height": 32, "notes": "", "loc": "410 -220", "angle": 180, "group": -74 },
|
||||
{ "key": "armChair222", "color": "#c0c0c0", "stroke": "#3F3F3F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 32.865, "height": 32, "notes": "", "loc": "360 -170", "angle": 90, "group": -74 },
|
||||
{ "isGroup": true, "key": -74, "caption": "Group", "notes": "" },
|
||||
{ "key": "wall25", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -300, "y": -240 }, "endpoint": { "class": "go.Point", "x": -300, "y": -180 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall252", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -160, "y": -240 }, "endpoint": { "class": "go.Point", "x": -160, "y": -180 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall2522", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 0, "y": -240 }, "endpoint": { "class": "go.Point", "x": 0, "y": -180 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall25222", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 140, "y": -240 }, "endpoint": { "class": "go.Point", "x": 140, "y": -180 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall253", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -300, "y": 180 }, "endpoint": { "class": "go.Point", "x": -300, "y": 240 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall2523", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -160, "y": 180 }, "endpoint": { "class": "go.Point", "x": -160, "y": 240 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall25223", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 0, "y": 180 }, "endpoint": { "class": "go.Point", "x": 0, "y": 240 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall252222", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 140, "y": 180 }, "endpoint": { "class": "go.Point", "x": 140, "y": 240 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "armChair3", "color": "#e1ddd0", "stroke": "#1E222F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 29, "height": 27, "notes": "", "loc": "-231.93243243243245 -192.5" },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode2", "caption": "Multi Purpose Node", "color": "#e1ddd0", "stroke": "#1E222F", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Desk", "width": 116, "height": 14, "notes": "", "loc": "-232 -220" },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode25", "caption": "Multi Purpose Node", "color": "#e1ddd0", "stroke": "#1E222F", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Desk", "width": 116, "height": 14, "notes": "", "loc": "-230 221" },
|
||||
{ "key": "armChair35", "color": "#e1ddd0", "stroke": "#1E222F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 29, "height": 27, "notes": "", "loc": "-230 190", "angle": 180 },
|
||||
{ "key": "armChair352", "color": "#e1ddd0", "stroke": "#1E222F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 29, "height": 27, "notes": "", "loc": "-80 190", "angle": 180 },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode252", "caption": "Multi Purpose Node", "color": "#e1ddd0", "stroke": "#1E222F", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Desk", "width": 116, "height": 14, "notes": "", "loc": "-80 220" },
|
||||
{ "key": "armChair353", "color": "#e1ddd0", "stroke": "#1E222F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 29, "height": 27, "notes": "", "loc": "70 190", "angle": 180 },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode253", "caption": "Multi Purpose Node", "color": "#e1ddd0", "stroke": "#1E222F", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Desk", "width": 116, "height": 14, "notes": "", "loc": "70 220" },
|
||||
{ "key": "armChair3532", "color": "#e1ddd0", "stroke": "#1E222F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 29, "height": 27, "notes": "", "loc": "200 190", "angle": 180 },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode2532", "caption": "Multi Purpose Node", "color": "#e1ddd0", "stroke": "#1E222F", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Desk", "width": 116, "height": 14, "notes": "", "loc": "200 220" },
|
||||
{ "key": "armChair32", "color": "#e1ddd0", "stroke": "#1E222F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 29, "height": 27, "notes": "", "loc": "-80 -190" },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode22", "caption": "Multi Purpose Node", "color": "#e1ddd0", "stroke": "#1E222F", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Desk", "width": 116, "height": 14, "notes": "", "loc": "-80 -220" },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode222", "caption": "Multi Purpose Node", "color": "#e1ddd0", "stroke": "#1E222F", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Desk", "width": 116, "height": 14, "notes": "", "loc": "70 -220" },
|
||||
{ "key": "armChair322", "color": "#e1ddd0", "stroke": "#1E222F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 29, "height": 27, "notes": "", "loc": "70 -190" },
|
||||
{ "key": "armChair3222", "color": "#e1ddd0", "stroke": "#1E222F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 29, "height": 27, "notes": "", "loc": "200 -190" },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode2222", "caption": "Multi Purpose Node", "color": "#e1ddd0", "stroke": "#1E222F", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Desk", "width": 116, "height": 14, "notes": "", "loc": "200 -220" },
|
||||
{ "key": "sofaMedium3", "color": "#b9fde0", "stroke": "#46021F", "caption": "Sofa", "type": "Sofa", "geo": "F1 M0 0 L80 0 80 40 0 40 0 0 M10 35 L10 10 M0 0 Q8 0 10 10 M0 40 Q40 15 80 40 M70 10 Q72 0 80 0 M70 10 L70 35", "height": 27, "width": 90, "notes": "", "loc": "-410 -30", "angle": 90 },
|
||||
{ "key": "sofaMedium32", "color": "#b9fde0", "stroke": "#46021F", "caption": "Sofa", "type": "Sofa", "geo": "F1 M0 0 L80 0 80 40 0 40 0 0 M10 35 L10 10 M0 0 Q8 0 10 10 M0 40 Q40 15 80 40 M70 10 Q72 0 80 0 M70 10 L70 35", "height": 27, "width": 90, "notes": "", "loc": "240 -20", "angle": 270 },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode4", "caption": "Multi Purpose Node", "color": "#d6b196", "stroke": "#294E69", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Desk", "width": 60, "height": 23, "notes": "", "loc": "-381.1676743184333 -190.94449856461264", "angle": 137.27258112448646 },
|
||||
{ "key": "wall26", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -300, "y": -180 }, "endpoint": { "class": "go.Point", "x": -380, "y": -100 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall27", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -380, "y": -100 }, "endpoint": { "class": "go.Point", "x": -430, "y": -100 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall28", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -300, "y": 180 }, "endpoint": { "class": "go.Point", "x": -380, "y": 100 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall29", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -380, "y": 100 }, "endpoint": { "class": "go.Point", "x": -430, "y": 100 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "armChair4", "color": "#d6b196", "stroke": "#294E69", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 21, "height": 21, "notes": "", "loc": "-400 -210", "angle": 135 },
|
||||
{ "key": "roundTable2", "color": "#d6dfc8", "stroke": "#292037", "caption": "Plant", "type": "Round Table", "shape": "Ellipse", "width": 21, "height": 21, "notes": "", "loc": "-402.5 -116.5", "text": "Plant" },
|
||||
{ "key": "armChair42", "color": "#d6b196", "stroke": "#294E69", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 21, "height": 21, "notes": "", "loc": "-400 210", "angle": 45 },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode42", "caption": "Multi Purpose Node", "color": "#d6b196", "stroke": "#294E69", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Desk", "width": 60, "height": 23, "notes": "", "loc": "-380 190", "angle": 47.002533598871146 },
|
||||
{ "key": "roundTable22", "color": "#d6dfc8", "stroke": "#292037", "caption": "Plant", "type": "Round Table", "shape": "Ellipse", "width": 21, "height": 21, "notes": "", "loc": "-400 120", "text": "Plant" },
|
||||
{ "key": "roundTable222", "color": "#d6dfc8", "stroke": "#292037", "caption": "Plant", "type": "Round Table", "shape": "Ellipse", "width": 21, "height": 21, "notes": "", "loc": "-320 200", "text": "Plant" },
|
||||
{ "key": "door6", "category": "DoorNode", "color": "rgba(0, 0, 0, 0)", "caption": "Door", "type": "Door", "length": 40, "doorOpeningHeight": 5, "swing": "left", "notes": "", "loc": "-334.00000047683716 -145.99999952316284", "group": "wall26", "angle": 315 },
|
||||
{ "key": "door7", "category": "DoorNode", "color": "rgba(0, 0, 0, 0)", "caption": "Door", "type": "Door", "length": 40, "doorOpeningHeight": 5, "swing": "left", "notes": "", "loc": "-343.7999997138977 136.2000002861023", "group": "wall28", "angle": 225 }
|
||||
],
|
||||
"linkDataArray": []
|
||||
};
|
||||
|
||||
// UI Interaction state object for FlooplaUI
|
||||
GUI_STATE = {
|
||||
menuButtons: {
|
||||
selectionInfoWindowButtonId: "selectionInfoWindowButton",
|
||||
palettesWindowButtonId: "myPaletteWindowButton",
|
||||
overviewWindowButtonId: "myOverviewWindowButton",
|
||||
optionsWindowButtonId: "optionsWindowButton",
|
||||
statisticsWindowButtonId: "statisticsWindowButton"
|
||||
},
|
||||
windows: {
|
||||
diagramHelpDiv: {
|
||||
id: "diagramHelpDiv"
|
||||
},
|
||||
selectionInfoWindow: {
|
||||
id: "selectionInfoWindow",
|
||||
textDivId: "selectionInfoTextDiv",
|
||||
handleId: "selectionInfoWindowHandle",
|
||||
colorPickerId: "colorPicker",
|
||||
heightLabelId: "heightLabel",
|
||||
heightInputId: "heightInput",
|
||||
widthInputId: "widthInput",
|
||||
nodeGroupInfoId: "nodeGroupInfo",
|
||||
nameInputId: "nameInput",
|
||||
notesTextareaId: "notesTextarea"
|
||||
},
|
||||
palettesWindow: {
|
||||
id: "myPaletteWindow",
|
||||
furnitureSearchInputId: "furnitureSearchBar",
|
||||
furniturePaletteId: "furniturePaletteDiv"
|
||||
},
|
||||
overviewWindow: {
|
||||
id: "myOverviewWindow"
|
||||
},
|
||||
optionsWindow: {
|
||||
id: "optionsWindow",
|
||||
gridSizeInputId: "gridSizeInput",
|
||||
unitsConversionFactorInputId: "unitsConversionFactorInput",
|
||||
unitsFormId: "unitsForm",
|
||||
unitsFormName: "units",
|
||||
checkboxes: {
|
||||
showGridCheckboxId: "showGridCheckbox",
|
||||
gridSnapCheckboxId: "gridSnapCheckbox",
|
||||
wallGuidelinesCheckboxId: "wallGuidelinesCheckbox",
|
||||
wallLengthsCheckboxId: "wallLengthsCheckbox",
|
||||
wallAnglesCheckboxId: "wallAnglesCheckbox",
|
||||
smallWallAnglesCheckboxId: "smallWallAnglesCheckbox"
|
||||
},
|
||||
},
|
||||
statisticsWindow: {
|
||||
id: "statisticsWindow",
|
||||
textDivId: "statisticsWindowTextDiv",
|
||||
numsTableId: "numsTable",
|
||||
totalsTableId: "totalsTable"
|
||||
}
|
||||
},
|
||||
scaleDisplayId: "scaleDisplay",
|
||||
setBehaviorClass: "setBehavior",
|
||||
wallThicknessInputId: "wallThicknessInput",
|
||||
wallThicknessBoxId: "wallThicknessBox",
|
||||
unitsBoxClass: "unitsBox",
|
||||
unitsInputClass: "unitsInput"
|
||||
};
|
||||
|
||||
// Filesystem state object for FloorplanFilesystem
|
||||
FILESYSTEM_UI_STATE = {
|
||||
openWindowId: "openDocument",
|
||||
removeWindowId: "removeDocument",
|
||||
currentFileId: "currentFile",
|
||||
filesToRemoveListId: "filesToRemove",
|
||||
filesToOpenListId: "filesToOpen"
|
||||
};
|
||||
|
||||
// Node Data Array for Furniture Palette
|
||||
FURNITURE_NODE_DATA_ARRAY = [
|
||||
{
|
||||
category: "MultiPurposeNode",
|
||||
key: "MultiPurposeNode",
|
||||
caption: "Multi Purpose Node",
|
||||
color: "#ffffff",
|
||||
stroke: '#000000',
|
||||
name: "Writable Node",
|
||||
type: "Writable Node",
|
||||
shape: "Rectangle",
|
||||
text: "Write here",
|
||||
width: 60,
|
||||
height: 60,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "roundTable",
|
||||
color: "#ffffff",
|
||||
stroke: '#000000',
|
||||
caption: "Round Table",
|
||||
type: "Round Table",
|
||||
shape: "Ellipse",
|
||||
width: 61,
|
||||
height: 61,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "armChair",
|
||||
color: "#ffffff",
|
||||
stroke: '#000000',
|
||||
caption: "Arm Chair",
|
||||
type: "Arm Chair",
|
||||
geo: "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30",
|
||||
width: 45,
|
||||
height: 45,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "sofaMedium",
|
||||
color: "#ffffff",
|
||||
stroke: "#000000",
|
||||
caption: "Sofa",
|
||||
type: "Sofa",
|
||||
geo: "F1 M0 0 L80 0 80 40 0 40 0 0 M10 35 L10 10 M0 0 Q8 0 10 10 M0 40 Q40 15 80 40 M70 10 Q72 0 80 0 M70 10 L70 35",
|
||||
height: 45,
|
||||
width: 90,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "sink",
|
||||
color: "#ffffff",
|
||||
stroke: '#000000',
|
||||
caption: "Sink",
|
||||
type: "Sink",
|
||||
geo: "F1 M0 0 L40 0 40 40 0 40 0 0z M5 7.5 L18.5 7.5 M 21.5 7.5 L35 7.5 35 35 5 35 5 7.5 M 15 21.25 A 5 5 180 1 0 15 21.24 M23 3.75 A 3 3 180 1 1 23 3.74 M21.5 6.25 L 21.5 12.5 18.5 12.5 18.5 6.25 M15 3.75 A 1 1 180 1 1 15 3.74 M 10 4.25 L 10 3.25 13 3.25 M 13 4.25 L 10 4.25 M27 3.75 A 1 1 180 1 1 27 3.74 M 26.85 3.25 L 30 3.25 30 4.25 M 26.85 4.25 L 30 4.25",
|
||||
width: 27,
|
||||
height: 27,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "doubleSink",
|
||||
color: "#ffffff",
|
||||
stroke: '#000000',
|
||||
caption: "Double Sink",
|
||||
type: "Double Sink",
|
||||
geo: "F1 M0 0 L75 0 75 40 0 40 0 0 M5 7.5 L35 7.5 35 35 5 35 5 7.5 M44 7.5 L70 7.5 70 35 40 35 40 9 M15 21.25 A5 5 180 1 0 15 21.24 M50 21.25 A 5 5 180 1 0 50 21.24 M40.5 3.75 A3 3 180 1 1 40.5 3.74 M40.5 3.75 L50.5 13.75 47.5 16.5 37.5 6.75 M32.5 3.75 A 1 1 180 1 1 32.5 3.74 M 27.5 4.25 L 27.5 3.25 30.5 3.25 M 30.5 4.25 L 27.5 4.25 M44.5 3.75 A 1 1 180 1 1 44.5 3.74 M 44.35 3.25 L 47.5 3.25 47.5 4.25 M 44.35 4.25 L 47.5 4.25",
|
||||
height: 27,
|
||||
width: 52,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "toilet",
|
||||
color: "#ffffff",
|
||||
stroke: '#000000',
|
||||
caption: "Toilet",
|
||||
type: "Toilet",
|
||||
geo: "F1 M0 0 L25 0 25 10 0 10 0 0 M20 10 L20 15 5 15 5 10 20 10 M5 15 Q0 15 0 25 Q0 40 12.5 40 Q25 40 25 25 Q25 15 20 15",
|
||||
width: 25,
|
||||
height: 35,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "shower",
|
||||
color: "#ffffff",
|
||||
stroke: '#000000',
|
||||
caption: "Shower/Tub",
|
||||
type: "Shower/Tub",
|
||||
geo: "F1 M0 0 L40 0 40 60 0 60 0 0 M35 15 L35 55 5 55 5 15 Q5 5 20 5 Q35 5 35 15 M22.5 20 A2.5 2.5 180 1 1 22.5 19.99",
|
||||
width: 45,
|
||||
height: 75,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "bed",
|
||||
color: "#ffffff",
|
||||
stroke: '#000000',
|
||||
caption: "Bed",
|
||||
type: "Bed",
|
||||
geo: "F1 M0 0 L40 0 40 60 0 60 0 0 M 7.5 2.5 L32.5 2.5 32.5 17.5 7.5 17.5 7.5 2.5 M0 20 L40 20 M0 25 L40 25",
|
||||
width: 76.2,
|
||||
height: 101.6,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "staircase",
|
||||
color: "#ffffff",
|
||||
stroke: '#000000',
|
||||
caption: "Staircase",
|
||||
type: "Staircase",
|
||||
geo: "F1 M0 0 L 0 100 250 100 250 0 0 0 M25 100 L 25 0 M 50 100 L 50 0 M 75 100 L 75 0 M 100 100 L 100 0 M 125 100 L 125 0 M 150 100 L 150 0 M 175 100 L 175 0 M 200 100 L 200 0 M 225 100 L 225 0",
|
||||
width: 125,
|
||||
height: 50,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "stove",
|
||||
color: "#ffffff",
|
||||
stroke: '#000000',
|
||||
caption: "Stove",
|
||||
type: "Stove",
|
||||
geo: "F1 M 0 0 L 0 100 100 100 100 0 0 0 M 30 15 A 15 15 180 1 0 30.01 15 M 70 15 A 15 15 180 1 0 70.01 15"
|
||||
+ "M 30 55 A 15 15 180 1 0 30.01 55 M 70 55 A 15 15 180 1 0 70.01 55",
|
||||
width: 75,
|
||||
height: 75,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "diningTable",
|
||||
color: "#ffffff",
|
||||
stroke: '#000000',
|
||||
caption: "Dining Table",
|
||||
type: "Dining Table",
|
||||
geo: "F1 M 0 0 L 0 100 200 100 200 0 0 0 M 25 0 L 25 -10 75 -10 75 0 M 125 0 L 125 -10 175 -10 175 0 M 200 25 L 210 25 210 75 200 75 M 125 100 L 125 110 L 175 110 L 175 100 M 25 100 L 25 110 75 110 75 100 M 0 75 -10 75 -10 25 0 25",
|
||||
width: 125,
|
||||
height: 62.5,
|
||||
notes: ""
|
||||
}
|
||||
];
|
||||
|
||||
// Node Data Array for Wall Parts Palette
|
||||
WALLPARTS_NODE_DATA_ARRAY = [
|
||||
{
|
||||
category: "PaletteWallNode",
|
||||
key: "wall",
|
||||
caption: "Wall",
|
||||
type: "Wall",
|
||||
color: "#000000",
|
||||
shape: "Rectangle",
|
||||
height: 10,
|
||||
length: 60,
|
||||
notes: "",
|
||||
},
|
||||
{
|
||||
category: "WindowNode",
|
||||
key: "window",
|
||||
color: "white",
|
||||
caption: "Window",
|
||||
type: "Window",
|
||||
shape: "Rectangle",
|
||||
height: 10,
|
||||
length: 60,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "door",
|
||||
category: "DoorNode",
|
||||
color: "rgba(0, 0, 0, 0)",
|
||||
caption: "Door",
|
||||
type: "Door",
|
||||
length: 40,
|
||||
doorOpeningHeight: 5,
|
||||
swing: "left",
|
||||
notes: ""
|
||||
}
|
||||
];
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
Default CSS for the Data inspector
|
||||
see also: DataInspector.js, DataInspector.html
|
||||
*/
|
||||
|
||||
/*
|
||||
Grey color palette
|
||||
https://www.google.com/design/spec/style/color.html
|
||||
/* #FAFAFA; /* Grey 50 */
|
||||
/* #F5F5F5; /* Grey 100 */
|
||||
/* #EEEEEE; /* Grey 200 */
|
||||
/* #E0E0E0; /* Grey 300 */
|
||||
/* #BDBDBD; /* Grey 400 */
|
||||
/* #9E9E9E; /* Grey 500 */
|
||||
/* #757575; /* Grey 600 */
|
||||
/* #616161; /* Grey 700 */
|
||||
/* #424242; /* Grey 800 */
|
||||
/* #212121; /* Grey 900 */
|
||||
|
||||
.inspector {
|
||||
display: inline-block;
|
||||
font: bold 14px helvetica, sans-serif;
|
||||
background-color: #212121; /* Grey 900 */
|
||||
color: #F5F5F5; /* Grey 100 */
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.inspector table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 2px;
|
||||
}
|
||||
|
||||
.inspector td, th {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.inspector input, .inspector textarea {
|
||||
background-color: #424242; /* Grey 800 */
|
||||
color: #F5F5F5; /* Grey 100 */
|
||||
font: bold 12px helvetica, sans-serif;
|
||||
border: 0px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.inspector input:disabled, .inspector textarea:disabled {
|
||||
background-color: #BDBDBD; /* Grey 400 */
|
||||
color: #616161; /* Grey 700 */
|
||||
}
|
||||
|
||||
.inspector select {
|
||||
background-color: #424242;
|
||||
}
|
||||
|
||||
.inspector td {
|
||||
color: #F5F5F5;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,718 @@
|
||||
"use strict";
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/**
|
||||
This class implements an inspector for GoJS model data objects.
|
||||
The constructor takes three arguments:
|
||||
{string} divid a string referencing the HTML ID of the to-be inspector's div.
|
||||
{Diagram} diagram a reference to a GoJS Diagram.
|
||||
{Object} options An optional JS Object describing options for the inspector.
|
||||
|
||||
Options:
|
||||
inspectSelection {boolean} Default true, whether to automatically show and populate the Inspector
|
||||
with the currently selected Diagram Part. If set to false, the inspector won't show anything
|
||||
until you call Inspector.inspectObject(object) with a Part or JavaScript object as the argument.
|
||||
includesOwnProperties {boolean} Default true, whether to list all properties currently on the inspected data object.
|
||||
properties {Object} An object of string:Object pairs representing propertyName:propertyOptions.
|
||||
Can be used to include or exclude additional properties.
|
||||
propertyModified function(propertyName, newValue) a callback
|
||||
multipleSelection {boolean} Default false, whether to allow multiple selection and change the properties of all the selected instead of
|
||||
the single first object
|
||||
showAllProperties {boolean} Default false, whether properties that are shown with multipleSelection use the intersect of the properties when false or the union when true
|
||||
only affects if multipleSelection is true
|
||||
showSize {number} Defaults 0, shows how many nodes are showed when selecting multiple nodes
|
||||
when its lower than 1, it shows all nodes
|
||||
|
||||
Options for properties:
|
||||
show: {boolean|function} a boolean value to show or hide the property from the inspector, or a predicate function to show conditionally.
|
||||
readOnly: {boolean|function} whether or not the property is read-only
|
||||
type: {string} a string describing the data type. Supported values: "string|number|boolean|color|arrayofnumber|point|rect|size|spot|margin|select"
|
||||
defaultValue: {*} a default value for the property. Defaults to the empty string.
|
||||
choices: {Array|function} when type == "select", the Array of choices to use or a function that returns the Array of choices.
|
||||
|
||||
Example usage of Inspector:
|
||||
|
||||
var inspector = new Inspector("myInspector", myDiagram,
|
||||
{
|
||||
includesOwnProperties: false,
|
||||
properties: {
|
||||
"key": { show: Inspector.showIfPresent, readOnly: true },
|
||||
"comments": { show: Inspector.showIfNode },
|
||||
"LinkComments": { show: Inspector.showIfLink },
|
||||
"chosen": { show: Inspector.showIfNode, type: "checkbox" },
|
||||
"state": { show: Inspector.showIfNode, type: "select", choices: ["Stopped", "Parked", "Moving"] }
|
||||
}
|
||||
});
|
||||
|
||||
This is the basic HTML Structure that the Inspector creates within the given DIV element:
|
||||
|
||||
<div id="divid" class="inspector">
|
||||
<tr>
|
||||
<td>propertyName</td>
|
||||
<td><input value=propertyValue /></td>
|
||||
</tr>
|
||||
...
|
||||
</div>
|
||||
|
||||
*/
|
||||
function Inspector(divid, diagram, options) {
|
||||
var mainDiv = document.getElementById(divid);
|
||||
mainDiv.className = "inspector";
|
||||
mainDiv.innerHTML = "";
|
||||
this._div = mainDiv;
|
||||
this._diagram = diagram;
|
||||
this._inspectedProperties = {};
|
||||
this._multipleProperties = {};
|
||||
|
||||
// Either a GoJS Part or a simple data object, such as Model.modelData
|
||||
this.inspectedObject = null;
|
||||
|
||||
// Inspector options defaults:
|
||||
this.includesOwnProperties = true;
|
||||
this.declaredProperties = {};
|
||||
this.inspectsSelection = true;
|
||||
this.propertyModified = null;
|
||||
this.multipleSelection = false;
|
||||
this.showAllProperties = false;
|
||||
this.showSize = 0;
|
||||
|
||||
if (options !== undefined) {
|
||||
if (options["includesOwnProperties"] !== undefined) this.includesOwnProperties = options["includesOwnProperties"];
|
||||
if (options["properties"] !== undefined) this.declaredProperties = options["properties"];
|
||||
if (options["inspectSelection"] !== undefined) this.inspectsSelection = options["inspectSelection"];
|
||||
if (options["propertyModified"] !== undefined) this.propertyModified = options["propertyModified"];
|
||||
if (options['multipleSelection'] !== undefined) this.multipleSelection = options['multipleSelection'];
|
||||
if (options['showAllProperties'] !== undefined) this.showAllProperties = options['showAllProperties'];
|
||||
if (options['showSize'] !== undefined) this.showSize = options['showSize'];
|
||||
}
|
||||
|
||||
var self = this;
|
||||
diagram.addModelChangedListener(function(e) {
|
||||
if (e.isTransactionFinished) self.inspectObject();
|
||||
});
|
||||
if (this.inspectsSelection) {
|
||||
diagram.addDiagramListener("ChangedSelection", function(e) { self.inspectObject(); });
|
||||
}
|
||||
}
|
||||
|
||||
// Some static predicates to use with the "show" property.
|
||||
Inspector.showIfNode = function(part) { return part instanceof go.Node };
|
||||
Inspector.showIfLink = function(part) { return part instanceof go.Link };
|
||||
Inspector.showIfGroup = function(part) { return part instanceof go.Group };
|
||||
|
||||
// Only show the property if its present. Useful for "key" which will be shown on Nodes and Groups, but normally not on Links
|
||||
Inspector.showIfPresent = function(data, propname) {
|
||||
if (data instanceof go.Part) data = data.data;
|
||||
return typeof data === "object" && data[propname] !== undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the HTML state of this Inspector given the properties of the {@link #inspectedObject}.
|
||||
* @param {Object} object is an optional argument, used when {@link #inspectSelection} is false to
|
||||
* set {@link #inspectedObject} and show and edit that object's properties.
|
||||
*/
|
||||
Inspector.prototype.inspectObject = function(object) {
|
||||
var inspectedObject = null;
|
||||
var inspectedObjects = null;
|
||||
if (object === null) return;
|
||||
if (object === undefined) {
|
||||
if (this.inspectsSelection) {
|
||||
if (this.multipleSelection) { // gets the selection if multiple selection is true
|
||||
inspectedObjects = this._diagram.selection;
|
||||
} else { // otherwise grab the first object
|
||||
inspectedObject = this._diagram.selection.first();
|
||||
}
|
||||
} else { // if there is a single inspected object
|
||||
inspectedObject = this.inspectedObject;
|
||||
}
|
||||
} else { // if object was passed in as a parameter
|
||||
inspectedObject = object;
|
||||
}
|
||||
if (inspectedObjects && inspectedObjects.count === 1) {
|
||||
inspectedObject = inspectedObjects.first();
|
||||
}
|
||||
if (inspectedObjects && inspectedObjects.count <= 1) {
|
||||
inspectedObjects = null;
|
||||
}
|
||||
|
||||
// single object or no objects
|
||||
if (!inspectedObjects || !this.multipleSelection) {
|
||||
if (inspectedObject === null || this.inspectedObject === inspectedObject) {
|
||||
this.inspectedObject = inspectedObject;
|
||||
this.updateAllHTML();
|
||||
return;
|
||||
}
|
||||
|
||||
this.inspectedObject = inspectedObject;
|
||||
if (this.inspectObject === null) return;
|
||||
var mainDiv = this._div;
|
||||
mainDiv.innerHTML = '';
|
||||
|
||||
// use either the Part.data or the object itself (for model.modelData)
|
||||
var data = (inspectedObject instanceof go.Part) ? inspectedObject.data : inspectedObject;
|
||||
if (!data) return;
|
||||
// Build table:
|
||||
var table = document.createElement('table');
|
||||
var tbody = document.createElement('tbody');
|
||||
this._inspectedProperties = {};
|
||||
this.tabIndex = 0;
|
||||
var declaredProperties = this.declaredProperties;
|
||||
|
||||
// Go through all the properties passed in to the inspector and show them, if appropriate:
|
||||
for (var name in declaredProperties) {
|
||||
var desc = declaredProperties[name];
|
||||
if (!this.canShowProperty(name, desc, inspectedObject)) continue;
|
||||
var val = this.findValue(name, desc, data);
|
||||
tbody.appendChild(this.buildPropertyRow(name, val));
|
||||
}
|
||||
// Go through all the properties on the model data and show them, if appropriate:
|
||||
if (this.includesOwnProperties) {
|
||||
for (var k in data) {
|
||||
if (k === '__gohashid') continue; // skip internal GoJS hash property
|
||||
if (this._inspectedProperties[k]) continue; // already exists
|
||||
if (declaredProperties[k] && !this.canShowProperty(k, declaredProperties[k], inspectedObject)) continue;
|
||||
tbody.appendChild(this.buildPropertyRow(k, data[k]));
|
||||
}
|
||||
}
|
||||
|
||||
table.appendChild(tbody);
|
||||
mainDiv.appendChild(table);
|
||||
} else { // multiple objects selected
|
||||
var mainDiv = this._div;
|
||||
mainDiv.innerHTML = '';
|
||||
var shared = new go.Map(); // for properties that the nodes have in common
|
||||
var properties = new go.Map(); // for adding properties
|
||||
var all = new go.Map(); // used later to prevent changing properties when unneeded
|
||||
var it = inspectedObjects.iterator;
|
||||
// Build table:
|
||||
var table = document.createElement('table');
|
||||
var tbody = document.createElement('tbody');
|
||||
this._inspectedProperties = {};
|
||||
this.tabIndex = 0;
|
||||
var declaredProperties = this.declaredProperties;
|
||||
it.next();
|
||||
inspectedObject = it.value;
|
||||
this.inspectedObject = inspectedObject;
|
||||
var data = (inspectedObject instanceof go.Part) ? inspectedObject.data : inspectedObject;
|
||||
if (data) { // initial pass to set shared and all
|
||||
// Go through all the properties passed in to the inspector and add them to the map, if appropriate:
|
||||
for (var name in declaredProperties) {
|
||||
var desc = declaredProperties[name];
|
||||
if (!this.canShowProperty(name, desc, inspectedObject)) continue;
|
||||
var val = this.findValue(name, desc, data);
|
||||
if (val === '' && desc && desc.type === 'checkbox') {
|
||||
shared.add(name, false);
|
||||
all.add(name, false);
|
||||
} else {
|
||||
shared.add(name, val);
|
||||
all.add(name, val);
|
||||
}
|
||||
}
|
||||
// Go through all the properties on the model data and add them to the map, if appropriate:
|
||||
if (this.includesOwnProperties) {
|
||||
for (var k in data) {
|
||||
if (k === '__gohashid') continue; // skip internal GoJS hash property
|
||||
if (this._inspectedProperties[k]) continue; // already exists
|
||||
if (declaredProperties[k] && !this.canShowProperty(k, declaredProperties[k], inspectedObject)) continue;
|
||||
shared.add(k, data[k]);
|
||||
all.add(k, data[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
var nodecount = 2;
|
||||
while (it.next() && (this.showSize < 1 || nodecount <= this.showSize)) { // grabs all the properties from the other selected objects
|
||||
properties.clear();
|
||||
inspectedObject = it.value;
|
||||
if (inspectedObject) {
|
||||
// use either the Part.data or the object itself (for model.modelData)
|
||||
data = (inspectedObject instanceof go.Part) ? inspectedObject.data : inspectedObject;
|
||||
if (data) {
|
||||
// Go through all the properties passed in to the inspector and add them to properties to add, if appropriate:
|
||||
for (var name in declaredProperties) {
|
||||
var desc = declaredProperties[name];
|
||||
if (!this.canShowProperty(name, desc, inspectedObject)) continue;
|
||||
var val = this.findValue(name, desc, data);
|
||||
if (val === '' && desc && desc.type === 'checkbox') {
|
||||
properties.add(name, false);
|
||||
} else {
|
||||
properties.add(name, val);
|
||||
}
|
||||
}
|
||||
// Go through all the properties on the model data and add them to properties to add, if appropriate:
|
||||
if (this.includesOwnProperties) {
|
||||
for (var k in data) {
|
||||
if (k === '__gohashid') continue; // skip internal GoJS hash property
|
||||
if (this._inspectedProperties[k]) continue; // already exists
|
||||
if (declaredProperties[k] && !this.canShowProperty(k, declaredProperties[k], inspectedObject)) continue;
|
||||
properties.add(k, data[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!this.showAllProperties) {
|
||||
// Cleans up shared map with properties that aren't shared between the selected objects
|
||||
// Also adds properties to the add and shared maps if applicable
|
||||
var addIt = shared.iterator;
|
||||
var toRemove = [];
|
||||
while (addIt.next()) {
|
||||
if (properties.has(addIt.key)) {
|
||||
var newVal = all.get(addIt.key) + '|' + properties.get(addIt.key);
|
||||
all.set(addIt.key, newVal);
|
||||
if ((declaredProperties[addIt.key] && declaredProperties[addIt.key].type !== 'color'
|
||||
&& declaredProperties[addIt.key].type !== 'checkbox' && declaredProperties[addIt.key].type !== 'select')
|
||||
|| !declaredProperties[addIt.key]) { // for non-string properties i.e color
|
||||
newVal = shared.get(addIt.key) + '|' + properties.get(addIt.key);
|
||||
shared.set(addIt.key, newVal);
|
||||
}
|
||||
} else { // toRemove array since addIt is still iterating
|
||||
toRemove.push(addIt.key);
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < toRemove.length; i++) { // removes anything that doesn't showAllPropertiess
|
||||
shared.remove(toRemove[i]);
|
||||
all.remove(toRemove[i]);
|
||||
}
|
||||
} else {
|
||||
// Adds missing properties to all with the correct amount of seperators
|
||||
var addIt = properties.iterator;
|
||||
while (addIt.next()) {
|
||||
if (all.has(addIt.key)) {
|
||||
if ((declaredProperties[addIt.key] && declaredProperties[addIt.key].type !== 'color'
|
||||
&& declaredProperties[addIt.key].type !== 'checkbox' && declaredProperties[addIt.key].type !== 'select')
|
||||
|| !declaredProperties[addIt.key]) { // for non-string properties i.e color
|
||||
var newVal = all.get(addIt.key) + '|' + properties.get(addIt.key);
|
||||
all.set(addIt.key, newVal);
|
||||
}
|
||||
} else {
|
||||
var newVal = '';
|
||||
for (var i = 0; i < nodecount - 1; i++) newVal += '|';
|
||||
newVal += properties.get(addIt.key);
|
||||
all.set(addIt.key, newVal);
|
||||
}
|
||||
}
|
||||
// Adds bars in case properties is not in all
|
||||
addIt = all.iterator;
|
||||
while (addIt.next()) {
|
||||
if (!properties.has(addIt.key)) {
|
||||
if ((declaredProperties[addIt.key] && declaredProperties[addIt.key].type !== 'color'
|
||||
&& declaredProperties[addIt.key].type !== 'checkbox' && declaredProperties[addIt.key].type !== 'select')
|
||||
|| !declaredProperties[addIt.key]) { // for non-string properties i.e color
|
||||
var newVal = all.get(addIt.key) + '|';
|
||||
all.set(addIt.key, newVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
nodecount++;
|
||||
}
|
||||
// builds the table property rows and sets multipleProperties to help with updateall
|
||||
var mapIt;
|
||||
if (!this.showAllProperties) mapIt = shared.iterator;
|
||||
else mapIt = all.iterator;
|
||||
while (mapIt.next()) {
|
||||
tbody.appendChild(this.buildPropertyRow(mapIt.key, mapIt.value)); // shows the properties that are allowed
|
||||
}
|
||||
table.appendChild(tbody);
|
||||
mainDiv.appendChild(table);
|
||||
var allIt = all.iterator;
|
||||
while (allIt.next()) {
|
||||
this._multipleProperties[allIt.key] = allIt.value; // used for updateall to know which properties to change
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* This predicate should be false if the given property should not be shown.
|
||||
* Normally it only checks the value of "show" on the property descriptor.
|
||||
* The default value is true.
|
||||
* @param {string} propertyName the property name
|
||||
* @param {Object} propertyDesc the property descriptor
|
||||
* @param {Object} inspectedObject the data object
|
||||
* @return {boolean} whether a particular property should be shown in this Inspector
|
||||
*/
|
||||
Inspector.prototype.canShowProperty = function(propertyName, propertyDesc, inspectedObject) {
|
||||
if (propertyDesc.show === false) return false;
|
||||
// if "show" is a predicate, make sure it passes or do not show this property
|
||||
if (typeof propertyDesc.show === "function") return propertyDesc.show(inspectedObject, propertyName);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* This predicate should be false if the given property should not be editable by the user.
|
||||
* Normally it only checks the value of "readOnly" on the property descriptor.
|
||||
* The default value is true.
|
||||
* @param {string} propertyName the property name
|
||||
* @param {Object} propertyDesc the property descriptor
|
||||
* @param {Object} inspectedObject the data object
|
||||
* @return {boolean} whether a particular property should be shown in this Inspector
|
||||
*/
|
||||
Inspector.prototype.canEditProperty = function(propertyName, propertyDesc, inspectedObject) {
|
||||
if (this._diagram.isReadOnly || this._diagram.isModelReadOnly) return false;
|
||||
// assume property values that are functions of Objects cannot be edited
|
||||
var data = (inspectedObject instanceof go.Part) ? inspectedObject.data : inspectedObject;
|
||||
var valtype = typeof data[propertyName];
|
||||
if (valtype === "function") return false;
|
||||
if (propertyDesc) {
|
||||
if (propertyDesc.readOnly === true) return false;
|
||||
// if "readOnly" is a predicate, make sure it passes or do not show this property
|
||||
if (typeof propertyDesc.readOnly === "function") return !propertyDesc.readOnly(inspectedObject, propertyName);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* @param {any} propName
|
||||
* @param {any} propDesc
|
||||
* @param {any} data
|
||||
* @return {any}
|
||||
*/
|
||||
Inspector.prototype.findValue = function(propName, propDesc, data) {
|
||||
var val = '';
|
||||
if (propDesc && propDesc.defaultValue !== undefined) val = propDesc.defaultValue;
|
||||
if (data[propName] !== undefined) val = data[propName];
|
||||
if (val === undefined) return '';
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* This sets this._inspectedProperties[propertyName] and creates the HTML table row:
|
||||
* <tr>
|
||||
* <td>propertyName</td>
|
||||
* <td><input value=propertyValue /></td>
|
||||
* </tr>
|
||||
* @param {string} propertyName the property name
|
||||
* @param {*} propertyValue the property value
|
||||
* @return the table row
|
||||
*/
|
||||
Inspector.prototype.buildPropertyRow = function(propertyName, propertyValue) {
|
||||
var mainDiv = this._div;
|
||||
var tr = document.createElement("tr");
|
||||
|
||||
var td1 = document.createElement("td");
|
||||
td1.textContent = propertyName;
|
||||
tr.appendChild(td1);
|
||||
|
||||
var td2 = document.createElement("td");
|
||||
var decProp = this.declaredProperties[propertyName];
|
||||
var input = null;
|
||||
var self = this;
|
||||
function updateall() { self.updateAllProperties(); }
|
||||
|
||||
if (decProp && decProp.type === "select") {
|
||||
input = document.createElement("select");
|
||||
this.updateSelect(decProp, input, propertyName, propertyValue);
|
||||
input.addEventListener("change", updateall);
|
||||
} else {
|
||||
input = document.createElement("input");
|
||||
|
||||
input.value = this.convertToString(propertyValue);
|
||||
if (decProp) {
|
||||
var t = decProp.type;
|
||||
if (t !== 'string' && t !== 'number' && t !== 'boolean' &&
|
||||
t !== 'arrayofnumber' && t !== 'point' && t !== 'size' &&
|
||||
t !== 'rect' && t !== 'spot' && t !== 'margin') {
|
||||
input.setAttribute("type", decProp.type);
|
||||
}
|
||||
if (decProp.type === "color") {
|
||||
if (input.type === "color") {
|
||||
input.value = this.convertToColor(propertyValue);
|
||||
input.addEventListener("change", updateall);
|
||||
}
|
||||
} if (decProp.type === "checkbox") {
|
||||
input.checked = !!propertyValue;
|
||||
input.addEventListener("change", updateall);
|
||||
}
|
||||
}
|
||||
if (input.type !== "color") input.addEventListener("blur", updateall);
|
||||
}
|
||||
|
||||
if (input) {
|
||||
input.tabIndex = this.tabIndex++;
|
||||
input.disabled = !this.canEditProperty(propertyName, decProp, this.inspectedObject);
|
||||
td2.appendChild(input);
|
||||
}
|
||||
tr.appendChild(td2);
|
||||
|
||||
this._inspectedProperties[propertyName] = input;
|
||||
return tr;
|
||||
};
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* HTML5 color input will only take hex,
|
||||
* so var HTML5 canvas convert the color into hex format.
|
||||
* This converts "rgb(255, 0, 0)" into "#FF0000", etc.
|
||||
* @param {string} propertyValue
|
||||
* @return {string}
|
||||
*/
|
||||
Inspector.prototype.convertToColor = function(propertyValue) {
|
||||
var ctx = document.createElement("canvas").getContext("2d");
|
||||
ctx.fillStyle = propertyValue;
|
||||
return ctx.fillStyle;
|
||||
};
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* @param {string}
|
||||
* @return {Array.<number>}
|
||||
*/
|
||||
Inspector.prototype.convertToArrayOfNumber = function(propertyValue) {
|
||||
if (propertyValue === "null") return null;
|
||||
var split = propertyValue.split(' ');
|
||||
var arr = [];
|
||||
for (var i = 0; i < split.length; i++) {
|
||||
var str = split[i];
|
||||
if (!str) continue;
|
||||
arr.push(parseFloat(str));
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* @param {*}
|
||||
* @return {string}
|
||||
*/
|
||||
Inspector.prototype.convertToString = function(x) {
|
||||
if (x === undefined) return "undefined";
|
||||
if (x === null) return "null";
|
||||
if (x instanceof go.Point) return go.Point.stringify(x);
|
||||
if (x instanceof go.Size) return go.Size.stringify(x);
|
||||
if (x instanceof go.Rect) return go.Rect.stringify(x);
|
||||
if (x instanceof go.Spot) return go.Spot.stringify(x);
|
||||
if (x instanceof go.Margin) return go.Margin.stringify(x);
|
||||
if (x instanceof go.List) return this.convertToString(x.toArray());
|
||||
if (Array.isArray(x)) {
|
||||
var str = "";
|
||||
for (var i = 0; i < x.length; i++) {
|
||||
if (i > 0) str += " ";
|
||||
var v = x[i];
|
||||
str += this.convertToString(v);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
return x.toString();
|
||||
};
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* Update all of the HTML in this Inspector.
|
||||
*/
|
||||
Inspector.prototype.updateAllHTML = function() {
|
||||
var inspectedProps = this._inspectedProperties;
|
||||
var diagram = this._diagram;
|
||||
var isPart = this.inspectedObject instanceof go.Part;
|
||||
var data = isPart ? this.inspectedObject.data : this.inspectedObject;
|
||||
if (!data) { // clear out all of the fields
|
||||
for (var name in inspectedProps) {
|
||||
var input = inspectedProps[name];
|
||||
if (input instanceof HTMLSelectElement) {
|
||||
input.innerHTML = "";
|
||||
} else if (input.type === "color") {
|
||||
input.value = "#000000";
|
||||
} else if (input.type === "checkbox") {
|
||||
input.checked = false;
|
||||
} else {
|
||||
input.value = "";
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
for (var name in inspectedProps) {
|
||||
var input = inspectedProps[name];
|
||||
var propertyValue = data[name];
|
||||
if (input instanceof HTMLSelectElement) {
|
||||
var decProp = this.declaredProperties[name];
|
||||
this.updateSelect(decProp, input, name, propertyValue);
|
||||
} else if (input.type === "color") {
|
||||
input.value = this.convertToColor(propertyValue);
|
||||
} else if (input.type === "checkbox") {
|
||||
input.checked = !!propertyValue;
|
||||
} else {
|
||||
input.value = this.convertToString(propertyValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* Update an HTMLSelectElement with an appropriate list of choices, given the propertyName
|
||||
*/
|
||||
Inspector.prototype.updateSelect = function(decProp, select, propertyName, propertyValue) {
|
||||
select.innerHTML = ""; // clear out anything that was there
|
||||
var choices = decProp.choices;
|
||||
if (typeof choices === "function") choices = choices(this.inspectedObject, propertyName);
|
||||
if (!Array.isArray(choices)) choices = [];
|
||||
decProp.choicesArray = choices; // remember list of actual choice values (not strings)
|
||||
for (var i = 0; i < choices.length; i++) {
|
||||
var choice = choices[i];
|
||||
var opt = document.createElement("option");
|
||||
opt.text = this.convertToString(choice);
|
||||
select.add(opt, null);
|
||||
}
|
||||
select.value = this.convertToString(propertyValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* Update all of the data properties of {@link #inspectedObject} according to the
|
||||
* current values held in the HTML input elements.
|
||||
*/
|
||||
Inspector.prototype.updateAllProperties = function() {
|
||||
var inspectedProps = this._inspectedProperties;
|
||||
var diagram = this._diagram;
|
||||
if (diagram.selection.count === 1 || !this.multipleSelection) { // single object update
|
||||
var isPart = this.inspectedObject instanceof go.Part;
|
||||
var data = isPart ? this.inspectedObject.data : this.inspectedObject;
|
||||
if (!data) return; // must not try to update data when there's no data!
|
||||
|
||||
diagram.startTransaction('set all properties');
|
||||
for (var name in inspectedProps) {
|
||||
var input = inspectedProps[name];
|
||||
var value = input.value;
|
||||
|
||||
// don't update "readOnly" data properties
|
||||
var decProp = this.declaredProperties[name];
|
||||
if (!this.canEditProperty(name, decProp, this.inspectedObject)) continue;
|
||||
|
||||
// If it's a boolean, or if its previous value was boolean,
|
||||
// parse the value to be a boolean and then update the input.value to match
|
||||
var type = '';
|
||||
if (decProp !== undefined && decProp.type !== undefined) {
|
||||
type = decProp.type;
|
||||
}
|
||||
if (type === '') {
|
||||
var oldval = data[name];
|
||||
if (typeof oldval === 'boolean') type = 'boolean'; // infer boolean
|
||||
else if (typeof oldval === 'number') type = 'number';
|
||||
else if (oldval instanceof go.Point) type = 'point';
|
||||
else if (oldval instanceof go.Size) type = 'size';
|
||||
else if (oldval instanceof go.Rect) type = 'rect';
|
||||
else if (oldval instanceof go.Spot) type = 'spot';
|
||||
else if (oldval instanceof go.Margin) type = 'margin';
|
||||
}
|
||||
|
||||
// convert to specific type, if needed
|
||||
switch (type) {
|
||||
case 'boolean': value = !(value === false || value === 'false' || value === '0'); break;
|
||||
case 'number': value = parseFloat(value); break;
|
||||
case 'arrayofnumber': value = this.convertToArrayOfNumber(value); break;
|
||||
case 'point': value = go.Point.parse(value); break;
|
||||
case 'size': value = go.Size.parse(value); break;
|
||||
case 'rect': value = go.Rect.parse(value); break;
|
||||
case 'spot': value = go.Spot.parse(value); break;
|
||||
case 'margin': value = go.Margin.parse(value); break;
|
||||
case 'checkbox': value = input.checked; break;
|
||||
case 'select': value = decProp.choicesArray[input.selectedIndex]; break;
|
||||
}
|
||||
|
||||
// in case parsed to be different, such as in the case of boolean values,
|
||||
// the value shown should match the actual value
|
||||
input.value = value;
|
||||
|
||||
// modify the data object in an undo-able fashion
|
||||
diagram.model.setDataProperty(data, name, value);
|
||||
|
||||
// notify any listener
|
||||
if (this.propertyModified !== null) this.propertyModified(name, value, this);
|
||||
}
|
||||
diagram.commitTransaction('set all properties');
|
||||
} else { // selection object update
|
||||
diagram.startTransaction('set all properties');
|
||||
for (var name in inspectedProps) {
|
||||
var input = inspectedProps[name];
|
||||
var value = input.value;
|
||||
var arr1 = value.split('|');
|
||||
var arr2 = [];
|
||||
if (this._multipleProperties[name]) {
|
||||
// don't split if it is union and its checkbox type
|
||||
if (this.declaredProperties[name] && this.declaredProperties[name].type === 'checkbox' && this.showAllProperties) {
|
||||
arr2.push(this._multipleProperties[name]);
|
||||
} else {
|
||||
arr2 = this._multipleProperties[name].toString().split('|');
|
||||
}
|
||||
}
|
||||
var it = diagram.selection.iterator;
|
||||
var change = false;
|
||||
if (this.declaredProperties[name] && this.declaredProperties[name].type === 'checkbox') change = true; // always change checkbox
|
||||
if (arr1.length < arr2.length // i.e Alpha|Beta -> Alpha procs the change
|
||||
&& (!this.declaredProperties[name] // from and to links
|
||||
|| !(this.declaredProperties[name] // do not change color checkbox and choices due to them always having less
|
||||
&& (this.declaredProperties[name].type === 'color' || this.declaredProperties[name].type === 'checkbox' || this.declaredProperties[name].type === 'choices')))) {
|
||||
change = true;
|
||||
} else { // standard detection in change in properties
|
||||
for (var j = 0; j < arr1.length && j < arr2.length; j++) {
|
||||
if (!(arr1[j] === arr2[j])
|
||||
&& !(this.declaredProperties[name] && this.declaredProperties[name].type === 'color' && arr1[j].toLowerCase() === arr2[j].toLowerCase())) {
|
||||
change = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (change) { // only change properties it needs to change instead all of them
|
||||
for (var i = 0; i < diagram.selection.count; i++) {
|
||||
it.next();
|
||||
var isPart = it.value instanceof go.Part;
|
||||
var data = isPart ? it.value.data : it.value;
|
||||
|
||||
if (data) { // ignores the selected node if there is no data
|
||||
if (i < arr1.length) value = arr1[i];
|
||||
else value = arr1[0];
|
||||
|
||||
// don't update "readOnly" data properties
|
||||
var decProp = this.declaredProperties[name];
|
||||
if (!this.canEditProperty(name, decProp, it.value)) continue;
|
||||
|
||||
// If it's a boolean, or if its previous value was boolean,
|
||||
// parse the value to be a boolean and then update the input.value to match
|
||||
var type = '';
|
||||
if (decProp !== undefined && decProp.type !== undefined) {
|
||||
type = decProp.type;
|
||||
}
|
||||
if (type === '') {
|
||||
var oldval = data[name];
|
||||
if (typeof oldval === 'boolean') type = 'boolean'; // infer boolean
|
||||
else if (typeof oldval === 'number') type = 'number';
|
||||
else if (oldval instanceof go.Point) type = 'point';
|
||||
else if (oldval instanceof go.Size) type = 'size';
|
||||
else if (oldval instanceof go.Rect) type = 'rect';
|
||||
else if (oldval instanceof go.Spot) type = 'spot';
|
||||
else if (oldval instanceof go.Margin) type = 'margin';
|
||||
}
|
||||
|
||||
// convert to specific type, if needed
|
||||
switch (type) {
|
||||
case 'boolean': value = !(value === false || value === 'false' || value === '0'); break;
|
||||
case 'number': value = parseFloat(value); break;
|
||||
case 'arrayofnumber': value = this.convertToArrayOfNumber(value); break;
|
||||
case 'point': value = go.Point.parse(value); break;
|
||||
case 'size': value = go.Size.parse(value); break;
|
||||
case 'rect': value = go.Rect.parse(value); break;
|
||||
case 'spot': value = go.Spot.parse(value); break;
|
||||
case 'margin': value = go.Margin.parse(value); break;
|
||||
case 'checkbox': value = input.checked; break;
|
||||
case 'select': value = decProp.choicesArray[input.selectedIndex]; break;
|
||||
}
|
||||
|
||||
// in case parsed to be different, such as in the case of boolean values,
|
||||
// the value shown should match the actual value
|
||||
input.value = value;
|
||||
|
||||
// modify the data object in an undo-able fashion
|
||||
diagram.model.setDataProperty(data, name, value);
|
||||
|
||||
// notify any listener
|
||||
if (this.propertyModified !== null) this.propertyModified(name, value, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
diagram.commitTransaction('set all properties');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Floorplanner app-specific tweaks to the basic Data Inspector class
|
||||
*/
|
||||
|
||||
function tweakInspectorForFloorplanner(inspector) {
|
||||
|
||||
inspector.declaredProperties =
|
||||
{
|
||||
"key": { show: false },
|
||||
"shape": { show: false },
|
||||
"caption": { show: false },
|
||||
"loc": { show: false },
|
||||
"geo": { show: false },
|
||||
"doorOpeningHeight": { show: false },
|
||||
"type": { show: Inspector.showIfPresent, readOnly: true },
|
||||
"category": { show: false },
|
||||
"isGroup": { show: false },
|
||||
"startpoint": { show: false },
|
||||
"endpoint": { show: false },
|
||||
"smpt1": { show: false },
|
||||
"smpt2": { show: false },
|
||||
"empt1": { show: false },
|
||||
"empt2": { show: false },
|
||||
"swing": { show: false },
|
||||
"area": { show: Inspector.showIfPresent, readOnly: true },
|
||||
"name": { show: Inspector.showIfPresent },
|
||||
"boundaryWalls": { show: false },
|
||||
"holes": { show: false },
|
||||
"angle": { show: false },
|
||||
"group": { show: false },
|
||||
"notes": { show: Inspector.showIfPresent },
|
||||
"color": { show: function (part) { return Inspector.showIfPresent && !part.data.usesTexture && (part.category === '' || part.category === 'MultiPurposeNode') }, type: 'color' },
|
||||
"stroke": { show: false, type: 'color' },
|
||||
"text": { show: Inspector.showIfPresent },
|
||||
"height": { show: function (part) { return part.category === "" || part.category === "MultiPurposeNode"; } },
|
||||
"width": { show: function (part) { return part.category === "" || part.category === "MultiPurposeNode"; } },
|
||||
"thickness": { show: function (part) { return part.category === "WallGroup" && !part.data.isDivider; } },
|
||||
"isDivider": { show: false },
|
||||
"showLabel": { show: Inspector.showIfPresent, type: 'checkbox' },
|
||||
"floorImage": { show: false },
|
||||
"showFlooringOptions": { show: false },
|
||||
"labelAlignment": { show: false },
|
||||
"texture": { show: false },
|
||||
"textures": { show: false },
|
||||
"showTextureOptions": { show: false },
|
||||
"usesTexture": { show: Inspector.showIfPresent, type: 'checkbox' }
|
||||
};
|
||||
|
||||
// this is tangentially related to inspector
|
||||
// when a node is double clicked on, show the inspector (if it is not shown)
|
||||
myFloorplan.nodeTemplateMap.iterator.each(function (kvp) {
|
||||
let template = kvp.value;
|
||||
template.doubleClick = function () {
|
||||
geHideShowWindow('ge-inspector-window', true);
|
||||
}
|
||||
myFloorplan.nodeTemplateMap.add(kvp.key, template);
|
||||
});
|
||||
|
||||
myFloorplan.groupTemplateMap.iterator.each(function (kvp) {
|
||||
let template = kvp.value;
|
||||
template.doubleClick = function () {
|
||||
geHideShowWindow('ge-inspector-window', true);
|
||||
}
|
||||
myFloorplan.groupTemplateMap.add(kvp.key, template);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Override changes include
|
||||
* - Convert raw measurement numbers to units (cm, m, ft, in)
|
||||
**/
|
||||
Inspector.prototype.buildPropertyRow = function (propertyName, propertyValue) {
|
||||
var mainDiv = this._div;
|
||||
var tr = document.createElement("tr");
|
||||
|
||||
var td1 = document.createElement("td");
|
||||
td1.textContent = propertyName;
|
||||
tr.appendChild(td1);
|
||||
|
||||
var td2 = document.createElement("td");
|
||||
var decProp = this.declaredProperties[propertyName];
|
||||
var input = null;
|
||||
var self = this;
|
||||
function updateall() { self.updateAllProperties(); }
|
||||
|
||||
if (decProp && decProp.type === "select") {
|
||||
input = document.createElement("select");
|
||||
this.updateSelect(decProp, input, propertyName, propertyValue);
|
||||
input.addEventListener("change", updateall);
|
||||
}
|
||||
|
||||
else {
|
||||
input = document.createElement("input");
|
||||
var doModify = true;
|
||||
// if height | width, convert to units (cm, m, ft, in)
|
||||
if (propertyName === "height" || propertyName === "width" || propertyName === "thickness" || propertyName === "length" || propertyName === "area") {
|
||||
propertyValue = myFloorplan.convertPixelsToUnits(propertyValue);
|
||||
if (propertyName === "area") {
|
||||
// units squared, must convert twice
|
||||
propertyValue = myFloorplan.convertPixelsToUnits(propertyValue);
|
||||
propertyValue = propertyValue.toFixed(2);
|
||||
}
|
||||
input.className = "unitsInput"; // this ensures values in these inputs are updated when units change
|
||||
if (isNaN(propertyValue) || propertyValue <= 0) {
|
||||
doModify = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (doModify) {
|
||||
input.value = this.convertToString(propertyValue);
|
||||
}
|
||||
|
||||
if (decProp) {
|
||||
var t = decProp.type;
|
||||
if (t !== 'string' && t !== 'number' && t !== 'boolean' &&
|
||||
t !== 'arrayofnumber' && t !== 'point' && t !== 'size' &&
|
||||
t !== 'rect' && t !== 'spot' && t !== 'margin') {
|
||||
input.setAttribute("type", decProp.type);
|
||||
}
|
||||
if (decProp.type === "color") {
|
||||
if (input.type === "color") {
|
||||
input.value = this.convertToColor(propertyValue);
|
||||
input.addEventListener("change", updateall);
|
||||
}
|
||||
} if (decProp.type === "checkbox") {
|
||||
input.checked = !!propertyValue;
|
||||
input.addEventListener("change", updateall);
|
||||
}
|
||||
}
|
||||
if (input.type !== "color") input.addEventListener("blur", updateall);
|
||||
}
|
||||
|
||||
if (input) {
|
||||
input.tabIndex = this.tabIndex++;
|
||||
input.disabled = !this.canEditProperty(propertyName, decProp, this.inspectedObject);
|
||||
td2.appendChild(input);
|
||||
}
|
||||
tr.appendChild(td2);
|
||||
|
||||
// maybe need a units tag
|
||||
if (input && propertyName === "height" || propertyName === "width" || propertyName === "thickness" || propertyName === "length" || propertyName === "area") {
|
||||
var input2 = document.createElement("input");
|
||||
input2.value = myFloorplan.model.modelData.unitsAbbreviation;
|
||||
input2.disabled = true;
|
||||
input2.className = "unitsBox";
|
||||
if (propertyName === "area") {
|
||||
input2.value += String.fromCharCode(178);
|
||||
}
|
||||
td2.appendChild(input2);
|
||||
}
|
||||
|
||||
this._inspectedProperties[propertyName] = input;
|
||||
return tr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override changes include
|
||||
* - Convert raw measurement numbers to units (cm, m, ft, in)
|
||||
*/
|
||||
Inspector.prototype.updateAllHTML = function () {
|
||||
var inspectedProps = this._inspectedProperties;
|
||||
var diagram = this._diagram;
|
||||
var isPart = this.inspectedObject instanceof go.Part;
|
||||
if (this.inspectedObject instanceof go.Node) {
|
||||
const node = this.inspectedObject;
|
||||
if (node.category !== 'RoomNode') {
|
||||
node.updateTargetBindings();
|
||||
node.updateAdornments();
|
||||
}
|
||||
}
|
||||
var data = isPart ? this.inspectedObject.data : this.inspectedObject;
|
||||
if (!data) { // clear out all of the fields
|
||||
for (var name in inspectedProps) {
|
||||
var input = inspectedProps[name];
|
||||
var table = input.parentNode.parentNode.parentNode;
|
||||
if (table) {
|
||||
table.innerHTML = "No node selected";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var name in inspectedProps) {
|
||||
var input = inspectedProps[name];
|
||||
var propertyValue = data[name];
|
||||
// if height | width, convert to units (cm, m, ft, in)
|
||||
if (name === "height" || name === "width" || name === "thickness" || name === "length" || name === "area") {
|
||||
propertyValue = myFloorplan.convertPixelsToUnits(propertyValue);
|
||||
if (name === "area") {
|
||||
// units squared, must convert twice
|
||||
propertyValue = myFloorplan.convertPixelsToUnits(propertyValue);
|
||||
propertyValue = propertyValue.toFixed(2);
|
||||
}
|
||||
input.value = propertyValue;
|
||||
}
|
||||
else if (input instanceof HTMLSelectElement) {
|
||||
var decProp = this.declaredProperties[name];
|
||||
this.updateSelect(decProp, input, name, propertyValue);
|
||||
} else if (input.type === "color") {
|
||||
input.value = this.convertToColor(propertyValue);
|
||||
} else if (input.type === "checkbox") {
|
||||
input.checked = !!propertyValue;
|
||||
} else {
|
||||
input.value = this.convertToString(propertyValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override changes include
|
||||
* - Convert units measurements to raw units (cm, m, ft, in to document units)
|
||||
*/
|
||||
Inspector.prototype.updateAllProperties = function () {
|
||||
var inspectedProps = this._inspectedProperties;
|
||||
var diagram = this._diagram;
|
||||
if (diagram.selection.count === 1 || !this.multipleSelection) { // single object update
|
||||
var isPart = this.inspectedObject instanceof go.Part;
|
||||
var data = isPart ? this.inspectedObject.data : this.inspectedObject;
|
||||
if (!data) return; // must not try to update data when there's no data!
|
||||
|
||||
diagram.startTransaction('set all properties');
|
||||
for (var name in inspectedProps) {
|
||||
var input = inspectedProps[name];
|
||||
var value = input.value;
|
||||
|
||||
// don't update "readOnly" data properties
|
||||
var decProp = this.declaredProperties[name];
|
||||
if (!this.canEditProperty(name, decProp, this.inspectedObject)) continue;
|
||||
|
||||
// If it's a boolean, or if its previous value was boolean,
|
||||
// parse the value to be a boolean and then update the input.value to match
|
||||
var type = '';
|
||||
if (decProp !== undefined && decProp.type !== undefined) {
|
||||
type = decProp.type;
|
||||
}
|
||||
if (type === '') {
|
||||
var oldval = data[name];
|
||||
if (typeof oldval === 'boolean') type = 'boolean'; // infer boolean
|
||||
else if (typeof oldval === 'number') type = 'number';
|
||||
else if (oldval instanceof go.Point) type = 'point';
|
||||
else if (oldval instanceof go.Size) type = 'size';
|
||||
else if (oldval instanceof go.Rect) type = 'rect';
|
||||
else if (oldval instanceof go.Spot) type = 'spot';
|
||||
else if (oldval instanceof go.Margin) type = 'margin';
|
||||
}
|
||||
|
||||
// convert to specific type, if needed
|
||||
switch (type) {
|
||||
case 'boolean': value = !(value === false || value === 'false' || value === '0'); break;
|
||||
case 'number': value = parseFloat(value); break;
|
||||
case 'arrayofnumber': value = this.convertToArrayOfNumber(value); break;
|
||||
case 'point': value = go.Point.parse(value); break;
|
||||
case 'size': value = go.Size.parse(value); break;
|
||||
case 'rect': value = go.Rect.parse(value); break;
|
||||
case 'spot': value = go.Spot.parse(value); break;
|
||||
case 'margin': value = go.Margin.parse(value); break;
|
||||
case 'checkbox': value = input.checked; break;
|
||||
case 'select': value = decProp.choicesArray[input.selectedIndex]; break;
|
||||
}
|
||||
|
||||
// if height | width, convert to units (cm, m, ft, in)
|
||||
if (input && name === "height" || name === "width" || name === "thickness" || name === "length" || name === "area") {
|
||||
value = myFloorplan.convertUnitsToPixels(value);
|
||||
if (name === "area") {
|
||||
// units squared, must convert twice
|
||||
value = myFloorplan.convertUnitsToPixels(value);
|
||||
value = propertyValue.toFixed(2);
|
||||
}
|
||||
if (isNaN(parseFloat(value)) || parseFloat(value) <= 0) {
|
||||
var oldVal = this.inspectedObject.data[name];
|
||||
value = oldVal;
|
||||
}
|
||||
}
|
||||
|
||||
// in case parsed to be different, such as in the case of boolean values,
|
||||
// the value shown should match the actual value
|
||||
input.value = value;
|
||||
|
||||
// modify the data object in an undo-able fashion
|
||||
diagram.model.setDataProperty(data, name, value);
|
||||
|
||||
if (this.inspectedObject.category === 'WallGroup') {
|
||||
var wall = this.inspectedObject;
|
||||
var wrt = myFloorplan.toolManager.mouseDownTools.elt(3);
|
||||
wrt.performMiteringOnWall(wall);
|
||||
var set = new go.Set(); set.add(wall);
|
||||
myFloorplan.updateAllRoomBoundaries(set);
|
||||
}
|
||||
|
||||
// notify any listener
|
||||
if (this.propertyModified !== null) this.propertyModified(name, value, this);
|
||||
}
|
||||
|
||||
diagram.commitTransaction('set all properties');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*-- set border box on all elements inside the grid*/
|
||||
.grid-container * {box-sizing: border-box;}
|
||||
|
||||
.row:before, .row::after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
[class*='col-'] {
|
||||
float: left;
|
||||
min-height: 1px;
|
||||
width: 16.66%;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.col-1 {width: 100%;}
|
||||
.col-2 {width: 50%;}
|
||||
.col-3 {width: 33.33%;}
|
||||
.col-4 {width: 25%;}
|
||||
.col-5 {width: 20%;}
|
||||
.col-6 {width: 16.66%;}
|
||||
|
||||
input{
|
||||
padding: 0px;
|
||||
border: 1px solid gray;
|
||||
}
|
||||
.unitsBox{ /*A special input that displays units*/
|
||||
text-align: center;
|
||||
width: 20px;
|
||||
border: 1px solid gray;
|
||||
border-left: 0px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#wallThicknessInput, #gridSizeInput, #unitsConversionFactorInput {
|
||||
width: 50px;
|
||||
float: left;
|
||||
border-right: 0px;
|
||||
}
|
||||
#gridSizeInput, #unitsConversionFactorInput {
|
||||
margin-left: 10%;
|
||||
}
|
||||
#wallThicknesshUnitsInput, #gridSizeUnitsInput{
|
||||
float: left;
|
||||
}
|
||||
|
||||
.unitsInput {
|
||||
float: left;
|
||||
border-right: 0px;
|
||||
}
|
||||
|
||||
#optionsWindow {
|
||||
height: unset;
|
||||
width: unset;
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* goeditor-setup.js
|
||||
* Called before init() events in every goeditor app
|
||||
* Initializes the diagrams, palettes, GoCloudStorage, and the Inspector
|
||||
* You may need to edit or override things in your own code
|
||||
* DO NOT edit this file -- make app-specific overrides or custom changes in your own code
|
||||
* DO NOT delete or rename this file
|
||||
*/
|
||||
|
||||
function setupEditorApplication(diagramsCount, palettesCount, pathToStorage, diagramsType) {
|
||||
|
||||
if (diagramsType === null || !diagramsType) {
|
||||
diagramsType = go.Diagram;
|
||||
}
|
||||
|
||||
// GoCloudStorage helpers
|
||||
var isAutoSavingCheckbox = document.getElementById("isAutoSavingCheckbox");
|
||||
var isAutoSavingP = document.getElementById("isAutoSavingP");
|
||||
|
||||
// choose new storage service; then, update the current storage span with the correct picture of the current storage service being used
|
||||
updateCurrentStorageSpan = function () {
|
||||
storageManager.selectStorageService().then(function (storage) {
|
||||
var span = document.getElementById("currentStorageSpan");
|
||||
span.innerHTML = "";
|
||||
var imageSrc = storageManager.getStorageIconPath(storage.className);
|
||||
var img = document.createElement("img");
|
||||
img.src = imageSrc;
|
||||
img.style.width = "20px"; img.style.height = "20px"; img.style.float = "left";
|
||||
span.appendChild(img);
|
||||
storage.isAutoSaving = isAutoSavingCheckbox.checked;
|
||||
updateAutoSaveVisibility();
|
||||
});
|
||||
}
|
||||
|
||||
isAutoSavingCheckbox.addEventListener("change", function () {
|
||||
storageManager.storages.iterator.each(function (storage) {
|
||||
storage.isAutoSaving = isAutoSavingCheckbox.checked;
|
||||
});
|
||||
// update the title to reflect the save
|
||||
var currentFile = document.getElementById("ge-filename");
|
||||
var currentFileTitle = currentFile.innerText;
|
||||
if (currentFileTitle[currentFileTitle.length - 1] == "*" && storageManager.currentStorage.currentDiagramFile.name != null) {
|
||||
currentFile.innerText = currentFileTitle.substr(0, currentFileTitle.length - 1);
|
||||
storageManager.currentStorage.save();
|
||||
}
|
||||
});
|
||||
|
||||
// update the title on page to reflect newly loaded diagram title
|
||||
updateTitle = function () {
|
||||
var currentFile = document.getElementById("ge-filename");
|
||||
if (storageManager.currentStorage.currentDiagramFile.path !== null) {
|
||||
var storage = storageManager.currentStorage;
|
||||
if (storage.currentDiagramFile.path) currentFile.innerHTML = storage.currentDiagramFile.path;
|
||||
else currentFile.innerHTML = storage.currentDiagramFile.name;
|
||||
}
|
||||
else {
|
||||
currentFile.innerHTML = "Untitled";
|
||||
storageTag.innerHTML = "Unsaved";
|
||||
}
|
||||
}
|
||||
|
||||
// can only use the auto save checkbox if the file is already saved to the current storage service
|
||||
updateAutoSaveVisibility = function () {
|
||||
var cdf = storageManager.currentStorage.currentDiagramFile;
|
||||
isAutoSavingP.style.visibility = (cdf.name === null) ? "hidden" : "visible";
|
||||
}
|
||||
|
||||
/*
|
||||
* Promise handler for core functions
|
||||
* @param {String} action Accepted values: Load, Delete, New, Save
|
||||
*/
|
||||
handlePromise = function (action) {
|
||||
|
||||
function handleFileData(action, fileData) {
|
||||
var words = [];
|
||||
switch (action) {
|
||||
case 'Load': words = ['Loaded', 'from']; break;
|
||||
case 'Delete': words = ['Deleted', 'from']; break;
|
||||
case 'New': words = ['Created', 'at']; break;
|
||||
case 'Save': words = ['Saved', 'to']; break;
|
||||
case 'SaveAs': words = ['Saved', 'to']; break;
|
||||
}
|
||||
var storageServiceName = storageManager.currentStorage.serviceName;
|
||||
if (fileData.id && fileData.name && fileData.path) storageManager.showMessage(words[0] + ' ' + fileData.name + ' (file ID ' + fileData.id + ') ' +
|
||||
words[1] + ' path ' + fileData.path + " in " + storageServiceName, 1.5);
|
||||
else console.log(fileData); // may have an explanation for why fileData isn't complete
|
||||
updateTitle();
|
||||
updateAutoSaveVisibility();
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case 'Load': storageManager.load().then(function (fileData) {
|
||||
handleFileData(action, fileData);
|
||||
}); break;
|
||||
case 'Delete': storageManager.remove().then(function (fileData) {
|
||||
handleFileData(action, fileData);
|
||||
}); break;
|
||||
case 'New':
|
||||
var saveBefore = false;
|
||||
var currentFile = document.getElementById("ge-filename");
|
||||
// only prompt to save current changes iff there is some modified state
|
||||
var currentFileTitle = currentFile.innerText;
|
||||
if (currentFileTitle.substr(currentFileTitle.length - 1, 1) === "*") {
|
||||
saveBefore = true;
|
||||
}
|
||||
storageManager.create(saveBefore).then(function (fileData) {
|
||||
handleFileData(action, fileData);
|
||||
});
|
||||
break;
|
||||
case 'SaveAs': storageManager.save().then(function (fileData) {
|
||||
handleFileData(action, fileData);
|
||||
}); break;
|
||||
case 'Save': storageManager.save(false).then(function (fileData) {
|
||||
handleFileData(action, fileData);
|
||||
}); break;
|
||||
}
|
||||
}
|
||||
|
||||
// Small, generic helper functions
|
||||
refreshDraggableWindows = function () {
|
||||
jQuery(".gt-menu").draggable({ handle: ".gt-handle", stack: ".gt-menu", containment: 'window', scroll: false });
|
||||
}
|
||||
|
||||
|
||||
// makes images of each diagram
|
||||
makeDiagramImage = function () {
|
||||
for (var i = 0; i < diagrams.length; i++) {
|
||||
var diagram = diagrams[i];
|
||||
var imgdata = diagram.makeImageData({ maxSize: new go.Size(Infinity, Infinity), scale: 2, padding: 10, background: diagram.div.style.background });
|
||||
var a = document.createElement("a");
|
||||
var filename = document.getElementById("ge-filename").innerText;
|
||||
filename.split('.');
|
||||
filename = filename[0];
|
||||
a.download = filename;
|
||||
a.href = imgdata;
|
||||
a.target = "_blank";
|
||||
a.click();
|
||||
}
|
||||
}
|
||||
|
||||
// make SVG files of each diagram
|
||||
makeDiagramSvg = function () {
|
||||
for (var i = 0; i < diagrams.length; i++) {
|
||||
var diagram = diagrams[i];
|
||||
var svgDataEl = diagram.makeSVG();
|
||||
var s = new XMLSerializer();
|
||||
var svgData = s.serializeToString(svgDataEl);
|
||||
var svgBlob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
|
||||
var svgUrl = URL.createObjectURL(svgBlob);
|
||||
var downloadLink = document.createElement("a");
|
||||
downloadLink.href = svgUrl;
|
||||
var filename = document.getElementById("ge-filename").innerText;
|
||||
filename = filename.split('.');
|
||||
filename = filename[0];
|
||||
downloadLink.download = filename + ".svg";
|
||||
document.body.appendChild(downloadLink);
|
||||
downloadLink.click();
|
||||
document.body.removeChild(downloadLink);
|
||||
}
|
||||
}
|
||||
|
||||
$ = go.GraphObject.make; // for conciseness in defining templates
|
||||
|
||||
|
||||
// build diagrams
|
||||
diagrams = [];
|
||||
overviews = [];
|
||||
for (var i = 0; i < diagramsCount; i++) {
|
||||
var diagram = new diagramsType("ge-diagram-" + i); // create a Diagram for the DIV HTML element
|
||||
diagram.undoManager.isEnabled = true;
|
||||
// When diagram is modified, change title to include a *
|
||||
diagram.addChangedListener(function (e) {
|
||||
// maybe update the file header
|
||||
var currentFile = document.getElementById("ge-filename");
|
||||
if (isAutoSavingCheckbox.checked && storageManager.currentStorage.currentDiagramFile.name != null) return;
|
||||
if (currentFile) {
|
||||
var idx = currentFile.textContent.indexOf("*");
|
||||
if (e.diagram.isModified) {
|
||||
if (idx < 0) currentFile.textContent = currentFile.textContent + "*";
|
||||
}
|
||||
else {
|
||||
if (idx >= 0) currentFile.textContent = currentFile.textContent.substr(0, idx);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
diagrams[i] = diagram;
|
||||
|
||||
// make an overview for each diagram
|
||||
var overview = $(go.Overview, "ge-overview-" + i, { observed: diagram });
|
||||
overviews[i] = overview;
|
||||
}
|
||||
|
||||
// if there are no diagrams, there will be no overviews, so do not list that option in View menu
|
||||
if (diagramsCount < 1) {
|
||||
var viewOverviewsOption = document.getElementById("ge-viewoption-overviews");
|
||||
viewOverviewsOption.parentNode.removeChild(viewOverviewsOption);
|
||||
}
|
||||
|
||||
// build palette(s)
|
||||
palettes = [];
|
||||
for (var i = 0; i < palettesCount; i++) {
|
||||
var palette = $(go.Palette, "ge-palette-" + i);
|
||||
palettes[i] = palette;
|
||||
}
|
||||
|
||||
|
||||
// Go Cloud Storage stuff
|
||||
defaultModel = null; // change this if you want -- so GoCloudStorage documentation
|
||||
|
||||
var iconsDir = pathToStorage + "/goCloudStorageIcons/";
|
||||
gls = new gcs.GoLocalStorage(diagrams, defaultModel, iconsDir);
|
||||
god = new gcs.GoOneDrive(diagrams, 'f9b171a6-a12e-48c1-b86c-814ed40fcdd1', defaultModel, iconsDir);
|
||||
ggd = new gcs.GoGoogleDrive(diagrams, '16225373139-n24vtg7konuetna3ofbmfcaj2infhgmg.apps.googleusercontent.com', 'AIzaSyDBj43lBLpYMMVKw4aN_pvuRg7_XMVGf18', defaultModel, iconsDir);
|
||||
gdb = new gcs.GoDropBox(diagrams, '3sm2ko6q7u1gbix', defaultModel, iconsDir);
|
||||
storages = [gls, god, ggd, gdb];
|
||||
storageManager = new gcs.GoCloudStorageManager(storages, iconsDir);
|
||||
|
||||
var span = document.getElementById("currentStorageSpan");
|
||||
span.innerHTML = "";
|
||||
var imageSrc = storageManager.getStorageIconPath(storageManager.currentStorage.className);
|
||||
var img = document.createElement("img");
|
||||
img.src = imageSrc;
|
||||
img.style.width = "20px"; img.style.height = "20px"; img.style.float = "left";
|
||||
span.appendChild(img);
|
||||
|
||||
|
||||
document.getElementById('file-input').accept = ".csv";
|
||||
|
||||
storageManager.currentStorage.isAutoSaving = isAutoSavingCheckbox.checked;
|
||||
|
||||
// enable hotkeys
|
||||
document.body.addEventListener("keydown", function (e) {
|
||||
var keynum = e.which;
|
||||
if (e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
switch (keynum) {
|
||||
case 83: handlePromise('Save'); break; // ctrl + s
|
||||
case 79: handlePromise('Load'); break; // ctrl + o
|
||||
case 68: handlePromise('New'); break; // ctrl + d
|
||||
case 82: handlePromise('Delete'); break; // ctrl + r
|
||||
case 80: geHideShowWindow('ge-palettes-window'); break;
|
||||
case 69: geHideShowWindow('ge-overviews-window'); break; // ctrl + e
|
||||
case 73: geHideShowWindow('ge-inspector-window'); break; // ctrl + i
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
updateAutoSaveVisibility();
|
||||
|
||||
// Format the inspector for your specific needs. You may need to edit the DataInspector class
|
||||
inspector = new Inspector('ge-inspector', diagrams[0],
|
||||
{
|
||||
includesOwnProperties: true,
|
||||
}
|
||||
);
|
||||
|
||||
geHideShowWindow = function (id, doShow) {
|
||||
var geWindow = document.getElementById(id);
|
||||
var vis = null;
|
||||
if (doShow === undefined) vis = geWindow.style.visibility === "visible" ? "hidden" : "visible";
|
||||
else if (doShow) vis = "visible";
|
||||
else vis = "hidden";
|
||||
|
||||
var pn = null;
|
||||
if (geWindow.parentNode.classList.contains("ge-menu")) {
|
||||
pn = geWindow.parentNode;
|
||||
}
|
||||
if (pn) {
|
||||
pn.style.visibility = vis;
|
||||
}
|
||||
geWindow.style.visibility = vis;
|
||||
}
|
||||
|
||||
function makeGCSWindowsDraggable() {
|
||||
// special -- make sure all gcs windows are draggable via jQuery UI classes
|
||||
// do so by wrapping the filepicker divs in a draggable ge-window div -- with a handle
|
||||
var gcsWindows = document.getElementsByClassName("goCustomFilepicker");
|
||||
gcsWindows = [].slice.call(gcsWindows);
|
||||
var gcsManagerMenu = document.getElementById("goCloudStorageManagerMenu");
|
||||
gcsWindows.push(gcsManagerMenu);
|
||||
for (var i = 0; i < gcsWindows.length; i++) {
|
||||
var gcsWindow = gcsWindows[i];
|
||||
|
||||
// possibly delete pre-existing window
|
||||
var id = "ge-" + gcsWindow.id + "-window";
|
||||
var windowParent = document.getElementById(id);
|
||||
if (windowParent !== null && windowParent !== undefined) {
|
||||
windowParent.parentNode.removeChild(windowParent);
|
||||
}
|
||||
|
||||
// construct window wrapper for gcs menu
|
||||
windowParent = document.createElement("div");
|
||||
windowParent.id = id;
|
||||
windowParent.classList.add("ge-draggable");
|
||||
windowParent.classList.add("ui-draggable");
|
||||
windowParent.classList.add("ge-menu");
|
||||
windowParent.style.visibility = "hidden";
|
||||
|
||||
|
||||
var handle = document.createElement("div");
|
||||
handle.id = id + "-handle";
|
||||
handle.classList.add("ge-handle");
|
||||
handle.classList.add("ui-draggable-handle");
|
||||
handle.innerText = "Storage";
|
||||
var button = document.createElement("button");
|
||||
button.id = id + "-close";
|
||||
button.innerText = "X";
|
||||
button.classList.add("ge-clickable");
|
||||
button.classList.add("ge-window-button");
|
||||
button.onclick = function () {
|
||||
var ci = this.id.indexOf("-close");
|
||||
var wpid = this.id.substring(0, ci);
|
||||
var windowParent = document.getElementById(wpid);
|
||||
geHideShowWindow(windowParent.id);
|
||||
for (var i = 0; i < windowParent.children.length; i++) {
|
||||
var child = windowParent.children[i];
|
||||
if (!child.classList.contains("ge-handle")) {
|
||||
child.style.visibility = windowParent.style.visibility;
|
||||
}
|
||||
}
|
||||
}
|
||||
handle.appendChild(button);
|
||||
|
||||
windowParent.appendChild(handle);
|
||||
|
||||
windowParent.appendChild(gcsWindow);
|
||||
document.body.appendChild(windowParent);
|
||||
|
||||
var observer = new MutationObserver(styleChangedCallback);
|
||||
observer.observe(gcsWindow, {
|
||||
attributes: true,
|
||||
attributeFilter: ['style'],
|
||||
});
|
||||
|
||||
function styleChangedCallback(mutations) {
|
||||
var newVis = mutations[0].target.style.visibility;
|
||||
var pn = mutations[0].target.parentNode;
|
||||
pn.style.visibility = newVis;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
JQUERY(function () {
|
||||
var draggables = document.getElementsByClassName("ge-draggable");
|
||||
for (var i = 0; i < draggables.length; i++) {
|
||||
var draggable = draggables[i];
|
||||
var id = "#" + draggable.id; var hid = id + "-handle";
|
||||
JQUERY(id).draggable({ handle: hid, stack: ".ge-draggable", containment: "window", scroll: false });
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
makeGCSWindowsDraggable();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* goeditor.css
|
||||
* Used for the goeditor framework
|
||||
* DO NOT edit this file. Use a custom CSS file for app-specific style changes
|
||||
* DO NOT delete or rename this file
|
||||
*/
|
||||
|
||||
|
||||
/********************************************** MAIN CONTENT STYLING ***********************************************/
|
||||
body {font-family: Arial; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none;}
|
||||
canvas:focus {outline:0;} /*don't outline palette and diagram when they are focused*/
|
||||
p, h1,h2,h3,h4,h5,h6{cursor: default}
|
||||
/*Style placeholder text to be black on all browsers*/
|
||||
::-webkit-input-placeholder { color: black; }
|
||||
::-moz-placeholder { color: black; }
|
||||
:-ms-input-placeholder { color: black; }
|
||||
/************************************************************* NAV BAR STYLING *************************************************/
|
||||
|
||||
nav {background: linear-gradient(#efefef ,#bbbbbb ); padding: 5px;}
|
||||
|
||||
nav ul { /*file menus and tool menus*/
|
||||
list-style: none;
|
||||
position: relative;
|
||||
display: flex;
|
||||
margin: 0; padding: 0;
|
||||
}
|
||||
|
||||
nav ul li { /*menu tabs*/
|
||||
float: left;
|
||||
transition-property: background;
|
||||
transition-duration: 0.3s;
|
||||
}
|
||||
nav ul li:hover {
|
||||
transition-delay: 0.1s, 0.1s;
|
||||
background: #4f5964;
|
||||
}
|
||||
|
||||
nav ul li a { /*menu tabs text*/
|
||||
display: block;
|
||||
padding-left: 25px; padding-right: 25px; padding-top: 2px;
|
||||
color: #757575; text-decoration: none;
|
||||
transition-property: background, color;
|
||||
transition-duration: 0.4s, 0.4s;
|
||||
}
|
||||
nav ul li:hover a {color: #fff;}
|
||||
|
||||
nav ul ul { /*drop down menus, general*/
|
||||
display: block;
|
||||
z-index: 20; /*in front of everything, even .draggables*/
|
||||
visibility: hidden; opacity: 0;
|
||||
transition-property: opacity;
|
||||
transition-duration: 0.3s;
|
||||
background: #5f6975; padding: 0;
|
||||
position: absolute; top: 100%;
|
||||
width: max-content;
|
||||
/* width: 220%; /*band aid fix for menu width -- TODO*/
|
||||
}
|
||||
nav ul li:hover > ul { /*drop down menus, visible*/
|
||||
visibility: visible; opacity: 1;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
nav ul ul li { /*drop down menu items*/
|
||||
border-top: 1px solid #6b727c;
|
||||
border-bottom: 1px solid #575f6a;
|
||||
position: relative; float: none;
|
||||
}
|
||||
nav ul ul li a { /*drown down menu items text*/
|
||||
padding: 5px;
|
||||
transition-property: background;
|
||||
transition-duration: 0.1s;
|
||||
}
|
||||
nav ul ul li a:hover {
|
||||
background: #4b545f;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
nav ul ul ul { /*drop down submenu (import from)*/
|
||||
left: 100%; top:0;
|
||||
}
|
||||
|
||||
nav p { /* grid input area, wall width area, and 'Tools' all wrapped in <p> tag*/
|
||||
float: left;
|
||||
margin-bottom: 0px;
|
||||
margin-top: 3px;
|
||||
padding-left: 25px;
|
||||
padding-right: 25px;
|
||||
color: #4b545f;
|
||||
}
|
||||
nav p.ge-shortcut {
|
||||
font-size: 9pt;
|
||||
float: right;
|
||||
color: #efefef;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#ge-header {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#ge-filename {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#ge-filemenus {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.ge-menu {
|
||||
z-index: 100;
|
||||
background: white;
|
||||
width: 400px;
|
||||
max-width: 500px;
|
||||
border: 1px solid black;
|
||||
top: 25%;
|
||||
left: 40%;
|
||||
position: absolute;
|
||||
box-shadow: 10px 10px 5px #888888;
|
||||
text-align: center;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.ge-handle * {
|
||||
padding: 2px;
|
||||
margin: 0;
|
||||
margin-bottom: 10px;
|
||||
background: gray;
|
||||
color: white;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.ge-scrollable {
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.ge-clickable {
|
||||
cursor: pointer;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.ge-clickable.ge-selected {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.ge-button {
|
||||
background-color: #4CAF50;
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
margin: 4px 2px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#ge-datas-div {
|
||||
margin: 10px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.ge-data-option-div {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.ge-data-option-div span {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.ge-build-event-property-div, .ge-build-range-property-div {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.ge-build-event-property-div button, .ge-build-range-property-div button {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.ge-build-event-property-div select, .ge-build-event-property-div input, .ge-build-range-property-div label, .ge-build-range-property-div button,
|
||||
.ge-edit-timeline-property-div input, .ge-edit-timeline-property-div label {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.ge-replace-event-data-checkbox-div {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.ge-clickable {cursor: pointer;}
|
||||
|
||||
label > input{ /* HIDE RADIO */
|
||||
visibility: hidden; /* Makes input not-clickable */
|
||||
position: absolute; /* Remove input from document flow */
|
||||
}
|
||||
label > input + img{ /* IMAGE STYLES */
|
||||
cursor:pointer;
|
||||
border:2px solid transparent;
|
||||
}
|
||||
label > input:checked + img{ /* (RADIO CHECKED) IMAGE STYLES */
|
||||
border:2px solid #f00;
|
||||
}
|
||||
|
||||
#ge-footer {
|
||||
background: linear-gradient(#efefef ,#bbbbbb);
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#ge-palettes-window {
|
||||
height: inherit;
|
||||
top:12%;
|
||||
left:.5%;
|
||||
}
|
||||
|
||||
#ge-overviews-window {
|
||||
height: inherit;
|
||||
top: 12%;
|
||||
left: 87.5%;
|
||||
}
|
||||
|
||||
/********************************** DRAGGABLE WINDOWS GENERAL **************/
|
||||
|
||||
.ge-draggable {
|
||||
border: 1px solid gray;
|
||||
background-color: #e2e2e2;
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
width: 300px;
|
||||
height: unset;
|
||||
z-index: 10;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ge-handle {
|
||||
background-color: #4b545f;
|
||||
text-align: center;
|
||||
font: bold 12px sans-serif;
|
||||
color: white;
|
||||
cursor: move;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ge-window-button{
|
||||
float: right;
|
||||
border: none;
|
||||
font: bold 12px sans-serif;
|
||||
padding-bottom: 0; padding-top: 0;
|
||||
padding-left: 10px; padding-right: 10px;
|
||||
}
|
||||
|
||||
/* jQuery UI specific stylings */
|
||||
|
||||
.ui-accordion .ui-accordion-content, .ui-accordion .ui-accordion-icons {padding: 0px;}
|
||||
.ui-widget * { outline: none; }
|
||||
.ui-widget-content { border: none; }
|
||||
|
||||
/* Inspector window */
|
||||
|
||||
#ge-inspector-window {
|
||||
height: unset;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#ge-inspector {
|
||||
height: inherit; width: inherit;
|
||||
}
|
||||
|
||||
/****** GO CLOUD STORAGE STYLINGS OVERRIDES ***/
|
||||
|
||||
.ge-menu .goCustomFilepicker {
|
||||
width: -webkit-fill-available;
|
||||
height: unset;
|
||||
top: auto; left: auto;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.ge-menu #goCloudStorageManagerMenu {
|
||||
width: unset;
|
||||
top: auto; left: auto;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#ge-goCloudStorageManagerMenu-window {
|
||||
height: auto;
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 742 B |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,512 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Floorplanner (Typescript)</title>
|
||||
<meta name="description" content="" />
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<!-- Local JS -->
|
||||
<!-- GoJS -->
|
||||
<script src="../../release/go.js"></script>
|
||||
<!-- GoEditor framework basic setup -->
|
||||
<script src="./goeditor-setup.js"></script>
|
||||
<!-- Base Data Inspector class -->
|
||||
<script src="./DataInspector.js"></script>
|
||||
<!-- Floorplanner specific tweaks to Data Inspector class -->
|
||||
<script src="./floorplanner-datainspector-overrides.js"></script>
|
||||
|
||||
<!-- Go Cloud Storage Classes -->
|
||||
<script src="../../projects/storage/lib/gcs.js"></script>
|
||||
|
||||
<!-- CDN's for GoCloud Storage subclasses -->
|
||||
<script src="https://apis.google.com/js/api.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/dropbox.js/2.5.7/Dropbox-sdk.min.js"></script>
|
||||
<script src="https://js.live.net/v7.2/OneDrive.js"></script>
|
||||
<script type="text/javascript" src="https://www.dropbox.com/static/api/2/dropins.js" id="dropboxjs" data-app-key="3sm2ko6q7u1gbix"></script>
|
||||
|
||||
<!-- GoFloorPlanner bundle -->
|
||||
<script src="./lib/gfp.js"></script>
|
||||
|
||||
<!-- jQuery / UI JS -->
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/jquery-ui.min.js"></script>
|
||||
|
||||
<!-- CSS for GoCloudStorage/Manager -->
|
||||
<link rel="stylesheet" type="text/css" href="../../projects/storage/samples/GoCloudStorageUI.css" />
|
||||
|
||||
<!-- jQuery UI CSS -->
|
||||
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/themes/smoothness/jquery-ui.css" />
|
||||
|
||||
<!-- CSS for this app -->
|
||||
<link rel="stylesheet" type="text/css" href="./goeditor.css" />
|
||||
<link rel="stylesheet" type="text/css" href="./floorplanner.css" />
|
||||
|
||||
<script>
|
||||
|
||||
JQUERY = $;
|
||||
|
||||
function init() {
|
||||
|
||||
Floorplan = gfp.Floorplan;
|
||||
FloorplanPalette = gfp.FloorplanPalette;
|
||||
WallBuildingTool = gfp.WallBuildingTool;
|
||||
WallReshapingTool = gfp.WallReshapingTool;
|
||||
|
||||
// Do not remove this function call! This sets up your editor
|
||||
setupEditorApplication(1, 2, "../../projects/storage", Floorplan);
|
||||
|
||||
// replace generic palettes with FloorplanPalettes
|
||||
myFloorplan = diagrams[0];
|
||||
palettes[0].div = null;
|
||||
palettes[1].div = null;
|
||||
|
||||
furniturePalette = new FloorplanPalette("ge-palette-0", myFloorplan);
|
||||
furniturePalette.model = new go.GraphLinksModel(myFloorplan.makeDefaultFurniturePaletteNodeData());
|
||||
palettes[0] = furniturePalette;
|
||||
|
||||
wallPartsPalette = new FloorplanPalette("ge-palette-1", myFloorplan);
|
||||
wallPartsPalette.model = new go.GraphLinksModel(myFloorplan.makeDefaultWallpartsPaletteNodeData());
|
||||
palettes[1] = wallPartsPalette;
|
||||
|
||||
// set default model for all Cloud Storage subclasses
|
||||
for (i in storages) {
|
||||
var storage = storages[i];
|
||||
var dm = JSON.stringify({
|
||||
"class": "GraphLinksModel",
|
||||
"copiesKey": false,
|
||||
"modelData": { "units": "meters", "unitsAbbreviation": "m", "unitsConversionFactor": 0.02, "gridSize": 10, "wallThickness": 10, "preferences": { "showWallGuidelines": true, "showWallLengths": true, "showWallAngles": true, "showOnlySmallWallAngles": true, "showGrid": true, "gridSnap": true } },
|
||||
"nodeDataArray": [],
|
||||
"linkDataArray": []
|
||||
});
|
||||
storage.defaultModel = dm;
|
||||
}
|
||||
|
||||
// listen if the model of the Floorplan changes completely -- if so, there has been a load event, and we must update walls / rooms
|
||||
myFloorplan.addDiagramListener("InitialLayoutCompleted", function (e) {
|
||||
|
||||
// update units, grid size, units / px, showGrid, and preferences from the loading model's modelData
|
||||
var unitsForm = document.getElementById('unitsForm');
|
||||
var gridSizeInput = document.getElementById('gridSizeInput');
|
||||
var showGridCheckbox = document.getElementById('showGridCheckbox');
|
||||
var gridSnapCheckbox = document.getElementById('gridSnapCheckbox');
|
||||
var showWallGuidelinesCheckbox = document.getElementById('wallGuidelinesCheckbox');
|
||||
var showWallLengthsCheckbox = document.getElementById('wallLengthsCheckbox');
|
||||
var showWallAnglesCheckbox = document.getElementById('wallAnglesCheckbox');
|
||||
var showOnlySmallWallAnglesCheckbox = document.getElementById('onlySmallWallAnglesCheckbox');
|
||||
|
||||
var unitsConversionFactorInput = document.getElementById('unitsConversionFactorInput');
|
||||
|
||||
var fp = e.diagram;
|
||||
var md = fp.model.modelData;
|
||||
var units = md.units;
|
||||
// if (units === undefined) return;
|
||||
var unitsRadioChecked = document.getElementById(units);
|
||||
unitsRadioChecked.checked = true;
|
||||
var gridSize = md.gridSize;
|
||||
gridSize = fp.convertPixelsToUnits(gridSize);
|
||||
gridSizeInput.value = gridSize;
|
||||
fp.changeGridSize(gridSizeInput);
|
||||
var unitsConversionFactor = md.unitsConversionFactor;
|
||||
unitsConversionFactorInput.value = unitsConversionFactor;
|
||||
fp.changeUnitsConversionFactor(unitsConversionFactorInput, gridSizeInput);
|
||||
fp.changeUnits(unitsForm);
|
||||
|
||||
var showGrid = md.preferences.showGrid;
|
||||
var gridSnap = md.preferences.gridSnap;
|
||||
var showWallGuidelines = md.preferences.showWallGuidelines;
|
||||
var showWallLengths = md.preferences.showWallLengths;
|
||||
var showWallAngles = md.preferences.showWallAngles;
|
||||
var showOnlySmallWallAngles = md.preferences.showOnlySmallWallAngles;
|
||||
|
||||
showGridCheckbox.checked = showGrid;
|
||||
gridSnapCheckbox.checked = gridSnap;
|
||||
showWallGuidelinesCheckbox.checked = showWallGuidelines;
|
||||
showWallLengthsCheckbox.checked = showWallLengths;
|
||||
showWallAnglesCheckbox.checked = showWallAngles;
|
||||
showOnlySmallWallAnglesCheckbox = showOnlySmallWallAngles;
|
||||
|
||||
fp.checkboxChanged('showGridCheckbox');
|
||||
fp.checkboxChanged('gridSnapCheckbox');
|
||||
fp.checkboxChanged('wallGuidelinesCheckbox');
|
||||
fp.checkboxChanged('wallLengthsCheckbox');
|
||||
fp.checkboxChanged('wallAnglesCheckbox');
|
||||
fp.checkboxChanged('onlySmallWallAnglesCheckbox');
|
||||
|
||||
// update walls and rooms geometries
|
||||
fp.nodes.iterator.each(function (n) {
|
||||
if (n.category === "WallGroup") {
|
||||
fp.updateWall(n);
|
||||
}
|
||||
|
||||
if (n.category === "RoomNode") {
|
||||
fp.updateRoom(n);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Update the tools buttons so the tool in use is highlighted
|
||||
updateButtons = function (func, el) {
|
||||
func.call(myFloorplan);
|
||||
var toolButtons = document.getElementsByClassName('toolButtons');
|
||||
for (var i = 0; i < toolButtons.length; i++) {
|
||||
var tb = toolButtons[i];
|
||||
if (tb === el) {
|
||||
tb.style.background = "#4b545f";
|
||||
tb.style.color = "white";
|
||||
}
|
||||
else {
|
||||
tb.style.background = "rgb(221, 221, 221)";
|
||||
tb.style.color = "black";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JQUERY(function () {
|
||||
JQUERY("#ge-palettes-container").accordion({
|
||||
heightStyle: "content",
|
||||
activate: function (event, ui) {
|
||||
for (var i = 0; i < palettes.length; i++) {
|
||||
var palette = palettes[i];
|
||||
palette.requestUpdate();
|
||||
}
|
||||
}
|
||||
});
|
||||
//JQUERY("#ge-overviews-container").accordion();
|
||||
var draggables = document.getElementsByClassName("ge-draggable");
|
||||
for (var i = 0; i < draggables.length; i++) {
|
||||
var draggable = draggables[i];
|
||||
var id = "#" + draggable.id; var hid = id + "-handle";
|
||||
// When a window is dragged, its height is set. this is bad. unset height / maybe width after dragging
|
||||
JQUERY(id).draggable({
|
||||
handle: hid, stack: ".ge-draggable", containment: "parent", scroll: false, stop: function (event) {
|
||||
this.style.height = "unset";
|
||||
var did = event.target.id;
|
||||
// only unset width for inspector and options menu, whose widths are dependent on contents
|
||||
if (did === 'ge-inspector-window' || did === 'optionsWindow') {
|
||||
this.style.width = "unset";
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}); // end jQuery
|
||||
|
||||
// add options window hotkey (other hotkeys are defined in goeditor-setup.js)
|
||||
document.body.addEventListener("keydown", function (e) {
|
||||
var keynum = e.which;
|
||||
if (e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
switch (keynum) {
|
||||
case 66: geHideShowWindow('optionsWindow'); break; // ctrl + b
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// function to tweal inspector for app-specific stuff is in floorplanner-datainspector-overrides.js
|
||||
tweakInspectorForFloorplanner(inspector);
|
||||
|
||||
var defaultModelTextarea = document.getElementById('defaultModelTextarea');
|
||||
var defaultModelString = defaultModelTextarea.value;
|
||||
var defaultModelJson = JSON.parse(defaultModelString);
|
||||
myFloorplan.model = go.Model.fromJson(defaultModelJson);
|
||||
|
||||
} // end init
|
||||
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<body onload="init();">
|
||||
<div>
|
||||
|
||||
<nav>
|
||||
|
||||
<span id="currentStorageSpan"></span>
|
||||
<ul id="ge-filemenus">
|
||||
<li>
|
||||
<a href="#">File</a>
|
||||
<ul>
|
||||
<li><a href="#" onclick="handlePromise('New')">New <p class="ge-shortcut">(Ctrl + D)</p></a></li>
|
||||
<li><a href="#" onclick="handlePromise('Load')">Open... <p class="ge-shortcut">(Ctrl + O)</p></a></li>
|
||||
<li><a href="#" onclick="handlePromise('Save')">Save <p class="ge-shortcut">(Ctrl + S)</p></a></li>
|
||||
<li><a href="#" onclick="handlePromise('SaveAs')">Save As...</a></li>
|
||||
<li><a href="#" onclick="handlePromise('Delete')">Remove... <p class="ge-shortcut">(Ctrl + R)</p></a></li>
|
||||
<li><a href="#" onclick="makeDiagramImage()">Export PNG</a></li>
|
||||
<li><a href="#" onclick="makeDiagramSvg()">Export SVG</a></li>
|
||||
<li><a href="#" onclick="updateCurrentStorageSpan()">Change Storage Service</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">View</a>
|
||||
<ul>
|
||||
<li id="ge-viewoption-palettes"><a href="#" onclick="geHideShowWindow('ge-palettes-window', true)" id="ge-palettes-windows-button">Palettes
|
||||
<p class="ge-shortcut"> (Ctrl + P)</p></a></li>
|
||||
<li id="ge-viewoption-overviews"><a href="#" onclick="geHideShowWindow('ge-overviews-window', true)" id="ge-overview-windows-button">Overview
|
||||
<p class="ge-shortcut"> (Ctrl + E)</p></a></li>
|
||||
<li id="ge-viewoption-inspector"><a href="#" onclick="geHideShowWindow('ge-inspector-window', true)" id="ge-inspector-windows-button">Inspector
|
||||
<p class="ge-shortcut"> (Ctrl + I)</p></a></li>
|
||||
<li id="ge-viewoption-options"><a href="#" onclick="geHideShowWindow('optionsWindow', true)" id="optionsWindow-button">Options<p
|
||||
class="ge-shortcut">(Ctrl + B)</p></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div id="toolButtonsDiv" style="float: left;">
|
||||
<button class="toolButtons" onclick="updateButtons(myFloorplan.enableWallBuilding, this)">Build Walls</button>
|
||||
<button class="toolButtons" onclick="updateButtons(myFloorplan.enableDividerBuilding, this)">Build Room
|
||||
Dividers</button>
|
||||
<button class="toolButtons" onclick="updateButtons(myFloorplan.disableWallBuilding, this)">Select</button>
|
||||
</div>
|
||||
|
||||
<p id="isAutoSavingP"><input type="checkbox" id="isAutoSavingCheckbox" unchecked /> <label for="isAutoSavingCheckbox">Autosave
|
||||
Enabled</label></p>
|
||||
|
||||
<p id="ge-header">GoFloorPlanner</p>
|
||||
<div id="ge-filename">(Unsaved file)</div>
|
||||
</nav>
|
||||
|
||||
<input type="file" id="file-input" style="display: none;" />
|
||||
|
||||
<div id="ge-diagrams-container" style="display: flex;">
|
||||
<div id="ge-diagram-0" style="height: 800px; width: 100%; background: #DAE4E4; border: 1px solid black; "></div>
|
||||
</div>
|
||||
|
||||
<div id="ge-palettes-window" style="visibility: visible" class="ge-draggable ui-draggable">
|
||||
<div id="ge-palettes-window-handle" class="ge-handle ui-draggable-handle">Palettes<button id="ge-palettes-window-close"
|
||||
class="ge-window-button ge-clickable" onclick="geHideShowWindow('ge-palettes-window')">X</button></div>
|
||||
<div id="ge-palettes-container">
|
||||
<h3>Furniture</h3>
|
||||
<div>
|
||||
<div id="ge-palette-0" style="height: 500px; background: lightgray; border: 1px solid black; "></div>
|
||||
</div>
|
||||
<h3>Wall Parts</h3>
|
||||
<div>
|
||||
<div id="ge-palette-1" style="height: 500px; background: lightgray; border: 1px solid black; "></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="ge-overviews-window" style="visibility: visible" class="ge-draggable ui-draggable">
|
||||
<div id="ge-overviews-window-handle" class="ge-handle ui-draggable-handle">Overview<button id="ge-overviews-window-close"
|
||||
class="ge-window-button ge-clickable" onclick="geHideShowWindow('ge-overviews-window')">X</button></div>
|
||||
<!--<div id="ge-overviews-container">
|
||||
<h3> Overview</h3><div> -->
|
||||
<div id="ge-overview-0" style="height: 200px; background: white; border: 1px solid black; "></div>
|
||||
</div>
|
||||
<!--</div> -->
|
||||
</div>
|
||||
|
||||
<div id="ge-inspector-window" style="visibility: visible" class="ge-draggable ui-draggable">
|
||||
<div id="ge-inspector-window-handle" class="ge-handle ui-draggable-handle">Properties<button id="ge-inspector-window-close"
|
||||
class="ge-window-button ge-clickable" onclick="geHideShowWindow('ge-inspector-window')">X</button></div>
|
||||
<div id="ge-inspector" class="inspector"></div>
|
||||
</div>
|
||||
|
||||
<div id="optionsWindow" style="visibility: hidden;" class="ge-draggable ui-draggable">
|
||||
<div id="optionsWindow-handle" class="ge-handle ui-draggable-handle">Options <button id="optionsWindowClose" class="windowButtons ge-window-button ge-clickable"
|
||||
onclick="geHideShowWindow('optionsWindow')">X</button></div>
|
||||
Units
|
||||
<div id="unitsRow" class="row data">
|
||||
<form id="unitsForm" onchange="myFloorplan.changeUnits(this)">
|
||||
<div class="col-4">
|
||||
<input type="radio" name="units" id="centimeters" />cm
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<input type="radio" name="units" id="meters" checked /> m
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<input type="radio" name="units" id="inches" />in
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<input type="radio" name="units" id="feet" />ft
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
Grid
|
||||
<div id="gridRow" class="row">
|
||||
<div class="col-2">
|
||||
<label for="gridSizeInput" style="float: left;">Grid size</label>
|
||||
<input id="gridSizeInput" placeholder="" class="unitsInput" onchange="myFloorplan.changeGridSize(this)" value="20" />
|
||||
<input id="gridSizeUnitsInput" class="unitsBox" value="cm" disabled />
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<input type="checkbox" id="showGridCheckbox" onchange="myFloorplan.checkboxChanged('showGridCheckbox')" checked />Show
|
||||
Grid
|
||||
</div>
|
||||
</div>
|
||||
<div id="gridRow" class="row">
|
||||
<div class="col-1">
|
||||
<label for="unitsConversionFactorInput" style="float: left;">Units/1px (at scale 100%)</label>
|
||||
<input id="unitsConversionFactorInput" placeholder="" onchange="myFloorplan.changeUnitsConversionFactor(this, document.getElementById('gridSizeInput'))"
|
||||
class="unitsInput" value=".02" />
|
||||
<input id="" class="unitsBox" value="cm" disabled />
|
||||
</div>
|
||||
</div>
|
||||
Preferences
|
||||
<div id="miscRow" class="row data">
|
||||
<div class="col-1">
|
||||
<input type="checkbox" id="gridSnapCheckbox" onchange="myFloorplan.checkboxChanged('gridSnapCheckbox')" checked />Grid
|
||||
Snap
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<input type="checkbox" id="wallGuidelinesCheckbox" onchange="myFloorplan.checkboxChanged('wallGuidelinesCheckbox')"
|
||||
checked /> Show Wall Guidelines
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<input type="checkbox" id="wallLengthsCheckbox" onchange="myFloorplan.checkboxChanged('wallLengthsCheckbox')"
|
||||
checked /> Show Wall Lengths
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<input type="checkbox" id="wallAnglesCheckbox" onchange="myFloorplan.checkboxChanged('wallAnglesCheckbox')"
|
||||
checked /> Show Wall Angles
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<input type="checkbox" id="onlySmallWallAnglesCheckbox" onchange="myFloorplan.checkboxChanged('onlySmallWallAnglesCheckbox')"
|
||||
checked /> Show Only Non-Reflex Wall Angles
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="ge-footer">
|
||||
<span>Built with the <a href="https://gojs.net">GoJS Diagramming Library</a>, by <a href="https://nwoods.com">
|
||||
Northwoods Software</a>.</span>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
This Floorplanner extension of GoJS makes use of multiple classes to allow for users to build, edit, save, and load
|
||||
feature-rich Floorplans.
|
||||
To start, build walls (press the 'Build Walls') button to activate the WallBuildingTool, or drag furniture from
|
||||
Palettes onto the Floorplan area.
|
||||
When you have an area enclosed by walls, you can create a room there by dragging the floor area node from the Wall
|
||||
Parts palette, or by right-clicking
|
||||
within your enclosed area and clicking 'Make Room'.
|
||||
</p>
|
||||
<p>
|
||||
You may also define areas with dividers, which allow one to specify different floor types without the need for wall
|
||||
boundaries. This is useful if you want to have multiple
|
||||
types of flooring within a single room, or want to define an area of floor that has no walls.
|
||||
</p>
|
||||
<p>
|
||||
This extension uses the following Floorplanner-specific files:
|
||||
<ul>
|
||||
<li><a href="./src/Floorplan.ts">Floorplan.ts</a> - A special kind of <a href="../../api/symbols/Diagram.html">Diagram</a>
|
||||
with listeners, properties, and methods that help with floorplanning </li>
|
||||
<li><a href="./src/FloorplanPalette.ts">FloorplanPalette.ts</a> - A special kind of <a href="../../api/symbols/Palette.html">Palette</a>
|
||||
linked with a specific instance of Floorplan </li>
|
||||
<li><a href="./src/WallBuildingTool.ts">WallBuildingTool.ts</a> - For constructing new walls / dividers. This
|
||||
works in conjunction with WallReshapingTool </li>
|
||||
<li><a href="./src/WallReshapingTool.ts">WallReshapingTool.ts</a> - For reshaping walls / dividers from their
|
||||
endpoints </li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
Additionally, this sample makes use of the <a href="../../intro/storage.html">GoCloudStorage</a> library, which
|
||||
allows users to save / load diagram files to / from LocalStorage, Google Drive, Microsoft OneDrive, and Dropbox.
|
||||
</p>
|
||||
<p>
|
||||
To modify this extension, one must modify the .ts source files (in this project's 'src' directory'), then run <code>npm run build</code>
|
||||
from the project's main directory. This will output a new
|
||||
<code>gfp.js</code> bundle that can be used.
|
||||
</p>
|
||||
|
||||
<textarea style='visibility: hidden;' id='defaultModelTextarea'>
|
||||
|
||||
{ "class": "GraphLinksModel",
|
||||
"copiesKey": false,
|
||||
"modelData": {"units":"meters", "unitsAbbreviation":"m", "unitsConversionFactor":0.02, "gridSize":10, "wallThickness":10, "preferences":{"showWallGuidelines":true, "showWallLengths":true, "showWallAngles":true, "showOnlySmallWallAngles":true, "showGrid":true, "gridSnap":true}},
|
||||
"nodeDataArray": [
|
||||
{"key":"wall6", "category":"WallGroup", "caption":"Wall", "type":"Wall", "color":"lightgray", "startpoint":{"class":"go.Point", "x":-910, "y":-320}, "endpoint":{"class":"go.Point", "x":-773, "y":-320}, "smpt1":{"class":"go.Point", "x":-905, "y":-315}, "smpt2":{"class":"go.Point", "x":-915, "y":-325}, "empt1":{"class":"go.Point", "x":-778, "y":-315}, "empt2":{"class":"go.Point", "x":-773, "y":-325}, "thickness":10, "isGroup":true, "notes":"", "isDivider":false},
|
||||
{"key":"wall5", "category":"WallGroup", "caption":"Wall", "type":"Wall", "startpoint":{"class":"go.Point", "x":-490, "y":-97.6667}, "endpoint":{"class":"go.Point", "x":-490, "y":-320}, "smpt1":{"class":"go.Point", "x":-485, "y":-97.6667}, "smpt2":{"class":"go.Point", "x":-495, "y":-102.6667}, "empt1":{"class":"go.Point", "x":-485, "y":-315}, "empt2":{"class":"go.Point", "x":-495, "y":-315}, "thickness":10, "color":"lightgray", "isGroup":true, "notes":"", "isDivider":false},
|
||||
{"key":"wall11", "category":"WallGroup", "caption":"Wall", "type":"Wall", "color":"lightgray", "startpoint":{"class":"go.Point", "x":-490, "y":-320}, "endpoint":{"class":"go.Point", "x":-120, "y":-320}, "smpt1":{"class":"go.Point", "x":-485, "y":-315}, "smpt2":{"class":"go.Point", "x":-490, "y":-325}, "empt1":{"class":"go.Point", "x":-125, "y":-315}, "empt2":{"class":"go.Point", "x":-124.99646446609407, "y":-325}, "thickness":10, "isGroup":true, "notes":"", "isDivider":false},
|
||||
{"key":"wall12", "category":"WallGroup", "caption":"Wall", "type":"Wall", "color":"lightgray", "startpoint":{"class":"go.Point", "x":-773, "y":-178.66666793823242}, "endpoint":{"class":"go.Point", "x":-631, "y":-178.66666793823242}, "smpt1":{"class":"go.Point", "x":-768, "y":-173.66666793823242}, "smpt2":{"class":"go.Point", "x":-768, "y":-183.66666793823242}, "empt1":{"class":"go.Point", "x":-636, "y":-173.66666793823242}, "empt2":{"class":"go.Point", "x":-636, "y":-183.66666793823242}, "thickness":10, "isGroup":true, "notes":"", "isDivider":false},
|
||||
{"key":"wall13", "category":"WallGroup", "caption":"Wall", "type":"Wall", "color":"lightgray", "startpoint":{"class":"go.Point", "x":-631, "y":-97.6667}, "endpoint":{"class":"go.Point", "x":-490, "y":-97.6667}, "smpt1":{"class":"go.Point", "x":-636, "y":-92.6667}, "smpt2":{"class":"go.Point", "x":-626, "y":-102.6667}, "empt1":{"class":"go.Point", "x":-490.0025, "y":-92.6667}, "empt2":{"class":"go.Point", "x":-495, "y":-102.6667}, "thickness":10, "isGroup":true, "notes":"", "isDivider":false},
|
||||
{"key":"wall2", "category":"WallGroup", "caption":"Wall", "type":"Wall", "color":"lightgray", "startpoint":{"class":"go.Point", "x":-773, "y":-320}, "endpoint":{"class":"go.Point", "x":-631, "y":-320}, "smpt1":{"class":"go.Point", "x":-768, "y":-315}, "smpt2":{"class":"go.Point", "x":-773, "y":-325}, "empt1":{"class":"go.Point", "x":-636, "y":-315}, "empt2":{"class":"go.Point", "x":-631, "y":-325}, "thickness":10, "isGroup":true, "notes":"", "isDivider":false},
|
||||
{"key":"wall14", "category":"WallGroup", "caption":"Wall", "type":"Wall", "color":"lightgray", "startpoint":{"class":"go.Point", "x":-631, "y":-320}, "endpoint":{"class":"go.Point", "x":-490, "y":-320}, "smpt1":{"class":"go.Point", "x":-626, "y":-315}, "smpt2":{"class":"go.Point", "x":-631, "y":-325}, "empt1":{"class":"go.Point", "x":-495, "y":-315}, "empt2":{"class":"go.Point", "x":-490, "y":-325}, "thickness":10, "isGroup":true, "notes":"", "isDivider":false},
|
||||
{"key":"wall10", "category":"WallGroup", "caption":"Wall", "type":"Wall", "color":"lightgray", "startpoint":{"class":"go.Point", "x":-631, "y":-320}, "endpoint":{"class":"go.Point", "x":-631, "y":-178.66666793823242}, "smpt1":{"class":"go.Point", "x":-636, "y":-315}, "smpt2":{"class":"go.Point", "x":-626, "y":-315}, "empt1":{"class":"go.Point", "x":-636, "y":-183.66666793823242}, "empt2":{"class":"go.Point", "x":-626, "y":-178.66666793823242}, "thickness":10, "isGroup":true, "notes":"", "isDivider":false},
|
||||
{"key":"wall15", "category":"WallGroup", "caption":"Wall", "type":"Wall", "color":"lightgray", "startpoint":{"class":"go.Point", "x":-631, "y":-178.66666793823242}, "endpoint":{"class":"go.Point", "x":-631, "y":-97.6667}, "smpt1":{"class":"go.Point", "x":-636, "y":-173.66666793823242}, "smpt2":{"class":"go.Point", "x":-626, "y":-178.66666793823242}, "empt1":{"class":"go.Point", "x":-636, "y":-92.6667}, "empt2":{"class":"go.Point", "x":-626, "y":-102.6667}, "thickness":10, "isGroup":true, "notes":"", "isDivider":false},
|
||||
{"key":"wall8", "category":"WallGroup", "caption":"Wall", "type":"Wall", "color":"lightgray", "startpoint":{"class":"go.Point", "x":-773, "y":-10}, "endpoint":{"class":"go.Point", "x":-773, "y":-178.66666793823242}, "smpt1":{"class":"go.Point", "x":-768, "y":-10}, "smpt2":{"class":"go.Point", "x":-778, "y":-10}, "empt1":{"class":"go.Point", "x":-768, "y":-173.66666793823242}, "empt2":{"class":"go.Point", "x":-778, "y":-178.66666793823242}, "thickness":10, "isGroup":true, "notes":""},
|
||||
{"key":"wall9", "category":"WallGroup", "caption":"Wall", "type":"Wall", "color":"lightgray", "startpoint":{"class":"go.Point", "x":-773, "y":-178.66666793823242}, "endpoint":{"class":"go.Point", "x":-773, "y":-320}, "smpt1":{"class":"go.Point", "x":-768, "y":-183.66666793823242}, "smpt2":{"class":"go.Point", "x":-778, "y":-178.66666793823242}, "empt1":{"class":"go.Point", "x":-768, "y":-315}, "empt2":{"class":"go.Point", "x":-778, "y":-315}, "thickness":10, "isGroup":true, "notes":""},
|
||||
{"key":"wall16", "category":"WallGroup", "caption":"Wall", "type":"Wall", "color":"lightgray", "startpoint":{"class":"go.Point", "x":-120, "y":200}, "endpoint":{"class":"go.Point", "x":-670.32, "y":200}, "smpt1":{"class":"go.Point", "x":-125, "y":195}, "smpt2":{"class":"go.Point", "x":-124.99646446609407, "y":205}, "empt1":{"class":"go.Point", "x":-665.32, "y":195}, "empt2":{"class":"go.Point", "x":-670.32, "y":205}, "thickness":10, "isGroup":true, "notes":"", "isDivider":false},
|
||||
{"key":"wall18", "category":"WallGroup", "caption":"Wall", "type":"Wall", "color":"lightgray", "startpoint":{"class":"go.Point", "x":-120, "y":-320}, "endpoint":{"class":"go.Point", "x":-120, "y":10}, "smpt1":{"class":"go.Point", "x":-125, "y":-315}, "smpt2":{"class":"go.Point", "x":-115, "y":-314.99646446609404}, "empt1":{"class":"go.Point", "x":-125, "y":0}, "empt2":{"class":"go.Point", "x":-115, "y":10}, "thickness":10, "isGroup":true, "notes":"", "isDivider":false},
|
||||
{"key":"wall19", "category":"WallGroup", "caption":"Wall", "type":"Wall", "color":"lightgray", "startpoint":{"class":"go.Point", "x":-120, "y":10}, "endpoint":{"class":"go.Point", "x":-120, "y":200}, "smpt1":{"class":"go.Point", "x":-125, "y":20}, "smpt2":{"class":"go.Point", "x":-115, "y":10}, "empt1":{"class":"go.Point", "x":-125, "y":195}, "empt2":{"class":"go.Point", "x":-115, "y":194.99646446609407}, "thickness":10, "isGroup":true, "notes":"", "isDivider":false},
|
||||
{"key":"wall4", "category":"WallGroup", "caption":"Divider", "type":"Divider", "startpoint":{"class":"go.Point", "x":-120, "y":-320}, "endpoint":{"class":"go.Point", "x":100, "y":-100}, "smpt1":{"class":"go.Point", "x":-115, "y":-314.99646446609404}, "smpt2":{"class":"go.Point", "x":-124.99646446609407, "y":-325}, "empt1":{"class":"go.Point", "x":99.9975, "y":-99.99896446609407}, "empt2":{"class":"go.Point", "x":100.0025, "y":-100.00103553390593}, "thickness":0.005, "color":"lightgray", "isGroup":true, "notes":"", "isDivider":true},
|
||||
{"key":"wall25", "category":"WallGroup", "caption":"Divider", "type":"Divider", "startpoint":{"class":"go.Point", "x":-120, "y":200}, "endpoint":{"class":"go.Point", "x":100, "y":-20}, "smpt1":{"class":"go.Point", "x":-124.99646446609407, "y":205}, "smpt2":{"class":"go.Point", "x":-115, "y":194.99646446609407}, "empt1":{"class":"go.Point", "x":100.0025, "y":-19.99896446609407}, "empt2":{"class":"go.Point", "x":99.9975, "y":-20.00103553390593}, "thickness":0.005, "color":"lightgray", "isGroup":true, "notes":"", "isDivider":true},
|
||||
{"key":"wall22", "category":"WallGroup", "caption":"Divider", "type":"Divider", "startpoint":{"class":"go.Point", "x":-490, "y":-97.6667}, "endpoint":{"class":"go.Point", "x":-490, "y":10}, "smpt1":{"class":"go.Point", "x":-490.0025, "y":-92.6667}, "smpt2":{"class":"go.Point", "x":-489.9975, "y":-97.6667}, "empt1":{"class":"go.Point", "x":-490.0025, "y":0}, "empt2":{"class":"go.Point", "x":-489.9975, "y":0}, "thickness":0.005, "color":"lightgray", "isGroup":true, "notes":"", "isDivider":true},
|
||||
{"key":"wall23", "category":"WallGroup", "caption":"Wall", "type":"Wall", "color":"#d3d3d3", "startpoint":{"class":"go.Point", "x":-670.32, "y":10}, "endpoint":{"class":"go.Point", "x":-490, "y":10}, "smpt1":{"class":"go.Point", "x":-665.32, "y":20}, "smpt2":{"class":"go.Point", "x":-675.32, "y":0}, "empt1":{"class":"go.Point", "x":-490, "y":20}, "empt2":{"class":"go.Point", "x":-490.0025, "y":0}, "thickness":20, "isGroup":true, "notes":""},
|
||||
{"key":"wall26", "category":"WallGroup", "caption":"Wall", "type":"Wall", "color":"#d3d3d3", "startpoint":{"class":"go.Point", "x":-490, "y":10}, "endpoint":{"class":"go.Point", "x":-120, "y":10}, "smpt1":{"class":"go.Point", "x":-490, "y":20}, "smpt2":{"class":"go.Point", "x":-489.9975, "y":0}, "empt1":{"class":"go.Point", "x":-125, "y":20}, "empt2":{"class":"go.Point", "x":-125, "y":0}, "thickness":20, "isGroup":true, "notes":""},
|
||||
{"key":"Room", "category":"RoomNode", "name":"Patio", "boundaryWalls":[ [ "wall4",1 ],[ "wall18",2 ],[ "wall19",2 ],[ "wall25",2 ],[ "wall29",1 ] ], "holes":[], "floorImage":"./images/textures/floor6.jpg", "showLabel":true, "showFlooringOptions":true, "loc":{"class":"go.Point", "x":-115, "y":-314.99646446609404}, "area":63423.27973184812, "labelAlignment":{"class":"go.Spot", "x":0.5, "y":0.5, "offsetX":-5.103470996432179, "offsetY":-64.0326718168821}},
|
||||
{"key":"Room2", "category":"RoomNode", "name":"Living Room", "boundaryWalls":[ [ "wall23",1 ],[ "wall21",1 ],[ "wall20",1 ],[ "wall16",1 ],[ "wall19",1 ],[ "wall26",1 ] ], "holes":[], "floorImage":"./images/textures/floor7.jpg", "showLabel":true, "showFlooringOptions":true, "loc":{"class":"go.Point", "x":-665.32, "y":20}, "area":94556, "labelAlignment":{"class":"go.Spot", "x":0.5, "y":0.5, "offsetX":-181.9269405923115, "offsetY":49.07812025216799}},
|
||||
{"key":"Room3", "category":"RoomNode", "name":"Hallway", "boundaryWalls":[ [ "wall12",1 ],[ "wall8",1 ],[ "wall8",2 ],[ "wall9",2 ],[ "wall6",1 ],[ "wall7",2 ],[ "wall17",2 ],[ "wall21",2 ],[ "wall23",2 ],[ "wall22",1 ],[ "wall13",1 ],[ "wall15",1 ] ], "holes":[], "floorImage":"images/textures/floor1.jpg", "showLabel":true, "showFlooringOptions":true, "loc":{"class":"go.Point", "x":-905, "y":-315}, "area":91348.35030109668, "labelAlignment":{"class":"go.Spot", "x":0.5, "y":0.5, "offsetX":52.650999571264606, "offsetY":67.99831835696534}},
|
||||
{"key":"Room4", "category":"RoomNode", "name":"Kitchen / Dining Room", "boundaryWalls":[ [ "wall11",1 ],[ "wall5",1 ],[ "wall22",2 ],[ "wall26",2 ],[ "wall18",1 ] ], "holes":[], "floorImage":"./images/textures/floor4.jpg", "showLabel":true, "showFlooringOptions":true, "loc":{"class":"go.Point", "x":-489.9975, "y":-315}, "area":113888.08933324998, "labelAlignment":{"class":"go.Spot", "x":0.5, "y":0.5, "offsetX":-80.49751952755344, "offsetY":29.97379349825087}},
|
||||
{"key":"Room5", "category":"RoomNode", "name":"Bathroom", "boundaryWalls":[ [ "wall14",1 ],[ "wall10",2 ],[ "wall15",2 ],[ "wall13",2 ],[ "wall5",2 ] ], "holes":[], "floorImage":"./images/textures/floor5.jpg", "showLabel":true, "showFlooringOptions":true, "loc":{"class":"go.Point", "x":-626, "y":-315}, "area":27815.6623},
|
||||
{"key":"Room6", "category":"RoomNode", "name":"Laundry Room", "boundaryWalls":[ [ "wall2",1 ],[ "wall9",1 ],[ "wall12",2 ],[ "wall10",1 ] ], "holes":[], "floorImage":"./images/textures/floor5.jpg", "showLabel":true, "showFlooringOptions":true, "loc":{"class":"go.Point", "x":-768, "y":-315}, "area":17335.99983215332, "labelAlignment":{"class":"go.Spot", "x":0.5, "y":0.5, "offsetX":19.00442716769237, "offsetY":27.642803153007236}},
|
||||
{"key":"door", "category":"DoorNode", "caption":"Door", "type":"Door", "length":40, "doorOpeningHeight":10, "swing":"left", "notes":"", "loc":"-876.8531037797546 -320", "group":"wall6", "angle":180},
|
||||
{"key":"door2", "category":"DoorNode", "caption":"Door", "type":"Door", "length":40, "doorOpeningHeight":10, "swing":"left", "notes":"", "loc":"-697.8658953341261 -178.66666793823242", "group":"wall12"},
|
||||
{"key":"door3", "category":"DoorNode", "caption":"Door", "type":"Door", "length":40, "doorOpeningHeight":10, "swing":"right", "notes":"", "loc":"-631 -248.59768600237845", "group":"wall10", "angle":270},
|
||||
{"key":"door4", "category":"DoorNode", "caption":"Door", "type":"Door", "length":40, "doorOpeningHeight":10, "swing":"left", "notes":"", "loc":"-557.5153168347209 -97.6667", "group":"wall13"},
|
||||
{"key":"door5", "category":"DoorNode", "caption":"Door", "type":"Door", "length":40, "doorOpeningHeight":10, "swing":"left", "notes":"", "loc":"-120 -50.50422651153826", "group":"wall18", "angle":90},
|
||||
{"key":"door6", "category":"DoorNode", "caption":"Door", "type":"Door", "length":50, "doorOpeningHeight":20, "swing":"right", "notes":"", "loc":"-236.61674968371233 10", "group":"wall26", "angle":180, "color":"#000000"},
|
||||
{"category":"WindowNode", "key":"window", "color":"white", "caption":"Window", "type":"Window", "shape":"Rectangle", "height":10, "length":60, "notes":"", "loc":"-705 -320", "group":"wall2"},
|
||||
{"category":"WindowNode", "key":"window2", "color":"white", "caption":"Window", "type":"Window", "shape":"Rectangle", "height":10, "length":60, "notes":"", "loc":"-559 -320", "group":"wall14"},
|
||||
{"key":"door7", "category":"DoorNode", "caption":"Door", "type":"Door", "length":50, "doorOpeningHeight":20, "swing":"left", "notes":"", "loc":"-596 10", "group":"wall23", "angle":180, "color":"#000000"},
|
||||
{"key":"wall3", "category":"WallGroup", "caption":"Wall", "type":"Wall", "color":"lightgray", "startpoint":{"class":"go.Point", "x":-670.32, "y":200}, "endpoint":{"class":"go.Point", "x":-910, "y":200}, "smpt1":{"class":"go.Point", "x":-675.32, "y":195}, "smpt2":{"class":"go.Point", "x":-670.32, "y":205}, "empt1":{"class":"go.Point", "x":-905, "y":195}, "empt2":{"class":"go.Point", "x":-915, "y":205}, "thickness":10, "isGroup":true, "notes":""},
|
||||
{"key":"wall17", "category":"WallGroup", "caption":"Wall", "type":"Wall", "startpoint":{"class":"go.Point", "x":-910, "y":69.39500000000001}, "endpoint":{"class":"go.Point", "x":-670.32, "y":69.39500000000001}, "smpt1":{"class":"go.Point", "x":-905, "y":74.39500000000001}, "smpt2":{"class":"go.Point", "x":-905, "y":64.39500000000001}, "empt1":{"class":"go.Point", "x":-675.32, "y":74.39500000000001}, "empt2":{"class":"go.Point", "x":-675.32, "y":64.39500000000001}, "thickness":10, "color":"lightgray", "isGroup":true, "notes":"", "isDivider":false},
|
||||
{"key":"wall20", "category":"WallGroup", "caption":"Wall", "type":"Wall", "color":"lightgray", "startpoint":{"class":"go.Point", "x":-670.32, "y":200}, "endpoint":{"class":"go.Point", "x":-670.32, "y":69.39500000000001}, "smpt1":{"class":"go.Point", "x":-665.32, "y":195}, "smpt2":{"class":"go.Point", "x":-675.32, "y":195}, "empt1":{"class":"go.Point", "x":-665.32, "y":69.39500000000001}, "empt2":{"class":"go.Point", "x":-675.32, "y":74.39500000000001}, "thickness":10, "isGroup":true, "notes":"", "isDivider":false},
|
||||
{"key":"wall21", "category":"WallGroup", "caption":"Wall", "type":"Wall", "color":"lightgray", "startpoint":{"class":"go.Point", "x":-670.32, "y":69.39500000000001}, "endpoint":{"class":"go.Point", "x":-670.32, "y":10}, "smpt1":{"class":"go.Point", "x":-665.32, "y":69.39500000000001}, "smpt2":{"class":"go.Point", "x":-675.32, "y":64.39500000000001}, "empt1":{"class":"go.Point", "x":-665.32, "y":20}, "empt2":{"class":"go.Point", "x":-675.32, "y":0}, "thickness":10, "isGroup":true, "notes":"", "isDivider":false},
|
||||
{"key":"wall7", "category":"WallGroup", "caption":"Wall", "type":"Wall", "color":"lightgray", "startpoint":{"class":"go.Point", "x":-910, "y":-320}, "endpoint":{"class":"go.Point", "x":-910, "y":69.39500000000001}, "smpt1":{"class":"go.Point", "x":-915, "y":-325}, "smpt2":{"class":"go.Point", "x":-905, "y":-315}, "empt1":{"class":"go.Point", "x":-915, "y":69.39500000000001}, "empt2":{"class":"go.Point", "x":-905, "y":64.39500000000001}, "thickness":10, "isGroup":true, "notes":"", "isDivider":false},
|
||||
{"key":"wall27", "category":"WallGroup", "caption":"Wall", "type":"Wall", "color":"lightgray", "startpoint":{"class":"go.Point", "x":-910, "y":69.39500000000001}, "endpoint":{"class":"go.Point", "x":-910, "y":200}, "smpt1":{"class":"go.Point", "x":-915, "y":69.39500000000001}, "smpt2":{"class":"go.Point", "x":-905, "y":74.39500000000001}, "empt1":{"class":"go.Point", "x":-915, "y":205}, "empt2":{"class":"go.Point", "x":-905, "y":195}, "thickness":10, "isGroup":true, "notes":"", "isDivider":false},
|
||||
{"key":"Room7", "category":"RoomNode", "name":"Spare Bedroom", "boundaryWalls":[ [ "wall17",1 ],[ "wall27",2 ],[ "wall3",1 ],[ "wall20",2 ] ], "holes":[], "floorImage":"./images/textures/floor2.jpg", "showLabel":true, "showFlooringOptions":true, "loc":{"class":"go.Point", "x":-905, "y":74.39500000000001}, "area":27700.556399999972, "labelAlignment":{"class":"go.Spot", "x":0.5, "y":0.5, "offsetX":-31.098153547132597, "offsetY":29.370478350070243}},
|
||||
{"key":"sofaMedium", "color":"#ffffff", "stroke":"#000000", "caption":"Sofa", "type":"Sofa", "geo":"F1 M0 0 L80 0 80 40 0 40 0 0 M10 35 L10 10 M0 0 Q8 0 10 10 M0 40 Q40 15 80 40 M70 10 Q72 0 80 0 M70 10 L70 35", "height":45, "width":90, "notes":"", "texture":"./images/textures/fabric3.jpg", "usesTexture":true, "showTextureOptions":true, "textures":[ "fabric1.jpg","fabric2.jpg","fabric3.jpg" ], "loc":"-470 58", "angle":180, "group":-47},
|
||||
{"key":"sofaMedium2", "color":"#ffffff", "stroke":"#000000", "caption":"Sofa", "type":"Sofa", "geo":"F1 M0 0 L80 0 80 40 0 40 0 0 M10 35 L10 10 M0 0 Q8 0 10 10 M0 40 Q40 15 80 40 M70 10 Q72 0 80 0 M70 10 L70 35", "height":45, "width":90, "notes":"", "texture":"./images/textures/fabric3.jpg", "usesTexture":true, "showTextureOptions":true, "textures":[ "fabric1.jpg","fabric2.jpg","fabric3.jpg" ], "loc":"-360 60", "angle":180, "group":-47},
|
||||
{"key":"armChair", "color":"purple", "stroke":"#000000", "caption":"Arm Chair", "type":"Arm Chair", "geo":"F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width":45, "height":45, "notes":"", "texture":"fabric1.jpg", "usesTexture":true, "showTextureOptions":true, "textures":[ "fabric1.jpg","fabric2.jpg","fabric3.jpg" ], "loc":"-600 110", "angle":90},
|
||||
{"key":"armChair2", "color":"purple", "stroke":"#000000", "caption":"Arm Chair", "type":"Arm Chair", "geo":"F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width":45, "height":45, "notes":"", "texture":"fabric1.jpg", "usesTexture":true, "showTextureOptions":true, "textures":[ "fabric1.jpg","fabric2.jpg","fabric3.jpg" ], "loc":"-220 110", "angle":270},
|
||||
{"isGroup":true, "key":-47, "caption":"Group", "notes":""},
|
||||
{"category":"MultiPurposeNode", "showLabel": true, "key":"MultiPurposeNode", "caption":"Multi Purpose Node", "color":"#000000", "stroke":"#000000", "name":"Writable Node", "type":"Writable Node", "shape":"Rectangle", "text":"TV", "width":175, "height":30, "notes":"", "texture":"granite1.jpg", "usesTexture":false, "showTextureOptions":true, "textures":[ "wood1.jpg","wood2.jpg","granite1.jpg","porcelain1.jpg","steel1.jpg" ], "loc":"-400 171.5"},
|
||||
{"category":"MultiPurposeNode", "showLabel": true, "key":"MultiPurposeNode2", "caption":"Multi Purpose Node", "color":"#ffffff", "stroke":"#000000", "name":"Writable Node", "type":"Writable Node", "shape":"Rectangle", "text":"Coffee Table", "width":194, "height":36, "notes":"", "texture":"./images/textures/wood2.jpg", "usesTexture":true, "showTextureOptions":true, "textures":[ "wood1.jpg","wood2.jpg","granite1.jpg","porcelain1.jpg","steel1.jpg" ], "loc":"-410 110"},
|
||||
{"key":"diningTable", "color":"#ffffff", "stroke":"#000000", "caption":"Dining Table", "type":"Dining Table", "geo":"F1 M 0 0 L 0 100 200 100 200 0 0 0 M 25 0 L 25 -10 75 -10 75 0 M 125 0 L 125 -10 175 -10 175 0 M 200 25 L 210 25 210 75 200 75 M 125 100 L 125 110 L 175 110 L 175 100 M 25 100 L 25 110 75 110 75 100 M 0 75 -10 75 -10 25 0 25", "width":150, "height":75, "notes":"", "texture":"./images/textures/floor3.jpg", "usesTexture":true, "showTextureOptions":true, "textures":[ "wood1.jpg","wood2.jpg","floor3.jpg","granite1.jpg","porcelain1.jpg","steel2.jpg" ], "loc":"-270 -70"},
|
||||
{"category":"MultiPurposeNode", "showLabel": true, "key":"MultiPurposeNode3", "caption":"Multi Purpose Node", "color":"#ffffff", "stroke":"#000000", "name":"Writable Node", "type":"Writable Node", "shape":"Rectangle", "text":"Island", "width":150, "height":50, "notes":"", "texture":"granite1.jpg", "usesTexture":true, "showTextureOptions":true, "textures":[ "wood1.jpg","wood2.jpg","granite1.jpg","porcelain1.jpg","steel1.jpg" ], "loc":"-310 -180"},
|
||||
{"key":"stove", "color":"#ffffff", "stroke":"#000000", "caption":"Stove", "type":"Stove", "geo":"F1 M 0 0 L 0 100 100 100 100 0 0 0M 30 15 A 15 15 180 1 0 30.01 15M 30 20 A 10 10 180 1 0 30.01 20M 30 25 A 5 5 180 1 0 30.01 25M 70 15 A 15 15 180 1 0 70.01 15M 70 20 A 10 10 180 1 0 70.01 20M 70 25 A 5 5 180 1 0 70.01 25M 30 55 A 15 15 180 1 0 30.01 55M 30 60 A 10 10 180 1 0 30.01 60M 30 65 A 5 5 180 1 0 30.01 65M 70 55 A 15 15 180 1 0 70.01 55M 70 60 A 10 10 180 1 0 70.01 60M 70 65 A 5 5 180 1 0 70.01 65", "width":50, "height":50, "notes":"", "texture":"plaster1.jpg", "usesTexture":true, "showTextureOptions":true, "textures":[ "steel1.jpg","porcelain1.jpg","copper1.jpg","plaster1.jpg" ], "loc":"-390 -280"},
|
||||
{"key":"toilet", "color":"#ffffff", "stroke":"#000000", "caption":"Toilet", "type":"Toilet", "geo":"F1 M0 0 L25 0 25 10 0 10 0 0 M20 10 L20 15 5 15 5 10 20 10 M5 15 Q0 15 0 25 Q0 40 12.5 40 Q25 40 25 25 Q25 15 20 15", "width":25, "height":35, "notes":"", "texture":"porcelain1.jpg", "usesTexture":true, "showTextureOptions":true, "textures":[ "copper1.jpg","steel1.jpg","porcelain1.jpg" ], "loc":"-590 -270"},
|
||||
{"key":"shower", "color":"#ffffff", "stroke":"#000000", "caption":"Shower/Tub", "type":"Shower/Tub", "geo":"F1 M0 0 L40 0 40 60 0 60 0 0 M35 15 L35 55 5 55 5 15 Q5 5 20 5 Q35 5 35 15 M22.5 20 A2.5 2.5 180 1 1 22.5 19.99", "width":45, "height":75, "notes":"", "texture":"copper1.jpg", "usesTexture":true, "showTextureOptions":true, "textures":[ "copper1.jpg","steel1.jpg","porcelain1.jpg" ], "loc":"-520 -260"},
|
||||
{"key":"doubleSink", "color":"#ffffff", "stroke":"#000000", "caption":"Double Sink", "type":"Double Sink", "geo":"F1 M0 0 L75 0 75 40 0 40 0 0 M5 7.5 L35 7.5 35 35 5 35 5 7.5 M44 7.5 L70 7.5 70 35 40 35 40 9M15 21.25 A5 5 180 1 0 15 21.24 M50 21.25 A 5 5 180 1 0 50 21.24 M40.5 3.75 A3 3 180 1 1 40.5 3.74M40.5 3.75 L50.5 13.75 47.5 16.5 37.5 6.75 M32.5 3.75 A 1 1 180 1 1 32.5 3.74 M 27.5 4.25 L 27.5 3.25 30.5 3.25M 30.5 4.25 L 27.5 4.25 M44.5 3.75 A 1 1 180 1 1 44.5 3.74 M 44.35 3.25 L 47.5 3.25 47.5 4.25 M 44.35 4.25 L 47.5 4.25", "height":27, "width":52, "notes":"", "texture":"steel2.jpg", "usesTexture":true, "showTextureOptions":true, "textures":[ "copper1.jpg","steel1.jpg","steel2.jpg","porcelain1.jpg" ], "loc":"-520 -180", "angle":90},
|
||||
{"key":"sink", "color":"#ffffff", "stroke":"#000000", "caption":"Sink", "type":"Sink", "geo":"F1 M0 0 L40 0 40 40 0 40 0 0z M5 7.5 L18.5 7.5 M 21.5 7.5 L35 7.5 35 35 5 35 5 7.5 M 15 21.25 A 5 5 180 1 0 15 21.24M23 3.75 A 3 3 180 1 1 23 3.74 M21.5 6.25 L 21.5 12.5 18.5 12.5 18.5 6.25 M15 3.75 A 1 1 180 1 1 15 3.74M 10 4.25 L 10 3.25 13 3.25 M 13 4.25 L 10 4.25 M27 3.75 A 1 1 180 1 1 27 3.74 M 26.85 3.25 L 30 3.25 30 4.25 M 26.85 4.25 L 30 4.25", "width":27, "height":27, "notes":"", "texture":"steel1.jpg", "usesTexture":true, "showTextureOptions":true, "textures":[ "copper1.jpg","steel1.jpg","steel2.jpg","porcelain1.jpg" ], "loc":"-750 -240", "angle":270},
|
||||
{"category":"MultiPurposeNode", "showLabel": true, "key":"MultiPurposeNode4", "caption":"Multi Purpose Node", "color":"#ffffff", "stroke":"#000000", "name":"Writable Node", "type":"Writable Node", "shape":"Rectangle", "text":"Washer", "width":50, "height":50, "notes":"", "texture":"./images/textures/porcelain1.jpg", "usesTexture":true, "showTextureOptions":true, "textures":[ "wood1.jpg","wood2.jpg","granite1.jpg","porcelain1.jpg","steel1.jpg" ], "loc":"-740 -290"},
|
||||
{"category":"MultiPurposeNode", "showLabel": true, "key":"MultiPurposeNode42", "caption":"Multi Purpose Node", "color":"#ffffff", "stroke":"#000000", "name":"Writable Node", "type":"Writable Node", "shape":"Rectangle", "text":"Dryer", "width":50, "height":50, "notes":"", "texture":"./images/textures/porcelain1.jpg", "usesTexture":true, "showTextureOptions":false, "textures":[ "wood1.jpg","wood2.jpg","granite1.jpg","porcelain1.jpg","steel1.jpg" ], "loc":"-680 -290"},
|
||||
{"key":"staircase", "color":"#ffffff", "stroke":"#000000", "caption":"Staircase", "type":"Staircase", "geo":"F1 M0 0 L 0 100 250 100 250 0 0 0 M25 100 L 25 0 M 50 100 L 50 0 M 75 100 L 75 0M 100 100 L 100 0 M 125 100 L 125 0 M 150 100 L 150 0 M 175 100 L 175 0 M 200 100 L 200 0 M 225 100 L 225 0", "width":125, "height":50, "notes":"", "texture":"./images/textures/wood1.jpg", "usesTexture":true, "showTextureOptions":true, "textures":[ "wood1.jpg","floor1.jpg","wood2.jpg","steel2.jpg","floor2.jpg" ], "loc":"-810 -240", "angle":270},
|
||||
{"key":"roundTable", "color":"#ffffff", "stroke":"#000000", "caption":"Round Table", "type":"Round Table", "shape":"Ellipse", "width":50, "height":50, "notes":"", "texture":"wood1.jpg", "usesTexture":true, "showTextureOptions":true, "textures":[ "wood1.jpg","wood2.jpg","floor3.jpg","granite1.jpg","porcelain1.jpg" ], "loc":"-710 30"},
|
||||
{"key":"door8", "category":"DoorNode", "caption":"Door", "type":"Door", "length":40, "doorOpeningHeight":10, "swing":"left", "notes":"", "loc":"-818.1830255911889 69.39500000000001", "group":"wall17", "angle":180},
|
||||
{"key":"door9", "category":"DoorNode", "caption":"Door", "type":"Door", "length":40, "doorOpeningHeight":10, "swing":"left", "notes":"", "loc":"-120 146.01328150307734", "group":"wall19", "angle":90},
|
||||
{"category":"WindowNode", "key":"window3", "color":"white", "caption":"Window", "type":"Window", "shape":"Rectangle", "height":10, "length":60, "notes":"", "loc":"-120 79.59151279559444", "group":"wall19", "angle":90},
|
||||
{"category":"WindowNode", "key":"window4", "color":"white", "caption":"Window", "type":"Window", "shape":"Rectangle", "height":10, "length":60, "notes":"", "loc":"-178 -320", "group":"wall11"},
|
||||
{"category":"WindowNode", "key":"window5", "color":"white", "caption":"Window", "type":"Window", "shape":"Rectangle", "height":10, "length":60, "notes":"", "loc":"-358 -320", "group":"wall11"},
|
||||
{"category":"MultiPurposeNode", "showLabel": true, "key":"MultiPurposeNode5", "caption":"Multi Purpose Node", "color":"#ffffff", "stroke":"#000000", "name":"Writable Node", "type":"Writable Node", "shape":"Rectangle", "text":"Fridge", "width":50, "height":50, "notes":"", "texture":"./images/textures/steel1.jpg", "usesTexture":true, "showTextureOptions":true, "textures":[ "wood1.jpg","wood2.jpg","granite1.jpg","porcelain1.jpg","steel1.jpg" ], "loc":"-450 -280"},
|
||||
{"category":"MultiPurposeNode", "showLabel": true, "key":"MultiPurposeNode52", "caption":"Multi Purpose Node", "color":"#ffffff", "stroke":"#000000", "name":"Writable Node", "type":"Writable Node", "shape":"Rectangle", "text":"Counter", "width":219, "height":50, "notes":"", "texture":"./images/textures/wood2.jpg", "usesTexture":true, "showTextureOptions":true, "textures":[ "wood1.jpg","wood2.jpg","granite1.jpg","porcelain1.jpg","steel1.jpg" ], "loc":"-245.5 -280"},
|
||||
{"key":"bed", "color":"#ffffff", "stroke":"#000000", "caption":"Bed", "type":"Bed", "geo":"F1 M0 0 L40 0 40 60 0 60 0 0 M 7.5 2.5 L32.5 2.5 32.5 17.5 7.5 17.5 7.5 2.5 M0 20 L40 20 M0 25 L40 25", "width":76.2, "height":101.6, "notes":"", "texture":"./images/textures/fabric2.jpg", "usesTexture":true, "showTextureOptions":true, "textures":[ "fabric1.jpg","fabric2.jpg","fabric3.jpg" ], "loc":"-720 130"},
|
||||
{"category":"MultiPurposeNode", "key":"MultiPurposeNode6", "caption":"Multi Purpose Node", "color":"#ffffff", "stroke":"#000000", "name":"Writable Node", "type":"Writable Node", "shape":"Rectangle", "text":"Dresser", "width":60, "height":60, "notes":"", "texture":"./images/textures/wood2.jpg", "usesTexture":true, "showTextureOptions":true, "textures":[ "wood1.jpg","wood2.jpg","granite1.jpg","porcelain1.jpg","steel1.jpg" ], "loc":"-870 110"},
|
||||
{"category":"WindowNode", "key":"window6", "color":"white", "caption":"Window", "type":"Window", "shape":"Rectangle", "height":10, "length":60, "notes":"", "loc":"-910 141.13616240146848", "group":"wall27", "angle":90},
|
||||
{"category":"WindowNode", "key":"window7", "color":"white", "caption":"Window", "type":"Window", "shape":"Rectangle", "height":10, "length":60, "notes":"", "loc":"-815.2749163157326 -320", "group":"wall6"},
|
||||
{"category":"WindowNode", "key":"window8", "color":"white", "caption":"Window", "type":"Window", "shape":"Rectangle", "height":10, "length":60, "notes":"", "loc":"-910 -226", "group":"wall7", "angle":90},
|
||||
{"category":"WindowNode", "key":"window9", "color":"white", "caption":"Window", "type":"Window", "shape":"Rectangle", "height":10, "length":60, "notes":"", "loc":"-910 -72", "group":"wall7", "angle":90},
|
||||
{"key":"door10", "category":"DoorNode", "caption":"Door", "type":"Door", "length":40, "doorOpeningHeight":10, "swing":"right", "notes":"", "loc":"-120 -90.50422651153826", "group":"wall18", "angle":90},
|
||||
{"category":"WindowNode", "key":"window10", "color":"white", "caption":"Window", "type":"Window", "shape":"Rectangle", "height":10, "length":60, "notes":"", "loc":"-120 -239", "group":"wall18", "angle":90},
|
||||
{"category":"MultiPurposeNode", "showLabel": true, "key":"MultiPurposeNode7", "caption":"Multi Purpose Node", "color":"#ffffff", "stroke":"#000000", "name":"Writable Node", "type":"Writable Node", "shape":"Rectangle", "text":"Bookshelf", "width":36, "height":125, "notes":"", "texture":"./images/textures/wood1.jpg", "usesTexture":true, "showTextureOptions":true, "textures":[ "wood1.jpg","wood2.jpg","granite1.jpg","porcelain1.jpg","steel1.jpg" ], "loc":"-880 -30"},
|
||||
{"key":"roundTable2", "color":"#ffffff", "stroke":"#000000", "caption":"Round Table", "type":"Round Table", "shape":"Ellipse", "width":61, "height":61, "notes":"", "texture":"./images/textures/wood1.jpg", "usesTexture":true, "showTextureOptions":true, "textures":[ "wood1.jpg","wood2.jpg","floor3.jpg","granite1.jpg","porcelain1.jpg" ], "loc":"-80 -230"},
|
||||
{"key":"roundTable22", "color":"#ffffff", "stroke":"#000000", "caption":"Round Table", "type":"Round Table", "shape":"Ellipse", "width":61, "height":61, "notes":"", "texture":"./images/textures/wood1.jpg", "usesTexture":true, "showTextureOptions":true, "textures":[ "wood1.jpg","wood2.jpg","floor3.jpg","granite1.jpg","porcelain1.jpg" ], "loc":"-70 50"},
|
||||
{"category":"WindowNode", "key":"window11", "color":"white", "caption":"Window", "type":"Window", "shape":"Rectangle", "height":10, "length":60, "notes":"", "loc":"-551 200", "group":"wall16", "angle":180},
|
||||
{"category":"WindowNode", "key":"window112", "color":"white", "caption":"Window", "type":"Window", "shape":"Rectangle", "height":10, "length":60, "notes":"", "loc":"-267.661267681676 200", "group":"wall16", "angle":180},
|
||||
{"key":"wall29", "category":"WallGroup", "caption":"Divider", "type":"Divider", "color":"lightgray", "startpoint":{"class":"go.Point", "x":100, "y":-100}, "endpoint":{"class":"go.Point", "x":100, "y":-20}, "smpt1":{"class":"go.Point", "x":99.9975, "y":-99.99896446609407}, "smpt2":{"class":"go.Point", "x":100.0025, "y":-100.00103553390593}, "empt1":{"class":"go.Point", "x":99.9975, "y":-20.00103553390593}, "empt2":{"class":"go.Point", "x":100.0025, "y":-19.99896446609407}, "thickness":0.005, "isGroup":true, "notes":"", "isDivider":true}
|
||||
],
|
||||
"linkDataArray": []}
|
||||
|
||||
</textarea>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,45 @@
|
||||
const child_process = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Part 1: Build webpack bundle
|
||||
console.log("Building gfp.js");
|
||||
try {
|
||||
console.log("Compiling typescript...");
|
||||
child_process.execSync("tsc");
|
||||
console.log("Running webpack...");
|
||||
child_process.execSync("webpack");
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
|
||||
// Part 2: Build d.ts file
|
||||
console.log("Building gfp.d.ts");
|
||||
try {
|
||||
// Concat all d.ts files in src into one big gcs.d.ts file
|
||||
var files = fs.readdirSync(path.join(__dirname, 'src'));
|
||||
var dtsFileData = "";
|
||||
for (var i in files) {
|
||||
var file = files[i];
|
||||
if (file.includes(".d.ts")) {
|
||||
var fileData = '';
|
||||
var lines = fs.readFileSync(path.join(__dirname, 'src', file), 'utf-8').split('\n');
|
||||
for (var j in lines) {
|
||||
var line = lines[j];
|
||||
if (line.substr(0, 6) !== "import") {
|
||||
fileData += line;
|
||||
}
|
||||
}
|
||||
dtsFileData += fileData;
|
||||
}
|
||||
}
|
||||
fs.writeFileSync('./lib/' + 'gfp.d.ts', dtsFileData);
|
||||
// Remove all individual d.ts files in src
|
||||
for (var i in files) {
|
||||
var file = files[i];
|
||||
if (file.includes('.d.ts') || file.includes('.js')) {
|
||||
fs.unlinkSync(path.join(__dirname, 'src', file));
|
||||
}
|
||||
}
|
||||
} catch (e) { console.log(e); }
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "floorplannertssample",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"devDependencies": {
|
||||
"awesome-typescript-loader": "^5.2.1",
|
||||
"concat": "^1.0.3",
|
||||
"dts-bundle": "^0.7.3",
|
||||
"dts-webpack-plugin": "0.0.9",
|
||||
"dtsbundler-webpack-plugin": "^1.0.0",
|
||||
"sweepline2": "^0.2.1",
|
||||
"typedoc": "^0.17.8",
|
||||
"webpack": "^4.44.1",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-node-externals": "^2.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.0",
|
||||
"child_process": "^1.0.2",
|
||||
"concatenate": "0.0.2",
|
||||
"del": "^5.1.0",
|
||||
"fs-extra": "9.0.1",
|
||||
"gojs": "^2.1.21",
|
||||
"grunt": "^1.2.1",
|
||||
"inquirer": "^7.3.3",
|
||||
"mkdirp": "^1.0.4",
|
||||
"py": "0.0.0",
|
||||
"replace-in-file": "^6.1.0",
|
||||
"request": "^2.88.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"run-sequence": "^2.2.1",
|
||||
"ts-loader": "^8.0.2",
|
||||
"tslint": "^6.1.3",
|
||||
"typedoc-default-themes": "^0.10.2",
|
||||
"typescript": "^3.9.7",
|
||||
"uglifyjs-webpack-plugin": "^2.2.0",
|
||||
"yargs": "^15.4.1"
|
||||
},
|
||||
"directories": {
|
||||
"lib": "lib"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node npm-gfp-build.js",
|
||||
"dts": "node npm-gfp-dts.js"
|
||||
},
|
||||
"author": "Northwoods Software"
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* FloorplanPalette Class
|
||||
* A FloorplanPalette is a Palette with special rules
|
||||
*/
|
||||
|
||||
import * as go from 'gojs';
|
||||
import { Floorplan } from './Floorplan.js';
|
||||
|
||||
export class FloorplanPalette extends go.Palette {
|
||||
|
||||
constructor(div: HTMLDivElement | string, floorplan: Floorplan/*, nodeDataArray: Array<any>*/) {
|
||||
super(div);
|
||||
|
||||
const $ = go.GraphObject.make;
|
||||
this.contentAlignment = go.Spot.Center;
|
||||
this.nodeTemplateMap = floorplan.nodeTemplateMap;
|
||||
|
||||
// palette also contains "floor" nodes -- nodes of particular floor types that can be dragged and dropped into wall-enclosed areas to create Room Nodes
|
||||
this.nodeTemplateMap.add('FloorNode',
|
||||
$(go.Node, 'Auto',
|
||||
$(go.Shape, { fill: makeFloorBrush(null), desiredSize: new go.Size(100, 100) },
|
||||
new go.Binding('fill', 'floorImage', function(src) {
|
||||
return makeFloorBrush(src);
|
||||
})
|
||||
),
|
||||
$(go.TextBlock, 'Drag me out to a wall-enclosed space to create a room', { desiredSize: new go.Size(90, NaN) },
|
||||
new go.Binding('visible', '', function(node: go.Node) {
|
||||
if (node.diagram instanceof go.Palette) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}).ofObject()
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
this.toolManager.contextMenuTool.isEnabled = false;
|
||||
|
||||
// add this new FloorplanPalette to the "palettes" field of its associated Floorplan
|
||||
floorplan.palettes.push(this);
|
||||
|
||||
} // end FloorplanPalette constructor
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a Pattern brush for floor nodes
|
||||
* @param src The relative path of the image to use for the pattern brush. If this is not specified, a default path is tried
|
||||
*/
|
||||
function makeFloorBrush(src: string | null) {
|
||||
const $ = go.GraphObject.make;
|
||||
if (src === null || src === undefined) { src = 'images/textures/floor1.jpg'; }
|
||||
const floorImage = new Image();
|
||||
floorImage.src = src;
|
||||
return $(go.Brush, 'Pattern', { pattern: floorImage });
|
||||
}
|
||||
|
||||
// export = FloorplanPalette;
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
import * as go from 'gojs';
|
||||
|
||||
// A custom Tool for moving a label on a Node
|
||||
export class NodeLabelDraggingTool extends go.Tool {
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @extends Tool
|
||||
* @class
|
||||
* This tool only works when the Node has a label (any GraphObject) marked with
|
||||
* { _isNodeLabel: true } that is positioned in a Spot Panel.
|
||||
* It works by modifying that label's GraphObject.alignment property to have an
|
||||
* offset from the center of the panel.
|
||||
*/
|
||||
|
||||
private label: go.GraphObject | null;
|
||||
private _offset: go.Point;
|
||||
private _originalAlignment: go.Spot | null;
|
||||
private _originalCenter: go.Point | null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.name = 'NodeLabelDragging';
|
||||
|
||||
/** @type {GraphObject} */
|
||||
this.label = null;
|
||||
/** @type {Point} */
|
||||
this._offset = new go.Point(); // of the mouse relative to the center of the label object
|
||||
/** @type {go.Spot} */
|
||||
this._originalAlignment = null;
|
||||
/** @type {Point} */
|
||||
this._originalCenter = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This tool can only start if the mouse has moved enough so that it is not a click,
|
||||
* and if the mouse down point is on a GraphObject "label" in a Spot Panel,
|
||||
* as determined by findLabel().
|
||||
* @this {NodeLabelDraggingTool}
|
||||
* @return {boolean}
|
||||
*/
|
||||
public canStart(): boolean {
|
||||
if (!go.Tool.prototype.canStart.call(this)) return false;
|
||||
const diagram = this.diagram;
|
||||
if (diagram === null) return false;
|
||||
// require left button & that it has moved far enough away from the mouse down point, so it isn't a click
|
||||
const e = diagram.lastInput;
|
||||
if (!e.left) return false;
|
||||
if (!this.isBeyondDragSize()) return false;
|
||||
|
||||
return this.findLabel() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* From the GraphObject at the mouse point, search up the visual tree until we get to
|
||||
* an object that has the "_isNodeLabel" property set to true, that is in a Spot Panel,
|
||||
* and that is not the first element of that Panel (i.e. not the main element of the panel).
|
||||
* @this {NodeLabelDraggingTool}
|
||||
* @return {GraphObject} This returns null if no such label is at the mouse down point.
|
||||
*/
|
||||
public findLabel(): go.GraphObject | null {
|
||||
const diagram = this.diagram;
|
||||
const e = diagram.firstInput;
|
||||
let elt = diagram.findObjectAt(e.documentPoint, null, null);
|
||||
|
||||
if (elt === null || !(elt.part instanceof go.Node)) return null;
|
||||
if (elt.part instanceof go.Node) {
|
||||
elt.part.isSelected = true;
|
||||
}
|
||||
while (elt.panel !== null) {
|
||||
if ((elt as any)._isNodeLabel && elt.panel.type === go.Panel.Spot && elt.panel.findMainElement() !== elt) return elt;
|
||||
elt = elt.panel;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a transaction, call findLabel and remember it as the "label" property,
|
||||
* and remember the original value for the label's alignment property.
|
||||
* @this {NodeLabelDraggingTool}
|
||||
*/
|
||||
public doActivate(): void {
|
||||
this.startTransaction('Shifted Label');
|
||||
this.label = this.findLabel();
|
||||
if (this.label !== null) {
|
||||
// compute the offset of the mouse-down point relative to the center of the label
|
||||
this._offset = this.diagram.firstInput.documentPoint.copy().subtract(this.label.getDocumentPoint(go.Spot.Center));
|
||||
this._originalAlignment = this.label.alignment.copy();
|
||||
if (this.label !== null && this.label.panel !== null) {
|
||||
const main = this.label.panel.findMainElement();
|
||||
if (main !== null) {
|
||||
this._originalCenter = main.getDocumentPoint(go.Spot.Center);
|
||||
}
|
||||
}
|
||||
}
|
||||
go.Tool.prototype.doActivate.call(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop any ongoing transaction.
|
||||
* @this {NodeLabelDraggingTool}
|
||||
*/
|
||||
public doDeactivate(): void {
|
||||
go.Tool.prototype.doDeactivate.call(this);
|
||||
this.stopTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear any reference to a label element.
|
||||
* @this {NodeLabelDraggingTool}
|
||||
*/
|
||||
public doStop(): void {
|
||||
this.label = null;
|
||||
go.Tool.prototype.doStop.call(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the label's original value for GraphObject.alignment.
|
||||
* @this {NodeLabelDraggingTool}
|
||||
*/
|
||||
public doCancel(): void {
|
||||
if (this.label !== null && this._originalAlignment !== null) {
|
||||
// this.label.alignment = this._originalAlignment;
|
||||
const node: go.Node = this.label.part as go.Node;
|
||||
this.diagram.model.set(node.data, 'labelAlignment', this._originalAlignment);
|
||||
}
|
||||
go.Tool.prototype.doCancel.call(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* During the drag, call updateAlignment in order to set the GraphObject.alignment of the label.
|
||||
* @this {NodeLabelDraggingTool}
|
||||
*/
|
||||
public doMouseMove(): void {
|
||||
if (!this.isActive) return;
|
||||
this.updateAlignment();
|
||||
}
|
||||
|
||||
/**
|
||||
* At the end of the drag, update the alignment of the label and finish the tool,
|
||||
* completing a transaction.
|
||||
* @this {NodeLabelDraggingTool}
|
||||
*/
|
||||
public doMouseUp(): void {
|
||||
if (!this.isActive) return;
|
||||
this.updateAlignment();
|
||||
this.transactionResult = 'Shifted Label';
|
||||
this.stopTool();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the label's GraphObject.alignment as an absolute offset from the center of the Spot Panel
|
||||
* that the label is in.
|
||||
* @this {NodeLabelDraggingTool}
|
||||
*/
|
||||
public updateAlignment(): void {
|
||||
if (this.label === null) return;
|
||||
const last = this.diagram.lastInput.documentPoint;
|
||||
const cntr = this._originalCenter;
|
||||
if (cntr !== null) {
|
||||
const align: go.Spot = new go.Spot(0.5, 0.5, last.x - this._offset.x - cntr.x, last.y - this._offset.y - cntr.y);
|
||||
// this.label.alignment = new go.Spot(0.5, 0.5, last.x - this._offset.x - cntr.x, last.y - this._offset.y - cntr.y);
|
||||
|
||||
const node: go.Node = this.label.part as go.Node;
|
||||
this.diagram.model.set(node.data, 'labelAlignment', align);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// export = NodeLabelDraggingTool;
|
||||
@@ -0,0 +1,197 @@
|
||||
/**
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* FLOOR PLANNER - WALL BUILDING TOOL
|
||||
* Used to construct new Walls in a Floorplan with mouse clicking / mouse point
|
||||
*/
|
||||
|
||||
import * as go from 'gojs';
|
||||
import { Floorplan } from './Floorplan.js';
|
||||
import { WallReshapingTool } from './WallReshapingTool.js';
|
||||
|
||||
export class WallBuildingTool extends go.Tool {
|
||||
|
||||
private _startPoint: go.Point | null;
|
||||
private _endPoint: go.Point | null;
|
||||
private _wallReshapingTool: WallReshapingTool | null;
|
||||
private _buildingWall: go.Group | null = null; // the wall being built
|
||||
// whether or not the "wall" we're building is really just a room / floor divider (not a physical wall)
|
||||
private _isBuildingDivider: boolean = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = 'WallBuilding';
|
||||
this._startPoint = null;
|
||||
this._endPoint = null;
|
||||
this._wallReshapingTool = null;
|
||||
this._isBuildingDivider = false;
|
||||
}
|
||||
|
||||
// Get / set the current startPoint
|
||||
get startPoint(): go.Point | null { return this._startPoint; }
|
||||
set startPoint(value: go.Point | null) { this._startPoint = value; }
|
||||
|
||||
// Get / set the current endPoint
|
||||
get endPoint(): go.Point | null { return this._endPoint; }
|
||||
set endPoint(value: go.Point | null) { this._endPoint = value; }
|
||||
|
||||
// Get / set the floorplan's WallReshapingTool
|
||||
get wallReshapingTool(): WallReshapingTool | null { return this._wallReshapingTool; }
|
||||
set wallReshapingTool(value: WallReshapingTool | null) { this._wallReshapingTool = value; }
|
||||
|
||||
// Get / set the wall being built
|
||||
get buildingWall(): go.Group | null { return this._buildingWall; }
|
||||
set buildingWall(value: go.Group | null) { this._buildingWall = value; }
|
||||
|
||||
// Get / set whether or not we're actually building a room / floor divider, not a wall
|
||||
get isBuildingDivider(): boolean { return this._isBuildingDivider; }
|
||||
set isBuildingDivider(value: boolean) { this._isBuildingDivider = value; }
|
||||
|
||||
/**
|
||||
* Start wall building transaction.
|
||||
* If the mouse point is inside a wall or near a wall endpoint, snap to that wall or endpoint
|
||||
*/
|
||||
public doActivate(): void {
|
||||
this.endPoint = null;
|
||||
this.startTransaction(this.name);
|
||||
this.diagram.isMouseCaptured = true;
|
||||
const tool = this;
|
||||
const fp: Floorplan = tool.diagram as Floorplan;
|
||||
|
||||
let clickPt: go.Point = tool.diagram.lastInput.documentPoint;
|
||||
let isSnapped: boolean = false;
|
||||
// if the clickPt is inside some other wall's geometry, project it onto that wall's segment
|
||||
const walls: go.Iterator<go.Group> = fp.findNodesByExample({ category: 'WallGroup' }) as go.Iterator<go.Group>;
|
||||
walls.iterator.each(function(w: go.Group) {
|
||||
if (fp.isPointInWall(w, clickPt)) {
|
||||
// don't check if you're inside the wall you're building, you obviously are
|
||||
if (tool.buildingWall === null) {
|
||||
const snapPt: go.Point = clickPt.projectOntoLineSegmentPoint(w.data.startpoint, w.data.endpoint);
|
||||
clickPt = snapPt;
|
||||
isSnapped = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// if the click point is close to another wall's start/endpoint, use that as the startpoint of the new wall
|
||||
walls.iterator.each(function(w: go.Group) {
|
||||
const sp: go.Point = w.data.startpoint; const ep: go.Point = w.data.endpoint;
|
||||
const distSp: number = Math.sqrt(sp.distanceSquaredPoint(clickPt));
|
||||
// TODO probably need a better "closeness" metric than just a raw number -- it could be an optional parameter?
|
||||
if (distSp < 15) {
|
||||
clickPt = sp;
|
||||
isSnapped = true;
|
||||
}
|
||||
const distEp: number = Math.sqrt(ep.distanceSquaredPoint(clickPt));
|
||||
if (distEp < 15) {
|
||||
clickPt = ep;
|
||||
isSnapped = true;
|
||||
}
|
||||
});
|
||||
|
||||
// assign startpoint based on grid (iff startpoint was not determined by another wall's endpoint)
|
||||
if (true) {
|
||||
let gs: number = fp.model.modelData.gridSize;
|
||||
if (!(tool.diagram.toolManager.draggingTool.isGridSnapEnabled) || isSnapped) gs = .0001;
|
||||
const newx: number = gs * Math.round(clickPt.x / gs);
|
||||
const newy: number = gs * Math.round(clickPt.y / gs);
|
||||
clickPt = new go.Point(newx, newy);
|
||||
}
|
||||
|
||||
this.startPoint = clickPt;
|
||||
|
||||
this.wallReshapingTool = fp.toolManager.mouseDownTools.elt(3) as WallReshapingTool;
|
||||
// Default functionality:
|
||||
this.isActive = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add wall data to Floorplan and begin reshaping the new wall
|
||||
*/
|
||||
public doMouseDown(): void {
|
||||
const diagram: go.Diagram = this.diagram;
|
||||
const tool = this;
|
||||
tool.diagram.currentCursor = 'crosshair';
|
||||
const data = {
|
||||
key: 'wall', category: 'WallGroup', caption: tool.isBuildingDivider ? 'Divider' : 'Wall', type: tool.isBuildingDivider ? 'Divider' : 'Wall',
|
||||
startpoint: tool.startPoint, endpoint: tool.startPoint, smpt1: tool.startPoint, smpt2: tool.startPoint, empt1: tool.startPoint, empt2: tool.startPoint,
|
||||
thickness: tool._isBuildingDivider ? .005 : parseFloat(diagram.model.modelData.wallThickness), color: 'lightgray', isGroup: true, notes: '',
|
||||
isDivider: tool.isBuildingDivider
|
||||
};
|
||||
this.diagram.model.addNodeData(data);
|
||||
const wall: go.Group = diagram.findPartForKey(data.key) as go.Group;
|
||||
this.buildingWall = wall;
|
||||
const fp: Floorplan = diagram as Floorplan;
|
||||
fp.updateWall(wall);
|
||||
const part: go.Part | null = diagram.findPartForData(data);
|
||||
if (part === null) return;
|
||||
// set the TransactionResult before raising event, in case it changes the result or cancels the tool
|
||||
tool.transactionResult = tool.name;
|
||||
diagram.raiseDiagramEvent('PartCreated', part);
|
||||
|
||||
if (tool.wallReshapingTool === null) return;
|
||||
// start the wallReshapingTool, tell it what wall it's reshaping (more accurately, the shape that will have the reshape handle)
|
||||
tool.wallReshapingTool.isEnabled = true;
|
||||
diagram.select(part);
|
||||
tool.wallReshapingTool.isBuilding = true;
|
||||
tool.wallReshapingTool.adornedShape = part.findObject('SHAPE') as go.Shape;
|
||||
tool.wallReshapingTool.doActivate();
|
||||
}
|
||||
|
||||
/**
|
||||
* If user presses Esc key, cancel the wall building
|
||||
*/
|
||||
public doKeyDown(): void {
|
||||
const fp: Floorplan = this.diagram as Floorplan;
|
||||
const e: go.InputEvent = fp.lastInput;
|
||||
if (e.key === 'Esc') {
|
||||
const wall: go.Group = fp.selection.first() as go.Group;
|
||||
fp.remove(wall);
|
||||
fp.pointNodes.iterator.each(function(node) { fp.remove(node); });
|
||||
fp.dimensionLinks.iterator.each(function(link) { fp.remove(link); });
|
||||
fp.pointNodes.clear();
|
||||
fp.dimensionLinks.clear();
|
||||
this.doDeactivate();
|
||||
}
|
||||
go.Tool.prototype.doKeyDown.call(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the mouse moves, reshape the wall
|
||||
*/
|
||||
public doMouseMove(): void {
|
||||
if (this.wallReshapingTool === null) return;
|
||||
this.diagram.currentCursor = 'crosshair';
|
||||
this.wallReshapingTool.doMouseMove();
|
||||
}
|
||||
|
||||
/**
|
||||
* End transaction, update wall dimensions and geometries (mitering?)
|
||||
*/
|
||||
public doDeactivate(): void {
|
||||
const diagram: go.Diagram = this.diagram;
|
||||
this.buildingWall = null;
|
||||
this.diagram.currentCursor = '';
|
||||
this.diagram.isMouseCaptured = false;
|
||||
|
||||
if (this.wallReshapingTool !== null) {
|
||||
this.wallReshapingTool.isEnabled = false;
|
||||
this.wallReshapingTool.adornedShape = null;
|
||||
this.wallReshapingTool.doMouseUp(); // perform mitering
|
||||
this.wallReshapingTool.doDeactivate();
|
||||
this.wallReshapingTool.isBuilding = false;
|
||||
}
|
||||
|
||||
const fp: Floorplan = diagram as Floorplan;
|
||||
fp.updateWallDimensions();
|
||||
|
||||
this.stopTransaction();
|
||||
|
||||
this.isActive = false; // Default functionality
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// export = WallBuildingTool;
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*/
|
||||
|
||||
import * as Floorplan from './Floorplan.js';
|
||||
import * as FloorplanPalette from './FloorplanPalette.js';
|
||||
import * as WallBuildingTool from './WallBuildingTool.js';
|
||||
import * as WallReshapingTool from './WallReshapingTool.js';
|
||||
|
||||
module.exports = {
|
||||
Floorplan: require('./Floorplan').Floorplan,
|
||||
FloorplanPalette: require('./FloorplanPalette').FloorplanPalette,
|
||||
WallBuildingTool: require('./WallBuildingTool').WallBuildingTool,
|
||||
WallReshapingTool: require('./WallReshapingTool').WallReshapingTool
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"removeComments": true,
|
||||
"stripInternal": true,
|
||||
"declaration": true,
|
||||
"declarationDir": "./src/",
|
||||
"strict": true
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*.ts"
|
||||
],
|
||||
"exclude": [],
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var webpack = require('webpack');
|
||||
//const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
"gfp": './src/floorplannerFiles', // webpack floorplanner bundle
|
||||
},
|
||||
target: 'web',
|
||||
devtool: "source-map",
|
||||
resolve: {
|
||||
extensions: [".tsx", ".js", ".ts"]
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'lib'),
|
||||
filename: 'gfp.js',
|
||||
libraryTarget: 'window',
|
||||
library: 'gfp'
|
||||
},
|
||||
optimization: {
|
||||
minimize: false
|
||||
},
|
||||
externals: [
|
||||
{
|
||||
'gojs': 'go'
|
||||
}
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.BannerPlugin("Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.")
|
||||
////new UglifyJSPlugin({ sourceMap: true })
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>GoJS Sample Projects</title>
|
||||
<meta name="description" content="Various samples using GoJS that are in separate project directories." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<h2>GoJS Sample Projects</h2>
|
||||
<p>
|
||||
Most of the hundreds of samples are in individual pages at <a href="../samples/index.html">Samples Index</a>
|
||||
and <a href="../extensions/index.html">Extensions</a>.
|
||||
</p>
|
||||
<p>
|
||||
This directory has subdirectories that hold samples that consist of more than one file or have nested subdirectories.
|
||||
</p>
|
||||
<p>
|
||||
The <a href="bpmn/BPMN.html" target="_blank">BPMN sample</a> demonstrates some basics of a BPMN editor.
|
||||
The sources are in the <code>bpmn</code> subdirectory.
|
||||
</p>
|
||||
<p>
|
||||
There is a floor plan editor at <a href="floorplannerTS/index.html" target="_blank">TypeScript FloorPlanner sample</a>.
|
||||
The sources are in the <code>floorplannerTS</code> subdirectory.
|
||||
There is also an <a href="floorplanner/FloorPlanner.html" target="_blank">older JavaScript FloorPlanner sample</a>,
|
||||
whose sources are in the <code>floorplanner</code> subdirectory.
|
||||
</p>
|
||||
<p>
|
||||
A demonstration of generating, showing, and downloading PDF for a diagram is at <a href="pdf/minimalPDF.html" target="_blank">minimalPDF.html</a>.
|
||||
The sample demonstrates both showing the PDF on the page itself as well as downloading it upon pressing a button.
|
||||
In a real application it is likely that you would only want to have the user download the PDF file.
|
||||
</p>
|
||||
<p>
|
||||
An example server-side project for running GoJS with Puppeteer is in the <code>puppeteer</code> subdirectory or at
|
||||
<a href="https://github.com/NorthwoodsSoftware/GoJS/tree/master/projects/puppeteer">GitHub: puppeteer</a>.
|
||||
</p>
|
||||
<p>
|
||||
The GoJS Cloud Storage extension is in the <code>storage</code> subdirectory or at
|
||||
<a href="https://github.com/NorthwoodsSoftware/GoJS/tree/master/projects/storage">GitHub: storage</a>.
|
||||
</p>
|
||||
<p>
|
||||
If you have a license to the GoJS source code, you can rebuild the full functionality `go.js` library
|
||||
in the <code>maximalSource</code> subdirectory or at
|
||||
<a href="https://github.com/NorthwoodsSoftware/GoJS/tree/master/projects/maximalSource">GitHub: maximalSource</a>
|
||||
</p>
|
||||
<p>
|
||||
If you have a license to the GoJS source code, you can build the subset functionality library
|
||||
in the <code>minimalSource</code> or <code>minimalSourceBrowserify</code> subdirectory or at
|
||||
<a href="https://github.com/NorthwoodsSoftware/GoJS/tree/master/projects/minimalSource">GitHub: minimalSource</a>, or at
|
||||
<a href="https://github.com/NorthwoodsSoftware/GoJS/tree/master/projects/minimalSourceBrowserify">GitHub: minimalSourceBrowserify</a>
|
||||
</p>
|
||||
|
||||
<h2>Other GoJS Sample Projects</h2>
|
||||
<p>
|
||||
As of GoJS 2.0 many of these projects have moved to their own GitHub repositories.
|
||||
</p>
|
||||
<p>
|
||||
There is a new package for Angular, named gojs-angular, which is at <a href="https://github.com/NorthwoodsSoftware/gojs-angular">GitHub: gojs-angular</a>.
|
||||
It is also available for <a href="https://www.npmjs.com/package/gojs-angular">npm: gojs-angular</a>.
|
||||
That package is used by a sample: <a href="https://github.com/NorthwoodsSoftware/gojs-angular-basic">GitHub: gojs-angular-basic</a>.
|
||||
Read more about Angular at the <a href="../intro/angular.html" target="_blank">GoJS with Angular</a> Intro page.
|
||||
</p>
|
||||
<p>
|
||||
There is a new package for React, named gojs-react, which is at <a href="https://github.com/NorthwoodsSoftware/gojs-react">GitHub: gojs-react</a>.
|
||||
It is also available for <a href="https://www.npmjs.com/package/gojs-react">npm: gojs-react</a>.
|
||||
That package is used by a sample: <a href="https://github.com/NorthwoodsSoftware/gojs-react-basic">GitHub: gojs-react-basic</a>.
|
||||
Read more about React at the <a href="../intro/react.html" target="_blank">GoJS with React</a> Intro page.
|
||||
</p>
|
||||
<ul>
|
||||
<li>The Electron sample is now at <a href="https://github.com/NorthwoodsSoftware/GoJS-projects/tree/master/electron-circuit">GitHub: electron-circuit</a></li>
|
||||
<li>The Cordova sample is now at <a href="https://github.com/NorthwoodsSoftware/GoJS-projects/tree/master/cordova-circuit">GitHub: cordova-circuit</a></li>
|
||||
<li>The NW sample is now at <a href="https://github.com/NorthwoodsSoftware/GoJS-projects/tree/master/nw-circuit">GitHub: nw-circuit</a></li>
|
||||
<li>The Vue Webpack sample is now at <a href="https://github.com/NorthwoodsSoftware/GoJS-projects/tree/master/vue-webpack">GitHub: vue-webpack</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This index demonstrates building a maximal library from source.
|
||||
* There is a minimal example in the same directory
|
||||
*/
|
||||
|
||||
/* tslint:disable:ordered-imports */
|
||||
import { EnumValue } from '../../srcTS/enumValue';
|
||||
import { Map, Set, List } from '../../srcTS/collections';
|
||||
import { Point } from '../../srcTS/point';
|
||||
import { Size } from '../../srcTS/size';
|
||||
import { Rect } from '../../srcTS/rect';
|
||||
import { Margin } from '../../srcTS/margin';
|
||||
import { Spot } from '../../srcTS/spot';
|
||||
import { PathSegment, PathFigure, Geometry } from '../../srcTS/geometry';
|
||||
import { DiagramEvent, InputEvent } from '../../srcTS/inputEvent';
|
||||
import { ChangedEvent } from '../../srcTS/changedEvent';
|
||||
import { Binding, Model } from '../../srcTS/model';
|
||||
import { GraphLinksModel } from '../../srcTS/graphLinksModel';
|
||||
import { TreeModel } from '../../srcTS/treeModel';
|
||||
import { UndoManager, Transaction } from '../../srcTS/undoManager';
|
||||
import { CommandHandler } from '../../srcTS/commandHandler';
|
||||
import { Tool } from '../../srcTS/tool';
|
||||
import { DraggingTool } from '../../srcTS/draggingTool';
|
||||
import { RelinkingTool, LinkingTool, LinkingBaseTool } from '../../srcTS/linkingTools';
|
||||
import { LinkReshapingTool } from '../../srcTS/linkReshapingTool';
|
||||
import { ResizingTool } from '../../srcTS/resizingTool';
|
||||
import { RotatingTool } from '../../srcTS/rotatingTool';
|
||||
import { PanningTool, DragSelectingTool, ClickCreatingTool, ActionTool, ClickSelectingTool } from '../../srcTS/selectingTools';
|
||||
import { TextEditingTool } from '../../srcTS/textEditingTool';
|
||||
import { ToolManager } from '../../srcTS/toolManager';
|
||||
import { Animation, AnimationManager, AnimationTrigger } from '../../srcTS/animationManager';
|
||||
import { Layer } from '../../srcTS/layer';
|
||||
import { Diagram, DraggingInfo } from '../../srcTS/diagram';
|
||||
import { Palette } from '../../srcTS/palette';
|
||||
import { Overview } from '../../srcTS/overview';
|
||||
import { Brush } from '../../srcTS/brush';
|
||||
import { GraphObject } from '../../srcTS/graphObject';
|
||||
import { Panel } from '../../srcTS/panel';
|
||||
import { RowColumnDefinition } from '../../srcTS/rowColumnDefinition';
|
||||
import { Shape } from '../../srcTS/shape';
|
||||
import { TextBlock, TextBlockMetrics } from '../../srcTS/textBlock';
|
||||
import { Picture } from '../../srcTS/picture';
|
||||
import { Adornment, Part } from '../../srcTS/parts';
|
||||
import { Node } from '../../srcTS/parts';
|
||||
import { Link } from '../../srcTS/parts';
|
||||
import { Group, Placeholder } from '../../srcTS/parts';
|
||||
import { LayoutEdge, LayoutVertex, LayoutNetwork, Layout } from '../../srcTS/layout';
|
||||
import { GridLayout } from '../../srcTS/gridLayout';
|
||||
import { GraphLinksPartManager } from '../../srcTS/graphLinksPartManager';
|
||||
import { TreePartManager } from '../../srcTS/treePartManager';
|
||||
|
||||
import { CircularEdge, CircularVertex, CircularNetwork, CircularLayout } from '../../srcTS/circularLayout';
|
||||
import { ForceDirectedEdge, ForceDirectedVertex, ForceDirectedNetwork, ForceDirectedLayout } from '../../srcTS/forceDirectedLayout';
|
||||
import { LayeredDigraphEdge, LayeredDigraphVertex, LayeredDigraphNetwork, LayeredDigraphLayout } from '../../srcTS/layeredDigraphLayout';
|
||||
import { TreeEdge, TreeVertex, TreeNetwork, TreeLayout } from '../../srcTS/treeLayout';
|
||||
import { HTMLInfo } from '../../srcTS/htmlInfo';
|
||||
import { ContextMenuTool } from '../../srcTS/contextMenuTool';
|
||||
import { PanelLayout } from '../../srcTS/panelLayout';
|
||||
import { PanelLayoutHorizontal } from '../../srcTS/panelLayoutHorizontal';
|
||||
import { PanelLayoutSpot } from '../../srcTS/panelLayoutSpot';
|
||||
import { PanelLayoutTable, PanelLayoutTableRow, PanelLayoutTableColumn } from '../../srcTS/panelLayoutTable';
|
||||
import { PanelLayoutViewbox } from '../../srcTS/panelLayoutViewbox';
|
||||
import { PanelLayoutGraduated } from '../../srcTS/panelLayoutGraduated';
|
||||
import { PanelLayoutGrid } from '../../srcTS/panelLayoutGrid';
|
||||
|
||||
// Already imported in Panel
|
||||
// import { PanelLayoutPosition } from '../../srcTS/panelLayoutPosition';
|
||||
// import { PanelLayoutVertical } from '../../srcTS/panelLayoutVertical';
|
||||
// import { PanelLayoutAuto } from '../../srcTS/panelLayoutAuto';
|
||||
// import { PanelLayoutLink } from '../../srcTS/panelLayoutLink';
|
||||
// import { PanelLayoutGrid } from '../../srcTS/panelLayoutGrid';
|
||||
|
||||
/**
|
||||
* @suppress {duplicate}
|
||||
* @this {ToolManager}
|
||||
*/
|
||||
ToolManager.prototype.initializeStandardTools = function(this: ToolManager): void {
|
||||
// For each of these lists, the tools need to be assigned in a specific order,
|
||||
// so that they are added to the list in the correct order.
|
||||
|
||||
// mouse-down tools:
|
||||
this.replaceStandardTool('Action', new ActionTool(), this.mouseDownTools);
|
||||
this.replaceStandardTool('Relinking', new RelinkingTool(), this.mouseDownTools);
|
||||
this.replaceStandardTool('LinkReshaping', new LinkReshapingTool(), this.mouseDownTools);
|
||||
this.replaceStandardTool('Rotating', new RotatingTool(), this.mouseDownTools);
|
||||
this.replaceStandardTool('Resizing', new ResizingTool(), this.mouseDownTools);
|
||||
|
||||
// mouse-move tools:
|
||||
this.replaceStandardTool('Linking', new LinkingTool(), this.mouseMoveTools);
|
||||
this.replaceStandardTool('Dragging', new DraggingTool(), this.mouseMoveTools);
|
||||
this.replaceStandardTool('DragSelecting', new DragSelectingTool(), this.mouseMoveTools);
|
||||
this.replaceStandardTool('Panning', new PanningTool(), this.mouseMoveTools);
|
||||
|
||||
// mouse-up tools:
|
||||
this.replaceStandardTool('ContextMenu', new ContextMenuTool(), this.mouseUpTools);
|
||||
this.replaceStandardTool('TextEditing', new TextEditingTool(), this.mouseUpTools);
|
||||
this.replaceStandardTool('ClickCreating', new ClickCreatingTool(), this.mouseUpTools);
|
||||
this.replaceStandardTool('ClickSelecting', new ClickSelectingTool(), this.mouseUpTools);
|
||||
};
|
||||
|
||||
// By default, these are added to the library in index.ts. When building from source, you must add them manually.
|
||||
Panel.definePanelLayout('Horizontal', new PanelLayoutHorizontal());
|
||||
Panel.definePanelLayout('Spot', new PanelLayoutSpot());
|
||||
Panel.definePanelLayout('Table', new PanelLayoutTable());
|
||||
Panel.definePanelLayout('Viewbox', new PanelLayoutViewbox());
|
||||
Panel.definePanelLayout('TableRow', new PanelLayoutTableRow());
|
||||
Panel.definePanelLayout('TableColumn', new PanelLayoutTableColumn());
|
||||
Panel.definePanelLayout('Graduated', new PanelLayoutGraduated());
|
||||
Panel.definePanelLayout('Grid', new PanelLayoutGrid());
|
||||
// These PanelLayouts are essential to GoJs and are added in panel.ts, so we don't add them here:
|
||||
// Panel.definePanelLayout('Position', new PanelLayoutPosition());
|
||||
// Panel.definePanelLayout('Vertical', new PanelLayoutVertical());
|
||||
// Panel.definePanelLayout('Auto', new PanelLayoutAuto());
|
||||
// Panel.definePanelLayout('Link', new PanelLayoutLink());
|
||||
|
||||
|
||||
// Add PartManagers for model subclasses.
|
||||
Diagram.addPartManager(GraphLinksModel.type, GraphLinksPartManager);
|
||||
Diagram.addPartManager(TreeModel.type, TreePartManager);
|
||||
|
||||
/**
|
||||
* @hidden @internal
|
||||
*/
|
||||
export const go = {
|
||||
get licenseKey() {
|
||||
return Diagram.licenseKey;
|
||||
},
|
||||
set licenseKey (licx) {
|
||||
Diagram.licenseKey = licx;
|
||||
},
|
||||
get version() {
|
||||
return Diagram.version;
|
||||
},
|
||||
|
||||
'Group': Group,
|
||||
|
||||
// 'Util': Util, // could add back for debug
|
||||
'EnumValue': EnumValue,
|
||||
'List': List,
|
||||
'Set': Set,
|
||||
'Map': Map,
|
||||
'Point': Point,
|
||||
'Size': Size,
|
||||
'Rect': Rect,
|
||||
'Margin': Margin,
|
||||
'Spot': Spot,
|
||||
'Geometry': Geometry,
|
||||
'PathFigure': PathFigure,
|
||||
'PathSegment': PathSegment,
|
||||
'InputEvent': InputEvent,
|
||||
'DiagramEvent': DiagramEvent,
|
||||
'ChangedEvent': ChangedEvent,
|
||||
'Model': Model,
|
||||
'GraphLinksModel': GraphLinksModel,
|
||||
'TreeModel': TreeModel,
|
||||
'Binding': Binding,
|
||||
'Transaction': Transaction,
|
||||
'UndoManager': UndoManager,
|
||||
'CommandHandler': CommandHandler,
|
||||
'Tool': Tool,
|
||||
'DraggingTool': DraggingTool,
|
||||
'DraggingInfo': DraggingInfo,
|
||||
'LinkingBaseTool': LinkingBaseTool,
|
||||
'LinkingTool': LinkingTool,
|
||||
'RelinkingTool': RelinkingTool,
|
||||
'LinkReshapingTool': LinkReshapingTool,
|
||||
'ResizingTool': ResizingTool,
|
||||
'RotatingTool': RotatingTool,
|
||||
'ClickSelectingTool': ClickSelectingTool,
|
||||
'ActionTool': ActionTool,
|
||||
'ClickCreatingTool': ClickCreatingTool,
|
||||
'HTMLInfo': HTMLInfo,
|
||||
'ContextMenuTool': ContextMenuTool,
|
||||
'DragSelectingTool': DragSelectingTool,
|
||||
'PanningTool': PanningTool,
|
||||
'TextEditingTool': TextEditingTool,
|
||||
'ToolManager': ToolManager,
|
||||
'Animation': Animation,
|
||||
'AnimationManager': AnimationManager,
|
||||
'AnimationTrigger': AnimationTrigger,
|
||||
'Layer': Layer,
|
||||
'Diagram': Diagram,
|
||||
'Palette': Palette,
|
||||
'Overview': Overview,
|
||||
'Brush': Brush,
|
||||
'GraphObject': GraphObject,
|
||||
'Panel': Panel,
|
||||
'RowColumnDefinition': RowColumnDefinition,
|
||||
'Shape': Shape,
|
||||
'TextBlock': TextBlock,
|
||||
'TextBlockMetrics': TextBlockMetrics,
|
||||
'Picture': Picture,
|
||||
'Part': Part,
|
||||
'Adornment': Adornment,
|
||||
'Node': Node,
|
||||
'Link': Link,
|
||||
'Placeholder': Placeholder,
|
||||
'Layout': Layout,
|
||||
'LayoutNetwork': LayoutNetwork,
|
||||
'LayoutVertex': LayoutVertex,
|
||||
'LayoutEdge': LayoutEdge,
|
||||
'GridLayout': GridLayout,
|
||||
'PanelLayout': PanelLayout,
|
||||
|
||||
'CircularLayout': CircularLayout,
|
||||
'CircularNetwork': CircularNetwork,
|
||||
'CircularVertex': CircularVertex,
|
||||
'CircularEdge': CircularEdge,
|
||||
'ForceDirectedLayout': ForceDirectedLayout,
|
||||
'ForceDirectedNetwork': ForceDirectedNetwork,
|
||||
'ForceDirectedVertex': ForceDirectedVertex,
|
||||
'ForceDirectedEdge': ForceDirectedEdge,
|
||||
'LayeredDigraphLayout': LayeredDigraphLayout,
|
||||
'LayeredDigraphNetwork': LayeredDigraphNetwork,
|
||||
'LayeredDigraphVertex': LayeredDigraphVertex,
|
||||
'LayeredDigraphEdge': LayeredDigraphEdge,
|
||||
'TreeLayout': TreeLayout,
|
||||
'TreeNetwork': TreeNetwork,
|
||||
'TreeVertex': TreeVertex,
|
||||
'TreeEdge': TreeEdge
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Webpack Maximal GoJS Sample</title>
|
||||
<meta name="description" content="An almost maximal diagram using a every GoJS class." />
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<script src="maximal.js"></script>
|
||||
|
||||
<script>
|
||||
window.onload = function() {
|
||||
// window.init() was created in maximal.js, which was built from maximal.ts with webpack. See readme for details.
|
||||
init();
|
||||
}
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="sample">
|
||||
<!-- The DIV for the Diagram needs an explicit size or else we won't see anything.
|
||||
This also adds a border to help see the edges of the viewport. -->
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:400px; height:400px"></div>
|
||||
|
||||
<p>
|
||||
This sample uses a JavaScript file that is built with Webpack. If you do not see a diagram, you may need to build first. See the <a href="./readme.md">readme.md in this directory</a> for details.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This maximal sample builds from source with all GoJS modules.
|
||||
As a result, the build JavaScript is large, but all functionality is present.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The <a href="./maximal-index.ts">maximal-index.ts</a> displays how to include every GoJS module into a build.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
For an example of building GoJS from source with as few modules as possible,
|
||||
see the <a href="../minimalSource/minimal.html">Minimal project</a> adjacent to this directory.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
The code in this file is typical of a "minimal" sample.
|
||||
|
||||
The code which includes all modules can be found in maximal-index.ts
|
||||
*/
|
||||
|
||||
|
||||
import { go } from './maximal-index';
|
||||
|
||||
window.init = function() {
|
||||
const $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
|
||||
const myDiagram = $(go.Diagram, 'myDiagramDiv', // create a Diagram for the DIV HTML element
|
||||
{
|
||||
'undoManager.isEnabled': true // enable undo & redo
|
||||
});
|
||||
|
||||
// define a simple Node template
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto', // the Shape will go around the TextBlock
|
||||
{ rotatable: true },
|
||||
$(go.Shape, 'RoundedRectangle', { strokeWidth: 0 },
|
||||
// Shape.fill is bound to Node.data.color
|
||||
new go.Binding('fill', 'color')),
|
||||
$(go.TextBlock,
|
||||
{ margin: 8 }, // some room around the text
|
||||
// TextBlock.text is bound to Node.data.key
|
||||
new go.Binding('text', 'key'))
|
||||
);
|
||||
|
||||
myDiagram.model = new go.GraphLinksModel(
|
||||
[
|
||||
{ key: 'Alpha', color: 'lightblue' },
|
||||
{ key: 'Beta', color: 'orange' },
|
||||
{ key: 'Gamma', color: 'lightgreen' },
|
||||
{ key: 'Delta', color: 'pink' }
|
||||
],
|
||||
[
|
||||
{ from: 'Alpha', to: 'Beta' },
|
||||
{ from: 'Alpha', to: 'Gamma' },
|
||||
{ from: 'Beta', to: 'Beta' },
|
||||
{ from: 'Gamma', to: 'Delta' },
|
||||
{ from: 'Delta', to: 'Alpha' }
|
||||
]);
|
||||
|
||||
// Attach to the window so you can manipulate in the console
|
||||
window.myDiagram = myDiagram;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "maximalsource",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"ts-loader": "^8.0.2",
|
||||
"typescript": "^3.9.7",
|
||||
"webpack": "^4.44.1",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-closure-compiler": "^2.1.6"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"author": "Northwoods Software"
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
This demonstrates how to build a diagram with GoJS source, using webpack.
|
||||
|
||||
Because Webpack is able to shake out all functionality not used, this Diagram is truly minimal, and the compiled JS contains no layouts, no GoJS Tools for interactivity, no TreeModel or GraphLinksModel, no SVG rendering capability,
|
||||
|
||||
There are many related projects in this directory:
|
||||
|
||||
* **minimalSource**, for an example of building GoJS while excluding as many modules as possible. This project uses webpack's tree shaking to remove unused modules.
|
||||
* **maximalSource**, for an example of building GoJS while including every module.
|
||||
* **minimalSourceBrowserify**, for another minimal example of building GoJS with Browserify. Also does not use `require`.
|
||||
|
||||
With npm, install the dependencies:
|
||||
|
||||
```
|
||||
$ npm install
|
||||
```
|
||||
|
||||
You may also need to install `webpack` globally. Run script with:
|
||||
|
||||
```
|
||||
$ webpack
|
||||
```
|
||||
|
||||
This creates `maximal.js`, which is referenced in `maximal.html`.
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"strictNullChecks": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
const path = require('path');
|
||||
const ClosureCompilerPlugin = require('webpack-closure-compiler');
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
entry: path.resolve(__dirname, './maximal.ts'),
|
||||
output: {
|
||||
path: path.resolve(__dirname, './'),
|
||||
library: 'maximal.js',
|
||||
libraryTarget: 'umd',
|
||||
filename: 'maximal.js'
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js']
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
// files with `.ts` or `.tsx` extension will be handled by `ts-loader`
|
||||
{ test: /\.tsx?$/, loader: 'ts-loader' }
|
||||
]
|
||||
},
|
||||
stats: 'errors-only',
|
||||
plugins: [
|
||||
new ClosureCompilerPlugin({
|
||||
compiler: {
|
||||
language_out: 'ECMASCRIPT5',
|
||||
process_common_js_modules: true,
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Removable modules are commented out in this file with "!!" in the comment.
|
||||
* This index demonstrates building a minimal library from source. There is a maximal example in the same directory.
|
||||
*/
|
||||
|
||||
/* tslint:disable:ordered-imports */
|
||||
import { EnumValue } from '../../srcTS/enumValue';
|
||||
import { Map, Set, List } from '../../srcTS/collections';
|
||||
import { Point } from '../../srcTS/point';
|
||||
import { Size } from '../../srcTS/size';
|
||||
import { Rect } from '../../srcTS/rect';
|
||||
import { Margin } from '../../srcTS/margin';
|
||||
import { Spot } from '../../srcTS/spot';
|
||||
import { PathSegment, PathFigure, Geometry } from '../../srcTS/geometry';
|
||||
import { DiagramEvent, InputEvent } from '../../srcTS/inputEvent';
|
||||
import { ChangedEvent } from '../../srcTS/changedEvent';
|
||||
import { Binding, Model } from '../../srcTS/model';
|
||||
import { UndoManager, Transaction } from '../../srcTS/undoManager';
|
||||
import { Tool } from '../../srcTS/tool';
|
||||
import { ToolManager } from '../../srcTS/toolManager';
|
||||
import { AnimationManager } from '../../srcTS/animationManager';
|
||||
import { Layer } from '../../srcTS/layer';
|
||||
import { Diagram } from '../../srcTS/diagram';
|
||||
import { Brush } from '../../srcTS/brush';
|
||||
import { GraphObject } from '../../srcTS/graphObject';
|
||||
import { Panel } from '../../srcTS/panel';
|
||||
import { RowColumnDefinition } from '../../srcTS/rowColumnDefinition';
|
||||
import { Shape } from '../../srcTS/shape';
|
||||
import { TextBlock, TextBlockMetrics } from '../../srcTS/textBlock';
|
||||
import { Picture } from '../../srcTS/picture';
|
||||
import { Adornment, Part } from '../../srcTS/parts';
|
||||
import { Node } from '../../srcTS/parts';
|
||||
import { Link } from '../../srcTS/parts';
|
||||
import { Group, Placeholder } from '../../srcTS/parts';
|
||||
import { LayoutEdge, LayoutVertex, LayoutNetwork, Layout } from '../../srcTS/layout';
|
||||
|
||||
|
||||
// Already imported in Panel
|
||||
// import { PanelLayoutPosition } from '../../srcTS/panelLayoutPosition';
|
||||
// import { PanelLayoutVertical } from '../../srcTS/panelLayoutVertical';
|
||||
// import { PanelLayoutAuto } from '../../srcTS/panelLayoutAuto';
|
||||
// import { PanelLayoutLink } from '../../srcTS/panelLayoutLink';
|
||||
// import { PanelLayoutGrid } from '../../srcTS/panelLayoutGrid';
|
||||
|
||||
/* !!
|
||||
import { root } from '../../srcTS/root';
|
||||
import { CommandHandler } from '../../srcTS/commandHandler';
|
||||
import { DraggingTool } from '../../srcTS/draggingTool';
|
||||
import { RelinkingTool, LinkingTool, LinkingBaseTool } from '../../srcTS/linkingTools';
|
||||
import { LinkReshapingTool } from '../../srcTS/linkReshapingTool';
|
||||
import { ResizingTool } from '../../srcTS/resizingTool';
|
||||
import { RotatingTool } from '../../srcTS/rotatingTool';
|
||||
import { PanningTool, DragSelectingTool, ClickCreatingTool, ActionTool, ClickSelectingTool } from '../../srcTS/selectingTools';
|
||||
import { TextEditingTool } from '../../srcTS/textEditingTool';
|
||||
import { ContextMenuTool } from '../../srcTS/contextMenuTool';
|
||||
|
||||
import { GridLayout } from '../../srcTS/gridLayout';
|
||||
import { CircularEdge, CircularVertex, CircularNetwork, CircularLayout } from '../../srcTS/circularLayout';
|
||||
import { ForceDirectedEdge, ForceDirectedVertex, ForceDirectedNetwork, ForceDirectedLayout } from '../../srcTS/forceDirectedLayout';
|
||||
import { LayeredDigraphEdge, LayeredDigraphVertex, LayeredDigraphNetwork, LayeredDigraphLayout } from '../../srcTS/layeredDigraphLayout';
|
||||
import { TreeEdge, TreeVertex, TreeNetwork, TreeLayout } from '../../srcTS/treeLayout';
|
||||
|
||||
import { SVGSurface } from '../../srcTS/svgContext';
|
||||
|
||||
import { PanelLayoutPosition } from '../../srcTS/panelLayoutPosition';
|
||||
import { PanelLayoutHorizontal } from '../../srcTS/panelLayoutHorizontal';
|
||||
import { PanelLayoutVertical } from '../../srcTS/panelLayoutVertical';
|
||||
import { PanelLayoutSpot } from '../../srcTS/panelLayoutSpot';
|
||||
import { PanelLayoutAuto } from '../../srcTS/panelLayoutAuto';
|
||||
import { PanelLayoutTable, PanelLayoutTableRow, PanelLayoutTableColumn } from '../../srcTS/panelLayoutTable';
|
||||
import { PanelLayoutViewbox } from '../../srcTS/panelLayoutViewbox';
|
||||
import { PanelLayoutLink } from '../../srcTS/panelLayoutLink';
|
||||
import { PanelLayoutGrid } from '../../srcTS/panelLayoutGrid';
|
||||
import { PanelLayoutGraduated } from '../../srcTS/panelLayoutGraduated';
|
||||
*/
|
||||
|
||||
/* !!
|
||||
ToolManager.prototype.initializeStandardTools = function(this: ToolManager): void {
|
||||
// For each of these lists, the tools need to be assigned in a specific order,
|
||||
// so that they are added to the list in the correct order.
|
||||
|
||||
// mouse-down tools:
|
||||
this.replaceStandardTool('Action', new ActionTool(), this.mouseDownTools);
|
||||
this.replaceStandardTool('Relinking', new RelinkingTool(), this.mouseDownTools);
|
||||
this.replaceStandardTool('LinkReshaping', new LinkReshapingTool(), this.mouseDownTools);
|
||||
this.replaceStandardTool('Resizing', new ResizingTool(), this.mouseDownTools);
|
||||
this.replaceStandardTool('Rotating', new RotatingTool(), this.mouseDownTools);
|
||||
|
||||
// mouse-move tools:
|
||||
this.replaceStandardTool('Linking', new LinkingTool(), this.mouseMoveTools);
|
||||
this.replaceStandardTool('Dragging', new DraggingTool(), this.mouseMoveTools);
|
||||
this.replaceStandardTool('DragSelecting', new DragSelectingTool(), this.mouseMoveTools);
|
||||
this.replaceStandardTool('Panning', new PanningTool(), this.mouseMoveTools);
|
||||
|
||||
// mouse-up tools:
|
||||
this.replaceStandardTool('ContextMenu', new ContextMenuTool(), this.mouseUpTools);
|
||||
this.replaceStandardTool('TextEditing', new TextEditingTool(), this.mouseUpTools);
|
||||
this.replaceStandardTool('ClickCreating', new ClickCreatingTool(), this.mouseUpTools);
|
||||
this.replaceStandardTool('ClickSelecting', new ClickSelectingTool(), this.mouseUpTools);
|
||||
};
|
||||
|
||||
Diagram.prototype.initializeStandardRenderers = function(this: Diagram): void {
|
||||
this.addRenderer('SVG', new SVGSurface(this, root.document));
|
||||
};
|
||||
|
||||
Panel.addPanelLayout('Position', new PanelLayoutPosition());
|
||||
Panel.addPanelLayout('Horizontal', new PanelLayoutHorizontal());
|
||||
Panel.addPanelLayout('Vertical', new PanelLayoutVertical());
|
||||
Panel.addPanelLayout('Spot', new PanelLayoutSpot());
|
||||
Panel.addPanelLayout('Auto', new PanelLayoutAuto());
|
||||
Panel.addPanelLayout('Table', new PanelLayoutTable());
|
||||
Panel.addPanelLayout('Viewbox', new PanelLayoutViewbox());
|
||||
Panel.addPanelLayout('TableRow', new PanelLayoutTableRow());
|
||||
Panel.addPanelLayout('TableColumn', new PanelLayoutTableColumn());
|
||||
Panel.addPanelLayout('Link', new PanelLayoutLink());
|
||||
Panel.addPanelLayout('Grid', new PanelLayoutGrid());
|
||||
Panel.addPanelLayout('Graduated', new PanelLayoutGraduated());
|
||||
*/
|
||||
|
||||
export const go = {
|
||||
get licenseKey() {
|
||||
return Diagram.licenseKey;
|
||||
},
|
||||
set licenseKey (licx) {
|
||||
Diagram.licenseKey = licx;
|
||||
},
|
||||
get version() {
|
||||
return Diagram.version;
|
||||
},
|
||||
|
||||
'Group': Group,
|
||||
'EnumValue': EnumValue,
|
||||
'List': List,
|
||||
'Set': Set,
|
||||
'Map': Map,
|
||||
'Point': Point,
|
||||
'Size': Size,
|
||||
'Rect': Rect,
|
||||
'Margin': Margin,
|
||||
'Spot': Spot,
|
||||
'Geometry': Geometry,
|
||||
'PathFigure': PathFigure,
|
||||
'PathSegment': PathSegment,
|
||||
'InputEvent': InputEvent,
|
||||
'DiagramEvent': DiagramEvent,
|
||||
'ChangedEvent': ChangedEvent,
|
||||
'Model': Model,
|
||||
'Binding': Binding,
|
||||
'Transaction': Transaction,
|
||||
'UndoManager': UndoManager,
|
||||
'Tool': Tool,
|
||||
'ToolManager': ToolManager,
|
||||
'AnimationManager': AnimationManager,
|
||||
'Layer': Layer,
|
||||
'Diagram': Diagram,
|
||||
'Brush': Brush,
|
||||
'GraphObject': GraphObject,
|
||||
'Panel': Panel,
|
||||
'RowColumnDefinition': RowColumnDefinition,
|
||||
'Shape': Shape,
|
||||
'TextBlock': TextBlock,
|
||||
'TextBlockMetrics': TextBlockMetrics,
|
||||
'Picture': Picture,
|
||||
'Part': Part,
|
||||
'Adornment': Adornment,
|
||||
'Node': Node,
|
||||
'Link': Link,
|
||||
'Placeholder': Placeholder,
|
||||
'Layout': Layout,
|
||||
'LayoutNetwork': LayoutNetwork,
|
||||
'LayoutVertex': LayoutVertex,
|
||||
'LayoutEdge': LayoutEdge
|
||||
|
||||
/* !!
|
||||
'DraggingTool': DraggingTool,
|
||||
'DraggingInfo': DraggingInfo,
|
||||
'LinkingBaseTool': LinkingBaseTool,
|
||||
'LinkingTool': LinkingTool,
|
||||
'RelinkingTool': RelinkingTool,
|
||||
'LinkReshapingTool': LinkReshapingTool,
|
||||
'ResizingTool': ResizingTool,
|
||||
'RotatingTool': RotatingTool,
|
||||
'ClickSelectingTool': ClickSelectingTool,
|
||||
'ActionTool': ActionTool,
|
||||
'ClickCreatingTool': ClickCreatingTool,
|
||||
'HTMLInfo': HTMLInfo,
|
||||
'ContextMenuTool': ContextMenuTool,
|
||||
'DragSelectingTool': DragSelectingTool,
|
||||
'PanningTool': PanningTool,
|
||||
'TextEditingTool': TextEditingTool,
|
||||
|
||||
'GraphLinksModel': GraphLinksModel,
|
||||
'TreeModel': TreeModel,
|
||||
'CommandHandler': CommandHandler,
|
||||
'Palette': Palette,
|
||||
'Overview': Overview,
|
||||
'GridLayout': GridLayout,
|
||||
'CircularLayout': CircularLayout,
|
||||
'CircularNetwork': CircularNetwork,
|
||||
'CircularVertex': CircularVertex,
|
||||
'CircularEdge': CircularEdge,
|
||||
'ForceDirectedLayout': ForceDirectedLayout,
|
||||
'ForceDirectedNetwork': ForceDirectedNetwork,
|
||||
'ForceDirectedVertex': ForceDirectedVertex,
|
||||
'ForceDirectedEdge': ForceDirectedEdge,
|
||||
'LayeredDigraphLayout': LayeredDigraphLayout,
|
||||
'LayeredDigraphNetwork': LayeredDigraphNetwork,
|
||||
'LayeredDigraphVertex': LayeredDigraphVertex,
|
||||
'LayeredDigraphEdge': LayeredDigraphEdge,
|
||||
'TreeLayout': TreeLayout,
|
||||
'TreeNetwork': TreeNetwork,
|
||||
'TreeVertex': TreeVertex,
|
||||
'TreeEdge': TreeEdge
|
||||
*/
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Webpack Minimal GoJS Sample</title>
|
||||
<meta name="description" content="An almost minimal diagram using a very simple node template and the default link template." />
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<script src="minimal.js"></script>
|
||||
|
||||
<script>
|
||||
window.onload = function() {
|
||||
// window.init() was created in minimal.js, which was built from minimal.ts with webpack. See readme for details.
|
||||
init();
|
||||
}
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="sample">
|
||||
<!-- The DIV for the Diagram needs an explicit size or else we won't see anything.
|
||||
This also adds a border to help see the edges of the viewport. -->
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:400px; height:400px"></div>
|
||||
|
||||
<p>
|
||||
This sample uses a JavaScript file that is built with Webpack. If you do not see a diagram, you may need to build first.
|
||||
See the <a href="./readme.md">readme.md in this directory</a> for details.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This minimal sample contains as few GoJS modules as possible when building from the source.
|
||||
As a result, there is almost no interactivity possible, as no tools or the CommandHandler exist.
|
||||
There are also no Layouts (except the base Layout), Overviews, or Palettes.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If you look at the <a href="./minimal-index.ts">minimal-index.ts</a> you will see what is dis-included.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
For an example of building GoJS from source with all modules, see the <a href="../maximalSource/maximal.html">Maximal project</a> adjacent to this directory.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
The code in this file is typical of a "minimal" sample, except it uses Model instead of GraphLinksModel.
|
||||
|
||||
The code which dis-includes unused modules can be found in minimal-index.ts
|
||||
*/
|
||||
|
||||
import { go } from './minimal-index';
|
||||
|
||||
window.init = function() {
|
||||
const $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
|
||||
const myDiagram = $(go.Diagram, 'myDiagramDiv', // create a Diagram for the DIV HTML element
|
||||
{
|
||||
'undoManager.isEnabled': true // enable undo & redo
|
||||
});
|
||||
|
||||
// define a simple Node template
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto', // the Shape will go around the TextBlock
|
||||
{ rotatable: true },
|
||||
$(go.Shape, 'RoundedRectangle', { strokeWidth: 0 },
|
||||
// Shape.fill is bound to Node.data.color
|
||||
new go.Binding('fill', 'color')),
|
||||
$(go.TextBlock,
|
||||
{ margin: 8 }, // some room around the text
|
||||
// TextBlock.text is bound to Node.data.key
|
||||
new go.Binding('text', 'key'))
|
||||
);
|
||||
|
||||
// but use the default Link template, by not setting Diagram.linkTemplate
|
||||
|
||||
myDiagram.model = new go.Model(
|
||||
[
|
||||
{ key: 'Alpha', color: 'lightblue' },
|
||||
{ key: 'Beta', color: 'orange' },
|
||||
{ key: 'Gamma', color: 'lightgreen' },
|
||||
{ key: 'Delta', color: 'pink' }
|
||||
]);
|
||||
|
||||
// Attach to the window so you can manipulate in the console
|
||||
(window as any).myDiagram = myDiagram;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "minimalsource",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"ts-loader": "^8.0.2",
|
||||
"typescript": "^3.9.7",
|
||||
"webpack": "^4.44.1",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-closure-compiler": "^2.1.6"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"author": "Northwoods Software"
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
This demonstrates how to build a diagram with GoJS source, using webpack.
|
||||
|
||||
Because Webpack is able to shake out all functionality not used, this Diagram is truly minimal, and the compiled JS contains no layouts, no GoJS Tools for interactivity, no TreeModel or GraphLinksModel, no SVG rendering capability,
|
||||
|
||||
There are many related projects in this directory:
|
||||
|
||||
* **minimalSource**, for an example of building GoJS while excluding as many modules as possible. This project uses webpack's tree shaking to remove unused modules.
|
||||
* **maximalSource**, for an example of building GoJS while including every module.
|
||||
* **minimalSourceBrowserify**, for another minimal example of building GoJS with Browserify. Also does not use `require`.
|
||||
|
||||
With npm, install the dependencies:
|
||||
|
||||
```
|
||||
$ npm install
|
||||
```
|
||||
|
||||
You may also need to install `webpack` globally. Run script with:
|
||||
|
||||
```
|
||||
$ webpack
|
||||
```
|
||||
|
||||
This creates `minimal.js`, which is referenced in `minimal.html`.
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"strictNullChecks": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
const path = require('path');
|
||||
const ClosureCompilerPlugin = require('webpack-closure-compiler');
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
entry: path.resolve(__dirname, './minimal.ts'),
|
||||
output: {
|
||||
path: path.resolve(__dirname, './'),
|
||||
library: 'minimal.js',
|
||||
libraryTarget: 'umd',
|
||||
filename: 'minimal.js'
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js']
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
// files with `.ts` or `.tsx` extension will be handled by `ts-loader`
|
||||
{ test: /\.tsx?$/, loader: 'ts-loader' }
|
||||
]
|
||||
},
|
||||
stats: 'errors-only',
|
||||
plugins: [
|
||||
new ClosureCompilerPlugin({
|
||||
compiler: {
|
||||
language_out: 'ECMASCRIPT5',
|
||||
process_common_js_modules: true,
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
var browserify = require('browserify');
|
||||
var tsify = require('tsify');
|
||||
var uglifyify = require('uglifyify');
|
||||
var fs = require('fs');
|
||||
var replace = require('stream-replace');
|
||||
|
||||
browserify()
|
||||
.add('minimal.ts')
|
||||
.plugin(tsify, { noImplicitAny: true })
|
||||
.transform(uglifyify, { global: true })
|
||||
.bundle()
|
||||
.on('error', function (error) { console.error(error.toString()); })
|
||||
.pipe(replace(/\.\.\/\.\.\/srcTS\//g, '')) // cleanup
|
||||
.pipe(fs.createWriteStream('minimal.js'));
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Removable modules are commented out in this file with "!!" in the comment.
|
||||
* This index demonstrates building a minimal library from source. There is a maximal example in the same directory.
|
||||
*/
|
||||
|
||||
/* tslint:disable:ordered-imports */
|
||||
import { EnumValue } from '../../srcTS/enumValue';
|
||||
import { Map, Set, List } from '../../srcTS/collections';
|
||||
import { Point } from '../../srcTS/point';
|
||||
import { Size } from '../../srcTS/size';
|
||||
import { Rect } from '../../srcTS/rect';
|
||||
import { Margin } from '../../srcTS/margin';
|
||||
import { Spot } from '../../srcTS/spot';
|
||||
import { PathSegment, PathFigure, Geometry } from '../../srcTS/geometry';
|
||||
import { DiagramEvent, InputEvent } from '../../srcTS/inputEvent';
|
||||
import { ChangedEvent } from '../../srcTS/changedEvent';
|
||||
import { Binding, Model } from '../../srcTS/model';
|
||||
import { UndoManager, Transaction } from '../../srcTS/undoManager';
|
||||
import { Tool } from '../../srcTS/tool';
|
||||
import { ToolManager } from '../../srcTS/toolManager';
|
||||
import { AnimationManager } from '../../srcTS/animationManager';
|
||||
import { Layer } from '../../srcTS/layer';
|
||||
import { Diagram } from '../../srcTS/diagram';
|
||||
import { Brush } from '../../srcTS/brush';
|
||||
import { GraphObject } from '../../srcTS/graphObject';
|
||||
import { Panel } from '../../srcTS/panel';
|
||||
import { RowColumnDefinition } from '../../srcTS/rowColumnDefinition';
|
||||
import { Shape } from '../../srcTS/shape';
|
||||
import { TextBlock, TextBlockMetrics } from '../../srcTS/textBlock';
|
||||
import { Picture } from '../../srcTS/picture';
|
||||
import { Adornment, Part } from '../../srcTS/parts';
|
||||
import { Node } from '../../srcTS/parts';
|
||||
import { Link } from '../../srcTS/parts';
|
||||
import { Group, Placeholder } from '../../srcTS/parts';
|
||||
import { LayoutEdge, LayoutVertex, LayoutNetwork, Layout } from '../../srcTS/layout';
|
||||
|
||||
|
||||
// Already imported in Panel
|
||||
// import { PanelLayoutPosition } from '../../srcTS/panelLayoutPosition';
|
||||
// import { PanelLayoutVertical } from '../../srcTS/panelLayoutVertical';
|
||||
// import { PanelLayoutAuto } from '../../srcTS/panelLayoutAuto';
|
||||
// import { PanelLayoutLink } from '../../srcTS/panelLayoutLink';
|
||||
// import { PanelLayoutGrid } from '../../srcTS/panelLayoutGrid';
|
||||
|
||||
/* !!
|
||||
import { root } from '../../srcTS/root';
|
||||
import { CommandHandler } from '../../srcTS/commandHandler';
|
||||
import { DraggingTool } from '../../srcTS/draggingTool';
|
||||
import { RelinkingTool, LinkingTool, LinkingBaseTool } from '../../srcTS/linkingTools';
|
||||
import { LinkReshapingTool } from '../../srcTS/linkReshapingTool';
|
||||
import { ResizingTool } from '../../srcTS/resizingTool';
|
||||
import { RotatingTool } from '../../srcTS/rotatingTool';
|
||||
import { PanningTool, DragSelectingTool, ClickCreatingTool, ActionTool, ClickSelectingTool } from '../../srcTS/selectingTools';
|
||||
import { TextEditingTool } from '../../srcTS/textEditingTool';
|
||||
import { ContextMenuTool } from '../../srcTS/contextMenuTool';
|
||||
|
||||
import { GridLayout } from '../../srcTS/gridLayout';
|
||||
import { CircularEdge, CircularVertex, CircularNetwork, CircularLayout } from '../../srcTS/circularLayout';
|
||||
import { ForceDirectedEdge, ForceDirectedVertex, ForceDirectedNetwork, ForceDirectedLayout } from '../../srcTS/forceDirectedLayout';
|
||||
import { LayeredDigraphEdge, LayeredDigraphVertex, LayeredDigraphNetwork, LayeredDigraphLayout } from '../../srcTS/layeredDigraphLayout';
|
||||
import { TreeEdge, TreeVertex, TreeNetwork, TreeLayout } from '../../srcTS/treeLayout';
|
||||
|
||||
import { SVGSurface } from '../../srcTS/svgContext';
|
||||
|
||||
import { PanelLayoutPosition } from '../../srcTS/panelLayoutPosition';
|
||||
import { PanelLayoutHorizontal } from '../../srcTS/panelLayoutHorizontal';
|
||||
import { PanelLayoutVertical } from '../../srcTS/panelLayoutVertical';
|
||||
import { PanelLayoutSpot } from '../../srcTS/panelLayoutSpot';
|
||||
import { PanelLayoutAuto } from '../../srcTS/panelLayoutAuto';
|
||||
import { PanelLayoutTable, PanelLayoutTableRow, PanelLayoutTableColumn } from '../../srcTS/panelLayoutTable';
|
||||
import { PanelLayoutViewbox } from '../../srcTS/panelLayoutViewbox';
|
||||
import { PanelLayoutLink } from '../../srcTS/panelLayoutLink';
|
||||
import { PanelLayoutGrid } from '../../srcTS/panelLayoutGrid';
|
||||
import { PanelLayoutGraduated } from '../../srcTS/panelLayoutGraduated';
|
||||
*/
|
||||
|
||||
/* !!
|
||||
ToolManager.prototype.initializeStandardTools = function(this: ToolManager): void {
|
||||
// For each of these lists, the tools need to be assigned in a specific order,
|
||||
// so that they are added to the list in the correct order.
|
||||
|
||||
// mouse-down tools:
|
||||
this.replaceStandardTool('Action', new ActionTool(), this.mouseDownTools);
|
||||
this.replaceStandardTool('Relinking', new RelinkingTool(), this.mouseDownTools);
|
||||
this.replaceStandardTool('LinkReshaping', new LinkReshapingTool(), this.mouseDownTools);
|
||||
this.replaceStandardTool('Resizing', new ResizingTool(), this.mouseDownTools);
|
||||
this.replaceStandardTool('Rotating', new RotatingTool(), this.mouseDownTools);
|
||||
|
||||
// mouse-move tools:
|
||||
this.replaceStandardTool('Linking', new LinkingTool(), this.mouseMoveTools);
|
||||
this.replaceStandardTool('Dragging', new DraggingTool(), this.mouseMoveTools);
|
||||
this.replaceStandardTool('DragSelecting', new DragSelectingTool(), this.mouseMoveTools);
|
||||
this.replaceStandardTool('Panning', new PanningTool(), this.mouseMoveTools);
|
||||
|
||||
// mouse-up tools:
|
||||
this.replaceStandardTool('ContextMenu', new ContextMenuTool(), this.mouseUpTools);
|
||||
this.replaceStandardTool('TextEditing', new TextEditingTool(), this.mouseUpTools);
|
||||
this.replaceStandardTool('ClickCreating', new ClickCreatingTool(), this.mouseUpTools);
|
||||
this.replaceStandardTool('ClickSelecting', new ClickSelectingTool(), this.mouseUpTools);
|
||||
};
|
||||
|
||||
Diagram.prototype.initializeStandardRenderers = function(this: Diagram): void {
|
||||
this.addRenderer('SVG', new SVGSurface(this, root.document));
|
||||
};
|
||||
|
||||
Panel.addPanelLayout('Position', new PanelLayoutPosition());
|
||||
Panel.addPanelLayout('Horizontal', new PanelLayoutHorizontal());
|
||||
Panel.addPanelLayout('Vertical', new PanelLayoutVertical());
|
||||
Panel.addPanelLayout('Spot', new PanelLayoutSpot());
|
||||
Panel.addPanelLayout('Auto', new PanelLayoutAuto());
|
||||
Panel.addPanelLayout('Table', new PanelLayoutTable());
|
||||
Panel.addPanelLayout('Viewbox', new PanelLayoutViewbox());
|
||||
Panel.addPanelLayout('TableRow', new PanelLayoutTableRow());
|
||||
Panel.addPanelLayout('TableColumn', new PanelLayoutTableColumn());
|
||||
Panel.addPanelLayout('Link', new PanelLayoutLink());
|
||||
Panel.addPanelLayout('Grid', new PanelLayoutGrid());
|
||||
Panel.addPanelLayout('Graduated', new PanelLayoutGraduated());
|
||||
*/
|
||||
|
||||
export const go = {
|
||||
get licenseKey() {
|
||||
return Diagram.licenseKey;
|
||||
},
|
||||
set licenseKey (licx) {
|
||||
Diagram.licenseKey = licx;
|
||||
},
|
||||
get version() {
|
||||
return Diagram.version;
|
||||
},
|
||||
|
||||
'Group': Group,
|
||||
'EnumValue': EnumValue,
|
||||
'List': List,
|
||||
'Set': Set,
|
||||
'Map': Map,
|
||||
'Point': Point,
|
||||
'Size': Size,
|
||||
'Rect': Rect,
|
||||
'Margin': Margin,
|
||||
'Spot': Spot,
|
||||
'Geometry': Geometry,
|
||||
'PathFigure': PathFigure,
|
||||
'PathSegment': PathSegment,
|
||||
'InputEvent': InputEvent,
|
||||
'DiagramEvent': DiagramEvent,
|
||||
'ChangedEvent': ChangedEvent,
|
||||
'Model': Model,
|
||||
'Binding': Binding,
|
||||
'Transaction': Transaction,
|
||||
'UndoManager': UndoManager,
|
||||
'Tool': Tool,
|
||||
'ToolManager': ToolManager,
|
||||
'AnimationManager': AnimationManager,
|
||||
'Layer': Layer,
|
||||
'Diagram': Diagram,
|
||||
'Brush': Brush,
|
||||
'GraphObject': GraphObject,
|
||||
'Panel': Panel,
|
||||
'RowColumnDefinition': RowColumnDefinition,
|
||||
'Shape': Shape,
|
||||
'TextBlock': TextBlock,
|
||||
'TextBlockMetrics': TextBlockMetrics,
|
||||
'Picture': Picture,
|
||||
'Part': Part,
|
||||
'Adornment': Adornment,
|
||||
'Node': Node,
|
||||
'Link': Link,
|
||||
'Placeholder': Placeholder,
|
||||
'Layout': Layout,
|
||||
'LayoutNetwork': LayoutNetwork,
|
||||
'LayoutVertex': LayoutVertex,
|
||||
'LayoutEdge': LayoutEdge
|
||||
|
||||
/* !!
|
||||
'DraggingTool': DraggingTool,
|
||||
'DraggingInfo': DraggingInfo,
|
||||
'LinkingBaseTool': LinkingBaseTool,
|
||||
'LinkingTool': LinkingTool,
|
||||
'RelinkingTool': RelinkingTool,
|
||||
'LinkReshapingTool': LinkReshapingTool,
|
||||
'ResizingTool': ResizingTool,
|
||||
'RotatingTool': RotatingTool,
|
||||
'ClickSelectingTool': ClickSelectingTool,
|
||||
'ActionTool': ActionTool,
|
||||
'ClickCreatingTool': ClickCreatingTool,
|
||||
'HTMLInfo': HTMLInfo,
|
||||
'ContextMenuTool': ContextMenuTool,
|
||||
'DragSelectingTool': DragSelectingTool,
|
||||
'PanningTool': PanningTool,
|
||||
'TextEditingTool': TextEditingTool,
|
||||
|
||||
'GraphLinksModel': GraphLinksModel,
|
||||
'TreeModel': TreeModel,
|
||||
'CommandHandler': CommandHandler,
|
||||
'Palette': Palette,
|
||||
'Overview': Overview,
|
||||
'GridLayout': GridLayout,
|
||||
'CircularLayout': CircularLayout,
|
||||
'CircularNetwork': CircularNetwork,
|
||||
'CircularVertex': CircularVertex,
|
||||
'CircularEdge': CircularEdge,
|
||||
'ForceDirectedLayout': ForceDirectedLayout,
|
||||
'ForceDirectedNetwork': ForceDirectedNetwork,
|
||||
'ForceDirectedVertex': ForceDirectedVertex,
|
||||
'ForceDirectedEdge': ForceDirectedEdge,
|
||||
'LayeredDigraphLayout': LayeredDigraphLayout,
|
||||
'LayeredDigraphNetwork': LayeredDigraphNetwork,
|
||||
'LayeredDigraphVertex': LayeredDigraphVertex,
|
||||
'LayeredDigraphEdge': LayeredDigraphEdge,
|
||||
'TreeLayout': TreeLayout,
|
||||
'TreeNetwork': TreeNetwork,
|
||||
'TreeVertex': TreeVertex,
|
||||
'TreeEdge': TreeEdge
|
||||
*/
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Browserify Minimal GoJS Sample</title>
|
||||
<meta name="description" content="An almost minimal diagram using a very simple node template and the default link template." />
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<script src="minimal.js"></script>
|
||||
|
||||
<script>
|
||||
window.onload = function() {
|
||||
// window.init() was created in minimal.js, which was built from minimal.ts with browserify. See readme for details.
|
||||
init();
|
||||
}
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="sample">
|
||||
<!-- The DIV for the Diagram needs an explicit size or else we won't see anything.
|
||||
This also adds a border to help see the edges of the viewport. -->
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:400px; height:400px"></div>
|
||||
|
||||
<p>
|
||||
This sample uses a JavaScript file that is built with Browserify. If you do not see a diagram, you may need to build first.
|
||||
See the <a href="./readme.md">readme.md in this directory</a> for details.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This minimal sample contains as few GoJS modules as possible when building from the source.
|
||||
As a result, there is almost no interactivity possible, as no tools or the CommandHandler exist.
|
||||
There are also no Layouts (except the base Layout), Overviews, or Palettes.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If you look at the <a href="./minimal-index.ts">minimal-index.ts</a> you will see what is dis-included.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
For an example of building GoJS from source with all modules, see the <a href="../maximalSource/maximal.html">Maximal project</a> adjacent to this directory.
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
// This is one way of exposing init() to minimal.html. You could also use require, like the other projects do
|
||||
|
||||
import { go } from './minimal-index';
|
||||
|
||||
window.init = function() {
|
||||
const $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
|
||||
const myDiagram = $(go.Diagram, 'myDiagramDiv', // create a Diagram for the DIV HTML element
|
||||
{
|
||||
'undoManager.isEnabled': true // enable undo & redo
|
||||
});
|
||||
|
||||
// define a simple Node template
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto', // the Shape will go around the TextBlock
|
||||
{ rotatable: true },
|
||||
$(go.Shape, 'RoundedRectangle', { strokeWidth: 0 },
|
||||
// Shape.fill is bound to Node.data.color
|
||||
new go.Binding('fill', 'color')),
|
||||
$(go.TextBlock,
|
||||
{ margin: 8 }, // some room around the text
|
||||
// TextBlock.text is bound to Node.data.key
|
||||
new go.Binding('text', 'key'))
|
||||
);
|
||||
|
||||
// but use the default Link template, by not setting Diagram.linkTemplate
|
||||
|
||||
myDiagram.model = new go.Model(
|
||||
[
|
||||
{ key: 'Alpha', color: 'lightblue' },
|
||||
{ key: 'Beta', color: 'orange' },
|
||||
{ key: 'Gamma', color: 'lightgreen' },
|
||||
{ key: 'Delta', color: 'pink' }
|
||||
]);
|
||||
|
||||
// Attach to the window so you can manipulate in the console
|
||||
(window as any).myDiagram = myDiagram;
|
||||
|
||||
|
||||
// Attach to the window so you can manipulate in the console
|
||||
(window as any).myDiagram = myDiagram;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "minimalsourcebrowserify",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"browserify": "^16.5.2",
|
||||
"stream-replace": "^1.0.0",
|
||||
"tsify": "^5.0.0",
|
||||
"typescript": "^3.9.7",
|
||||
"uglifyify": "^5.0.2"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"author": "Northwoods Software"
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
This demonstrates how to build a diagram with GoJS source, using Browserify.
|
||||
|
||||
Because Browserify is able to shake out all functionality not used, this Diagram is truly minimal, and the compiled JS contains no layouts, no GoJS Tools for interactivity, no TreeModel or GraphLinksModel, no SVG rendering capability,
|
||||
|
||||
There are many related projects in this directory:
|
||||
|
||||
* **minimalSource**, for an example of building GoJS while excluding as many modules as possible. This project uses webpack's tree shaking to remove unused modules.
|
||||
* **maximalSource**, for an example of building GoJS while including every module.
|
||||
* **minimalSourceBrowserify**, for another minimal example of building GoJS with Browserify. Also does not use `require`.
|
||||
|
||||
With npm, install the dependencies:
|
||||
|
||||
```
|
||||
$ npm install
|
||||
```
|
||||
|
||||
You may also need to install `Browserify` globally (`npm install --global browserify`). Run the script with:
|
||||
|
||||
```
|
||||
$ node browserify-run.js
|
||||
```
|
||||
|
||||
This creates `minimal.js`, which is referenced in `minimal.html`.
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"strictNullChecks": false // webpack --entry ..\minimalSource\minimal.js --output-filename all.js --display-error-details
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Minimal GoJS Sample Generating PDF</title>
|
||||
<meta name="description" content="A simple demonstration of generating a PDF file in the browser, showing it in the page, and downloading it as a file." />
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta charset="UTF-8">
|
||||
<script src="https://unpkg.com/gojs"></script>
|
||||
<script src="../../samples/assets/require.js"></script>
|
||||
<script id="code">
|
||||
// This just creates and initializes a Diagram.\
|
||||
// The details do not really matter for this demo.
|
||||
function init() {
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
myDiagram = $(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
"undoManager.isEnabled": true,
|
||||
"grid.visible": true
|
||||
});
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
$(go.Shape, "RoundedRectangle",
|
||||
{ strokeWidth: 0, fill: "white" },
|
||||
new go.Binding("fill", "color")),
|
||||
$(go.TextBlock,
|
||||
{ margin: 8 },
|
||||
new go.Binding("text", "key"))
|
||||
);
|
||||
|
||||
myDiagram.model = new go.GraphLinksModel(
|
||||
[
|
||||
{ key: "Alpha", color: "lightblue" },
|
||||
{ key: "Beta", color: "orange" },
|
||||
{ key: "Gamma", color: "lightgreen" },
|
||||
{ key: "Delta", color: "pink" }
|
||||
],
|
||||
[
|
||||
{ from: "Alpha", to: "Beta" },
|
||||
{ from: "Alpha", to: "Gamma" },
|
||||
{ from: "Beta", to: "Beta" },
|
||||
{ from: "Gamma", to: "Delta" },
|
||||
{ from: "Delta", to: "Alpha" }
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
// This common function is called both when showing the PDF in an iframe and when downloading a PDF file.
|
||||
// The options include:
|
||||
// "pageSize", either "A4" or "LETTER" (the default)
|
||||
// "layout", either "portrait" (the default) or "landscape"
|
||||
// "margin" for the uniform page margin on each page (default is 36 pt)
|
||||
// "padding" instead of the Diagram.padding when adjusting the Diagram.documentBounds for the area to render
|
||||
// "imgWidth", size of diagram image for one page; defaults to the page width minus margins
|
||||
// "imgHeight", size of diagram image for one page; defaults to the page height minus margins
|
||||
// "imgResolutionFactor" for how large the image should be scaled when rendered for each page;
|
||||
// larger is better but significantly increases memory usage (default is 3)
|
||||
// "parts", "background", "showTemporary", "showGrid", all are passed to Diagram.makeImageData
|
||||
function generatePdf(action, diagram, options) {
|
||||
if (!(diagram instanceof go.Diagram)) throw new Error("no Diagram provided when calling generatePdf");
|
||||
if (!options) options = {};
|
||||
|
||||
var pageSize = options.pageSize || "LETTER";
|
||||
pageSize = pageSize.toUpperCase();
|
||||
if (pageSize !== "LETTER" && pageSize !== "A4") throw new Error("unknown page size: " + pageSize);
|
||||
// LETTER: 612x792 pt == 816x1056 CSS units
|
||||
// A4: 595.28x841.89 pt == 793.71x1122.52 CSS units
|
||||
var pageWidth = (pageSize === "LETTER" ? 612 : 595.28) * 96 / 72; // convert from pt to CSS units
|
||||
var pageHeight = (pageSize === "LETTER" ? 792 : 841.89) * 96 / 72;
|
||||
|
||||
var layout = options.layout || "portrait";
|
||||
layout = layout.toLowerCase();
|
||||
if (layout !== "portrait" && layout !== "landscape") throw new Error("unknown layout: " + layout);
|
||||
if (layout === "landscape") {
|
||||
var temp = pageWidth;
|
||||
pageWidth = pageHeight;
|
||||
pageHeight = temp;
|
||||
}
|
||||
|
||||
var margin = options.margin !== undefined ? options.margin : 36; // pt: 0.5 inch margin on each side
|
||||
var padding = options.padding !== undefined ? options.padding : diagram.padding; // CSS units
|
||||
|
||||
var imgWidth = options.imgWidth !== undefined ? options.imgWidth : (pageWidth-margin/72*96*2); // CSS units
|
||||
var imgHeight = options.imgHeight !== undefined ? options.imgHeight : (pageHeight-margin/72*96*2); // CSS units
|
||||
var imgResolutionFactor = options.imgResolutionFactor !== undefined ? options.imgResolutionFactor : 3;
|
||||
|
||||
var pageOptions = {
|
||||
size: pageSize,
|
||||
margin: margin, // pt
|
||||
layout: layout
|
||||
};
|
||||
|
||||
require(["blob-stream", "pdfkit"], function(blobStream, PDFDocument) {
|
||||
var doc = new PDFDocument(pageOptions);
|
||||
var stream = doc.pipe(blobStream());
|
||||
var bnds = diagram.documentBounds;
|
||||
|
||||
// add some descriptive text
|
||||
//doc.text(diagram.nodes.count + " nodes, " + diagram.links.count + " links Diagram size: " + bnds.width.toFixed(2) + " x " + bnds.height.toFixed(2));
|
||||
|
||||
var db = diagram.documentBounds.copy().subtractMargin(diagram.padding).addMargin(padding);
|
||||
var p = db.position;
|
||||
// iterate over page areas of document bounds
|
||||
for (var j = 0; j < db.height; j += imgHeight) {
|
||||
for (var i = 0; i < db.width; i += imgWidth) {
|
||||
|
||||
// if any page has no Parts partially or fully in it, skip rendering that page
|
||||
var r = new go.Rect(p.x + i, p.y + j, imgWidth, imgHeight);
|
||||
if (diagram.findPartsIn(r, true, false).count === 0) continue;
|
||||
|
||||
if (i > 0 || j > 0) doc.addPage(pageOptions);
|
||||
|
||||
var makeOptions = {};
|
||||
if (options.parts !== undefined) makeOptions.parts = options.parts;
|
||||
if (options.background !== undefined) makeOptions.background = options.background;
|
||||
if (options.showTemporary !== undefined) makeOptions.showTemporary = options.showTemporary;
|
||||
if (options.showGrid !== undefined) makeOptions.showGrid = options.showGrid;
|
||||
makeOptions.scale = imgResolutionFactor;
|
||||
makeOptions.position = new go.Point(p.x + i, p.y + j);
|
||||
makeOptions.size = new go.Size(imgWidth*imgResolutionFactor, imgHeight*imgResolutionFactor);
|
||||
makeOptions.maxSize = new go.Size(Infinity, Infinity);
|
||||
|
||||
var imgdata = diagram.makeImageData(makeOptions);
|
||||
doc.image(imgdata, { scale: 1/(imgResolutionFactor*96/72) });
|
||||
}
|
||||
}
|
||||
|
||||
doc.end();
|
||||
stream.on('finish', function() { action(stream.toBlob('application/pdf')); });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Two different uses of generatePdf: one shows the PDF document in the page,
|
||||
// the other downloads it as a file and the user specifies where to save it.
|
||||
|
||||
var pdfOptions = // shared by both ways of generating PDF
|
||||
{
|
||||
showTemporary: true, // default is false
|
||||
// layout: "landscape", // instead of "portrait"
|
||||
// pageSize: "A4" // instead of "LETTER"
|
||||
};
|
||||
|
||||
function showPdf() {
|
||||
generatePdf(function(blob) {
|
||||
var datauri = window.URL.createObjectURL(blob);
|
||||
var frame = document.getElementById("myFrame");
|
||||
if (frame) {
|
||||
frame.style.display = "block";
|
||||
frame.src = datauri; // doesn't work in IE 11, but works everywhere else
|
||||
setTimeout(function() { window.URL.revokeObjectURL(datauri); }, 1);
|
||||
}
|
||||
}, myDiagram, pdfOptions);
|
||||
}
|
||||
|
||||
function downloadPdf() {
|
||||
generatePdf(function(blob) {
|
||||
var datauri = window.URL.createObjectURL(blob);
|
||||
var a = document.createElement("a");
|
||||
a.style = "display: none";
|
||||
a.href = datauri;
|
||||
a.download = "myDiagram.pdf";
|
||||
|
||||
if (window.navigator.msSaveBlob !== undefined) { // IE 11 & Edge
|
||||
window.navigator.msSaveBlob(blob, a.download);
|
||||
window.URL.revokeObjectURL(datauri);
|
||||
return;
|
||||
}
|
||||
|
||||
document.body.appendChild(a);
|
||||
requestAnimationFrame(function() {
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(datauri);
|
||||
document.body.removeChild(a);
|
||||
});
|
||||
}, myDiagram, pdfOptions);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:400px; height:400px"></div>
|
||||
<div><button onclick="showPdf()">Show PDF</button> <button onclick="downloadPdf()">Download PDF</button></div>
|
||||
<iframe id="myFrame" style="display:none; width:1000px; height:1000px"></iframe>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "pdf",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "blob-stream.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"blob-stream": "^0.1.3",
|
||||
"gojs": "^2.1.5",
|
||||
"pdfkit": "^0.11.0",
|
||||
"requirejs": "^2.3.6"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
This demonstrates a GoJS diagram rendered by Puppeteer.
|
||||
|
||||
With Node 8 or higher and npm, install puppeteer:
|
||||
```
|
||||
$ npm install puppeteer
|
||||
```
|
||||
|
||||
Run script with:
|
||||
```
|
||||
$ node puppet.js
|
||||
```
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Northwoods Software",
|
||||
"dependencies": {
|
||||
"puppeteer": "^5.2.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
// This example loads a web page with a GoJS diagram,
|
||||
// then creates a screenshot of the Diagram with makeImageData, plus a screenshot of the page.
|
||||
|
||||
const puppeteer = require('puppeteer');
|
||||
const fs = require('fs');
|
||||
|
||||
const parseDataUrl = (dataUrl) => {
|
||||
const matches = dataUrl.match(/^data:(.+);base64,(.+)$/);
|
||||
if (matches.length !== 3) {
|
||||
throw new Error('Could not parse data URL.');
|
||||
}
|
||||
return { mime: matches[1], buffer: Buffer.from(matches[2], 'base64') };
|
||||
};
|
||||
|
||||
(async () => {
|
||||
const browser = await puppeteer.launch();
|
||||
const page = await browser.newPage();
|
||||
// This does not have to be a page on the web, it can be a localhost page, or file://
|
||||
await page.goto('https://gojs.net/samples/orgChartEditor.html', {
|
||||
waitUntil: 'networkidle2' // ensures images are loaded
|
||||
});
|
||||
|
||||
|
||||
const imageData = await page.evaluate(() => {
|
||||
window.myDiagram.animationManager.stopAnimation();
|
||||
|
||||
return window.myDiagram.makeImageData();
|
||||
});
|
||||
|
||||
// Output the GoJS makeImageData as a .png:
|
||||
const { buffer } = parseDataUrl(imageData);
|
||||
fs.writeFileSync('gojs-screenshot.png', buffer, 'base64');
|
||||
|
||||
// Output a page screenshot
|
||||
await page.screenshot({ path: 'page-screenshot.png' });
|
||||
await browser.close();
|
||||
})();
|
||||