diff --git a/src/ui/PodcastView/EpisodeListItem.svelte b/src/ui/PodcastView/EpisodeListItem.svelte
index 0e61d23..3a64378 100644
--- a/src/ui/PodcastView/EpisodeListItem.svelte
+++ b/src/ui/PodcastView/EpisodeListItem.svelte
@@ -120,9 +120,11 @@
}
.podcast-episode-thumbnail-container {
- flex: 0 0 4rem;
- width: 4rem;
- height: 4rem;
+ flex: 0 0 5rem;
+ width: 5rem;
+ height: 5rem;
+ max-width: 5rem;
+ max-height: 5rem;
display: flex;
align-items: center;
justify-content: center;
@@ -134,8 +136,8 @@
:global(.podcast-episode-thumbnail) {
width: 100%;
height: 100%;
- border-radius: 15%;
object-fit: cover;
+ border-radius: 15%;
cursor: pointer !important;
}
diff --git a/src/ui/PodcastView/TopBar.svelte b/src/ui/PodcastView/TopBar.svelte
index e71534d..feef28c 100644
--- a/src/ui/PodcastView/TopBar.svelte
+++ b/src/ui/PodcastView/TopBar.svelte
@@ -6,6 +6,19 @@
export let canShowEpisodeList: boolean = false;
export let canShowPlayer: boolean = false;
+ const gridTooltip = "Browse podcast grid";
+ const disabledEpisodeTooltip =
+ "Select a podcast or playlist to view its episodes.";
+ const disabledPlayerTooltip =
+ "Start playing an episode to open the player.";
+
+ $: episodeTooltip = canShowEpisodeList
+ ? "View episode list"
+ : disabledEpisodeTooltip;
+ $: playerTooltip = canShowPlayer
+ ? "Open player"
+ : disabledPlayerTooltip;
+
function handleClickMenuItem(newState: ViewState) {
if (viewState === newState) return;
@@ -29,6 +42,7 @@
`}
aria-label="Podcast grid"
aria-pressed={viewState === ViewState.PodcastGrid}
+ title={gridTooltip}
>
@@ -38,11 +52,16 @@
class={`
topbar-menu-button
${viewState === ViewState.EpisodeList ? "topbar-selected" : ""}
- ${canShowEpisodeList ? "topbar-selectable" : ""}
+ ${canShowEpisodeList ? "topbar-selectable" : "topbar-disabled"}
`}
- aria-label="Episode list"
+ aria-label={
+ canShowEpisodeList
+ ? "Episode list"
+ : "Episode list (select a podcast or playlist first)"
+ }
aria-pressed={viewState === ViewState.EpisodeList}
disabled={!canShowEpisodeList}
+ title={episodeTooltip}
>
@@ -52,11 +71,16 @@
class={`
topbar-menu-button
${viewState === ViewState.Player ? "topbar-selected" : ""}
- ${canShowPlayer ? "topbar-selectable" : ""}
+ ${canShowPlayer ? "topbar-selectable" : "topbar-disabled"}
`}
- aria-label="Player"
+ aria-label={
+ canShowPlayer
+ ? "Player"
+ : "Player (start playing an episode to open the player)"
+ }
aria-pressed={viewState === ViewState.Player}
disabled={!canShowPlayer}
+ title={playerTooltip}
>
@@ -68,9 +92,12 @@
flex-direction: row;
align-items: center;
justify-content: space-between;
+ gap: 0.5rem;
+ padding: 0.25rem 0.5rem;
height: 50px;
min-height: 50px;
border-bottom: 1px solid var(--background-divider);
+ box-sizing: border-box;
}
.topbar-menu-button {
@@ -78,14 +105,18 @@
align-items: center;
justify-content: center;
width: 100%;
- height: 100%;
- color: var(--text-muted, #8a8f98);
- opacity: 1;
- border: none;
- background: none;
- padding: 0;
- transition: color 120ms ease, background-color 120ms ease,
- box-shadow 120ms ease, opacity 120ms ease;
+ padding: 0.4rem 0.25rem;
+ flex: 1 1 0;
+ border: 1px solid var(--background-modifier-border, #3a3a3a);
+ border-radius: 8px;
+ background: var(--background-secondary, transparent);
+ color: var(--text-muted, #8a8a8a);
+ transition:
+ background-color 120ms ease,
+ border-color 120ms ease,
+ color 120ms ease,
+ box-shadow 120ms ease,
+ opacity 120ms ease;
}
.topbar-menu-button:focus-visible {
@@ -93,24 +124,32 @@
outline-offset: 2px;
}
- .topbar-menu-button:disabled {
- color: var(--text-faint, #6b6b6b);
- opacity: 0.45;
- cursor: not-allowed;
- }
-
.topbar-selectable {
cursor: pointer;
- color: var(--text-normal, #dfe2e7);
+ color: var(--text-normal, #e6e6e6);
+ background: var(--background-secondary-alt, rgba(255, 255, 255, 0.02));
+ }
+
+ .topbar-menu-button:hover.topbar-selectable:not(.topbar-selected) {
+ background: var(--background-modifier-hover, rgba(255, 255, 255, 0.06));
+ border-color: var(--interactive-accent, #5c6bf7);
+ color: var(--text-normal, #e6e6e6);
}
- .topbar-selectable:hover {
- background-color: var(--background-divider);
+ .topbar-selected,
+ .topbar-selected:hover {
+ color: var(--text-on-accent, #ffffff);
+ background: var(--interactive-accent, #5c6bf7);
+ border-color: var(--interactive-accent, #5c6bf7);
+ box-shadow: 0 0 0 1px var(--interactive-accent, #5c6bf7);
}
- .topbar-selected {
- color: var(--interactive-accent, #5c6bf7);
- background-color: var(--background-secondary, var(--background-divider));
- box-shadow: inset 0 -2px var(--interactive-accent, #5c6bf7);
+ .topbar-disabled,
+ .topbar-menu-button:disabled {
+ cursor: not-allowed;
+ color: var(--text-faint, #a0a0a0);
+ background: var(--background-modifier-border, #3a3a3a);
+ border-style: dashed;
+ opacity: 1;
}
diff --git a/src/ui/PodcastView/TopBar.test.ts b/src/ui/PodcastView/TopBar.test.ts
index cf441cd..07a5b8c 100644
--- a/src/ui/PodcastView/TopBar.test.ts
+++ b/src/ui/PodcastView/TopBar.test.ts
@@ -15,14 +15,19 @@ describe("TopBar", () => {
});
const grid = getByLabelText("Podcast grid");
- const episode = getByLabelText("Episode list");
- const player = getByLabelText("Player");
+ const episode = getByLabelText(/Episode list/);
+ const player = getByLabelText(/Player/);
expect(grid.className).toContain("topbar-selected");
expect(episode.className).toContain("topbar-selectable");
expect(episode).not.toBeDisabled();
expect(player).toBeDisabled();
expect(player.className).not.toContain("topbar-selectable");
+ expect(player.className).toContain("topbar-disabled");
+ expect(player.getAttribute("title")).toBe(
+ "Start playing an episode to open the player."
+ );
+ expect(episode.getAttribute("title")).toBe("View episode list");
});
test("activates episode list when clicked", async () => {
@@ -34,8 +39,8 @@ describe("TopBar", () => {
},
});
- const episodeButton = getByLabelText("Episode list");
- const playerButton = getByLabelText("Player");
+ const episodeButton = getByLabelText(/Episode list/);
+ const playerButton = getByLabelText(/Player/);
await fireEvent.click(episodeButton);
@@ -52,12 +57,20 @@ describe("TopBar", () => {
},
});
- const episodeButton = getByLabelText("Episode list");
- const playerButton = getByLabelText("Player");
+ const episodeButton = getByLabelText(/Episode list/);
+ const playerButton = getByLabelText(/Player/);
expect(episodeButton).toBeDisabled();
expect(playerButton).toBeDisabled();
expect(episodeButton.className).not.toContain("topbar-selectable");
+ expect(episodeButton.className).toContain("topbar-disabled");
+ expect(playerButton.className).toContain("topbar-disabled");
+ expect(episodeButton.getAttribute("title")).toBe(
+ "Select a podcast or playlist to view its episodes."
+ );
+ expect(playerButton.getAttribute("title")).toBe(
+ "Start playing an episode to open the player."
+ );
});
test("activates player control when clicked", async () => {
@@ -69,7 +82,7 @@ describe("TopBar", () => {
},
});
- const playerButton = getByLabelText("Player");
+ const playerButton = getByLabelText(/Player/);
await fireEvent.click(playerButton);
diff --git a/src/ui/common/Image.svelte b/src/ui/common/Image.svelte
index 2d1b850..7b76f54 100644
--- a/src/ui/common/Image.svelte
+++ b/src/ui/common/Image.svelte
@@ -6,7 +6,7 @@
export let fadeIn: boolean = false;
export let opacity: number = 0; // Falsey value so condition isn't triggered if not set.
export let interactive: boolean = false;
- export let loading: "lazy" | "eager" | null | undefined = undefined;
+ export let loading: "lazy" | "eager" | null | undefined = "lazy";
export {_class as class};
let _class = "";
diff --git a/src/ui/common/ImageLoader.svelte b/src/ui/common/ImageLoader.svelte
index ee66b3f..d08bcb3 100644
--- a/src/ui/common/ImageLoader.svelte
+++ b/src/ui/common/ImageLoader.svelte
@@ -1,5 +1,4 @@
-
-
-
-
-
-