summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-05-03 10:20:48 +0200
committerDanilo M. <danix@danix.xyz>2026-05-03 10:20:48 +0200
commit3ade340c587da09c777f4b3fbb383f550c457c9b (patch)
treefa7a94bb1210727488c3a1b5e4f10845afb7cf72
parent22ae089a6aedb3fb002b4e6fabd45841a2fc6c4f (diff)
downloadpublisher-3ade340c587da09c777f4b3fbb383f550c457c9b.tar.gz
publisher-3ade340c587da09c777f4b3fbb383f550c457c9b.zip
feat: main window with sidebar navigation and file watcher
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rw-r--r--main.py13
-rw-r--r--ui/main_window.py144
-rw-r--r--ui/setup_dialog.py55
3 files changed, 212 insertions, 0 deletions
diff --git a/main.py b/main.py
index 98a3fd4..4c270dd 100644
--- 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
index 0000000..9f0594b
--- /dev/null
+++ b/ui/main_window.py
@@ -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
index 0000000..4a8c6b7
--- /dev/null
+++ b/ui/setup_dialog.py
@@ -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()