Compare commits

...

7 Commits

Author SHA1 Message Date
Zoe Forbes
383eefce9c fix(UX): offer registration when BOM opened on untracked document (#56)
Replace the dead-end warning in Silo_BOM with a question dialog that
offers to register the document via Silo_New. If the user accepts and
registration succeeds, the BOM dialog opens seamlessly.
2026-02-08 18:46:22 -06:00
Zoe Forbes
1676b3e1a0 art: add missing icons for TagProjects, Rollback, SetStatus (#60)
Create silo-tag.svg, silo-rollback.svg, and silo-status.svg in the
Catppuccin Mocha style matching existing silo icons. These were
referenced by _icon() but did not exist, causing the commands to
render without toolbar icons.
2026-02-08 18:36:22 -06:00
Zoe Forbes
06cd30e88d fix: update silo-client — consistent error handling in delete_bom_entry (#59) 2026-02-08 18:29:31 -06:00
f9924d35f7 Merge pull request 'feat: enhance Database Activity pane with comments, interaction, and badges' (#11) from feature/activity-pane-enhancements into main
Reviewed-on: #11
2026-02-08 22:23:50 +00:00
373f3aaa79 Merge branch 'main' into feature/activity-pane-enhancements 2026-02-08 22:23:40 +00:00
66b2baf510 Merge pull request 'fix: SSE reconnect with exponential backoff and terminal state' (#7) from fix/sse-reconnect into main
Reviewed-on: #7
2026-02-08 22:23:23 +00:00
Zoe Forbes
d26bb6da2d feat: enhance Database Activity pane with comments, interaction, and badges
Enhance the Database Activity panel in SiloAuthDockWidget:

- Show latest revision number and comment inline per activity entry
- Truncate long descriptions with ellipsis, full text in tooltip
- Mark locally checked-out items with green color and "local" badge
- Double-click opens local file or triggers checkout from server
- Right-click context menu: Open in Create, Open in Browser,
  Copy Part Number, View Revisions

Closes #9
2026-02-08 16:23:06 -06:00
5 changed files with 135 additions and 9 deletions

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#cba6f7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<!-- Counter-clockwise arrow -->
<polyline points="1 4 1 10 7 10" stroke="#f38ba8"/>
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10" stroke="#cba6f7"/>
<!-- Clock hands -->
<line x1="12" y1="7" x2="12" y2="12" stroke="#89dceb" stroke-width="1.5"/>
<line x1="12" y1="12" x2="15" y2="14" stroke="#89dceb" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 493 B

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#cba6f7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<!-- Shield shape -->
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" fill="#313244"/>
<!-- Status bars -->
<line x1="8" y1="10" x2="16" y2="10" stroke="#a6e3a1" stroke-width="1.5"/>
<line x1="8" y1="14" x2="13" y2="14" stroke="#89dceb" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 435 B

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#cba6f7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<!-- Tag shape -->
<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" fill="#313244"/>
<!-- Tag hole -->
<circle cx="7" cy="7" r="1.5" fill="#cba6f7" stroke="none"/>
</svg>

After

Width:  |  Height:  |  Size: 373 B

View File

@@ -1998,15 +1998,18 @@ class Silo_BOM:
obj = get_tracked_object(doc)
if not obj:
FreeCAD.Console.PrintError("No tracked Silo item in active document.\n")
from PySide import QtGui as _qg
_qg.QMessageBox.warning(
reply = QtGui.QMessageBox.question(
None,
"BOM",
"This document is not registered with Silo.\nUse Silo > New to register it first.",
"This document is not registered with Silo.\n\nRegister it now?",
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
)
return
if reply != QtGui.QMessageBox.Yes:
return
FreeCADGui.runCommand("Silo_New")
obj = get_tracked_object(doc)
if not obj:
return
part_number = obj.SiloPartNumber
@@ -2785,7 +2788,7 @@ class SiloAuthDockWidget:
def _refresh_activity_panel(self):
"""Refresh the Database Activity panel if it exists."""
from PySide import QtWidgets
from PySide import QtCore, QtGui, QtWidgets
mw = FreeCADGui.getMainWindow()
if mw is None:
@@ -2797,6 +2800,24 @@ class SiloAuthDockWidget:
if activity_list is None:
return
activity_list.clear()
# Connect interaction signals (once)
if not getattr(activity_list, "_silo_connected", False):
activity_list.itemDoubleClicked.connect(self._on_activity_double_click)
activity_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
activity_list.customContextMenuRequested.connect(
lambda pos: self._on_activity_context_menu(activity_list, pos)
)
activity_list._silo_connected = True
# Collect local part numbers for badge
local_pns = set()
try:
for lf in search_local_files():
local_pns.add(lf.get("part_number", ""))
except Exception:
pass
try:
items = _client.list_items()
if isinstance(items, list):
@@ -2806,12 +2827,96 @@ class SiloAuthDockWidget:
updated = item.get("updated_at", "")
if updated:
updated = updated[:10]
activity_list.addItem(f"{pn} - {desc} - {updated}")
# Fetch latest revision info
rev_num = ""
comment = ""
try:
revs = _client.get_revisions(pn)
if revs:
latest = revs[0] if isinstance(revs, list) else revs
rev_num = str(latest.get("revision_number", ""))
comment = latest.get("comment", "") or ""
except Exception:
pass
# Truncate long descriptions
desc_display = desc
if len(desc_display) > 40:
desc_display = desc_display[:37] + "..."
# Build display text
rev_part = f" \u2013 Rev {rev_num}" if rev_num else ""
date_part = f" \u2013 {updated}" if updated else ""
local_badge = " \u25cf local" if pn in local_pns else ""
line1 = (
f"{pn} \u2013 {desc_display}{rev_part}{date_part}{local_badge}"
)
if comment:
line1 += f'\n "{comment}"'
else:
line1 += "\n (no comment)"
list_item = QtWidgets.QListWidgetItem(line1)
list_item.setData(QtCore.Qt.UserRole, pn)
if desc and len(desc) > 40:
list_item.setToolTip(desc)
if pn in local_pns:
list_item.setForeground(QtGui.QColor("#4CAF50"))
activity_list.addItem(list_item)
if activity_list.count() == 0:
activity_list.addItem("(No items in database)")
except Exception:
activity_list.addItem("(Unable to refresh activity)")
def _on_activity_double_click(self, item):
"""Open/checkout item from activity pane."""
pn = item.data(256) # Qt.UserRole
if not pn:
return
local_path = find_file_by_part_number(pn)
if local_path and local_path.exists():
FreeCAD.openDocument(str(local_path))
else:
_sync.open_item(pn)
def _on_activity_context_menu(self, activity_list, pos):
"""Show context menu for activity pane items."""
from PySide import QtCore, QtGui
item = activity_list.itemAt(pos)
if item is None:
return
pn = item.data(QtCore.Qt.UserRole)
if not pn:
return
menu = QtGui.QMenu()
open_action = menu.addAction("Open in Create")
browser_action = menu.addAction("Open in Browser")
copy_action = menu.addAction("Copy Part Number")
revisions_action = menu.addAction("View Revisions")
action = menu.exec_(activity_list.mapToGlobal(pos))
if action == open_action:
local_path = find_file_by_part_number(pn)
if local_path and local_path.exists():
FreeCAD.openDocument(str(local_path))
else:
_sync.open_item(pn)
elif action == browser_action:
import webbrowser
api_url = _get_api_url().rstrip("/")
base_url = api_url[:-4] if api_url.endswith("/api") else api_url
webbrowser.open(f"{base_url}/items/{pn}")
elif action == copy_action:
QtGui.QApplication.clipboard().setText(pn)
elif action == revisions_action:
FreeCADGui.runCommand("Silo_Info", 0)
# -- Actions ------------------------------------------------------------
def _on_login_clicked(self):