feat: Add branding import/export functionality and enhance settings dialog with new fields
Some checks are pending
Tests & Quality Checks / Test on Python 3.11 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.12 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.11-1 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.12-1 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.10 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.11-2 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.12-2 (push) Waiting to run
Tests & Quality Checks / Build Artifacts (push) Blocked by required conditions
Tests & Quality Checks / Build Artifacts-1 (push) Blocked by required conditions
Some checks are pending
Tests & Quality Checks / Test on Python 3.11 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.12 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.11-1 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.12-1 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.10 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.11-2 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.12-2 (push) Waiting to run
Tests & Quality Checks / Build Artifacts (push) Blocked by required conditions
Tests & Quality Checks / Build Artifacts-1 (push) Blocked by required conditions
This commit is contained in:
parent
b826bd9b20
commit
55f2ddf4b1
10 changed files with 296 additions and 10 deletions
|
|
@ -99,7 +99,11 @@
|
|||
"settings.branding.window_title_label": "Fenstertitel (optional):",
|
||||
"settings.branding.logo_path_label": "Logo/Icon-Datei (optional):",
|
||||
"settings.branding.save_as_btn": "Branding speichern",
|
||||
"settings.branding.export_btn": "Branding exportieren",
|
||||
"settings.branding.import_btn": "Branding importieren",
|
||||
"settings.branding.delete_btn": "Branding löschen",
|
||||
"settings.branding.export_title": "Branding exportieren",
|
||||
"settings.branding.import_title": "Branding importieren",
|
||||
"settings.branding.preview_label": "Vorschau:",
|
||||
"settings.branding.no_icon_selected": "Kein Icon ausgewählt",
|
||||
"settings.branding.preview_default_name": "Default",
|
||||
|
|
|
|||
|
|
@ -99,7 +99,11 @@
|
|||
"settings.branding.window_title_label": "Window title (optional):",
|
||||
"settings.branding.logo_path_label": "Logo/Icon file (optional):",
|
||||
"settings.branding.save_as_btn": "Save Branding",
|
||||
"settings.branding.export_btn": "Export Branding",
|
||||
"settings.branding.import_btn": "Import Branding",
|
||||
"settings.branding.delete_btn": "Delete Branding",
|
||||
"settings.branding.export_title": "Export Branding",
|
||||
"settings.branding.import_title": "Import Branding",
|
||||
"settings.branding.preview_label": "Preview:",
|
||||
"settings.branding.no_icon_selected": "No icon selected",
|
||||
"settings.branding.preview_default_name": "Default",
|
||||
|
|
|
|||
|
|
@ -99,7 +99,11 @@
|
|||
"settings.branding.window_title_label": "Titre de la fenêtre (facultatif) :",
|
||||
"settings.branding.logo_path_label": "Chemin du logo (facultatif) :",
|
||||
"settings.branding.save_as_btn": "Enregistrer le branding",
|
||||
"settings.branding.export_btn": "Exporter le branding",
|
||||
"settings.branding.import_btn": "Importer le branding",
|
||||
"settings.branding.delete_btn": "Supprimer le branding",
|
||||
"settings.branding.export_title": "Exporter le branding",
|
||||
"settings.branding.import_title": "Importer le branding",
|
||||
"settings.branding.preview_label": "Aperçu :",
|
||||
"settings.branding.no_icon_selected": "Aucune icône sélectionnée",
|
||||
"settings.branding.preview_default_name": "Default",
|
||||
|
|
|
|||
|
|
@ -99,7 +99,11 @@
|
|||
"settings.branding.window_title_label": "Titolo finestra (opzionale):",
|
||||
"settings.branding.logo_path_label": "Percorso logo (opzionale):",
|
||||
"settings.branding.save_as_btn": "Salva branding",
|
||||
"settings.branding.export_btn": "Esporta branding",
|
||||
"settings.branding.import_btn": "Importa branding",
|
||||
"settings.branding.delete_btn": "Elimina branding",
|
||||
"settings.branding.export_title": "Esporta branding",
|
||||
"settings.branding.import_title": "Importa branding",
|
||||
"settings.branding.preview_label": "Anteprima:",
|
||||
"settings.branding.no_icon_selected": "Nessuna icona selezionata",
|
||||
"settings.branding.preview_default_name": "Default",
|
||||
|
|
|
|||
|
|
@ -99,7 +99,11 @@
|
|||
"settings.branding.window_title_label": "Заголовок окна (необязательно):",
|
||||
"settings.branding.logo_path_label": "Путь к логотипу (необязательно):",
|
||||
"settings.branding.save_as_btn": "Сохранить брендинг",
|
||||
"settings.branding.export_btn": "Экспортировать брендинг",
|
||||
"settings.branding.import_btn": "Импортировать брендинг",
|
||||
"settings.branding.delete_btn": "Удалить брендинг",
|
||||
"settings.branding.export_title": "Экспортировать брендинг",
|
||||
"settings.branding.import_title": "Импортировать брендинг",
|
||||
"settings.branding.preview_label": "Предпросмотр:",
|
||||
"settings.branding.no_icon_selected": "Значок не выбран",
|
||||
"settings.branding.preview_default_name": "Default",
|
||||
|
|
|
|||
|
|
@ -99,7 +99,11 @@
|
|||
"settings.branding.window_title_label": "窗口标题(可选):",
|
||||
"settings.branding.logo_path_label": "Logo 路径(可选):",
|
||||
"settings.branding.save_as_btn": "保存品牌配置",
|
||||
"settings.branding.export_btn": "导出品牌配置",
|
||||
"settings.branding.import_btn": "导入品牌配置",
|
||||
"settings.branding.delete_btn": "删除品牌配置",
|
||||
"settings.branding.export_title": "导出品牌配置",
|
||||
"settings.branding.import_title": "导入品牌配置",
|
||||
"settings.branding.preview_label": "预览:",
|
||||
"settings.branding.no_icon_selected": "未选择图标",
|
||||
"settings.branding.preview_default_name": "Default",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import json
|
|||
import logging
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
from dataclasses import asdict, dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
|
@ -15,6 +16,7 @@ from webdrop_bridge.config import DEFAULT_CONFIG_DIR_NAME, Config, Configuration
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_BRANDING_TEMPLATE_ID = "default"
|
||||
DEFAULT_LOGO_PATH = "resources/icons/app.png"
|
||||
SUPPORTED_LOGO_SUFFIXES = {".png", ".jpg", ".jpeg", ".bmp", ".svg", ".ico", ".icns"}
|
||||
|
||||
|
||||
|
|
@ -83,6 +85,7 @@ BUILTIN_BRANDING_TEMPLATES: dict[str, BrandingTemplate] = {
|
|||
display_name="Default",
|
||||
app_name="WebDrop Bridge",
|
||||
window_title="",
|
||||
logo_path=DEFAULT_LOGO_PATH,
|
||||
accent_color="#667eea",
|
||||
),
|
||||
"agravity": BrandingTemplate(
|
||||
|
|
@ -90,6 +93,7 @@ BUILTIN_BRANDING_TEMPLATES: dict[str, BrandingTemplate] = {
|
|||
display_name="Agravity",
|
||||
app_name="Agravity Bridge",
|
||||
window_title="",
|
||||
logo_path=DEFAULT_LOGO_PATH,
|
||||
accent_color="#2d7d6e",
|
||||
),
|
||||
}
|
||||
|
|
@ -103,6 +107,7 @@ class BrandingManager:
|
|||
resolved_base = Path(env_dir).resolve() if env_dir and base_dir is None else base_dir
|
||||
self.base_dir = resolved_base or self._default_base_dir()
|
||||
self.templates_dir = self.base_dir / "templates"
|
||||
self.assets_dir = self.base_dir / "assets"
|
||||
self.active_branding_path = self.base_dir / "active_branding.json"
|
||||
self.ensure_builtin_templates()
|
||||
|
||||
|
|
@ -114,6 +119,7 @@ class BrandingManager:
|
|||
def ensure_builtin_templates(self) -> None:
|
||||
"""Ensure built-in templates exist on disk for discovery and later editing."""
|
||||
self.templates_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.assets_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for template in BUILTIN_BRANDING_TEMPLATES.values():
|
||||
template_path = self.templates_dir / f"{template.template_id}.json"
|
||||
|
|
@ -165,10 +171,29 @@ class BrandingManager:
|
|||
if template.template_id in BUILTIN_BRANDING_TEMPLATES:
|
||||
raise ConfigurationError(f"Cannot overwrite built-in branding: {template.template_id}")
|
||||
|
||||
stored_logo_path = ""
|
||||
if template.logo_path:
|
||||
stored_logo_path = self._copy_logo_asset(template.logo_path, template.template_id)
|
||||
|
||||
stored_template = BrandingTemplate(
|
||||
template_id=template.template_id,
|
||||
display_name=template.display_name,
|
||||
app_name=template.app_name,
|
||||
window_title=template.window_title,
|
||||
logo_path=stored_logo_path or template.logo_path,
|
||||
app_icon_path_windows=stored_logo_path or template.app_icon_path_windows,
|
||||
app_icon_path_macos=stored_logo_path or template.app_icon_path_macos,
|
||||
toolbar_icon_home=template.toolbar_icon_home,
|
||||
toolbar_icon_reload=template.toolbar_icon_reload,
|
||||
toolbar_icon_open=template.toolbar_icon_open,
|
||||
toolbar_icon_openwith=template.toolbar_icon_openwith,
|
||||
accent_color=template.accent_color,
|
||||
)
|
||||
|
||||
self.templates_dir.mkdir(parents=True, exist_ok=True)
|
||||
template_path = self.templates_dir / f"{template.template_id}.json"
|
||||
template_path.write_text(json.dumps(template.to_dict(), indent=2), encoding="utf-8")
|
||||
logger.info("Branding template saved: %s", template.template_id)
|
||||
template_path = self.templates_dir / f"{stored_template.template_id}.json"
|
||||
template_path.write_text(json.dumps(stored_template.to_dict(), indent=2), encoding="utf-8")
|
||||
logger.info("Branding template saved: %s", stored_template.template_id)
|
||||
return template_path
|
||||
|
||||
def build_template(
|
||||
|
|
@ -191,8 +216,8 @@ class BrandingManager:
|
|||
if not safe_name:
|
||||
raise ConfigurationError("Branding requires a display name")
|
||||
if logo:
|
||||
logo_file = Path(logo)
|
||||
if not logo_file.exists() or not logo_file.is_file():
|
||||
logo_file = self._resolve_asset_path(logo)
|
||||
if logo_file is None or not logo_file.exists() or not logo_file.is_file():
|
||||
raise ConfigurationError(f"Logo file not found: {logo}")
|
||||
if logo_file.suffix.lower() not in SUPPORTED_LOGO_SUFFIXES:
|
||||
raise ConfigurationError(
|
||||
|
|
@ -214,6 +239,97 @@ class BrandingManager:
|
|||
"""Convert a human-readable branding name into a stable id."""
|
||||
return "".join(c.lower() if c.isalnum() else "_" for c in value).strip("_")
|
||||
|
||||
@staticmethod
|
||||
def _resolve_asset_path(configured_path: str) -> Path | None:
|
||||
"""Resolve a branding asset path in dev and packaged layouts."""
|
||||
if not configured_path:
|
||||
return None
|
||||
|
||||
path = Path(configured_path)
|
||||
candidates = [path] if path.is_absolute() else [Path.cwd() / path]
|
||||
if not path.is_absolute():
|
||||
project_root = Path(__file__).resolve().parents[3]
|
||||
candidates.append(project_root / path)
|
||||
|
||||
for candidate in candidates:
|
||||
if candidate.exists():
|
||||
return candidate
|
||||
|
||||
return None
|
||||
|
||||
def _copy_logo_asset(self, configured_path: str, template_id: str) -> str:
|
||||
"""Copy a user-selected logo into managed branding storage."""
|
||||
resolved_path = self._resolve_asset_path(configured_path)
|
||||
if resolved_path is None or not resolved_path.exists() or not resolved_path.is_file():
|
||||
raise ConfigurationError(f"Logo file not found: {configured_path}")
|
||||
|
||||
self.assets_dir.mkdir(parents=True, exist_ok=True)
|
||||
target_path = self.assets_dir / f"{template_id}{resolved_path.suffix.lower()}"
|
||||
if resolved_path.resolve() != target_path.resolve():
|
||||
shutil.copy2(resolved_path, target_path)
|
||||
|
||||
return str(target_path)
|
||||
|
||||
def export_template(self, template_id: str, export_path: Path) -> Path:
|
||||
"""Export a branding into a shareable JSON file plus optional logo asset."""
|
||||
template = self.load_template(template_id)
|
||||
export_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
export_data = template.to_dict()
|
||||
if template.logo_path:
|
||||
resolved_logo = self._resolve_asset_path(template.logo_path)
|
||||
if resolved_logo and resolved_logo.exists() and resolved_logo.is_file():
|
||||
export_logo_path = export_path.parent / resolved_logo.name
|
||||
if resolved_logo.resolve() != export_logo_path.resolve():
|
||||
shutil.copy2(resolved_logo, export_logo_path)
|
||||
export_data["logo_path"] = export_logo_path.name
|
||||
export_data["app_icon_path_windows"] = export_logo_path.name
|
||||
export_data["app_icon_path_macos"] = export_logo_path.name
|
||||
|
||||
export_path.write_text(json.dumps(export_data, indent=2), encoding="utf-8")
|
||||
logger.info("Branding template exported: %s -> %s", template_id, export_path)
|
||||
return export_path
|
||||
|
||||
def import_template(self, import_path: Path) -> BrandingTemplate:
|
||||
"""Import a branding from a previously exported JSON file."""
|
||||
if not import_path.exists() or not import_path.is_file():
|
||||
raise ConfigurationError(f"Branding import file not found: {import_path}")
|
||||
|
||||
try:
|
||||
data = json.loads(import_path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise ConfigurationError(f"Invalid branding import file: {import_path}") from exc
|
||||
|
||||
template = BrandingTemplate.from_dict(data)
|
||||
imported_template_id = template.template_id
|
||||
if imported_template_id in BUILTIN_BRANDING_TEMPLATES:
|
||||
imported_template_id = self._slugify(f"{template.display_name}_imported")
|
||||
|
||||
imported_logo_path = ""
|
||||
if template.logo_path:
|
||||
candidate_logo = Path(template.logo_path)
|
||||
if not candidate_logo.is_absolute():
|
||||
candidate_logo = import_path.parent / candidate_logo
|
||||
imported_logo_path = self._copy_logo_asset(str(candidate_logo), imported_template_id)
|
||||
|
||||
imported_template = BrandingTemplate(
|
||||
template_id=imported_template_id,
|
||||
display_name=template.display_name,
|
||||
app_name=template.app_name,
|
||||
window_title=template.window_title,
|
||||
logo_path=imported_logo_path,
|
||||
app_icon_path_windows=imported_logo_path or template.app_icon_path_windows,
|
||||
app_icon_path_macos=imported_logo_path or template.app_icon_path_macos,
|
||||
toolbar_icon_home=template.toolbar_icon_home,
|
||||
toolbar_icon_reload=template.toolbar_icon_reload,
|
||||
toolbar_icon_open=template.toolbar_icon_open,
|
||||
toolbar_icon_openwith=template.toolbar_icon_openwith,
|
||||
accent_color=template.accent_color,
|
||||
)
|
||||
self.save_template(imported_template)
|
||||
logger.info("Branding template imported: %s", imported_template.template_id)
|
||||
return imported_template
|
||||
|
||||
def delete_template(self, template_id: str) -> None:
|
||||
"""Delete a user template while protecting built-ins."""
|
||||
if template_id in BUILTIN_BRANDING_TEMPLATES:
|
||||
|
|
|
|||
|
|
@ -198,6 +198,20 @@ class SettingsDialog(QDialog):
|
|||
layout.addWidget(QLabel(tr("settings.branding.display_name_label")))
|
||||
layout.addWidget(self.branding_display_name_input)
|
||||
|
||||
self.branding_app_name_input = QLineEdit()
|
||||
self.branding_app_name_input.setPlaceholderText(tr("settings.branding.app_name_label"))
|
||||
self.branding_app_name_input.textChanged.connect(self._update_branding_preview)
|
||||
layout.addWidget(QLabel(tr("settings.branding.app_name_label")))
|
||||
layout.addWidget(self.branding_app_name_input)
|
||||
|
||||
self.branding_window_title_input = QLineEdit()
|
||||
self.branding_window_title_input.setPlaceholderText(
|
||||
tr("settings.branding.window_title_label")
|
||||
)
|
||||
self.branding_window_title_input.textChanged.connect(self._update_branding_preview)
|
||||
layout.addWidget(QLabel(tr("settings.branding.window_title_label")))
|
||||
layout.addWidget(self.branding_window_title_input)
|
||||
|
||||
layout.addWidget(QLabel(tr("settings.branding.logo_path_label")))
|
||||
logo_layout = QHBoxLayout()
|
||||
self.branding_logo_path_input = QLineEdit()
|
||||
|
|
@ -215,6 +229,10 @@ class SettingsDialog(QDialog):
|
|||
self.branding_preview_name_label.setStyleSheet("font-weight: bold;")
|
||||
layout.addWidget(self.branding_preview_name_label)
|
||||
|
||||
self.branding_preview_title_label = QLabel()
|
||||
self.branding_preview_title_label.setStyleSheet("color: gray;")
|
||||
layout.addWidget(self.branding_preview_title_label)
|
||||
|
||||
self.branding_preview_icon_label = QLabel(tr("settings.branding.no_icon_selected"))
|
||||
self.branding_preview_icon_label.setFixedSize(72, 72)
|
||||
self.branding_preview_icon_label.setStyleSheet(
|
||||
|
|
@ -227,6 +245,14 @@ class SettingsDialog(QDialog):
|
|||
self.save_branding_as_btn.clicked.connect(self._save_branding_as)
|
||||
branding_button_layout.addWidget(self.save_branding_as_btn)
|
||||
|
||||
self.export_branding_btn = QPushButton(tr("settings.branding.export_btn"))
|
||||
self.export_branding_btn.clicked.connect(self._export_branding)
|
||||
branding_button_layout.addWidget(self.export_branding_btn)
|
||||
|
||||
self.import_branding_btn = QPushButton(tr("settings.branding.import_btn"))
|
||||
self.import_branding_btn.clicked.connect(self._import_branding)
|
||||
branding_button_layout.addWidget(self.import_branding_btn)
|
||||
|
||||
self.delete_branding_btn = QPushButton(tr("settings.branding.delete_btn"))
|
||||
self.delete_branding_btn.clicked.connect(self._delete_branding)
|
||||
branding_button_layout.addWidget(self.delete_branding_btn)
|
||||
|
|
@ -262,9 +288,30 @@ class SettingsDialog(QDialog):
|
|||
"""Load the selected branding into the editable fields."""
|
||||
template = self.branding_manager.load_template(template_id)
|
||||
self.branding_display_name_input.setText(template.display_name)
|
||||
self.branding_logo_path_input.setText(template.logo_path)
|
||||
self.branding_app_name_input.setText(template.app_name)
|
||||
self.branding_window_title_input.setText(
|
||||
template.window_title or f"{template.app_name} v{self.config.app_version}"
|
||||
)
|
||||
self.branding_logo_path_input.setText(template.logo_path or template.get_app_icon_path())
|
||||
self._update_branding_preview()
|
||||
|
||||
def _resolve_branding_preview_path(self, configured_path: str) -> Optional[Path]:
|
||||
"""Resolve a branding preview path in both dev and packaged layouts."""
|
||||
if not configured_path:
|
||||
return None
|
||||
|
||||
path = Path(configured_path)
|
||||
candidates = [path] if path.is_absolute() else [Path.cwd() / path]
|
||||
if not path.is_absolute():
|
||||
project_root = Path(__file__).resolve().parents[3]
|
||||
candidates.append(project_root / path)
|
||||
|
||||
for candidate in candidates:
|
||||
if candidate.exists() and candidate.is_file():
|
||||
return candidate
|
||||
|
||||
return None
|
||||
|
||||
def _update_branding_preview(self) -> None:
|
||||
"""Refresh the small branding preview for name and icon."""
|
||||
display_name = self.branding_display_name_input.text().strip() or tr(
|
||||
|
|
@ -272,11 +319,17 @@ class SettingsDialog(QDialog):
|
|||
)
|
||||
self.branding_preview_name_label.setText(display_name)
|
||||
|
||||
effective_title = self.branding_window_title_input.text().strip() or (
|
||||
self.branding_app_name_input.text().strip() or display_name
|
||||
)
|
||||
self.branding_preview_title_label.setText(effective_title)
|
||||
|
||||
logo_path = self.branding_logo_path_input.text().strip()
|
||||
if logo_path and Path(logo_path).exists():
|
||||
pixmap = QPixmap(logo_path)
|
||||
resolved_logo_path = self._resolve_branding_preview_path(logo_path)
|
||||
if resolved_logo_path:
|
||||
pixmap = QPixmap(str(resolved_logo_path))
|
||||
if pixmap.isNull():
|
||||
icon = QIcon(logo_path)
|
||||
icon = QIcon(str(resolved_logo_path))
|
||||
pixmap = icon.pixmap(64, 64)
|
||||
|
||||
if not pixmap.isNull():
|
||||
|
|
@ -318,10 +371,13 @@ class SettingsDialog(QDialog):
|
|||
|
||||
try:
|
||||
display_name = self.branding_display_name_input.text().strip() or branding_name
|
||||
app_name = self.branding_app_name_input.text().strip() or display_name
|
||||
window_title = self.branding_window_title_input.text().strip()
|
||||
template = self.branding_manager.build_template(
|
||||
template_id=branding_name,
|
||||
display_name=display_name,
|
||||
app_name=display_name,
|
||||
app_name=app_name,
|
||||
window_title=window_title,
|
||||
logo_path=self.branding_logo_path_input.text(),
|
||||
)
|
||||
self.branding_manager.save_template(template)
|
||||
|
|
@ -330,6 +386,44 @@ class SettingsDialog(QDialog):
|
|||
except ConfigurationError as e:
|
||||
self._show_error(f"Failed to save branding: {e}")
|
||||
|
||||
def _export_branding(self) -> None:
|
||||
"""Export the selected branding so it can be shared with other users."""
|
||||
template_id = self.branding_combo.currentData()
|
||||
if not template_id:
|
||||
return
|
||||
|
||||
file_path, _ = QFileDialog.getSaveFileName(
|
||||
self,
|
||||
tr("settings.branding.export_title"),
|
||||
str(Path.home() / f"{template_id}.json"),
|
||||
"JSON Files (*.json);;All Files (*)",
|
||||
)
|
||||
if not file_path:
|
||||
return
|
||||
|
||||
try:
|
||||
self.branding_manager.export_template(template_id, Path(file_path))
|
||||
except ConfigurationError as e:
|
||||
self._show_error(f"Failed to export branding: {e}")
|
||||
|
||||
def _import_branding(self) -> None:
|
||||
"""Import a branding package from another user."""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
tr("settings.branding.import_title"),
|
||||
str(Path.home()),
|
||||
"JSON Files (*.json);;All Files (*)",
|
||||
)
|
||||
if not file_path:
|
||||
return
|
||||
|
||||
try:
|
||||
template = self.branding_manager.import_template(Path(file_path))
|
||||
self._refresh_branding_combo(template.template_id)
|
||||
self._load_branding_into_editor(template.template_id)
|
||||
except ConfigurationError as e:
|
||||
self._show_error(f"Failed to import branding: {e}")
|
||||
|
||||
def _delete_branding(self) -> None:
|
||||
"""Delete the currently selected custom branding."""
|
||||
template_id = self.branding_combo.currentData()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
"""Tests for runtime branding template management."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from webdrop_bridge.config import Config, ConfigurationError
|
||||
|
|
@ -114,6 +116,21 @@ def test_delete_custom_branding_removes_it(tmp_path):
|
|||
assert not manager.has_template("customer_b")
|
||||
|
||||
|
||||
def test_build_template_preserves_app_and_window_titles(tmp_path):
|
||||
"""Custom brandings should keep their editable app and window title values."""
|
||||
manager = BrandingManager(base_dir=tmp_path)
|
||||
|
||||
template = manager.build_template(
|
||||
template_id="Customer C",
|
||||
display_name="Customer C",
|
||||
app_name="Customer Bridge",
|
||||
window_title="Customer Bridge Desktop",
|
||||
)
|
||||
|
||||
assert template.app_name == "Customer Bridge"
|
||||
assert template.window_title == "Customer Bridge Desktop"
|
||||
|
||||
|
||||
def test_invalid_logo_file_is_rejected(tmp_path):
|
||||
"""Non-existent logo files should not be accepted for saved brandings."""
|
||||
manager = BrandingManager(base_dir=tmp_path)
|
||||
|
|
@ -124,3 +141,32 @@ def test_invalid_logo_file_is_rejected(tmp_path):
|
|||
display_name="Customer C",
|
||||
logo_path=str(tmp_path / "missing-logo.png"),
|
||||
)
|
||||
|
||||
|
||||
def test_exported_branding_can_be_imported_for_another_user(tmp_path):
|
||||
"""Exported brandings should be shareable and importable by another user."""
|
||||
source_logo = tmp_path / "shared-logo.png"
|
||||
source_logo.write_bytes(b"fake-png-data")
|
||||
|
||||
source_manager = BrandingManager(base_dir=tmp_path / "source")
|
||||
template = source_manager.build_template(
|
||||
template_id="Customer D",
|
||||
display_name="Customer D",
|
||||
app_name="Customer Bridge",
|
||||
window_title="Customer Window",
|
||||
logo_path=str(source_logo),
|
||||
)
|
||||
source_manager.save_template(template)
|
||||
|
||||
export_path = tmp_path / "export" / "customer_d.json"
|
||||
source_manager.export_template("customer_d", export_path)
|
||||
|
||||
target_manager = BrandingManager(base_dir=tmp_path / "target")
|
||||
imported = target_manager.import_template(export_path)
|
||||
|
||||
assert imported.template_id == "customer_d"
|
||||
assert imported.display_name == "Customer D"
|
||||
assert imported.app_name == "Customer Bridge"
|
||||
assert imported.window_title == "Customer Window"
|
||||
assert Path(imported.logo_path).exists()
|
||||
assert target_manager.has_template("customer_d")
|
||||
|
|
|
|||
|
|
@ -120,9 +120,15 @@ class TestSettingsDialogInitialization:
|
|||
qtbot.addWidget(dialog)
|
||||
|
||||
assert dialog.branding_display_name_input.text() == "Default"
|
||||
assert dialog.branding_app_name_input.text() == "WebDrop Bridge"
|
||||
assert "WebDrop Bridge" in dialog.branding_window_title_input.text()
|
||||
assert dialog.branding_logo_path_input is not None
|
||||
assert dialog.browse_branding_logo_btn is not None
|
||||
assert dialog.branding_preview_name_label.text() == "Default"
|
||||
assert dialog.branding_preview_icon_label.pixmap() is not None
|
||||
assert not dialog.branding_preview_icon_label.pixmap().isNull()
|
||||
assert dialog.export_branding_btn is not None
|
||||
assert dialog.import_branding_btn is not None
|
||||
assert dialog.delete_branding_btn is not None
|
||||
|
||||
def test_save_branding_as_creates_custom_template(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue