Kachelpositionen: Messblatt unter tools/, fuenf neue Kacheln
Wo eine Kachel im Grundriss hingehoert, liess sich bisher nur schaetzen - und der Bezugspunkt ist auch noch die linke obere Ecke, nicht die Mitte, die man im Auge hat. tools/kachelpositionen.php nimmt beides ab: Grundriss mit Raster, die vorhandenen Kacheln in Originalgroesse samt einem Punkt an ihrem Bezugspunkt, eine Geisterkachel am Zeiger und darunter die Zeile, die in rooms.php gehoert. Ein Klick friert sie ein. Als PHP-Seite im Webverzeichnis, nicht als erzeugte HTML-Datei: so liest sie ihre Raeume live aus restricted/rooms.php und veraltet nicht, wenn ein Raum dazukommt. Geprueft an bekannten Werten - auf die Mitte einer vorhandenen Kachel gezeigt kommt genau ihre gespeicherte Koordinate heraus. Damit haben jetzt alle Raeume eine Kachel: EG Kueche und Technik, UG Technik, dazu OG Kueche und Technik als neue Raeume. Sie zeigen vorerst die Platzhalter der Temperaturvorlage - was auf ihnen stehen soll (Leistung, Wasserverbrauch), ist der naechste Schritt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+13
-6
@@ -15,9 +15,14 @@
|
||||
*
|
||||
* Ein Raum ohne x/y ist trotzdem ein Raum: Geräte lassen sich ihm zuordnen
|
||||
* (siehe die Spalte actors.room und den Zuordnungsdialog in der Karte
|
||||
* "Automatismen"), er erscheint nur nicht als Kachel im Grundriss. Das
|
||||
* betrifft Räume ohne Thermostat - die Küche im EG, die Technikräume -,
|
||||
* deren Kachel eine Temperatur anzeigen würde, die niemand misst.
|
||||
* "Automatismen"), er erscheint nur nicht als Kachel im Grundriss. Derzeit
|
||||
* hat jeder Raum eine Kachel; die Unterscheidung bleibt, weil ein neuer Raum
|
||||
* zuerst als Zeile ohne Position entsteht und erst dann seinen Platz im
|
||||
* Grundriss bekommt.
|
||||
*
|
||||
* Wo eine Kachel hingehört, misst tools/kachelpositionen.php aus: Grundriss
|
||||
* mit Raster, Geisterkachel am Zeiger, fertige Zeile zum Abschreiben. x/y
|
||||
* ist die linke obere Ecke der 24 x 19 grossen Kachel, nicht ihre Mitte.
|
||||
*/
|
||||
|
||||
// Reihenfolge im Menü und in der Anzeige; OG ist die Standardetage.
|
||||
@@ -29,17 +34,19 @@ $rooms = [
|
||||
["floor" => "OG", "mqtt" => "Schlafzimmer", "id" => "schlafen", "x" => 235, "y" => 70],
|
||||
["floor" => "OG", "mqtt" => "Bad", "id" => "bad", "x" => 260, "y" => 120],
|
||||
["floor" => "OG", "mqtt" => "Buero", "id" => "buero", "x" => 265, "y" => 175],
|
||||
["floor" => "OG", "mqtt" => "Kueche", "id" => "kueche", "x" => 122, "y" => 138],
|
||||
["floor" => "OG", "mqtt" => "Technik", "id" => "technik", "x" => 173, "y" => 67],
|
||||
["floor" => "EG", "mqtt" => "Wohnzimmer", "id" => "wozi", "x" => 185, "y" => 150],
|
||||
["floor" => "EG", "mqtt" => "Florian", "id" => "kizi", "x" => 130, "y" => 90],
|
||||
["floor" => "EG", "mqtt" => "Schlafen", "id" => "schlafen", "x" => 235, "y" => 80],
|
||||
["floor" => "EG", "mqtt" => "Bad", "id" => "bad", "x" => 253, "y" => 130],
|
||||
["floor" => "EG", "mqtt" => "Magdalena", "id" => "buero", "x" => 258, "y" => 180],
|
||||
["floor" => "EG", "mqtt" => "Kueche", "id" => "kueche"],
|
||||
["floor" => "EG", "mqtt" => "Technik", "id" => "technik"],
|
||||
["floor" => "EG", "mqtt" => "Kueche", "id" => "kueche", "x" => 126, "y" => 146],
|
||||
["floor" => "EG", "mqtt" => "Technik", "id" => "technik", "x" => 172, "y" => 78],
|
||||
["floor" => "UG", "mqtt" => "Buero", "id" => "buero", "x" => 140, "y" => 140],
|
||||
["floor" => "UG", "mqtt" => "Bad", "id" => "bad", "x" => 210, "y" => 80],
|
||||
["floor" => "UG", "mqtt" => "Kueche", "id" => "kueche", "x" => 190, "y" => 195],
|
||||
["floor" => "UG", "mqtt" => "Technik", "id" => "technik"],
|
||||
["floor" => "UG", "mqtt" => "Technik", "id" => "technik", "x" => 238, "y" => 93],
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
/**
|
||||
* Messblatt für die Kachelpositionen im Grundriss.
|
||||
*
|
||||
* Die Kacheln der Home-Ansicht stehen in restricted/rooms.php als x/y in
|
||||
* einer 400 × 300 großen Zeichenfläche. Wo eine neue hingehört, lässt sich
|
||||
* an einem Bild nur schätzen - und der Bezugspunkt ist auch noch die linke
|
||||
* obere Ecke, nicht die Mitte, die man eigentlich im Auge hat.
|
||||
*
|
||||
* Diese Seite nimmt beides ab: sie zeigt die Grundrisse mit Raster und den
|
||||
* vorhandenen Kacheln in Originalgröße, hängt eine Geisterkachel an den
|
||||
* Zeiger und schreibt darunter die Zeile, die in die Tabelle gehört.
|
||||
*
|
||||
* Aufruf: http://<host>/smart/tools/kachelpositionen.php
|
||||
*
|
||||
* Bewusst eine eigene Seite und kein Eintrag im Menü: sie wird gebraucht,
|
||||
* wenn ein Raum dazukommt, und das ist selten.
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . "/../helper.php");
|
||||
require_once(__DIR__ . "/../restricted/rooms.php");
|
||||
|
||||
if (!checkLogin()) {
|
||||
http_response_code(403);
|
||||
exit;
|
||||
}
|
||||
|
||||
/** So groß ist eine Kachel im SVG - siehe restricted/home.php. */
|
||||
const KACHEL_BREIT = 24;
|
||||
const KACHEL_HOCH = 19;
|
||||
|
||||
/** So groß ist die Zeichenfläche. */
|
||||
const PLAN_BREIT = 400;
|
||||
const PLAN_HOCH = 300;
|
||||
|
||||
?><!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Kachelpositionen</title>
|
||||
<style>
|
||||
:root {
|
||||
--grund: #fbfbfa; --schrift: #1f2328; --leise: #6b7280;
|
||||
--linie: #d8d8d4; --karte: #ffffff; --akzent: #00788f;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--grund: #16181c; --schrift: #e8eaed; --leise: #9aa0a6;
|
||||
--linie: #2f333a; --karte: #1e2126; --akzent: #35b6cf;
|
||||
}
|
||||
}
|
||||
body {
|
||||
margin: 0; padding: 1.5rem 1rem 3rem;
|
||||
background: var(--grund); color: var(--schrift);
|
||||
font: 15px/1.55 -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||
}
|
||||
.huelle { max-width: 900px; margin: 0 auto; }
|
||||
h1 { font-size: 1.35rem; margin: 0 0 .3rem; }
|
||||
.vorwort { color: var(--leise); margin: 0 0 1.5rem; max-width: 62ch; }
|
||||
.vorwort code {
|
||||
background: var(--karte); border: 1px solid var(--linie);
|
||||
border-radius: .25rem; padding: .05rem .3rem; font-size: .88em;
|
||||
}
|
||||
.etage {
|
||||
background: var(--karte); border: 1px solid var(--linie);
|
||||
border-radius: .6rem; padding: .8rem; margin-bottom: 1.2rem;
|
||||
}
|
||||
h2 { font-size: .95rem; margin: 0 0 .5rem; font-weight: 600; }
|
||||
h2 span { font-weight: 400; color: var(--leise); }
|
||||
.plan {
|
||||
display: block; width: 100%; height: auto; border-radius: .4rem;
|
||||
cursor: crosshair; touch-action: none; background: #0b0d10;
|
||||
}
|
||||
.kachel { fill: rgba(0, 0, 0, .4); stroke: var(--akzent); stroke-width: .7; }
|
||||
.anker { fill: var(--akzent); }
|
||||
.kachelname { fill: #fff; font: 4px sans-serif; text-anchor: middle; opacity: .85; }
|
||||
.achsen text { fill: #ffffffaa; font: 7px sans-serif; }
|
||||
.geist { fill: rgba(255, 190, 60, .28); stroke: #ffbe3c; stroke-width: .8; }
|
||||
.kreuz { stroke: #ffbe3c; stroke-width: .4; stroke-dasharray: 3 3; }
|
||||
.ablesung {
|
||||
margin: .6rem 0 0; font-family: ui-monospace, Consolas, monospace;
|
||||
font-size: .88rem; color: var(--leise); min-height: 1.5em;
|
||||
}
|
||||
.ablesung b { color: var(--schrift); }
|
||||
.ablesung .fest { color: var(--akzent); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="huelle">
|
||||
|
||||
<h1>Kachelpositionen im Grundriss</h1>
|
||||
<p class="vorwort">
|
||||
Die Zeichenfläche ist <?= PLAN_BREIT ?> × <?= PLAN_HOCH ?>. Eine Kachel ist
|
||||
<?= KACHEL_BREIT ?> × <?= KACHEL_HOCH ?> groß, und <code>x</code>/<code>y</code>
|
||||
in <code>restricted/rooms.php</code> ist ihre <b>linke obere Ecke</b> – die
|
||||
Punkte an den vorhandenen Kacheln zeigen, wo dieser Bezugspunkt sitzt.
|
||||
Zeig auf die Stelle, an der die <b>Mitte</b> der neuen Kachel liegen soll; das gelbe
|
||||
Rechteck zeigt sie in Originalgröße, darunter stehen die Werte für die Tabelle.
|
||||
Ein Klick friert die Ablesung ein.
|
||||
</p>
|
||||
|
||||
<?php foreach ($floors as $floor): ?>
|
||||
<?php
|
||||
$mitKachel = roomsOnFloor($floor);
|
||||
$ohneKachel = array_values(array_filter(allRooms(), function ($r) use ($floor) {
|
||||
return $r["floor"] === $floor && !isset($r["x"]);
|
||||
}));
|
||||
?>
|
||||
<section class="etage">
|
||||
<h2><?= htmlspecialchars($floor) ?><?php if ($ohneKachel): ?>
|
||||
<span>· noch ohne Kachel:
|
||||
<?= htmlspecialchars(implode(", ", array_column($ohneKachel, "mqtt"))) ?></span>
|
||||
<?php endif; ?></h2>
|
||||
|
||||
<svg viewBox="0 0 <?= PLAN_BREIT ?> <?= PLAN_HOCH ?>" class="plan">
|
||||
<defs>
|
||||
<!-- Eigenes Raster je Etage: eine Kennung darf im Dokument nur
|
||||
einmal vorkommen, sonst zeigt url(#...) immer auf dasselbe. -->
|
||||
<pattern id="raster<?= $floor ?>" width="10" height="10" patternUnits="userSpaceOnUse">
|
||||
<path d="M10 0H0V10" fill="none" stroke="#ffffff22" stroke-width=".4"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<image href="../assets/img/<?= $floor ?>.png" x="0" y="0"
|
||||
width="<?= PLAN_BREIT ?>" height="<?= PLAN_HOCH ?>"/>
|
||||
<rect x="0" y="0" width="<?= PLAN_BREIT ?>" height="<?= PLAN_HOCH ?>"
|
||||
fill="url(#raster<?= $floor ?>)"/>
|
||||
|
||||
<g class="achsen">
|
||||
<?php for ($v = 50; $v < PLAN_BREIT; $v += 50): ?>
|
||||
<text x="<?= $v ?>" y="9" text-anchor="middle"><?= $v ?></text>
|
||||
<?php endfor; ?>
|
||||
<?php for ($v = 50; $v < PLAN_HOCH; $v += 50): ?>
|
||||
<text x="4" y="<?= $v + 3 ?>"><?= $v ?></text>
|
||||
<?php endfor; ?>
|
||||
</g>
|
||||
|
||||
<?php foreach ($mitKachel as $room): ?>
|
||||
<g transform="translate(<?= $room["x"] ?>,<?= $room["y"] ?>)">
|
||||
<rect class="kachel" width="<?= KACHEL_BREIT ?>" height="<?= KACHEL_HOCH ?>" rx="5"/>
|
||||
<circle class="anker" r="1.6"/>
|
||||
<text class="kachelname" x="<?= KACHEL_BREIT / 2 ?>" y="11">
|
||||
<?= htmlspecialchars(mb_substr($room["mqtt"], 0, 8)) ?></text>
|
||||
</g>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<g class="zeiger" style="display:none">
|
||||
<rect class="geist" width="<?= KACHEL_BREIT ?>" height="<?= KACHEL_HOCH ?>" rx="5"/>
|
||||
<line class="kreuz" x1="<?= -PLAN_BREIT ?>" x2="<?= PLAN_BREIT ?>"
|
||||
y1="<?= KACHEL_HOCH / 2 ?>" y2="<?= KACHEL_HOCH / 2 ?>"/>
|
||||
<line class="kreuz" y1="<?= -PLAN_HOCH ?>" y2="<?= PLAN_HOCH ?>"
|
||||
x1="<?= KACHEL_BREIT / 2 ?>" x2="<?= KACHEL_BREIT / 2 ?>"/>
|
||||
</g>
|
||||
</svg>
|
||||
<p class="ablesung">Zeiger über den Grundriss bewegen …</p>
|
||||
</section>
|
||||
<?php endforeach; ?>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Muss zu den Konstanten oben passen - der Server schreibt sie hier hinein.
|
||||
const BREIT = <?= KACHEL_BREIT ?>, HOCH = <?= KACHEL_HOCH ?>;
|
||||
const PLAN_B = <?= PLAN_BREIT ?>, PLAN_H = <?= PLAN_HOCH ?>;
|
||||
|
||||
document.querySelectorAll(".etage").forEach(function (etage) {
|
||||
const plan = etage.querySelector(".plan");
|
||||
const zeiger = etage.querySelector(".zeiger");
|
||||
const text = etage.querySelector(".ablesung");
|
||||
let fest = false;
|
||||
|
||||
/**
|
||||
* Vom Zeiger zur Kachel: gezeigt wird auf die Mitte, gebraucht wird die
|
||||
* linke obere Ecke. Die Umrechnung gehoert hierher und nicht in den Kopf
|
||||
* dessen, der misst.
|
||||
*/
|
||||
function stelle(ereignis) {
|
||||
const r = plan.getBoundingClientRect();
|
||||
const mx = (ereignis.clientX - r.left) / r.width * PLAN_B;
|
||||
const my = (ereignis.clientY - r.top) / r.height * PLAN_H;
|
||||
const x = Math.round(mx - BREIT / 2);
|
||||
const y = Math.round(my - HOCH / 2);
|
||||
zeiger.style.display = "";
|
||||
zeiger.setAttribute("transform", "translate(" + x + "," + y + ")");
|
||||
return { x: x, y: y, mx: Math.round(mx), my: Math.round(my) };
|
||||
}
|
||||
|
||||
function zeile(p) {
|
||||
return 'Mitte ' + p.mx + ',' + p.my + ' → <b>"x" => ' +
|
||||
p.x + ', "y" => ' + p.y + '</b>';
|
||||
}
|
||||
|
||||
plan.addEventListener("pointermove", function (e) {
|
||||
if (fest) return;
|
||||
text.innerHTML = zeile(stelle(e));
|
||||
});
|
||||
plan.addEventListener("pointerleave", function () {
|
||||
if (fest) return;
|
||||
zeiger.style.display = "none";
|
||||
text.textContent = "Zeiger über den Grundriss bewegen …";
|
||||
});
|
||||
plan.addEventListener("click", function (e) {
|
||||
const p = stelle(e);
|
||||
fest = !fest;
|
||||
text.innerHTML = fest
|
||||
? '<span class="fest">festgehalten:</span> "x" => <b>' + p.x +
|
||||
'</b>, "y" => <b>' + p.y + '</b> (Mitte ' + p.mx + ',' + p.my +
|
||||
') – nochmal klicken zum Lösen'
|
||||
: zeile(p);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user