Merge pull request 'vm-manager: add empty list screen' (#1264) from hsjobeki-vm-manager/empty-splash into main

This commit is contained in:
clan-bot
2024-04-23 14:22:47 +00:00
7 changed files with 109 additions and 4 deletions

View File

@@ -1,3 +1,7 @@
from pathlib import Path from pathlib import Path
loc: Path = Path(__file__).parent loc: Path = Path(__file__).parent
def get_asset(name: str | Path) -> Path:
return loc / name

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, ClassVar, Generic, TypeVar
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,71 @@
import logging
from collections.abc import Callable
from typing import TypeVar
import gi
from clan_vm_manager import assets
gi.require_version("Adw", "1")
from gi.repository import Adw, GdkPixbuf, Gio, GObject, Gtk
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) -> GdkPixbuf.Pixbuf | None:
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,7 +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.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,
@@ -72,17 +74,33 @@ 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)
self.splash = EmptySplash(on_join=lambda x: self.on_join_request(x, x))
def display_splash(self, source: GKVStore) -> None:
print("Displaying splash")
if (
ClanStore.use().clan_store.get_n_items() == 0
and JoinList.use().list_store.get_n_items() == 0
):
self.append(self.splash)
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)
grp = Adw.PreferencesGroup() grp = Adw.PreferencesGroup()
grp.set_title(vm.data.flake.clan_name) grp.set_title(vm.data.flake.clan_name)
grp.set_description(vm.data.flake.flake_url) grp.set_description(vm.data.flake.flake_url)

View File

@@ -1,5 +1,6 @@
import logging import logging
import threading import threading
from collections.abc 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()