add floor plan
This commit is contained in:
parent
5935064606
commit
5d689e6005
5 changed files with 254 additions and 41 deletions
|
@ -5,7 +5,30 @@ import type { PageServerLoad } from "./$types";
|
|||
import { connect } from "mqtt";
|
||||
|
||||
export const load: PageServerLoad = async ({ params }) => {
|
||||
{
|
||||
// Convert slug to number for floor lookup
|
||||
const floorNumber = Number(params.slug);
|
||||
|
||||
// First check if we have a saved floor configuration in the floors table
|
||||
const floorData = await db.select({
|
||||
floor: table.floors.floor,
|
||||
url: table.floors.url
|
||||
}).from(table.floors).where(eq(table.floors.floor, floorNumber));
|
||||
|
||||
if (floorData.length > 0 && floorData[0].url && floorData[0].url !== "/") {
|
||||
try {
|
||||
// Try to parse the saved configuration
|
||||
const config = JSON.parse(floorData[0].url);
|
||||
return {
|
||||
slug: params.slug,
|
||||
floorConfig: config,
|
||||
hasConfig: true
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error parsing floor configuration:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to the old canvas drawing system
|
||||
const floor_cnt = await db.select({ floor: table.plans.floor, json: table.plans.plan }).from(table.plans).where(eq(table.plans.floor, params.slug));
|
||||
if (floor_cnt.length == 0) {
|
||||
await db.insert(table.plans).values({ floor: params.slug, plan: {
|
||||
|
@ -31,7 +54,10 @@ export const load: PageServerLoad = async ({ params }) => {
|
|||
}});
|
||||
}
|
||||
|
||||
}
|
||||
const floor_ = await db.select({ floor: table.plans.floor, json: table.plans.plan }).from(table.plans).where(eq(table.plans.floor, params.slug));
|
||||
return { slug: params.slug, floor: floor_ };
|
||||
return {
|
||||
slug: params.slug,
|
||||
floor: floor_,
|
||||
hasConfig: false
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { onMount, onDestroy } from "svelte";
|
||||
import { Thermometer, Droplets, Gauge, Mountain, Cpu } from "@lucide/svelte";
|
||||
|
||||
const { data } = $props();
|
||||
|
||||
|
@ -37,7 +38,7 @@
|
|||
}
|
||||
});
|
||||
|
||||
let canvasEl: HTMLCanvasElement;
|
||||
let canvasEl = $state<HTMLCanvasElement>();
|
||||
let ctx;
|
||||
|
||||
function drawFloorplan(data) {
|
||||
|
@ -81,14 +82,68 @@
|
|||
ctx.stroke();
|
||||
});
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!data.hasConfig && data.floor?.[0]?.json) {
|
||||
ctx = canvasEl.getContext("2d");
|
||||
drawFloorplan(data.floor[0].json);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div style="display: flex; justify-content: center; align-items: center; height: 100%;">
|
||||
<h1 class="text-center text-4xl font-bold">Floor {data.slug}</h1>
|
||||
<span>{mqttMessage}</span>
|
||||
<canvas bind:this={canvasEl} class="obj-contain" width="800%" height="600%"></canvas>
|
||||
<div class="container mx-auto p-6">
|
||||
<h1 class="mb-6 text-center text-4xl font-bold">Floor {data.slug}</h1>
|
||||
|
||||
{#if data.hasConfig && data.floorConfig}
|
||||
<!-- Display saved floor plan configuration -->
|
||||
<div class="relative mx-auto max-w-4xl">
|
||||
{#if data.floorConfig.image}
|
||||
<div class="relative overflow-hidden rounded-lg border bg-gray-50">
|
||||
<img src={data.floorConfig.image} alt="Floor plan" class="h-auto w-full" />
|
||||
|
||||
{#if data.floorConfig.devices}
|
||||
{#each data.floorConfig.devices as device}
|
||||
<div
|
||||
class="absolute -translate-x-1/2 -translate-y-1/2 transform rounded-lg border-2 border-blue-500 bg-white p-3 shadow-lg"
|
||||
style="left: {device.x}%; top: {device.y}%;"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<Cpu class="h-5 w-5 text-blue-600" />
|
||||
<span class="text-xs font-medium">{device.name}</span>
|
||||
</div>
|
||||
<div class="mt-2 grid grid-cols-2 gap-1">
|
||||
<div class="flex items-center gap-1">
|
||||
<Thermometer class="h-3 w-3 text-orange-500" />
|
||||
<span class="text-xs">23°C</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<Droplets class="h-3 w-3 text-blue-500" />
|
||||
<span class="text-xs">45%</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<Gauge class="h-3 w-3 text-green-500" />
|
||||
<span class="text-xs">1013</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<Mountain class="h-3 w-3 text-purple-500" />
|
||||
<span class="text-xs">120m</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="mt-4 rounded-lg bg-gray-100 p-4">
|
||||
<p class="text-sm text-gray-600">MQTT Status: {mqttMessage}</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Fallback to canvas drawing -->
|
||||
<div class="flex flex-col items-center">
|
||||
<span class="mb-4">{mqttMessage}</span>
|
||||
<canvas bind:this={canvasEl} class="border" width="800" height="600"></canvas>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,24 @@ import { fail } from "@sveltejs/kit";
|
|||
import { eq } from "drizzle-orm";
|
||||
|
||||
export const load = async (event) => {
|
||||
return {};
|
||||
// Fetch all available floors
|
||||
const floors = await db
|
||||
.select({ floor: table.floors.floor })
|
||||
.from(table.floors)
|
||||
.orderBy(table.floors.floor);
|
||||
|
||||
// Provide mock ESP8266 devices for now
|
||||
const devices = [
|
||||
{ id: "esp8266-001", name: "ESP8266 #001", type: "esp8266", status: "online" },
|
||||
{ id: "esp8266-002", name: "ESP8266 #002", type: "esp8266", status: "online" },
|
||||
{ id: "esp8266-003", name: "ESP8266 #003", type: "esp8266", status: "offline" },
|
||||
{ id: "esp8266-004", name: "ESP8266 #004", type: "esp8266", status: "online" },
|
||||
];
|
||||
|
||||
return {
|
||||
floors: floors.map((f) => f.floor),
|
||||
devices: devices,
|
||||
};
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
|
@ -41,4 +58,49 @@ export const actions = {
|
|||
|
||||
await db.delete(table.floors).where(eq(table.floors.floor, n));
|
||||
},
|
||||
savefloor: async (event) => {
|
||||
const formData = await event.request.formData();
|
||||
const floorNumber = formData.get("floorNumber");
|
||||
const floorPlanImage = formData.get("floorPlanImage");
|
||||
const devices = formData.get("devices");
|
||||
|
||||
const n = Number(floorNumber);
|
||||
if (isNaN(n)) return fail(400, { message: "Invalid floor number!" });
|
||||
|
||||
if (!floorPlanImage || !devices) {
|
||||
return fail(400, { message: "Missing floor plan or device configuration!" });
|
||||
}
|
||||
|
||||
try {
|
||||
const deviceData = JSON.parse(devices);
|
||||
|
||||
// Check if floor exists
|
||||
const exists = await db
|
||||
.select({ floor: table.floors.floor })
|
||||
.from(table.floors)
|
||||
.where(eq(table.floors.floor, n));
|
||||
|
||||
if (exists.length === 0) {
|
||||
return fail(400, { message: `Floor ${n} does not exist! Please create it first.` });
|
||||
}
|
||||
|
||||
// Update floor with configuration
|
||||
// Note: In a real implementation, you would store this data properly
|
||||
// For now, we'll just update the url field as a JSON string
|
||||
const floorConfig = {
|
||||
image: floorPlanImage,
|
||||
devices: deviceData,
|
||||
};
|
||||
|
||||
await db
|
||||
.update(table.floors)
|
||||
.set({ url: JSON.stringify(floorConfig) })
|
||||
.where(eq(table.floors.floor, n));
|
||||
|
||||
return { success: true, message: `Floor ${n} configuration saved successfully!` };
|
||||
} catch (error) {
|
||||
console.error("Error saving floor configuration:", error);
|
||||
return fail(500, { message: "Failed to save floor configuration!" });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -10,19 +10,29 @@
|
|||
import CardTitle from "$lib/components/ui/card/card-title.svelte";
|
||||
import { Thermometer, Droplets, Gauge, Mountain, Upload, Cpu } from "@lucide/svelte";
|
||||
|
||||
const { form } = $props();
|
||||
const { form, data } = $props();
|
||||
|
||||
let floorPlanImage = $state(null);
|
||||
let availableDevices = $state([
|
||||
{ id: "esp8266-1", name: "ESP8266 #1", type: "esp8266", status: "online" },
|
||||
{ id: "esp8266-2", name: "ESP8266 #2", type: "esp8266", status: "online" },
|
||||
{ id: "esp8266-3", name: "ESP8266 #3", type: "esp8266", status: "offline" },
|
||||
{ id: "esp8266-4", name: "ESP8266 #4", type: "esp8266", status: "online" },
|
||||
]);
|
||||
let availableDevices = $state(data.devices || []);
|
||||
|
||||
let placedDevices = $state([]);
|
||||
let draggedDevice = $state(null);
|
||||
let floorPlanRef = $state(null);
|
||||
let selectedFloorNumber = $state("");
|
||||
let saveMessage = $state(null);
|
||||
|
||||
$effect(() => {
|
||||
if (data.floors && data.floors.length > 0 && !selectedFloorNumber) {
|
||||
selectedFloorNumber = data.floors[0].toString();
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
// Update devices when data changes
|
||||
if (data.devices) {
|
||||
availableDevices = data.devices;
|
||||
}
|
||||
});
|
||||
|
||||
function handleFileUpload(event) {
|
||||
const file = event.target.files?.[0];
|
||||
|
@ -168,6 +178,65 @@
|
|||
<p class="mt-2 text-sm text-gray-600">
|
||||
Drag ESP8266 devices from below onto the floor plan to place them in specific rooms.
|
||||
</p>
|
||||
|
||||
{#if floorPlanImage && placedDevices.length > 0}
|
||||
<div class="mt-4 rounded-lg border bg-blue-50 p-4">
|
||||
<h4 class="mb-3 font-semibold">Save Floor Configuration</h4>
|
||||
<form
|
||||
method="post"
|
||||
action="?/savefloor"
|
||||
use:enhance={() => {
|
||||
return async ({ result }) => {
|
||||
if (result.type === "success") {
|
||||
saveMessage = { type: "success", text: result.data.message };
|
||||
// Clear form after successful save
|
||||
selectedFloorNumber = "";
|
||||
} else if (result.type === "failure") {
|
||||
saveMessage = {
|
||||
type: "error",
|
||||
text: result.data?.message || "Failed to save configuration",
|
||||
};
|
||||
}
|
||||
};
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="floorPlanImage" value={floorPlanImage} />
|
||||
<input type="hidden" name="devices" value={JSON.stringify(placedDevices)} />
|
||||
<div class="mb-3 flex items-end gap-4">
|
||||
<div class="flex-1">
|
||||
<Label for="floorNumber">Select Floor</Label>
|
||||
<select
|
||||
name="floorNumber"
|
||||
id="floorNumber"
|
||||
bind:value={selectedFloorNumber}
|
||||
class="mt-1 w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
|
||||
required
|
||||
>
|
||||
{#if data.floors && data.floors.length > 0}
|
||||
{#each data.floors as floor}
|
||||
<option value={floor.toString()}>
|
||||
Floor {floor}
|
||||
</option>
|
||||
{/each}
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
<Button type="submit" variant="default" disabled={!selectedFloorNumber}>
|
||||
Save Floor Configuration
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
{#if saveMessage}
|
||||
<p
|
||||
class="mt-2 text-sm {saveMessage.type === 'success'
|
||||
? 'text-green-600'
|
||||
: 'text-red-600'}"
|
||||
>
|
||||
{saveMessage.text}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<div class="pt-4">
|
||||
|
|
|
@ -18,6 +18,7 @@ function connectMqtt() {
|
|||
|
||||
// Replace with your MQTT broker details
|
||||
const BROKER_URL = "mqtt://kada49.it:1883"; // Example public broker
|
||||
//const BROKER_URL = "mqtt://test.mosquitto.org:1883";
|
||||
const TOPIC = "esp8266/+/data"; // Replace with your desired topic
|
||||
|
||||
client = mqtt.connect(BROKER_URL);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue