diff --git a/src/api/APIManager.ts b/src/api/APIManager.ts index 841d447..665e8a2 100644 --- a/src/api/APIManager.ts +++ b/src/api/APIManager.ts @@ -42,6 +42,16 @@ export class APIManager { } } + getApiByName(name: string): APIModel { + for (const api of this.apis) { + if (api.apiName === name) { + return api; + } + } + + return null; + } + registerAPI(api: APIModel): void { this.apis.push(api); } diff --git a/src/api/APIModel.ts b/src/api/APIModel.ts index 4d042c8..60f223d 100644 --- a/src/api/APIModel.ts +++ b/src/api/APIModel.ts @@ -1,30 +1,30 @@ import {MediaTypeModel} from '../models/MediaTypeModel'; export abstract class APIModel { - apiName: string; - apiUrl: string; - apiDescription: string; - types: string[]; + apiName: string; + apiUrl: string; + apiDescription: string; + types: string[]; - /** - * This function should query the api and return a list of matches. The matches should be caped at 20. - * - * @param title the title to query for - */ - abstract searchByTitle(title: string): Promise; + /** + * This function should query the api and return a list of matches. The matches should be caped at 20. + * + * @param title the title to query for + */ + abstract searchByTitle(title: string): Promise; - abstract getById(item: MediaTypeModel): Promise; + abstract getById(item: MediaTypeModel): Promise; - hasType(type: string): boolean { - return this.types.contains(type); - } + hasType(type: string): boolean { + return this.types.contains(type); + } - hasTypeOverlap(types: string[]): boolean { - for (const type of types) { - if (this.hasType(type)) { - return true; - } - } - return false; - } + hasTypeOverlap(types: string[]): boolean { + for (const type of types) { + if (this.hasType(type)) { + return true; + } + } + return false; + } } diff --git a/src/api/apis/MALAPI.ts b/src/api/apis/MALAPI.ts index 197efe5..13dbc8f 100644 --- a/src/api/apis/MALAPI.ts +++ b/src/api/apis/MALAPI.ts @@ -77,6 +77,7 @@ export class MALAPI extends APIModel { title: result.title, year: result.year ?? result.aired?.prop?.from?.year ?? '', dataSource: this.apiName, + url: result.url, id: result.mal_id, genres: result.genres?.map((x: any) => x.name) ?? [], @@ -100,6 +101,7 @@ export class MALAPI extends APIModel { title: result.title, year: result.year ?? result.aired?.prop?.from?.year ?? '', dataSource: this.apiName, + url: result.url, id: result.mal_id, genres: result.genres?.map((x: any) => x.name) ?? [], diff --git a/src/api/apis/OMDbAPI.ts b/src/api/apis/OMDbAPI.ts index 173ae37..d3df2f1 100644 --- a/src/api/apis/OMDbAPI.ts +++ b/src/api/apis/OMDbAPI.ts @@ -32,6 +32,10 @@ export class OMDbAPI extends APIModel { } const data = await fetchData.json(); + if (data.Response === 'False') { + throw Error(`Received error from ${this.apiName}: ${data.Error}`); + } + if (!data.Search) { return []; } @@ -76,14 +80,20 @@ export class OMDbAPI extends APIModel { } const result = await fetchData.json(); + console.log(result); + if (result.Response === 'False') { + throw Error(`Received error from ${this.apiName}: ${result.Error}`); + } + if (result.Type === 'movie') { const model = new MovieModel({ type: 'movie', title: result.Title, year: result.Year, dataSource: this.apiName, + url: `https://www.imdb.com/title/${result.imdbID}/`, id: result.imdbID, genres: result.Genre?.split(', ') ?? [], @@ -107,6 +117,7 @@ export class OMDbAPI extends APIModel { title: result.Title, year: result.Year, dataSource: this.apiName, + url: `https://www.imdb.com/title/${result.imdbID}/`, id: result.imdbID, genres: result.Genre?.split(', ') ?? [], diff --git a/src/main.ts b/src/main.ts index f5cd501..9ff174d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,6 +8,7 @@ import {OMDbAPI} from './api/apis/OMDbAPI'; import {MediaDbAdvancedSearchModal} from './modals/MediaDbAdvancedSearchModal'; import {MediaDbSearchResultModal} from './modals/MediaDbSearchResultModal'; import {MALAPI} from './api/apis/MALAPI'; +import {MediaDbIdSearchModal} from './modals/MediaDbIdSearchModal'; export default class MediaDbPlugin extends Plugin { settings: MediaDbPluginSettings; @@ -18,7 +19,7 @@ export default class MediaDbPlugin extends Plugin { // add icon to the left ribbon const ribbonIconEl = this.addRibbonIcon('database', 'Add new Media DB entry', (evt: MouseEvent) => - this.createMediaDbNote(), + this.createMediaDbNote(this.openMediaDbSearchModal.bind(this)), ); ribbonIconEl.addClass('obsidian-media-db-plugin-ribbon-class'); @@ -26,7 +27,13 @@ export default class MediaDbPlugin extends Plugin { this.addCommand({ id: 'open-media-db-search-modal', name: 'Add new Media DB entry', - callback: () => this.createMediaDbNote(), + callback: () => this.createMediaDbNote(this.openMediaDbSearchModal.bind(this)), + }); + // register command to open id search modal + this.addCommand({ + id: 'open-media-db-id-search-modal', + name: 'Add new Media DB entry by id', + callback: () => this.createMediaDbNote(this.openMediaDbIdSearchModal.bind(this)), }); // register the settings tab @@ -40,9 +47,9 @@ export default class MediaDbPlugin extends Plugin { this.apiManager.registerAPI(new MALAPI(this)); } - async createMediaDbNote(): Promise { + async createMediaDbNote(modal: () => Promise): Promise { try { - let data: MediaTypeModel = await this.openMediaDbSearchModal(); + let data: MediaTypeModel = await modal(); console.log('MDB | Creating new note...'); data = await this.apiManager.queryDetailedInfo(data); @@ -91,13 +98,22 @@ export default class MediaDbPlugin extends Plugin { new MediaDbAdvancedSearchModal(this.app, this.apiManager, (err, results) => { if (err) return reject(err); new MediaDbSearchResultModal(this.app, results, (err2, res) => { - if (err) return reject(err2); + if (err2) return reject(err2); resolve(res); }).open(); }).open(); })); } + async openMediaDbIdSearchModal(): Promise { + return new Promise(((resolve, reject) => { + new MediaDbIdSearchModal(this.app, this.apiManager, (err, res) => { + if (err) return reject(err); + resolve(res); + }).open(); + })); + } + async loadSettings() { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); } diff --git a/src/modals/MediaDbIdSearchModal.ts b/src/modals/MediaDbIdSearchModal.ts new file mode 100644 index 0000000..bf7b5ce --- /dev/null +++ b/src/modals/MediaDbIdSearchModal.ts @@ -0,0 +1,111 @@ +import {App, ButtonComponent, DropdownComponent, Modal, Notice, Setting, TextComponent} from 'obsidian'; +import {MediaTypeModel} from '../models/MediaTypeModel'; +import {APIManager} from '../api/APIManager'; + +export class MediaDbIdSearchModal extends Modal { + query: string; + isBusy: boolean; + apiManager: APIManager; + searchBtn: ButtonComponent; + selectedApi: string; + onSubmit: (err: Error, result?: MediaTypeModel) => void; + + constructor(app: App, apiManager: APIManager, onSubmit?: (err: Error, result?: MediaTypeModel) => void) { + super(app); + this.apiManager = apiManager; + this.onSubmit = onSubmit; + this.selectedApi = ''; + } + + submitCallback(event: KeyboardEvent) { + if (event.key === 'Enter') { + this.search(); + } + } + + async search(): Promise { + + console.log(this.selectedApi); + + if (!this.query) { + new Notice('MDB: no Id entered'); + return; + } + + if (!this.selectedApi) { + new Notice('MDB: No API selected'); + return; + } + + if (!this.isBusy) { + try { + this.isBusy = true; + this.searchBtn.setDisabled(false); + this.searchBtn.setButtonText('Searching...'); + + console.log('MDB | query started with id ' + this.query); + + const api = this.apiManager.getApiByName(this.selectedApi); + if (!api) { + this.onSubmit(new Error('the selected api does not exist')); + } + const res = await api.getById({id: this.query} as MediaTypeModel); // TODO: fix jank + + // console.log(res) + + this.onSubmit(null, res); + } catch (e) { + this.onSubmit(e); + } finally { + this.close(); + } + } + } + + onOpen() { + const {contentEl} = this; + + contentEl.createEl('h2', {text: 'Search media db by id'}); + + const placeholder = 'Search by id'; + const searchComponent = new TextComponent(contentEl); + searchComponent.inputEl.style.width = '100%'; + searchComponent.setPlaceholder(placeholder); + searchComponent.onChange(value => (this.query = value)); + searchComponent.inputEl.addEventListener('keydown', this.submitCallback.bind(this)); + + contentEl.appendChild(searchComponent.inputEl); + searchComponent.inputEl.focus(); + + const apiSelectorWrapper = contentEl.createEl('div', {cls: 'media-db-plugin-list-wrapper'}); + const apiSelectorTExtWrapper = apiSelectorWrapper.createEl('div', {cls: 'media-db-plugin-list-text-wrapper'}); + apiSelectorTExtWrapper.createEl('span', {text: 'API to search', cls: 'media-db-plugin-list-text'}); + + const apiSelectorComponent = new DropdownComponent(apiSelectorWrapper); + apiSelectorComponent.onChange((value: string) => { + this.selectedApi = value; + }); + for (const api of this.apiManager.apis) { + apiSelectorComponent.addOption(api.apiName, api.apiName); + } + apiSelectorWrapper.appendChild(apiSelectorComponent.selectEl); + + new Setting(contentEl) + .addButton(btn => btn.setButtonText('Cancel').onClick(() => this.close())) + .addButton(btn => { + return (this.searchBtn = btn + .setButtonText('Ok') + .setCta() + .onClick(() => { + this.search(); + })); + }); + } + + onClose() { + const {contentEl} = this; + contentEl.empty(); + } + + +} diff --git a/src/models/MediaTypeModel.ts b/src/models/MediaTypeModel.ts index e0ed5a1..c54829c 100644 --- a/src/models/MediaTypeModel.ts +++ b/src/models/MediaTypeModel.ts @@ -3,6 +3,7 @@ export abstract class MediaTypeModel { title: string; year: string; dataSource: string; + url: string; id: string; abstract toMetaData(): string; diff --git a/src/models/MovieModel.ts b/src/models/MovieModel.ts index 4d3cb4e..8d5987a 100644 --- a/src/models/MovieModel.ts +++ b/src/models/MovieModel.ts @@ -7,6 +7,7 @@ export class MovieModel extends MediaTypeModel { title: string; year: string; dataSource: string; + url: string; id: string; genres: string[]; diff --git a/src/models/SeriesModel.ts b/src/models/SeriesModel.ts index 289d891..1112548 100644 --- a/src/models/SeriesModel.ts +++ b/src/models/SeriesModel.ts @@ -7,6 +7,7 @@ export class SeriesModel extends MediaTypeModel { title: string; year: string; dataSource: string; + url: string; id: string; genres: string[]; diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 58c83c3..4fb0c12 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -1,8 +1,8 @@ import {App, PluginSettingTab, Setting} from 'obsidian'; import MediaDbPlugin from '../main'; -import {FolderSuggest} from './suggesters/FolderSuggester'; -import {FileSuggest} from './suggesters/FileSuggester'; +import {FolderSuggest} from './suggesters/FolderSuggest'; +import {FileSuggest} from './suggesters/FileSuggest'; export interface MediaDbPluginSettings { diff --git a/src/settings/suggesters/FileSuggester.ts b/src/settings/suggesters/FileSuggest.ts similarity index 100% rename from src/settings/suggesters/FileSuggester.ts rename to src/settings/suggesters/FileSuggest.ts diff --git a/src/settings/suggesters/FolderSuggester.ts b/src/settings/suggesters/FolderSuggest.ts similarity index 100% rename from src/settings/suggesters/FolderSuggester.ts rename to src/settings/suggesters/FolderSuggest.ts diff --git a/styles.css b/styles.css index 3a16e9f..272d000 100644 --- a/styles.css +++ b/styles.css @@ -2,6 +2,7 @@ display: flex; align-content: center; margin-bottom: 5px; + margin-top: 5px; } .media-db-plugin-list-toggle {