Initial commit
This commit is contained in:
commit
2d7e645d94
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/dist
|
||||||
|
/node_modules
|
205
demo.html
Normal file
205
demo.html
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
<!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="" />
|
||||||
|
<title></title>
|
||||||
|
|
||||||
|
<script src="dist/project.dist.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
font-size: 10pt;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
float: left;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row:after {
|
||||||
|
content: "";
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="column left" id="projectPlanner1">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="column right">
|
||||||
|
|
||||||
|
<div id="ganttChart1"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
Document.ready = async function(fn) {
|
||||||
|
(async function() {
|
||||||
|
(document.readyState !== 'loading') ?
|
||||||
|
fn() : document.addEventListener('DOMContentLoaded',
|
||||||
|
function() {
|
||||||
|
fn();
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Document.ready(async function() {
|
||||||
|
var project1 = new LiteRyzJS.Project({ Name: "New Project 1" });
|
||||||
|
var taskGrid1 = null;
|
||||||
|
var ganttChart1 = null;
|
||||||
|
|
||||||
|
var tasks = [
|
||||||
|
{"Order":1,"ID":1,"Name":"Task A","Description":"","Tag":null,"StartDate":"2024-08-11T23:00:00.000Z","FinishDate":"2024-08-14T23:00:00.000Z","StartDelay":0,"Duration":3,"PredecessorTaskID":null,"IsCollated":false,"CollatedTaskID":null,"CalcWorkHours":22.5,"ActuWorkHours":null,"Progress":0,"Resources":[],"Level":0,"PredecessorTaskNo":null},
|
||||||
|
{"Order":2,"ID":2,"Name":"Task B","Description":"","Tag":null,"StartDate":"2024-08-14T23:00:00.000Z","FinishDate":"2024-08-28T23:00:00.000Z","StartDelay":0,"Duration":14,"PredecessorTaskID":1,"IsCollated":false,"CollatedTaskID":null,"CalcWorkHours":105,"ActuWorkHours":null,"Progress":0,"Resources":[],"Level":0,"PredecessorTaskNo":1},
|
||||||
|
{"Order":3,"ID":5,"Name":"Task B1","Description":"","Tag":null,"StartDate":"2024-08-29T23:00:00.000Z","FinishDate":"2024-09-26T23:00:00.000Z","StartDelay":1,"Duration":28,"PredecessorTaskID":2,"IsCollated":true,"CollatedTaskID":null,"CalcWorkHours":210,"ActuWorkHours":null,"Progress":25,"Resources":[],"Level":0,"PredecessorTaskNo":2},
|
||||||
|
{"Order":4,"ID":6,"Name":"Task B11","Description":"","Tag":null,"StartDate":"2024-09-02T23:00:00.000Z","FinishDate":"2024-09-26T23:00:00.000Z","StartDelay":4,"Duration":24,"PredecessorTaskID":null,"IsCollated":false,"CollatedTaskID":5,"CalcWorkHours":180,"ActuWorkHours":null,"Progress":0,"Resources":[],"Level":1,"PredecessorTaskNo":null},
|
||||||
|
{"Order":5,"ID":7,"Name":"Task B12","Description":"","Tag":null,"StartDate":"2024-08-30T23:00:00.000Z","FinishDate":"2024-09-25T23:00:00.000Z","StartDelay":1,"Duration":26,"PredecessorTaskID":null,"IsCollated":false,"CollatedTaskID":5,"CalcWorkHours":0,"ActuWorkHours":null,"Progress":50,"Resources":[],"Level":1,"PredecessorTaskNo":null},
|
||||||
|
{"Order":6,"ID":8,"Name":"Task E","Description":"","Tag":null,"StartDate":"2024-08-11T23:00:00.000Z","FinishDate":"2024-08-26T23:00:00.000Z","StartDelay":0,"Duration":15,"PredecessorTaskID":null,"IsCollated":true,"CollatedTaskID":null,"CalcWorkHours":112.5,"ActuWorkHours":null,"Progress":0,"Resources":[],"Level":0,"PredecessorTaskNo":null},
|
||||||
|
{"Order":7,"ID":3,"Name":"Task C","Description":"","Tag":null,"StartDate":"2024-08-26T23:00:00.000Z","FinishDate":"2024-09-13T23:00:00.000Z","StartDelay":0,"Duration":18,"PredecessorTaskID":8,"IsCollated":false,"CollatedTaskID":null,"CalcWorkHours":135,"ActuWorkHours":null,"Progress":0,"Resources":[],"Level":0,"PredecessorTaskNo":6},
|
||||||
|
{"Order":8,"ID":4,"Name":"Task D","Description":"","Tag":null,"StartDate":"2024-09-14T23:00:00.000Z","FinishDate":"2024-09-24T23:00:00.000Z","StartDelay":1,"Duration":10,"PredecessorTaskID":3,"IsCollated":false,"CollatedTaskID":null,"CalcWorkHours":0,"ActuWorkHours":null,"Progress":0,"Resources":[],"Level":0,"PredecessorTaskNo":7},
|
||||||
|
{"Order":9,"ID":9,"Name":"Task E1","Description":"","Tag":null,"StartDate":"2024-08-14T23:00:00.000Z","FinishDate":"2024-08-26T23:00:00.000Z","StartDelay":3,"Duration":12,"PredecessorTaskID":null,"IsCollated":false,"CollatedTaskID":8,"CalcWorkHours":90,"ActuWorkHours":null,"Progress":0,"Resources":[],"Level":1,"PredecessorTaskNo":null}]
|
||||||
|
;
|
||||||
|
tasks.forEach((e, i) => {
|
||||||
|
project1.AddTask(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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(taskData);
|
||||||
|
|
||||||
|
if (taskGrid1 == null) {
|
||||||
|
taskGrid1 = new LiteRyzJS.ProjectPlanner(document.getElementById("projectPlanner1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
taskGrid1.Load(taskData);
|
||||||
|
|
||||||
|
if (ganttChart1 == null) {
|
||||||
|
ganttChart1 = new LiteRyzJS.GanttChart(document.getElementById("ganttChart1"), { AutoSize: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
ganttChart1.Load(project1);
|
||||||
|
|
||||||
|
// console.log(JSON.stringify(taskData));
|
||||||
|
// console.log(JSON.stringify(project1.Tasks));
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
3100
package-lock.json
generated
Normal file
3100
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
package.json
Normal file
16
package.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "LiteRyzJS/Project",
|
||||||
|
"version": "0.2.0.248",
|
||||||
|
"devDependencies": {
|
||||||
|
"css-loader": "^7.1.2",
|
||||||
|
"sass": "^1.77.8",
|
||||||
|
"sass-loader": "^16.0.0",
|
||||||
|
"style-loader": "^4.0.0",
|
||||||
|
"webpack": "^5.93.0",
|
||||||
|
"webpack-cli": "^5.1.4"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack",
|
||||||
|
"watch": "webpack --watch"
|
||||||
|
}
|
||||||
|
}
|
11
src/index.js
Normal file
11
src/index.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// src/index.js
|
||||||
|
import Project from './project/project.js';
|
||||||
|
import ProjectPlanner from './project/project-planner.js';
|
||||||
|
import GanttChart from './project/gantt-chart.js';
|
||||||
|
|
||||||
|
|
||||||
|
export {
|
||||||
|
Project,
|
||||||
|
ProjectPlanner,
|
||||||
|
GanttChart
|
||||||
|
};
|
387
src/project/gantt-chart.js
Normal file
387
src/project/gantt-chart.js
Normal file
@ -0,0 +1,387 @@
|
|||||||
|
import Canvas from '../references/canvas.js';
|
||||||
|
|
||||||
|
import './project.scss';
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
a.#initialiseComponents();
|
||||||
|
}
|
||||||
|
|
||||||
|
#initialiseComponents() {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
if (!a.Canvas.el.classList.contains("literyzjs-project")) {
|
||||||
|
a.Canvas.el.classList.add("literyzjs-project");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.Canvas.FlowContainer.classList.contains("border")) {
|
||||||
|
a.Canvas.FlowContainer.classList.remove("border");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!a.Canvas.FlowContainer.classList.contains("gantt-chart")) {
|
||||||
|
a.Canvas.FlowContainer.classList.add("gantt-chart");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
get DefaultOptions() {
|
||||||
|
return {
|
||||||
|
DayWidth: 24,
|
||||||
|
HeaderRow: {
|
||||||
|
Height: [ 19, 20 ]
|
||||||
|
},
|
||||||
|
Row: {
|
||||||
|
Height: 28,
|
||||||
|
BackColour: "rgba(235, 235, 235, 0.3)",
|
||||||
|
Task: {
|
||||||
|
Height: 14,
|
||||||
|
BorderColour: "#555555",
|
||||||
|
FillColour: "#D8EEDB"
|
||||||
|
},
|
||||||
|
OrphanTask: {
|
||||||
|
Height: 14,
|
||||||
|
BorderColour: "#555555",
|
||||||
|
FillColour: "#9CC2E6"
|
||||||
|
},
|
||||||
|
CollatedTask: {
|
||||||
|
Height: 6,
|
||||||
|
BorderColour: "#555555",
|
||||||
|
FillColour: "#555555"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Column: {
|
||||||
|
SatColour: "rgba(233, 237, 239, 0.8)",
|
||||||
|
SunColour: "rgba(233, 237, 239, 0.9)",
|
||||||
|
},
|
||||||
|
Line: {
|
||||||
|
Colour: "#555555",
|
||||||
|
Width: 1,
|
||||||
|
ArrowSize: 5
|
||||||
|
},
|
||||||
|
DateFont: "7pt sans-serif",
|
||||||
|
DateForeColour: "#636363",
|
||||||
|
BorderWidth: 1,
|
||||||
|
BorderColour: "#B8B8B8",
|
||||||
|
BorderDashPattern: [1, 1],
|
||||||
|
ShowDateLines: true,
|
||||||
|
ShowStartDayOfWeekLine: true,
|
||||||
|
ShowRowLines: true,
|
||||||
|
ShowRowStripes: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get HeaderRow1Rectangle() {
|
||||||
|
return {
|
||||||
|
X: 0,
|
||||||
|
Y: 0,
|
||||||
|
W: this.Canvas.Width,
|
||||||
|
H: (this.Options.HeaderRow.Height[0] - this.Options.BorderWidth)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get HeaderRow2Rectangle() {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
return {
|
||||||
|
X: 0,
|
||||||
|
Y: this.Options.HeaderRow.Height[0],
|
||||||
|
W: this.Canvas.Width,
|
||||||
|
H: (this.Options.HeaderRow.Height[1] - this.Options.BorderWidth)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Canvas.Size = { W: width, H: height };
|
||||||
|
|
||||||
|
a.Canvas.Invalidate();
|
||||||
|
|
||||||
|
a.#drawChartHeader(a.Project.Duration);
|
||||||
|
a.#drawColumnLayout(a.Project.Duration, a.Project.Project.StartOfWeek);
|
||||||
|
a.#drawRowLayout(tasks);
|
||||||
|
a.#drawTasks(tasks);
|
||||||
|
a.#drawConnectorLines(tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
Load(project) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
a.Canvas.Clear();
|
||||||
|
|
||||||
|
if (project == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Project = project;
|
||||||
|
a.StartDate = new Date(project.StartDate);
|
||||||
|
|
||||||
|
a.Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
#drawChartHeader(projectDuration) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
const displayDays = projectDuration + 2;
|
||||||
|
|
||||||
|
if (a.Debug) {
|
||||||
|
a.Canvas.DrawRectangle(a.HeaderRow1Rectangle, "red", {});
|
||||||
|
a.Canvas.DrawRectangle(a.HeaderRow2Rectangle, "orange", {});
|
||||||
|
}
|
||||||
|
|
||||||
|
let startDate = new Date(a.StartDate);
|
||||||
|
startDate.addDays(-1);
|
||||||
|
|
||||||
|
// Draw horizontal line under months
|
||||||
|
a.#drawHorizontalLine(a.Options.HeaderRow.Height[0]);
|
||||||
|
|
||||||
|
// Draw vertical lines for dates
|
||||||
|
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), a.Options.BorderColour, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw horizontal line under dates
|
||||||
|
a.#drawHorizontalLine(a.HeaderHeight);
|
||||||
|
|
||||||
|
// 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[0],
|
||||||
|
W: a.Options.DayWidth - a.Options.BorderWidth,
|
||||||
|
H: (a.Options.HeaderRow.Height[1] - a.Options.BorderWidth)
|
||||||
|
};
|
||||||
|
|
||||||
|
a.Canvas.FillText(dateRectangle, date.getDate(), a.Options.DateFont, a.Options.DateForeColour);
|
||||||
|
|
||||||
|
if (a.Debug) {
|
||||||
|
a.Canvas.DrawRectangle(dateRectangle, "red", {});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#drawColumnLayout(projectDuration, startOfWeek) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
const height = a.Canvas.Height;
|
||||||
|
const displayDays = projectDuration + 2;
|
||||||
|
|
||||||
|
let startDate = new Date(a.StartDate);
|
||||||
|
startDate.addDays(-1);
|
||||||
|
|
||||||
|
// Write dates
|
||||||
|
for (let i=0; i<displayDays; i++) {
|
||||||
|
const date = Date.addDays(startDate, i);
|
||||||
|
const x = (a.Options.DayWidth * i);
|
||||||
|
|
||||||
|
const dateRectangle = {
|
||||||
|
X: x,
|
||||||
|
Y: a.HeaderHeight,
|
||||||
|
W: a.Options.DayWidth - a.Options.BorderWidth,
|
||||||
|
H: height - a.Options.BorderWidth
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fill background for Saturday and Sunday
|
||||||
|
if (date.getDay() == 6) {
|
||||||
|
a.Canvas.DrawRectangle(dateRectangle, a.Options.Column.SatColour, { FillColour: a.Options.Column.SatColour });
|
||||||
|
} else if (date.getDay() == 0) {
|
||||||
|
a.Canvas.DrawRectangle(dateRectangle, a.Options.Column.SunColour, { FillColour: a.Options.Column.SunColour });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw vertical date lines
|
||||||
|
if (startOfWeek == date.getDay()) {
|
||||||
|
if (a.Options.ShowStartDayOfWeekLine) {
|
||||||
|
a.Canvas.DrawVerticalLine(dateRectangle.X, a.HeaderHeight, dateRectangle.H, a.Options.BorderColour, {});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (a.Options.ShowDateLines) {
|
||||||
|
a.Canvas.DrawVerticalLine(dateRectangle.X, a.HeaderHeight, dateRectangle.H, a.Options.BorderColour, { LineDash: a.Options.BorderDashPattern });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#drawRowLayout(tasks) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
for (let i=0; i<tasks.length; i++) {
|
||||||
|
const rowRectangle = {
|
||||||
|
X: 0,
|
||||||
|
Y: (a.HeaderHeight + (a.Options.Row.Height * i)),
|
||||||
|
W: a.Canvas.Width,
|
||||||
|
H: (a.Options.Row.Height - a.Options.BorderWidth)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (a.Options.ShowRowStripes) {
|
||||||
|
if ((i % 2) == 1) {
|
||||||
|
a.Canvas.DrawRectangle(rowRectangle, a.Options.Row.BackColour, { FillColour: a.Options.Row.BackColour });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.Options.ShowRowLines) {
|
||||||
|
a.Canvas.DrawHorizontalLine(0, (rowRectangle.Y + a.Options.Row.Height), rowRectangle.W, a.Options.BorderColour, { LineDash: a.Options.BorderDashPattern });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#drawTasks(tasks) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
for (let i=0; i<tasks.length; i++) {
|
||||||
|
let style = a.Options.Row.Task;
|
||||||
|
|
||||||
|
if (tasks[i].IsCollated == true) {
|
||||||
|
style = a.Options.Row.CollatedTask;
|
||||||
|
} else if (tasks[i].PredecessorTaskNo == null) {
|
||||||
|
style = a.Options.Row.OrphanTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#drawConnectorLines(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 paddingX = (a.Options.Row.CollatedTask.Height + a.Options.BorderWidth);
|
||||||
|
const offsetX = (rectangle.X + paddingX);
|
||||||
|
|
||||||
|
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: offsetX, Y: (predecessorRectangle.Y + Math.half(predecessorRectangle.H)) });
|
||||||
|
|
||||||
|
points.push({ X: offsetX, Y: (rectangle.Y - paddingX) });
|
||||||
|
points.push({ X: offsetX, Y: rectangle.Y });
|
||||||
|
|
||||||
|
a.Canvas.DrawLines(points, a.Options.Line.Colour, { LineWidth: a.Options.Line.Width });
|
||||||
|
|
||||||
|
const arrowRectangle = {
|
||||||
|
X: (offsetX - 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#drawHorizontalLine(y) {
|
||||||
|
const a = this;
|
||||||
|
const width = a.Canvas.Width;
|
||||||
|
|
||||||
|
a.Canvas.DrawHorizontalLine(0, y, width, this.Options.BorderColour);
|
||||||
|
}
|
||||||
|
|
||||||
|
#getTaskRectangle(task, style) {
|
||||||
|
const a = this;
|
||||||
|
const i = (task.Order - 1);
|
||||||
|
|
||||||
|
const rowRectangle = {
|
||||||
|
X: 0,
|
||||||
|
Y: (a.HeaderHeight + (a.Options.Row.Height * i)),
|
||||||
|
W: a.Canvas.Width,
|
||||||
|
H: (a.Options.Row.Height - a.Options.BorderWidth)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (a.Debug) {
|
||||||
|
a.Canvas.DrawRectangle(rowRectangle, "red", {});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task.Duration <= 0) {
|
||||||
|
let result = {
|
||||||
|
X: (a.Options.DayWidth * (Date.diffDays(a.StartDate, task.StartDate) + 1)),
|
||||||
|
Y: (a.HeaderHeight + (a.Options.Row.Height * i) + Math.half(rowRectangle.H - style.Height)),
|
||||||
|
W: style.Height,
|
||||||
|
H: style.Height
|
||||||
|
};
|
||||||
|
|
||||||
|
result.X += Math.half(a.Options.DayWidth - (style.Height + a.Options.BorderWidth));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
X: (a.Options.DayWidth * (Date.diffDays(a.StartDate, task.StartDate) + 1)),
|
||||||
|
Y: (a.HeaderHeight + (a.Options.Row.Height * i) + Math.half(rowRectangle.H - style.Height)),
|
||||||
|
W: (a.Options.DayWidth * task.Duration),
|
||||||
|
H: style.Height
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default GanttChart;
|
133
src/project/project-planner.js
Normal file
133
src/project/project-planner.js
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import '../references/extensions.dist.js';
|
||||||
|
|
||||||
|
import './project.scss';
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectPlanner {
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (!a.Container.classList.contains("literyzjs-project")) {
|
||||||
|
a.Container.classList.add("literyzjs-project");
|
||||||
|
}
|
||||||
|
|
||||||
|
let htmlContent = "";
|
||||||
|
htmlContent += "<table class='project-planner'>";
|
||||||
|
htmlContent += a.#renderTHead();
|
||||||
|
htmlContent += "<tbody>";
|
||||||
|
htmlContent += a.#renderPlaceholder();
|
||||||
|
htmlContent += "</tbody>";
|
||||||
|
htmlContent += "</table>";
|
||||||
|
|
||||||
|
a.Container.innerHTML = htmlContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Load(model) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
if (!a.Container.classList.contains("literyzjs-project")) {
|
||||||
|
a.Container.classList.add("literyzjs-project");
|
||||||
|
}
|
||||||
|
|
||||||
|
let htmlContent = "";
|
||||||
|
htmlContent += "<table class='project-planner'>";
|
||||||
|
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 += "<input type='text' value='" + 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 ProjectPlanner;
|
444
src/project/project.js
Normal file
444
src/project/project.js
Normal file
@ -0,0 +1,444 @@
|
|||||||
|
import '../references/extensions.dist.js';
|
||||||
|
|
||||||
|
|
||||||
|
class Project {
|
||||||
|
constructor(options) {
|
||||||
|
const a = this;
|
||||||
|
const _options = Object.assign(a.DefaultOptions, options);
|
||||||
|
|
||||||
|
a.Project = _options;
|
||||||
|
a.Tasks = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 DefaultOptions() {
|
||||||
|
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) {
|
||||||
|
let 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) {
|
||||||
|
let 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.#sortNode(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#sortNode(node) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
if (Array.isEmpty(node.Tasks)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Tasks.orderBy("StartDelay");
|
||||||
|
|
||||||
|
const childDependentTasks = node.Tasks.notSelect("PredecessorTaskID", null);
|
||||||
|
if (childDependentTasks.length > 0) {
|
||||||
|
node.Tasks.removeRange(childDependentTasks);
|
||||||
|
node.Tasks.addRange(childDependentTasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Tasks.forEach(e => {
|
||||||
|
a.#sortNode(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default Project;
|
133
src/project/project.scss
Normal file
133
src/project/project.scss
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
.literyzjs-project {
|
||||||
|
|
||||||
|
.project-planner {
|
||||||
|
border-spacing: 0px;
|
||||||
|
border-collapse: separate;
|
||||||
|
cursor: default;
|
||||||
|
padding: 0px 0px 30px 0px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.b {
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.i {
|
||||||
|
display: inline-block;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
tr {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.8em;
|
||||||
|
user-select: none;
|
||||||
|
height: 40px;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: bottom;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
th:first-child {
|
||||||
|
background-color: inherit;
|
||||||
|
border-left-width: 0px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
th:last-child {
|
||||||
|
border-right-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody {
|
||||||
|
tr {
|
||||||
|
input[type="text"] {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: default;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:first-child {
|
||||||
|
border-right-width: 1px;
|
||||||
|
color: #7E7E7E;
|
||||||
|
font-size: 0.8em;
|
||||||
|
text-align: center;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:last-child {
|
||||||
|
border-right-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:hover {
|
||||||
|
td:first-child {
|
||||||
|
background-color: #D1F2C7;
|
||||||
|
color: #3E7138;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:not(:first-child) {
|
||||||
|
background-color: rgba(233, 237, 239, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(odd) {
|
||||||
|
background-color: rgba(233, 237, 239, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.gantt-chart {
|
||||||
|
border-color: #B8B8B8;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #CDCDCD #F0F0F0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-chart.scroll-x {
|
||||||
|
overflow-x: scroll !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
530
src/references/canvas.js
Normal file
530
src/references/canvas.js
Normal file
@ -0,0 +1,530 @@
|
|||||||
|
class Canvas {
|
||||||
|
|
||||||
|
constructor(el) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
a.el = el;
|
||||||
|
|
||||||
|
a.BorderWidth = 2;
|
||||||
|
a.ScrollbarWidth = 11;
|
||||||
|
a.Padding = {
|
||||||
|
Top: 0,
|
||||||
|
Right: 0,
|
||||||
|
Bottom: 0,
|
||||||
|
Left: 0
|
||||||
|
};
|
||||||
|
a.EnableDock = true;
|
||||||
|
|
||||||
|
a.#initialiseComponents();
|
||||||
|
}
|
||||||
|
|
||||||
|
#initialiseComponents() {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
if (a.el == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.el.innerHTML = "<div class='border'><canvas></canvas></div>";
|
||||||
|
|
||||||
|
a.Invalidate();
|
||||||
|
a.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
get FlowContainer() {
|
||||||
|
return this.el.getElementsByTagName("div")[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
get CanvasContainer() {
|
||||||
|
return this.FlowContainer.getElementsByTagName("canvas")[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
get CanvasContext() {
|
||||||
|
return this.CanvasContainer.getContext("2d");
|
||||||
|
}
|
||||||
|
|
||||||
|
get Height() {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
if (a.CanvasContainer == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.#getHeight(a.CanvasContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
set Height(value) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
// if (a.el != null) {
|
||||||
|
// a.el.style.height = value + "px";
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (a.FlowContainer != null) {
|
||||||
|
a.FlowContainer.style.height = value + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.CanvasContainer != null) {
|
||||||
|
a.CanvasContainer.style.height = value + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.CanvasContext != null) {
|
||||||
|
a.CanvasContext.canvas.height = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get Width() {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
if (a.CanvasContainer == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.#getWidth(a.CanvasContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
set Width(value) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
// if (a.el != null) {
|
||||||
|
// a.el.style.width = value + "px";
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (a.FlowContainer != null) {
|
||||||
|
a.FlowContainer.style.width = value + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.CanvasContainer != null) {
|
||||||
|
a.CanvasContainer.style.width = value + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.CanvasContext != null) {
|
||||||
|
a.CanvasContext.canvas.width = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get Size() {
|
||||||
|
return {
|
||||||
|
W: this.Width,
|
||||||
|
H: this.Height
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
set Size(value) {
|
||||||
|
this.Width = value.W;
|
||||||
|
this.Height = value.H;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.CanvasContext == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.CanvasContext.clearRect(0, 0, a.CanvasContext.canvas.width, a.CanvasContext.canvas.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawArrowS(rectangle, penColour, options) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
if (a.CanvasContext == 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.CanvasContext.beginPath();
|
||||||
|
a.CanvasContext.strokeStyle = penColour;
|
||||||
|
a.CanvasContext.lineWidth = opt.LineWidth;
|
||||||
|
a.CanvasContext.setLineDash(opt.LineDash);
|
||||||
|
|
||||||
|
a.CanvasContext.moveTo(rectangle.X, rectangle.Y);
|
||||||
|
a.CanvasContext.lineTo((rectangle.X + rectangle.W), rectangle.Y);
|
||||||
|
a.CanvasContext.lineTo((rectangle.X + Math.half(rectangle.W)), (rectangle.Y + rectangle.H));
|
||||||
|
|
||||||
|
a.CanvasContext.closePath();
|
||||||
|
|
||||||
|
if (opt.FillColour != null) {
|
||||||
|
a.CanvasContext.fillStyle = opt.FillColour;
|
||||||
|
a.CanvasContext.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
a.CanvasContext.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawCircle(x, y, width, penColour, options) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
if (a.CanvasContext == 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.CanvasContext.beginPath();
|
||||||
|
a.CanvasContext.strokeStyle = penColour;
|
||||||
|
a.CanvasContext.lineWidth = opt.LineWidth;
|
||||||
|
a.CanvasContext.setLineDash(opt.LineDash);
|
||||||
|
|
||||||
|
a.CanvasContext.arc(rectangle.X, rectangle.Y, rectangle.W, 0, 2 * Math.PI, false);
|
||||||
|
|
||||||
|
a.CanvasContext.closePath();
|
||||||
|
|
||||||
|
if (opt.FillColour != null) {
|
||||||
|
a.CanvasContext.fillStyle = opt.FillColour;
|
||||||
|
a.CanvasContext.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
a.CanvasContext.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawDiamond(rectangle, penColour, options) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
if (a.CanvasContext == 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.CanvasContext.beginPath();
|
||||||
|
a.CanvasContext.strokeStyle = penColour;
|
||||||
|
a.CanvasContext.lineWidth = opt.LineWidth;
|
||||||
|
a.CanvasContext.setLineDash(opt.LineDash);
|
||||||
|
|
||||||
|
a.CanvasContext.moveTo((rectangle.X + Math.half(rectangle.W)), rectangle.Y);
|
||||||
|
a.CanvasContext.lineTo((rectangle.X + rectangle.W), (rectangle.Y + Math.half(rectangle.H)));
|
||||||
|
a.CanvasContext.lineTo((rectangle.X + Math.half(rectangle.W)), (rectangle.Y + rectangle.H));
|
||||||
|
a.CanvasContext.lineTo(rectangle.X, (rectangle.Y + Math.half(rectangle.H)));
|
||||||
|
|
||||||
|
a.CanvasContext.closePath();
|
||||||
|
|
||||||
|
if (opt.FillColour != null) {
|
||||||
|
a.CanvasContext.fillStyle = opt.FillColour;
|
||||||
|
a.CanvasContext.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
a.CanvasContext.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawRectangle(rectangle, penColour, options) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
if (a.CanvasContext == 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.CanvasContext.beginPath();
|
||||||
|
a.CanvasContext.strokeStyle = penColour;
|
||||||
|
a.CanvasContext.lineWidth = opt.LineWidth;
|
||||||
|
a.CanvasContext.setLineDash(opt.LineDash);
|
||||||
|
a.CanvasContext.rect(rectangle.X, rectangle.Y, rectangle.W, rectangle.H);
|
||||||
|
|
||||||
|
if (opt.FillColour != null) {
|
||||||
|
a.CanvasContext.fillStyle = opt.FillColour;
|
||||||
|
a.CanvasContext.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
a.CanvasContext.closePath();
|
||||||
|
a.CanvasContext.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawLine(startX, startY, finishX, finishY, penColour, options) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
if (a.CanvasContext == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const opt = Object.assign({
|
||||||
|
LineWidth: 1,
|
||||||
|
LineDash: []
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
startY -= 0.5;
|
||||||
|
// y -= penWidth;
|
||||||
|
|
||||||
|
a.CanvasContext.beginPath();
|
||||||
|
a.CanvasContext.strokeStyle = penColour;
|
||||||
|
a.CanvasContext.lineWidth = opt.LineWidth;
|
||||||
|
a.CanvasContext.setLineDash(opt.LineDash);
|
||||||
|
a.CanvasContext.moveTo(startX, startY);
|
||||||
|
a.CanvasContext.lineTo(finishX, finishY);
|
||||||
|
a.CanvasContext.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawLines(points, penColour, options) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
if (a.CanvasContext == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (points == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (points.length <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const opt = Object.assign({
|
||||||
|
LineWidth: 1,
|
||||||
|
LineDash: []
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
a.CanvasContext.beginPath();
|
||||||
|
a.CanvasContext.strokeStyle = penColour;
|
||||||
|
a.CanvasContext.lineWidth = opt.LineWidth;
|
||||||
|
a.CanvasContext.setLineDash(opt.LineDash);
|
||||||
|
|
||||||
|
a.CanvasContext.moveTo(points[0].X, points[0].Y);
|
||||||
|
|
||||||
|
for (let i=1; i<points.length; i++) {
|
||||||
|
a.CanvasContext.lineTo(points[i].X, points[i].Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
a.CanvasContext.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawHorizontalLine(x, y, width, penColour, options) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
if (a.CanvasContext == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const opt = Object.assign({
|
||||||
|
LineWidth: 1,
|
||||||
|
LineDash: []
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
y -= 0.5;
|
||||||
|
// y -= penWidth;
|
||||||
|
|
||||||
|
a.CanvasContext.beginPath();
|
||||||
|
a.CanvasContext.strokeStyle = penColour;
|
||||||
|
a.CanvasContext.lineWidth = opt.LineWidth;
|
||||||
|
a.CanvasContext.setLineDash(opt.LineDash);
|
||||||
|
a.CanvasContext.moveTo(x, y);
|
||||||
|
a.CanvasContext.lineTo((x + width), y);
|
||||||
|
a.CanvasContext.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawText(x, y, text, font, foreColour, options) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
if (a.CanvasContext == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const opt = Object.assign({
|
||||||
|
Align: "left"
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
a.CanvasContext.font = font;
|
||||||
|
a.CanvasContext.fillStyle = foreColour;
|
||||||
|
a.CanvasContext.textAlign = opt.Align;
|
||||||
|
a.CanvasContext.textBaseline = "top";
|
||||||
|
|
||||||
|
a.CanvasContext.fillText(text, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawVerticalLine(x, y, height, penColour, options) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
if (a.CanvasContext == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const opt = Object.assign({
|
||||||
|
LineWidth: 1,
|
||||||
|
LineDash: []
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
x -= 0.5;
|
||||||
|
y -= opt.LineWidth;
|
||||||
|
|
||||||
|
a.CanvasContext.beginPath();
|
||||||
|
a.CanvasContext.strokeStyle = penColour;
|
||||||
|
a.CanvasContext.lineWidth = opt.LineWidth;
|
||||||
|
a.CanvasContext.setLineDash(opt.LineDash);
|
||||||
|
a.CanvasContext.moveTo(x, y);
|
||||||
|
a.CanvasContext.lineTo(x, (y + height));
|
||||||
|
a.CanvasContext.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
FillText(rectangle, text, font, foreColour, options) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
if (a.CanvasContext == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const opt = Object.assign({
|
||||||
|
Align: "center",
|
||||||
|
VAlign: "middle"
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
a.CanvasContext.font = font;
|
||||||
|
a.CanvasContext.fillStyle = foreColour;
|
||||||
|
a.CanvasContext.textAlign = opt.Align;
|
||||||
|
// a._ctx.textBaseline = verticalAlign;
|
||||||
|
a.CanvasContext.textBaseline = "top";
|
||||||
|
|
||||||
|
const size = a.CanvasContext.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.CanvasContext.fillText(text, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
Invalidate() {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
if (a.EnableDock) {
|
||||||
|
a.FlowContainer.style.width = "100%";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.#getWidth(a.CanvasContainer) > (a.#getWidth(a.FlowContainer) + (a.BorderWidth * 2))) {
|
||||||
|
a.FlowContainer.classList.add("scroll-x");
|
||||||
|
a.FlowContainer.style.height = (a.#getHeight(a.CanvasContainer) + a.ScrollbarWidth) + "px";
|
||||||
|
} else {
|
||||||
|
a.FlowContainer.classList.remove("scroll-x");
|
||||||
|
a.FlowContainer.style.height = a.#getHeight(a.CanvasContainer) + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
MeasureText(font, value) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
if (a.CanvasContext == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.CanvasContext.font = font;
|
||||||
|
|
||||||
|
const size = a.CanvasContext.measureText(value);
|
||||||
|
|
||||||
|
return {
|
||||||
|
W: size.width,
|
||||||
|
H: Math.round(size.fontBoundingBoxDescent + size.fontBoundingBoxAscent)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#getWidth(el) {
|
||||||
|
if (el == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof(el) == "undefined") {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (el.offsetWidth || el.innerWidth || el.clientWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
#getHeight(el) {
|
||||||
|
if (el == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof(el) == "undefined") {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (el.offsetHeight || el.innerHeight || el.clientHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default Canvas;
|
6
src/references/extensions.dist.js
Normal file
6
src/references/extensions.dist.js
Normal file
File diff suppressed because one or more lines are too long
60
webpack.config.js
Normal file
60
webpack.config.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const { version } = require('./package.json');
|
||||||
|
const webpack = require('webpack');
|
||||||
|
|
||||||
|
|
||||||
|
class PrependVersionPlugin {
|
||||||
|
apply(compiler) {
|
||||||
|
compiler.hooks.emit.tapAsync('PrependVersionPlugin', (compilation, callback) => {
|
||||||
|
Object.keys(compilation.assets).forEach((filename) => {
|
||||||
|
if (filename.endsWith('.js')) {
|
||||||
|
const asset = compilation.assets[filename];
|
||||||
|
|
||||||
|
const headerText = `/*!\n * LiteRyzJS/Project v${version}\n * Copyright 2023-2024 Ray Lam (https://www.hiimray.co.uk)\n *\n */\n`;
|
||||||
|
|
||||||
|
const newContent = headerText + asset.source();
|
||||||
|
|
||||||
|
compilation.assets[filename] = {
|
||||||
|
source: () => newContent,
|
||||||
|
size: () => newContent.length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: {
|
||||||
|
project: './src/index.js'
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
filename: `[name].dist.js`,
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
library: 'LiteRyzJS',
|
||||||
|
libraryTarget: 'umd',
|
||||||
|
globalObject: 'this'
|
||||||
|
},
|
||||||
|
mode: 'production', // development|production
|
||||||
|
plugins: [
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env.VERSION': JSON.stringify(version)
|
||||||
|
}),
|
||||||
|
new PrependVersionPlugin()
|
||||||
|
],
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.scss$/,
|
||||||
|
use: [
|
||||||
|
'style-loader',
|
||||||
|
'css-loader',
|
||||||
|
'sass-loader'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user