Add svelte modal helper

This commit is contained in:
Evan Fiordeliso 2025-06-29 08:49:06 -04:00
parent 8ebed95fda
commit 6ffad2b5d1
5 changed files with 61 additions and 76 deletions

View File

@ -1,8 +1,8 @@
import GoodreadsSearch from "@components/GoodreadsSearch.svelte"; import GoodreadsSearch from "@components/GoodreadsSearch.svelte";
import { type SearchResult } from "@data-sources/goodreads"; import { type SearchResult } from "@data-sources/goodreads";
import { Event, EventEmitter } from "@utils/event"; import { Event, EventEmitter } from "@utils/event";
import { App, Modal, Notice } from "obsidian"; import { App } from "obsidian";
import { mount, unmount } from "svelte"; import { SvelteModal } from "./svelte-modal";
export class SearchEvent extends Event { export class SearchEvent extends Event {
constructor( constructor(
@ -26,13 +26,10 @@ interface GoodreadsSearchModalEventMap {
export class GoodreadsSearchModal extends EventEmitter< export class GoodreadsSearchModal extends EventEmitter<
GoodreadsSearchModalEventMap, GoodreadsSearchModalEventMap,
typeof Modal typeof SvelteModal<typeof GoodreadsSearch>
>(Modal) { >(SvelteModal) {
private component: ReturnType<typeof GoodreadsSearch> | undefined; constructor(app: App) {
super(app, GoodreadsSearch, {
onOpen() {
this.component = mount(GoodreadsSearch, {
target: this.contentEl,
props: { props: {
onError: (error: Error) => { onError: (error: Error) => {
this.emit("error", new ErrorEvent(error)); this.emit("error", new ErrorEvent(error));
@ -44,13 +41,6 @@ export class GoodreadsSearchModal extends EventEmitter<
}); });
} }
onClose() {
if (this.component) {
unmount(this.component);
this.component = undefined;
}
}
static createAndOpen(app: App): Promise<SearchResult[]> { static createAndOpen(app: App): Promise<SearchResult[]> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const modal = new GoodreadsSearchModal(app); const modal = new GoodreadsSearchModal(app);

View File

@ -1,7 +1,7 @@
import Rating from "@components/Rating.svelte"; import Rating from "@components/Rating.svelte";
import { Event, EventEmitter } from "@utils/event"; import { Event, EventEmitter } from "@utils/event";
import { App, Modal } from "obsidian"; import { App } from "obsidian";
import { mount, unmount } from "svelte"; import { SvelteModal } from "./svelte-modal";
class SubmitEvent extends Event { class SubmitEvent extends Event {
constructor(public readonly rating: number) { constructor(public readonly rating: number) {
@ -15,13 +15,10 @@ interface RatingModalEventMap {
export class RatingModal extends EventEmitter< export class RatingModal extends EventEmitter<
RatingModalEventMap, RatingModalEventMap,
typeof Modal typeof SvelteModal<typeof Rating>
>(Modal) { >(SvelteModal) {
private component: ReturnType<typeof Rating> | undefined; constructor(app: App) {
super(app, Rating, {
onOpen(): void {
this.component = mount(Rating, {
target: this.contentEl,
props: { props: {
onSubmit: (rating: number) => onSubmit: (rating: number) =>
this.emit("submit", new SubmitEvent(rating)), this.emit("submit", new SubmitEvent(rating)),
@ -29,13 +26,6 @@ export class RatingModal extends EventEmitter<
}); });
} }
onClose(): void {
if (this.component) {
unmount(this.component);
this.component = undefined;
}
}
static createAndOpen(app: App): Promise<number> { static createAndOpen(app: App): Promise<number> {
return new Promise((resolve) => { return new Promise((resolve) => {
const modal = new RatingModal(app); const modal = new RatingModal(app);

View File

@ -1,10 +1,10 @@
import ReadingLogEntryEditor from "@components/ReadingLogEntryEditor.svelte"; import ReadingLogEntryEditor from "@components/ReadingLogEntryEditor.svelte";
import type { ReadingLogEntry } from "@src/types"; import type { ReadingLogEntry } from "@src/types";
import { Event, EventEmitter } from "@utils/event"; import { Event, EventEmitter } from "@utils/event";
import { App, Modal } from "obsidian"; import { App } from "obsidian";
import { mount, unmount } from "svelte"; import { SvelteModal } from "./svelte-modal";
class SubmitEvent extends Event { export class SubmitEvent extends Event {
constructor(public readonly entry: ReadingLogEntry) { constructor(public readonly entry: ReadingLogEntry) {
super(); super();
} }
@ -16,29 +16,15 @@ interface ReadingLogEntryEditModalEventMap {
export class ReadingLogEntryEditModal extends EventEmitter< export class ReadingLogEntryEditModal extends EventEmitter<
ReadingLogEntryEditModalEventMap, ReadingLogEntryEditModalEventMap,
typeof Modal typeof SvelteModal<typeof ReadingLogEntryEditor>
>(Modal) { >(SvelteModal) {
private component: ReturnType<typeof ReadingLogEntryEditor> | undefined; constructor(app: App, entry: ReadingLogEntry) {
super(app, ReadingLogEntryEditor, {
constructor(app: App, private readonly entry: ReadingLogEntry) {
super(app);
}
onOpen(): void {
this.component = mount(ReadingLogEntryEditor, {
target: this.contentEl,
props: { props: {
entry: this.entry, entry,
onSubmit: (entry: ReadingLogEntry) => onSubmit: (entry: ReadingLogEntry) =>
this.emit("submit", new SubmitEvent(entry)), this.emit("submit", new SubmitEvent(entry)),
}, },
}); });
} }
onClose(): void {
if (this.component) {
unmount(this.component);
this.component = undefined;
}
}
} }

View File

@ -1,9 +1,9 @@
import ReadingProgress from "@components/ReadingProgress.svelte"; import ReadingProgress from "@components/ReadingProgress.svelte";
import { Event, EventEmitter } from "@utils/event"; import { Event, EventEmitter } from "@utils/event";
import { App, Modal } from "obsidian"; import { App } from "obsidian";
import { mount, unmount } from "svelte"; import { SvelteModal } from "./svelte-modal";
class SubmitEvent extends Event { export class SubmitEvent extends Event {
constructor(public readonly pageNumber: number) { constructor(public readonly pageNumber: number) {
super(); super();
} }
@ -15,19 +15,12 @@ interface ReadingProgressModalEventMap {
export class ReadingProgressModal extends EventEmitter< export class ReadingProgressModal extends EventEmitter<
ReadingProgressModalEventMap, ReadingProgressModalEventMap,
typeof Modal typeof SvelteModal<typeof ReadingProgress>
>(Modal) { >(SvelteModal) {
private component: ReturnType<typeof ReadingProgress> | undefined; constructor(app: App, pageLength: number) {
super(app, ReadingProgress, {
constructor(app: App, private readonly pageLength: number) {
super(app);
}
onOpen(): void {
this.component = mount(ReadingProgress, {
target: this.contentEl,
props: { props: {
pageLength: this.pageLength, pageLength,
onSubmit: (pageNumber: number) => { onSubmit: (pageNumber: number) => {
this.emit("submit", new SubmitEvent(pageNumber)); this.emit("submit", new SubmitEvent(pageNumber));
}, },
@ -35,13 +28,6 @@ export class ReadingProgressModal extends EventEmitter<
}); });
} }
onClose(): void {
if (this.component) {
unmount(this.component);
this.component = undefined;
}
}
static createAndOpen(app: App, pageLength: number): Promise<number> { static createAndOpen(app: App, pageLength: number): Promise<number> {
return new Promise((resolve) => { return new Promise((resolve) => {
const modal = new ReadingProgressModal(app, pageLength); const modal = new ReadingProgressModal(app, pageLength);

33
src/views/svelte-modal.ts Normal file
View File

@ -0,0 +1,33 @@
import { App, Modal } from "obsidian";
import { mount, unmount, type Component, type MountOptions } from "svelte";
export class SvelteModal<
TComponent extends Component<TProps, TExports, TBindings>,
TProps extends Record<string, any> = {},
TExports extends Record<string, any> = {},
TBindings extends keyof TProps | "" = string
> extends Modal {
protected component: TExports | undefined;
constructor(
app: App,
private readonly componentCtor: TComponent,
private readonly mountOpts: Omit<MountOptions<TProps>, "target">
) {
super(app);
}
onOpen(): void {
this.component = mount(this.componentCtor, {
...this.mountOpts,
target: this.contentEl,
});
}
onClose(): void {
if (this.component) {
unmount(this.component);
this.component = undefined;
}
}
}