setting design
This commit is contained in:
parent
1c7be2a776
commit
a44284b9b1
1 changed files with 216 additions and 13 deletions
|
@ -3,20 +3,223 @@
|
||||||
import Button from "$lib/components/ui/button/button.svelte";
|
import Button from "$lib/components/ui/button/button.svelte";
|
||||||
import Input from "$lib/components/ui/input/input.svelte";
|
import Input from "$lib/components/ui/input/input.svelte";
|
||||||
import Label from "$lib/components/ui/label/label.svelte";
|
import Label from "$lib/components/ui/label/label.svelte";
|
||||||
|
import Card from "$lib/components/ui/card/card.svelte";
|
||||||
|
import CardContent from "$lib/components/ui/card/card-content.svelte";
|
||||||
|
import CardDescription from "$lib/components/ui/card/card-description.svelte";
|
||||||
|
import CardHeader from "$lib/components/ui/card/card-header.svelte";
|
||||||
|
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 } = $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 placedDevices = $state([]);
|
||||||
|
let draggedDevice = $state(null);
|
||||||
|
let floorPlanRef = $state(null);
|
||||||
|
|
||||||
|
function handleFileUpload(event) {
|
||||||
|
const file = event.target.files?.[0];
|
||||||
|
if (file && file.type.startsWith("image/")) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
floorPlanImage = e.target.result;
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragStart(device) {
|
||||||
|
draggedDevice = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragOver(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDrop(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (!draggedDevice || !floorPlanRef) return;
|
||||||
|
|
||||||
|
const rect = floorPlanRef.getBoundingClientRect();
|
||||||
|
const x = ((event.clientX - rect.left) / rect.width) * 100;
|
||||||
|
const y = ((event.clientY - rect.top) / rect.height) * 100;
|
||||||
|
|
||||||
|
// Check if device is already placed
|
||||||
|
const existingIndex = placedDevices.findIndex((d) => d.id === draggedDevice.id);
|
||||||
|
|
||||||
|
if (existingIndex >= 0) {
|
||||||
|
// Update position
|
||||||
|
placedDevices[existingIndex] = { ...placedDevices[existingIndex], x, y };
|
||||||
|
} else {
|
||||||
|
// Add new device
|
||||||
|
placedDevices = [...placedDevices, { ...draggedDevice, x, y }];
|
||||||
|
}
|
||||||
|
|
||||||
|
draggedDevice = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeDevice(deviceId) {
|
||||||
|
placedDevices = placedDevices.filter((d) => d.id !== deviceId);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class="container mx-auto space-y-6 p-6">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Floor Management</CardTitle>
|
||||||
|
<CardDescription>Add or remove floors from your monitoring system</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
<form method="post" action="?/newfloor" use:enhance>
|
<form method="post" action="?/newfloor" use:enhance>
|
||||||
<div class="form-group">
|
<div class="form-group mb-4">
|
||||||
<Label for="number">Floor number</Label>
|
<Label for="number">Floor number</Label>
|
||||||
<Input id="number" name="number" />
|
<Input id="number" name="number" class="mt-2" />
|
||||||
</div>
|
</div>
|
||||||
<div class="button-group">
|
<div class="button-group flex gap-4">
|
||||||
<Button type="submit">Add floor</Button>
|
<Button type="submit">Add floor</Button>
|
||||||
<Button type="submit" formaction="?/rmfloor" variant="secondary">Delete floor</Button>
|
<Button type="submit" formaction="?/rmfloor" variant="secondary">Delete floor</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{#if form?.message}
|
{#if form?.message}
|
||||||
<p class="error-message">{form.message}</p>
|
<p class="error-message mt-4 text-red-600">{form.message}</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Floor Plan & Devices</CardTitle>
|
||||||
|
<CardDescription>Upload a floor plan image and view available devices</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<Label for="floorplan" class="cursor-pointer">
|
||||||
|
<div
|
||||||
|
class="rounded-lg border-2 border-dashed border-gray-300 p-6 text-center transition-colors hover:border-gray-400"
|
||||||
|
>
|
||||||
|
<Upload class="mx-auto mb-2 h-8 w-8 text-gray-400" />
|
||||||
|
<span class="text-sm text-gray-600">Click to upload floor plan image</span>
|
||||||
|
</div>
|
||||||
|
</Label>
|
||||||
|
<input
|
||||||
|
id="floorplan"
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
class="hidden"
|
||||||
|
onchange={handleFileUpload}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if floorPlanImage}
|
||||||
|
<div
|
||||||
|
class="relative overflow-hidden rounded-lg border bg-gray-50"
|
||||||
|
bind:this={floorPlanRef}
|
||||||
|
ondragover={handleDragOver}
|
||||||
|
ondrop={handleDrop}
|
||||||
|
>
|
||||||
|
<img src={floorPlanImage} alt="Floor plan" class="pointer-events-none h-auto w-full" />
|
||||||
|
|
||||||
|
{#each placedDevices as device}
|
||||||
|
<div
|
||||||
|
class="absolute -translate-x-1/2 -translate-y-1/2 transform cursor-move rounded-lg border-2 border-blue-500 bg-white p-3 shadow-lg transition-shadow hover:shadow-xl"
|
||||||
|
style="left: {device.x}%; top: {device.y}%;"
|
||||||
|
draggable="true"
|
||||||
|
ondragstart={() => handleDragStart(device)}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
<button
|
||||||
|
onclick={() => removeDevice(device.id)}
|
||||||
|
class="ml-2 text-red-500 hover:text-red-700"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</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">Temp</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<Droplets class="h-3 w-3 text-blue-500" />
|
||||||
|
<span class="text-xs">Humid</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<Gauge class="h-3 w-3 text-green-500" />
|
||||||
|
<span class="text-xs">Press</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<Mountain class="h-3 w-3 text-purple-500" />
|
||||||
|
<span class="text-xs">Alt</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<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}
|
||||||
|
|
||||||
|
<div class="pt-4">
|
||||||
|
<h3 class="mb-3 font-semibold">Available ESP8266 Devices</h3>
|
||||||
|
<div class="grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-4">
|
||||||
|
{#each availableDevices as device}
|
||||||
|
<div
|
||||||
|
class="cursor-move rounded-lg border-2 bg-white p-4 transition-shadow hover:shadow-md {device.status ===
|
||||||
|
'online'
|
||||||
|
? 'border-green-200'
|
||||||
|
: 'border-gray-200'}"
|
||||||
|
draggable="true"
|
||||||
|
ondragstart={() => handleDragStart(device)}
|
||||||
|
>
|
||||||
|
<div class="mb-2 flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<Cpu class="h-5 w-5 text-blue-600" />
|
||||||
|
<span class="text-sm font-medium">{device.name}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<div
|
||||||
|
class="h-2 w-2 rounded-full {device.status === 'online'
|
||||||
|
? 'bg-green-500'
|
||||||
|
: 'bg-gray-400'}"
|
||||||
|
></div>
|
||||||
|
<span class="text-xs text-gray-600">{device.status}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2 text-xs text-gray-500">Sensors:</div>
|
||||||
|
<div class="grid grid-cols-2 gap-1">
|
||||||
|
<div class="flex items-center gap-1 text-xs">
|
||||||
|
<Thermometer class="h-3 w-3 text-orange-500" />
|
||||||
|
<span>Temperature</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1 text-xs">
|
||||||
|
<Droplets class="h-3 w-3 text-blue-500" />
|
||||||
|
<span>Humidity</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1 text-xs">
|
||||||
|
<Gauge class="h-3 w-3 text-green-500" />
|
||||||
|
<span>Pressure</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1 text-xs">
|
||||||
|
<Mountain class="h-3 w-3 text-purple-500" />
|
||||||
|
<span>Altitude</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue