archive: replace toolbar with a 220px split sidebar (camera list + calendar)
The player-frame toolbar (camera <select> + date input) is removed. Archive main is now a row: a 220px sidebar split in half by height — the top half is a scrollable clickable camera list, the bottom half holds the date input plus a new inline month calendar (prev/next month, today + selected highlight, Monday-first). Picking a camera or a day loads that day's recordings. The player and DVR timeline move into an .arch-content column on the right. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+79
-9
@@ -1025,7 +1025,7 @@ class ArchivePlayer {
|
||||
}
|
||||
|
||||
const Archive = {
|
||||
cameras: [], cam: null, date: null, segments: [], transcode: false,
|
||||
cameras: [], cam: null, date: null, segments: [], transcode: false, calYear: 0, calMonth: 0,
|
||||
centerTime: 0, dayStart: 0, dragging: false, following: false, track: null, _curSeg: null, _playTok: 0,
|
||||
|
||||
async init() {
|
||||
@@ -1033,16 +1033,19 @@ const Archive = {
|
||||
this.cameras = data.cameras;
|
||||
this.transcode = profileMode("a") === "transcode"; // профиль архива (этот браузер)
|
||||
this.cam = this.cameras[0] ? this.cameras[0].id : null;
|
||||
this.buildCamList();
|
||||
|
||||
const sel = document.getElementById("cam-select");
|
||||
sel.innerHTML = this.cameras.map((c) => `<option value="${c.id}">${c.name}</option>`).join("");
|
||||
sel.onchange = () => { this.cam = sel.value; this.load(); };
|
||||
|
||||
const dp = document.getElementById("date");
|
||||
const today = new Date();
|
||||
dp.value = `${today.getFullYear()}-${pad(today.getMonth() + 1)}-${pad(today.getDate())}`;
|
||||
this.date = dp.value;
|
||||
dp.onchange = () => { this.date = dp.value; this.load(); };
|
||||
this.date = `${today.getFullYear()}-${pad(today.getMonth() + 1)}-${pad(today.getDate())}`;
|
||||
this.calYear = today.getFullYear();
|
||||
this.calMonth = today.getMonth();
|
||||
const dp = document.getElementById("date");
|
||||
if (dp) {
|
||||
dp.value = this.date;
|
||||
dp.onchange = () => { this.date = dp.value; this.syncCalToDate(); this.renderCalendar(); this.load(); };
|
||||
}
|
||||
this.bindCalendar();
|
||||
this.renderCalendar();
|
||||
|
||||
// при ресайзе окна пересчитать смещение ленты (центр зависит от ширины вьюпорта)
|
||||
window.addEventListener("resize", () => { if (this.track) this._applyTl(); });
|
||||
@@ -1050,6 +1053,73 @@ const Archive = {
|
||||
this.load();
|
||||
},
|
||||
|
||||
// верхняя половина сайдбара: список камер (клик — выбор)
|
||||
buildCamList() {
|
||||
const box = document.getElementById("arch-cams");
|
||||
if (!box) return;
|
||||
box.innerHTML = this.cameras.map((c) =>
|
||||
`<div class="arch-cam${c.id === this.cam ? " active" : ""}" data-cam="${c.id}">${c.name}</div>`
|
||||
).join("");
|
||||
box.onclick = (e) => {
|
||||
const it = e.target.closest(".arch-cam[data-cam]");
|
||||
if (!it) return;
|
||||
this.cam = it.dataset.cam;
|
||||
box.querySelectorAll(".arch-cam").forEach((x) => x.classList.toggle("active", x.dataset.cam === this.cam));
|
||||
this.load();
|
||||
};
|
||||
},
|
||||
|
||||
syncCalToDate() {
|
||||
const [y, m] = (this.date || "").split("-").map(Number);
|
||||
if (y && m) { this.calYear = y; this.calMonth = m - 1; }
|
||||
},
|
||||
|
||||
bindCalendar() {
|
||||
const box = document.getElementById("calendar");
|
||||
if (!box) return;
|
||||
box.onclick = (e) => {
|
||||
const nav = e.target.closest("[data-cal]");
|
||||
if (nav) {
|
||||
if (nav.dataset.cal === "prev") { if (--this.calMonth < 0) { this.calMonth = 11; this.calYear--; } }
|
||||
else { if (++this.calMonth > 11) { this.calMonth = 0; this.calYear++; } }
|
||||
this.renderCalendar();
|
||||
return;
|
||||
}
|
||||
const day = e.target.closest(".cal-day[data-day]");
|
||||
if (!day) return;
|
||||
this.date = `${this.calYear}-${pad(this.calMonth + 1)}-${pad(+day.dataset.day)}`;
|
||||
const dp = document.getElementById("date");
|
||||
if (dp) dp.value = this.date;
|
||||
this.renderCalendar();
|
||||
this.load();
|
||||
};
|
||||
},
|
||||
|
||||
// нижняя половина сайдбара: месячный календарь
|
||||
renderCalendar() {
|
||||
const box = document.getElementById("calendar");
|
||||
if (!box) return;
|
||||
const MN = ["Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"];
|
||||
const WD = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"];
|
||||
const y = this.calYear, m = this.calMonth;
|
||||
const [sy, sm, sd] = (this.date || "").split("-").map(Number);
|
||||
const startDow = (new Date(y, m, 1).getDay() + 6) % 7; // 0 = понедельник
|
||||
const days = new Date(y, m + 1, 0).getDate();
|
||||
const t = new Date();
|
||||
let html = `<div class="cal-head">` +
|
||||
`<button class="cal-nav" data-cal="prev" title="Предыдущий месяц">‹</button>` +
|
||||
`<span class="cal-title">${MN[m]} ${y}</span>` +
|
||||
`<button class="cal-nav" data-cal="next" title="Следующий месяц">›</button></div>`;
|
||||
html += `<div class="cal-grid">` + WD.map((w) => `<div class="cal-wd">${w}</div>`).join("");
|
||||
for (let i = 0; i < startDow; i++) html += `<div class="cal-day empty"></div>`;
|
||||
for (let dd = 1; dd <= days; dd++) {
|
||||
const sel = (y === sy && m === sm - 1 && dd === sd) ? " sel" : "";
|
||||
const today = (y === t.getFullYear() && m === t.getMonth() && dd === t.getDate()) ? " today" : "";
|
||||
html += `<div class="cal-day${sel}${today}" data-day="${dd}">${dd}</div>`;
|
||||
}
|
||||
box.innerHTML = html + `</div>`;
|
||||
},
|
||||
|
||||
async load() {
|
||||
if (!this.cam || !this.date) return;
|
||||
const [y, m, d] = this.date.split("-").map(Number);
|
||||
|
||||
Reference in New Issue
Block a user