Convert to native Tkinter GUI: add mac_native.py and update setup.py/README
Co-authored-by: Copilot <[email protected]>
This commit is contained in:
1 parent
c77c1a6f6f
commit
4de3064dd1
3 files changed
+166
-3
No files matched your search
@@ -41,7 +41,7 @@ chmod +x build_mac.sh
|
||||
./build_mac.sh
|
||||
```
|
||||
|
||||
The built .app will appear in dist/ (example: `dist/mac_launcher.app`). Double-click the `.app` to launch; it will start a local web server and open your default browser to the UI.
|
||||
The built .app will appear in dist/ (example: `dist/MediaEpisodesViewer.app`). Double-click the `.app` to launch; it will open a native GUI application (no local web server is required).
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Native GUI using tkinter to generate One Piece progress reports without a web server.
|
||||
"""
|
||||
|
||||
import os
|
||||
import threading
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox, scrolledtext
|
||||
from datetime import datetime
|
||||
|
||||
# Reuse helper logic from app.py
|
||||
from app import current_week, format_date_range, get_latest_sunday, get_previous_sunday
|
||||
from clients import jellyfin
|
||||
|
||||
|
||||
def generate_report(ui):
|
||||
def task():
|
||||
ui['status_var'].set("Fetching...")
|
||||
jellyfin.clear_debug_logs()
|
||||
try:
|
||||
latest = get_latest_sunday()
|
||||
previous = get_previous_sunday()
|
||||
latest_dt = datetime.fromisoformat(latest.replace("Z", "+00:00"))
|
||||
previous_dt = datetime.fromisoformat(previous.replace("Z", "+00:00"))
|
||||
user_id = os.environ.get("JELLYFIN_USER_ID")
|
||||
if not user_id:
|
||||
raise RuntimeError("JELLYFIN_USER_ID not set in environment")
|
||||
|
||||
ep_info = jellyfin.find_last_one_piece_episode(latest_dt, user_id)
|
||||
prev_ep_info = jellyfin.find_last_one_piece_before_previous_sunday(previous_dt, user_id)
|
||||
previous_episode = prev_ep_info["episode_number"]
|
||||
total_episodes = int(os.environ.get("TOTAL_EPISODES", 1155))
|
||||
watched_this_week = ep_info["episode_number"] - previous_episode
|
||||
week_start, week_end, _ = current_week()
|
||||
date_range = format_date_range(week_start, week_end)
|
||||
|
||||
report_text = (
|
||||
f"ONE PIECE PROGRESS!!! ({date_range})\n"
|
||||
f"Episode {ep_info['episode_number']}/{total_episodes}\n"
|
||||
f"Around {watched_this_week} episodes watched this week!"
|
||||
)
|
||||
|
||||
debug_output = jellyfin.get_debug_logs()
|
||||
ui['root'].after(0, lambda: update_ui(ui, ep_info, previous_episode, watched_this_week, total_episodes, report_text, debug_output))
|
||||
except Exception as e:
|
||||
debug_output = jellyfin.get_debug_logs()
|
||||
ui['root'].after(0, lambda: ui['status_var'].set(f"Error: {e}\n{debug_output}"))
|
||||
|
||||
threading.Thread(target=task, daemon=True).start()
|
||||
|
||||
|
||||
def update_ui(ui, ep_info, previous_episode, watched_this_week, total_episodes, report_text, debug_output):
|
||||
ui['latest_var'].set(str(ep_info.get("episode_number", "—")))
|
||||
ui['previous_var'].set(str(previous_episode))
|
||||
ui['watched_var'].set(str(watched_this_week))
|
||||
ui['total_var'].set(str(total_episodes))
|
||||
|
||||
ui['report_box'].config(state='normal')
|
||||
ui['report_box'].delete('1.0', tk.END)
|
||||
ui['report_box'].insert(tk.END, report_text)
|
||||
ui['report_box'].config(state='disabled')
|
||||
|
||||
ui['debug_box'].config(state='normal')
|
||||
ui['debug_box'].delete('1.0', tk.END)
|
||||
ui['debug_box'].insert(tk.END, debug_output)
|
||||
ui['debug_box'].config(state='disabled')
|
||||
|
||||
ui['status_var'].set("Done")
|
||||
|
||||
|
||||
def copy_report(ui):
|
||||
report = ui['report_box'].get('1.0', tk.END).strip()
|
||||
if not report:
|
||||
messagebox.showinfo("Copy", "No report to copy.")
|
||||
return
|
||||
ui['root'].clipboard_clear()
|
||||
ui['root'].clipboard_append(report)
|
||||
messagebox.showinfo("Copy", "Report copied to clipboard.")
|
||||
|
||||
|
||||
def build_ui(root):
|
||||
root.title("One Piece Progress Reporter")
|
||||
root.geometry("900x650")
|
||||
main = ttk.Frame(root, padding=12)
|
||||
main.pack(fill='both', expand=True)
|
||||
|
||||
week_frame = ttk.Frame(main)
|
||||
week_frame.pack(fill='x', pady=(0,8))
|
||||
week_start, week_end, _ = current_week()
|
||||
date_range = format_date_range(week_start, week_end)
|
||||
week_label = ttk.Label(week_frame, text=f"Week: {date_range} ({week_start.isoformat()} → {week_end.isoformat()})", font=("Helvetica", 12))
|
||||
week_label.pack(anchor='w')
|
||||
|
||||
info_frame = ttk.Frame(main)
|
||||
info_frame.pack(fill='x', pady=(6,6))
|
||||
|
||||
ttk.Label(info_frame, text="Last episode watched:").grid(row=0, column=0, sticky='w', padx=4, pady=2)
|
||||
latest_var = tk.StringVar(value="—")
|
||||
ttk.Label(info_frame, textvariable=latest_var).grid(row=0, column=1, sticky='w', padx=4)
|
||||
|
||||
ttk.Label(info_frame, text="Previous week ended at:").grid(row=1, column=0, sticky='w', padx=4, pady=2)
|
||||
previous_var = tk.StringVar(value="—")
|
||||
ttk.Label(info_frame, textvariable=previous_var).grid(row=1, column=1, sticky='w', padx=4)
|
||||
|
||||
ttk.Label(info_frame, text="Watched this week:").grid(row=2, column=0, sticky='w', padx=4, pady=2)
|
||||
watched_var = tk.StringVar(value="—")
|
||||
ttk.Label(info_frame, textvariable=watched_var).grid(row=2, column=1, sticky='w', padx=4)
|
||||
|
||||
ttk.Label(info_frame, text="Total in series:").grid(row=3, column=0, sticky='w', padx=4, pady=2)
|
||||
total_var = tk.StringVar(value="—")
|
||||
ttk.Label(info_frame, textvariable=total_var).grid(row=3, column=1, sticky='w', padx=4)
|
||||
|
||||
btn_frame = ttk.Frame(info_frame)
|
||||
btn_frame.grid(row=0, column=2, rowspan=4, padx=18)
|
||||
status_var = tk.StringVar(value="")
|
||||
generate_btn = ttk.Button(btn_frame, text="Generate Report")
|
||||
generate_btn.pack(fill='x', pady=(0,6))
|
||||
copy_btn = ttk.Button(btn_frame, text="Copy to Clipboard")
|
||||
copy_btn.pack(fill='x')
|
||||
|
||||
report_label = ttk.Label(main, text="Report:")
|
||||
report_label.pack(anchor='w', pady=(8,0))
|
||||
report_box = scrolledtext.ScrolledText(main, height=6, wrap='word', state='disabled')
|
||||
report_box.pack(fill='both', expand=False, pady=(4,6))
|
||||
|
||||
debug_label = ttk.Label(main, text="Debug:")
|
||||
debug_label.pack(anchor='w')
|
||||
debug_box = scrolledtext.ScrolledText(main, height=12, wrap='word', state='disabled')
|
||||
debug_box.pack(fill='both', expand=True, pady=(4,0))
|
||||
|
||||
status = ttk.Label(root, textvariable=status_var, relief='sunken', anchor='w')
|
||||
status.pack(side='bottom', fill='x')
|
||||
|
||||
ui = {
|
||||
'root': root,
|
||||
'latest_var': latest_var,
|
||||
'previous_var': previous_var,
|
||||
'watched_var': watched_var,
|
||||
'total_var': total_var,
|
||||
'status_var': status_var,
|
||||
'report_box': report_box,
|
||||
'debug_box': debug_box,
|
||||
}
|
||||
|
||||
generate_btn.config(command=lambda: generate_report(ui))
|
||||
copy_btn.config(command=lambda: copy_report(ui))
|
||||
|
||||
debug_box.config(state='normal')
|
||||
debug_box.insert(tk.END, f"LATEST_SUNDAY={get_latest_sunday()}\nPREVIOUS_SUNDAY={get_previous_sunday()}\n")
|
||||
debug_box.config(state='disabled')
|
||||
|
||||
return ui
|
||||
|
||||
|
||||
def main():
|
||||
root = tk.Tk()
|
||||
ui = build_ui(root)
|
||||
root.mainloop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,11 +1,11 @@
|
||||
from setuptools import setup
|
||||
|
||||
APP = ['mac_launcher.py']
|
||||
APP = ['mac_native.py']
|
||||
DATA_FILES = []
|
||||
OPTIONS = {
|
||||
'argv_emulation': True,
|
||||
'packages': ['clients'],
|
||||
'includes': ['jinja2', 'requests', 'dotenv'],
|
||||
'includes': ['tkinter','requests', 'dotenv'],
|
||||
'resources': ['templates'],
|
||||
'plist': {
|
||||
'CFBundleName': 'MediaEpisodesViewer',
|
||||
|
||||
Reference in new issue
Block a user