diff --git a/data/com.vixalien.muzika.data.gresource.xml b/data/com.vixalien.muzika.data.gresource.xml
index 8cf1de99..feddf39f 100644
--- a/data/com.vixalien.muzika.data.gresource.xml
+++ b/data/com.vixalien.muzika.data.gresource.xml
@@ -86,6 +86,7 @@
icons/scalable/actions/playlist2-symbolic.svg
icons/scalable/actions/profit-symbolic.svg
icons/scalable/actions/progress-symbolic.svg
+ icons/scalable/actions/refresh-symbolic.svg
icons/scalable/actions/sentiment-satisfied-symbolic.svg
icons/scalable/actions/skip-backwards-10-symbolic.svg
icons/scalable/actions/skip-forward-10-symbolic.svg
diff --git a/data/icons/scalable/actions/refresh-symbolic.svg b/data/icons/scalable/actions/refresh-symbolic.svg
new file mode 100644
index 00000000..485f106a
--- /dev/null
+++ b/data/icons/scalable/actions/refresh-symbolic.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/data/ui/components/library/history.blp b/data/ui/components/library/history.blp
index dce8bf4a..1bb1b455 100644
--- a/data/ui/components/library/history.blp
+++ b/data/ui/components/library/history.blp
@@ -4,7 +4,13 @@ using Adw 1;
template $HistoryPage : Adw.Bin {
Adw.ToolbarView {
[top]
- Adw.HeaderBar {}
+ Adw.HeaderBar {
+ [start]
+ Button {
+ icon-name: "refresh";
+ action-name: "navigator.reload";
+ }
+ }
content: Adw.BreakpointBin {
width-request: 200;
diff --git a/data/ui/components/library/songs.blp b/data/ui/components/library/songs.blp
index c1357d3c..40c50661 100644
--- a/data/ui/components/library/songs.blp
+++ b/data/ui/components/library/songs.blp
@@ -4,7 +4,13 @@ using Adw 1;
template $LibrarySongsPage : Adw.Bin {
Adw.ToolbarView {
[top]
- Adw.HeaderBar {}
+ Adw.HeaderBar {
+ [start]
+ Button {
+ icon-name: "refresh";
+ action-name: "navigator.reload";
+ }
+ }
content: Adw.BreakpointBin {
width-request: 200;
diff --git a/data/ui/components/nav/page.blp b/data/ui/components/nav/page.blp
index 45bd5cb7..9e7da09f 100644
--- a/data/ui/components/nav/page.blp
+++ b/data/ui/components/nav/page.blp
@@ -4,6 +4,7 @@ using Adw 1;
template $Page : $AdwNavigationPage {
Stack stack {
vhomogeneous: false;
+ transition-type: crossfade;
Adw.Bin loading {
Adw.ToolbarView {
diff --git a/data/ui/pages/album.blp b/data/ui/pages/album.blp
index efd10b5e..4f1fde2a 100644
--- a/data/ui/pages/album.blp
+++ b/data/ui/pages/album.blp
@@ -5,6 +5,12 @@ template $AlbumPage : Adw.Bin {
Adw.ToolbarView {
[top]
Adw.HeaderBar {
+ [start]
+ Button {
+ icon-name: "refresh";
+ action-name: "navigator.reload";
+ }
+
[end]
MenuButton menu {
icon-name: "view-more-symbolic";
diff --git a/data/ui/pages/artist-albums.blp b/data/ui/pages/artist-albums.blp
index 84eac13c..2189748f 100644
--- a/data/ui/pages/artist-albums.blp
+++ b/data/ui/pages/artist-albums.blp
@@ -4,7 +4,13 @@ using Adw 1;
template $ArtistAlbumsPage : Adw.Bin {
Adw.ToolbarView {
[top]
- Adw.HeaderBar {}
+ Adw.HeaderBar {
+ [start]
+ Button {
+ icon-name: "refresh";
+ action-name: "navigator.reload";
+ }
+ }
content: ScrolledWindow scrolled {
hexpand: true;
diff --git a/data/ui/pages/artist.blp b/data/ui/pages/artist.blp
index 9105d032..f4f569be 100644
--- a/data/ui/pages/artist.blp
+++ b/data/ui/pages/artist.blp
@@ -5,6 +5,12 @@ template $ArtistPage : Adw.Bin {
Adw.ToolbarView {
[top]
Adw.HeaderBar {
+ [start]
+ Button {
+ icon-name: "refresh";
+ action-name: "navigator.reload";
+ }
+
[end]
MenuButton menu {
icon-name: "view-more-symbolic";
diff --git a/data/ui/pages/authentication-error.blp b/data/ui/pages/authentication-error.blp
index d17013be..25239103 100644
--- a/data/ui/pages/authentication-error.blp
+++ b/data/ui/pages/authentication-error.blp
@@ -1,74 +1,85 @@
using Gtk 4.0;
using Adw 1;
-template $AuthenticationErrorPage : Box {
- Adw.StatusPage status {
- hexpand: true;
- icon-name: "system-lock-screen-symbolic";
- title: _("Authentication is required");
+template $AuthenticationErrorPage : Adw.Bin {
+ Adw.ToolbarView {
+ [top]
+ Adw.HeaderBar {
+ [start]
+ Button {
+ icon-name: "refresh";
+ action-name: "navigator.reload";
+ }
+ }
- Box more {
- orientation: vertical;
- spacing: 6;
+ Adw.StatusPage status {
+ hexpand: true;
+ icon-name: 'system-lock-screen-symbolic';
+ title: _('Authentication is required');
- Box buttons {
- halign: center;
- margin-bottom: 12;
+ Box more {
+ orientation: vertical;
spacing: 6;
- Button {
- label: _("Log in");
- action-name: "win.login";
+ Box buttons {
+ halign: center;
+ margin-bottom: 12;
+ spacing: 6;
- styles [
- "pill",
- "suggested-action",
- ]
- }
+ Button {
+ label: _('Log in');
+ action-name: 'win.login';
- Button home_button {
- label: _("Go to Home");
-
- styles [
- "pill",
- ]
- }
- }
+ styles [
+ "pill",
+ "suggested-action",
+ ]
+ }
- Expander expander {
- label: _("Error Details");
- halign: center;
- }
+ Button home_button {
+ label: _('Go to Home');
- Revealer {
- reveal-child: bind expander.expanded;
+ styles [
+ "pill",
+ ]
+ }
+ }
- Adw.Clamp {
- maximum-size: 1000;
- tightening-threshold: 600;
+ Expander expander {
+ label: _('Error Details');
+ halign: center;
+ }
- Box {
- margin-top: 6;
- margin-bottom: 6;
- margin-end: 6;
- margin-start: 6;
+ Revealer {
+ reveal-child: bind-property expander.expanded;
- styles [
- "card",
- ]
+ Adw.Clamp {
+ maximum-size: 1000;
+ tightening-threshold: 600;
- TextView text_view {
- hexpand: true;
- top-margin: 12;
- bottom-margin: 12;
- left-margin: 12;
- right-margin: 12;
- wrap-mode: word_char;
- editable: false;
+ Box {
+ margin-top: 6;
+ margin-bottom: 6;
+ margin-end: 6;
+ margin-start: 6;
styles [
- "transparent",
+ "card",
]
+
+ TextView text_view {
+ hexpand: true;
+ top-margin: 12;
+ bottom-margin: 12;
+ left-margin: 12;
+ right-margin: 12;
+ wrap-mode: word_char;
+ editable: false;
+
+ styles [
+ "transparent",
+ ]
+ }
}
}
}
diff --git a/data/ui/pages/channel-playlists.blp b/data/ui/pages/channel-playlists.blp
index e3da460d..f7f33667 100644
--- a/data/ui/pages/channel-playlists.blp
+++ b/data/ui/pages/channel-playlists.blp
@@ -4,7 +4,13 @@ using Adw 1;
template $ChannelPlaylistsPage : Adw.Bin {
Adw.ToolbarView {
[top]
- Adw.HeaderBar {}
+ Adw.HeaderBar {
+ [start]
+ Button {
+ icon-name: "refresh";
+ action-name: "navigator.reload";
+ }
+ }
content: ScrolledWindow scrolled {
hexpand: true;
diff --git a/data/ui/pages/channel.blp b/data/ui/pages/channel.blp
index 559136b2..1e99d74d 100644
--- a/data/ui/pages/channel.blp
+++ b/data/ui/pages/channel.blp
@@ -5,6 +5,12 @@ template $ChannelPage : Adw.Bin {
Adw.ToolbarView {
[top]
Adw.HeaderBar {
+ [start]
+ Button {
+ icon-name: "refresh";
+ action-name: "navigator.reload";
+ }
+
[end]
MenuButton menu {
icon-name: "view-more-symbolic";
diff --git a/data/ui/pages/charts.blp b/data/ui/pages/charts.blp
index 10755483..e3b13dff 100644
--- a/data/ui/pages/charts.blp
+++ b/data/ui/pages/charts.blp
@@ -4,7 +4,13 @@ using Adw 1;
template $ChartsPage : Adw.Bin {
Adw.ToolbarView {
[top]
- Adw.HeaderBar {}
+ Adw.HeaderBar {
+ [start]
+ Button {
+ icon-name: "refresh";
+ action-name: "navigator.reload";
+ }
+ }
content: ScrolledWindow scrolled {
vexpand: true;
diff --git a/data/ui/pages/error.blp b/data/ui/pages/error.blp
index 4def4b13..b430403a 100644
--- a/data/ui/pages/error.blp
+++ b/data/ui/pages/error.blp
@@ -1,51 +1,62 @@
using Gtk 4.0;
using Adw 1;
-template $ErrorPage : Box {
- Adw.StatusPage status {
- hexpand: true;
- icon-name: "dialog-question-symbolic";
- title: _("An error occurred");
- description: "";
-
- Box more {
- orientation: vertical;
- spacing: 6;
-
- Expander expander {
- label: _("Error Details");
- halign: center;
+template $ErrorPage : Adw.Bin {
+ Adw.ToolbarView {
+ [top]
+ Adw.HeaderBar {
+ [start]
+ Button {
+ icon-name: "refresh";
+ action-name: "navigator.reload";
}
+ }
+
+ Adw.StatusPage status {
+ hexpand: true;
+ icon-name: 'dialog-question-symbolic';
+ title: _('An error occurred');
+ description: '';
- Revealer {
- reveal-child: bind expander.expanded;
+ Box more {
+ orientation: vertical;
+ spacing: 6;
- Adw.Clamp {
- maximum-size: 1000;
- tightening-threshold: 600;
+ Expander expander {
+ label: _('Error Details');
+ halign: center;
+ }
- Box {
- margin-top: 6;
- margin-bottom: 6;
- margin-end: 6;
- margin-start: 6;
+ Revealer {
+ reveal-child: bind expander.expanded;
- styles [
- "card",
- ]
+ Adw.Clamp {
+ maximum-size: 1000;
+ tightening-threshold: 600;
- TextView text_view {
- hexpand: true;
- top-margin: 12;
- bottom-margin: 12;
- left-margin: 12;
- right-margin: 12;
- wrap-mode: word_char;
- editable: false;
+ Box {
+ margin-top: 6;
+ margin-bottom: 6;
+ margin-end: 6;
+ margin-start: 6;
styles [
- "transparent",
+ "card",
]
+
+ TextView text_view {
+ hexpand: true;
+ top-margin: 12;
+ bottom-margin: 12;
+ left-margin: 12;
+ right-margin: 12;
+ wrap-mode: word_char;
+ editable: false;
+
+ styles [
+ "transparent",
+ ]
+ }
}
}
}
diff --git a/data/ui/pages/explore.blp b/data/ui/pages/explore.blp
index 61de58cf..05f766e6 100644
--- a/data/ui/pages/explore.blp
+++ b/data/ui/pages/explore.blp
@@ -4,7 +4,13 @@ using Adw 1;
template $ExplorePage : Adw.Bin {
Adw.ToolbarView {
[top]
- Adw.HeaderBar {}
+ Adw.HeaderBar {
+ [start]
+ Button {
+ icon-name: "refresh";
+ action-name: "navigator.reload";
+ }
+ }
content: ScrolledWindow scrolled {
vexpand: true;
diff --git a/data/ui/pages/home.blp b/data/ui/pages/home.blp
index 3583b413..07da0033 100644
--- a/data/ui/pages/home.blp
+++ b/data/ui/pages/home.blp
@@ -4,7 +4,13 @@ using Adw 1;
template $HomePage : Adw.Bin {
Adw.ToolbarView {
[top]
- Adw.HeaderBar {}
+ Adw.HeaderBar {
+ [start]
+ Button {
+ icon-name: "refresh";
+ action-name: "navigator.reload";
+ }
+ }
content: ScrolledWindow scrolled {
vexpand: true;
diff --git a/data/ui/pages/login.blp b/data/ui/pages/login.blp
index b70e3ff3..94afb54a 100644
--- a/data/ui/pages/login.blp
+++ b/data/ui/pages/login.blp
@@ -97,6 +97,14 @@ template $LoginPage : Adw.Window {
}
}
}
+
+ Button {
+ label: _("Get a new code");
+ halign: center;
+ clicked => $refresh_cb();
+
+ styles ["pill"]
+ }
}
}
}
diff --git a/data/ui/pages/mood-playlists.blp b/data/ui/pages/mood-playlists.blp
index daf0ea69..e262aab2 100644
--- a/data/ui/pages/mood-playlists.blp
+++ b/data/ui/pages/mood-playlists.blp
@@ -4,7 +4,13 @@ using Adw 1;
template $MoodPlaylistsPage : Adw.Bin {
Adw.ToolbarView {
[top]
- Adw.HeaderBar {}
+ Adw.HeaderBar {
+ [start]
+ Button {
+ icon-name: "refresh";
+ action-name: "navigator.reload";
+ }
+ }
content: ScrolledWindow scrolled {
vexpand: true;
diff --git a/data/ui/pages/moods.blp b/data/ui/pages/moods.blp
index 27b24846..53346c3f 100644
--- a/data/ui/pages/moods.blp
+++ b/data/ui/pages/moods.blp
@@ -4,7 +4,13 @@ using Adw 1;
template $MoodsPage : Adw.Bin {
Adw.ToolbarView {
[top]
- Adw.HeaderBar {}
+ Adw.HeaderBar {
+ [start]
+ Button {
+ icon-name: "refresh";
+ action-name: "navigator.reload";
+ }
+ }
content: ScrolledWindow scrolled {
vexpand: true;
diff --git a/data/ui/pages/new-releases.blp b/data/ui/pages/new-releases.blp
index 54efdd59..8b1215fc 100644
--- a/data/ui/pages/new-releases.blp
+++ b/data/ui/pages/new-releases.blp
@@ -4,7 +4,13 @@ using Adw 1;
template $NewReleasesPage : Adw.Bin {
Adw.ToolbarView {
[top]
- Adw.HeaderBar {}
+ Adw.HeaderBar {
+ [start]
+ Button {
+ icon-name: "refresh";
+ action-name: "navigator.reload";
+ }
+ }
content: ScrolledWindow scrolled {
vexpand: true;
diff --git a/data/ui/pages/playlist.blp b/data/ui/pages/playlist.blp
index f7eb9827..a9413389 100644
--- a/data/ui/pages/playlist.blp
+++ b/data/ui/pages/playlist.blp
@@ -8,14 +8,20 @@ template $PlaylistPage : Adw.Bin {
[top]
Adw.HeaderBar {
[start]
- ToggleButton select {
- icon-name: "selection-mode-symbolic";
+ Button {
+ icon-name: "refresh";
+ action-name: "navigator.reload";
}
[end]
MenuButton menu {
icon-name: "view-more-symbolic";
}
+
+ [end]
+ ToggleButton select {
+ icon-name: "selection-mode-symbolic";
+ }
}
content: Adw.BreakpointBin {
diff --git a/data/ui/pages/search.blp b/data/ui/pages/search.blp
index 819a00c8..7c7424ed 100644
--- a/data/ui/pages/search.blp
+++ b/data/ui/pages/search.blp
@@ -4,7 +4,13 @@ using Adw 1;
template $SearchPage : Adw.Bin {
Adw.ToolbarView {
[top]
- Adw.HeaderBar {}
+ Adw.HeaderBar {
+ [start]
+ Button {
+ icon-name: "refresh";
+ action-name: "navigator.reload";
+ }
+ }
[top]
Adw.Clamp {
diff --git a/gi-types b/gi-types
index eb2a87a2..94acb630 160000
--- a/gi-types
+++ b/gi-types
@@ -1 +1 @@
-Subproject commit eb2a87a25c5e2fb580b605fbec0bd312fe34c492
+Subproject commit 94acb6307e8d467cd9b3e340a18431496636b8f6
diff --git a/src/components/inline-tab-switcher.ts b/src/components/inline-tab-switcher.ts
index d709357b..3d05bdc2 100644
--- a/src/components/inline-tab-switcher.ts
+++ b/src/components/inline-tab-switcher.ts
@@ -306,7 +306,7 @@ export class InlineTabSwitcher extends Gtk.Widget {
}
if (tab.navigate != null) {
- button.action_name = "navigator.visit";
+ button.action_name = "navigator.replace";
button.action_target = GLib.Variant.new_string(tab.navigate);
}
diff --git a/src/components/nav/page.ts b/src/components/nav/page.ts
index aed30b59..5712fced 100644
--- a/src/components/nav/page.ts
+++ b/src/components/nav/page.ts
@@ -3,10 +3,11 @@ import Adw from "gi://Adw";
import GObject from "gi://GObject";
import { Loading } from "../loading";
-import { Endpoint, MuzikaComponent } from "src/navigation";
+import { MuzikaPageMeta, MuzikaPageWidget } from "src/navigation";
import { ERROR_CODE, MuseError } from "src/muse";
import { AuthenticationErrorPage } from "src/pages/authentication-error";
import { ErrorPage } from "src/pages/error";
+import { MatchResult } from "path-to-regexp";
export interface PageState {
state: State;
@@ -49,8 +50,6 @@ export class Page
private _loading!: Loading;
private _content!: Adw.Bin;
- uri: string;
-
get loading() {
return this._stack.visible_child === this._loading;
}
@@ -72,43 +71,31 @@ export class Page
}
}
- page: MuzikaComponent;
- endpoint: Endpoint>;
+ uri: string;
+ page?: MuzikaPageWidget;
+ meta: MuzikaPageMeta;
+ last_match?: MatchResult>;
private __state: PageState | null = null;
private __error: any;
constructor(
- uri: string,
- endpoint: Endpoint>,
- page: MuzikaComponent,
+ meta: MuzikaPageMeta,
) {
super();
- this.uri = uri;
+ this.uri = meta.uri;
- this.endpoint = endpoint;
- this.page = page;
- this.title = endpoint.title;
+ this.meta = meta;
+ this.title = meta.title;
this.loading = true;
}
- loaded(data: Data) {
- const page = this.endpoint.component();
-
- page.present(data);
-
- this.loading = false;
-
- this.page = page;
- this._content.child = page;
- }
-
vfunc_hidden(): void {
if (this.__error) {
this.page = this._content.child = null as any;
- } else {
+ } else if (this.page) {
this.__state = {
state: this.page.get_state(),
};
@@ -118,34 +105,84 @@ export class Page
vfunc_showing(): void {
if (this.__state) {
- const page = this.endpoint.component();
+ const page = this.meta.build();
page.restore_state(this.__state.state);
this.page = page;
this._content.child = page;
this.__state = null;
} else if (this.__error) {
this.show_error(this.__error);
+ } else if (this.last_match) {
+ // page never got to load
+ this.reload();
}
}
show_error(error: any) {
this.__error = error;
- let error_widget: Gtk.Widget;
+ let error_page: Gtk.Widget;
if (error instanceof MuseError && error.code === ERROR_CODE.AUTH_REQUIRED) {
- error_widget = new AuthenticationErrorPage({ error });
- this.title = _("Authentication Required");
+ error_page = new AuthenticationErrorPage({ error });
} else {
- error_widget = new ErrorPage({ error });
- this.title = _("Error");
+ error_page = new ErrorPage({ error });
}
- const toolbar_view = new Adw.ToolbarView();
- toolbar_view.add_top_bar(Adw.HeaderBar.new());
- toolbar_view.content = error_widget;
-
this.loading = false;
- this._content.child = toolbar_view;
+ this._content.child = error_page;
+ }
+
+ async load(
+ uri: string,
+ match: MatchResult>,
+ signal: AbortSignal,
+ ) {
+ this.loading = true;
+ this.uri = uri;
+ this.last_match = match;
+
+ try {
+ const result = await this.meta.load({
+ match,
+ set_title: this.set_title.bind(this),
+ signal,
+ url: new URL("muzika:" + uri),
+ })?.catch((error) => {
+ throw error;
+ });
+
+ this.loading = false;
+ this.__error = null;
+
+ const page = this.meta.build();
+ page.present(result!);
+
+ this._content.child = this.page = page;
+ } catch (error) {
+ this._handle_error(error);
+ }
+ }
+
+ private _handle_error(error: unknown) {
+ if (error instanceof DOMException && error.name == "AbortError") {
+ // do nothing
+ // TODO: maybe
+ // this._view.remove(page);
+ return;
+ }
+
+ this.show_error(error);
+ }
+
+ reload(_signal?: AbortSignal) {
+ const signal = _signal ?? new AbortController().signal;
+
+ if (!this.last_match) {
+ console.error("trying to reload a page that never loaded");
+ return;
+ }
+
+ return this.load(this.uri, this.last_match, signal);
}
}
diff --git a/src/navigation.ts b/src/navigation.ts
index ddaddb5c..79dd19ad 100644
--- a/src/navigation.ts
+++ b/src/navigation.ts
@@ -6,56 +6,37 @@ import Adw from "gi://Adw";
import { match, MatchFunction, MatchResult } from "path-to-regexp";
import { Window } from "./window.js";
-import { endpoints } from "./endpoints.js";
+import { pageMetas } from "./pages.js";
import { AddActionEntries } from "./util/action.js";
import { Page } from "./components/nav/page.js";
import { list_model_to_array } from "./util/list.js";
-export type EndpointResponse = {
- title?: string;
- data: Data;
-};
-
-export interface ShowPageOptions {
- title: string;
- page: Gtk.Widget;
- endpoint: Endpoint;
- uri: string;
-}
-
-export interface MuzikaComponent<
- Data extends any,
- State extends any,
-> {
+export interface MuzikaPageWidget
+ extends Gtk.Widget {
__page_state?: State;
present(data: Data): void;
get_state(): State;
restore_state(state: State): void;
}
-export interface EndpointContext {
+export interface PageLoadContext {
match: MatchResult>;
url: URL;
signal: AbortSignal;
set_title(title: string): void;
}
-export type Endpoint> =
- Page extends MuzikaComponent ? {
- title: string;
- uri: string;
- component(): MuzikaComponent & Gtk.Widget;
- load(context: EndpointContext): void | Promise;
- }
- : never;
+export type MuzikaPageMeta = {
+ title: string;
+ uri: string;
+ build(): MuzikaPageWidget;
+ load(context: PageLoadContext): void | Promise;
+};
export class Navigator extends GObject.Object {
private _view: Adw.NavigationView;
- private match_map: Map<
- MatchFunction,
- Endpoint>
- > = new Map();
+ private match_map: Map = new Map();
private stacks: Map = new Map();
@@ -97,9 +78,9 @@ export class Navigator extends GObject.Object {
this.match_map = new Map();
- for (const endpoint of endpoints) {
- const fn = match(endpoint.uri, {});
- this.match_map.set(fn, endpoint);
+ for (const page of pageMetas) {
+ const fn = match(page.uri, {});
+ this.match_map.set(fn, page);
}
}
@@ -122,71 +103,78 @@ export class Navigator extends GObject.Object {
}
},
},
+ {
+ name: "replace",
+ parameter_type: "s",
+ activate: (_action, param) => {
+ if (!param) return;
+
+ const [url] = param.get_string();
+
+ if (url) {
+ if (url.startsWith("muzika:")) {
+ this.replace(url.slice(7));
+ }
+ }
+ },
+ },
{
name: "back",
activate: (_) => {
this.back();
},
},
+ {
+ name: "reload",
+ activate: () => {
+ this.reload();
+ },
+ },
]);
return action_group;
}
- last_controller: AbortController | null = null;
-
- private show_page(
- uri: string,
- match: MatchResult>,
- endpoint: Endpoint>,
- ) {
- const url = new URL("muzika:" + uri);
- const component = endpoint.component();
- const page = new Page(uri, endpoint, component);
+ private abort_controller: AbortController | null = null;
- if (this.last_controller) {
- this.last_controller.abort();
+ private abort_current() {
+ if (this.abort_controller) {
+ this.abort_controller.abort();
}
- this.last_controller = new AbortController();
-
- const response = endpoint.load({
- match,
- url,
- signal: this.last_controller.signal,
- set_title(title) {
- page.title = title;
- },
- });
-
- this.loading = true;
+ this.abort_controller = new AbortController();
+ }
- this._view.push(page);
+ private reset_abort_controller() {
+ this.abort_controller = null;
+ }
- const handle_error = (error: any) => {
- // TODO: handle this better
+ reload() {
+ const page = this._view.get_last_child() as Page | null;
- if (error instanceof DOMException && error.name == "AbortError") {
- this._view.remove(page);
- return;
- }
+ if (!page || page.loading || !(page instanceof Page)) return;
- this.loading = false;
- this.last_controller = null;
+ this.abort_current();
- page.show_error(error);
- };
+ return page.reload(this.abort_controller!.signal)
+ ?.then(() => {
+ this.reset_abort_controller();
+ });
+ }
- Promise.resolve(response).then(
- (data) => {
- this.loading = false;
- this.last_controller = null;
+ private show_page(
+ uri: string,
+ match: MatchResult>,
+ meta: MuzikaPageMeta,
+ ) {
+ this.abort_current();
- if (data != null) {
- page.loaded(data);
- }
- },
- ).catch(handle_error);
+ const page = new Page(meta);
+ this._view.push(page);
+ page.load(uri, match, this.abort_controller!.signal)
+ .then(() => {
+ this.reset_abort_controller();
+ });
}
get current_uri(): string | null {
@@ -215,12 +203,6 @@ export class Navigator extends GObject.Object {
this._view.pop();
}
- // reload(navigate?: boolean) {
- // this.go(0, navigate);
- // }
- reload() {
- }
-
private match_uri(uri: string) {
// muzika:home to
// muzika/home
@@ -238,13 +220,13 @@ export class Navigator extends GObject.Object {
);
}
- for (const [fn, endpoint] of this.match_map) {
+ for (const [fn, meta] of this.match_map) {
const match = fn(path);
if (match) {
return {
match: match as MatchResult>,
- endpoint,
+ meta,
};
}
}
@@ -254,11 +236,45 @@ export class Navigator extends GObject.Object {
const match = this.match_uri(uri);
if (match) {
- this.show_page(uri, match.match, match.endpoint);
+ this.show_page(uri, match.match, match.meta);
this.emit("navigate");
}
}
+ replace(uri: string) {
+ const page = this._view.get_last_child() as Page | null;
+
+ if (!page || !(page instanceof Page)) return;
+
+ const match = this.match_uri(uri);
+
+ if (!match) {
+ console.error(`Tried to replace a page with an invalid uri: ${uri}`);
+ return;
+ }
+
+ if (match.meta !== page.meta) {
+ console.error(
+ `Tried to replace a page with a uri that renders a different page. Expected: ${page.meta.uri}, got: ${match.meta.uri}`,
+ );
+ return;
+ }
+
+ this.abort_current();
+
+ if (match) {
+ page.load;
+ return page.load(
+ uri,
+ match.match,
+ this.abort_controller!.signal,
+ )
+ ?.then(() => {
+ this.reset_abort_controller();
+ });
+ }
+ }
+
switch_stack(uri: string) {
const stack = this._view.navigation_stack as Gio.ListModel<
Page
diff --git a/src/endpoints.ts b/src/pages.ts
similarity index 75%
rename from src/endpoints.ts
rename to src/pages.ts
index 90150747..e37bce42 100644
--- a/src/endpoints.ts
+++ b/src/pages.ts
@@ -1,4 +1,4 @@
-import { Endpoint, MuzikaComponent } from "./navigation.js";
+import { MuzikaPageMeta } from "./navigation.js";
import { LibraryPage } from "./pages/library/index.js";
import { HomePage } from "./pages/home.js";
@@ -21,125 +21,125 @@ import { MoodsPage } from "./pages/moods.js";
import { MoodPlaylistsPage } from "./pages/mood-playlists.js";
import { NewReleasesPage } from "./pages/new-releases.js";
-export const endpoints: Endpoint>[] = [
+export const pageMetas: MuzikaPageMeta[] = [
{
uri: "home",
title: _("Home"),
- component: () => new HomePage(),
+ build: () => new HomePage(),
load: HomePage.load,
},
{
uri: "playlist/:playlistId",
title: _("Playlist"),
- component: () => new PlaylistPage(),
+ build: () => new PlaylistPage(),
load: PlaylistPage.load,
},
{
uri: "album/:albumId",
title: _("Album"),
- component: () => new AlbumPage(),
+ build: () => new AlbumPage(),
load: AlbumPage.load,
},
{
uri: "artist/:channelId",
title: _("Artist"),
- component: () => new ArtistPage(),
+ build: () => new ArtistPage(),
load: ArtistPage.load,
},
{
uri: "search/:query",
title: _("Search Results"),
- component: () => new SearchPage(),
+ build: () => new SearchPage(),
load: SearchPage.load,
},
{
uri: "library",
title: _("Library"),
- component: () => new LibraryPage(),
+ build: () => new LibraryPage(),
load: LibraryPage.load,
},
{
uri: "library/playlists",
title: _("Library Playlists"),
- component: () => new LibraryPlaylistsPage(),
+ build: () => new LibraryPlaylistsPage(),
load: LibraryPlaylistsPage.load,
},
{
uri: "library/albums",
title: _("Library Albums"),
- component: () => new LibraryAlbumsPage(),
+ build: () => new LibraryAlbumsPage(),
load: LibraryAlbumsPage.load,
},
{
uri: "library/artists",
title: _("Library Artists"),
- component: () => new LibraryArtistsPage(),
+ build: () => new LibraryArtistsPage(),
load: LibraryArtistsPage.load,
},
{
uri: "library/subscriptions",
title: _("Library Subscriptions"),
- component: () => new LibrarySubscriptionsPage(),
+ build: () => new LibrarySubscriptionsPage(),
load: LibrarySubscriptionsPage.load,
},
{
uri: "library/songs",
title: _("Library Songs"),
- component: () => new LibrarySongsPage(),
+ build: () => new LibrarySongsPage(),
load: LibrarySongsPage.load,
},
{
uri: "history",
title: _("History"),
- component: () => new HistoryPage(),
+ build: () => new HistoryPage(),
load: HistoryPage.load,
},
{
uri: "artist-albums/:channelId/:params",
title: _("Artist Albums"),
- component: () => new ArtistAlbumsPage(),
+ build: () => new ArtistAlbumsPage(),
load: ArtistAlbumsPage.load,
},
{
uri: "channel/:channelId",
title: _("Channel"),
- component: () => new ChannelPage(),
+ build: () => new ChannelPage(),
load: ChannelPage.load,
},
{
uri: "channel-playlists/:channelId/:params",
title: _("Channel Playlists"),
- component: () => new ChannelPlaylistsPage(),
+ build: () => new ChannelPlaylistsPage(),
load: ChannelPlaylistsPage.load,
},
{
uri: "explore",
title: _("Explore"),
- component: () => new ExplorePage(),
+ build: () => new ExplorePage(),
load: ExplorePage.load,
},
{
uri: "charts",
title: _("Charts"),
- component: () => new ChartsPage(),
+ build: () => new ChartsPage(),
load: ChartsPage.load,
},
{
uri: "moods-and-genres",
title: _("Moods and genres"),
- component: () => new MoodsPage(),
+ build: () => new MoodsPage(),
load: MoodsPage.load,
},
{
uri: "mood-playlists/:params",
title: _("Mood"),
- component: () => new MoodPlaylistsPage(),
+ build: () => new MoodPlaylistsPage(),
load: MoodPlaylistsPage.load,
},
{
uri: "new-releases",
title: _("New Releases"),
- component: () => new NewReleasesPage(),
+ build: () => new NewReleasesPage(),
load: NewReleasesPage.load,
},
];
diff --git a/src/pages/album.ts b/src/pages/album.ts
index b4fb93d3..5355e335 100644
--- a/src/pages/album.ts
+++ b/src/pages/album.ts
@@ -13,7 +13,7 @@ import {
} from "../muse.js";
import { Carousel } from "../components/carousel/index.js";
-import { EndpointContext, MuzikaComponent } from "src/navigation.js";
+import { MuzikaPageWidget, PageLoadContext } from "src/navigation.js";
import { PlaylistItemView } from "src/components/playlist/itemview.js";
import { PlayableContainer, PlayableList } from "src/util/playablelist.js";
import {
@@ -22,11 +22,6 @@ import {
} from "src/util/scrolled.js";
import { PlaylistHeader } from "src/components/playlist/header.js";
-interface AlbumState extends VScrollState {
- album: AlbumResult;
- track?: string;
-}
-
GObject.type_ensure(PlaylistHeader.$gtype);
GObject.type_ensure(PlaylistItemView.$gtype);
@@ -35,8 +30,10 @@ interface AlbumProps {
track?: string;
}
+interface AlbumState extends VScrollState, AlbumProps {}
+
export class AlbumPage extends Adw.Bin
- implements MuzikaComponent {
+ implements MuzikaPageWidget {
static {
GObject.registerClass({
GTypeName: "AlbumPage",
@@ -217,7 +214,7 @@ export class AlbumPage extends Adw.Bin
no_more = false;
- static async load(context: EndpointContext) {
+ static async load(context: PageLoadContext) {
const album = await get_album(context.match.params.albumId, {
signal: context.signal,
});
diff --git a/src/pages/artist-albums.ts b/src/pages/artist-albums.ts
index b16197a7..697209a4 100644
--- a/src/pages/artist-albums.ts
+++ b/src/pages/artist-albums.ts
@@ -5,7 +5,7 @@ import Gtk from "gi://Gtk?version=4.0";
import { ArtistAlbums, get_artist_albums } from "src/muse.js";
-import { EndpointContext, MuzikaComponent } from "src/navigation.js";
+import { PageLoadContext, MuzikaPageWidget } from "src/navigation.js";
import { CarouselGridView } from "src/components/carousel/view/grid";
import { PlayableContainer } from "src/util/playablelist";
import { MixedCardItem } from "src/components/library/mixedcard";
@@ -21,7 +21,7 @@ interface ArtistAlbumsState extends VScrollState {
GObject.type_ensure(CarouselGridView.$gtype);
export class ArtistAlbumsPage extends Adw.Bin
- implements MuzikaComponent {
+ implements MuzikaPageWidget {
static {
GObject.registerClass({
GTypeName: "ArtistAlbumsPage",
@@ -113,7 +113,7 @@ export class ArtistAlbumsPage extends Adw.Bin
);
}
- static async load(context: EndpointContext) {
+ static async load(context: PageLoadContext) {
const results = await get_artist_albums(
context.match.params.channelId,
context.match.params.params,
diff --git a/src/pages/artist.ts b/src/pages/artist.ts
index 32d13a5d..16232ac9 100644
--- a/src/pages/artist.ts
+++ b/src/pages/artist.ts
@@ -7,7 +7,7 @@ import Gio from "gi://Gio";
import { Artist, Category, get_artist, MixedItem } from "../muse.js";
import { Carousel } from "../components/carousel/index.js";
-import { EndpointContext, MuzikaComponent } from "src/navigation.js";
+import { PageLoadContext, MuzikaPageWidget } from "src/navigation.js";
import { PlaylistListView } from "src/components/playlist/listview.js";
import { PlaylistItemView } from "src/components/playlist/itemview.js";
import { PlayableContainer, PlayableList } from "src/util/playablelist.js";
@@ -26,7 +26,7 @@ GObject.type_ensure(PlaylistHeader.$gtype);
GObject.type_ensure(PlaylistListView.$gtype);
export class ArtistPage extends Adw.Bin
- implements MuzikaComponent {
+ implements MuzikaPageWidget {
static {
GObject.registerClass({
GTypeName: "ArtistPage",
@@ -177,7 +177,7 @@ export class ArtistPage extends Adw.Bin
this._carousels.append(carousel);
}
- static async load(context: EndpointContext) {
+ static async load(context: PageLoadContext) {
const artist = await get_artist(context.match.params.channelId, {
signal: context.signal,
});
diff --git a/src/pages/authentication-error.ts b/src/pages/authentication-error.ts
index d7570e71..79eaea81 100644
--- a/src/pages/authentication-error.ts
+++ b/src/pages/authentication-error.ts
@@ -6,7 +6,7 @@ import Adw from "gi://Adw";
import { get_option } from "../muse.js";
import { error_to_string, ErrorPageOptions } from "./error.js";
-export class AuthenticationErrorPage extends Gtk.Box {
+export class AuthenticationErrorPage extends Adw.Bin {
static {
GObject.registerClass({
GTypeName: "AuthenticationErrorPage",
diff --git a/src/pages/channel-playlists.ts b/src/pages/channel-playlists.ts
index b1048dcd..763b96d9 100644
--- a/src/pages/channel-playlists.ts
+++ b/src/pages/channel-playlists.ts
@@ -5,7 +5,7 @@ import Gtk from "gi://Gtk?version=4.0";
import { ChannelPlaylists, get_channel_playlists } from "src/muse.js";
-import { EndpointContext, MuzikaComponent } from "src/navigation.js";
+import { PageLoadContext, MuzikaPageWidget } from "src/navigation.js";
import { CarouselGridView } from "src/components/carousel/view/grid";
import { PlayableContainer } from "src/util/playablelist";
import { MixedCardItem } from "src/components/library/mixedcard";
@@ -21,7 +21,7 @@ interface ChannelPlaylistsState extends VScrollState {
GObject.type_ensure(CarouselGridView.$gtype);
export class ChannelPlaylistsPage extends Adw.Bin
- implements MuzikaComponent {
+ implements MuzikaPageWidget {
static {
GObject.registerClass({
GTypeName: "ChannelPlaylistsPage",
@@ -113,7 +113,7 @@ export class ChannelPlaylistsPage extends Adw.Bin
);
}
- static async load(context: EndpointContext) {
+ static async load(context: PageLoadContext) {
const results = await get_channel_playlists(
context.match.params.channelId,
context.match.params.params,
diff --git a/src/pages/channel.ts b/src/pages/channel.ts
index df68bbe6..4efd2cfd 100644
--- a/src/pages/channel.ts
+++ b/src/pages/channel.ts
@@ -12,7 +12,7 @@ import {
} from "src/muse.js";
import { Carousel } from "../components/carousel/index.js";
-import { EndpointContext, MuzikaComponent } from "src/navigation.js";
+import { PageLoadContext, MuzikaPageWidget } from "src/navigation.js";
import { PlaylistListView } from "src/components/playlist/listview.js";
import { PlaylistItemView } from "src/components/playlist/itemview.js";
import { PlayableContainer, PlayableList } from "src/util/playablelist.js";
@@ -30,7 +30,7 @@ GObject.type_ensure(PlaylistHeader.$gtype);
GObject.type_ensure(PlaylistListView.$gtype);
export class ChannelPage extends Adw.Bin
- implements MuzikaComponent {
+ implements MuzikaPageWidget {
static {
GObject.registerClass({
GTypeName: "ChannelPage",
@@ -156,7 +156,7 @@ export class ChannelPage extends Adw.Bin
this._carousels.append(carousel);
}
- static async load(context: EndpointContext) {
+ static async load(context: PageLoadContext) {
const artist = await get_channel(context.match.params.channelId, {
signal: context.signal,
});
diff --git a/src/pages/charts.ts b/src/pages/charts.ts
index d6bf8d84..e9bc4624 100644
--- a/src/pages/charts.ts
+++ b/src/pages/charts.ts
@@ -11,7 +11,7 @@ import type {
import { Carousel } from "../components/carousel/index.js";
import { Loading } from "../components/loading.js";
-import { EndpointContext, MuzikaComponent } from "src/navigation.js";
+import { PageLoadContext, MuzikaPageWidget } from "src/navigation.js";
import { MixedCardItem } from "src/components/library/mixedcard.js";
import {
set_scrolled_window_initial_vscroll,
@@ -25,7 +25,7 @@ export interface ChartsPageState extends VScrollState {
}
export class ChartsPage extends Adw.Bin
- implements MuzikaComponent {
+ implements MuzikaPageWidget {
static {
GObject.registerClass({
GTypeName: "ChartsPage",
@@ -51,15 +51,15 @@ export class ChartsPage extends Adw.Bin
if (!country || country.selected) return;
this.activate_action(
- `navigator.visit`,
+ `navigator.replace`,
GLib.Variant.new_string(
- `muzika:charts?country=${country.code}&replace=true`,
+ `muzika:charts?country=${country.code}`,
),
);
});
}
- static load(ctx: EndpointContext) {
+ static load(ctx: PageLoadContext) {
return get_charts(ctx.url.searchParams.get("country") ?? undefined, {
signal: ctx.signal,
});
diff --git a/src/pages/error.ts b/src/pages/error.ts
index ee61db32..3c15d628 100644
--- a/src/pages/error.ts
+++ b/src/pages/error.ts
@@ -16,7 +16,7 @@ export interface ErrorPageOptions {
error?: any;
}
-export class ErrorPage extends Gtk.Box {
+export class ErrorPage extends Adw.Bin {
static {
GObject.registerClass({
GTypeName: "ErrorPage",
diff --git a/src/pages/explore.ts b/src/pages/explore.ts
index bd43a8f2..e8566032 100644
--- a/src/pages/explore.ts
+++ b/src/pages/explore.ts
@@ -11,7 +11,7 @@ import type {
import { Carousel } from "../components/carousel/index.js";
import { Loading } from "../components/loading.js";
-import { EndpointContext, MuzikaComponent } from "src/navigation.js";
+import { PageLoadContext, MuzikaPageWidget } from "src/navigation.js";
import { MixedCardItem } from "src/components/library/mixedcard.js";
import {
set_scrolled_window_initial_vscroll,
@@ -25,7 +25,7 @@ export interface ExplorePageState extends VScrollState {
}
export class ExplorePage extends Adw.Bin
- implements MuzikaComponent {
+ implements MuzikaPageWidget {
static {
GObject.registerClass({
GTypeName: "ExplorePage",
@@ -39,7 +39,7 @@ export class ExplorePage extends Adw.Bin
contents?: ExploreContents;
- static load(ctx: EndpointContext) {
+ static load(ctx: PageLoadContext) {
return get_explore({
signal: ctx.signal,
});
diff --git a/src/pages/home.ts b/src/pages/home.ts
index 1a903b93..b3c1653e 100644
--- a/src/pages/home.ts
+++ b/src/pages/home.ts
@@ -8,7 +8,7 @@ import { get_home, Home, MixedContent } from "../muse.js";
import { Carousel } from "../components/carousel/index.js";
import { Loading } from "../components/loading.js";
-import { EndpointContext, MuzikaComponent } from "src/navigation.js";
+import { PageLoadContext, MuzikaPageWidget } from "src/navigation.js";
import {
set_scrolled_window_initial_vscroll,
VScrollState,
@@ -22,7 +22,7 @@ export interface HomePageState extends VScrollState {
}
export class HomePage extends Adw.Bin
- implements MuzikaComponent {
+ implements MuzikaPageWidget {
static {
GObject.registerClass({
GTypeName: "HomePage",
@@ -52,7 +52,7 @@ export class HomePage extends Adw.Bin
});
}
- static load(ctx: EndpointContext) {
+ static load(ctx: PageLoadContext) {
return get_home({ limit: 3, signal: ctx.signal });
}
diff --git a/src/pages/library/base.ts b/src/pages/library/base.ts
index c3c7a412..462efbd3 100644
--- a/src/pages/library/base.ts
+++ b/src/pages/library/base.ts
@@ -8,7 +8,7 @@ import { LibraryView } from "../../components/library/view.js";
import type { LibraryOrder, Order } from "libmuse/types/mixins/utils.js";
import { MixedCardItem } from "src/components/library/mixedcard.js";
-import { EndpointContext, MuzikaComponent } from "src/navigation.js";
+import { MuzikaPageWidget, PageLoadContext } from "src/navigation.js";
import {
set_scrolled_window_initial_vscroll,
VScrollState,
@@ -56,7 +56,7 @@ interface LibraryState extends VScrollState {
export class AbstractLibraryPage
extends Adw.Bin
- implements MuzikaComponent {
+ implements MuzikaPageWidget {
static {
GObject.registerClass({
GTypeName: "AbstractLibraryPage",
@@ -79,15 +79,24 @@ export class AbstractLibraryPage
this.uri = options.uri;
this.view = new LibraryView({
- filters: Array.from((this.orders).keys()),
+ filters: Array.from(this.orders.keys()),
});
this.view.connect("paginate", () => {
this.load_more();
});
+ const headerbar = new Adw.HeaderBar();
+
+ headerbar.pack_start(
+ new Gtk.Button({
+ icon_name: "refresh",
+ action_name: "navigator.reload",
+ }),
+ );
+
this.toolbar_view = new Adw.ToolbarView();
- this.toolbar_view.add_top_bar(Adw.HeaderBar.new());
+ this.toolbar_view.add_top_bar(headerbar);
this.toolbar_view.content = this.view;
this.child = this.toolbar_view;
@@ -100,9 +109,9 @@ export class AbstractLibraryPage
if (!order) return;
- const url = `muzika:${this.uri}?replace=true&order=${order}`;
+ const url = `muzika:${this.uri}?order=${order}`;
- this.activate_action("navigator.visit", GLib.Variant.new_string(url));
+ this.activate_action("navigator.replace", GLib.Variant.new_string(url));
}
show_library(library: LibraryResults) {
@@ -170,7 +179,7 @@ export class AbstractLibraryPage
static get_loader(
loader: LibraryLoader | LibraryLoader,
) {
- return function (context: EndpointContext) {
+ return function (context: PageLoadContext) {
return (loader as LibraryLoader)({
signal: context.signal,
order: context.url.searchParams.get("order") as LibraryOrder ??
@@ -181,7 +190,7 @@ export class AbstractLibraryPage
order: context.url.searchParams.get("order"),
};
});
- } as (context: EndpointContext) => Promise;
+ } as (context: PageLoadContext) => Promise;
}
static load: ReturnType;
diff --git a/src/pages/library/history.ts b/src/pages/library/history.ts
index d27e48da..c6652620 100644
--- a/src/pages/library/history.ts
+++ b/src/pages/library/history.ts
@@ -4,7 +4,7 @@ import Gtk from "gi://Gtk?version=4.0";
import { get_history, History, PlaylistItem } from "../../muse.js";
-import { EndpointContext, MuzikaComponent } from "src/navigation.js";
+import { PageLoadContext, MuzikaPageWidget } from "src/navigation.js";
import { PlaylistItemView } from "src/components/playlist/itemview.js";
import {
SectionedPlayableContainer,
@@ -63,7 +63,7 @@ interface CategoryMeta {
}
export class HistoryPage extends Adw.Bin
- implements MuzikaComponent {
+ implements MuzikaPageWidget {
static {
GObject.registerClass({
GTypeName: "HistoryPage",
@@ -153,7 +153,7 @@ export class HistoryPage extends Adw.Bin
this.present(state.results);
}
- static load(context: EndpointContext) {
+ static load(context: PageLoadContext) {
return get_history();
}
}
diff --git a/src/pages/library/songs.ts b/src/pages/library/songs.ts
index 9faf1521..96a5cd81 100644
--- a/src/pages/library/songs.ts
+++ b/src/pages/library/songs.ts
@@ -8,7 +8,7 @@ import { get_library_songs, LibrarySongs } from "../../muse.js";
import { alphabetical_orders, order_id_to_name } from "./base.js";
import { Paginator } from "src/components/paginator.js";
import type { Order } from "libmuse/types/mixins/utils.js";
-import { EndpointContext, MuzikaComponent } from "src/navigation.js";
+import { PageLoadContext, MuzikaPageWidget } from "src/navigation.js";
import { PlayableContainer, PlayableList } from "src/util/playablelist.js";
import { PlaylistItemView } from "src/components/playlist/itemview.js";
import {
@@ -20,7 +20,7 @@ import {
GObject.type_ensure(Paginator.$gtype);
export class LibrarySongsPage extends Adw.Bin
- implements MuzikaComponent {
+ implements MuzikaPageWidget {
static {
GObject.registerClass({
GTypeName: "LibrarySongsPage",
@@ -70,9 +70,9 @@ export class LibrarySongsPage extends Adw.Bin
if (!order) return;
- let url = `muzika:${this.uri}?replace=true&order=${order}`;
+ let url = `muzika:${this.uri}?order=${order}`;
- this.activate_action("navigator.visit", GLib.Variant.new_string(url));
+ this.activate_action("navigator.replace", GLib.Variant.new_string(url));
}
loading = false;
@@ -148,7 +148,7 @@ export class LibrarySongsPage extends Adw.Bin
}
}
- static load(context: EndpointContext): Promise {
+ static load(context: PageLoadContext): Promise {
return get_library_songs({
signal: context.signal,
order: context.url.searchParams.get("order") as Order ??
diff --git a/src/pages/login.ts b/src/pages/login.ts
index 9e4c541a..d5cbb236 100644
--- a/src/pages/login.ts
+++ b/src/pages/login.ts
@@ -74,14 +74,35 @@ export class LoginPage extends Adw.Window {
});
}
+ private _last_signal?: AbortSignal;
+
async auth_flow(signal?: AbortSignal) {
this._stack.visible_child = this._spinner;
this._spinner.start();
+ this._last_signal = signal;
+
const login_code = await get_option("auth").get_login_code();
this.show_code(login_code);
- await get_option("auth").load_token_with_code(login_code, signal);
+ await get_option("auth")
+ .load_token_with_code(login_code, signal)
+ .then(() => {
+ this._last_signal = undefined;
+ })
+ .catch((error) => {
+ if ((error instanceof DOMException) && error.name === "AbortError") {
+ return;
+ } else {
+ throw error;
+ }
+ });
+ }
+
+ private refresh_cb() {
+ const signal = this._last_signal;
+
+ this.auth_flow(signal);
}
}
diff --git a/src/pages/mood-playlists.ts b/src/pages/mood-playlists.ts
index fd3a4b30..1aea081a 100644
--- a/src/pages/mood-playlists.ts
+++ b/src/pages/mood-playlists.ts
@@ -6,7 +6,7 @@ import { get_mood_playlists, MoodPlaylists } from "../muse.js";
import { Carousel } from "../components/carousel/index.js";
import { Loading } from "../components/loading.js";
-import { EndpointContext, MuzikaComponent } from "src/navigation.js";
+import { PageLoadContext, MuzikaPageWidget } from "src/navigation.js";
import {
set_scrolled_window_initial_vscroll,
VScrollState,
@@ -19,7 +19,7 @@ export interface MoodPlaylistsPageState extends VScrollState {
}
export class MoodPlaylistsPage extends Adw.Bin
- implements MuzikaComponent {
+ implements MuzikaPageWidget {
static {
GObject.registerClass({
GTypeName: "MoodPlaylistsPage",
@@ -33,7 +33,7 @@ export class MoodPlaylistsPage extends Adw.Bin
contents?: MoodPlaylists;
- static async load(ctx: EndpointContext) {
+ static async load(ctx: PageLoadContext) {
const data = await get_mood_playlists(ctx.match.params.params, {
signal: ctx.signal,
});
diff --git a/src/pages/moods.ts b/src/pages/moods.ts
index 3dba5bc9..8ab25b1b 100644
--- a/src/pages/moods.ts
+++ b/src/pages/moods.ts
@@ -6,7 +6,7 @@ import { get_mood_categories, MoodCategories } from "../muse.js";
import { Carousel } from "../components/carousel/index.js";
import { Loading } from "../components/loading.js";
-import { EndpointContext, MuzikaComponent } from "src/navigation.js";
+import { PageLoadContext, MuzikaPageWidget } from "src/navigation.js";
import {
set_scrolled_window_initial_vscroll,
VScrollState,
@@ -19,7 +19,7 @@ export interface MoodsPageState extends VScrollState {
}
export class MoodsPage extends Adw.Bin
- implements MuzikaComponent {
+ implements MuzikaPageWidget {
static {
GObject.registerClass({
GTypeName: "MoodsPage",
@@ -33,7 +33,7 @@ export class MoodsPage extends Adw.Bin
contents?: MoodCategories;
- static load(ctx: EndpointContext) {
+ static load(ctx: PageLoadContext) {
return get_mood_categories({
signal: ctx.signal,
});
diff --git a/src/pages/new-releases.ts b/src/pages/new-releases.ts
index 6f00a551..3d99c512 100644
--- a/src/pages/new-releases.ts
+++ b/src/pages/new-releases.ts
@@ -6,7 +6,7 @@ import { get_new_releases, NewReleases } from "../muse.js";
import { Carousel } from "../components/carousel/index.js";
import { Loading } from "../components/loading.js";
-import { EndpointContext, MuzikaComponent } from "src/navigation.js";
+import { PageLoadContext, MuzikaPageWidget } from "src/navigation.js";
import {
set_scrolled_window_initial_vscroll,
VScrollState,
@@ -19,7 +19,7 @@ export interface NewReleasesPageState extends VScrollState {
}
export class NewReleasesPage extends Adw.Bin
- implements MuzikaComponent {
+ implements MuzikaPageWidget {
static {
GObject.registerClass({
GTypeName: "NewReleasesPage",
@@ -33,7 +33,7 @@ export class NewReleasesPage extends Adw.Bin
contents?: NewReleases;
- static async load(ctx: EndpointContext) {
+ static async load(ctx: PageLoadContext) {
const data = await get_new_releases({
signal: ctx.signal,
});
diff --git a/src/pages/playlist.ts b/src/pages/playlist.ts
index 36e1b061..36209aeb 100644
--- a/src/pages/playlist.ts
+++ b/src/pages/playlist.ts
@@ -22,7 +22,7 @@ import {
import { Carousel } from "../components/carousel/index.js";
import { PlaylistHeader } from "../components/playlist/header.js";
-import { EndpointContext, MuzikaComponent } from "src/navigation.js";
+import { PageLoadContext, MuzikaPageWidget } from "src/navigation.js";
import { ObjectContainer } from "src/util/objectcontainer.js";
import { PlaylistItemView } from "src/components/playlist/itemview.js";
import { Paginator } from "src/components/paginator.js";
@@ -51,7 +51,7 @@ GObject.type_ensure(PlaylistItemView.$gtype);
GObject.type_ensure(PlaylistBar.$gtype);
export class PlaylistPage extends Adw.Bin
- implements MuzikaComponent {
+ implements MuzikaPageWidget {
static {
GObject.registerClass({
GTypeName: "PlaylistPage",
@@ -528,7 +528,7 @@ export class PlaylistPage extends Adw.Bin
}
}
- static async load(context: EndpointContext) {
+ static async load(context: PageLoadContext) {
const data = await get_playlist(context.match.params.playlistId, {
related: true,
suggestions_limit: 6,
diff --git a/src/pages/search.ts b/src/pages/search.ts
index 0aafe682..8e644e07 100644
--- a/src/pages/search.ts
+++ b/src/pages/search.ts
@@ -16,7 +16,7 @@ import { SearchSection } from "../components/search/section.js";
import { TopResultSection } from "../components/search/topresultsection.js";
import { Paginator } from "../components/paginator.js";
import { InlineTabSwitcher, Tab } from "../components/inline-tab-switcher.js";
-import { EndpointContext, MuzikaComponent } from "src/navigation.js";
+import { PageLoadContext, MuzikaPageWidget } from "src/navigation.js";
import { escape_label } from "src/util/text.js";
import {
set_scrolled_window_initial_vscroll,
@@ -38,7 +38,7 @@ interface SearchData {
GObject.type_ensure(InlineTabSwitcher.$gtype);
export class SearchPage extends Adw.Bin
- implements MuzikaComponent {
+ implements MuzikaPageWidget {
static {
GObject.registerClass({
GTypeName: "SearchPage",
@@ -80,7 +80,7 @@ export class SearchPage extends Adw.Bin
this._context_label.connect("activate-link", (_, uri) => {
if (uri && uri.startsWith("muzika:")) {
this.activate_action(
- "navigator.visit",
+ "navigator.replace",
GLib.Variant.new_string(uri),
);
@@ -162,7 +162,7 @@ export class SearchPage extends Adw.Bin
new Gtk.ToggleButton({
label: filter_to_string(filter),
css_classes: ["chip"],
- action_name: "navigator.visit",
+ action_name: "navigator.replace",
action_target: GLib.Variant.new("s", url),
active: selected,
}),
@@ -269,7 +269,7 @@ export class SearchPage extends Adw.Bin
});
}
- static load(context: EndpointContext) {
+ static load(context: PageLoadContext) {
const autocorrect = context.url.searchParams.get("autocorrect");
const args = [decodeURIComponent(context.match.params.query), {