diff --git a/apps/app/src/assets/blueprint-svgrepo-com-blue-grey.svg b/apps/app/src/assets/blueprint-svgrepo-com-blue-grey.svg new file mode 100644 index 00000000..80f16f2c --- /dev/null +++ b/apps/app/src/assets/blueprint-svgrepo-com-blue-grey.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/libs/app/components/src/lib/dashboard-page/dashboard-page.component.html b/libs/app/components/src/lib/dashboard-page/dashboard-page.component.html index 5d03f985..c377f088 100644 --- a/libs/app/components/src/lib/dashboard-page/dashboard-page.component.html +++ b/libs/app/components/src/lib/dashboard-page/dashboard-page.component.html @@ -1,4 +1,14 @@ -
+
+
+

Fetching floor plan data

+ +
+
+
+ Empty state +

There seems to be no floor plan to provide any data.

+
+
; @ViewChild('totalUserCountChart') totalUserCountChart!: ElementRef; @ViewChild('totalDeviceCountChart') totalDeviceCountChart!: ElementRef; @@ -70,7 +70,10 @@ export class DashboardPageComponent implements OnInit { showStatsOnSide = false; largeScreen = false; mediumScreen = false; - floorlayoutSnapshot: string | null = null; + floorlayoutSnapshot: string | null = null; + floorlayoutImages: IImage[] = []; + STALL_IMAGE_URL = 'assets/stall-icon.png'; + noFloorPlan = false; // Functional eventStartTime: Date = new Date(); @@ -174,11 +177,17 @@ export class DashboardPageComponent implements OnInit { // get the boundaries from the floorlayout const response = await this.appApiService.getFloorplanBoundaries(this.id); - this.floorlayoutBounds = response.boundaries; + this.floorlayoutBounds = response?.boundaries; //get event floorplan const layout = await this.appApiService.getEventFloorLayout(this.id); this.floorlayoutSnapshot = layout; + if (!layout) { + this.noFloorPlan = true; + } + + const images = await this.appApiService.getFloorLayoutImages(this.id); + this.floorlayoutImages = images; const eventStartDate = this.event.event.StartDate; const eventEndDate = this.event.event.EndDate; @@ -212,10 +221,10 @@ export class DashboardPageComponent implements OnInit { this.mediumScreen = false; } - this.loading = false; setTimeout(() => { this.show = true; - }, 200); + this.loading = false; + }, 1600); } @HostListener('window:resize', ['$event']) @@ -272,6 +281,9 @@ export class DashboardPageComponent implements OnInit { } async ngAfterViewInit() { + setTimeout(() => { + this.isLoading = false; + }, 1500); // wait until the heatmap container is rendered setTimeout(() => { // set the number of hours of the event @@ -582,7 +594,9 @@ export class DashboardPageComponent implements OnInit { async getImageFromJSONData(eventId: string) { const response = this.floorlayoutSnapshot; - if (response) { + const imageResponse = this.floorlayoutImages; + + if (response || imageResponse) { // use the response to create an image this.floorlayoutStage = new Konva.Stage({ container: 'floormap', @@ -644,33 +658,73 @@ export class DashboardPageComponent implements OnInit { // this.floorlayoutStage.add(new Konva.Layer().add(rect)); - // create node from JSON string - this.heatmapLayer = Konva.Node.create(response, 'floormap'); - console.log(this.heatmapLayer) - if (this.heatmapLayer) { - this.heatmapLayer?.setAttr('name', 'floorlayoutLayer'); - - // run through the layer and set the components not to be draggable - this.heatmapLayer?.children?.forEach(element => { - element.draggable(false); - }); - - // run through the layer and change the colors of the walls - this.heatmapLayer?.find('Path').forEach((path) => { - if (path.name() == 'wall') { - path.attrs.stroke = this.chartColors['ept-blue-grey']; - } - }); - // run through the layer and change the colors of the border of the sensors - this.heatmapLayer?.find('Circle').forEach((circle) => { - if (circle.name() == 'sensor') { - circle.attrs.stroke = this.chartColors['ept-blue-grey']; - } - }); - - // // add the node to the layer - this.floorlayoutStage.add(this.heatmapLayer); + if (response) { + this.heatmapLayer = Konva.Node.create(response, 'floormap'); + if (this.heatmapLayer) { + this.heatmapLayer?.setAttr('name', 'floorlayoutLayer'); + + // run through the layer and set the components not to be draggable + this.heatmapLayer?.children?.forEach(element => { + element.draggable(false); + }); + + // run through the layer and change the colors of the walls + this.heatmapLayer?.find('Path').forEach((path) => { + if (path.name() == 'wall') { + path.attrs.stroke = this.chartColors['ept-blue-grey']; + } + }); + // run through the layer and change the colors of the border of the sensors + this.heatmapLayer?.find('Circle').forEach((circle) => { + if (circle.name() == 'sensor') { + circle.attrs.stroke = this.chartColors['ept-blue-grey']; + } + }); + // run through the layer and change the image attribute for the stalls + this.heatmapLayer?.find('Group').forEach((group) => { + if (group.name() == 'stall') { + (group as Konva.Group).children?.forEach((child) => { + if (child instanceof Konva.Image) { + const image = new Image(); + image.onload = () => { + // This code will execute once the image has finished loading. + child.attrs.image = image; + this.heatmapLayer?.draw(); + }; + image.src = this.STALL_IMAGE_URL; + } + }); + } + }); + + imageResponse.forEach((image: any) => { + const imageID = image._id; + const imageSrc = image.imageBase64; + + this.heatmapLayer?.find('Group').forEach((group) => { + if (group.name() === 'uploadedFloorplan') { + if (group.getAttr('databaseID') === imageID) { + (group as Konva.Group).children?.forEach((child) => { + if (child instanceof Konva.Image) { + const image = new Image(); + image.onload = () => { + // This code will execute once the image has finished loading. + child.attrs.image = image; + this.heatmapLayer?.draw(); + }; + image.src = imageSrc; + } + }); + } + } + }); + }); + + // // add the node to the layer + this.floorlayoutStage.add(this.heatmapLayer); + } } + // add event listener to the layer for scrolling const zoomFactor = 1.2; // Adjust this as needed diff --git a/libs/app/components/src/lib/floorplan-editor-page/floorplan-editor-page.component.ts b/libs/app/components/src/lib/floorplan-editor-page/floorplan-editor-page.component.ts index bff75812..a3807b9d 100644 --- a/libs/app/components/src/lib/floorplan-editor-page/floorplan-editor-page.component.ts +++ b/libs/app/components/src/lib/floorplan-editor-page/floorplan-editor-page.component.ts @@ -114,7 +114,7 @@ export class FloorplanEditorPageComponent implements OnInit, AfterViewInit{ gridLines !: Konva.Group; currentPathStrokeWidth = 0; currentGridStrokeWidth = 0; - currentSensorCircleStrokeWidth = 0; + currentSensorCircleStrokeWidth = 1; snaps: number[] = []; wheelCounter = 0; contentLoaded = false; @@ -273,7 +273,9 @@ export class FloorplanEditorPageComponent implements OnInit, AfterViewInit{ // set mouse enter and mouse leave events item.konvaObject?.on('mouseenter', () => { - document.body.style.cursor = 'not-allowed'; + if (item.konvaObject?.getAttr('name') !== 'gridGroup') { + document.body.style.cursor = 'not-allowed'; + } }); item.konvaObject?.on('mouseleave', () => { document.body.style.cursor = 'default'; @@ -380,6 +382,16 @@ export class FloorplanEditorPageComponent implements OnInit, AfterViewInit{ image.setAttr('name', 'sensor'); const sensor = this.canvas.findOne('.sensor'); + + if (sensor) { + this.currentSensorCircleStrokeWidth = sensor.getAttr('strokeWidth'); + } + else if (this.currentScale !== 1){ + this.currentSensorCircleStrokeWidth = this.currentGridStrokeWidth; + } + else { + this.currentSensorCircleStrokeWidth = 1; + } // create circle to represent sensor const sensorCount = this.canvasItems.filter(item => item.konvaObject?.getAttr('name').includes('sensor')).length + 1; @@ -391,7 +403,7 @@ export class FloorplanEditorPageComponent implements OnInit, AfterViewInit{ radius: 2, fill: 'red', stroke: 'black', - strokeWidth: this.currentSensorCircleStrokeWidth === 0 && !sensor ? 1 : sensor.getAttr('strokeWidth'), + strokeWidth: this.currentSensorCircleStrokeWidth, draggable: true, cursor: 'move', }); @@ -609,18 +621,45 @@ export class FloorplanEditorPageComponent implements OnInit, AfterViewInit{ const tooltipID = element.getAttr('text') ? element.getAttr('text') : element.getAttr('id'); const alreadyExistingTooltip = this.tooltips.find(tooltip => tooltip.getAttr('id').includes(tooltipID)); + + if (this.currentScale !== 1) { + if (!alreadyExistingTooltip) { + this.currentLabelPointerHeight = this.currentGridStrokeWidth * 4; + this.currentLabelPointerWidth = this.currentGridStrokeWidth * 4; + this.currentLabelShadowBlur = this.currentGridStrokeWidth * 10; + this.currentLabelShadowOffsetX = this.currentGridStrokeWidth * 10; + this.currentLabelShadowOffsetY = this.currentGridStrokeWidth * 10; + this.currentLabelFontSize = this.currentGridStrokeWidth * 10; + } + else { + this.currentLabelPointerHeight = this.currentLabelPointerHeight * 1; + this.currentLabelPointerWidth = this.currentLabelPointerWidth * 1; + this.currentLabelShadowBlur = this.currentLabelShadowBlur * 1; + this.currentLabelShadowOffsetX = this.currentLabelShadowOffsetX * 1; + this.currentLabelShadowOffsetY = this.currentLabelShadowOffsetY * 1; + this.currentLabelFontSize = this.currentLabelFontSize * 1; + } + } + else { + this.currentLabelPointerHeight = 4; + this.currentLabelPointerWidth = 4; + this.currentLabelShadowBlur = 10; + this.currentLabelShadowOffsetX = 10; + this.currentLabelShadowOffsetY = 10; + this.currentLabelFontSize = 10; + } if (alreadyExistingTooltip) { const tag = alreadyExistingTooltip.getChildren()[0]; const text = alreadyExistingTooltip.getChildren()[1]; - tag.setAttr('pointerWidth', this.currentLabelPointerWidth === 0 ? 4 : this.currentLabelPointerWidth); - tag.setAttr('pointerHeight', this.currentLabelPointerHeight === 0 ? 4 : this.currentLabelPointerHeight); - tag.setAttr('shadowBlur', this.currentLabelShadowBlur === 0 ? 10 : this.currentLabelShadowBlur); - tag.setAttr('shadowOffsetX', this.currentLabelShadowOffsetX === 0 ? 10 : this.currentLabelShadowOffsetX); - tag.setAttr('shadowOffsetY', this.currentLabelShadowOffsetY === 0 ? 10 : this.currentLabelShadowOffsetY); + tag.setAttr('pointerWidth', this.currentLabelPointerWidth); + tag.setAttr('pointerHeight', this.currentLabelPointerHeight); + tag.setAttr('shadowBlur', this.currentLabelShadowBlur); + tag.setAttr('shadowOffsetX', this.currentLabelShadowOffsetX); + tag.setAttr('shadowOffsetY', this.currentLabelShadowOffsetY ); - text.setAttr('fontSize', this.currentLabelFontSize === 0 ? 10 : this.currentLabelFontSize); + text.setAttr('fontSize', this.currentLabelFontSize); alreadyExistingTooltip.setAttr('x', element instanceof Konva.Circle ? positionX : positionX + 5); alreadyExistingTooltip.setAttr('y', element instanceof Konva.Circle ? positionY - 3 : positionY); @@ -639,13 +678,13 @@ export class FloorplanEditorPageComponent implements OnInit, AfterViewInit{ new Konva.Tag({ fill: 'black', pointerDirection: 'down', - pointerWidth: this.currentLabelPointerWidth ===0 ? 4 : this.currentLabelPointerWidth, - pointerHeight: this.currentLabelPointerHeight === 0 ? 4 : this.currentLabelPointerHeight, + pointerWidth: this.currentLabelPointerWidth, + pointerHeight: this.currentLabelPointerHeight, lineJoin: 'round', shadowColor: 'black', - shadowBlur: this.currentLabelShadowBlur === 0 ? 10 : this.currentLabelShadowBlur, - shadowOffsetX: this.currentLabelShadowOffsetX === 0 ? 10 : this.currentLabelShadowOffsetX, - shadowOffsetY: this.currentLabelShadowOffsetY === 0 ? 10 : this.currentLabelShadowOffsetY, + shadowBlur: this.currentLabelShadowBlur, + shadowOffsetX: this.currentLabelShadowOffsetX, + shadowOffsetY: this.currentLabelShadowOffsetY, shadowOpacity: 0.5, }) ); @@ -653,7 +692,7 @@ export class FloorplanEditorPageComponent implements OnInit, AfterViewInit{ new Konva.Text({ text: tooltipID, fontFamily: 'Calibri', - fontSize: this.currentLabelFontSize === 0 ? 10 : this.currentLabelFontSize, + fontSize: this.currentLabelFontSize, padding: 2, fill: 'white', }) @@ -729,7 +768,7 @@ export class FloorplanEditorPageComponent implements OnInit, AfterViewInit{ const firstPromise = this.appApiService.getFloorLayoutImages(this.eventId).then((response: any) => { if (response === null || response === '' || response.length === 0) return; - + response.forEach((obj: any) => { const imageObjects = obj.imageObj; const imageBase64 = obj.imageBase64; @@ -748,6 +787,7 @@ export class FloorplanEditorPageComponent implements OnInit, AfterViewInit{ cursor: 'move', databaseID: imageID, }); + console.log(uploadedImagesLayer) uploadedImagesLayer.children?.forEach(child => { const image = new Konva.Image(child.getAttrs()); @@ -820,7 +860,9 @@ export class FloorplanEditorPageComponent implements OnInit, AfterViewInit{ break; case 'Group': type = new Konva.Group(child.getAttrs()); - this.addGroupChildren(type, child); + if (type.hasName('stall')) { + this.addGroupChildren(type, child); + } break; case 'Text': type = new Konva.Text(child.getAttrs()); @@ -844,6 +886,10 @@ export class FloorplanEditorPageComponent implements OnInit, AfterViewInit{ this.moveSensorsAndTooltipsToTop(); this.centerFloorPlan(); this.reorderCanvasItems(); + if (this.canvasItems.length === 0) { + this.canvasContainer.x(0); + this.canvasContainer.y(0); + } }); }); @@ -861,6 +907,29 @@ export class FloorplanEditorPageComponent implements OnInit, AfterViewInit{ this.zoomOut(); if (this.currentScale < 1) { this.currentScale = 1; + this.currentGridStrokeWidth = 1; + this.currentPathStrokeWidth = 3; + this.currentSensorCircleStrokeWidth = 1; + this.currentLabelPointerHeight = 4; + this.currentLabelPointerWidth = 4; + this.currentLabelShadowBlur = 10; + this.currentLabelShadowOffsetX = 10; + this.currentLabelShadowOffsetY = 10; + this.currentLabelFontSize = 10; + + // loop through all tooltips + this.tooltips.forEach(tooltip => { + const tag = tooltip.getChildren()[0]; + const text = tooltip.getChildren()[1]; + + tag.setAttr('pointerWidth', this.currentLabelPointerWidth); + tag.setAttr('pointerHeight', this.currentLabelPointerHeight); + tag.setAttr('shadowBlur', this.currentLabelShadowBlur); + tag.setAttr('shadowOffsetX', this.currentLabelShadowOffsetX); + tag.setAttr('shadowOffsetY', this.currentLabelShadowOffsetY ); + + text.setAttr('fontSize', this.currentLabelFontSize); + }); } } @@ -938,7 +1007,6 @@ export class FloorplanEditorPageComponent implements OnInit, AfterViewInit{ fill: 'white', opacity: 1, }); - const oldText = type.getChildren().find(child => child instanceof Konva.Text) as Konva.Text; type.children = type.children?.filter(child => child.getClassName() !== 'Text'); const newText = new Konva.Text({ @@ -1122,16 +1190,15 @@ export class FloorplanEditorPageComponent implements OnInit, AfterViewInit{ this.updateStrokeWidths(2); + this.updateLabelSize(2, this.maxReached); if (newScale < 8) { this.maxReached = oldScale >= 8 ? true : false; this.tooltipAllowedVisible = true; - this.updateLabelSize(2, this.maxReached); this.maxReached = false; } else { this.tooltipAllowedVisible = false; this.setAllTootipsVisibility(false); - this.updateLabelSize(0.5, this.maxReached); } this.setZoomInDisabled(this.displayedSnap); this.setZoomOutDisabled(this.displayedSnap); @@ -1189,13 +1256,8 @@ export class FloorplanEditorPageComponent implements OnInit, AfterViewInit{ this.tooltips.forEach((tooltip: any) => { tooltip.children?.forEach((child: any) => { if (child instanceof Konva.Text) { - const prevSize = child.getAttr('fontSize') - if (maxWasReached) { - child.fontSize(prevSize); - } - else { - child.fontSize(prevSize * scale); - } + const prevSize = child.getAttr('fontSize'); + child.fontSize(prevSize * scale); this.currentLabelFontSize = prevSize * scale; } else if (child instanceof Konva.Tag) { @@ -1205,20 +1267,12 @@ export class FloorplanEditorPageComponent implements OnInit, AfterViewInit{ const prevShadowOffsetX = child.getAttr('shadowOffsetX'); const prevShadowOffsetY = child.getAttr('shadowOffsetY'); - if (maxWasReached) { - child.pointerWidth(prevPointerWidth); - child.pointerHeight(prevPointerHeight); - child.shadowBlur(prevShadowBlur); - child.shadowOffsetX(prevShadowOffsetX); - child.shadowOffsetY(prevShadowOffsetY); - } - else { child.pointerWidth(prevPointerWidth * scale); child.pointerHeight(prevPointerHeight * scale); child.shadowBlur(prevShadowBlur * scale); child.shadowOffsetX(prevShadowOffsetX * scale); child.shadowOffsetY(prevShadowOffsetY * scale); - } + this.currentLabelPointerWidth = prevPointerWidth * scale; this.currentLabelPointerHeight = prevPointerHeight * scale; @@ -1273,7 +1327,7 @@ export class FloorplanEditorPageComponent implements OnInit, AfterViewInit{ this.canvas.batchDraw(); } } - else if (event.ctrlKey) { + else if (event.ctrlKey && this.canvasItems.length !== 0) { this.ctrlDown = true; document.body.style.cursor = 'grab'; if (this.mouseDown) { @@ -2160,9 +2214,14 @@ export class FloorplanEditorPageComponent implements OnInit, AfterViewInit{ //add line to canvasItems array this.canvasItems.push({ - name: 'path', + name: 'wall', konvaObject: this.activePath, }); + console.log(this.canvasItems); + this.removeDuplicates(); + console.log(this.canvasItems); + this.removeFaultyPaths(); + console.log(this.canvasItems); // set the height of the wall const height = Math.abs(snapPoint.y - this.activePath.y()); @@ -2186,6 +2245,32 @@ export class FloorplanEditorPageComponent implements OnInit, AfterViewInit{ // Remove the mouse up event listener this.canvasContainer.off('mouseup', this.onMouseUp.bind(this)); } + + removeDuplicates() { + //loop through canvasItems array and remove duplicates + const unique: DroppedItem[] = []; + this.canvasItems.forEach((item) => { + if (!unique.some((uniqueItem) => uniqueItem.konvaObject === item.konvaObject)) { + unique.push(item); + } + }); + this.canvasItems = unique; + } + + removeFaultyPaths() { + const faultyPaths = this.canvasItems.filter((item) => + item.konvaObject?.hasName('wall') && + item.konvaObject?.getAttr('data') === 'M0,0 L0,0' + ); + faultyPaths.forEach((path) => { + path.konvaObject?.remove(); + }); + // remove them from canvasItems + this.canvasItems = this.canvasItems.filter((item) => + item.konvaObject?.hasName('wall') && + item.konvaObject?.getAttr('data') !== 'M0,0 L0,0' + ); + } createGridLines() { const grid = this.initialGridSize; @@ -2581,6 +2666,9 @@ export class FloorplanEditorPageComponent implements OnInit, AfterViewInit{ this.canvas = newCanvas; this.canvasItems = []; this.canvas.children?.forEach((item: any) => { + if (item.attrs.name === 'gridGroup' || item instanceof Konva.Transformer || item.attrs.name === 'selectionBox' || item.attrs.name === 'rectOverlay') { + return; + } const droppedItem = { name: item.attrs.name, konvaObject: item, @@ -2628,8 +2716,11 @@ export class FloorplanEditorPageComponent implements OnInit, AfterViewInit{ }); // remove the grid lines, transformers and groups from the JSON data - json.children = json.children.filter((child: any) => { - return child.attrs.name === 'wall' || child.attrs.name === 'stall' || child.attrs.name === 'sensor' || child.attrs.name === 'textBox'; + json.children = json.children.filter((child: KonvaTypes) => { + if (child.attrs.name === 'wall' || child.attrs.name === 'stall' || child.attrs.name === 'sensor' || child.attrs.name === 'textBox' || child.attrs.name === 'uploadedFloorplan') { + child.attrs.opacity = 1; + } + return child.attrs.name === 'wall' || child.attrs.name === 'stall' || child.attrs.name === 'sensor' || child.attrs.name === 'textBox' || child.attrs.name === 'uploadedFloorplan'; }); const adjustedJson = JSON.parse(JSON.stringify(json));