From 6dc813a14c90deb51b54634cab571ba405150a91 Mon Sep 17 00:00:00 2001 From: mProjectsCode Date: Thu, 5 May 2022 20:55:54 +0200 Subject: [PATCH] New Search Modal --- src/api/APIManager.ts | 12 ++- src/api/apis/OMDbAPI.ts | 4 +- src/api/apis/TestAPI.ts | 4 +- src/main.ts | 51 ++++++----- src/modals/MediaDbAdvancedSearchModal.ts | 110 +++++++++++++++++++++++ src/modals/MediaDbSearchResultModal.ts | 31 +++++++ src/models/MovieModel.ts | 4 +- src/utils/Utils.ts | 2 +- styles.css | 13 +++ 9 files changed, 203 insertions(+), 28 deletions(-) create mode 100644 src/modals/MediaDbAdvancedSearchModal.ts create mode 100644 src/modals/MediaDbSearchResultModal.ts diff --git a/src/api/APIManager.ts b/src/api/APIManager.ts index d25494d..841d447 100644 --- a/src/api/APIManager.ts +++ b/src/api/APIManager.ts @@ -8,11 +8,20 @@ export class APIManager { this.apis = []; } - async query(query: string, types: string[] = []): Promise { + async query(query: string, apisToQuery: any): Promise { console.log('MDB | api manager queried'); let res: MediaTypeModel[] = []; + for (const api of this.apis) { + if (Object.keys(apisToQuery).contains(api.apiName) && apisToQuery[api.apiName]) { + const apiRes = await api.searchByTitle(query); + // console.log(apiRes); + res = res.concat(apiRes); + } + } + + /* for (const api of this.apis) { if (types.length === 0 || api.hasTypeOverlap(types)) { const apiRes = await api.searchByTitle(query); @@ -20,6 +29,7 @@ export class APIManager { res = res.concat(apiRes); } } + */ return res; } diff --git a/src/api/apis/OMDbAPI.ts b/src/api/apis/OMDbAPI.ts index 16fe7d5..02af6f6 100644 --- a/src/api/apis/OMDbAPI.ts +++ b/src/api/apis/OMDbAPI.ts @@ -36,7 +36,7 @@ export class OMDbAPI extends APIModel { for (const value of data.Search) { if (value.Type === 'movie') { - ret.push(new MovieModel({title: value.Title, id: value.imdbID, dataSource: this.apiName, type: 'movie'} as MovieModel)) + ret.push(new MovieModel({title: value.Title, id: value.imdbID, dataSource: this.apiName, type: 'movie'} as MovieModel)); } } @@ -61,6 +61,6 @@ export class OMDbAPI extends APIModel { return model; } - return ; + return; } } diff --git a/src/api/apis/TestAPI.ts b/src/api/apis/TestAPI.ts index d9b995c..9b52cbf 100644 --- a/src/api/apis/TestAPI.ts +++ b/src/api/apis/TestAPI.ts @@ -14,8 +14,8 @@ export class TestAPI extends APIModel { console.log(`MDB | api "${this.apiName}" queried`); return [ - new MovieModel ({title: 'test_1', type: this.types[0], dataSource: this.apiName}) , - new MovieModel ({title: 'test_2', type: this.types[0], dataSource: this.apiName, producer: 'Max Musterman'}), + new MovieModel({title: 'test_1', type: this.types[0], dataSource: this.apiName}), + new MovieModel({title: 'test_2', type: this.types[0], dataSource: this.apiName, producer: 'Max Musterman'}), ]; } diff --git a/src/main.ts b/src/main.ts index bc9eb39..107aa80 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,11 +1,12 @@ -import {Plugin} from 'obsidian'; +import {Notice, Plugin} from 'obsidian'; import {DEFAULT_SETTINGS, MediaDbPluginSettings, MediaDbSettingTab} from './settings/Settings'; -import {MediaDbSearchModal} from './modals/MediaDbSearchModal'; import {APIManager} from './api/APIManager'; import {TestAPI} from './api/apis/TestAPI'; import {MediaTypeModel} from './models/MediaTypeModel'; import {getFileName} from './utils/Utils'; import {OMDbAPI} from './api/apis/OMDbAPI'; +import {MediaDbAdvancedSearchModal} from './modals/MediaDbAdvancedSearchModal'; +import {MediaDbSearchResultModal} from './modals/MediaDbSearchResultModal'; export default class MediaDbPlugin extends Plugin { settings: MediaDbPluginSettings; @@ -15,7 +16,7 @@ export default class MediaDbPlugin extends Plugin { await this.loadSettings(); // add icon to the left ribbon - const ribbonIconEl = this.addRibbonIcon('book', 'Add new Media DB entry', (evt: MouseEvent) => + const ribbonIconEl = this.addRibbonIcon('database', 'Add new Media DB entry', (evt: MouseEvent) => this.createMediaDbNote(), ); ribbonIconEl.addClass('obsidian-media-db-plugin-ribbon-class'); @@ -38,35 +39,43 @@ export default class MediaDbPlugin extends Plugin { } async createMediaDbNote(): Promise { - let data: MediaTypeModel = await this.openMediaDbSearchModal(); - console.log('MDB | Create new note or something...'); + try { + let data: MediaTypeModel = await this.openMediaDbSearchModal(); + console.log('MDB | Create new note or something...'); - data = await this.apiManager.queryDetailedInfo(data); + data = await this.apiManager.queryDetailedInfo(data); - console.log(data); + console.log(data); - data.toMetaData() + data.toMetaData(); - const fileContent = `---\n${data.toMetaData()}\n---\n`; + const fileContent = `---\n${data.toMetaData()}\n---\n`; - const fileName = getFileName(data); - const filePath = `${this.settings.folder.replace(/\/$/, '')}/${fileName}.md`; - const targetFile = await this.app.vault.create(filePath, fileContent); + const fileName = getFileName(data); + const filePath = `${this.settings.folder.replace(/\/$/, '')}/${fileName}.md`; + const targetFile = await this.app.vault.create(filePath, fileContent); - // open file - const activeLeaf = this.app.workspace.getLeaf(); - if (!activeLeaf) { - console.warn('No active leaf'); - return; + // open file + const activeLeaf = this.app.workspace.getLeaf(); + if (!activeLeaf) { + console.warn('No active leaf'); + return; + } + await activeLeaf.openFile(targetFile, {state: {mode: 'source'}}); + } catch (e) { + console.warn(e); + new Notice(e.toString()); } - await activeLeaf.openFile(targetFile, { state: { mode: 'source' } }); } - async openMediaDbSearchModal(): Promise { + async openMediaDbSearchModal(): Promise { return new Promise(((resolve, reject) => { - new MediaDbSearchModal(this.app, this.apiManager, (err, result) => { + new MediaDbAdvancedSearchModal(this.app, this.apiManager, (err, results) => { if (err) return reject(err); - resolve(result); + new MediaDbSearchResultModal(this.app, results, (err2, res) => { + if (err) return reject(err2); + resolve(res); + }).open(); }).open(); })); } diff --git a/src/modals/MediaDbAdvancedSearchModal.ts b/src/modals/MediaDbAdvancedSearchModal.ts new file mode 100644 index 0000000..77c34b7 --- /dev/null +++ b/src/modals/MediaDbAdvancedSearchModal.ts @@ -0,0 +1,110 @@ +import {App, ButtonComponent, Component, Modal, Setting, TextComponent, ToggleComponent} from 'obsidian'; +import {MediaTypeModel} from '../models/MediaTypeModel'; +import {APIManager} from '../api/APIManager'; + +export class MediaDbAdvancedSearchModal extends Modal { + query: string; + isBusy: boolean; + apiManager: APIManager; + searchBtn: ButtonComponent; + selectedApis: any; + 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.selectedApis = []; + for (const api of this.apiManager.apis) { + this.selectedApis[api.apiName] = true; + } + } + + submitCallback(event: KeyboardEvent) { + if (event.key === 'Enter') { + this.search(); + } + } + + async search(): Promise { + + console.log(this.selectedApis); + + if (!this.query) { + return; + } + + if (!this.isBusy) { + try { + this.isBusy = true; + this.searchBtn.setDisabled(false); + this.searchBtn.setButtonText('Searching...'); + + console.log('MDB | query started with ' + this.query); + + const res = await this.apiManager.query(this.query, this.selectedApis); + + // 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'}); + + const placeholder = 'Search by title'; + 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(); + + contentEl.createEl('h3', {text: 'APIs to search'}); + + const apiToggleComponents: Component[] = []; + for (const api of this.apiManager.apis) { + const apiToggleListElementWrapper = contentEl.createEl('div', {cls: 'media-db-plugin-list-wrapper'}); + + apiToggleListElementWrapper.createEl('span', {text: api.apiName, cls: 'media-db-plugin-list-text'}); + const apiToggleComponentWrapper = apiToggleListElementWrapper.createEl('div', {cls: 'media-db-plugin-list-toggle'}); + + const apiToggleComponent = new ToggleComponent(apiToggleComponentWrapper); + apiToggleComponent.setTooltip(api.apiName); + apiToggleComponent.setValue(this.selectedApis[api.apiName]); + apiToggleComponent.onChange((value) => { + this.selectedApis[api.apiName] = value; + }); + apiToggleComponentWrapper.appendChild(apiToggleComponent.toggleEl); + } + + + 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/modals/MediaDbSearchResultModal.ts b/src/modals/MediaDbSearchResultModal.ts new file mode 100644 index 0000000..0106f02 --- /dev/null +++ b/src/modals/MediaDbSearchResultModal.ts @@ -0,0 +1,31 @@ +import {App, SuggestModal} from 'obsidian'; +import {MediaTypeModel} from '../models/MediaTypeModel'; + +export class MediaDbSearchResultModal extends SuggestModal { + suggestion: MediaTypeModel[]; + onChoose: (error: Error, result?: MediaTypeModel) => void; + + constructor(app: App, suggestion: MediaTypeModel[], onChoose: (error: Error, result?: MediaTypeModel) => void) { + super(app); + this.suggestion = suggestion; + this.onChoose = onChoose; + } + + getSuggestions(query: string): MediaTypeModel[] { + return this.suggestion.filter(item => { + const searchQuery = query.toLowerCase(); + return item.title.toLowerCase().includes(searchQuery); + }); + } + + // Renders each suggestion item. + renderSuggestion(item: MediaTypeModel, el: HTMLElement) { + el.createEl('div', {text: item.title}); + el.createEl('small', {text: `${item.type} from ${item.dataSource}`}); + } + + // Perform action on the selected suggestion. + onChooseSuggestion(item: MediaTypeModel, evt: MouseEvent | KeyboardEvent) { + this.onChoose(null, item); + } +} diff --git a/src/models/MovieModel.ts b/src/models/MovieModel.ts index 877a9d3..7bb3fd3 100644 --- a/src/models/MovieModel.ts +++ b/src/models/MovieModel.ts @@ -1,4 +1,6 @@ import {MediaTypeModel} from './MediaTypeModel'; +import {stringifyYaml} from 'obsidian'; + export class MovieModel extends MediaTypeModel { type: string; @@ -29,7 +31,7 @@ export class MovieModel extends MediaTypeModel { } toMetaData(): string { - return JSON.stringify(this); + return stringifyYaml(this); } } diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 270ae73..80cdb0b 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -4,7 +4,7 @@ export function wrapAround(value: number, size: number): number { return ((value % size) + size) % size; } -export function sleep (ms: number) { +export function sleep(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } diff --git a/styles.css b/styles.css index e69de29..1da0300 100644 --- a/styles.css +++ b/styles.css @@ -0,0 +1,13 @@ +.media-db-plugin-list-wrapper { + display: flex; + align-content: center; + /* margin: 5px; */ +} + +.media-db-plugin-list-toggle { + +} + +.media-db-plugin-list-text { + flex: 1; +}