vm-manager: add empty list screen

This commit is contained in:
Johannes Kirschbauer
2024-04-23 16:16:48 +02:00
parent 51a32dd5c6
commit ec17bfb4ba
6 changed files with 100 additions and 31 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

View File

@@ -1,6 +1,6 @@
import logging import logging
from collections.abc import Callable from collections.abc import Callable
from typing import Any, Generic, TypeVar from typing import Any, Generic, TypeVar, ClassVar
import gi import gi
@@ -24,6 +24,10 @@ class GKVStore(GObject.GObject, Gio.ListModel, Generic[K, V]):
This class could be optimized by having the objects remember their position in the list. This class could be optimized by having the objects remember their position in the list.
""" """
__gsignals__: ClassVar = {
"is_ready": (GObject.SignalFlags.RUN_FIRST, None, []),
}
def __init__(self, gtype: type[V], key_gen: Callable[[V], K]) -> None: def __init__(self, gtype: type[V], key_gen: Callable[[V], K]) -> None:
super().__init__() super().__init__()
self.gtype = gtype self.gtype = gtype

View File

@@ -0,0 +1,70 @@
import logging
from typing import Callable, Optional, TypeVar
import gi
from clan_vm_manager import assets
gi.require_version("Adw", "1")
from gi.repository import Adw, Gio, GObject, Gtk, GdkPixbuf
log = logging.getLogger(__name__)
ListItem = TypeVar("ListItem", bound=GObject.Object)
CustomStore = TypeVar("CustomStore", bound=Gio.ListModel)
class EmptySplash(Gtk.Box):
def __init__(self, on_join: Callable[[str], None]) -> None:
super().__init__(orientation=Gtk.Orientation.VERTICAL)
self.on_join = on_join
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
clan_icon = self.load_image(str(assets.get_asset("clan_black_notext.png")))
if clan_icon:
image = Gtk.Image.new_from_pixbuf(clan_icon)
else:
image = Gtk.Image.new_from_icon_name("image-missing")
# same as the clamp
image.set_pixel_size(400)
image.set_opacity(0.5)
image.set_margin_top(20)
image.set_margin_bottom(10)
vbox.append(image)
empty_label = Gtk.Label(label="Welcome to Clan! Join your first clan.")
join_entry = Gtk.Entry()
join_entry.set_placeholder_text("clan://<url>")
join_entry.set_hexpand(True)
join_button = Gtk.Button(label="Join")
join_button.connect("clicked", self._on_join, join_entry)
clamp = Adw.Clamp()
clamp.set_maximum_size(400)
vbox.append(empty_label)
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
hbox.append(join_entry)
hbox.append(join_button)
vbox.append(hbox)
clamp.set_child(vbox)
self.append(clamp)
def load_image(self, file_path: str) -> Optional[GdkPixbuf.Pixbuf]:
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file(file_path)
return pixbuf
except Exception as e:
log.error(f"Failed to load image: {e}")
return None
def _on_join(self, button: Gtk.Button, entry: Gtk.Entry) -> None:
"""
Callback for the join button
Extracts the text from the entry and calls the on_join callback
"""
log.info(f"Splash screen: Joining {entry.get_text()}")
self.on_join(entry.get_text())

View File

