Die Uebersicht auf der Startseite zeigt die Automatiken jetzt als Zeitleiste: eine Bahn je Geraeteart, die Spur ist der Tag. Der Umschalter "Zeitleiste | Liste" oben in der Karte fuehrt zur bisherigen Tabelle zurueck und wird im Browser gemerkt. Jede Automatik erscheint genau einmal, nach festen Regeln (restricted/zeitleiste.php), auch kuenftige: - feste Uhrzeit oder Sonnenstand: Punkt; Zeitfenster mit Messwert: Balken; Kette: hinter ihrem Ausloeser, als gestrichelter Bogen verbunden - ohne jede Zeit: Band "Jederzeit"; pausiert: Band "Pausiert" - an diesem Tag nicht dran (Wochentag, Ferien, Feiertag, Vorabend, wie im Runner gerechnet): gestrichelt mit Grund, nie ausgeblendet - Bahn aus den geschalteten Geraeten ueber bedienform(), Mehrheit gewinnt; Bewaesserung am Geraetetyp - Zaehler "x von y" im Kopf Dazu: Tag vor/zurueck, Etagen einzeln einblendbar (gemerkt), Jetzt-Linie und Nachtschatten aus solarLog.daylight (spaetere Tage vom selben Kalendertag eines Vorjahres), gelaufene Eintraege mit Uhrzeit aus automation_log, Klick oeffnet den Editor. Schmal nur Symbole in den Bahnen und Start bei der aktuellen Uhrzeit. Auf einer Probeseite mit echten Daten geprueft: nichts ragt ueber, keine ueberlappenden Etiketten, alle 22 Automatiken genau einmal, Filter, Tageswechsel und Umschalter. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
285 lines
15 KiB
PHP
285 lines
15 KiB
PHP
<?php
|
||
require_once(__DIR__ . "/rooms.php");
|
||
require_once(__DIR__ . "/reiter.php");
|
||
?>
|
||
<!--begin::App Main-->
|
||
<main class="app-main">
|
||
<!--begin::App Content Header-->
|
||
<div class="app-content-header">
|
||
<!--begin::Container-->
|
||
<div class="container-fluid">
|
||
<!--begin::Row-->
|
||
<div class="row">
|
||
<div class="col-sm-12">
|
||
<h3 class="mb-0">Home</h3>
|
||
</div>
|
||
</div>
|
||
<!--end::Row-->
|
||
</div>
|
||
<!--end::Container-->
|
||
</div>
|
||
<!--end::App Content Header-->
|
||
<!--begin::App Content-->
|
||
<div class="app-content">
|
||
<!--begin::Container-->
|
||
<div class="container-fluid">
|
||
<!--begin::Row-->
|
||
<div class="row">
|
||
<!-- Start col -->
|
||
<div class="col-xxl-12 col-xl-12 col-lg-12 col-md-12 d-flex align-items-stretch">
|
||
<div class="card p-0 mb-4" style="width:100%">
|
||
<div class="card-header pb-0 pt-1">
|
||
<h3 class="card-title">Home</h3>
|
||
<div class="card-tools">
|
||
<button type="button" class="btn btn-tool" data-lte-toggle="card-remove">
|
||
<i class="bi bi-x-lg"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="card-body pb-1">
|
||
<?php if (!floorsWithPlan()): ?>
|
||
<!-- Eine neue Installation: noch keine Etage mit platzierten
|
||
Raeumen. Statt einer leeren Zeichenflaeche der Weg dorthin,
|
||
wo sich das aendert. -->
|
||
<div class="alert alert-secondary mb-3">
|
||
<?php if (!grundrissDa()): ?>
|
||
Etagen und Räume lassen sich nicht lesen – die Datenbank
|
||
<code>homeMesh</code> ist nicht erreichbar, oder
|
||
<code>homeMesh_grundriss.sql</code> ist noch nicht eingespielt.
|
||
<?php else: ?>
|
||
Noch kein Grundriss. Unter
|
||
<a href="?action=settings#grundriss">Einstellungen → Grundriss</a>
|
||
werden Etagen angelegt, ihre Grundrissbilder hochgeladen und die
|
||
Kacheln der Räume gesetzt.
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php else: ?>
|
||
<div lass="img-overlay-wrap">
|
||
<svg viewBox="0 0 400 300" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
||
|
||
<defs>
|
||
<g id="heater" filter="url(#shadow)" transform="translate(1.5,1),scale(0.2,0.2)" fill="#993311" stroke="#FF2211">
|
||
<path clip-path="M-22 2.24h42V22h-42z"
|
||
d="M16.543 8.028c-.023 1.503-.523 3.538-2.867 4.327.734-1.746.846-3.417.326-4.979-.695-2.097-3.014-3.735-4.557-4.627-.527-.306-1.203.074-1.193.683.02 1.112-.318 2.737-1.959 4.378C4.107 9.994 3 12.251 3 14.517 3 17.362 5 21 9 21c-4.041-4.041-1-7.483-1-7.483C8.846 19.431 12.988 21 15 21c1.711 0 5-1.25 5-6.448 0-3.133-1.332-5.511-2.385-6.899-.347-.458-1.064-.198-1.072.375" />
|
||
</g>
|
||
<g id="buffer" filter="url(#shadow)" transform="translate(18,1),scale(0.3,0.3)" fill="#ffffff">
|
||
<path d="M5 12.5a1.5 1.5 0 1 1-2-1.415V2.5a.5.5 0 0 1 1 0v8.585A1.5 1.5 0 0 1 5 12.5"/>
|
||
<path d="M1 2.5a2.5 2.5 0 0 1 5 0v7.55a3.5 3.5 0 1 1-5 0zM3.5 1A1.5 1.5 0 0 0 2 2.5v7.987l-.167.15a2.5 2.5 0 1 0 3.333 0L5 10.486V2.5A1.5 1.5 0 0 0 3.5 1m5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0v-1a.5.5 0 0 1 .5-.5m4.243 1.757a.5.5 0 0 1 0 .707l-.707.708a.5.5 0 1 1-.708-.708l.708-.707a.5.5 0 0 1 .707 0M8 5.5a.5.5 0 0 1 .5-.5 3 3 0 1 1 0 6 .5.5 0 0 1 0-1 2 2 0 0 0 0-4 .5.5 0 0 1-.5-.5M12.5 8a.5.5 0 0 1 .5-.5h1a.5.5 0 1 1 0 1h-1a.5.5 0 0 1-.5-.5m-1.172 2.828a.5.5 0 0 1 .708 0l.707.708a.5.5 0 0 1-.707.707l-.708-.707a.5.5 0 0 1 0-.708M8.5 12a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0v-1a.5.5 0 0 1 .5-.5"/>
|
||
</g>
|
||
</defs>
|
||
<g>
|
||
<?php
|
||
/*
|
||
* Ein Bild je Etage, dazu fuer jede Etage eine Blende.
|
||
* Ausgeschrieben waren das bei drei Etagen neun Zeilen
|
||
* und bei vier schon sechzehn - und jede neue Etage
|
||
* haette alle anderen mit angefasst.
|
||
*
|
||
* floorsWithPlan() liefert nur Etagen, deren Raeume
|
||
* Koordinaten haben. Eine Etage ohne angeklickte
|
||
* Kachelpositionen taucht hier also gar nicht erst auf.
|
||
*/
|
||
$etagen = floorsWithPlan();
|
||
?>
|
||
<?php /* Von unten nach oben gestapelt, wie bisher: waehrend der Blende
|
||
sind zwei Bilder gleichzeitig halb sichtbar, und dann soll das
|
||
obere Geschoss obenauf liegen. */ ?>
|
||
<?php foreach (array_reverse($etagen) as $etage): ?>
|
||
<g id="<?= $etage ?>img" <?php if ($_GET["floor"] != $etage) echo "opacity='0'"; ?> >
|
||
<?php foreach ($etagen as $andere): ?>
|
||
<animate begin="<?= $andere ?>.click" attributetype="CSS" attributeName="opacity"
|
||
to=<?= $andere === $etage ? "1.0" : "0.0" ?> dur="0.7s"
|
||
repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1" fill="freeze" />
|
||
<?php endforeach; ?>
|
||
<image href="<?= htmlspecialchars((string)etageBild($etage)) ?>" height="100%" width="100%" x=0 y=0 />
|
||
</g>
|
||
<?php endforeach; ?>
|
||
<?php
|
||
/*
|
||
* Groesse der Etagenschalter. Der Kreis ist im eigenen
|
||
* Koordinatensystem 88 Einheiten breit und reicht von 28
|
||
* bis 116 unter die Oberkante seiner Gruppe; alles andere
|
||
* folgt daraus. Sie waren einmal doppelt so gross und
|
||
* beherrschten damit die linke Seite - und das Bild des
|
||
* Aussengelaendes musste umso weiter einruecken, um sie
|
||
* freizuhalten.
|
||
*/
|
||
$mass = 0.38;
|
||
$kreisUnten = 116 * $mass;
|
||
|
||
/*
|
||
* Bei drei Etagen bleibt es bei den gewohnten 100
|
||
* Einheiten Abstand; ab der vierten ruecken sie zusammen,
|
||
* sonst faellt die unterste aus der 300 hohen viewBox.
|
||
*/
|
||
$abstand = count($etagen) > 1
|
||
? min(100, (295 - $kreisUnten) / (count($etagen) - 1))
|
||
: 0;
|
||
?>
|
||
<?php foreach ($etagen as $i => $etage): ?>
|
||
<g id="<?= $etage ?>" class="floorBtn<?= $_GET["floor"] == $etage ? " active" : "" ?>"
|
||
onclick="switchFloor('<?= $etage ?>')"
|
||
transform="translate(0,<?= round($i * $abstand, 1) ?>), scale(<?= $mass ?>)">
|
||
<!-- Die Beschriftung sitzt ueber text-anchor und
|
||
dominant-baseline im Kreismittelpunkt. Vorher stand
|
||
je Etage ein von Hand gesuchter Versatz im Markup
|
||
(62, 63, 64), und eine neue Etage brauchte einen
|
||
neuen. Aussehen und Zustand stehen in css/solar.css. -->
|
||
<circle cx="50" cy="72" r="44" />
|
||
<text x="50" y="72" text-anchor="middle"
|
||
dominant-baseline="central"><?= $etage ?></text>
|
||
</g>
|
||
<?php endforeach; ?>
|
||
<?php foreach (floorsWithPlan() as $floor): ?>
|
||
<g id="<?= $floor ?>_Info" <?php if ($floor != standardEtage()) echo 'opacity="0" '; ?><?php if($_GET["floor"] != $floor){echo "display='none'";} ?> >
|
||
<?php foreach (array_map("mitWerten", roomsOnFloor($floor)) as $room): ?>
|
||
<?php
|
||
/*
|
||
* Grundlinien je nach Anzahl der Werte. Die Kachel ist 19 hoch; mit
|
||
* drei Werten sind es dieselben Zeilen wie eh und je (klein, gross,
|
||
* klein), mit weniger ruecken sie in die Mitte.
|
||
*/
|
||
$zeilen = [1 => [12], 2 => [9, 16], 3 => [5, 11.5, 16.5]];
|
||
$y = $zeilen[count($room["werte"])] ?? [];
|
||
?>
|
||
<g transform="translate(<?= $room["x"] ?>,<?= $room["y"] ?>)" onclick="openRoomView(<?= $room["nr"] ?>);" cursor="pointer">
|
||
<rect width="24" height="19" rx="5" fill="#0006" />
|
||
<?php if ($room["heizung"]): ?>
|
||
<use id="<?= $floor ?>_<?= $room["id"] ?>_heater" href="#heater" display="none" />
|
||
<use id="<?= $floor ?>_<?= $room["id"] ?>_buffer" href="#buffer" display="none" />
|
||
<?php endif; ?>
|
||
<text fill="#fff" style="font: 4px sans-serif;">
|
||
<?php if (!$room["werte"]): ?>
|
||
<?php
|
||
/*
|
||
* Kein Messwert: die Kachel traegt ihren Namen und oeffnet den Raum.
|
||
* Ein leeres Rechteck saehe aus wie ein Fehler.
|
||
*
|
||
* Lange Namen brechen am letzten Leerzeichen um. Die Kachel ist 24
|
||
* Einheiten breit, und "Terrasse OG" stand einzeilig links und rechts
|
||
* darueber hinaus. Aufgefallen ist das erst mit dem Aussengelaende:
|
||
* dort besteht jeder Raumname aus zwei Woertern.
|
||
*/
|
||
$luecke = strrpos($room["mqtt"], " ");
|
||
$teile = (strlen($room["mqtt"]) > 8 && $luecke !== false)
|
||
? [substr($room["mqtt"], 0, $luecke), substr($room["mqtt"], $luecke + 1)]
|
||
: [$room["mqtt"]];
|
||
$namenszeilen = count($teile) === 2 ? [9, 15.5] : [11.5];
|
||
?>
|
||
<?php foreach ($teile as $i => $teil): ?>
|
||
<tspan x="12" y="<?= $namenszeilen[$i] ?>" font-size="1.2em" text-anchor="middle"><?= htmlspecialchars($teil) ?></tspan>
|
||
<?php endforeach; ?>
|
||
<?php endif; ?>
|
||
<?php foreach ($room["werte"] as $i => $wert): ?>
|
||
<?php $zeichen = kachelIcon($wert["icon"] ?? ""); ?>
|
||
<?php if ($zeichen !== ""): ?>
|
||
<!-- Ein Symbol vor der Zahl. "0 W" allein sagt nicht,
|
||
ob geladen, erzeugt oder verbraucht wird; die
|
||
Einheit beantwortet das nicht. Die Zahl rueckt
|
||
dafuer nach rechts, damit Symbol und Wert
|
||
zusammen mittig stehen. -->
|
||
<tspan x="4" y="<?= $y[$i] ?>" text-anchor="middle" opacity=".8"
|
||
style="font-family:bootstrap-icons"><?= $zeichen ?></tspan>
|
||
<?php endif; ?>
|
||
<!-- Der Platzhalter ist nur ein Strich: bevor der
|
||
erste Wert da ist, soll hier keine Einheit
|
||
stehen, die vielleicht gar nicht passt. -->
|
||
<tspan id="<?= $floor ?>_w<?= $i ?>_<?= $room["id"] ?>"
|
||
x="<?= $zeichen !== "" ? 15 : 12 ?>" y="<?= $y[$i] ?>"
|
||
<?= empty($wert["gross"]) ? "" : 'font-size="1.6em"' ?> text-anchor="middle">–</tspan>
|
||
<?php endforeach; ?>
|
||
</text>
|
||
</g>
|
||
<?php endforeach; ?>
|
||
</g>
|
||
<?php endforeach; ?>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
<!-- /.card -->
|
||
</div>
|
||
<div class="col-xxl-12 col-xl-12 col-lg-12 col-md-12 d-flex align-items-stretch">
|
||
<div class="card p-0 mb-4" style="width:100%">
|
||
<div class="card-header pb-1 pt-1 d-flex align-items-center gap-2 flex-wrap">
|
||
<h3 class="card-title me-auto">Automatismen</h3>
|
||
<!-- Zwei Ansichten derselben Automatiken. Die Zeitleiste zeigt den
|
||
Tag (js/solar/zeitleiste.js), die Liste ist die bisherige
|
||
Tabelle je Etage. Welche offen ist, merkt sich der Browser. -->
|
||
<div class="btn-group btn-group-sm zl-ansicht" role="group" aria-label="Ansicht">
|
||
<button type="button" class="btn btn-outline-secondary" data-ansicht="zeitleiste">
|
||
<i class="bi bi-bar-chart-steps me-1"></i>Zeitleiste</button>
|
||
<button type="button" class="btn btn-outline-secondary" data-ansicht="liste">
|
||
<i class="bi bi-list-ul me-1"></i>Liste</button>
|
||
</div>
|
||
<button type="button" class="btn btn-sm btn-success zl-neu" title="Neue Automatik">
|
||
<i class="bi bi-plus-lg"></i></button>
|
||
</div>
|
||
<div class="card-body pb-2">
|
||
<div id="zeitleiste" class="zl" hidden></div>
|
||
<div id="automatikListe">
|
||
<?php
|
||
/*
|
||
* Eine Etage, ein Reiter. Gezeichnet von reiterLeiste() aus
|
||
* restricted/reiter.php - dieselbe Bauart wie im Raum-Modal und
|
||
* auf der Einstellungsseite. Frueher stand hier ein eigenes
|
||
* Markup samt eigenem switchTab() in homeMQTT.js; umgeschaltet
|
||
* wird jetzt von Bootstrap wie ueberall sonst.
|
||
*
|
||
* Der Name "actions" haelt die Element-Ids, an denen
|
||
* autoActionFuncs.js haengt: actions-OG, actions-OG-list.
|
||
*/
|
||
$autoReiter = array_map(function ($etage) {
|
||
return ["id" => $etage, "titel" => $etage,
|
||
"titel2" => floorLabel($etage)];
|
||
}, $floors);
|
||
$autoOpt = ["name" => "actions", "aktiv" => $_GET["floor"],
|
||
"klasse" => "mb-2"];
|
||
$autoOpt["aktiv"] = reiterAktiv($autoReiter, $autoOpt);
|
||
?>
|
||
<?= reiterLeiste($autoReiter, $autoOpt) ?>
|
||
<div class="tab-content" id="actions-tabContent">
|
||
<?php foreach ($floors as $etage): ?>
|
||
<?= reiterFlaecheAuf($etage, $autoOpt) ?>
|
||
<!-- Inhalt kommt aus ajax/AutoAction.php?action=list, gefuellt von
|
||
refreshAutomations() in js/solar/autoActionFuncs.js -->
|
||
<div id="actions-<?= $etage ?>-list">Wird geladen...</div>
|
||
<button type="button" class="btn btn-success" title="Neue Automatik"
|
||
onclick="openAutoActionModal('?action=editor&floor=<?= $etage ?>')"><i class="bi bi-plus-square"></i></button>
|
||
<?= reiterFlaecheZu() ?>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</div><!-- /automatikListe -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- /.Start col -->
|
||
</div>
|
||
|
||
<!-- /.row (main row) -->
|
||
</div>
|
||
<!--end::Container-->
|
||
</div>
|
||
<!--end::App Content-->
|
||
</main>
|
||
<!--end::App Main-->
|
||
|
||
<script>
|
||
// Raumtabelle aus restricted/rooms.php - homeMQTT.js aktualisiert darueber
|
||
// die SVG-Kacheln, ohne die Raeume noch einmal aufzufuehren. In "werte"
|
||
// steht, welche Messwerte eine Kachel zeigt; homeTopics sind die Zweige,
|
||
// die der Browser dafuer beim Broker anmelden muss.
|
||
const homeRooms = <?= json_encode(roomsWithTile(), JSON_UNESCAPED_UNICODE) ?>;
|
||
const homeTopics = <?= json_encode(tileTopics(), JSON_UNESCAPED_UNICODE) ?>;
|
||
const homeStandardEtage = <?= json_encode(standardEtage()) ?>;
|
||
<?php if ($_GET["floor"] != standardEtage() && in_array($_GET["floor"], floorsWithPlan())): ?>
|
||
document.addEventListener('readystatechange', function () {
|
||
if (event.target.readyState === 'complete') {
|
||
switchFloor('<?= $_GET["floor"] ?>');
|
||
}
|
||
});
|
||
<?php endif; ?>
|
||
</script>
|