image display basic functionality

This commit is contained in:
Tim Bendt
2025-05-16 17:17:37 -06:00
parent fc57e201a2
commit bec09bade8
7 changed files with 1187 additions and 117 deletions

View File

@@ -1,14 +1,11 @@
import os
import sys
import json
import asyncio
from datetime import datetime
from pathlib import Path
import msal
import aiohttp
from rich.panel import Panel
from rich import print as rprint
from textual.app import App, ComposeResult
from textual.binding import Binding
@@ -16,17 +13,13 @@ from textual.containers import Container, Horizontal, Vertical
from textual.widgets import (
Header,
Footer,
Static,
Label,
DataTable,
Button,
ListView,
ListItem,
LoadingIndicator,
OptionList
OptionList,
)
from textual.reactive import reactive
from textual.worker import Worker, get_current_worker
from textual import work
from textual.widgets.option_list import Option
@@ -41,7 +34,7 @@ from maildir_gtd.screens.DocumentViewer import DocumentViewerScreen
class FolderHistoryEntry:
"""Represents an entry in the folder navigation history."""
def __init__(self, folder_id: str, folder_name: str, parent_id: str = None):
def __init__(self, folder_id: str, folder_name: str, parent_id: str = ""):
self.folder_id = folder_id
self.folder_name = folder_name
self.parent_id = parent_id
@@ -101,13 +94,15 @@ class OneDriveTUI(App):
with Container(id="main_container"):
with Horizontal(id="top_bar"):
yield Button("\uf148 Up", id="back_button", classes="hide")
yield Label("Authenticating with Microsoft Graph API...", id="status_label")
yield Label(
"Authenticating with Microsoft Graph API...", id="status_label"
)
yield LoadingIndicator(id="loading")
yield OptionList(
Option("Following", id="following"),
Option("Root", id="root"),
id="view_options"
)
Option("Following", id="following"),
Option("Root", id="root"),
id="view_options",
)
with Container(id="auth_container"):
yield Label("", id="auth_message")
@@ -125,7 +120,7 @@ class OneDriveTUI(App):
self.query_one("#login_button").styles.width = "20"
self.query_one("#view_options").border_title = "My Files"
# Initialize the table
table = self.query_one("#items_table")
table = self.query_one("#items_table", DataTable)
table.cursor_type = "row"
table.add_columns("", "Name", "Last Modified", "Size", "Web URL")
table.focus()
@@ -173,13 +168,17 @@ class OneDriveTUI(App):
@work
async def get_token_silent(self, account):
"""Get token silently."""
token_response = self.msal_app.acquire_token_silent(self.scopes, account=account)
token_response = self.msal_app.acquire_token_silent(
self.scopes, account=account
)
if token_response and "access_token" in token_response:
self.access_token = token_response["access_token"]
self.is_authenticated = True
self.load_initial_data()
else:
self.query_one("#status_label").update("Silent authentication failed. Please log in.")
self.query_one("#status_label").update(
"Silent authentication failed. Please log in."
)
self.query_one("#auth_container").remove_class("hide")
self.query_one("#loading").remove()
@@ -237,11 +236,13 @@ class OneDriveTUI(App):
try:
async with aiohttp.ClientSession() as session:
async with session.get(
"https://graph.microsoft.com/v1.0/me/drives",
headers=headers
"https://graph.microsoft.com/v1.0/me/drives", headers=headers
) as response:
if response.status != 200:
self.notify(f"Failed to load drives: {response.status}", severity="error")
self.notify(
f"Failed to load drives: {response.status}",
severity="error",
)
return
drives_data = await response.json()
@@ -269,7 +270,9 @@ class OneDriveTUI(App):
if self.selected_drive_id:
self.load_followed_items()
async def on_option_list_option_selected(self, event: OptionList.OptionSelected) -> None:
async def on_option_list_option_selected(
self, event: OptionList.OptionSelected
) -> None:
"""Handle option list selection."""
selected_option = event.option.id
if selected_option == "following":
@@ -286,12 +289,20 @@ class OneDriveTUI(App):
selected_id = event.row_key.value
self.open_item(selected_id)
async def on_data_table_row_highlighted(self, event: DataTable.RowHighlighted) -> None:
async def on_data_table_row_highlighted(
self, event: DataTable.RowHighlighted
) -> None:
self.selected_item_id = event.row_key.value
def open_item(self, selected_id: str):
if selected_id:
self.folder_history.append(FolderHistoryEntry(self.current_folder_id, self.current_folder_name, self.selected_drive_id))
self.folder_history.append(
FolderHistoryEntry(
self.current_folder_id,
self.current_folder_name,
self.selected_drive_id,
)
)
# Get an item from current items by ID string
selected_row = self.current_items[selected_id]
@@ -303,22 +314,31 @@ class OneDriveTUI(App):
self.notify(f"Selected folder: {item_name}", timeout=1)
# Load items in the folder
self.query_one("#back_button").remove_class("hide")
self.query_one("#status_label").update(f"Loading items in folder: {item_name}")
self.load_drive_folder_items(folder_id=selected_id, drive_id=selected_row.get("parentReference", {}).get("driveId", self.selected_drive_id))
self.query_one("#status_label").update(
f"Loading items in folder: {item_name}"
)
self.load_drive_folder_items(
folder_id=selected_id,
drive_id=selected_row.get("parentReference", {}).get(
"driveId", self.selected_drive_id
),
)
else:
self.notify(f"Selected file: {item_name}")
self.action_view_document()
@work
async def load_drive_folder_items(self, folder_id: str = "", drive_id: str = "", track_history: bool = True):
async def load_drive_folder_items(
self, folder_id: str = "", drive_id: str = "", track_history: bool = True
):
"""Load root items from the selected drive."""
if not self.access_token or not self.selected_drive_id:
return
self.query_one("#status_label").update("Loading drive folder items...")
headers = {"Authorization": f"Bearer {self.access_token}"}
url = f"https://graph.microsoft.com/v1.0/me/drive/root/children"
url = "https://graph.microsoft.com/v1.0/me/drive/root/children"
if folder_id and drive_id and folder_id != "root":
url = f"https://graph.microsoft.com/v1.0/drives/{drive_id}/items/{folder_id}/children"
@@ -326,30 +346,38 @@ class OneDriveTUI(App):
# self.folder_history.append(FolderHistoryEntry(folder_id, self.current_folder_name, drive_id))
self.selected_drive_id = drive_id
self.current_folder_id = folder_id
self.current_folder_name = self.current_items[folder_id].get("name", "Unknown")
self.current_folder_name = self.current_items[folder_id].get(
"name", "Unknown"
)
try:
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers) as response:
if response.status != 200:
self.notify(f"Failed to load drive items: {response.status}", severity="error")
self.notify(
f"Failed to load drive items: {response.status}",
severity="error",
)
return
items_data = await response.json()
# Update the table with the root items
self.update_items_table(items_data.get("value", []))
except Exception as e:
self.notify(f"Error loading root items: {str(e)}", severity="error")
#update the status label with breadcrumbs from the folder_history
# update the status label with breadcrumbs from the folder_history
if self.folder_history:
breadcrumbs = " / \uf07b ".join([entry.folder_name for entry in self.folder_history])
self.query_one("#status_label").update(f"\uf07b {breadcrumbs} / \uf07b {self.current_folder_name}")
breadcrumbs = " / \uf07b ".join(
[entry.folder_name for entry in self.folder_history]
)
self.query_one("#status_label").update(
f"\uf07b {breadcrumbs} / \uf07b {self.current_folder_name}"
)
else:
self.query_one("#status_label").update(f" \uf07b {self.current_folder_name}")
self.query_one("#status_label").update(
f" \uf07b {self.current_folder_name}"
)
@work
async def load_followed_items(self):
@@ -361,18 +389,20 @@ class OneDriveTUI(App):
headers = {"Authorization": f"Bearer {self.access_token}"}
try:
url = f"https://graph.microsoft.com/v1.0/me/drive/following"
url = "https://graph.microsoft.com/v1.0/me/drive/following"
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers) as response:
if response.status != 200:
self.notify(f"Failed to load followed items: {response.status}", severity="error")
self.notify(
f"Failed to load followed items: {response.status}",
severity="error",
)
return
items_data = await response.json()
followed_items = items_data.get("value", [])
# Update the table with the followed items
self.update_items_table(followed_items)
except Exception as e:
@@ -402,7 +432,9 @@ class OneDriveTUI(App):
last_modified = item.get("lastModifiedDateTime", "")
if last_modified:
try:
date_obj = datetime.fromisoformat(last_modified.replace('Z', '+00:00'))
date_obj = datetime.fromisoformat(
last_modified.replace("Z", "+00:00")
)
last_modified = date_obj.strftime("%Y-%m-%d %H:%M")
except:
pass
@@ -422,10 +454,9 @@ class OneDriveTUI(App):
size_str = "N/A"
web_url = item.get("webUrl", "")
item_id = item.get("id")
# Limit filename length to 160 characters
display_name = name[:50] + '...' if len(name) > 50 else name
display_name = name[:50] + "..." if len(name) > 50 else name
# Add row to table with the appropriate icon class for styling
row_key = table.add_row(
@@ -434,7 +465,7 @@ class OneDriveTUI(App):
last_modified,
size_str,
web_url,
key=item.get("id")
key=item.get("id"),
)
# Add item to the list of current items keyed by row_key so we can look up all information later
@@ -490,10 +521,14 @@ class OneDriveTUI(App):
return
selected_name = selected_row.get("name")
drive_id = selected_row.get("parentReference", {}).get("driveId", self.selected_drive_id)
drive_id = selected_row.get("parentReference", {}).get(
"driveId", self.selected_drive_id
)
web_url = selected_row.get("webUrl", "")
# Open the document viewer screen with all required details
viewer = DocumentViewerScreen(self.selected_item_id, selected_name, self.access_token, drive_id)
viewer = DocumentViewerScreen(
self.selected_item_id, selected_name, self.access_token, drive_id
)
viewer.web_url = web_url # Pass the webUrl to the viewer
self.push_screen(viewer)
@@ -509,7 +544,11 @@ class OneDriveTUI(App):
self.current_folder_name = previous_entry.folder_name
if len(self.folder_history) <= 0:
self.query_one("#back_button").add_class("hide")
self.load_drive_folder_items(folder_id=previous_entry.folder_id, drive_id=previous_entry.parent_id, track_history=False)
self.load_drive_folder_items(
folder_id=previous_entry.folder_id,
drive_id=previous_entry.parent_id,
track_history=False,
)
else:
self.notify("No previous folder to navigate back to")