Etagen, Raeume, Kachelpositionen und Kachelwerte standen als Tabelle in restricted/rooms.php, die Werte zusaetzlich ueberschreibbar in solarLog.kachelWerte. Fuer ein anderes Haus haette man den Quelltext umschreiben muessen. Jetzt liegen sie in homeMesh.floors und homeMesh.rooms (homeMesh_grundriss.sql). rooms.php liest sie und gibt sie in der bisherigen Form weiter; Startseite, Menue, Editor und Einstellungen sind unveraendert - vorher und nachher als HTML verglichen, gleich bis auf Livewerte und die zwei neuen Felder nr und thermostat in homeRooms. - Vorgabe einer Kachel ist jetzt allein das Thermostat (rooms.thermostat): Soll, Ist, Feuchte - sonst nichts. Was bisher im Code als Vorgabe stand, gilt damit als selbst gesetzt. - Grundrissbild je Etage (floors.bild) statt assets/img/<code>.png. - Standard-Etage (floors.standard) statt "OG" an vier Stellen. - meshDb() in eigener Datei, sonst Ring zwischen rooms.php und automations.php. - solarLog_kacheln.sql entfaellt; die Tabelle kachelWerte liest niemand mehr. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
215 lines
8.1 KiB
PHP
215 lines
8.1 KiB
PHP
<?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="../<?= htmlspecialchars((string)etageBild($floor)) ?>" 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>
|