diff --git a/sharepoint_browser.py b/sharepoint_browser.py index 7b757b0..b28d771 100644 --- a/sharepoint_browser.py +++ b/sharepoint_browser.py @@ -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