From 3247ebcd64116bf3234afe0ab6f71a3958df54a3 Mon Sep 17 00:00:00 2001 From: Vladislav Kopnin Date: Mon, 21 Sep 2026 21:56:34 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A4=D1=80=D0=BE=D0=BD=D1=82:=20=D0=B1=D0=B5?= =?UTF-8?q?=D0=B7=20=D0=B1=D0=B5=D0=B9=D0=B4=D0=B6=D0=B0=20=C2=AB=D0=9E?= =?UTF-8?q?=D0=B1=D0=B5=20=D0=BD=D0=B5=D0=B4=D0=B5=D0=BB=D0=B8=C2=BB,=20?= =?UTF-8?q?=D0=BD=D0=B5=D0=B4=D0=B5=D0=BB=D0=B8=20=D0=BD=D0=B0=20=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D0=B0=D1=85=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B5=D0=BF=D0=BE=D0=B4=D0=BE=D0=B2/=D0=B0=D1=83=D0=B4=D0=B8?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=B8=D0=B9,=20=D0=B8=D0=BA=D0=BE=D0=BD?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BF=D1=80=D0=B8=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - пары с одинаковыми ч/з показываются одним списком без пометки недели (группы, преподаватели, аудитории); раньше на страницах преподавателей и аудиторий такое занятие дублировалось в обеих колонках - бейдж «Обе недели» убран как информационный шум (отсутствие пометки и означает «любая неделя») - favicon: квадрат с градиентом боковой панели (#1b2dd8 → #167cf2) и совой - PWA: manifest.json, sw.js, apple-touch-icon и иконки 192/512 — при добавлении на главный экран смартфона появляется логотип - tools/make_icons.py — генерация иконок из логотипа (rsvg-convert) - удалён неиспользуемый roomPeriodCell и мёртвый favicon.png --- README.md | 18 ++- src/frontend/static/app.js | 75 ++++++------ src/frontend/static/img/app-icon.svg | 36 ++++++ src/frontend/static/img/apple-touch-icon.png | Bin 0 -> 9822 bytes src/frontend/static/img/favicon-32.png | Bin 0 -> 905 bytes src/frontend/static/img/favicon.png | Bin 315 -> 0 bytes src/frontend/static/img/icon-192.png | Bin 0 -> 10684 bytes src/frontend/static/img/icon-512.png | Bin 0 -> 31511 bytes src/frontend/static/index.html | 11 +- src/frontend/static/manifest.json | 16 +++ src/frontend/static/sw.js | 8 ++ tools/make_icons.py | 121 +++++++++++++++++++ 12 files changed, 244 insertions(+), 41 deletions(-) create mode 100644 src/frontend/static/img/app-icon.svg create mode 100644 src/frontend/static/img/apple-touch-icon.png create mode 100644 src/frontend/static/img/favicon-32.png delete mode 100644 src/frontend/static/img/favicon.png create mode 100644 src/frontend/static/img/icon-192.png create mode 100644 src/frontend/static/img/icon-512.png create mode 100644 src/frontend/static/manifest.json create mode 100644 src/frontend/static/sw.js create mode 100644 tools/make_icons.py diff --git a/README.md b/README.md index 16feb20..6c06888 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,17 @@ nginx (https://schedule.our-place.ru) → 127.0.0.1:8090 Итог по паре: `{time, ч: {raw, lessons[]}, з: {raw, lessons[]}}`, где `lesson = {marker, subject, teacher, room, note}`. +## Как отображается + +- Если занятия числителя и знаменателя совпадают, пара показывается одним + списком без пометки недели (одинаково для групп, преподавателей и + аудиторий); иначе — две колонки «числитель»/«знаменатель», пустая + заменяется на «Окно». +- Подпись недели появляется только там, где недели действительно + различаются; бейдж «Сейчас» подсвечивает текущую неделю. +- Приложение можно «установить» на главный экран (манифест + service + worker + иконки приложения); на iOS иконку берёт `apple-touch-icon`. + ## Структура кода ``` @@ -90,7 +101,12 @@ src/ main.py FastAPI: статика + прокси /api/* (таймаут 150 c) static/ index.html, app.js, style.css - img/ логотипы (owl.svg, owl-white.svg), favicon.png + manifest.json PWA: имя, иконки, standalone-режим + sw.js минимальный service worker (без кэша) для установки приложения + img/ логотипы (owl.svg, owl-white.svg), иконки приложения + (app-icon.svg, icon-192/512.png, apple-touch-icon.png, favicon-32.png) +tools/ + make_icons.py генерация иконок приложения из логотипа (нужен rsvg-convert) tests/ conftest.py общие фикстуры xlsx_builder.py генерация xlsx в памяти (без openpyxl) diff --git a/src/frontend/static/app.js b/src/frontend/static/app.js index 273ea19..215e903 100644 --- a/src/frontend/static/app.js +++ b/src/frontend/static/app.js @@ -227,7 +227,9 @@ function addPeriodRow(block, num, time, lessonsHTML) { } function entityLessonHTML(parity, entry) { - const active = parity === currentParity ? " active" : ""; + const badge = parity + ? `${PARITY_LABEL[parity]}` + : ""; const details = []; if (entry.room) details.push(entry.room); if (entry.teacher) details.push(entry.teacher); @@ -235,7 +237,7 @@ function entityLessonHTML(parity, entry) { return `
- ${PARITY_LABEL[parity]} + ${badge} ${entry.marker ? `${escapeHTML(entry.marker)}` : ""} ${escapeHTML(entry.subject)}
@@ -244,19 +246,36 @@ function entityLessonHTML(parity, entry) {
`; } +/** Занятия числителя и знаменателя совпадают: неделя не важна. */ +function sameEntries(chEntries, znEntries) { + return chEntries.length > 0 && JSON.stringify(chEntries) === JSON.stringify(znEntries); +} + +/** Колонки занятий для страниц преподавателя и аудитории. */ +function lessonsColHTML(chEntries, znEntries) { + if (sameEntries(chEntries, znEntries)) { + // не зависит от недели — одним списком, без бейджа недели + return `
${alwaysColHTML(chEntries.map((e) => entityLessonHTML(null, e)).join(""))}
`; + } + const chHtml = chEntries.map((e) => entityLessonHTML("ч", e)).join(""); + const znHtml = znEntries.map((e) => entityLessonHTML("з", e)).join(""); + return `
${parityColHTML("", chHtml || OKNO_HTML)}${parityColHTML("", znHtml || OKNO_HTML)}
`; +} + function singleLessonHTML(badgeKey, lesson, full) { const config = { - always: { label: "Обе недели", cls: "badge badge-always" }, + always: { label: "", cls: "badge badge-always" }, ч: { label: "Числитель", cls: "badge badge-ch" }, з: { label: "Знаменатель", cls: "badge badge-zn" }, }; const { label, cls } = config[badgeKey]; const active = badgeKey !== "always" && badgeKey === currentParity ? " active" : ""; + const badge = label ? `${label}` : ""; const details = [lesson.teacher, lesson.room].filter(Boolean).join(" · "); return `
- ${label} + ${badge} ${lesson.marker ? `${escapeHTML(lesson.marker)}` : ""} ${escapeHTML(lesson.subject)}
@@ -368,11 +387,11 @@ function renderTeacher(data, status) { Object.entries(periods) .sort(([a], [b]) => Number(a) - Number(b)) .forEach(([num, slot]) => { - const chHtml = (slot.ч || []).map((entry) => { hasAny = true; return entityLessonHTML("ч", entry); }).join(""); - const znHtml = (slot.з || []).map((entry) => { hasAny = true; return entityLessonHTML("з", entry); }).join(""); - if (!chHtml && !znHtml) return; - const inner = `
${parityColHTML("Числитель", chHtml || OKNO_HTML)}${parityColHTML("Знаменатель", znHtml || OKNO_HTML)}
`; - addPeriodRow(block, num, slot.time, inner); + const chEntries = slot.ч || []; + const znEntries = slot.з || []; + if (!chEntries.length && !znEntries.length) return; + hasAny = true; + addPeriodRow(block, num, slot.time, lessonsColHTML(chEntries, znEntries)); }); daysContainer.appendChild(block); }); @@ -457,9 +476,7 @@ function buildRoomsList(data, periodNums) { if (!chEntries.length && !znEntries.length) { body.innerHTML = `
${parityColHTML("", OKNO_HTML)}
`; } else { - const chHtml = chEntries.map((e) => entityLessonHTML("ч", e)).join(""); - const znHtml = znEntries.map((e) => entityLessonHTML("з", e)).join(""); - body.innerHTML = `
${parityColHTML("", chHtml || OKNO_HTML)}${parityColHTML("", znHtml || OKNO_HTML)}
`; + body.innerHTML = lessonsColHTML(chEntries, znEntries); } row.appendChild(body); card.appendChild(row); @@ -469,33 +486,6 @@ function buildRoomsList(data, periodNums) { return list; } -function roomPeriodCell(slot) { - const td = document.createElement("td"); - td.className = "r-cell"; - const all = [...((slot && slot.ч) || []), ...((slot && slot.з) || [])]; - if (!all.length) { - td.classList.add("r-free"); - td.textContent = "окно"; - return td; - } - td.classList.add("r-busy"); - const seen = new Set(); - const lines = []; - all.forEach((e) => { - if (!e.subject || seen.has(e.subject)) return; - seen.add(e.subject); - lines.push(e.subject); - }); - lines.forEach((x) => { - const div = document.createElement("div"); - div.className = "r-subj"; - div.textContent = x; - td.appendChild(div); - }); - td.title = lines.join("; "); - return td; -} - function escapeHTML(s) { return s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c])); } @@ -547,3 +537,10 @@ loadWeek().finally(() => { errorEl.classList.remove("hidden"); }); }); + +// PWA: ярлык «на главный экран» с иконкой приложения (манифест + service worker) +if ("serviceWorker" in navigator) { + window.addEventListener("load", () => { + navigator.serviceWorker.register("/sw.js").catch(() => {}); + }); +} diff --git a/src/frontend/static/img/app-icon.svg b/src/frontend/static/img/app-icon.svg new file mode 100644 index 0000000..1550027 --- /dev/null +++ b/src/frontend/static/img/app-icon.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/frontend/static/img/apple-touch-icon.png b/src/frontend/static/img/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..49bd0743dccbfa073322f2b412056b13284cd69b GIT binary patch literal 9822 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 WJDxKXBR_#LfVi-XP_=+wz<&W*?n_4i literal 0 HcmV?d00001 diff --git a/src/frontend/static/img/favicon-32.png b/src/frontend/static/img/favicon-32.png new file mode 100644 index 0000000000000000000000000000000000000000..76ab8f8b53bf218cdca6d2a7edc7c1296fd75ad2 GIT binary patch literal 905 zcmV;419tq0P)6j>C#Z>j3)u1=>DV|S8{OU4)-m+>QrxPm$=Mo~~u z2&h4E84&z%2E^dch|7onBB;0!7i30cL=qK(;)ZA>4l_pW#Mqjp)18*ys;aBrt2ZC> zC0&t3Xdzzi-@ETz&OP_u_fY7kPAH^NAgI6zaDfwuG0y-4KiW?u= zQ(EjJR6;%x2et`p0sbDswndlMMeO?ZAc_}d#8D#IhkK@ z3>Ze`ih?Qvxx`wk!eLjtxx+Qm+SJZPQ-7wc5jGb%-8sPW=;M`DhMpx_Pko8EMu5@Y4MjH}>swcOkfaT%quZ4ia9uZ zDZOri`}`fPVVUZ2;mh~*S=CZoM~+f-^h&xWM1OBiO$d;ECzDgFsAd8a@P)Z?(BSO= zSKYXFqV(dO^}3f0rbSPT|4>P1R?9b^*NQ+8D&I1Y z&#SIR`WWXaO(c=*b6ED~uK&vjFL;nrs&19m<*xY4^{y3`Zpgn%L;grG=4 zoxNkoyGXM{A|nPff?%jA$2m*HGr}dKq%Lv{ ztQgmGg4w`r_>>hu&~P{?xH~oyrlnrIdXA)-aQmg1&5g7^0DywO#77?e6*l%?wC~_o f@jd+P3Y*|RY;H@LTMSJO00000NkvXXu0mjfmp-ju literal 0 HcmV?d00001 diff --git a/src/frontend/static/img/favicon.png b/src/frontend/static/img/favicon.png deleted file mode 100644 index bf71d6c93018a9569a4a403abfcda3cdd2b39d1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 315 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 literal 0 HcmV?d00001 diff --git a/src/frontend/static/img/icon-512.png b/src/frontend/static/img/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..8dffe1919206d195deb81ee4c0651e27cbd477d5 GIT binary patch literal 31511 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 Расписание · Факультет ИВТ ЯрГУ - + + + + + + + + + + diff --git a/src/frontend/static/manifest.json b/src/frontend/static/manifest.json new file mode 100644 index 0000000..088987f --- /dev/null +++ b/src/frontend/static/manifest.json @@ -0,0 +1,16 @@ +{ + "name": "Расписание · Факультет ИВТ ЯрГУ", + "short_name": "Расписание", + "description": "Расписание занятий факультета информатики и вычислительной техники ЯрГУ", + "lang": "ru", + "start_url": "/", + "scope": "/", + "display": "standalone", + "background_color": "#f1f5f9", + "theme_color": "#1b2dd8", + "icons": [ + { "src": "/img/icon-192.png", "sizes": "192x192", "type": "image/png" }, + { "src": "/img/icon-512.png", "sizes": "512x512", "type": "image/png" }, + { "src": "/img/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } + ] +} diff --git a/src/frontend/static/sw.js b/src/frontend/static/sw.js new file mode 100644 index 0000000..9d9b67c --- /dev/null +++ b/src/frontend/static/sw.js @@ -0,0 +1,8 @@ +// Минимальный service worker: браузеры требуют его наличия, чтобы предложить +// «установить приложение» и показать иконку на главном экране. +// Ничего не кэширует — источник данных всегда сеть. +self.addEventListener("install", () => self.skipWaiting()); +self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim())); +self.addEventListener("fetch", () => { + // пустой обработчик: запросы уходят в сеть как обычно +}); diff --git a/tools/make_icons.py b/tools/make_icons.py new file mode 100644 index 0000000..bfc82a0 --- /dev/null +++ b/tools/make_icons.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +"""Генерация иконок приложения (фавиконка + PWA) из логотипа факультета. + +Квадрат с градиентом боковой панели сайта (#1b2dd8 → #167cf2), внутрь +помещается белая сова. Результат: + img/app-icon.svg — самодостаточный svg (favicon) + img/icon-192.png — иконка манифеста + img/icon-512.png — иконка манифеста (maskable) + img/apple-touch-icon.png — 180x180 для iOS + img/favicon-32.png — растровая запасная фавиконка + +Запуск (нужен rsvg-convert из librsvg2-bin): + python3 tools/make_icons.py +""" + +from __future__ import annotations + +import subprocess +import sys +import xml.etree.ElementTree as ET +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +IMG = ROOT / "src" / "frontend" / "static" / "img" + +SVG_NS = "http://www.w3.org/2000/svg" +SODIPODI_NS = "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" +INKSCAPE_NS = "http://www.inkscape.org/namespaces/inkscape" + +SIZE = 512 +GRADIENT = (("#1b2dd8", "0%"), ("#167cf2", "100%")) # как .side в светлой теме +OWL_SCALE = 0.62 # доля стороны иконки под логотип + + +def strip_ns(el: ET.Element) -> None: + """Убирает inkscape/sodipodi-атрибуты (в чужом документе они не нужны).""" + for key in list(el.attrib): + if key.startswith(f"{{{SODIPODI_NS}}}") or key.startswith(f"{{{INKSCAPE_NS}}}"): + del el.attrib[key] + for child in el: + strip_ns(child) + + +def build_icon_svg() -> str: + owl_root = ET.parse(IMG / "owl-white.svg").getroot() + view_box = owl_root.get("viewBox", "0 0 109.34 109.87") + _, _, ow, oh = (float(v) for v in view_box.replace(",", " ").split()) + + # размеры и положение логотипа внутри квадрата + target = SIZE * OWL_SCALE + scale = target / max(ow, oh) + tx = (SIZE - ow * scale) / 2 + ty = (SIZE - oh * scale) / 2 + + ET.register_namespace("", SVG_NS) + icon = ET.Element( + f"{{{SVG_NS}}}svg", + { + "viewBox": f"0 0 {SIZE} {SIZE}", + "width": str(SIZE), + "height": str(SIZE), + }, + ) + defs = ET.SubElement(icon, f"{{{SVG_NS}}}defs") + grad = ET.SubElement( + defs, + f"{{{SVG_NS}}}linearGradient", + {"id": "bg", "x1": "0", "y1": "0", "x2": "0", "y2": "1"}, + ) + for color, offset in GRADIENT: + ET.SubElement( + grad, f"{{{SVG_NS}}}stop", {"offset": offset, "stop-color": color} + ) + ET.SubElement( + icon, f"{{{SVG_NS}}}rect", {"width": str(SIZE), "height": str(SIZE), "fill": "url(#bg)"} + ) + + group = ET.SubElement( + icon, + f"{{{SVG_NS}}}g", + {"transform": f"translate({tx:.2f} {ty:.2f}) scale({scale:.6f})"}, + ) + for child in owl_root: + tag = child.tag + if tag.endswith("namedview") or tag.endswith("metadata"): + continue + clone = ET.fromstring(ET.tostring(child)) + strip_ns(clone) + group.append(clone) + + xml = ET.tostring(icon, encoding="unicode") + return '\n' + xml + "\n" + + +def rasterize(svg_path: Path, size: int, out_path: Path) -> None: + subprocess.run( + ["rsvg-convert", "-w", str(size), "-h", str(size), "-o", str(out_path), str(svg_path)], + check=True, + ) + + +def main() -> int: + svg = build_icon_svg() + icon = IMG / "app-icon.svg" + icon.write_text(svg, encoding="utf-8") + + if not subprocess.run(["which", "rsvg-convert"], capture_output=True).stdout: + print("нет rsvg-convert — svg создан, png не сгенерированы", file=sys.stderr) + return 1 + + rasterize(icon, 192, IMG / "icon-192.png") + rasterize(icon, 512, IMG / "icon-512.png") + rasterize(icon, 180, IMG / "apple-touch-icon.png") + rasterize(icon, 32, IMG / "favicon-32.png") + for name in ("app-icon.svg", "icon-192.png", "icon-512.png", "apple-touch-icon.png", "favicon-32.png"): + print("готово:", (IMG / name).relative_to(ROOT)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())