remove solid; rework season search

This commit is contained in:
Moritz Jung 2026-06-03 19:06:03 +02:00
parent ee985419e0
commit 21ad8c59d0
17 changed files with 407 additions and 357 deletions

View file

@ -1,7 +1,7 @@
import type { TFolder } from 'obsidian';
import { TFile } from 'obsidian';
import type MediaDbPlugin from 'packages/obsidian/src/main';
import { MediaDbBulkImportModal as MediaDbBulkImportModal } from 'packages/obsidian/src/modals/MediaDbBulkImportModal';
import { MediaDbBulkImportModal } from 'packages/obsidian/src/modals/MediaDbBulkImportModal';
import type { MediaTypeModel } from 'packages/obsidian/src/models/MediaTypeModel';
import { OutcomeStatus } from 'packages/obsidian/src/utils/result';
import { dateTimeToString, markdownTable } from 'packages/obsidian/src/utils/Utils';
@ -26,7 +26,7 @@ export class BulkImportHelper {
async import(folder: TFolder): Promise<void> {
const erroredFiles: BulkImportError[] = [];
let canceled: boolean = false;
let canceled = false;
const { selectedAPI, lookupMethod, fieldName, appendContent } = await new Promise<{
selectedAPI: string;
@ -87,7 +87,11 @@ export class BulkImportHelper {
}
if (modelResult.value) {
await this.plugin.fileHelper.createMediaDbNotes([modelResult.value], appendContent ? file : undefined);
const createResult = await this.plugin.fileHelper.createMediaDbNotes([modelResult.value], appendContent ? file : undefined);
if (!createResult.ok) {
return { filePath: file.path, error: createResult.error.userMessage ?? createResult.error.message };
}
return undefined;
}
@ -127,8 +131,25 @@ export class BulkImportHelper {
return { filePath: file.path, error: `no search results selected` };
}
const seasonSelectionResult = await this.plugin.entryHelper.handleSeasonSearchSelections(selectModalResult.data.selected, appendContent ? file : undefined);
if (seasonSelectionResult.handled) {
if (!seasonSelectionResult.created) {
return { filePath: file.path, error: 'season selection canceled or no season note was created' };
}
return undefined;
}
const detailedResults = await this.plugin.entryHelper.queryDetails(selectModalResult.data.selected);
await this.plugin.fileHelper.createMediaDbNotes(detailedResults, appendContent ? file : undefined);
if (detailedResults.length === 0) {
return { filePath: file.path, error: 'failed to load details for selected search results' };
}
const createResult = await this.plugin.fileHelper.createMediaDbNotes(detailedResults, appendContent ? file : undefined);
if (!createResult.ok) {
return { filePath: file.path, error: createResult.error.userMessage ?? createResult.error.message };
}
return undefined;
}

View file

@ -1,9 +1,12 @@
import type { TFile } from 'obsidian';
import { MarkdownView, Notice } from 'obsidian';
import type { TMDBSeasonAPI } from 'packages/obsidian/src/api/apis/TMDBSeasonAPI';
import type { SeasonListAPIModel } from 'packages/obsidian/src/api/APIModel';
import { isSeasonListAPIModel } from 'packages/obsidian/src/api/APIModel';
import type MediaDbPlugin from 'packages/obsidian/src/main';
import type { SeasonSelectModalElement } from 'packages/obsidian/src/modals/MediaDbSeasonSelectModal';
import type { MediaTypeModel } from 'packages/obsidian/src/models/MediaTypeModel';
import type { SeasonModel } from 'packages/obsidian/src/models/SeasonModel';
import { SeasonSearchResultModel } from 'packages/obsidian/src/models/SeasonSearchResultModel';
import type { MDBError } from 'packages/obsidian/src/utils/MDBError';
import { MDBErrorKind } from 'packages/obsidian/src/utils/MDBError';
import { MediaType } from 'packages/obsidian/src/utils/MediaType';
@ -112,6 +115,7 @@ export class MediaDbEntryHelper {
types.length === 1 && types[0] === MediaType.Season
? await this.plugin.modalHelper.promptSelectModal({
elements: filteredSearchResults,
multiSelect: false,
description: 'Select one search result to proceed.',
submitButtonText: 'Ok',
})
@ -121,7 +125,11 @@ export class MediaDbEntryHelper {
return;
}
const selectResults = types.length === 1 && types[0] === MediaType.Season ? selectResultsData.selected : await this.queryDetails(selectResultsData.selected);
if ((await this.handleSeasonSearchSelections(selectResultsData.selected)).handled) {
return;
}
const selectResults = await this.queryDetails(selectResultsData.selected);
if (selectResults.length === 0) {
return;
@ -167,6 +175,10 @@ export class MediaDbEntryHelper {
return;
}
if ((await this.handleSeasonSearchSelections(selectResultsData.selected)).handled) {
return;
}
const selectResults = await this.queryDetails(selectResultsData.selected);
if (selectResults.length < 1) {
return;
@ -239,31 +251,62 @@ export class MediaDbEntryHelper {
return detailModels;
}
private async handleSeasonWorkflow(types: string[], selectResults: MediaTypeModel[]): Promise<{ handled: boolean; seasonsCreated?: boolean }> {
if (types.length === 1 && types[0] === 'season' && selectResults.length === 1 && selectResults[0].dataSource === 'TMDBSeasonAPI') {
const created = await this.showSeasonSelectAndCreate(selectResults[0].id, selectResults[0].englishTitle || selectResults[0].title);
return { handled: true, seasonsCreated: created };
private async handleSeasonWorkflow(types: MediaType[], selectResults: MediaTypeModel[]): Promise<{ handled: boolean; seasonsCreated?: boolean }> {
if (!types.includes(MediaType.Series) || !types.includes(MediaType.Season)) {
return { handled: false };
}
if (types.includes('series') && selectResults.some(result => result.dataSource === 'TMDBSeriesAPI')) {
const seriesResults = selectResults.filter(result => result.dataSource === 'TMDBSeriesAPI');
if (seriesResults.length === 1 && types.includes('season')) {
const created = await this.showSeasonSelectAndCreate(seriesResults[0].id, seriesResults[0].title);
return { handled: true, seasonsCreated: created };
}
const seriesResults = selectResults.filter(result => result.getMediaType() === MediaType.Series);
if (seriesResults.length !== 1) {
return { handled: false };
}
const seriesResult = seriesResults[0];
const sourceApi = this.plugin.apiManager.getApiByName(seriesResult.dataSource);
const seasonApiName = sourceApi?.getSeasonApiNameForSeries(seriesResult);
if (seasonApiName) {
const created = await this.showSeasonSelectAndCreate(seriesResult.id, seriesResult.title, undefined, seasonApiName);
return { handled: true, seasonsCreated: created };
}
return { handled: false };
}
private async showSeasonSelectAndCreate(seriesId: string, seriesTitle: string): Promise<boolean> {
const tmdbSeasonAPI = this.plugin.apiManager.getApiByName('TMDBSeasonAPI') as TMDBSeasonAPI | undefined;
if (!tmdbSeasonAPI) {
new Notice('TMDBSeasonAPI not available.');
private isSeasonSearchResult(model: MediaTypeModel): model is SeasonSearchResultModel {
return model instanceof SeasonSearchResultModel || (model.getMediaType() === MediaType.Season && typeof (model as { seasonCount?: unknown }).seasonCount === 'number');
}
async handleSeasonSearchSelections(selectedResults: MediaTypeModel[], attachFile?: TFile): Promise<{ handled: boolean; created: boolean }> {
const seasonSearchResults = selectedResults.filter(result => this.isSeasonSearchResult(result));
if (seasonSearchResults.length === 0) {
return { handled: false, created: false };
}
if (selectedResults.length !== 1) {
new Notice('Select exactly one season search result before choosing seasons.');
return { handled: true, created: false };
}
const selectedResult = seasonSearchResults[0];
return {
handled: true,
created: await this.showSeasonSelectAndCreate(selectedResult.id, selectedResult.englishTitle || selectedResult.title, attachFile, selectedResult.dataSource),
};
}
private getSeasonListApi(apiName: string): SeasonListAPIModel | undefined {
const api = this.plugin.apiManager.getApiByName(apiName);
return isSeasonListAPIModel(api) ? api : undefined;
}
private async showSeasonSelectAndCreate(seriesId: string, seriesTitle: string, attachFile: TFile | undefined, seasonApiName: string): Promise<boolean> {
const seasonAPI = this.getSeasonListApi(seasonApiName);
if (!seasonAPI) {
new Notice(`${seasonApiName} does not support season selection.`);
return false;
}
const allSeasonsResult = await tmdbSeasonAPI.getSeasonsForSeries(seriesId);
const allSeasonsResult = await seasonAPI.getSeasonsForSeries(seriesId);
if (!allSeasonsResult.ok) {
this.reportMdbError(allSeasonsResult.error);
new Notice(`Error loading seasons: ${allSeasonsResult.error.userMessage}`);
@ -281,8 +324,13 @@ export class MediaDbEntryHelper {
return false;
}
await this.createNotesForSelectedSeasons(selectedSeasons, allSeasons, tmdbSeasonAPI);
new Notice(`Successfully created ${selectedSeasons.length} season ${selectedSeasons.length === 1 ? 'entry' : 'entries'}.`);
const createdCount = await this.createNotesForSelectedSeasons(selectedSeasons, allSeasons, seasonAPI, attachFile);
if (createdCount === 0) {
new Notice('No season entries were created.');
return false;
}
new Notice(`Successfully created ${createdCount} season ${createdCount === 1 ? 'entry' : 'entries'}.`);
return true;
}
@ -300,21 +348,36 @@ export class MediaDbEntryHelper {
});
}
private async createNotesForSelectedSeasons(selectedSeasons: SeasonSelectModalElement[], allSeasons: SeasonModel[], tmdbSeasonAPI: TMDBSeasonAPI): Promise<void> {
await Promise.all(
private async createNotesForSelectedSeasons(
selectedSeasons: SeasonSelectModalElement[],
allSeasons: SeasonModel[],
seasonAPI: SeasonListAPIModel,
attachFile?: TFile,
): Promise<number> {
const results = await Promise.all(
selectedSeasons.map(async selectedSeason => {
const seasonModel = allSeasons.find(season => season.seasonNumber === selectedSeason.season_number);
if (seasonModel) {
const fullMetadataResult = await tmdbSeasonAPI.getById(seasonModel.id);
const fullMetadataResult = await seasonAPI.getById(seasonModel.id);
if (!fullMetadataResult.ok) {
this.reportMdbError(fullMetadataResult.error);
new Notice(`Failed to load season ${selectedSeason.season_number}: ${fullMetadataResult.error.userMessage}`);
return;
return false;
}
await this.plugin.fileHelper.createMediaDbNotes([fullMetadataResult.value]);
const createResult = await this.plugin.fileHelper.createMediaDbNotes([fullMetadataResult.value], attachFile);
if (!createResult.ok) {
this.reportMdbError(createResult.error);
return false;
}
return true;
}
return false;
}),
);
return results.filter(created => created).length;
}
}