feat: implement favorites sidebar with add, remove, and navigation functionality
This commit is contained in:
@@ -27,7 +27,9 @@ def load_settings():
|
||||
"client_id": "DIN_CLIENT_ID_HER",
|
||||
"tenant_id": "DIN_TENANT_ID_HER",
|
||||
"temp_dir": "C:\\Temp_SP",
|
||||
"language": "da" # da eller en
|
||||
"language": "da", # da eller en
|
||||
"favorites": [], # Liste over {id, name, type, drive_id, site_id, path}
|
||||
"fav_visible": True
|
||||
}
|
||||
if not os.path.exists(SETTINGS_FILE):
|
||||
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
||||
@@ -137,7 +139,11 @@ STRINGS = {
|
||||
"settings_cancel": "Annuller",
|
||||
"msg_settings_saved": "Indstillingerne er gemt.",
|
||||
"msg_restart_required": "Visse ændringer (f.eks. ID'er) træder først i kraft efter genstart.",
|
||||
"status_login_needed": "Session udløbet. Log ind igen."
|
||||
"status_login_needed": "Session udløbet. Log ind igen.",
|
||||
"btn_add_fav": "⭐ Tilføj til favoritter",
|
||||
"btn_remove_fav": "❌ Fjern fra favoritter",
|
||||
"label_favorites": "⭐ Favoritter",
|
||||
"msg_fav_exists": "'{name}' er allerede i favoritter."
|
||||
},
|
||||
"en": {
|
||||
"title": "SharePoint Explorer",
|
||||
@@ -222,7 +228,11 @@ STRINGS = {
|
||||
"settings_cancel": "Cancel",
|
||||
"msg_settings_saved": "Settings saved.",
|
||||
"msg_restart_required": "Some changes (e.g., IDs) only take effect after restart.",
|
||||
"status_login_needed": "Session expired. Please login again."
|
||||
"status_login_needed": "Session expired. Please login again.",
|
||||
"btn_add_fav": "⭐ Add to favorites",
|
||||
"btn_remove_fav": "❌ Remove from favorites",
|
||||
"label_favorites": "⭐ Favorites",
|
||||
"msg_fav_exists": "'{name}' is already in favorites."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,6 +412,8 @@ class SharePointApp(wx.Frame):
|
||||
self.tree_root = None
|
||||
self.is_navigating_back = False
|
||||
self.active_edits = {} # item_id -> { "name": name, "event": Event, "waiting": bool }
|
||||
self.favorites = settings.get("favorites", [])
|
||||
self.fav_visible = settings.get("fav_visible", True)
|
||||
|
||||
# System Ikoner (ArtProvider - mest basale for kompatibilitet)
|
||||
self.image_list = wx.ImageList(16, 16)
|
||||
@@ -523,12 +535,53 @@ class SharePointApp(wx.Frame):
|
||||
# 3. SPLITTER FOR TREE AND LIST
|
||||
self.splitter = wx.SplitterWindow(panel, style=wx.SP_LIVE_UPDATE | wx.SP_3DSASH)
|
||||
|
||||
# Left side: Tree
|
||||
self.tree_ctrl = wx.TreeCtrl(self.splitter, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.BORDER_SUNKEN)
|
||||
# Left side: Tree and Favorites Container
|
||||
self.left_container = wx.Panel(self.splitter)
|
||||
self.left_vbox = wx.BoxSizer(wx.VERTICAL)
|
||||
self.left_container.SetSizer(self.left_vbox)
|
||||
|
||||
# LEFT SIDE - TOP: Tree
|
||||
self.tree_ctrl = wx.TreeCtrl(self.left_container, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.BORDER_SUNKEN)
|
||||
self.tree_ctrl.AssignImageList(self.image_list)
|
||||
self.tree_ctrl.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.on_tree_expanding)
|
||||
self.tree_ctrl.Bind(wx.EVT_TREE_SEL_CHANGED, self.on_tree_selected)
|
||||
self.tree_ctrl.Bind(wx.EVT_TREE_ITEM_MENU, self.on_tree_right_click)
|
||||
self.left_vbox.Add(self.tree_ctrl, 1, wx.EXPAND)
|
||||
|
||||
# LEFT SIDE - BOTTOM: Favorites
|
||||
self.fav_section = wx.Panel(self.left_container)
|
||||
self.fav_vbox = wx.BoxSizer(wx.VERTICAL)
|
||||
self.fav_section.SetSizer(self.fav_vbox)
|
||||
|
||||
# Fav Header (Toggle)
|
||||
self.fav_header = wx.Panel(self.fav_section)
|
||||
self.fav_header.SetBackgroundColour(wx.Colour(240, 240, 240))
|
||||
h_hbox = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
self.fav_label = wx.StaticText(self.fav_header, label=self.get_txt("label_favorites"))
|
||||
self.fav_label.SetFont(wx.Font(9, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD))
|
||||
h_hbox.Add(self.fav_label, 1, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)
|
||||
|
||||
self.fav_toggle_btn = wx.Button(self.fav_header, label="▼" if self.fav_visible else "▲", size=(25, 25), style=wx.BU_EXACTFIT)
|
||||
self.fav_toggle_btn.Bind(wx.EVT_BUTTON, self.toggle_favorites)
|
||||
h_hbox.Add(self.fav_toggle_btn, 0, wx.ALL, 2)
|
||||
|
||||
self.fav_header.SetSizer(h_hbox)
|
||||
self.fav_vbox.Add(self.fav_header, 0, wx.EXPAND)
|
||||
|
||||
# Fav List
|
||||
self.fav_list = wx.ListCtrl(self.fav_section, style=wx.LC_REPORT | wx.LC_NO_HEADER | wx.BORDER_SUNKEN)
|
||||
self.fav_list.AssignImageList(self.image_list, wx.IMAGE_LIST_SMALL)
|
||||
self.fav_list.InsertColumn(0, "Name", width=250)
|
||||
self.fav_list.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.on_favorite_activated)
|
||||
self.fav_list.Bind(wx.EVT_CONTEXT_MENU, self.on_favorite_right_click)
|
||||
self.fav_vbox.Add(self.fav_list, 1, wx.EXPAND)
|
||||
|
||||
self.left_vbox.Add(self.fav_section, 0, wx.EXPAND)
|
||||
if not self.fav_visible:
|
||||
self.fav_list.Hide()
|
||||
else:
|
||||
self.left_vbox.SetItemMinSize(self.fav_section, -1, 200)
|
||||
|
||||
# Right side: File Area - ListCtrl
|
||||
self.list_ctrl = wx.ListCtrl(self.splitter, style=wx.LC_REPORT | wx.BORDER_SUNKEN)
|
||||
@@ -544,10 +597,13 @@ class SharePointApp(wx.Frame):
|
||||
# AKTIVER DRAG & DROP
|
||||
self.list_ctrl.SetDropTarget(UploadDropTarget(self.list_ctrl, self))
|
||||
|
||||
self.splitter.SplitVertically(self.tree_ctrl, self.list_ctrl, 250)
|
||||
self.splitter.SplitVertically(self.left_container, self.list_ctrl, 250)
|
||||
self.splitter.SetMinimumPaneSize(100)
|
||||
|
||||
vbox.Add(self.splitter, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
|
||||
|
||||
# Load initial favorites
|
||||
self.refresh_fav_list()
|
||||
|
||||
# 4. STATUS BAR
|
||||
self.status_bar = self.CreateStatusBar()
|
||||
@@ -572,14 +628,10 @@ class SharePointApp(wx.Frame):
|
||||
if len(selected_indices) == 1:
|
||||
item = selected_items[0]
|
||||
|
||||
if item.get("web_url"):
|
||||
browser_item = menu.Append(wx.ID_ANY, self.get_txt("msg_open_browser"))
|
||||
self.Bind(wx.EVT_MENU, lambda e: webbrowser.open(item["web_url"]), browser_item)
|
||||
|
||||
download_item = menu.Append(wx.ID_ANY, self.get_txt("msg_download"))
|
||||
self.Bind(wx.EVT_MENU, lambda e: self.on_download_clicked(item), download_item)
|
||||
|
||||
menu.AppendSeparator()
|
||||
if item['type'] in ["FOLDER", "DRIVE", "SITE"]:
|
||||
fav_item = menu.Append(wx.ID_ANY, self.get_txt("btn_add_fav"))
|
||||
self.Bind(wx.EVT_MENU, lambda e, i=item: self.add_favorite(i), fav_item)
|
||||
menu.AppendSeparator()
|
||||
|
||||
if item['type'] == "FILE":
|
||||
edit_item = menu.Append(wx.ID_ANY, self.get_txt("msg_edit_file"))
|
||||
@@ -622,12 +674,109 @@ class SharePointApp(wx.Frame):
|
||||
if not item.IsOk() or item == self.tree_root: return
|
||||
|
||||
self.tree_ctrl.SelectItem(item)
|
||||
data = self.tree_item_data.get(item)
|
||||
|
||||
menu = wx.Menu()
|
||||
if data:
|
||||
fav_item = menu.Append(wx.ID_ANY, self.get_txt("btn_add_fav"))
|
||||
self.Bind(wx.EVT_MENU, lambda e, d=data: self.add_favorite(d), fav_item)
|
||||
menu.AppendSeparator()
|
||||
|
||||
refresh_item = menu.Append(wx.ID_ANY, self.get_txt("btn_refresh"))
|
||||
self.Bind(wx.EVT_MENU, self.on_refresh, refresh_item)
|
||||
self.PopupMenu(menu)
|
||||
menu.Destroy()
|
||||
|
||||
# --- FAVORITES LOGIC ---
|
||||
def add_favorite(self, item):
|
||||
# Check if exists
|
||||
for fav in self.favorites:
|
||||
if fav['id'] == item['id']:
|
||||
self.show_info(self.get_txt("msg_fav_exists", name=item['name']), wx.ICON_INFORMATION)
|
||||
return
|
||||
|
||||
new_fav = {
|
||||
"id": item['id'],
|
||||
"name": item['name'],
|
||||
"type": item['type'],
|
||||
"drive_id": item.get('drive_id'),
|
||||
"site_id": item.get('id') if item['type'] == "SITE" else self.current_site_id,
|
||||
"path": self.current_path + [item['name']]
|
||||
}
|
||||
self.favorites.append(new_fav)
|
||||
self.save_favorites()
|
||||
self.refresh_fav_list()
|
||||
self.show_info(self.get_txt("msg_success"))
|
||||
|
||||
def remove_favorite(self, id):
|
||||
self.favorites = [f for f in self.favorites if f['id'] != id]
|
||||
self.save_favorites()
|
||||
self.refresh_fav_list()
|
||||
|
||||
def save_favorites(self):
|
||||
settings["favorites"] = self.favorites
|
||||
save_settings(settings)
|
||||
|
||||
def refresh_fav_list(self):
|
||||
self.fav_list.DeleteAllItems()
|
||||
for i, fav in enumerate(self.favorites):
|
||||
img_idx = self.idx_folder
|
||||
if fav['type'] == "DRIVE": img_idx = self.idx_drive
|
||||
elif fav['type'] == "SITE": img_idx = self.idx_site
|
||||
|
||||
self.fav_list.InsertItem(i, fav['name'], img_idx)
|
||||
self.fav_list.SetItemData(i, i) # Store index
|
||||
|
||||
def on_favorite_activated(self, event):
|
||||
idx = event.GetIndex()
|
||||
if idx < 0 or idx >= len(self.favorites): return
|
||||
fav = self.favorites[idx]
|
||||
|
||||
self.current_path = fav['path']
|
||||
self.update_path_display()
|
||||
|
||||
# Navigate to contents
|
||||
data = {
|
||||
"type": fav['type'],
|
||||
"id": fav['id'],
|
||||
"drive_id": fav['drive_id'],
|
||||
"path": fav['path']
|
||||
}
|
||||
|
||||
if fav['type'] == "SITE": self.current_site_id = fav['id']
|
||||
elif fav['drive_id']: self.current_drive_id = fav['drive_id']
|
||||
|
||||
self.list_ctrl.DeleteAllItems()
|
||||
self.current_items = []
|
||||
self.set_status(self.get_txt("status_loading_content"))
|
||||
threading.Thread(target=self._fetch_list_contents_bg, args=(data,), daemon=True).start()
|
||||
|
||||
def on_favorite_right_click(self, event):
|
||||
idx = self.fav_list.GetFirstSelected()
|
||||
if idx < 0: return
|
||||
fav = self.favorites[idx]
|
||||
|
||||
menu = wx.Menu()
|
||||
remove_item = menu.Append(wx.ID_ANY, self.get_txt("btn_remove_fav"))
|
||||
self.Bind(wx.EVT_MENU, lambda e: self.remove_favorite(fav['id']), remove_item)
|
||||
self.PopupMenu(menu)
|
||||
menu.Destroy()
|
||||
|
||||
def toggle_favorites(self, event=None):
|
||||
self.fav_visible = not self.fav_visible
|
||||
self.fav_toggle_btn.SetLabel("▼" if self.fav_visible else "▲")
|
||||
|
||||
if self.fav_visible:
|
||||
self.fav_list.Show()
|
||||
self.left_vbox.SetItemMinSize(self.fav_section, -1, 200)
|
||||
else:
|
||||
self.fav_list.Hide()
|
||||
self.left_vbox.SetItemMinSize(self.fav_section, -1, 30)
|
||||
|
||||
settings["fav_visible"] = self.fav_visible
|
||||
save_settings(settings)
|
||||
self.left_container.Layout()
|
||||
|
||||
# --- FILHÅNDTERING (Upload, Slet, Ny Mappe) ---
|
||||
def on_delete_items_clicked(self, items):
|
||||
if not items: return
|
||||
|
||||
Reference in New Issue
Block a user