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: 新闻自动采集脚本
This commit is contained in:
li
2026-03-30 20:16:32 +08:00
commit 1b24994e74
6721 changed files with 1308571 additions and 0 deletions
@@ -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;
}
+718
View File
@@ -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();
}
+287
View File
@@ -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;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 742 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+512
View File
@@ -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>
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -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); }
File diff suppressed because it is too large Load Diff
+47
View File
@@ -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"
}
File diff suppressed because it is too large Load Diff
@@ -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;
File diff suppressed because it is too large Load Diff
@@ -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
};
+15
View File
@@ -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 })
]
};