Implement the status bar
This commit is contained in:
1 parent
3b2e68757d
commit
7a7b94f57c
2 files changed
+52
-16
No files matched your search
@@ -262,6 +262,20 @@ class LinkLabel(ttk.Label):
|
|||||||
return lambda e: webbrowser.open_new_tab(url)
|
return lambda e: webbrowser.open_new_tab(url)
|
||||||
|
|
||||||
|
|
||||||
|
class StatusBar:
|
||||||
|
def __init__(self, manager: GUIManager, master: ttk.Widget):
|
||||||
|
frame = ttk.LabelFrame(master, text="Status", padding=(4, 0, 4, 4))
|
||||||
|
frame.grid(column=0, row=0, columnspan=3, sticky="nsew", padx=2)
|
||||||
|
self._label = ttk.Label(frame)
|
||||||
|
self._label.grid(column=0, row=0, sticky="nsew")
|
||||||
|
|
||||||
|
def update(self, text: str):
|
||||||
|
self._label.config(text=text)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._label.config(text='')
|
||||||
|
|
||||||
|
|
||||||
class _WSEntry(TypedDict):
|
class _WSEntry(TypedDict):
|
||||||
status: str
|
status: str
|
||||||
topics: int
|
topics: int
|
||||||
@@ -269,10 +283,10 @@ class _WSEntry(TypedDict):
|
|||||||
|
|
||||||
class WebsocketStatus:
|
class WebsocketStatus:
|
||||||
def __init__(self, manager: GUIManager, master: ttk.Widget):
|
def __init__(self, manager: GUIManager, master: ttk.Widget):
|
||||||
self._status_var = StringVar(master)
|
|
||||||
self._topics_var = StringVar(master)
|
|
||||||
frame = ttk.LabelFrame(master, text="Websocket Status", padding=(4, 0, 4, 4))
|
frame = ttk.LabelFrame(master, text="Websocket Status", padding=(4, 0, 4, 4))
|
||||||
frame.grid(column=0, row=0, sticky="nsew", padx=2)
|
frame.grid(column=0, row=1, sticky="nsew", padx=2)
|
||||||
|
self._status_var = StringVar(frame)
|
||||||
|
self._topics_var = StringVar(frame)
|
||||||
ttk.Label(
|
ttk.Label(
|
||||||
frame,
|
frame,
|
||||||
text='\n'.join(f"Websocket #{i}:" for i in range(1, MAX_WEBSOCKETS + 1)),
|
text='\n'.join(f"Websocket #{i}:" for i in range(1, MAX_WEBSOCKETS + 1)),
|
||||||
@@ -334,7 +348,7 @@ class LoginForm:
|
|||||||
self._manager = manager
|
self._manager = manager
|
||||||
self._var = StringVar(master)
|
self._var = StringVar(master)
|
||||||
frame = ttk.LabelFrame(master, text="Login Form", padding=(4, 0, 4, 4))
|
frame = ttk.LabelFrame(master, text="Login Form", padding=(4, 0, 4, 4))
|
||||||
frame.grid(column=1, row=0, sticky="nsew", padx=2)
|
frame.grid(column=1, row=1, sticky="nsew", padx=2)
|
||||||
frame.columnconfigure(0, weight=2)
|
frame.columnconfigure(0, weight=2)
|
||||||
frame.columnconfigure(1, weight=1)
|
frame.columnconfigure(1, weight=1)
|
||||||
frame.rowconfigure(4, weight=1)
|
frame.rowconfigure(4, weight=1)
|
||||||
@@ -425,7 +439,7 @@ class CampaignProgress:
|
|||||||
self._frame = frame = ttk.LabelFrame(
|
self._frame = frame = ttk.LabelFrame(
|
||||||
master, text="Campaign Progress", padding=(4, 0, 4, 4)
|
master, text="Campaign Progress", padding=(4, 0, 4, 4)
|
||||||
)
|
)
|
||||||
frame.grid(column=0, row=1, columnspan=2, sticky="nsew", padx=2)
|
frame.grid(column=0, row=2, columnspan=2, sticky="nsew", padx=2)
|
||||||
frame.columnconfigure(0, weight=2)
|
frame.columnconfigure(0, weight=2)
|
||||||
frame.columnconfigure(1, weight=1)
|
frame.columnconfigure(1, weight=1)
|
||||||
ttk.Label(frame, text="Campaign:").grid(column=0, row=0, columnspan=2)
|
ttk.Label(frame, text="Campaign:").grid(column=0, row=0, columnspan=2)
|
||||||
@@ -542,11 +556,11 @@ class CampaignProgress:
|
|||||||
class ConsoleOutput:
|
class ConsoleOutput:
|
||||||
def __init__(self, manager: GUIManager, master: ttk.Widget):
|
def __init__(self, manager: GUIManager, master: ttk.Widget):
|
||||||
frame = ttk.LabelFrame(master, text="Output", padding=(4, 0, 4, 4))
|
frame = ttk.LabelFrame(master, text="Output", padding=(4, 0, 4, 4))
|
||||||
frame.grid(column=0, row=2, columnspan=2, sticky="nsew", padx=2)
|
frame.grid(column=0, row=3, columnspan=2, sticky="nsew", padx=2)
|
||||||
|
# tell master frame that the containing row can expand
|
||||||
|
master.rowconfigure(3, weight=1)
|
||||||
frame.rowconfigure(0, weight=1) # let the frame expand
|
frame.rowconfigure(0, weight=1) # let the frame expand
|
||||||
frame.columnconfigure(0, weight=1)
|
frame.columnconfigure(0, weight=1)
|
||||||
# tell master frame that the containing row can expand
|
|
||||||
master.rowconfigure(2, weight=1)
|
|
||||||
xscroll = ttk.Scrollbar(frame, orient="horizontal")
|
xscroll = ttk.Scrollbar(frame, orient="horizontal")
|
||||||
yscroll = ttk.Scrollbar(frame, orient="vertical")
|
yscroll = ttk.Scrollbar(frame, orient="vertical")
|
||||||
self._text = tk.Text(
|
self._text = tk.Text(
|
||||||
@@ -582,11 +596,11 @@ class ChannelList:
|
|||||||
def __init__(self, manager: GUIManager, master: ttk.Widget):
|
def __init__(self, manager: GUIManager, master: ttk.Widget):
|
||||||
self._manager = manager
|
self._manager = manager
|
||||||
frame = ttk.LabelFrame(master, text="Channels", padding=(4, 0, 4, 4))
|
frame = ttk.LabelFrame(master, text="Channels", padding=(4, 0, 4, 4))
|
||||||
frame.grid(column=2, row=0, rowspan=3, sticky="nsew", padx=2)
|
frame.grid(column=2, row=1, rowspan=3, sticky="nsew", padx=2)
|
||||||
frame.rowconfigure(1, weight=1)
|
|
||||||
frame.columnconfigure(0, weight=1)
|
|
||||||
# tell master frame that the containing column can expand
|
# tell master frame that the containing column can expand
|
||||||
master.columnconfigure(2, weight=1)
|
master.columnconfigure(2, weight=1)
|
||||||
|
frame.rowconfigure(1, weight=1)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
buttons_frame = ttk.Frame(frame)
|
buttons_frame = ttk.Frame(frame)
|
||||||
self._buttons: _Buttons = {
|
self._buttons: _Buttons = {
|
||||||
"frame": buttons_frame,
|
"frame": buttons_frame,
|
||||||
@@ -911,6 +925,7 @@ class InventoryOverview:
|
|||||||
"expired": IntVar(master, 1),
|
"expired": IntVar(master, 1),
|
||||||
"upcoming": IntVar(master, 1),
|
"upcoming": IntVar(master, 1),
|
||||||
}
|
}
|
||||||
|
# Filtering options
|
||||||
filter_frame = ttk.LabelFrame(master, text="Filter", padding=(4, 0, 4, 4))
|
filter_frame = ttk.LabelFrame(master, text="Filter", padding=(4, 0, 4, 4))
|
||||||
LABEL_SPACING = 20
|
LABEL_SPACING = 20
|
||||||
filter_frame.grid(column=0, row=0, columnspan=2, sticky="nsew")
|
filter_frame.grid(column=0, row=0, columnspan=2, sticky="nsew")
|
||||||
@@ -928,6 +943,7 @@ class InventoryOverview:
|
|||||||
filter_frame, text="Upcoming", padding=(0, 0, LABEL_SPACING, 0)
|
filter_frame, text="Upcoming", padding=(0, 0, LABEL_SPACING, 0)
|
||||||
).grid(column=6, row=0)
|
).grid(column=6, row=0)
|
||||||
ttk.Button(filter_frame, text="Refresh", command=self.refresh).grid(column=7, row=0)
|
ttk.Button(filter_frame, text="Refresh", command=self.refresh).grid(column=7, row=0)
|
||||||
|
# Inventory view
|
||||||
self._canvas = tk.Canvas(master, scrollregion=(0, 0, 0, 0))
|
self._canvas = tk.Canvas(master, scrollregion=(0, 0, 0, 0))
|
||||||
self._canvas.grid(column=0, row=1, sticky="nsew")
|
self._canvas.grid(column=0, row=1, sticky="nsew")
|
||||||
master.rowconfigure(1, weight=1)
|
master.rowconfigure(1, weight=1)
|
||||||
@@ -1546,6 +1562,7 @@ class GUIManager:
|
|||||||
# Main tab
|
# Main tab
|
||||||
main_frame = ttk.Frame(root_frame, padding=8)
|
main_frame = ttk.Frame(root_frame, padding=8)
|
||||||
self.tabs.add_tab(main_frame, name="Main")
|
self.tabs.add_tab(main_frame, name="Main")
|
||||||
|
self.status = StatusBar(self, main_frame)
|
||||||
self.websockets = WebsocketStatus(self, main_frame)
|
self.websockets = WebsocketStatus(self, main_frame)
|
||||||
self.login = LoginForm(self, main_frame)
|
self.login = LoginForm(self, main_frame)
|
||||||
self.progress = CampaignProgress(self, main_frame)
|
self.progress = CampaignProgress(self, main_frame)
|
||||||
|
|||||||
@@ -202,6 +202,7 @@ class Twitch:
|
|||||||
self.change_state(State.INVENTORY_FETCH)
|
self.change_state(State.INVENTORY_FETCH)
|
||||||
while True:
|
while True:
|
||||||
if self._state is State.IDLE:
|
if self._state is State.IDLE:
|
||||||
|
self.gui.status.update("Idle")
|
||||||
self.stop_watching()
|
self.stop_watching()
|
||||||
# clear the flag and wait until it's set again
|
# clear the flag and wait until it's set again
|
||||||
self._state_change.clear()
|
self._state_change.clear()
|
||||||
@@ -244,6 +245,7 @@ class Twitch:
|
|||||||
self.restart_watching()
|
self.restart_watching()
|
||||||
self.change_state(State.CHANNELS_CLEANUP)
|
self.change_state(State.CHANNELS_CLEANUP)
|
||||||
elif self._state is State.CHANNELS_CLEANUP:
|
elif self._state is State.CHANNELS_CLEANUP:
|
||||||
|
self.gui.status.update("Cleaning up channels...")
|
||||||
if not self.games or full_cleanup:
|
if not self.games or full_cleanup:
|
||||||
# no games selected or we're doing full cleanup: remove everything
|
# no games selected or we're doing full cleanup: remove everything
|
||||||
to_remove: list[Channel] = list(channels.values())
|
to_remove: list[Channel] = list(channels.values())
|
||||||
@@ -280,6 +282,7 @@ class Twitch:
|
|||||||
)
|
)
|
||||||
self.change_state(State.IDLE)
|
self.change_state(State.IDLE)
|
||||||
elif self._state is State.CHANNELS_FETCH:
|
elif self._state is State.CHANNELS_FETCH:
|
||||||
|
self.gui.status.update("Gathering channels...")
|
||||||
# start with all current channels
|
# start with all current channels
|
||||||
new_channels: OrderedSet[Channel] = OrderedSet(self.channels.values())
|
new_channels: OrderedSet[Channel] = OrderedSet(self.channels.values())
|
||||||
# gather and add ACL channels from campaigns
|
# gather and add ACL channels from campaigns
|
||||||
@@ -360,6 +363,7 @@ class Twitch:
|
|||||||
break
|
break
|
||||||
self.change_state(State.CHANNEL_SWITCH)
|
self.change_state(State.CHANNEL_SWITCH)
|
||||||
elif self._state is State.CHANNEL_SWITCH:
|
elif self._state is State.CHANNEL_SWITCH:
|
||||||
|
self.gui.status.update("Switching the channel...")
|
||||||
# Change into the selected channel, stay in the watching channel,
|
# Change into the selected channel, stay in the watching channel,
|
||||||
# or select a new channel that meets the required conditions
|
# or select a new channel that meets the required conditions
|
||||||
new_watching = None
|
new_watching = None
|
||||||
@@ -388,9 +392,14 @@ class Twitch:
|
|||||||
# if we have a better switch target - do so
|
# if we have a better switch target - do so
|
||||||
# otherwise, continue watching what we had before
|
# otherwise, continue watching what we had before
|
||||||
self.watch(new_watching)
|
self.watch(new_watching)
|
||||||
|
# should immediately complete, since we'll definitely
|
||||||
|
# have a channel we're watching here
|
||||||
|
watching_channel = await self.watching_channel.get()
|
||||||
|
self.gui.status.update(f"Watching {watching_channel.name}")
|
||||||
# break the state change chain by clearing the flag
|
# break the state change chain by clearing the flag
|
||||||
self._state_change.clear()
|
self._state_change.clear()
|
||||||
elif self._state is State.EXIT:
|
elif self._state is State.EXIT:
|
||||||
|
self.gui.status.update("Exiting...")
|
||||||
# we've been requested to exit the application
|
# we've been requested to exit the application
|
||||||
break
|
break
|
||||||
await self._state_change.wait()
|
await self._state_change.wait()
|
||||||
@@ -908,6 +917,8 @@ class Twitch:
|
|||||||
return DropsCampaign(self, campaign_data, claimed_benefits)
|
return DropsCampaign(self, campaign_data, claimed_benefits)
|
||||||
|
|
||||||
async def fetch_inventory(self) -> None:
|
async def fetch_inventory(self) -> None:
|
||||||
|
status_update = self.gui.status.update
|
||||||
|
status_update("Fetching inventory...")
|
||||||
# fetch in-progress campaigns (inventory)
|
# fetch in-progress campaigns (inventory)
|
||||||
response = await self.gql_request(GQL_OPERATIONS["Inventory"])
|
response = await self.gql_request(GQL_OPERATIONS["Inventory"])
|
||||||
inventory: JsonType = response["data"]["currentUser"]["inventory"]
|
inventory: JsonType = response["data"]["currentUser"]["inventory"]
|
||||||
@@ -934,19 +945,27 @@ class Twitch:
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
# add campaigns that remained, that can be earned but are not in-progress yet
|
# add campaigns that remained, that can be earned but are not in-progress yet
|
||||||
fetched_campaigns: list[DropsCampaign] = await asyncio.gather(
|
status_update(f"Fetching campaigns... (0/{len(available_campaigns)})")
|
||||||
*(
|
fetched_campaigns: list[DropsCampaign] = []
|
||||||
|
for i, coro in enumerate(
|
||||||
|
# specifically use an intermediate list per a Python bug
|
||||||
|
# https://github.com/python/cpython/issues/88342
|
||||||
|
asyncio.as_completed([
|
||||||
self.fetch_campaign(campaign_id, available_data, claimed_benefits)
|
self.fetch_campaign(campaign_id, available_data, claimed_benefits)
|
||||||
for campaign_id, available_data in available_campaigns.items()
|
for campaign_id, available_data in available_campaigns.items()
|
||||||
)
|
]),
|
||||||
)
|
start=1,
|
||||||
|
):
|
||||||
|
fetched_campaigns.append(await coro)
|
||||||
|
status_update(f"Fetching campaigns... ({i}/{len(available_campaigns)})")
|
||||||
campaigns.extend(fetched_campaigns)
|
campaigns.extend(fetched_campaigns)
|
||||||
campaigns.sort(key=lambda c: c.ends_at)
|
campaigns.sort(key=lambda c: c.ends_at)
|
||||||
campaigns.sort(key=lambda c: not c.linked)
|
campaigns.sort(key=lambda c: not c.linked)
|
||||||
self._drops.clear()
|
self._drops.clear()
|
||||||
self.gui.inv.clear()
|
self.gui.inv.clear()
|
||||||
self.inventory.clear()
|
self.inventory.clear()
|
||||||
for campaign in campaigns:
|
for i, campaign in enumerate(campaigns, start=1):
|
||||||
|
status_update(f"Adding campaigns to inventory... ({i}/{len(campaigns)})")
|
||||||
self._drops.update({drop.id: drop for drop in campaign.drops})
|
self._drops.update({drop.id: drop for drop in campaign.drops})
|
||||||
await self.gui.inv.add_campaign(campaign)
|
await self.gui.inv.add_campaign(campaign)
|
||||||
self.inventory.append(campaign)
|
self.inventory.append(campaign)
|
||||||
|
|||||||
Reference in new issue
Block a user