Skip to content

Commit cdbe383

Browse files
committed
Zware refactoring van situatieplan tekenmodule met het oog op stabiliteit en performance
- Nieuwe class ElectroItemZoeker - Nieuwe file voor geometrische functies - Nieuwe class voor drag en drop functies - Code om missing links naar eendraadschema te verwijderen verplaatst naar SituationPlan - Nieuwe eigenschap needsViewUpdate in SituationPlanElement om te vermijden dan alles altijd opnieuw getekend wordt - Hernoemen van de file waarin de eigenschappen van situatieplan elementen kunnen gewijzigd worden - Aanzienlijke wijzigingen in SituationPlanView met het oog op performance, vooral in google Chrome. Door gebruik te maken van een parameter needsViewUpdate en van documentfragments wordt het aantal DOM updates sterk terug gedrongen en komt de performance van google chrome opnieuw in de buurt van die van firefox - class EventManager heeft zijn eigen file gekregen - outerbox heet nu outerdiv - Er kan niet meer geklikked worden op de undo functies in de ribbon als er geen undo mogelijk is.
1 parent f3349b6 commit cdbe383

18 files changed

+1434
-768
lines changed

builddate.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
var CONF_builddate="20250105-150906"
1+
var CONF_builddate="20250112-145253"

css/styles.css

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
:root {
44
--selectPadding: 2;
5+
--paperPadding: 10px;
56

67
--menu-height: 40px;
78
--ribbon-height: 68px;
@@ -48,9 +49,6 @@ body {
4849
display: block;
4950
}
5051

51-
52-
53-
5452
button, select, select option, input[type="checkbox"] {
5553
cursor: pointer;
5654
}
@@ -278,7 +276,7 @@ ul#minitabs a:hover {
278276
flex-direction: row; /* Ensure columns are side by side */
279277
}
280278

281-
#outerbox {
279+
#outerdiv {
282280
display: flex;
283281
position: absolute;
284282
top: calc(var(--menu-height) + var(--ribbon-height));
@@ -292,8 +290,8 @@ ul#minitabs a:hover {
292290
}
293291

294292
#paper {
295-
top: 10px;
296-
left: 10px;
293+
top: var(--paperPadding);
294+
left: var(--paperPadding);
297295
width: 277mm; /* Useful drawing area on A4 */
298296
height: 150mm; /* Useful drawing area on A4 */
299297
background-color: white;

eendraadschema.js

Lines changed: 693 additions & 362 deletions
Large diffs are not rendered by default.

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
<div id="right_col_inner"></div>
3636
</div>
3737
</div>
38-
<div id="outerbox" style="display:none;"> <!-- Situatieschets -->
38+
<div id="outerdiv" style="display:none;"> <!-- Situatieschets -->
3939
<div id="paper"></div>
4040
</div>
4141
</div>

src/EventManager.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* Manages the addition and removal of event listeners on HTML elements.
3+
*/
4+
class EventManager {
5+
private listeners: { element: HTMLElement, type: string, listener: EventListenerOrEventListenerObject }[] = [];
6+
7+
/**
8+
* Adds an event listener to a specified HTML element. If a listener of the same
9+
* type already exists on the element, it is removed before adding the new one.
10+
*
11+
* @param element - The HTML element to attach the event listener to.
12+
* @param type - The type of the event.
13+
* @param listener - The event listener function or object.
14+
*/
15+
addEventListener(element: HTMLElement, type: string, listener: EventListenerOrEventListenerObject) {
16+
const existingListenerIndex = this.listeners.findIndex(l => l.element === element && l.type === type);
17+
if (existingListenerIndex !== -1) {
18+
const existingListener = this.listeners[existingListenerIndex];
19+
element.removeEventListener(type, existingListener.listener);
20+
this.listeners.splice(existingListenerIndex, 1);
21+
}
22+
23+
this.listeners.push({ element, type, listener });
24+
element.addEventListener(type, listener);
25+
}
26+
27+
/**
28+
* Removes all event listeners managed by this EventManager instance.
29+
*/
30+
removeAllEventListeners() {
31+
this.listeners.forEach(({ element, type, listener }) => {
32+
element.removeEventListener(type, listener);
33+
});
34+
this.listeners = [];
35+
}
36+
37+
/**
38+
* Disposes of the EventManager by removing all event listeners.
39+
*/
40+
dispose() {
41+
this.removeAllEventListeners();
42+
}
43+
}

