refactor season search code
This commit is contained in:
parent
418601d2d1
commit
faeca3459c
1 changed files with 148 additions and 96 deletions
194
src/main.ts
194
src/main.ts
|
|
@ -22,6 +22,7 @@ import { ConfirmOverwriteModal } from './modals/ConfirmOverwriteModal';
|
||||||
import type { SeasonSelectModalElement } from './modals/MediaDbSeasonSelectModal';
|
import type { SeasonSelectModalElement } from './modals/MediaDbSeasonSelectModal';
|
||||||
import { MediaDbSeasonSelectModal } from './modals/MediaDbSeasonSelectModal';
|
import { MediaDbSeasonSelectModal } from './modals/MediaDbSeasonSelectModal';
|
||||||
import type { MediaTypeModel } from './models/MediaTypeModel';
|
import type { MediaTypeModel } from './models/MediaTypeModel';
|
||||||
|
import type { SeasonModel } from './models/SeasonModel';
|
||||||
import { PropertyMapper } from './settings/PropertyMapper';
|
import { PropertyMapper } from './settings/PropertyMapper';
|
||||||
import { PropertyMappingModel } from './settings/PropertyMapping';
|
import { PropertyMappingModel } from './settings/PropertyMapping';
|
||||||
import type { MediaDbPluginSettings } from './settings/Settings';
|
import type { MediaDbPluginSettings } from './settings/Settings';
|
||||||
|
|
@ -203,87 +204,125 @@ export default class MediaDbPlugin extends Plugin {
|
||||||
types = searchModalData.types;
|
types = searchModalData.types;
|
||||||
const apis = this.apiManager.apis.filter(x => x.hasTypeOverlap(searchModalData.types)).map(x => x.apiName);
|
const apis = this.apiManager.apis.filter(x => x.hasTypeOverlap(searchModalData.types)).map(x => x.apiName);
|
||||||
try {
|
try {
|
||||||
console.log(apis);
|
|
||||||
return await this.apiManager.query(searchModalData.query, apis);
|
return await this.apiManager.query(searchModalData.query, apis);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn('MDB | Query failed:', e);
|
||||||
|
new Notice(`Search failed: ${e}`);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!apiSearchResults) {
|
if (!apiSearchResults || apiSearchResults.length === 0) {
|
||||||
// TODO: add new notice saying no results found?
|
new Notice('No results found.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter the results
|
// filter the results
|
||||||
apiSearchResults = apiSearchResults.filter(x => types.contains(x.type));
|
apiSearchResults = apiSearchResults.filter(x => types.contains(x.type));
|
||||||
|
|
||||||
let selectResults: MediaTypeModel[];
|
if (apiSearchResults.length === 0) {
|
||||||
const proceed: boolean = false;
|
new Notice('No results found for the selected types.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
while (!proceed) {
|
// Show selection modal - for seasons, skip detail query
|
||||||
if (types.length === 1 && types[0] === 'season') {
|
const selectResults =
|
||||||
selectResults =
|
types.length === 1 && types[0] === 'season'
|
||||||
(await this.modalHelper.openSelectModal(
|
? await this.modalHelper.openSelectModal(
|
||||||
{
|
{
|
||||||
elements: apiSearchResults,
|
elements: apiSearchResults,
|
||||||
description: 'Select one search result to proceed.',
|
description: 'Select one search result to proceed.',
|
||||||
submitButtonText: 'Ok',
|
submitButtonText: 'Ok',
|
||||||
},
|
},
|
||||||
async selectModalData => {
|
async selectModalData => selectModalData.selected,
|
||||||
return selectModalData.selected;
|
)
|
||||||
},
|
: await this.modalHelper.openSelectModal({ elements: apiSearchResults }, async selectModalData => this.queryDetails(selectModalData.selected));
|
||||||
)) ?? [];
|
|
||||||
} else {
|
if (!selectResults || selectResults.length === 0) {
|
||||||
selectResults =
|
|
||||||
(await this.modalHelper.openSelectModal(
|
|
||||||
{
|
|
||||||
elements: apiSearchResults,
|
|
||||||
},
|
|
||||||
async selectModalData => {
|
|
||||||
return await this.queryDetails(selectModalData.selected);
|
|
||||||
},
|
|
||||||
)) ?? [];
|
|
||||||
}
|
|
||||||
if (!selectResults || selectResults.length < 1) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only show the season select modal if the user searches for seasons
|
// Handle season selection for both direct season searches and series-to-season conversion
|
||||||
if (await this.handleSeasonSelectModal(types, selectResults)) {
|
const seasonHandlingResult = await this.handleSeasonWorkflow(types, selectResults);
|
||||||
|
if (seasonHandlingResult.handled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirmed = await this.modalHelper.openPreviewModal({ elements: selectResults }, async previewModalData => {
|
// Show preview and confirm
|
||||||
return previewModalData.confirmed;
|
const confirmed = await this.modalHelper.openPreviewModal({ elements: selectResults }, async previewModalData => previewModalData.confirmed);
|
||||||
});
|
|
||||||
if (!confirmed) {
|
if (!confirmed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
// User confirmed, create notes and exit
|
||||||
|
await this.createMediaDbNotes(selectResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.createMediaDbNotes(selectResults!);
|
/**
|
||||||
}
|
* Handles the season workflow for both direct season searches and series-to-season conversion.
|
||||||
|
* Returns an object indicating what happened and how to proceed.
|
||||||
// Season select modal
|
*/
|
||||||
private async handleSeasonSelectModal(types: string[], selectResults: MediaTypeModel[]): Promise<boolean> {
|
private async handleSeasonWorkflow(types: string[], selectResults: MediaTypeModel[]): Promise<{ handled: boolean; seasonsCreated?: boolean }> {
|
||||||
|
// Case 1: User searched specifically for seasons and selected a series from TMDB
|
||||||
if (types.length === 1 && types[0] === 'season' && selectResults.length === 1 && selectResults[0].dataSource === 'TMDBSeasonAPI') {
|
if (types.length === 1 && types[0] === 'season' && selectResults.length === 1 && selectResults[0].dataSource === 'TMDBSeasonAPI') {
|
||||||
// Use static import for the modal
|
const created = await this.showSeasonSelectAndCreate(selectResults[0].id, selectResults[0].englishTitle || selectResults[0].title);
|
||||||
|
return { handled: true, seasonsCreated: created };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 2: User searched for series but it's actually from TMDBSeasonAPI
|
||||||
|
// (This happens when searching for seasons returns series results)
|
||||||
|
if (types.includes('series') && selectResults.some(r => r.dataSource === 'TMDBSeriesAPI')) {
|
||||||
|
const seriesResults = selectResults.filter(r => r.dataSource === 'TMDBSeriesAPI');
|
||||||
|
// If only one series result and user searched for seasons, show season selection
|
||||||
|
if (seriesResults.length === 1 && types.includes('season')) {
|
||||||
|
const created = await this.showSeasonSelectAndCreate(seriesResults[0].id, seriesResults[0].title);
|
||||||
|
return { handled: true, seasonsCreated: created };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { handled: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the season selection modal for a given series and creates notes for selected seasons.
|
||||||
|
* Returns true if seasons were successfully created, false if cancelled.
|
||||||
|
*/
|
||||||
|
private async showSeasonSelectAndCreate(seriesId: string, seriesTitle: string): Promise<boolean> {
|
||||||
const tmdbSeasonAPI = this.apiManager.getApiByName('TMDBSeasonAPI') as TMDBSeasonAPI;
|
const tmdbSeasonAPI = this.apiManager.getApiByName('TMDBSeasonAPI') as TMDBSeasonAPI;
|
||||||
if (!tmdbSeasonAPI) {
|
if (!tmdbSeasonAPI) {
|
||||||
new Notice('TMDBSeasonAPI not found.');
|
new Notice('TMDBSeasonAPI not available.');
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
// Fetch all seasons for the selected series
|
// Fetch all seasons for the selected series
|
||||||
const allSeasons = await tmdbSeasonAPI.getSeasonsForSeries(selectResults[0].id);
|
const allSeasons = await tmdbSeasonAPI.getSeasonsForSeries(seriesId);
|
||||||
if (!allSeasons || allSeasons.length === 0) {
|
if (!allSeasons || allSeasons.length === 0) {
|
||||||
new Notice('No seasons found for this series.');
|
new Notice('No seasons found for this series.');
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
// Pass the original series title from the search result
|
|
||||||
const seriesName = selectResults[0]?.englishTitle || selectResults[0]?.title || '';
|
// Show season selection modal
|
||||||
|
const selectedSeasons = await this.showSeasonSelectModal(allSeasons, seriesTitle);
|
||||||
|
if (!selectedSeasons || selectedSeasons.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create notes for all selected seasons in parallel
|
||||||
|
await this.createNotesForSelectedSeasons(selectedSeasons, allSeasons, tmdbSeasonAPI);
|
||||||
|
new Notice(`Successfully created ${selectedSeasons.length} season ${selectedSeasons.length === 1 ? 'entry' : 'entries'}.`);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('MDB | Error in season selection workflow:', e);
|
||||||
|
new Notice(`Error loading seasons: ${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the season selection modal and returns the selected seasons.
|
||||||
|
*/
|
||||||
|
private async showSeasonSelectModal(allSeasons: SeasonModel[], seriesTitle: string): Promise<SeasonSelectModalElement[] | undefined> {
|
||||||
const modal = new MediaDbSeasonSelectModal(
|
const modal = new MediaDbSeasonSelectModal(
|
||||||
this,
|
this,
|
||||||
allSeasons.map(s => ({
|
allSeasons.map(s => ({
|
||||||
|
|
@ -294,34 +333,34 @@ export default class MediaDbPlugin extends Plugin {
|
||||||
poster_path: s.image,
|
poster_path: s.image,
|
||||||
})),
|
})),
|
||||||
true,
|
true,
|
||||||
seriesName,
|
seriesTitle,
|
||||||
);
|
);
|
||||||
const selectedSeasons: SeasonSelectModalElement[] = await new Promise(resolve => {
|
|
||||||
|
return new Promise(resolve => {
|
||||||
modal.setSubmitCb(resolve);
|
modal.setSubmitCb(resolve);
|
||||||
modal.open();
|
modal.open();
|
||||||
});
|
});
|
||||||
if (!selectedSeasons || selectedSeasons.length === 0) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
// Fetch full metadata for each selected season and create the note
|
|
||||||
|
/**
|
||||||
|
* Creates notes for all selected seasons by fetching full metadata and creating entries.
|
||||||
|
*/
|
||||||
|
private async createNotesForSelectedSeasons(selectedSeasons: SeasonSelectModalElement[], allSeasons: SeasonModel[], tmdbSeasonAPI: TMDBSeasonAPI): Promise<void> {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
selectedSeasons.map(async season => {
|
selectedSeasons.map(async selectedSeason => {
|
||||||
const orig = allSeasons.find(s => s.seasonNumber === season.season_number);
|
const seasonModel = allSeasons.find(s => s.seasonNumber === selectedSeason.season_number);
|
||||||
if (orig) {
|
if (seasonModel) {
|
||||||
|
try {
|
||||||
// Fetch full metadata using getById
|
// Fetch full metadata using getById
|
||||||
const tmdbSeasonAPI = this.apiManager.getApiByName('TMDBSeasonAPI') as TMDBSeasonAPI;
|
const fullMetadata = await tmdbSeasonAPI.getById(seasonModel.id);
|
||||||
if (tmdbSeasonAPI) {
|
await this.createMediaDbNotes([fullMetadata]);
|
||||||
const fullMeta = await tmdbSeasonAPI.getById(orig.id);
|
} catch (e) {
|
||||||
await this.createMediaDbNotes([fullMeta]);
|
console.warn(`MDB | Failed to create season ${selectedSeason.season_number}:`, e);
|
||||||
} else {
|
new Notice(`Failed to create season ${selectedSeason.season_number}: ${e}`);
|
||||||
await this.createMediaDbNotes([orig]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async createEntryWithAdvancedSearchModal(): Promise<void> {
|
async createEntryWithAdvancedSearchModal(): Promise<void> {
|
||||||
|
|
@ -329,8 +368,8 @@ export default class MediaDbPlugin extends Plugin {
|
||||||
return await this.apiManager.query(advancedSearchModalData.query, advancedSearchModalData.apis);
|
return await this.apiManager.query(advancedSearchModalData.query, advancedSearchModalData.apis);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!apiSearchResults) {
|
if (!apiSearchResults || apiSearchResults.length === 0) {
|
||||||
// TODO: add new notice saying no results found?
|
new Notice('No results found.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -382,19 +421,32 @@ export default class MediaDbPlugin extends Plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
async createMediaDbNotes(models: MediaTypeModel[], attachFile?: TFile): Promise<void> {
|
async createMediaDbNotes(models: MediaTypeModel[], attachFile?: TFile): Promise<void> {
|
||||||
for (const model of models) {
|
// Create notes in parallel for better performance
|
||||||
await this.createMediaDbNoteFromModel(model, { attachTemplate: true, attachFile: attachFile });
|
const results = await Promise.allSettled(models.map(model => this.createMediaDbNoteFromModel(model, { attachTemplate: true, attachFile: attachFile })));
|
||||||
|
|
||||||
|
// Report any failures
|
||||||
|
const failures = results.filter(r => r.status === 'rejected');
|
||||||
|
if (failures.length > 0) {
|
||||||
|
console.warn('MDB | Some notes failed to create:', failures);
|
||||||
|
new Notice(`${models.length - failures.length} of ${models.length} notes created successfully.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async queryDetails(models: MediaTypeModel[]): Promise<MediaTypeModel[]> {
|
async queryDetails(models: MediaTypeModel[]): Promise<MediaTypeModel[]> {
|
||||||
const detailModels: MediaTypeModel[] = [];
|
// Query details in parallel for better performance
|
||||||
for (const model of models) {
|
const results = await Promise.allSettled(models.map(model => this.apiManager.queryDetailedInfo(model)));
|
||||||
const res = await this.apiManager.queryDetailedInfo(model);
|
|
||||||
if (res) {
|
// Filter out failures and return successful results
|
||||||
detailModels.push(res);
|
const detailModels: MediaTypeModel[] = results
|
||||||
}
|
.filter((r): r is PromiseFulfilledResult<MediaTypeModel | undefined> => r.status === 'fulfilled' && r.value !== undefined)
|
||||||
|
.map(r => r.value!);
|
||||||
|
|
||||||
|
// Log failures for debugging
|
||||||
|
const failures = results.filter(r => r.status === 'rejected');
|
||||||
|
if (failures.length > 0) {
|
||||||
|
console.warn('MDB | Some detail queries failed:', failures);
|
||||||
}
|
}
|
||||||
|
|
||||||
return detailModels;
|
return detailModels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue