165 lines
7.4 KiB
PHP
Executable File
165 lines
7.4 KiB
PHP
Executable File
<?php
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/lib/bootstrap.php';
|
|
|
|
function app_is_embed_request(): bool
|
|
{
|
|
$value = strtolower(trim((string)($_GET['embed'] ?? '')));
|
|
if (in_array($value, ['1', 'true', 'yes', 'on'], true)) {
|
|
return true;
|
|
}
|
|
|
|
$mode = strtolower(trim((string)($_GET['mode'] ?? '')));
|
|
return in_array($mode, ['embed', 'panel', 'lovelace', 'ha'], true);
|
|
}
|
|
|
|
$config = app_load_config();
|
|
$client = new HomeAssistantClient($config);
|
|
$bootstrap = app_build_snapshot($config, $client, 'main');
|
|
$embedMode = app_is_embed_request();
|
|
$runtimeMode = trim((string)getenv('WALL_PANEL_RUNTIME_MODE'));
|
|
if ($runtimeMode === '') {
|
|
$runtimeMode = $embedMode ? 'ha' : 'standalone';
|
|
}
|
|
$bootstrap['ui'] = [
|
|
'embed' => $embedMode,
|
|
'mode' => $runtimeMode,
|
|
'shell' => $embedMode ? 'embed' : 'standalone',
|
|
'config_source' => app_remote_sync_enabled($config) ? 'ha' : 'file',
|
|
];
|
|
$appTitle = htmlspecialchars((string)($config['app']['title'] ?? 'Wall Panel'), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
|
?>
|
|
<!doctype html>
|
|
<html lang="ru">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
<meta name="theme-color" content="#0d0f14">
|
|
<title><?= $appTitle ?></title>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Space+Grotesk:wght@400;500;700&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css">
|
|
<script src="https://home.striker72rus.ru/local/community/custom-brand-icons/custom-brand-icons.js" defer></script>
|
|
<script>
|
|
window.APP_BOOTSTRAP = <?= json_encode($bootstrap, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
|
</script>
|
|
<link rel="stylesheet" href="assets/app.css?v=0.28">
|
|
<script src="assets/app.js?v=0.28" defer></script>
|
|
</head>
|
|
<body class="<?= $embedMode ? 'is-embedded' : '' ?>" data-ui-mode="<?= htmlspecialchars($runtimeMode, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>">
|
|
<div class="app-shell<?= $embedMode ? ' app-shell--embed' : '' ?>">
|
|
<aside class="sidebar">
|
|
<section class="clock-panel">
|
|
<div class="clock-panel__time" id="clock-time">--:--</div>
|
|
<div class="clock-panel__date" id="clock-date">---</div>
|
|
</section>
|
|
|
|
<section class="rooms-panel">
|
|
<div class="panel-header">
|
|
<div>
|
|
<div class="panel-header__label">Пространства</div>
|
|
<div class="panel-header__sub" id="rooms-count">0</div>
|
|
</div>
|
|
<button class="icon-button" id="edit-mode-toggle" type="button" aria-label="Edit mode">
|
|
<i class="mdi mdi-cog-outline"></i>
|
|
</button>
|
|
</div>
|
|
<div class="room-list" id="room-list"></div>
|
|
</section>
|
|
</aside>
|
|
|
|
<main class="content">
|
|
<div class="content-top" id="content-top">
|
|
<div class="main-print-strip-slot" id="main-print-strip-slot"></div>
|
|
</div>
|
|
<header class="content-header">
|
|
<button class="icon-button icon-button--ghost content-header__back" id="selected-room-back" type="button" aria-label="Back" hidden>
|
|
<i class="mdi mdi-arrow-left"></i>
|
|
</button>
|
|
<div>
|
|
<div class="content-header__eyebrow" id="selected-room-eyebrow"></div>
|
|
<h1 class="content-header__title" id="selected-room-title">Загрузка</h1>
|
|
<div class="content-header__meta" id="selected-room-meta"></div>
|
|
</div>
|
|
<div class="content-header__actions" id="selected-room-actions"></div>
|
|
</header>
|
|
|
|
<section class="dashboard-grid" id="dashboard-grid">
|
|
<div class="grid-surface" id="dashboard-surface">
|
|
<div class="loading-card">Загрузка панели...</div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
</div>
|
|
|
|
<div class="modal-backdrop" id="camera-modal" aria-hidden="true">
|
|
<div class="camera-modal" id="camera-modal-panel">
|
|
<button class="icon-button icon-button--ghost camera-modal__close" id="camera-modal-close" type="button" aria-label="Close">
|
|
<i class="mdi mdi-close"></i>
|
|
</button>
|
|
<div class="camera-modal__body">
|
|
<div class="camera-stage" id="camera-stage">
|
|
<img class="camera-stage__poster" id="camera-poster" alt="Camera poster">
|
|
<div class="camera-stage__placeholder" id="camera-placeholder">
|
|
<div class="camera-stage__placeholder-icon"><i class="mdi mdi-cctv"></i></div>
|
|
<div class="camera-stage__placeholder-title">Поток загружается</div>
|
|
<div class="camera-stage__placeholder-subtitle">Показываем poster, пока не доступен video bridge</div>
|
|
</div>
|
|
</div>
|
|
<div class="camera-modal__footer">
|
|
<div class="camera-modal__countdown" id="camera-countdown"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-backdrop" id="entity-modal" aria-hidden="true">
|
|
<div class="entity-modal" id="entity-modal-panel" role="dialog" aria-modal="true" aria-labelledby="entity-modal-title">
|
|
<div class="entity-modal__header">
|
|
<div>
|
|
<div class="entity-modal__eyebrow" id="entity-modal-eyebrow"></div>
|
|
<div class="entity-modal__title" id="entity-modal-title">Устройство</div>
|
|
</div>
|
|
<button class="icon-button icon-button--ghost" id="entity-modal-close" type="button" aria-label="Close">
|
|
<i class="mdi mdi-close"></i>
|
|
</button>
|
|
</div>
|
|
<div class="entity-modal__body" id="entity-modal-body"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-backdrop" id="temperature-sensor-modal" aria-hidden="true">
|
|
<div class="temperature-sensor-modal" id="temperature-sensor-modal-panel" role="dialog" aria-modal="true" aria-labelledby="temperature-sensor-modal-title">
|
|
<div class="temperature-sensor-modal__header">
|
|
<div>
|
|
<div class="temperature-sensor-modal__eyebrow">Настройка комнаты</div>
|
|
<div class="temperature-sensor-modal__title" id="temperature-sensor-modal-title">Выбрать датчик температуры</div>
|
|
</div>
|
|
<button class="icon-button icon-button--ghost" id="temperature-sensor-modal-close" type="button" aria-label="Close">
|
|
<i class="mdi mdi-close"></i>
|
|
</button>
|
|
</div>
|
|
<div class="temperature-sensor-modal__body" id="temperature-sensor-modal-body"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-backdrop" id="confirm-modal" aria-hidden="true">
|
|
<div class="confirm-modal" id="confirm-modal-panel" role="dialog" aria-modal="true" aria-labelledby="confirm-modal-title">
|
|
<div class="confirm-modal__header">
|
|
<div>
|
|
<div class="confirm-modal__eyebrow">Подтверждение</div>
|
|
<div class="confirm-modal__title" id="confirm-modal-title">Хотите закрыть?</div>
|
|
</div>
|
|
</div>
|
|
<div class="confirm-modal__body" id="confirm-modal-message">Это действие отправит команду закрытия.</div>
|
|
<div class="confirm-modal__footer">
|
|
<button class="mushroom-button mushroom-button--small" id="confirm-modal-no" type="button">Нет</button>
|
|
<button class="mushroom-button mushroom-button--small is-on" id="confirm-modal-yes" type="button">Да</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|