Initial commit
This commit is contained in:
commit
54d448d75d
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/dist
|
||||
/node_modules
|
45
demo-extensions.html
Normal file
45
demo-extensions.html
Normal file
@ -0,0 +1,45 @@
|
||||
<!doctype html>
|
||||
<html lang="en-GB">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="content-type" content="text/html" charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="" />
|
||||
<meta name="keyword" content="" />
|
||||
|
||||
<script src="dist/extensions.js"></script>
|
||||
<!-- <script src="bsdialog4.min.js"></script> -->
|
||||
<title></title>
|
||||
|
||||
<style>
|
||||
|
||||
.text-sm {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
var dataSet = [
|
||||
{ key: "b", value: "B2", num: 2, f: 2.2, date: new Date(2001, 2, 2) },
|
||||
{ key: "d", value: "D4", num: 4, f: 4.4, date: new Date(2001, 4, 4) },
|
||||
{ key: "a", value: "A1", num: 1, f: 1.1, date: new Date(2001, 10, 1) },
|
||||
{ key: "e", value: "E5", num: 5, f: 5.5, date: new Date(2001, 5, 5) },
|
||||
{ key: "c", value: "C3", num: 3, f: 3.3, date: new Date(2001, 3, 3) }
|
||||
];
|
||||
|
||||
dataSet = dataSet.orderByDesc("date");
|
||||
|
||||
// console.log(result);
|
||||
console.log(dataSet);
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
80
demo-project.css
Normal file
80
demo-project.css
Normal file
@ -0,0 +1,80 @@
|
||||
.ryz-project {
|
||||
border-spacing: 0px;
|
||||
border-collapse: separate;
|
||||
cursor: default;
|
||||
padding: 0px 0px 30px 0px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ryz-project .b {
|
||||
font-weight: bold;
|
||||
}
|
||||
.ryz-project .c {
|
||||
text-align: center;
|
||||
}
|
||||
.ryz-project .i {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.ryz-project thead tr {
|
||||
font-weight: bold;
|
||||
font-size: 0.8em;
|
||||
user-select: none;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.ryz-project thead tr th {
|
||||
background-color: #E1E1E1;
|
||||
border-color: #B8B8B8;
|
||||
border-style: solid;
|
||||
border-width: 0px 0px 1px 1px;
|
||||
color: #3E7138;
|
||||
margin: 0px;
|
||||
padding: 6px 10px 5px 10px;
|
||||
min-width: 20px;
|
||||
}
|
||||
.ryz-project thead tr th:first-child {
|
||||
background-color: inherit;
|
||||
border-left-width: 0px;
|
||||
}
|
||||
.ryz-project thead tr th:last-child {
|
||||
border-right-width: 1px;
|
||||
}
|
||||
|
||||
.ryz-project tbody tr td {
|
||||
border-color: #B8B8B8;
|
||||
border-style: solid;
|
||||
border-width: 0px 0px 1px 0px;
|
||||
margin: 0px;
|
||||
overflow: hidden;
|
||||
padding: 5px 5px 5px 5px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ryz-project tbody tr td:first-child {
|
||||
border-right-width: 1px;
|
||||
color: #7E7E7E;
|
||||
font-size: 0.8em;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
}
|
||||
.ryz-project tbody tr td:last-child {
|
||||
border-right-width: 1px;
|
||||
}
|
||||
|
||||
.ryz-project tbody tr:hover td:first-child {
|
||||
background-color: #D1F2C7;
|
||||
color: #3E7138;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.border {
|
||||
border-color: #B8B8B8;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
}
|
178
demo-project.html
Normal file
178
demo-project.html
Normal file
@ -0,0 +1,178 @@
|
||||
<!doctype html>
|
||||
<html lang="en-GB">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="content-type" content="text/html" charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="" />
|
||||
<meta name="keyword" content="" />
|
||||
|
||||
<script src="dist/extensions.js"></script>
|
||||
<!-- <script src="build/ryzproj.min.js"></script> -->
|
||||
|
||||
<script src="dist/project.js"></script>
|
||||
<!-- <script src="project/task-grid.js"></script> -->
|
||||
<!-- <script src="project/gantt-chart.js"></script> -->
|
||||
<link href="demo-project.css" rel="stylesheet" />
|
||||
|
||||
<title></title>
|
||||
|
||||
<style>
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
font-size: 10pt;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.row {
|
||||
/* display: flex; */
|
||||
}
|
||||
|
||||
.row:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.column {
|
||||
/* flex: 50%; */
|
||||
float: left;
|
||||
/* width: 50%; */
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="row">
|
||||
<div class="column" id="taskGrid1" style="width:800px">
|
||||
|
||||
</div>
|
||||
<div class="column" style="padding-left: 3px; width: calc(100% - 800px - 3px);">
|
||||
|
||||
<!-- <div style="width:100%; height: 100%; overflow: scroll;"> -->
|
||||
<div id="ganttChart1"></div>
|
||||
<!-- </div> -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
Document.ready(async function() {
|
||||
var project1 = new LiteRyzJS.Project({ Name: "New Project 1" });
|
||||
var taskGrid1 = null;
|
||||
var ganttChart1 = null;
|
||||
|
||||
|
||||
project1.AddTask({
|
||||
ID: 1,
|
||||
Name: "Task A",
|
||||
StartDelay: 0,
|
||||
Duration: Math.randomN(0, 28),
|
||||
PredecessorTaskID: null,
|
||||
IsCollated: false,
|
||||
CollatedTaskID: null
|
||||
});
|
||||
|
||||
project1.AddTask({
|
||||
ID: 2,
|
||||
Name: "Task B",
|
||||
StartDelay: Math.randomN(0, 5),
|
||||
Duration: Math.randomN(0, 28),
|
||||
PredecessorTaskID: 1,
|
||||
IsCollated: false,
|
||||
CollatedTaskID: null
|
||||
});
|
||||
|
||||
project1.AddTask({
|
||||
ID: 5,
|
||||
Name: "Task B1",
|
||||
StartDelay: Math.randomN(0, 5),
|
||||
Duration: Math.randomN(0, 28),
|
||||
PredecessorTaskID: 2,
|
||||
IsCollated: true,
|
||||
CollatedTaskID: null
|
||||
});
|
||||
|
||||
project1.AddTask({
|
||||
ID: 6,
|
||||
Name: "Task B11",
|
||||
StartDelay: Math.randomN(0, 5),
|
||||
Duration: Math.randomN(0, 28),
|
||||
PredecessorTaskID: null,
|
||||
IsCollated: false,
|
||||
CollatedTaskID: 5
|
||||
});
|
||||
|
||||
project1.AddTask({
|
||||
ID: 7,
|
||||
Name: "Task B12",
|
||||
StartDelay: Math.randomN(0, 5),
|
||||
Duration: Math.randomN(0, 28),
|
||||
PredecessorTaskID: null,
|
||||
IsCollated: false,
|
||||
CollatedTaskID: 5,
|
||||
Progress: 50
|
||||
});
|
||||
|
||||
project1.AddTask({
|
||||
ID: 8,
|
||||
Name: "Task E",
|
||||
StartDelay: Math.randomN(0, 5),
|
||||
Duration: Math.randomN(0, 28),
|
||||
PredecessorTaskID: null,
|
||||
IsCollated: true,
|
||||
CollatedTaskID: null
|
||||
});
|
||||
|
||||
project1.AddTask({
|
||||
ID: 9,
|
||||
Name: "Task E1",
|
||||
StartDelay: Math.randomN(0, 5),
|
||||
Duration: Math.randomN(0, 28),
|
||||
PredecessorTaskID: null,
|
||||
IsCollated: false,
|
||||
CollatedTaskID: 8
|
||||
});
|
||||
|
||||
project1.AddTask({
|
||||
ID: 3,
|
||||
Name: "Task C",
|
||||
StartDelay: Math.randomN(0, 5),
|
||||
Duration: Math.randomN(0, 28),
|
||||
PredecessorTaskID: 8,
|
||||
IsCollated: false,
|
||||
CollatedTaskID: null
|
||||
});
|
||||
|
||||
project1.AddTask({
|
||||
ID: 4,
|
||||
Name: "Task D",
|
||||
StartDelay: Math.randomN(0, 5),
|
||||
Duration: Math.randomN(0, 28),
|
||||
PredecessorTaskID: 3,
|
||||
IsCollated: false,
|
||||
CollatedTaskID: null
|
||||
});
|
||||
|
||||
project1.Invalidate();
|
||||
console.log(new Date(project1.StartDate).toLocaleDateString() + " - " + new Date(project1.FinishDate).toLocaleDateString() + " [" + project1.Duration + "]");
|
||||
|
||||
const taskData = project1.ExportTasks();
|
||||
|
||||
console.log(project1.Tasks);
|
||||
console.log(taskData);
|
||||
|
||||
if (taskGrid1 == null) taskGrid1 = new LiteRyzJS.ProjectTaskGrid(document.getElementById("taskGrid1"));
|
||||
taskGrid1.Render(taskData);
|
||||
|
||||
if (ganttChart1 == null) ganttChart1 = new LiteRyzJS.GanttChart(document.getElementById("ganttChart1"), {});
|
||||
ganttChart1.Load(project1);
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
2
devnotes.txt
Normal file
2
devnotes.txt
Normal file
@ -0,0 +1,2 @@
|
||||
npm install --save-dev webpack webpack-cli
|
||||
npm run build
|
2343
package-lock.json
generated
Normal file
2343
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
9
package.json
Normal file
9
package.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"webpack": "^5.93.0",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack"
|
||||
}
|
||||
}
|
9
src/extensions.js
Normal file
9
src/extensions.js
Normal file
@ -0,0 +1,9 @@
|
||||
// src/extensions.js
|
||||
import './extensions/array.js';
|
||||
import './extensions/boolean.js';
|
||||
import './extensions/date.js';
|
||||
import './extensions/document.js';
|
||||
import './extensions/math.js';
|
||||
import './extensions/object.js';
|
||||
import './extensions/string.js';
|
||||
import './extensions/window.js';
|
298
src/extensions/array.js
Normal file
298
src/extensions/array.js
Normal file
@ -0,0 +1,298 @@
|
||||
Array.isEmpty = function(value) {
|
||||
const dataType = Object.getDataType(value);
|
||||
if (dataType != "array") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (value.length <= 0);
|
||||
};
|
||||
|
||||
Array.toFlatten = function (sourceArray, propName, destArray) {
|
||||
for (let i=0; i<sourceArray.length; i++) {
|
||||
destArray.push(sourceArray[i]);
|
||||
|
||||
if (typeof(sourceArray[i][propName]) != undefined) {
|
||||
if (sourceArray[i][propName].length > 0) {
|
||||
Array.toFlatten(sourceArray[i][propName], propName, destArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Array.prototype.addRange = function (array) {
|
||||
if (array == null) {
|
||||
return this;
|
||||
}
|
||||
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
this.push(array[i]);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Array.prototype.any = function (propName, value) {
|
||||
return (this.count(propName, value) > 0);
|
||||
};
|
||||
|
||||
Array.prototype.copy = function () {
|
||||
return JSON.parse(JSON.stringify(this));
|
||||
};
|
||||
|
||||
Array.prototype.count = function (propName, value) {
|
||||
let result = 0;
|
||||
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
if (propName == null) {
|
||||
if (this[i] == value){
|
||||
result++;
|
||||
}
|
||||
} else {
|
||||
if (typeof(this[i][propName]) != "undefined") {
|
||||
if (this[i][propName] == value){
|
||||
result++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Array.prototype.countMany = function (...filters) {
|
||||
let result = 0;
|
||||
|
||||
filters.forEach(e => {
|
||||
result += this.count(e.propName, e.value);
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Array.prototype.create = function (length, value) {
|
||||
let result = [];
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
result.push(value);
|
||||
}
|
||||
|
||||
return result
|
||||
};
|
||||
|
||||
Array.prototype.flatten = function (propName) {
|
||||
let result = [];
|
||||
|
||||
Array.toFlatten(this, propName, result);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Array.prototype.index = function (propName, value) {
|
||||
const result = this.indexes(propName, value);
|
||||
if (result.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result[0];
|
||||
};
|
||||
|
||||
Array.prototype.indexes = function (propName, value) {
|
||||
let result = [];
|
||||
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
if (propName == null) {
|
||||
if (this[i] == value){
|
||||
result.push(i);
|
||||
}
|
||||
} else {
|
||||
if (typeof(this[i][propName]) != "undefined") {
|
||||
if (this[i][propName] == value) {
|
||||
result.push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Array.prototype.insert = function(index, item) {
|
||||
if (index < 0) {
|
||||
this.splice(0, 0, item);
|
||||
} else if (index >= this.length) {
|
||||
this.push(item);
|
||||
} else {
|
||||
this.splice(index, 0, item);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Array.prototype.joinIfNotNullOrWhitespace = function (separator) {
|
||||
const a = this;
|
||||
|
||||
let result = "";
|
||||
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
if (String.isNullOrWhitespace(this[i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!String.isNullOrWhitespace(result)) {
|
||||
result += separator;
|
||||
}
|
||||
|
||||
result += this[i];
|
||||
};
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Array.prototype.first = function (propName, value) {
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
if (typeof(this[i][propName]) == "undefined") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this[i][propName] == value){
|
||||
return this[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
Array.prototype.forEachTree = function (propName, func) {
|
||||
for (let i=0; i<this.length; i++) {
|
||||
if (func(this[i]) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this[i][propName].length <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this[i][propName].forEachTree(propName, func) === false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Array.prototype.orderBy = function (propName) {
|
||||
this.sort(function(a, b) {
|
||||
if (a[propName] < b[propName]) {
|
||||
return -1;
|
||||
} else if (a[propName] > b[propName]) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Array.prototype.orderByDesc = function (propName) {
|
||||
this.sort(function(a, b) {
|
||||
if (a[propName] < b[propName]) {
|
||||
return 1;
|
||||
} else if (a[propName] > b[propName]) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Array.prototype.remove = function (element) {
|
||||
let result = [];
|
||||
|
||||
for (let i=0; i<this.length; i++) {
|
||||
if (this[i] == element) {
|
||||
result.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i=(result.length - 1); i>=0; i--) {
|
||||
this.removeAt(result[i]);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Array.prototype.removeAt = function(index) {
|
||||
if ((index < 0) || (index >= this.length)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
this.splice(index, 1);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Array.prototype.removeRange = function (array) {
|
||||
for (let i=0; i<array.length; i++) {
|
||||
this.remove(array[i]);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find elements where a property equals a value.
|
||||
* @returns {Array} Of elements found.
|
||||
*/
|
||||
Array.prototype.select = function (propName, value) {
|
||||
let result = [];
|
||||
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
if (typeof(this[i][propName]) == "undefined") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this[i][propName] == value){
|
||||
result.push(this[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Array.prototype.selectMany = function (...filters) {
|
||||
let result = this;
|
||||
|
||||
filters.forEach(e => {
|
||||
result = result.select(e.propName, e.value);
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Array.prototype.toList = function (propName) {
|
||||
let result = [];
|
||||
|
||||
this.forEach(e => {
|
||||
if (typeof(e[propName]) == undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
result.push(e[propName]);
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Array.prototype.sortTree = function (childPropName, sortPropName) {
|
||||
this.orderBy(sortPropName);
|
||||
|
||||
for (let i=0; i<this.length; i++) {
|
||||
if (this[i][childPropName].length <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this[i][childPropName].orderBy(sortPropName);
|
||||
}
|
||||
};
|
25
src/extensions/boolean.js
Normal file
25
src/extensions/boolean.js
Normal file
@ -0,0 +1,25 @@
|
||||
Boolean.isFalse = function(value) {
|
||||
const a = this;
|
||||
|
||||
if (String.isNullOrUndefined(value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return value.toString().containsCI("false", "f", "y", "0", "x");
|
||||
};
|
||||
|
||||
Boolean.isTrue = function(value) {
|
||||
const a = this;
|
||||
|
||||
if (String.isNullOrUndefined(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return value.toString().containsCI("true", "t", "n", "1", "o");
|
||||
};
|
||||
|
||||
Boolean.ifTrue = function(value, trueValue, falseValue) {
|
||||
const a = this;
|
||||
|
||||
return (Boolean.isTrue(value) ? trueValue : falseValue);
|
||||
};
|
121
src/extensions/date.js
Normal file
121
src/extensions/date.js
Normal file
@ -0,0 +1,121 @@
|
||||
Date.addDays = function (date, days) {
|
||||
let result = new Date(date);
|
||||
|
||||
result.addDays(days);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Date.addMonths = function (date, months) {
|
||||
let result = new Date(date);
|
||||
|
||||
result.addMonths(months);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Date.addYears = function (date, years) {
|
||||
let result = new Date(date);
|
||||
|
||||
result.addYears(years);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Date.max = function (date1, date2) {
|
||||
if (date1 == null) {
|
||||
return date2;
|
||||
}
|
||||
|
||||
if (date2 == null) {
|
||||
return date1;
|
||||
}
|
||||
|
||||
return ((new Date(date1) <= new Date(date2)) ? new Date(date2) : new Date(date1));
|
||||
};
|
||||
|
||||
Date.min = function (date1, date2) {
|
||||
if (date1 == null) {
|
||||
return date2;
|
||||
}
|
||||
|
||||
if (date2 == null) {
|
||||
return date1;
|
||||
}
|
||||
|
||||
return ((new Date(date1) <= new Date(date2)) ? new Date(date1) : new Date(date2));
|
||||
};
|
||||
|
||||
Date.diffDays = function (date1, date2) {
|
||||
return Math.ceil((new Date(date2).getTime() - new Date(date1).getTime()) / (1000 * 3600 * 24));
|
||||
};
|
||||
|
||||
Date.today = function () {
|
||||
let result = new Date();
|
||||
|
||||
result.setHours(0);
|
||||
result.setMinutes(0);
|
||||
result.setSeconds(0);
|
||||
result.setMilliseconds(0);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
Date.prototype.addDays = function (days) {
|
||||
this.setDate(this.getDate() + parseInt(days));
|
||||
}
|
||||
|
||||
Date.prototype.addMonths = function (months) {
|
||||
this.setMonth(this.getMonth() + parseInt(months));
|
||||
}
|
||||
|
||||
Date.prototype.addYears = function (years) {
|
||||
this.setFullYear(this.getFullYear() + parseInt(years));
|
||||
}
|
||||
|
||||
Date.prototype.toCString = function(pattern) {
|
||||
let result = pattern;
|
||||
|
||||
result = result.replace("fffffff", this.getMilliseconds().toString().padStart(7, '0'));
|
||||
result = result.replace("ffffff", this.getMilliseconds().toString().padStart(6, '0'));
|
||||
result = result.replace("fffff", this.getMilliseconds().toString().padStart(5, '0'));
|
||||
result = result.replace("yyyy", this.getFullYear().toString().padStart(4, '0'));
|
||||
result = result.replace("MMMM", "{1}");
|
||||
result = result.replace("dddd", "{2}");
|
||||
result = result.replace("ffff", this.getMilliseconds().toString().padStart(4, '0'));
|
||||
result = result.replace("yyy", this.getFullYear().toString().padStart(3, '0'));
|
||||
result = result.replace("MMM", "{3}");
|
||||
result = result.replace("ddd", "{4}");
|
||||
result = result.replace("fff", this.getMilliseconds().toString().padStart(3, '0'));
|
||||
result = result.replace("zzz", "");
|
||||
result = result.replace("yy", this.getFullYear().toString().slice(-2));
|
||||
result = result.replace("MM", (this.getMonth() + 1).toString().padStart(2, '0'));
|
||||
result = result.replace("dd", this.getDate().toString().padStart(2, '0'));
|
||||
result = result.replace("HH", this.getHours().toString().padStart(2, '0'));
|
||||
result = result.replace("hh", (this.getHours() > 12 ? (this.getHours() - 12) : this.getHours()).toString().padStart(2, '0'));
|
||||
result = result.replace("mm", this.getMinutes().toString().padStart(2, '0'));
|
||||
result = result.replace("ss", this.getSeconds().toString().padStart(2, '0'));
|
||||
result = result.replace("ff", this.getMilliseconds().toString().padStart(2, '0'));
|
||||
result = result.replace("tt", "{5}");
|
||||
result = result.replace("zz", "");
|
||||
result = result.replace("y", this.getFullYear().toString());
|
||||
result = result.replace("M", (this.getMonth() + 1).toString());
|
||||
result = result.replace("d", this.getDate().toString());
|
||||
result = result.replace("H", this.getHours().toString());
|
||||
result = result.replace("h", (this.getHours() > 12 ? (this.getHours() - 12) : this.getHours()).toString());
|
||||
result = result.replace("m", this.getMinutes().toString());
|
||||
result = result.replace("s", this.getSeconds().toString());
|
||||
result = result.replace("z", "");
|
||||
result = result.replace("t", "{6}");
|
||||
result = result.replace("Z", "");
|
||||
|
||||
result = result.replace("{1}", this.toLocaleString('default', { month: 'long' }));
|
||||
result = result.replace("{2}", this.toLocaleString('default', { weekday: 'long' }));
|
||||
result = result.replace("{3}", this.toLocaleString('default', { month: 'short' }));
|
||||
result = result.replace("{4}", this.toLocaleString('default', { weekday: 'short' }));
|
||||
result = result.replace("{5}", (this.getHours() >= 12 ? "PM" : "AM"));
|
||||
result = result.replace("{6}", (this.getHours() >= 12 ? "P" : "A"));
|
||||
|
||||
return result;
|
||||
}
|
9
src/extensions/document.js
Normal file
9
src/extensions/document.js
Normal file
@ -0,0 +1,9 @@
|
||||
Document.ready = async function(fn) {
|
||||
(async function() {
|
||||
(document.readyState !== 'loading') ?
|
||||
fn() : document.addEventListener('DOMContentLoaded',
|
||||
function() {
|
||||
fn();
|
||||
});
|
||||
})();
|
||||
}
|
30
src/extensions/math.js
Normal file
30
src/extensions/math.js
Normal file
@ -0,0 +1,30 @@
|
||||
Math.randomN = function (min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
|
||||
return Math.floor(Math.random() * (max - min) + min);
|
||||
};
|
||||
|
||||
Math.average = function (values) {
|
||||
let result = 0;
|
||||
|
||||
values.forEach(e => {
|
||||
result += parseFloat(e);
|
||||
});
|
||||
|
||||
return (result / values.length);
|
||||
};
|
||||
|
||||
Math.avg = function (...values) {
|
||||
let result = 0;
|
||||
|
||||
values.forEach(e => {
|
||||
result += parseFloat(e);
|
||||
});
|
||||
|
||||
return (result / values.length);
|
||||
};
|
||||
|
||||
Math.half = function (value) {
|
||||
return (value / 2);
|
||||
};
|
27
src/extensions/object.js
Normal file
27
src/extensions/object.js
Normal file
@ -0,0 +1,27 @@
|
||||
Object.isNullOrUndefined = function(value) {
|
||||
if (typeof (value) == "undefined") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
Object.getDataType = function(value) {
|
||||
if (String.isNullOrUndefined(value)) {
|
||||
return "null";
|
||||
}
|
||||
|
||||
if (typeof(value) == "object") {
|
||||
if (Array.isArray(value)) {
|
||||
return "array";
|
||||
} else {
|
||||
return "object";
|
||||
}
|
||||
}
|
||||
|
||||
return typeof(value);
|
||||
};
|
83
src/extensions/string.js
Normal file
83
src/extensions/string.js
Normal file
@ -0,0 +1,83 @@
|
||||
String.isNullOrUndefined = function(value) {
|
||||
if (typeof (value) == "undefined") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
String.isNullOrWhitespace = function(value) {
|
||||
const a = this;
|
||||
|
||||
if (String.isNullOrUndefined(value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof(value) == "string") {
|
||||
return (value.trim().length <= 0);
|
||||
} else {
|
||||
return (value.toString().trim().length <= 0);
|
||||
}
|
||||
};
|
||||
|
||||
String.joinIfNotNullOrWhitespace = function (separator, ...values) {
|
||||
const a = this;
|
||||
|
||||
let result = "";
|
||||
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
if (String.isNullOrWhitespace(values[i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!String.isNullOrWhitespace(result)) {
|
||||
result += separator;
|
||||
}
|
||||
|
||||
result += values[i];
|
||||
};
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
String.prototype.contains = function(...args) {
|
||||
for (let arg of args) {
|
||||
if (this == arg) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
String.prototype.containsCI = function(...args) {
|
||||
for (let arg of args) {
|
||||
if (this.toLowerCase() == arg.toLowerCase()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
String.prototype.encodeHtmlLinks = function() {
|
||||
return value.replace(/(http[s]{0,1}:\/\/[^\s]+)/g, "<a href='$1'>$1</a>");
|
||||
};
|
||||
|
||||
String.prototype.toTitleCase = function () {
|
||||
let result = this;
|
||||
|
||||
result = result.replace(/([A-Z]{1})/g, " $1");
|
||||
result = result.trim();
|
||||
result = result.charAt(0).toUpperCase() + result.substr(1);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
String.prototype.getFilename = function () {
|
||||
return this.substring(this.lastIndexOf('/') + 1);
|
||||
};
|
43
src/extensions/window.js
Normal file
43
src/extensions/window.js
Normal file
@ -0,0 +1,43 @@
|
||||
Window.goToTop = function() {
|
||||
Window.scrollTo(0, 0);
|
||||
};
|
||||
|
||||
Window.fragment = {
|
||||
get: function() {
|
||||
if (!window.location.hash) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const n = window.location.hash.indexOf("?");
|
||||
if (n < 0) {
|
||||
return window.location.hash.substring(1);
|
||||
} else {
|
||||
return window.location.hash.substring(1, n);
|
||||
}
|
||||
},
|
||||
getQuery: function() {
|
||||
if (!window.location.hash) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let hashQueryString = window.location.hash;
|
||||
const n = hashQueryString.indexOf("?");
|
||||
if (n < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
hashQueryString = hasQueryString.substring(n + 1);
|
||||
|
||||
const params = new URLSearchParams(hashQueryString);
|
||||
const result = {}
|
||||
for(const [key, value] of params.entries()) {
|
||||
result[key] = value;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
clear: function() {
|
||||
location.hash = "";
|
||||
history.replaceState("", "", location.pathname);
|
||||
}
|
||||
};
|
9
src/graphics.js
Normal file
9
src/graphics.js
Normal file
@ -0,0 +1,9 @@
|
||||
// src/graphics.js
|
||||
import Canvas from './graphics/canvas.js';
|
||||
import Rectangle from './graphics/rectangle.js';
|
||||
|
||||
|
||||
export {
|
||||
Canvas,
|
||||
Rectangle
|
||||
};
|
543
src/graphics/canvas.js
Normal file
543
src/graphics/canvas.js
Normal file
@ -0,0 +1,543 @@
|
||||
class Canvas {
|
||||
|
||||
constructor(el) {
|
||||
const a = this;
|
||||
|
||||
a.container = el;
|
||||
a.ctx = null;
|
||||
|
||||
a.flowContainer = null;
|
||||
a.canvasContainer = null;
|
||||
|
||||
a.autoSize = true;
|
||||
a.padding = {
|
||||
Top: 0,
|
||||
Right: 0,
|
||||
Bottom: 0,
|
||||
Left: 0
|
||||
};
|
||||
|
||||
a.initialiseComponents();
|
||||
}
|
||||
|
||||
initialiseComponents() {
|
||||
const a = this;
|
||||
|
||||
if (a.container == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
a.container.innerHTML = "<div class='border'><canvas></canvas></div>";
|
||||
|
||||
if (a.container != null) {
|
||||
a.container.style.width = "100%";
|
||||
a.container.style.height = "100%";
|
||||
|
||||
a.flowContainer = a.container.getElementsByTagName("div")[0];
|
||||
a.canvasContainer = a.flowContainer.getElementsByTagName("canvas")[0];
|
||||
a.ctx = a.canvasContainer.getContext("2d");
|
||||
}
|
||||
|
||||
a.AutoSize = a.AutoSize;
|
||||
a.Width = a.Width;
|
||||
a.Height = a.Height;
|
||||
|
||||
a.Clear();
|
||||
}
|
||||
|
||||
|
||||
get AutoSize() {
|
||||
return this.autoSize;
|
||||
}
|
||||
|
||||
set AutoSize(value) {
|
||||
const a = this;
|
||||
|
||||
a.autoSize = value;
|
||||
|
||||
if (a.flowContainer != null) {
|
||||
if (value == true) {
|
||||
a.flowContainer.style.overflow = "hidden";
|
||||
} else {
|
||||
a.flowContainer.style.overflowX = ((a.Width >= a.ClientWidth) ? "hidden" : "auto");
|
||||
a.flowContainer.style.overflowY = ((a.Height >= a.ClientHeight) ? "hidden" : "auto");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get ClientHeight() {
|
||||
const a = this;
|
||||
|
||||
if (a.canvasContainer == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return a.getHeight(a.canvasContainer);
|
||||
}
|
||||
|
||||
set ClientHeight(value) {
|
||||
const a = this;
|
||||
|
||||
if (a.canvasContainer != null) a.canvasContainer.style.height = value + "px";
|
||||
if (a.ctx != null) a.ctx.canvas.height = value;
|
||||
}
|
||||
|
||||
get ClientWidth() {
|
||||
const a = this;
|
||||
|
||||
if (a.canvasContainer == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return a.getWidth(a.canvasContainer);
|
||||
}
|
||||
|
||||
set ClientWidth(value) {
|
||||
const a = this;
|
||||
|
||||
if (a.canvasContainer != null) a.canvasContainer.style.width = value + "px";
|
||||
if (a.ctx != null) a.ctx.canvas.width = value;
|
||||
}
|
||||
|
||||
get Height() {
|
||||
const a = this;
|
||||
|
||||
if (a.container == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return a.getHeight(a.container);
|
||||
}
|
||||
|
||||
set Height(value) {
|
||||
const a = this;
|
||||
|
||||
if (a.container != null) a.container.style.height = value + "px";
|
||||
if (a.flowContainer != null) a.flowContainer.style.height = value + "px";
|
||||
|
||||
if (a.AutoSize == true) {
|
||||
a.ClientHeight = value;
|
||||
}
|
||||
}
|
||||
|
||||
get Width() {
|
||||
const a = this;
|
||||
|
||||
if (a.container == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return a.getWidth(a.container);
|
||||
}
|
||||
|
||||
set Width(value) {
|
||||
const a = this;
|
||||
|
||||
if (a.container != null) a.container.style.width = value + "px";
|
||||
if (a.flowContainer != null) a.flowContainer.style.width = value + "px";
|
||||
|
||||
if (a.AutoSize == true) {
|
||||
a.ClientWidth = value;
|
||||
}
|
||||
}
|
||||
|
||||
get Size() {
|
||||
const a = this;
|
||||
|
||||
return {
|
||||
W: a.Width,
|
||||
H: a.Height
|
||||
};
|
||||
}
|
||||
|
||||
get ClientRectangle() {
|
||||
const a = this;
|
||||
|
||||
return {
|
||||
X: a.padding.Left,
|
||||
Y: a.padding.Top,
|
||||
W: (a.Width - (a.padding.Left + a.padding.Right)),
|
||||
H: (a.Height - (a.padding.Top + a.padding.Bottom))
|
||||
};
|
||||
}
|
||||
|
||||
get Rectangle() {
|
||||
const a = this;
|
||||
|
||||
return {
|
||||
X: 0,
|
||||
Y: 0,
|
||||
W: a.Width,
|
||||
H: a.Height
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Clear() {
|
||||
const a = this;
|
||||
|
||||
if (a.ctx == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
a.ctx.clearRect(0, 0, a.ctx.canvas.width, a.ctx.canvas.height);
|
||||
}
|
||||
|
||||
DrawArrowS(rectangle, penColour, options) {
|
||||
const a = this;
|
||||
|
||||
if (a.ctx == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const opt = Object.assign({
|
||||
LineWidth: 1,
|
||||
LineDash: [],
|
||||
FillColour: null
|
||||
}, options);
|
||||
|
||||
// Adjust for pen discrepancy
|
||||
rectangle.X += 0.5;
|
||||
rectangle.Y += 0.5;
|
||||
rectangle.W -= opt.LineWidth;
|
||||
rectangle.H -= opt.LineWidth;
|
||||
|
||||
a.ctx.beginPath();
|
||||
a.ctx.strokeStyle = penColour;
|
||||
a.ctx.lineWidth = opt.LineWidth;
|
||||
a.ctx.setLineDash(opt.LineDash);
|
||||
|
||||
a.ctx.moveTo(rectangle.X, rectangle.Y);
|
||||
a.ctx.lineTo((rectangle.X + rectangle.W), rectangle.Y);
|
||||
a.ctx.lineTo((rectangle.X + Math.half(rectangle.W)), (rectangle.Y + rectangle.H));
|
||||
|
||||
a.ctx.closePath();
|
||||
|
||||
if (opt.FillColour != null) {
|
||||
a.ctx.fillStyle = opt.FillColour;
|
||||
a.ctx.fill();
|
||||
}
|
||||
|
||||
a.ctx.stroke();
|
||||
}
|
||||
|
||||
DrawCircle(x, y, width, penColour, options) {
|
||||
const a = this;
|
||||
|
||||
if (a.ctx == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const opt = Object.assign({
|
||||
LineWidth: 1,
|
||||
LineDash: [],
|
||||
FillColour: null
|
||||
}, options);
|
||||
|
||||
let rectangle = {
|
||||
X: x + Math.half(width),
|
||||
Y: y + Math.half(width),
|
||||
W: Math.half(width),
|
||||
H: Math.half(width)
|
||||
};
|
||||
|
||||
// Adjust for pen discrepancy
|
||||
rectangle.X += 0.5;
|
||||
rectangle.Y += 0.5;
|
||||
rectangle.W -= Math.half(opt.LineWidth);
|
||||
rectangle.H -= Math.half(opt.LineWidth);
|
||||
|
||||
a.ctx.beginPath();
|
||||
a.ctx.strokeStyle = penColour;
|
||||
a.ctx.lineWidth = opt.LineWidth;
|
||||
a.ctx.setLineDash(opt.LineDash);
|
||||
|
||||
a.ctx.arc(rectangle.X, rectangle.Y, rectangle.W, 0, 2 * Math.PI, false);
|
||||
|
||||
a.ctx.closePath();
|
||||
|
||||
if (opt.FillColour != null) {
|
||||
a.ctx.fillStyle = opt.FillColour;
|
||||
a.ctx.fill();
|
||||
}
|
||||
|
||||
a.ctx.stroke();
|
||||
}
|
||||
|
||||
DrawDiamond(rectangle, penColour, options) {
|
||||
const a = this;
|
||||
|
||||
if (a.ctx == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const opt = Object.assign({
|
||||
LineWidth: 1,
|
||||
LineDash: [],
|
||||
FillColour: null
|
||||
}, options);
|
||||
|
||||
// Adjust for pen discrepancy
|
||||
rectangle.X += 0.5;
|
||||
rectangle.Y += 0.5;
|
||||
rectangle.W -= opt.LineWidth;
|
||||
rectangle.H -= opt.LineWidth;
|
||||
|
||||
a.ctx.beginPath();
|
||||
a.ctx.strokeStyle = penColour;
|
||||
a.ctx.lineWidth = opt.LineWidth;
|
||||
a.ctx.setLineDash(opt.LineDash);
|
||||
|
||||
a.ctx.moveTo((rectangle.X + Math.half(rectangle.W)), rectangle.Y);
|
||||
a.ctx.lineTo((rectangle.X + rectangle.W), (rectangle.Y + Math.half(rectangle.H)));
|
||||
a.ctx.lineTo((rectangle.X + Math.half(rectangle.W)), (rectangle.Y + rectangle.H));
|
||||
a.ctx.lineTo(rectangle.X, (rectangle.Y + Math.half(rectangle.H)));
|
||||
|
||||
a.ctx.closePath();
|
||||
|
||||
if (opt.FillColour != null) {
|
||||
a.ctx.fillStyle = opt.FillColour;
|
||||
a.ctx.fill();
|
||||
}
|
||||
|
||||
a.ctx.stroke();
|
||||
}
|
||||
|
||||
DrawRectangle(rectangle, penColour, options) {
|
||||
const a = this;
|
||||
|
||||
if (a.ctx == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const opt = Object.assign({
|
||||
LineWidth: 1,
|
||||
LineDash: [],
|
||||
FillColour: null
|
||||
}, options);
|
||||
|
||||
// Adjust for pen discrepancy
|
||||
rectangle.X += 0.5;
|
||||
rectangle.Y += 0.5;
|
||||
rectangle.W -= opt.LineWidth;
|
||||
rectangle.H -= opt.LineWidth;
|
||||
|
||||
a.ctx.beginPath();
|
||||
a.ctx.strokeStyle = penColour;
|
||||
a.ctx.lineWidth = opt.LineWidth;
|
||||
a.ctx.setLineDash(opt.LineDash);
|
||||
a.ctx.rect(rectangle.X, rectangle.Y, rectangle.W, rectangle.H);
|
||||
|
||||
if (opt.FillColour != null) {
|
||||
a.ctx.fillStyle = opt.FillColour;
|
||||
a.ctx.fill();
|
||||
}
|
||||
|
||||
a.ctx.closePath();
|
||||
a.ctx.stroke();
|
||||
}
|
||||
|
||||
DrawLine(startX, startY, finishX, finishY, penColour, options) {
|
||||
const a = this;
|
||||
|
||||
if (a.ctx == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const opt = Object.assign({
|
||||
LineWidth: 1,
|
||||
LineDash: []
|
||||
}, options);
|
||||
|
||||
startY -= 0.5;
|
||||
// y -= penWidth;
|
||||
|
||||
a.ctx.beginPath();
|
||||
a.ctx.strokeStyle = penColour;
|
||||
a.ctx.lineWidth = opt.LineWidth;
|
||||
a.ctx.setLineDash(opt.LineDash);
|
||||
a.ctx.moveTo(startX, startY);
|
||||
a.ctx.lineTo(finishX, finishY);
|
||||
a.ctx.stroke();
|
||||
}
|
||||
|
||||
DrawLines(points, penColour, options) {
|
||||
const a = this;
|
||||
|
||||
if (a.ctx == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (points == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (points.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const opt = Object.assign({
|
||||
LineWidth: 1,
|
||||
LineDash: []
|
||||
}, options);
|
||||
|
||||
a.ctx.beginPath();
|
||||
a.ctx.strokeStyle = penColour;
|
||||
a.ctx.lineWidth = opt.LineWidth;
|
||||
a.ctx.setLineDash(opt.LineDash);
|
||||
|
||||
a.ctx.moveTo(points[0].X, points[0].Y);
|
||||
|
||||
for (let i=1; i<points.length; i++) {
|
||||
a.ctx.lineTo(points[i].X, points[i].Y);
|
||||
}
|
||||
|
||||
a.ctx.stroke();
|
||||
}
|
||||
|
||||
DrawHorizontalLine(x, y, width, penColour, options) {
|
||||
const a = this;
|
||||
|
||||
if (a.ctx == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const opt = Object.assign({
|
||||
LineWidth: 1,
|
||||
LineDash: []
|
||||
}, options);
|
||||
|
||||
y -= 0.5;
|
||||
// y -= penWidth;
|
||||
|
||||
a.ctx.beginPath();
|
||||
a.ctx.strokeStyle = penColour;
|
||||
a.ctx.lineWidth = opt.LineWidth;
|
||||
a.ctx.setLineDash(opt.LineDash);
|
||||
a.ctx.moveTo(x, y);
|
||||
a.ctx.lineTo((x + width), y);
|
||||
a.ctx.stroke();
|
||||
}
|
||||
|
||||
DrawText(x, y, text, font, foreColour, options) {
|
||||
const a = this;
|
||||
|
||||
if (a.ctx == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const opt = Object.assign({
|
||||
Align: "left"
|
||||
}, options);
|
||||
|
||||
a.ctx.font = font;
|
||||
a.ctx.fillStyle = foreColour;
|
||||
a.ctx.textAlign = opt.Align;
|
||||
a.ctx.textBaseline = "top";
|
||||
|
||||
a.ctx.fillText(text, x, y);
|
||||
}
|
||||
|
||||
DrawVerticalLine(x, y, height, penColour, options) {
|
||||
const a = this;
|
||||
|
||||
if (a.ctx == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const opt = Object.assign({
|
||||
LineWidth: 1,
|
||||
LineDash: []
|
||||
}, options);
|
||||
|
||||
x -= 0.5;
|
||||
y -= opt.LineWidth;
|
||||
|
||||
a.ctx.beginPath();
|
||||
a.ctx.strokeStyle = penColour;
|
||||
a.ctx.lineWidth = opt.LineWidth;
|
||||
a.ctx.setLineDash(opt.LineDash);
|
||||
a.ctx.moveTo(x, y);
|
||||
a.ctx.lineTo(x, (y + height));
|
||||
a.ctx.stroke();
|
||||
}
|
||||
|
||||
FillText(rectangle, text, font, foreColour, options) {
|
||||
const a = this;
|
||||
|
||||
if (a.ctx == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const opt = Object.assign({
|
||||
Align: "center",
|
||||
VAlign: "middle"
|
||||
}, options);
|
||||
|
||||
a.ctx.font = font;
|
||||
a.ctx.fillStyle = foreColour;
|
||||
a.ctx.textAlign = opt.Align;
|
||||
// a._ctx.textBaseline = verticalAlign;
|
||||
a.ctx.textBaseline = "top";
|
||||
|
||||
const size = a.ctx.measureText(text);
|
||||
const x = rectangle.X + Math.half(rectangle.W);
|
||||
let y = rectangle.Y;
|
||||
|
||||
switch (opt.VAlign) {
|
||||
case "center":
|
||||
case "middle":
|
||||
y += Math.half((rectangle.H - size.fontBoundingBoxDescent)) + size.fontBoundingBoxAscent;
|
||||
break;
|
||||
case "bottom":
|
||||
y += (rectangle.H - size.fontBoundingBoxDescent);
|
||||
break;
|
||||
case "top":
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
a.ctx.fillText(text, x, y);
|
||||
}
|
||||
|
||||
Invalidate() {
|
||||
const a = this;
|
||||
|
||||
a.AutoSize = a.AutoSize;
|
||||
a.Width = a.Width;
|
||||
a.Height = a.Height;
|
||||
}
|
||||
|
||||
MeasureText(font, value) {
|
||||
const a = this;
|
||||
|
||||
if (a.ctx == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
a.ctx.font = font;
|
||||
|
||||
const size = a.ctx.measureText(value);
|
||||
|
||||
return {
|
||||
W: size.width,
|
||||
H: Math.round(size.fontBoundingBoxDescent + size.fontBoundingBoxAscent)
|
||||
};
|
||||
}
|
||||
|
||||
getWidth(el) {
|
||||
const result = (el.offsetWidth || el.innerWidth || el.clientWidth);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
getHeight(el) {
|
||||
const result = (el.offsetHeight || el.innerHeight || el.clientHeight);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default Canvas;
|
36
src/graphics/rectangle.js
Normal file
36
src/graphics/rectangle.js
Normal file
@ -0,0 +1,36 @@
|
||||
class Rectangle {
|
||||
constructor(x, y, w, h) {
|
||||
this.X = x;
|
||||
this.Y = y;
|
||||
this.W = w;
|
||||
this.H = h;
|
||||
}
|
||||
|
||||
static containsPoint(rectangle, point) {
|
||||
const x2 = (rectangle.X + rectangle.W);
|
||||
const y2 = (rectangle.Y + rectangle.H);
|
||||
|
||||
return ((point.X >= rectangle.X) && (point.X <= x2) && (point.Y >= rectangle.Y) && (point.Y <= y2));
|
||||
}
|
||||
|
||||
static combine(rect1, rect2) {
|
||||
const x2 = Math.max((rect1.X + rect1.W), (rect2.X + rect2.W));
|
||||
const y2 = Math.max((rect1.Y + rect1.H), (rect2.Y + rect2.H));
|
||||
|
||||
const rect = {
|
||||
X: Math.min(rect1.X, rect2.X),
|
||||
Y: Math.min(rect1.Y, rect2.Y),
|
||||
W: 0,
|
||||
H: 0
|
||||
};
|
||||
|
||||
rect.W = x2 - rect.X;
|
||||
rect.H = y2 - rect.Y;
|
||||
|
||||
return rect;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default Rectangle;
|
11
src/project.js
Normal file
11
src/project.js
Normal file
@ -0,0 +1,11 @@
|
||||
// src/project.js
|
||||
import GanttChart from './project/gantt-chart.js';
|
||||
import Project from './project/project.js';
|
||||
import ProjectTaskGrid from './project/task-grid.js';
|
||||
|
||||
|
||||
export {
|
||||
GanttChart,
|
||||
Project,
|
||||
ProjectTaskGrid
|
||||
};
|
244
src/project/gantt-chart.js
Normal file
244
src/project/gantt-chart.js
Normal file
@ -0,0 +1,244 @@
|
||||
import Canvas from '../graphics/canvas.js';
|
||||
|
||||
|
||||
class GanttChart {
|
||||
constructor(el, options) {
|
||||
const a = this;
|
||||
|
||||
a.Canvas = new Canvas(el);
|
||||
a.Options = Object.assign(a.DefaultOptions, options);
|
||||
|
||||
a.Debug = false;
|
||||
a.Project = null;
|
||||
|
||||
a.StartDate = null;
|
||||
}
|
||||
|
||||
|
||||
get DefaultOptions() {
|
||||
return {
|
||||
DayWidth: 24,
|
||||
HeaderRow: {
|
||||
Height: [ 21, 21 ]
|
||||
},
|
||||
Row: {
|
||||
Height: 28,
|
||||
Task: {
|
||||
Height: 13,
|
||||
PaddingTop: 6,
|
||||
BorderColour: "#555555",
|
||||
FillColour: "#9CC2E6"
|
||||
},
|
||||
CollatedTask: {
|
||||
Height: 3,
|
||||
PaddingTop: 11,
|
||||
BorderColour: "#555555",
|
||||
FillColour: "#555555"
|
||||
}
|
||||
},
|
||||
Line: {
|
||||
Margin: 5,
|
||||
Colour: "#555555",
|
||||
Width: 1,
|
||||
ArrowSize: 5
|
||||
},
|
||||
DateFont: "7pt sans-serif",
|
||||
DateForeColour: "#636363",
|
||||
BorderWidth: 1,
|
||||
BorderColour: "#B8B8B8",
|
||||
BorderDashPattern: [1, 3],
|
||||
};
|
||||
}
|
||||
|
||||
get HeaderHeight() {
|
||||
const a = this;
|
||||
|
||||
return a.Options.HeaderRow.Height[0] + a.Options.HeaderRow.Height[1];
|
||||
}
|
||||
|
||||
Invalidate() {
|
||||
const a = this;
|
||||
|
||||
a.Canvas.Clear();
|
||||
|
||||
if (a.Project == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tasks = a.Project.ExportTasks();
|
||||
const width = ((a.Project.Duration + 2) * a.Options.DayWidth);
|
||||
const height = (tasks.length * a.Options.Row.Height) + a.HeaderHeight;
|
||||
|
||||
a.AutoSize = false;
|
||||
a.ClientWidth = width;
|
||||
a.ClientHeight = height;
|
||||
|
||||
a.Canvas.Invalidate();
|
||||
|
||||
a.drawChartLabel(a.Project);
|
||||
a.drawTasks(tasks);
|
||||
a.drawLines(tasks);
|
||||
}
|
||||
|
||||
Load(project) {
|
||||
const a = this;
|
||||
|
||||
a.Canvas.Clear();
|
||||
|
||||
if (project == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
a.Project = project;
|
||||
a.StartDate = new Date(project.StartDate);
|
||||
|
||||
a.Invalidate();
|
||||
}
|
||||
|
||||
drawChartLabel(project) {
|
||||
const a = this;
|
||||
|
||||
const width = a.Canvas.ClientWidth;
|
||||
const height = a.Canvas.ClientHeight;
|
||||
const displayDays = project.Duration + 2;
|
||||
|
||||
let startDate = new Date(a.StartDate);
|
||||
startDate.addDays(-1);
|
||||
|
||||
// Draw vertical lines
|
||||
for (let i=1; i<displayDays; i++) {
|
||||
a.Canvas.DrawVerticalLine((a.Options.DayWidth * i), (a.Options.HeaderRow.Height[0] + a.Options.BorderWidth), (a.Options.HeaderRow.Height[1] - (a.Options.BorderWidth * 2)), a.Options.BorderColour, {});
|
||||
}
|
||||
|
||||
a.Canvas.DrawHorizontalLine(0, a.Options.HeaderRow.Height[0], width, a.Options.BorderColour);
|
||||
a.Canvas.DrawHorizontalLine(0, (a.HeaderHeight - a.Options.BorderWidth), width, a.Options.BorderColour);
|
||||
|
||||
// Write dates
|
||||
for (let i=0; i<displayDays; i++) {
|
||||
const date = Date.addDays(startDate, i);
|
||||
const x = (a.Options.DayWidth * i);
|
||||
|
||||
// Draw month
|
||||
if (date.getDate() == 1) {
|
||||
const size = a.Canvas.MeasureText(a.Options.DateFont, "#");
|
||||
const monthPoint = {
|
||||
X: (x + 2),
|
||||
Y: Math.half(a.Options.HeaderRow.Height[0] - size.H)
|
||||
};
|
||||
|
||||
a.Canvas.DrawText(monthPoint.X, monthPoint.Y, date.toCString("MMMM"), a.Options.DateFont, a.Options.DateForeColour);
|
||||
}
|
||||
|
||||
// Draw day
|
||||
const dateRectangle = {
|
||||
X: x,
|
||||
Y: a.Options.HeaderRow.Height[1],
|
||||
W: a.Options.DayWidth - a.Options.BorderWidth,
|
||||
H: (a.Options.HeaderRow.Height[1] - (a.Options.BorderWidth * 2))
|
||||
};
|
||||
|
||||
a.Canvas.FillText(dateRectangle, date.getDate(), a.Options.DateFont, a.Options.DateForeColour);
|
||||
|
||||
if (a.Debug) a.Canvas.DrawRectangle(dateRectangle, "red", {});
|
||||
|
||||
// Draw day-of-week guideline
|
||||
const guidelineHeight = height - a.Options.BorderWidth;
|
||||
|
||||
if (project.Project.StartOfWeek == date.getDay()) {
|
||||
// Draw start-of-the-week guideline
|
||||
a.Canvas.DrawVerticalLine(x, a.HeaderHeight, guidelineHeight, a.Options.BorderColour, {});
|
||||
} else {
|
||||
a.Canvas.DrawVerticalLine(x, a.HeaderHeight, guidelineHeight, a.Options.BorderColour, { LineDash: a.Options.BorderDashPattern });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawTasks(tasks) {
|
||||
const a = this;
|
||||
|
||||
for (let i=0; i<tasks.length; i++) {
|
||||
const style = ((tasks[i].IsCollated == true) ? a.Options.Row.CollatedTask : a.Options.Row.Task);
|
||||
const rectangle = a.getTaskRectangle(tasks[i], style);
|
||||
|
||||
if (tasks[i].Duration <= 0) {
|
||||
a.Canvas.DrawDiamond(rectangle, style.BorderColour, { FillColour: style.FillColour });
|
||||
} else {
|
||||
a.Canvas.DrawRectangle(rectangle, style.BorderColour, { FillColour: style.FillColour });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawLines(tasks) {
|
||||
const a = this;
|
||||
|
||||
for (let i=0; i<tasks.length; i++) {
|
||||
if (tasks[i].PredecessorTaskID == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tasks[i].PredecessorTaskNo == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const style = ((tasks[i].IsCollated == true) ? a.Options.Row.CollatedTask : a.Options.Row.Task);
|
||||
const rectangle = a.getTaskRectangle(tasks[i], style);
|
||||
|
||||
const predecessorTask = tasks.first("Order", tasks[i].PredecessorTaskNo);
|
||||
if (predecessorTask == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const predecessorStyle = ((predecessorTask.IsCollated == true) ? a.Options.Row.CollatedTask : a.Options.Row.Task);
|
||||
const predecessorRectangle = a.getTaskRectangle(predecessorTask, predecessorStyle);
|
||||
|
||||
let points = [];
|
||||
points.push({ X: (predecessorRectangle.X + predecessorRectangle.W), Y: (predecessorRectangle.Y + Math.half(predecessorRectangle.H)) });
|
||||
points.push({ X: (rectangle.X + a.Options.Line.Margin), Y: (predecessorRectangle.Y + Math.half(predecessorRectangle.H)) });
|
||||
|
||||
points.push({ X: (rectangle.X + a.Options.Line.Margin), Y: (rectangle.Y - a.Options.Line.Margin) });
|
||||
points.push({ X: (rectangle.X + a.Options.Line.Margin), Y: rectangle.Y });
|
||||
|
||||
a.Canvas.DrawLines(points, a.Options.Line.Colour, { LineWidth: a.Options.Line.Width });
|
||||
|
||||
const arrowRectangle = {
|
||||
X: (rectangle.X + a.Options.Line.Margin - Math.half(a.Options.Line.ArrowSize)),
|
||||
Y: (rectangle.Y - a.Options.Line.ArrowSize),
|
||||
W: a.Options.Line.ArrowSize,
|
||||
H: a.Options.Line.ArrowSize
|
||||
}
|
||||
|
||||
a.Canvas.DrawArrowS(arrowRectangle, a.Options.Line.Colour, { FillColour: a.Options.Line.Colour });
|
||||
}
|
||||
}
|
||||
|
||||
getTaskRectangle(task, style) {
|
||||
const a = this;
|
||||
const i = (task.Order - 1);
|
||||
|
||||
let rectangle = {};
|
||||
|
||||
if (task.Duration <= 0) {
|
||||
rectangle = {
|
||||
X: (a.Options.DayWidth * (Date.diffDays(a.StartDate, task.StartDate) + 1)),
|
||||
Y: (a.HeaderHeight + (a.Options.Row.Height * i) + style.PaddingTop),
|
||||
W: style.Height,
|
||||
H: style.Height
|
||||
};
|
||||
|
||||
rectangle.X -= Math.half(rectangle.W);
|
||||
} else {
|
||||
rectangle = {
|
||||
X: (a.Options.DayWidth * (Date.diffDays(a.StartDate, task.StartDate) + 1)),
|
||||
Y: (a.HeaderHeight + (a.Options.Row.Height * i) + style.PaddingTop),
|
||||
W: (a.Options.DayWidth * task.Duration),
|
||||
H: style.Height
|
||||
};
|
||||
}
|
||||
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default GanttChart;
|
428
src/project/project.js
Normal file
428
src/project/project.js
Normal file
@ -0,0 +1,428 @@
|
||||
class Project {
|
||||
constructor(options) {
|
||||
const a = this;
|
||||
const _options = Object.assign(a.newProject, options);
|
||||
|
||||
a.Project = _options;
|
||||
a.Tasks = [];
|
||||
|
||||
a.initialiseComponents();
|
||||
}
|
||||
|
||||
initialiseComponents() {
|
||||
const a = this;
|
||||
}
|
||||
|
||||
|
||||
get NewTask() {
|
||||
return {
|
||||
// Order: null,
|
||||
ID: null,
|
||||
Name: "",
|
||||
Description: "",
|
||||
Tag: null,
|
||||
// StartDate: null, // new Date(),
|
||||
// FinishDate: null, // new Date(),
|
||||
StartDelay: 0, // Days
|
||||
Duration: 1, // Days
|
||||
PredecessorTaskID: null,
|
||||
IsCollated: false,
|
||||
CollatedTaskID: null,
|
||||
// CalcWorkHours: 0,
|
||||
ActuWorkHours: null,
|
||||
Progress: 0,
|
||||
Resources: []
|
||||
};
|
||||
}
|
||||
|
||||
get StartDate() {
|
||||
const a = this;
|
||||
|
||||
return new Date(a.Project.StartDate);
|
||||
}
|
||||
|
||||
get FinishDate() {
|
||||
const a = this;
|
||||
|
||||
let result = new Date(a.Project.StartDate);
|
||||
|
||||
a.ExportTasks().forEach(e => {
|
||||
if (e.FinishDate == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
result = Date.max(result, e.FinishDate);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
get Duration() {
|
||||
const a = this;
|
||||
|
||||
return Date.diffDays(a.StartDate, a.FinishDate);
|
||||
}
|
||||
|
||||
|
||||
get newProject() {
|
||||
return {
|
||||
Name: "",
|
||||
Description: "",
|
||||
StartDate: Date.today(),
|
||||
Tag: null,
|
||||
StartOfWeek: 1, // Monday
|
||||
WorkHours: [0, 7.5, 7.5, 7.5, 7.5, 7.5, 0] // 0 = Sunday
|
||||
};
|
||||
}
|
||||
|
||||
get newTaskNode() {
|
||||
return {
|
||||
Order: null,
|
||||
ID: null,
|
||||
Name: "",
|
||||
Description: "",
|
||||
Tag: null,
|
||||
StartDate: null, // new Date(),
|
||||
FinishDate: null, // new Date(),
|
||||
StartDelay: 0, // Days
|
||||
Duration: 1, // Days
|
||||
PredecessorTaskID: null,
|
||||
IsCollated: false,
|
||||
CollatedTaskID: null,
|
||||
CalcWorkHours: 0,
|
||||
ActuWorkHours: null,
|
||||
Progress: 0,
|
||||
Resources: [],
|
||||
Level: 0,
|
||||
PredecessorTaskNo: null,
|
||||
Tasks: []
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
AddTask(task) {
|
||||
const a = this;
|
||||
const newTask = Object.assign(a.NewTask, task);
|
||||
const newTaskNode = Object.assign(a.newTaskNode, newTask);
|
||||
|
||||
if ((newTaskNode.PredecessorTaskID == null) && (newTaskNode.CollatedTaskID == null)) {
|
||||
a.Tasks.push(newTaskNode);
|
||||
} else if (newTaskNode.PredecessorTaskID != null) {
|
||||
const node = a.FindTask(newTaskNode.PredecessorTaskID);
|
||||
|
||||
if (node != null) {
|
||||
node.Tasks.push(newTaskNode);
|
||||
} else {
|
||||
a.log("Task not found (" + newTaskNode.PredecessorTaskID + ")");
|
||||
}
|
||||
} else if (newTaskNode.CollatedTaskID != null) {
|
||||
const node = a.FindTask(newTaskNode.CollatedTaskID);
|
||||
|
||||
if (node != null) {
|
||||
node.Tasks.push(newTaskNode);
|
||||
} else {
|
||||
a.log("Task not found (" + newTaskNode.CollatedTaskID + ")");
|
||||
}
|
||||
} else {
|
||||
a.log("Task not found (" + newTaskNode.ID + ")");
|
||||
}
|
||||
}
|
||||
|
||||
ClearTasks() {
|
||||
const a = this;
|
||||
|
||||
a.Tasks = [];
|
||||
}
|
||||
|
||||
ExportTasks() {
|
||||
const a = this;
|
||||
|
||||
let result = a.Tasks.copy().flatten("Tasks");
|
||||
for (var i=0; i<result.length; i++) {
|
||||
result[i].Order = (i + 1);
|
||||
|
||||
delete result[i].Tasks;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
FindTask(id) {
|
||||
const a = this;
|
||||
|
||||
let result = null;
|
||||
|
||||
a.Tasks.forEachTree("Tasks", function(e) {
|
||||
if (e.ID == id) {
|
||||
result = e;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get node type.
|
||||
* @returns {int} enum
|
||||
*
|
||||
* 0 = orphan
|
||||
* 1 = task no-parent/root
|
||||
* 2 = task no-parent/root, collated/group
|
||||
* 3 = task with parent
|
||||
* 4 = task with parent, collated/group
|
||||
* 5 = sub-task
|
||||
* 6 = sub-task, collated/group
|
||||
*
|
||||
*/
|
||||
GetNodeType(node) {
|
||||
if (node == null) {
|
||||
// 0 = orphan
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((node.PredecessorTaskID == null) && (node.CollatedTaskID == null) && (node.IsCollated == false)) {
|
||||
// 1 = task no-parent/root
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ((node.PredecessorTaskID == null) && (node.CollatedTaskID == null) && (node.IsCollated == true)) {
|
||||
// 2 = task no-parent/root, collated/group
|
||||
return 2;
|
||||
}
|
||||
|
||||
if ((node.PredecessorTaskID != null) && (node.CollatedTaskID == null) && (node.IsCollated == false)) {
|
||||
// 3 = task with parent
|
||||
return 3;
|
||||
}
|
||||
|
||||
if ((node.PredecessorTaskID != null) && (node.CollatedTaskID == null) && (node.IsCollated == true)) {
|
||||
// 4 = task with parent, collated/group
|
||||
return 4;
|
||||
}
|
||||
|
||||
if ((node.CollatedTaskID != null) && (node.IsCollated == false)) {
|
||||
// 5 = sub-task
|
||||
return 5;
|
||||
}
|
||||
|
||||
if ((node.CollatedTaskID != null) && (node.IsCollated == true)) {
|
||||
// 6 = sub-task, collated/group
|
||||
return 6;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Invalidate() {
|
||||
const a = this;
|
||||
let isSuccess = false;
|
||||
|
||||
if (!a.Recalculate()) {
|
||||
isSuccess = false;
|
||||
}
|
||||
|
||||
return isSuccess;
|
||||
}
|
||||
|
||||
Recalculate() {
|
||||
const a = this;
|
||||
let isSuccess = false;
|
||||
|
||||
a.Sort();
|
||||
|
||||
// Get flat references
|
||||
let result = a.Tasks.flatten("Tasks");
|
||||
|
||||
// Reset calculated values
|
||||
for (var i=0; i<result.length; i++) {
|
||||
result[i].Order = (i + 1);
|
||||
result[i].StartDate = null;
|
||||
result[i].FinishDate = null;
|
||||
result[i].CalcWorkHours = null;
|
||||
}
|
||||
|
||||
// Maximum 128 rounds
|
||||
for (var x=0; x<128; x++) {
|
||||
let pendingCount = result.copy().countMany(
|
||||
{ propName: "StartDate", value: null },
|
||||
{ propName: "FinishDate", value: null }
|
||||
);
|
||||
|
||||
// Done
|
||||
if (pendingCount <= 0) {
|
||||
isSuccess = true;
|
||||
break;
|
||||
}
|
||||
|
||||
a.log("Round " + (x + 1));
|
||||
|
||||
for (var i=0; i<result.length; i++) {
|
||||
const nodeType = a.GetNodeType(result[i]);
|
||||
|
||||
switch (nodeType) {
|
||||
case 1: // task no-parent/root
|
||||
result[i].StartDate = Date.addDays(a.Project.StartDate, result[i].StartDelay);
|
||||
result[i].FinishDate = Date.addDays(result[i].StartDate, result[i].Duration);
|
||||
break;
|
||||
case 2: // task no-parent/root, collated/group
|
||||
result[i].StartDate = Date.addDays(a.Project.StartDate, result[i].StartDelay);
|
||||
result[i].Progress = 0;
|
||||
|
||||
// update finish date, if possible
|
||||
a.recalculateCollatedTask(result, i);
|
||||
|
||||
break;
|
||||
case 3: // task with parent
|
||||
const node3 = result.first("ID", result[i].PredecessorTaskID);
|
||||
if (node3 != null) {
|
||||
if (node3.FinishDate != null) {
|
||||
result[i].StartDate = Date.addDays(node3.FinishDate, result[i].StartDelay);
|
||||
result[i].FinishDate = Date.addDays(result[i].StartDate, result[i].Duration);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case 4: // task with parent, collated/group
|
||||
const node4 = result.first("ID", result[i].PredecessorTaskID);
|
||||
if (node4 != null) {
|
||||
if (node4.FinishDate != null) {
|
||||
result[i].StartDate = Date.addDays(node4.FinishDate, result[i].StartDelay);
|
||||
result[i].Progress = 0;
|
||||
|
||||
// update finish date, if possible
|
||||
a.recalculateCollatedTask(result, i);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case 5: // sub-task
|
||||
const node5 = result.first("ID", result[i].CollatedTaskID);
|
||||
if (node5 != null) {
|
||||
if (node5.StartDate != null) {
|
||||
result[i].StartDate = Date.addDays(node5.StartDate, result[i].StartDelay);
|
||||
result[i].FinishDate = Date.addDays(result[i].StartDate, result[i].Duration);
|
||||
result[i].Level = (node5.Level + 1);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case 6: // sub-task, collated/group
|
||||
const node6 = result.first("ID", result[i].CollatedTaskID);
|
||||
if (node6 != null) {
|
||||
if (node6.StartDate != null) {
|
||||
result[i].StartDate = Date.addDays(node6.StartDate, result[i].StartDelay);
|
||||
result[i].Progress = 0;
|
||||
result[i].Level = (node6.Level + 1);
|
||||
|
||||
// update finish date, if possible
|
||||
a.recalculateCollatedTask(result, i);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
default: // orphan
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate work-hours
|
||||
a.recalculateWorkHours(result);
|
||||
|
||||
// Calculate predecessor
|
||||
a.recalculatePredecessorTaskNo(result);
|
||||
|
||||
return isSuccess;
|
||||
}
|
||||
|
||||
Sort() {
|
||||
const a = this;
|
||||
|
||||
a.Tasks.sortTree("Tasks", "StartDelay");
|
||||
}
|
||||
|
||||
log(message) {
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
recalculateWorkHours(array) {
|
||||
const a = this;
|
||||
|
||||
for (var i=0; i<array.length; i++) {
|
||||
if (array[i].StartDate == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (array[i].FinishDate == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (array[i].Duration <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let workHours = 0;
|
||||
let date = new Date(array[i].StartDate);
|
||||
for (let x=0; x<array[i].Duration; x++) {
|
||||
const d = date.getDay();
|
||||
|
||||
workHours += a.Project.WorkHours[d];
|
||||
}
|
||||
|
||||
array[i].CalcWorkHours = workHours;
|
||||
}
|
||||
}
|
||||
|
||||
recalculatePredecessorTaskNo(array) {
|
||||
const a = this;
|
||||
|
||||
for (var i=0; i<array.length; i++) {
|
||||
if (array[i].PredecessorTaskID == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const predecessor = a.FindTask(array[i].PredecessorTaskID);
|
||||
if (predecessor == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
array[i].PredecessorTaskNo = predecessor.Order;
|
||||
}
|
||||
}
|
||||
|
||||
recalculateCollatedTask(array, index) {
|
||||
let node2 = array.select("CollatedTaskID", array[index].ID);
|
||||
if (node2.length <= 0) {
|
||||
// No children
|
||||
array[index].Duration = 0;
|
||||
|
||||
return new Date(array[index].StartDate);
|
||||
}
|
||||
|
||||
// Not ready, calculation pending
|
||||
if (node2.any("FinishDate", null)) {
|
||||
array[index].Duration = 0;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
let node2FinishDate = new Date(array[index].StartDate);
|
||||
node2.forEach(e => {
|
||||
if (e.FinishDate > node2FinishDate) {
|
||||
node2FinishDate = e.FinishDate;
|
||||
}
|
||||
});
|
||||
|
||||
array[index].Progress = Math.average(node2.toList("Progress"));
|
||||
array[index].FinishDate = new Date(node2FinishDate);
|
||||
array[index].Duration = Date.diffDays(array[index].StartDate, array[index].FinishDate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default Project;
|
120
src/project/task-grid.js
Normal file
120
src/project/task-grid.js
Normal file
@ -0,0 +1,120 @@
|
||||
class ProjectTaskGrid {
|
||||
constructor(el) {
|
||||
const a = this;
|
||||
|
||||
a.Container = el;
|
||||
|
||||
a.Columns = [
|
||||
null,
|
||||
null,
|
||||
"Task Name",
|
||||
"Duration",
|
||||
"Start",
|
||||
"Finish",
|
||||
"Predecessor",
|
||||
"Resource Names",
|
||||
null
|
||||
];
|
||||
|
||||
a.initialiseComponents();
|
||||
}
|
||||
|
||||
initialiseComponents() {
|
||||
const a = this;
|
||||
|
||||
let htmlContent = "";
|
||||
htmlContent += "<table class='ryz-project'>";
|
||||
htmlContent += a.renderTHead();
|
||||
htmlContent += "<tbody>";
|
||||
htmlContent += a.renderPlaceholder();
|
||||
htmlContent += "</tbody>";
|
||||
htmlContent += "</table>";
|
||||
|
||||
a.Container.innerHTML = htmlContent;
|
||||
}
|
||||
|
||||
|
||||
Render(model) {
|
||||
const a = this;
|
||||
|
||||
let htmlContent = "";
|
||||
htmlContent += "<table class='ryz-project'>";
|
||||
htmlContent += a.renderTHead();
|
||||
htmlContent += "<tbody>";
|
||||
|
||||
model.forEach(e => {
|
||||
htmlContent += a.renderRow(e);
|
||||
});
|
||||
|
||||
htmlContent += "</tbody>";
|
||||
htmlContent += "</table>";
|
||||
|
||||
a.Container.innerHTML = htmlContent;
|
||||
}
|
||||
|
||||
|
||||
renderTHead() {
|
||||
const a = this;
|
||||
|
||||
let htmlContent = "";
|
||||
htmlContent += "<thead>";
|
||||
htmlContent += "<tr>";
|
||||
|
||||
a.Columns.forEach(e => {
|
||||
htmlContent += "<th>" + (e ?? "") + "</th>";
|
||||
});
|
||||
|
||||
htmlContent += "</tr>";
|
||||
htmlContent += "</thead>";
|
||||
|
||||
return htmlContent;
|
||||
}
|
||||
|
||||
renderPlaceholder() {
|
||||
const a = this;
|
||||
|
||||
let htmlContent = "";
|
||||
htmlContent += "<tr>";
|
||||
htmlContent += "<td></td>";
|
||||
htmlContent += "<td colspan='" + (a.Columns.length - 1) + "' class='c'>Loading...</td>";
|
||||
htmlContent += "</tr>";
|
||||
|
||||
return htmlContent;
|
||||
}
|
||||
|
||||
renderRow(e) {
|
||||
const a = this;
|
||||
|
||||
let htmlContent = "";
|
||||
|
||||
if (e.IsCollated == true) {
|
||||
htmlContent += "<tr class='b'>";
|
||||
} else {
|
||||
htmlContent += "<tr>";
|
||||
}
|
||||
|
||||
htmlContent += "<td class='c'>" + e.Order + "</td>";
|
||||
htmlContent += "<td></td>";
|
||||
htmlContent += "<td>";
|
||||
|
||||
for (let i=0; i<e.Level; i++) {
|
||||
htmlContent += "<span class='i'></span>";
|
||||
}
|
||||
|
||||
htmlContent += e.Name;
|
||||
htmlContent += "</td>";
|
||||
htmlContent += "<td>" + e.Duration + " day" + (parseInt(e.Duration) == 1 ? "" : "s") + "</td>";
|
||||
htmlContent += "<td class='c'>" + new Date(e.StartDate).toLocaleDateString() + "</td>";
|
||||
htmlContent += "<td class='c'>" + new Date(e.FinishDate).toLocaleDateString() + "</td>";
|
||||
htmlContent += "<td class='c'>" + (e.PredecessorTaskNo ?? "") + "</td>";
|
||||
htmlContent += "<td></td>";
|
||||
htmlContent += "<td></td>";
|
||||
htmlContent += "</tr>";
|
||||
|
||||
return htmlContent;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default ProjectTaskGrid;
|
17
webpack.config.js
Normal file
17
webpack.config.js
Normal file
@ -0,0 +1,17 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
extensions: './src/extensions.js',
|
||||
graphics: './src/graphics.js',
|
||||
project: './src/project.js'
|
||||
},
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
library: 'LiteRyzJS', // The global variable name under which the classes will be exposed
|
||||
libraryTarget: 'umd',
|
||||
globalObject: 'this'
|
||||
},
|
||||
mode: 'production', // development|production
|
||||
};
|
Loading…
Reference in New Issue
Block a user