src/Hierarchical_List.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -525,11 +525,11 @@ class Hierarchical_List {
525525

526526
// Plaats bovenaan de switch van editeer-mode (teken of verplaats) --
527527
output += `
528-
<div class="icon" onclick="undoClicked()" ${(undostruct.undoStackSize() > 0 ? "" : "style=\"filter: opacity(45%)\"")}>
528+
<div class="icon" ${(undostruct.undoStackSize() > 0 ? 'onclick="undoClicked()"' : "style=\"filter: opacity(45%)\"")}>
529529
<img src="gif/undo.png" alt="Ongedaan maken" class="icon-image">
530530
<span class="icon-text">Ongedaan maken</span>
531531
</div>
532-
<div class="icon" onclick="redoClicked()" ${(undostruct.redoStackSize() > 0 ? "" : "style=\"filter: opacity(45%)\"")}>
532+
<div class="icon" ${(undostruct.redoStackSize() > 0 ? 'onclick="redoClicked()"' : "style=\"filter: opacity(45%)\"")}>
533533
<img src="gif/redo.png" alt="Opnieuw" class="icon-image">
534534
<span class="icon-text">Opnieuw</span>
535535
</div>

src/general.ts

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,24 @@ function deepClone (obj) {
1717
return _out;
1818
}
1919

20+
/**
21+
* Returns true if the current mode is a development mode.
22+
* This is determined by the presence of a 'dev' parameter in the URL.
23+
*
24+
* @returns {boolean} True if this is a development mode, false otherwise.
25+
*/
26+
27+
function isDevMode(): boolean {
28+
try {
29+
const urlParams = new URLSearchParams(window.location.search);
30+
return urlParams.has('dev');
31+
} catch (error) {
32+
console.error('Error checking for dev mode:', error);
33+
return false;
34+
}
35+
}
36+
37+
2038
// Function for length of a string in 8 bit bytes
2139
const byteSize = str => new Blob([str]).size;
2240

@@ -35,22 +53,6 @@ function isInt(value) {
3553
!isNaN(parseInt(value, 10));
3654
}
3755

38-
function getPixelsPerMillimeter() {
39-
const div = document.createElement('div');
40-
div.style.width = '10mm';
41-
div.style.position = 'absolute';
42-
document.body.appendChild(div);
43-
const widthInPixels = div.offsetWidth;
44-
document.body.removeChild(div);
45-
const pixelsPerMillimeter = widthInPixels / 10;
46-
return pixelsPerMillimeter;
47-
}
48-
49-
// Example usage
50-
const pixelsPerMM = getPixelsPerMillimeter();
51-
console.log(`Your browser uses approximately ${pixelsPerMM} pixels per millimeter.`);
52-
53-
5456
function svgTextWidth(input:String, fontsize:Number = 10, options:String = '') {
5557
const div = document.createElement('div');
5658
div.innerHTML = '<svg width="1000" height="20"><text x="0" y="10" style="text-anchor:start" font-family="Arial, Helvetica, sans-serif" font-size="' + Number(fontsize) + '" ' + options + '>' + input + '</text></svg>';
@@ -59,8 +61,8 @@ function svgTextWidth(input:String, fontsize:Number = 10, options:String = '') {
5961

6062
/*if (document.getElementById("configsection").style.display === 'block') {
6163
tryoutdiv = document.getElementById("configsection") as HTMLElement;
62-
} else if (document.getElementById("outerbox").style.display === 'block') {
63-
tryoutdiv = document.getElementById("outerbox") as HTMLElement;
64+
} else if (document.getElementById("outerdiv").style.display === 'block') {
65+
tryoutdiv = document.getElementById("outerdiv") as HTMLElement;
6466
} else {
6567
tryoutdiv = document.getElementById("right_col_inner") as HTMLElement;
6668
}*/

src/main.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -291,18 +291,18 @@ function toggleAppView(type: '2col' | 'config' | 'draw') {
291291
structure.properties.currentView = type;
292292
if (type === '2col') {
293293
document.getElementById("configsection").style.display = 'none';
294-
document.getElementById("outerbox").style.display = 'none';
294+
document.getElementById("outerdiv").style.display = 'none';
295295
document.getElementById("ribbon").style.display = 'flex';
296296
document.getElementById("canvas_2col").style.display = 'flex';
297297
structure.updateRibbon();
298298
} else if (type === 'config') {
299299
document.getElementById("configsection").style.display = 'block';
300-
document.getElementById("outerbox").style.display = 'none';
300+
document.getElementById("outerdiv").style.display = 'none';
301301
document.getElementById("ribbon").style.display = 'none';
302302
document.getElementById("canvas_2col").style.display = 'none';
303303
} else if (type === 'draw') {
304304
document.getElementById("configsection").style.display = 'none';
305-
document.getElementById("outerbox").style.display = 'flex';
305+
document.getElementById("outerdiv").style.display = 'flex';
306306
document.getElementById("ribbon").style.display = 'flex';
307307
document.getElementById("canvas_2col").style.display = 'none';
308308
}

src/sitplan/ElectroItemZoeker.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**
2+
* Class gebruikt in SituationPlanView om te zoeken naar electroitems op basis van de kringnaam.
3+
* Dit laat toe items to selecteren uit het volledige eendraadschema en ze te plaatsen op het situatieschema.
4+
*
5+
* Deze class refereert naar de volgende globale variabelen:
6+
* - structure
7+
*/
8+
9+
class ElectroItemZoeker {
10+
11+
private excludedTypes = ['Aansluiting','Bord','Kring','Domotica','Domotica module (verticaal)',
12+
'Domotica gestuurde verbruiker','Leiding','Splitsing','Verlenging',
13+
'Vrije ruimte','Meerdere verbruikers'];
14+
15+
private data: {id: number, kringnaam: string, adres: string, type: string}[] = [];
16+
17+
/**
18+
* Constructor van de ElectroItemZoeker.
19+
*
20+
* Initialiseert de lijst van alle toegestane ElectroItems in het situatieplan.
21+
*/
22+
23+
constructor() {
24+
this.reCalculate();
25+
}
26+
27+
/**
28+
* Geeft de lijst van alle toegestane ElectroItems in het situatieplan retour.
29+
* @returns {Object[]} een lijst van objecten met de volgende structuur:
30+
* {id: number, kringnaam: string, adres: string, type: string}
31+
*/
32+
33+
getData() {
34+
return this.data;
35+
}
36+
37+
/**
38+
* Geeft een lijst van alle unieke kringnamen retour uit de lijst van ElectroItems.
39+
* @returns {string[]} een lijst van unieke kringnamen.
40+
*/
41+
42+
getUniqueKringnaam(): string[] {
43+
return Array.from(new Set(this.data.map(x => x.kringnaam)));
44+
}
45+
46+
/**
47+
* Geeft een lijst van alle ElectroItems retour die behoren tot de kring met de naam 'kringnaam'.
48+
* @param {string} kringnaam - de naam van de kring.
49+
* @returns {Object[]} een lijst van objecten met de volgende structuur:
50+
* {id: number, adres: string, type: string}
51+
*/
52+
53+
getElectroItemsByKring(kringnaam: string): {id: number, adres: string, type: string}[] {
54+
return this.data.filter(x => x.kringnaam === kringnaam).map(x => ({id: x.id, adres: x.adres, type: x.type}));
55+
}
56+
57+
/**
58+
* Rekent de lijst van alle toegestane ElectroItems opnieuw uit.
59+
*
60+
* Deze methode wordt gebruikt om de lijst van ElectroItems te vullen die in het situatieplan gebruikt mogen worden.
61+
* De lijst wordt opnieuw uitgerekend door de volgende stappen:
62+
* 1. Doorlopen alle actieve ElectroItems in de structuur.
63+
* 2. Voor elke ElectroItem worden de kringnaam en het type bepaald.
64+
* 3. Als de kringnaam niet leeg is en het type niet voorkomt in de lijst van uitgesloten types, dan wordt de ElectroItem toegevoegd aan de lijst.
65+
* 4. De ElectroItem wordt toegevoegd met de volgende structuur: {id: number, kringnaam: string, adres: string, type: string}
66+
*/
67+
68+
reCalculate() {
69+
for (let i = 0; i<structure.length; i++) {
70+
if (structure.active[i]) {
71+
let id:number = structure.id[i];
72+
let kringnaam:string = structure.findKringName(id).trim();
73+
if (kringnaam != '') {
74+
let type:string = (structure.data[i] as Electro_Item).getType();
75+
if ( (type != null) && (this.excludedTypes.indexOf(type) === -1) ) {
76+
let adres:string = (structure.data[i] as Electro_Item).getReadableAdres();
77+
this.data.push({id: id, kringnaam: kringnaam, adres:adres, type: type});
78+
}
79+
}
80+
}
81+
}
82+
}
83+
84+
}

src/sitplan/GeometricFunctions.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Functie die de breedte en hoogte van een rechthoek als invoer neemt, evenals een rotatie rond het midden van de rechthoek.
3+
* De functie retourneert de breedte en hoogte van de kleinste rechthoek die de geroteerde rechthoek omsluit met zijden langs de X- en Y-assen.
4+
*/
5+
6+
function getRotatedRectangleSize(width: number, height: number, rotation: number) {
7+
const rotationInRadians = rotation * Math.PI / 180;
8+
const cos = Math.cos(rotationInRadians);
9+
const sin = Math.sin(rotationInRadians);
10+
const rotatedWidth = Math.abs(width * cos) + Math.abs(height * sin);
11+
const rotatedHeight = Math.abs(width * sin) + Math.abs(height * cos);
12+
return { width: rotatedWidth, height: rotatedHeight };
13+
}
14+
15+
/**
16+
* Functie die de breedte en hoogte van een rechthoek als invoer neemt, evenals een rotatie rond het midden van de rechthoek.
17+
* De functie retourneert de breedte en hoogte van de rechthoek die voldoet aan de volgende eigenschappen:
18+
* - De zijden zijn parallel aan de X-as en de Y-as.
19+
* - De rechthoek snijdt de X-as en Y-as in dezelfde punten als de originele geroteerde rechthoek
20+
*
21+
* Deze functie kan gebruikt worden om de locatie van labels te bepalen.
22+
*/
23+
24+
function getXYRectangleSize(width: number, height: number, rotation: number) {
25+
rotation = Math.abs(rotation) % 180;
26+
if (rotation > 90) rotation = 180 - rotation;
27+
const rotationInRadians = rotation * Math.PI / 180;
28+
const cos = Math.cos(rotationInRadians);
29+
const sin = Math.sin(rotationInRadians);
30+
return { width: Math.min(width/cos,height/sin), height: Math.min(width/sin,height/cos) };
31+
}
32+
33+
/**
34+
* Cache het resultaat van getPixelsPerMillimeter() om de overhead van het maken en verwijderen van een DOM-element bij elke oproep te voorkomen.
35+
*/
36+
let cachedPixelsPerMillimeter: number | null = null;
37+
38+
/**
39+
* Berekent het aantal pixels in een millimeter op het huidige scherm.
40+
* Maakt gebruik van een cache om de overhead van het maken en verwijderen van een DOM-element bij elke oproep te voorkomen.
41+
* @returns {number} Het aantal pixels in een millimeter.
42+
*/
43+
function getPixelsPerMillimeter(): number {
44+
if (cachedPixelsPerMillimeter === null) {
45+
const div = document.createElement('div');
46+
div.style.width = '10mm';
47+
div.style.position = 'absolute';
48+
document.body.appendChild(div);
49+
const widthInPixels = div.offsetWidth;
50+
document.body.removeChild(div);
51+
cachedPixelsPerMillimeter = widthInPixels / 10;
52+
}
53+
return cachedPixelsPerMillimeter;
54+
}

src/sitplan/MouseDrag.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Class that helps with dragging a box on the situation plan view.
3+
* It keeps track of the start position of the drag and the zoomfactor.
4+
*/
5+
class MouseDrag {
6+
7+
private startDragx: number = 0;
8+
private startDragy: number = 0;
9+
private startOffsetLeft: number = 0;
10+
private startOffsetTop: number = 0;
11+
private zoomfactor: number = 1;
12+
13+
/**
14+
* Start the drag.
15+
* @param mousex The x position of the mouse when the drag starts.
16+
* @param mousey The y position of the mouse when the drag starts.
17+
* @param startOffsetLeft The left position of the box when the drag starts.
18+
* @param startOffsetTop The top position of the box when the drag starts.
19+
* @param zoomfactor The zoomfactor of the situation plan view when the drag starts.
20+
*/
21+
startDrag(mousex: number = 0, mousey: number = 0,
22+
startOffsetLeft: number = 0, startOffsetTop: number = 0,
23+
zoomfactor: number = 1) {
24+
this.startDragx = mousex;
25+
this.startDragy = mousey;
26+
this.startOffsetLeft = startOffsetLeft;
27+
this.startOffsetTop = startOffsetTop;
28+
this.zoomfactor = zoomfactor;
29+
}
30+
31+
/**
32+
* Return the new left and top position of the box based on the current mouse position.
33+
* @param mousex The current x position of the mouse.
34+
* @param mousey The current y position of the mouse.
35+
* @returns An object with the new left and top position of the box.
36+
*/
37+
returnNewLeftTop(mousex: number = 0, mousey: number = 0) {
38+
return ( {
39+
left: (mousex - this.startDragx) / this.zoomfactor + this.startOffsetLeft,
40+
top: (mousey - this.startDragy) / this.zoomfactor + this.startOffsetTop});
41+
}
42+
}

0 commit comments

Comments
 (0)