Merge pull request 'vm-manager: add empty list screen' (#1264) from hsjobeki-vm-manager/empty-splash into main
This commit is contained in:
@@ -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 |
@@ -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
|
||||||
|
|||||||
@@ -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())
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user