]> danix's work - publisher.git/commitdiff
feat: main window with sidebar navigation and file watcher
authorDanilo M. <redacted>
Sun, 3 May 2026 08:20:48 +0000 (10:20 +0200)
committerDanilo M. <redacted>
Sun, 3 May 2026 08:20:48 +0000 (10:20 +0200)
Co-Authored-By: Claude Sonnet 4.6 <redacted>
main.py
ui/main_window.py [new file with mode: 0644]
ui/setup_dialog.py [new file with mode: 0644]

diff --git a/main.py b/main.py
index 98a3fd4a5bd739abff4bd9f5b2e8c7602dc63a61..4c270dd001a60d5aa9dac65dbc30cc3bdfed2635 100644 (file)
--- a/main.py
+++ b/main.py
@@ -1,10 +1,23 @@
 import sys
 from PyQt6.QtWidgets import QApplication
+from core.config import Config
+from ui.main_window import MainWindow
+from ui.setup_dialog import SetupDialog
 
 def main():
     app = QApplication(sys.argv)
     app.setApplicationName("my-publisher")
     app.setOrganizationName("danix")
+
+    config = Config.load()
+    if not config.is_complete():
+        dlg = SetupDialog(config)
+        if dlg.exec() == 0:
+            sys.exit(0)
+        config = Config.load()
+
+    window = MainWindow(config)
+    window.show()
     sys.exit(app.exec())
 
 if __name__ == "__main__":
diff --git a/ui/main_window.py b/ui/main_window.py
new file mode 100644 (file)
index 0000000..9f0594b
--- /dev/null
@@ -0,0 +1,144 @@
+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()
diff --git a/ui/setup_dialog.py b/ui/setup_dialog.py
new file mode 100644 (file)
index 0000000..4a8c6b7
--- /dev/null
@@ -0,0 +1,55 @@
+from __future__ import annotations
+from PyQt6.QtWidgets import (
+    QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
+    QPushButton, QFileDialog, QFormLayout,
+)
+from core.config import Config
+
+class SetupDialog(QDialog):
+    def __init__(self, config: Config, parent=None):
+        super().__init__(parent)
+        self.setWindowTitle("my-publisher โ€” Setup")
+        self.setMinimumWidth(500)
+        self._config = config
+        self._build_ui()
+
+    def _build_ui(self):
+        layout = QVBoxLayout(self)
+        layout.addWidget(QLabel("Configura my-publisher prima di iniziare."))
+
+        form = QFormLayout()
+
+        self._blog_repo = QLineEdit(self._config.blog_repo)
+        browse_btn = QPushButton("Sfoglia...")
+        browse_btn.clicked.connect(self._browse_repo)
+        row = QHBoxLayout()
+        row.addWidget(self._blog_repo)
+        row.addWidget(browse_btn)
+        form.addRow("Blog repo:", row)
+
+        self._transart = QLineEdit(self._config.transart_script)
+        form.addRow("transart.py:", self._transart)
+
+        self._typora = QLineEdit(self._config.typora_bin)
+        form.addRow("Typora bin:", self._typora)
+
+        layout.addLayout(form)
+
+        btns = QHBoxLayout()
+        save_btn = QPushButton("Salva")
+        save_btn.clicked.connect(self._save)
+        btns.addStretch()
+        btns.addWidget(save_btn)
+        layout.addLayout(btns)
+
+    def _browse_repo(self):
+        path = QFileDialog.getExistingDirectory(self, "Seleziona blog repo")
+        if path:
+            self._blog_repo.setText(path)
+
+    def _save(self):
+        self._config.blog_repo = self._blog_repo.text().strip()
+        self._config.transart_script = self._transart.text().strip()
+        self._config.typora_bin = self._typora.text().strip()
+        self._config.save()
+        self.accept()