1
0
forked from clan/clan-core

Merge pull request 'add join to list' (#752) from hsjobeki-main into main

This commit is contained in:
clan-bot 2024-01-20 12:21:23 +00:00
commit c98fdc08de
10 changed files with 229 additions and 63 deletions

View File

@ -117,6 +117,11 @@ class ClanURI:
def get_full_uri(self) -> str:
return self._full_uri
# TODO(@Qubasa): return a comparable id e.g. f"{url}#{attr}"
# This should be our standard.
def get_id(self) -> str:
return f"{self._components.path}#{self._components.fragment}"
@classmethod
def from_path(
cls, # noqa

View File

@ -28,7 +28,7 @@ def main() -> None:
def show_join(args: argparse.Namespace) -> None:
app = MainApplication(
config=ClanConfig(url=args.clan_uri, initial_view="join.trust"),
config=ClanConfig(url=args.clan_uri, initial_view="list"),
)
return app.run()

View File

@ -3,6 +3,8 @@ from pathlib import Path
import gi
from clan_vm_manager.models.use_join import Join
gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")
@ -23,6 +25,9 @@ class MainApplication(Adw.Application):
self.config = config
self.connect("shutdown", self.on_shutdown)
if config.url:
Join.use().push(config.url)
def on_shutdown(self, app: Gtk.Application) -> None:
print("Shutting down")
VMS.use().kill_all()

View File

@ -7,11 +7,6 @@ from clan_cli.clan_uri import ClanURI
gi.require_version("Gtk", "4.0")
@dataclass
class InitialJoinValues:
url: ClanURI | None
@dataclass
class ClanConfig:
initial_view: str

View File

@ -0,0 +1,73 @@
from collections.abc import Callable
from typing import Any
import gi
from clan_cli import ClanError
from clan_cli.clan_uri import ClanURI
from clan_cli.history.add import HistoryEntry, add_history
from clan_vm_manager.errors.show_error import show_error_dialog
gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")
from gi.repository import Gio, GObject
class JoinValue(GObject.Object):
# TODO: custom signals for async join
# __gsignals__: ClassVar = {}
url: ClanURI
def __init__(self, url: ClanURI) -> None:
super().__init__()
self.url = url
class Join:
"""
This is a singleton.
It is initialized with the first call of use()
"""
_instance: "None | Join" = None
list_store: Gio.ListStore
# Make sure the VMS class is used as a singleton
def __init__(self) -> None:
raise RuntimeError("Call use() instead")
@classmethod
def use(cls: Any) -> "Join":
if cls._instance is None:
print("Creating new instance")
cls._instance = cls.__new__(cls)
cls.list_store = Gio.ListStore.new(JoinValue)
return cls._instance
def push(self, url: ClanURI) -> None:
"""
Add a join request.
This method can add multiple join requests if called subsequently for each request.
"""
self.list_store.append(JoinValue(url))
def join(self, item: JoinValue, cb: Callable[[list[HistoryEntry]], None]) -> None:
# TODO: remove the item that was accepted join from this list
# and call the success function. (The caller is responsible for handling the success)
try:
print(f"trying to join: {item.url}")
history = add_history(item.url)
cb(history)
self.discard(item)
except ClanError as e:
show_error_dialog(e)
pass
def discard(self, item: JoinValue) -> None:
(has, idx) = self.list_store.find(item)
if has:
self.list_store.remove(idx)

View File

@ -142,6 +142,11 @@ class VMS:
for vm in self.get_running_vms():
vm.stop()
def refresh(self) -> None:
self.list_store.remove_all()
for vm in get_initial_vms():
self.list_store.append(vm)
def get_initial_vms() -> list[VM]:
vm_list = []

View File

@ -12,5 +12,20 @@ avatar {
}
.trust {
padding: 25px;
}
padding-top: 25px;
padding-bottom: 25px;
}
.vm-list {
margin-top: 25px;
margin-bottom: 25px;
}
.no-shadow {
box-shadow: none;
}
/* TODO: Disable shadow for empty lists */
/* list:empty {
box-shadow: none;
} */

View File

@ -1,13 +1,29 @@
from collections.abc import Callable
from functools import partial
import gi
from clan_cli.history.add import HistoryEntry
from clan_vm_manager.models.use_join import Join, JoinValue
gi.require_version("Adw", "1")
from gi.repository import Adw, Gdk, Gtk
from gi.repository import Adw, Gdk, Gio, GObject, Gtk
from clan_vm_manager.models.use_vms import VM, VMS
def create_boxed_list(
model: Gio.ListStore, render_row: Callable[[Gtk.ListBox, GObject], Gtk.Widget]
) -> Gtk.ListBox:
boxed_list = Gtk.ListBox()
boxed_list.set_selection_mode(Gtk.SelectionMode.NONE)
boxed_list.add_css_class("boxed-list")
boxed_list.add_css_class("no-shadow")
boxed_list.bind_model(model, create_widget_func=partial(render_row, boxed_list))
return boxed_list
class ClanList(Gtk.Box):
"""
The ClanList
@ -25,52 +41,96 @@ class ClanList(Gtk.Box):
def __init__(self) -> None:
super().__init__(orientation=Gtk.Orientation.VERTICAL)
boxed_list = Gtk.ListBox()
boxed_list.set_selection_mode(Gtk.SelectionMode.NONE)
boxed_list.add_css_class("boxed-list")
def create_widget(item: VM) -> Gtk.Widget:
flake = item.data.flake
row = Adw.ActionRow()
print("Creating", item.data.flake.flake_attr)
# Title
row.set_title(flake.clan_name)
row.set_title_lines(1)
row.set_title_selectable(True)
# Subtitle
row.set_subtitle(flake.flake_attr)
row.set_subtitle_lines(1)
# Avatar
avatar = Adw.Avatar()
avatar.set_custom_image(Gdk.Texture.new_from_filename(flake.icon))
avatar.set_text(flake.clan_name + " " + flake.flake_attr)
avatar.set_show_initials(True)
avatar.set_size(50)
row.add_prefix(avatar)
# Switch
switch = Gtk.Switch()
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
box.set_valign(Gtk.Align.CENTER)
box.append(switch)
switch.connect("notify::active", partial(self.on_row_toggle, item))
row.add_suffix(box)
return row
vms = VMS.use()
join = Join.use()
# TODO: Move this up to create_widget and connect every VM signal to its corresponding switch
vms.handle_vm_stopped(self.stopped_vm)
vms.handle_vm_started(self.started_vm)
boxed_list.bind_model(vms.list_store, create_widget_func=create_widget)
self.join_boxed_list = create_boxed_list(
model=join.list_store, render_row=self.render_join_row
)
self.append(boxed_list)
self.vm_boxed_list = create_boxed_list(
model=vms.list_store, render_row=self.render_vm_row
)
self.vm_boxed_list.add_css_class("vm-list")
self.append(self.join_boxed_list)
self.append(self.vm_boxed_list)
def render_vm_row(self, boxed_list: Gtk.ListBox, item: VM) -> Gtk.Widget:
if boxed_list.has_css_class("no-shadow"):
boxed_list.remove_css_class("no-shadow")
flake = item.data.flake
row = Adw.ActionRow()
print("Creating", item.data.flake.flake_attr)
# Title
row.set_title(flake.clan_name)
row.set_title_lines(1)
row.set_title_selectable(True)
# Subtitle
row.set_subtitle(flake.flake_attr)
row.set_subtitle_lines(1)
# Avatar
avatar = Adw.Avatar()
avatar.set_custom_image(Gdk.Texture.new_from_filename(flake.icon))
avatar.set_text(flake.clan_name + " " + flake.flake_attr)
avatar.set_show_initials(True)
avatar.set_size(50)
row.add_prefix(avatar)
# Switch
switch = Gtk.Switch()
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
box.set_valign(Gtk.Align.CENTER)
box.append(switch)
switch.connect("notify::active", partial(self.on_row_toggle, item))
row.add_suffix(box)
return row
def render_join_row(self, boxed_list: Gtk.ListBox, item: JoinValue) -> Gtk.Widget:
if boxed_list.has_css_class("no-shadow"):
boxed_list.remove_css_class("no-shadow")
row = Adw.ActionRow()
row.set_title(str(item.url))
row.add_css_class("trust")
# TODO: figure out how to detect that
if True:
row.set_subtitle("Clan already exists. Joining again will update it")
avatar = Adw.Avatar()
avatar.set_text(str(item.url))
avatar.set_show_initials(True)
avatar.set_size(50)
row.add_prefix(avatar)
cancel_button = Gtk.Button(label="Cancel")
cancel_button.add_css_class("error")
cancel_button.connect("clicked", partial(self.on_discard_clicked, item))
trust_button = Gtk.Button(label="Join")
trust_button.add_css_class("success")
trust_button.connect("clicked", partial(self.on_trust_clicked, item))
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
box.set_valign(Gtk.Align.CENTER)
box.append(cancel_button)
box.append(trust_button)
row.add_suffix(box)
return row
def started_vm(self, vm: VM, _vm: VM) -> None:
print("VM started", vm.data.flake.flake_attr)
@ -89,6 +149,21 @@ class ClanList(Gtk.Box):
dialog.run()
dialog.destroy()
def on_trust_clicked(self, item: JoinValue, widget: Gtk.Widget) -> None:
def on_join(_history: list[HistoryEntry]) -> None:
VMS.use().refresh()
Join.use().join(item, cb=on_join)
# If the join request list is empty disable the shadow artefact
if not Join.use().list_store.get_n_items():
self.join_boxed_list.add_css_class("no-shadow")
def on_discard_clicked(self, item: JoinValue, widget: Gtk.Widget) -> None:
Join.use().discard(item)
if not Join.use().list_store.get_n_items():
self.join_boxed_list.add_css_class("no-shadow")
def on_row_toggle(self, vm: VM, row: Adw.SwitchRow, state: bool) -> None:
print("Toggled", vm.data.flake.flake_attr, "active:", row.get_active())

View File

@ -1,12 +1,11 @@
from functools import partial
import gi
from clan_cli.clan_uri import ClanURI
from clan_cli.errors import ClanError
from clan_cli.history.add import add_history
from clan_vm_manager.errors.show_error import show_error_dialog
from clan_vm_manager.models.interfaces import InitialJoinValues
from clan_vm_manager.models.use_join import JoinValue
gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")
@ -16,9 +15,9 @@ from gi.repository import Adw, Gio, GObject, Gtk
class TrustValues(GObject.Object):
data: InitialJoinValues
data: JoinValue
def __init__(self, data: InitialJoinValues) -> None:
def __init__(self, data: JoinValue) -> None:
super().__init__()
print("TrustValues", data)
self.data = data
@ -27,13 +26,11 @@ class TrustValues(GObject.Object):
class Trust(Gtk.Box):
def __init__(
self,
*,
initial_values: InitialJoinValues,
) -> None:
super().__init__(orientation=Gtk.Orientation.VERTICAL)
# self.on_trust = on_trust
self.url: ClanURI | None = initial_values.url
# self.url: ClanURI | None = Join.use().
def render(item: TrustValues) -> Gtk.Widget:
row = Adw.ActionRow()
@ -49,7 +46,7 @@ class Trust(Gtk.Box):
cancel_button = Gtk.Button(label="Cancel")
cancel_button.add_css_class("error")
trust_button = Gtk.Button(label="Trust")
trust_button = Gtk.Button(label="Join")
trust_button.add_css_class("success")
trust_button.connect("clicked", partial(self.on_trust_clicked, item.data))
@ -68,7 +65,7 @@ class Trust(Gtk.Box):
boxed_list.add_css_class("boxed-list")
list_store = Gio.ListStore.new(TrustValues)
list_store.append(TrustValues(data=initial_values))
# list_store.append(TrustValues(data=initial_values))
# icon = Gtk.Image.new_from_pixbuf(
# GdkPixbuf.Pixbuf.new_from_file_at_scale(
@ -117,7 +114,7 @@ class Trust(Gtk.Box):
# trust_button.connect("clicked", self.on_trust_clicked)
# layout.append(trust_button)
def on_trust_clicked(self, item: InitialJoinValues, widget: Gtk.Widget) -> None:
def on_trust_clicked(self, item: JoinValue, widget: Gtk.Widget) -> None:
try:
uri = item.url
# or ClanURI(self.entry.get_text())

View File

@ -1,9 +1,8 @@
import gi
from clan_vm_manager.models.interfaces import ClanConfig, InitialJoinValues
from clan_vm_manager.models.interfaces import ClanConfig
from clan_vm_manager.models.use_views import Views
from clan_vm_manager.views.list import ClanList
from clan_vm_manager.views.trust_join import Trust
gi.require_version("Adw", "1")
@ -25,9 +24,6 @@ class MainWindow(Adw.ApplicationWindow):
# Initialize all views
stack_view = Views.use().view
stack_view.add_named(ClanList(), "list")
stack_view.add_named(
Trust(initial_values=InitialJoinValues(url=config.url)), "join.trust"
)
stack_view.set_visible_child_name(config.initial_view)