From 851a3f762dc00d687bd43e86bf2277c3d4a51322 Mon Sep 17 00:00:00 2001 From: Christian Bager Bach Houmann Date: Fri, 8 Jul 2022 12:39:38 +0200 Subject: [PATCH 1/2] refactor: add store for saved feeds --- src/SavedFeedsController.ts | 21 +++++++++++++++++++++ src/episodeStatusController.ts | 26 +++++++++----------------- src/main.ts | 16 ++++++++++++---- src/store/index.ts | 2 ++ src/types/StoreController.ts | 22 ++++++++++++++++++++++ src/ui/PodcastView/PodcastView.svelte | 13 ++++++++++--- src/ui/PodcastView/index.ts | 16 +++------------- 7 files changed, 79 insertions(+), 37 deletions(-) create mode 100644 src/SavedFeedsController.ts create mode 100644 src/types/StoreController.ts diff --git a/src/SavedFeedsController.ts b/src/SavedFeedsController.ts new file mode 100644 index 00000000..b89e680d --- /dev/null +++ b/src/SavedFeedsController.ts @@ -0,0 +1,21 @@ +import { Writable } from "svelte/store"; +import { IPodNotes } from "./types/IPodNotes"; +import { PodcastFeed } from "./types/PodcastFeed"; +import { StoreController } from "./types/StoreController"; + +type TSavedFeedsStoreValue = { [podcastName: string]: PodcastFeed }; + +export class SavedFeedsController extends StoreController { + private plugin: IPodNotes; + + constructor(store: Writable, plugin: IPodNotes) { + super(store) + this.plugin = plugin; + } + + protected onChange(value: TSavedFeedsStoreValue) { + this.plugin.settings.savedFeeds = value; + + this.plugin.saveSettings(); + } +} \ No newline at end of file diff --git a/src/episodeStatusController.ts b/src/episodeStatusController.ts index 8181682c..b197951a 100644 --- a/src/episodeStatusController.ts +++ b/src/episodeStatusController.ts @@ -1,28 +1,20 @@ -import { Unsubscriber } from "svelte/store"; -import { playedEpisodes } from "./store"; +import { Writable } from "svelte/store"; +import { StoreController } from "./types/StoreController"; import { IPodNotes } from "./types/IPodNotes"; import { PlayedEpisode } from "./types/playedEpisode"; -export class EpisodeStatusController { +type TPlayedStoreValue = { [episodeName: string]: PlayedEpisode }; + +export class EpisodeStatusController extends StoreController { private plugin: IPodNotes; - private unsubscribe: Unsubscriber; - constructor(plugin: IPodNotes) { + constructor(store: Writable, plugin: IPodNotes) { + super(store) this.plugin = plugin; } - public on(): EpisodeStatusController { - this.unsubscribe = playedEpisodes.subscribe(this.onStoreUpdate.bind(this)); - return this; - } - - public off(): EpisodeStatusController { - this.unsubscribe(); - return this; - } - - private onStoreUpdate(store: {[episodeName: string]: PlayedEpisode}) { - this.plugin.settings.playedEpisodes = store; + protected onChange(value: TPlayedStoreValue) { + this.plugin.settings.playedEpisodes = value; this.plugin.saveSettings(); } diff --git a/src/main.ts b/src/main.ts index cbf35f23..b0fb6f9b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,5 @@ import FeedParser from 'src/parser/feedParser'; -import { currentEpisode, playedEpisodes } from 'src/store'; +import { currentEpisode, playedEpisodes, savedFeeds } from 'src/store'; import { Notice, Plugin, WorkspaceLeaf } from 'obsidian'; import { API } from 'src/API/API'; import { IAPI } from 'src/API/IAPI'; @@ -10,7 +10,11 @@ import { IPodNotesSettings } from './types/IPodNotesSettings'; import { plugin } from './store'; import { get } from 'svelte/store'; import { IPodNotes } from './types/IPodNotes'; -import { EpisodeStatusController } from './episodeStatusController'; +import { EpisodeStatusController } from './EpisodeStatusController'; +import { StoreController } from './types/StoreController'; +import { PlayedEpisode } from './types/playedEpisode'; +import { PodcastFeed } from './types/PodcastFeed'; +import { SavedFeedsController } from './SavedFeedsController'; export default class PodNotes extends Plugin implements IPodNotes { public api: IAPI; @@ -18,7 +22,8 @@ export default class PodNotes extends Plugin implements IPodNotes { private view: MainView; - private playedEpisodeController: EpisodeStatusController; + private playedEpisodeController: StoreController<{[episodeName: string]: PlayedEpisode}>; + private savedFeedsController: StoreController<{[podcastName: string]: PodcastFeed}>; async onload() { plugin.set(this); @@ -26,8 +31,10 @@ export default class PodNotes extends Plugin implements IPodNotes { await this.loadSettings(); playedEpisodes.set(this.settings.playedEpisodes); + savedFeeds.set(this.settings.savedFeeds); - this.playedEpisodeController = new EpisodeStatusController(this).on(); + this.playedEpisodeController = new EpisodeStatusController(playedEpisodes, this).on(); + this.savedFeedsController = new SavedFeedsController(savedFeeds, this).on(); this.addCommand({ id: 'start-playing', @@ -109,6 +116,7 @@ export default class PodNotes extends Plugin implements IPodNotes { onunload() { this?.playedEpisodeController.off(); + this?.savedFeedsController.off(); } async loadSettings() { diff --git a/src/store/index.ts b/src/store/index.ts index 415908f8..622aaf65 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -2,6 +2,7 @@ import { writable } from 'svelte/store'; import type PodNotes from 'src/main'; import { Episode } from 'src/types/Episode'; import { PlayedEpisode } from 'src/types/playedEpisode'; +import { PodcastFeed } from 'src/types/PodcastFeed'; export const plugin = writable(); export const currentTime = writable(0); @@ -11,3 +12,4 @@ export const isPaused = writable(true); export const playedEpisodes = writable<{ [key: string]: PlayedEpisode; }>({}); +export const savedFeeds = writable<{[podcastName: string]: PodcastFeed}>({}); \ No newline at end of file diff --git a/src/types/StoreController.ts b/src/types/StoreController.ts new file mode 100644 index 00000000..9c99d6e1 --- /dev/null +++ b/src/types/StoreController.ts @@ -0,0 +1,22 @@ +import { Unsubscriber, Writable } from "svelte/store"; + +export abstract class StoreController { + protected unsubscribe: Unsubscriber; + protected store: Writable; + + constructor(store: Writable) { + this.store = store; + } + + public on(): StoreController { + this.unsubscribe = this.store.subscribe(this.onChange.bind(this)); + return this; + } + + public off(): StoreController { + this.unsubscribe(); + return this; + } + + protected abstract onChange(value: T): void; +} diff --git a/src/ui/PodcastView/PodcastView.svelte b/src/ui/PodcastView/PodcastView.svelte index 1c91b8cf..c03213bd 100644 --- a/src/ui/PodcastView/PodcastView.svelte +++ b/src/ui/PodcastView/PodcastView.svelte @@ -1,20 +1,25 @@
@@ -50,7 +57,7 @@ on:clickEpisode={handleClickEpisode} /> {:else if viewState === ViewState.PodcastGrid} - + {/if}
diff --git a/src/ui/PodcastView/index.ts b/src/ui/PodcastView/index.ts index 8310380c..3d8a458f 100644 --- a/src/ui/PodcastView/index.ts +++ b/src/ui/PodcastView/index.ts @@ -1,4 +1,3 @@ -import { PodcastFeed } from 'src/types/PodcastFeed'; import { IPodNotes } from "../../types/IPodNotes"; import { ItemView, WorkspaceLeaf } from "obsidian"; import { VIEW_TYPE } from "../../constants"; @@ -24,7 +23,9 @@ export class MainView extends ItemView { } protected async onOpen(): Promise { - this.render(); + this.PodcastView = new PodcastView({ + target: this.contentEl, + }) } protected async onClose(): Promise { @@ -32,16 +33,5 @@ export class MainView extends ItemView { this.contentEl.empty(); } - - private render() { - const savedFeeds: PodcastFeed[] = Object.values(this.plugin.settings.savedFeeds); - - this.PodcastView = new PodcastView({ - target: this.contentEl, - props: { - feeds: savedFeeds, - } - }) - } } From 282610c6af6eff8eaeed55ef1eb0d70c5d632d3c Mon Sep 17 00:00:00 2001 From: Christian Bager Bach Houmann Date: Fri, 8 Jul 2022 12:42:17 +0200 Subject: [PATCH 2/2] feat: refresh podcast grid on newly added podcasts --- package.json | 4 -- src/ui/PodcastView/PodcastGrid.svelte | 4 +- src/ui/settings/PodNotesSettingsTab.ts | 14 +++- src/ui/settings/PodcastQueryGrid.svelte | 90 ++++++++++++++++++++++++ src/ui/settings/PodcastQueryGrid.ts | 70 ------------------ src/ui/settings/PodcastResultCard.svelte | 57 +++++++++++++++ styles.css | 49 ------------- 7 files changed, 161 insertions(+), 127 deletions(-) create mode 100644 src/ui/settings/PodcastQueryGrid.svelte delete mode 100644 src/ui/settings/PodcastQueryGrid.ts create mode 100644 src/ui/settings/PodcastResultCard.svelte delete mode 100644 styles.css diff --git a/package.json b/package.json index bd340e3a..a674c3b5 100644 --- a/package.json +++ b/package.json @@ -102,10 +102,6 @@ "path": "main.js", "label": "main.js" }, - { - "path": "styles.css", - "label": "styles.css" - }, { "path": "manifest.json", "label": "manifest.json" diff --git a/src/ui/PodcastView/PodcastGrid.svelte b/src/ui/PodcastView/PodcastGrid.svelte index 112f5bd2..9c44efde 100644 --- a/src/ui/PodcastView/PodcastGrid.svelte +++ b/src/ui/PodcastView/PodcastGrid.svelte @@ -11,7 +11,7 @@ } -
+
{#if feeds.length > 0} {#each feeds as feed} diff --git a/src/ui/settings/PodNotesSettingsTab.ts b/src/ui/settings/PodNotesSettingsTab.ts index 79f3ec8d..e76b1eb1 100644 --- a/src/ui/settings/PodNotesSettingsTab.ts +++ b/src/ui/settings/PodNotesSettingsTab.ts @@ -1,9 +1,11 @@ import { App, PluginSettingTab, Setting } from 'obsidian'; import PodNotes from '../../main'; -import { PodcastQueryGrid } from './PodcastQueryGrid'; +import PodcastQueryGrid from './PodcastQueryGrid.svelte'; export class PodNotesSettingsTab extends PluginSettingTab { plugin: PodNotes; + + private podcastQueryGrid: PodcastQueryGrid; constructor(app: App, plugin: PodNotes) { super(app, plugin); @@ -21,8 +23,11 @@ export class PodNotesSettingsTab extends PluginSettingTab { const settingsContainer = containerEl.createDiv(); settingsContainer.classList.add('settings-container'); - PodcastQueryGrid(settingsContainer, this.plugin); - + const queryGridContainer = settingsContainer.createDiv(); + this.podcastQueryGrid = new PodcastQueryGrid({ + target: queryGridContainer + }); + const defaultPlaybackRateSetting = new Setting(this.containerEl); defaultPlaybackRateSetting.setName('Default Playback Rate'); defaultPlaybackRateSetting.addSlider((slider) => slider @@ -36,4 +41,7 @@ export class PodNotesSettingsTab extends PluginSettingTab { ); } + hide(): void { + this.podcastQueryGrid?.$destroy(); + } } diff --git a/src/ui/settings/PodcastQueryGrid.svelte b/src/ui/settings/PodcastQueryGrid.svelte new file mode 100644 index 00000000..b8eed73e --- /dev/null +++ b/src/ui/settings/PodcastQueryGrid.svelte @@ -0,0 +1,90 @@ + + +
+

Search for a podcast

+ + +
+ {#each searchResults as podcast} + + {/each} +
+
+ + \ No newline at end of file diff --git a/src/ui/settings/PodcastQueryGrid.ts b/src/ui/settings/PodcastQueryGrid.ts deleted file mode 100644 index ec1fd5f1..00000000 --- a/src/ui/settings/PodcastQueryGrid.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { IPodNotes } from "../../types/IPodNotes"; -import { ButtonComponent, debounce, TextComponent } from "obsidian"; -import { consume } from "src/iTunesAPIConsumer"; -import { PodcastFeed } from "src/types/PodcastFeed"; - -export function PodcastQueryGrid(container: HTMLElement, plugin: IPodNotes) { - container.empty(); - - container.createEl('h3', { text: 'Search for a podcast' }); - - const inputEl = new TextComponent(container); - inputEl.inputEl.style.marginBottom = '0.5rem'; - inputEl.inputEl.style.width = "100%"; - inputEl.setPlaceholder('Search for a podcast'); - - const debouncedUpdate = debounce(async (value: string) => { - const results = await consume(value); - renderSearchResults(searchResultsContainer, results, plugin); - }, 500, true); - inputEl.onChange(debouncedUpdate); - - const searchResultsContainer = container.createDiv(); - searchResultsContainer.classList.add('search-results-container'); -} - -function renderSearchResults(container: HTMLElement, results: PodcastFeed[], plugin: IPodNotes) { - container.empty(); - - if (results.length === 0) { - const noResults = container.createDiv(); - noResults.innerText = 'No results found'; - return; - } - - container.classList.remove('grid-3'); - container.classList.remove('grid-2'); - container.classList.remove('grid-1'); - - if (results.length % 3 === 0 || results.length > 3) { - container.classList.add('grid-3'); - } else if (results.length % 2 === 0) { - container.classList.add('grid-2'); - } else if (results.length % 1 === 0) { - container.classList.add('grid-1'); - } - - - for (const result of results) { - const resultEl = container.createDiv(); - resultEl.classList.add('result-container'); - - const imageContainer = resultEl.createDiv(); - imageContainer.classList.add('result-image-container'); - const img = imageContainer.createEl('img'); - img.src = result.artworkUrl || ""; - img.addClass('result-podcast-artwork'); - - const title = resultEl.createEl('h4', { text: result.title }); - title.style.textAlign = "center"; - - const buttonEl = new ButtonComponent(resultEl); - buttonEl.setButtonText("Add"); - buttonEl.onClick(() => { - plugin.settings.savedFeeds[result.title] = result; - buttonEl.setDisabled(true); - buttonEl.setButtonText("Added"); - plugin.saveSettings(); - }); - } -} diff --git a/src/ui/settings/PodcastResultCard.svelte b/src/ui/settings/PodcastResultCard.svelte new file mode 100644 index 00000000..ad41327c --- /dev/null +++ b/src/ui/settings/PodcastResultCard.svelte @@ -0,0 +1,57 @@ + + +
+
+ {podcast.title} +
+ +

{podcast.title}

+ + +
+ + \ No newline at end of file diff --git a/styles.css b/styles.css deleted file mode 100644 index 55742703..00000000 --- a/styles.css +++ /dev/null @@ -1,49 +0,0 @@ -.podcast-input-container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - width: 100%; - height: 100%; -} - -/* Grid with 3 rows per column */ -.search-results-container { - width: 100%; - height: 100%; -} - -.grid-3 { - display: grid; - grid-template-columns: repeat(3, 1fr); - grid-gap: 1rem; -} - -.grid-2 { - display: grid; - grid-template-columns: repeat(2, 1fr); - grid-gap: 1rem; -} - -.grid-1 { - display: grid; - grid-template-columns: repeat(1, 1fr); - grid-gap: 1rem; -} - -/* card */ -.result-container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - width: 100%; - height: 100%; -} - -.result-image-container { - width: 100%; - display: flex; - align-items: center; - justify-content: center; -}