from __future__ import annotations from PyQt6.QtWidgets import ( QMainWindow, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QPushButton, QStackedWidget, QFrame, QSizePolicy, ) from PyQt6.QtCore import Qt, QFileSystemWatcher from PyQt6.QtGui import QFont from pathlib import Path from core.config import Config from core.article_scanner import scan_articles from core.models import Article class SidebarButton(QPushButton): def __init__(self, text: str, parent=None): super().__init__(text, parent) self.setFlat(True) self.setCheckable(True) self.setStyleSheet(""" QPushButton { text-align: left; padding: 5px 10px; border-radius: 4px; color: #888; } QPushButton:checked { background: #2a2a4e; color: #a855f7; } QPushButton:hover:!checked { background: #1a1a2e; color: #ccc; } """) class MainWindow(QMainWindow): def __init__(self, config: Config, parent=None): super().__init__(parent) self.config = config self._articles: list[Article] = [] self._watcher = QFileSystemWatcher(self) self._watcher.directoryChanged.connect(self._on_fs_change) self.setWindowTitle("my-publisher") self.setMinimumSize(1100, 700) self._build_ui() self._refresh_articles() self._setup_watcher() def _build_ui(self): central = QWidget() self.setCentralWidget(central) root = QHBoxLayout(central) root.setContentsMargins(0, 0, 0, 0) root.setSpacing(0) # Sidebar sidebar = self._build_sidebar() root.addWidget(sidebar) # Divider line = QFrame() line.setFrameShape(QFrame.Shape.VLine) line.setStyleSheet("color: #2a2a4e;") root.addWidget(line) # Content stack self._stack = QStackedWidget() root.addWidget(self._stack, stretch=1) # Placeholder pages (replaced in later tasks) for name in ["articles", "no_translation", "new_article", "taxonomy", "media", "translations", "git", "hugo"]: w = QLabel(f"[{name}]") w.setAlignment(Qt.AlignmentFlag.AlignCenter) self._stack.addWidget(w) setattr(self, f"_page_{name}", self._stack.count() - 1) def _build_sidebar(self) -> QWidget: w = QWidget() w.setFixedWidth(210) w.setStyleSheet("background: #1a1a2e;") layout = QVBoxLayout(w) layout.setContentsMargins(8, 12, 8, 12) layout.setSpacing(2) header = QHBoxLayout() title = QLabel("๐Ÿ“ฐ my-publisher") title.setStyleSheet("color: #fff; font-weight: bold; font-size: 13px; padding: 4px 8px;") header.addWidget(title) self._refresh_btn = QPushButton("๐Ÿ”„") self._refresh_btn.setFlat(True) self._refresh_btn.setFixedSize(28, 28) self._refresh_btn.setStyleSheet("color: #555; border: none;") self._refresh_btn.clicked.connect(self._refresh_articles) header.addWidget(self._refresh_btn) layout.addLayout(header) def section(text): lbl = QLabel(text) lbl.setStyleSheet("color: #a855f7; font-size: 9px; text-transform: uppercase; letter-spacing: 1px; padding: 8px 8px 2px;") layout.addWidget(lbl) self._btn_group: list[SidebarButton] = [] def btn(icon_text: str, page_attr: str) -> SidebarButton: b = SidebarButton(icon_text) b.clicked.connect(lambda: self._show_page(page_attr, b)) layout.addWidget(b) self._btn_group.append(b) return b section("CONTENUTO") btn("๐Ÿ“‹ Articoli", "_page_articles") self._btn_no_trans = btn("โš ๏ธ Senza Traduzione", "_page_no_translation") self._badge_no_trans = QLabel("0") self._badge_no_trans.setStyleSheet("background:#3a1a1a;color:#ff6b6b;border-radius:8px;padding:1px 6px;font-size:10px;") btn("โž• Nuovo articolo", "_page_new_article") btn("๐Ÿท Tassonomia", "_page_taxonomy") btn("๐Ÿ–ผ Media", "_page_media") section("WORKFLOW") btn("๐ŸŒ Traduzioni", "_page_translations") btn("๐Ÿ”ง Git ops", "_page_git") btn("๐Ÿš€ Hugo server", "_page_hugo") section("DEPLOY") btn("๐Ÿงช Test (master)", "_page_git") btn("๐Ÿšข Production", "_page_git") layout.addStretch() return w def _show_page(self, page_attr: str, active_btn: SidebarButton): for b in self._btn_group: b.setChecked(False) active_btn.setChecked(True) self._stack.setCurrentIndex(getattr(self, page_attr)) def _refresh_articles(self): if not self.config.blog_repo: return self._articles = scan_articles(Path(self.config.blog_repo)) missing = [a for a in self._articles if not a.has_translation] self._badge_no_trans.setText(str(len(missing))) def _setup_watcher(self): if not self.config.blog_repo: return root = Path(self.config.blog_repo) for lang in ("it", "en"): d = root / "content" / lang / "articles" if d.exists(): self._watcher.addPath(str(d)) def _on_fs_change(self, _path: str): self._refresh_articles()