@@ -7,8 +7,9 @@ from typing import Any, TypeVar
import gi import gi
from clan_cli.clan_uri import ClanURI from clan_cli.clan_uri import ClanURI
from clan_vm_manager import assets from clan_vm_manager.components.gkvstore import GKVStore
from clan_vm_manager.components.interfaces import ClanConfig from clan_vm_manager.components.interfaces import ClanConfig
from clan_vm_manager.components.list_splash import EmptySplash
from clan_vm_manager.components.vmobj import VMObject from clan_vm_manager.components.vmobj import VMObject
from clan_vm_manager.singletons.toast import ( from clan_vm_manager.singletons.toast import (
LogToast, LogToast,
@@ -73,43 +74,29 @@ class ClanList(Gtk.Box):
self.join_boxed_list.add_css_class("join-list") self.join_boxed_list.add_css_class("join-list")
self.append(self.join_boxed_list) self.append(self.join_boxed_list)
clan_store = ClanStore.use().clan_store
clan_store.connect("is_ready", self.display_splash)
self.group_list = create_boxed_list( self.group_list = create_boxed_list(
model=ClanStore.use().clan_store, render_row=self.render_group_row model=clan_store, render_row=self.render_group_row
) )
self.group_list.add_css_class("group-list") self.group_list.add_css_class("group-list")
self.append(self.group_list) self.append(self.group_list)
# LIST SPLASH self.splash = EmptySplash(on_join=lambda x: self.on_join_request(x, x))
clan_icon = assets.get_asset("clan.svg")
if not icon_path: def display_splash(self, source: GKVStore) -> None:
return ico_buffer print("Displaying splash")
if (
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_path, icon_size, icon_size) ClanStore.use().clan_store.get_n_items() == 0
and JoinList.use().list_store.get_n_items() == 0
empty_label = Gtk.Label(label="Welcome to Clan! Join your first clan.") ):
join_entry = Gtk.Entry() self.append(self.splash)
join_entry.set_placeholder_text("clan://<url>")
join_entry.set_hexpand(True)
join_button = Gtk.Button(label="Join")
join_button.connect("clicked", lambda x: (), join_entry)
clamp = Adw.Clamp()
clamp.set_maximum_size(400)
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
vbox.append(empty_label)
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
hbox.append(join_entry)
hbox.append(join_button)
vbox.append(hbox)
clamp.set_child(vbox)
self.append(clamp)
def render_group_row( def render_group_row(
self, boxed_list: Gtk.ListBox, vm_store: VMStore self, boxed_list: Gtk.ListBox, vm_store: VMStore
) -> Gtk.Widget: ) -> Gtk.Widget:
self.remove(self.splash)
vm = vm_store.first() vm = vm_store.first()
log.debug("Rendering group row for %s", vm.data.flake.flake_url) log.debug("Rendering group row for %s", vm.data.flake.flake_url)

View File

@@ -1,5 +1,6 @@
import logging import logging
import threading import threading
from typing import Callable
import gi import gi
from clan_cli.history.list import list_history from clan_cli.history.list import list_history
@@ -41,7 +42,9 @@ class MainWindow(Adw.ApplicationWindow):
self.tray_icon: TrayIcon = TrayIcon(app) self.tray_icon: TrayIcon = TrayIcon(app)
# Initialize all ClanStore # Initialize all ClanStore
threading.Thread(target=self._populate_vms).start() threading.Thread(
target=self._populate_vms, args=[self._set_clan_store_ready]
).start()
# Initialize all views # Initialize all views
stack_view = ViewStack.use().view stack_view = ViewStack.use().view
@@ -65,12 +68,17 @@ class MainWindow(Adw.ApplicationWindow):
self.connect("destroy", self.on_destroy) self.connect("destroy", self.on_destroy)
def _populate_vms(self) -> None: def _set_clan_store_ready(self) -> None:
ClanStore.use().clan_store.emit("is_ready")
def _populate_vms(self, done: Callable[[], None]) -> None:
# Execute `clan flakes add <path>` to democlan for this to work # Execute `clan flakes add <path>` to democlan for this to work
# TODO: Make list_history a generator function # TODO: Make list_history a generator function
for entry in list_history(): for entry in list_history():
GLib.idle_add(ClanStore.use().create_vm_task, entry) GLib.idle_add(ClanStore.use().create_vm_task, entry)
GLib.idle_add(done)
def kill_vms(self) -> None: def kill_vms(self) -> None:
log.debug("Killing all VMs") log.debug("Killing all VMs")
ClanStore.use().kill_all() ClanStore.use().kill_all()