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,24 +0,0 @@
import { onMount, Show } from 'solid-js';
import { setIcon } from 'obsidian';
interface IconProps {
iconName?: string;
}
export default function Icon(props: IconProps) {
let iconEl: HTMLDivElement | undefined;
onMount(() => {
if (iconEl) {
setIcon(iconEl, props.iconName || '');
}
});
return (
<Show when={(props.iconName || '').length > 0}>
<div class="icon-wrapper">
<div ref={iconEl} class="icon"></div>
</div>
</Show>
);
}

View file

@ -0,0 +1,159 @@
import { Component, setIcon } from 'obsidian';
import type { PropertyMappingModelData } from 'packages/obsidian/src/settings/PropertyMapping';
import { PropertyMappingModel, PropertyMappingOption, propertyMappingOptions } from 'packages/obsidian/src/settings/PropertyMapping';
import { capitalizeFirstLetter } from 'packages/obsidian/src/utils/Utils';
interface PropertyMappingModelComponentProps {
model: PropertyMappingModelData;
save: (model: PropertyMappingModelData) => void;
}
export default class PropertyMappingModelComponent extends Component {
private readonly containerEl: HTMLElement;
private readonly save: (model: PropertyMappingModelData) => void;
private modelData: PropertyMappingModelData;
private unsavedChanges = false;
constructor(containerEl: HTMLElement, props: PropertyMappingModelComponentProps) {
super();
this.containerEl = containerEl;
this.modelData = props.model;
this.save = props.save;
}
override onload(): void {
this.render();
}
override onunload(): void {
this.containerEl.empty();
}
private get validationResult(): { res: boolean; err?: Error } {
const model = PropertyMappingModel.fromJSON(this.modelData);
return model.validate();
}
private handleSave(): void {
const model = PropertyMappingModel.fromJSON(this.modelData);
if (!model.validate().res) {
return;
}
this.save(model.toJSON());
this.unsavedChanges = false;
this.render();
}
private onModelUpdate(): void {
this.unsavedChanges = true;
this.render();
}
private render(): void {
this.containerEl.empty();
const validationResult = this.validationResult;
const rootEl = this.containerEl.createDiv('media-db-plugin-property-mappings-model-container');
const headerEl = rootEl.createDiv('media-db-plugin-property-mappings-model-header');
headerEl.createDiv({ cls: 'setting-item-name', text: capitalizeFirstLetter(this.modelData.type) });
const actionsEl = headerEl.createDiv('media-db-plugin-property-mappings-model-actions');
if (this.unsavedChanges) {
actionsEl.createDiv({ cls: 'media-db-plugin-property-mapping-unsaved-changes', text: 'Unsaved changes' });
}
const saveButtonEl = actionsEl.createEl('button', {
cls: `media-db-plugin-property-mappings-save-button ${validationResult.res ? 'mod-cta' : 'mod-muted'}`,
text: 'Save',
});
saveButtonEl.addEventListener('click', () => this.handleSave());
if (!validationResult.res) {
rootEl.createDiv({
cls: 'media-db-plugin-property-mapping-validation',
text: validationResult.err?.message ?? '',
});
}
const tableContainerEl = rootEl.createDiv('media-db-plugin-property-mappings-table-container');
const tableEl = tableContainerEl.createEl('table', 'media-db-plugin-property-mappings-table');
const tableHeadEl = tableEl.createEl('thead');
const headerRowEl = tableHeadEl.createEl('tr');
headerRowEl.createEl('th', { cls: 'col-property', text: 'Property' });
headerRowEl.createEl('th', { cls: 'col-mapping', text: 'Mapping' });
headerRowEl.createEl('th', { cls: 'col-new-name', text: 'New name' });
headerRowEl.createEl('th', { cls: 'col-wikilink', text: 'Wikilink' });
const tableBodyEl = tableEl.createEl('tbody');
for (const [index, property] of this.modelData.properties.entries()) {
const rowEl = tableBodyEl.createEl('tr');
const propertyCellEl = rowEl.createEl('td', 'col-property');
propertyCellEl.createEl('code', { text: property.property });
if (property.locked) {
const lockedCellEl = rowEl.createEl('td', 'col-locked');
lockedCellEl.colSpan = 3;
lockedCellEl.createDiv({
cls: 'media-db-plugin-property-binding-text',
text: 'property cannot be remapped',
});
continue;
}
const mappingCellEl = rowEl.createEl('td', 'col-mapping');
const selectEl = mappingCellEl.createEl('select', 'dropdown');
for (const remappingOption of propertyMappingOptions) {
selectEl.createEl('option', {
attr: { value: remappingOption },
text: remappingOption,
});
}
selectEl.value = propertyMappingOptions.includes(property.mapping) ? property.mapping : PropertyMappingOption.Default;
selectEl.addEventListener('change', event => {
const targetEl = event.currentTarget as HTMLSelectElement;
this.modelData.properties[index].mapping = targetEl.value as PropertyMappingOption;
this.modelData.properties[index].newProperty = '';
this.onModelUpdate();
});
const newNameCellEl = rowEl.createEl('td', 'col-new-name');
if (property.mapping === PropertyMappingOption.Map) {
const mappingToEl = newNameCellEl.createDiv('media-db-plugin-property-mapping-to');
const iconWrapperEl = mappingToEl.createDiv('icon-wrapper');
const iconEl = iconWrapperEl.createDiv('icon');
setIcon(iconEl, 'arrow-right');
const inputEl = mappingToEl.createEl('input', {
cls: 'media-db-plugin-property-mapping-input',
type: 'text',
});
inputEl.spellcheck = false;
inputEl.value = property.newProperty;
inputEl.addEventListener('input', event => {
const targetEl = event.currentTarget as HTMLInputElement;
this.modelData.properties[index].newProperty = targetEl.value;
this.onModelUpdate();
});
} else {
newNameCellEl.createSpan({
cls: 'media-db-plugin-property-mapping-to-disabled',
text: 'N/A',
});
}
const wikilinkCellEl = rowEl.createEl('td', 'col-wikilink');
const wikilinkLabelEl = wikilinkCellEl.createEl('label', {
cls: 'media-db-plugin-property-mapping-wikilink-label',
attr: { title: 'Convert value to wikilink ([[value]])' },
});
const wikilinkInputEl = wikilinkLabelEl.createEl('input', { type: 'checkbox' });
wikilinkInputEl.checked = property.wikilink ?? false;
wikilinkInputEl.addEventListener('change', event => {
const targetEl = event.currentTarget as HTMLInputElement;
this.modelData.properties[index].wikilink = targetEl.checked;
this.onModelUpdate();
});
}
}
}

View file

@ -1,138 +0,0 @@
import { createSignal, createMemo, For, Show } from 'solid-js';
import { createStore } from 'solid-js/store';
import { PropertyMappingModel, PropertyMappingOption, propertyMappingOptions, type PropertyMappingModelData } from './PropertyMapping';
import { capitalizeFirstLetter } from '../utils/Utils';
import Icon from './Icon';
interface PropertyMappingModelComponentProps {
model: PropertyMappingModelData;
save: (model: PropertyMappingModelData) => void;
}
export default function PropertyMappingModelComponent(props: PropertyMappingModelComponentProps) {
const [unsavedChanges, setUnsavedChanges] = createSignal(false);
// Create a store from the model's plain data
const [modelData, setModelData] = createStore(props.model);
// Derive the validation result reactively
const validationResult = createMemo(() => {
const model = PropertyMappingModel.fromJSON(modelData);
return model.validate();
});
const onModelUpdate = () => {
setUnsavedChanges(true);
};
const handleSave = () => {
const model = PropertyMappingModel.fromJSON(modelData);
if (model.validate().res) {
props.save(model);
setUnsavedChanges(false);
}
};
return (
<div class="media-db-plugin-property-mappings-model-container">
<div class="media-db-plugin-property-mappings-model-header">
<div class="setting-item-name">{capitalizeFirstLetter(modelData.type)}</div>
<div class="media-db-plugin-property-mappings-model-actions">
<Show when={unsavedChanges()}>
<div class="media-db-plugin-property-mapping-unsaved-changes">Unsaved changes</div>
</Show>
<button class={`media-db-plugin-property-mappings-save-button ${validationResult().res ? 'mod-cta' : 'mod-muted'}`} onClick={handleSave}>
Save
</button>
</div>
</div>
<Show when={!validationResult().res}>
<div class="media-db-plugin-property-mapping-validation">{validationResult().err?.message}</div>
</Show>
<div class="media-db-plugin-property-mappings-table-container">
<table class="media-db-plugin-property-mappings-table">
<thead>
<tr>
<th class="col-property">Property</th>
<th class="col-mapping">Mapping</th>
<th class="col-new-name">New name</th>
<th class="col-wikilink">Wikilink</th>
</tr>
</thead>
<tbody>
<For each={modelData.properties}>
{(property, index) => (
<tr>
<td class="col-property">
<code>{property.property}</code>
</td>
<Show
when={!property.locked}
fallback={
<td class="col-locked" colspan={3}>
<div class="media-db-plugin-property-binding-text">property cannot be remapped</div>
</td>
}
>
<td class="col-mapping">
<select
class="dropdown"
value={property.mapping}
onChange={e => {
setModelData('properties', index(), 'mapping', e.currentTarget.value as PropertyMappingOption);
setModelData('properties', index(), 'newProperty', '');
onModelUpdate();
}}
>
<For each={propertyMappingOptions}>{remappingOption => <option value={remappingOption}>{remappingOption}</option>}</For>
</select>
</td>
<td class="col-new-name">
<Show
when={property.mapping === PropertyMappingOption.Map}
fallback={<span class="media-db-plugin-property-mapping-to-disabled">N/A</span>}
>
<div class="media-db-plugin-property-mapping-to">
<Icon iconName="arrow-right" />
<input
class="media-db-plugin-property-mapping-input"
type="text"
spellcheck={false}
value={property.newProperty}
onInput={e => {
setModelData('properties', index(), 'newProperty', e.currentTarget.value);
onModelUpdate();
}}
/>
</div>
</Show>
</td>
<td class="col-wikilink">
<label class="media-db-plugin-property-mapping-wikilink-label" title="Convert value to wikilink ([[value]])">
<input
type="checkbox"
checked={property.wikilink}
onChange={e => {
setModelData('properties', index(), 'wikilink', e.currentTarget.checked);
onModelUpdate();
}}
/>
</label>
</td>
</Show>
</tr>
)}
</For>
</tbody>
</table>
</div>
</div>
);
}

View file

@ -0,0 +1,40 @@
import { Component } from 'obsidian';
import type { PropertyMappingModelData } from 'packages/obsidian/src/settings/PropertyMapping';
import PropertyMappingModelComponent from 'packages/obsidian/src/settings/PropertyMappingModelComponent';
interface PropertyMappingModelsComponentProps {
models?: PropertyMappingModelData[];
save: (model: PropertyMappingModelData) => void;
}
export default class PropertyMappingModelsComponent extends Component {
private readonly containerEl: HTMLElement;
private readonly models: PropertyMappingModelData[];
private readonly save: (model: PropertyMappingModelData) => void;
constructor(containerEl: HTMLElement, props: PropertyMappingModelsComponentProps) {
super();
this.containerEl = containerEl;
this.models = props.models ?? [];
this.save = props.save;
}
override onload(): void {
this.containerEl.empty();
const rootEl = this.containerEl.createDiv('setting-item media-db-plugin-property-mappings-models-container');
for (const model of this.models) {
const modelContainerEl = rootEl.createDiv();
this.addChild(
new PropertyMappingModelComponent(modelContainerEl, {
model,
save: this.save,
}),
);
}
}
override onunload(): void {
this.containerEl.empty();
}
}

View file

@ -1,16 +0,0 @@
import { For } from 'solid-js';
import { type PropertyMappingModelData } from './PropertyMapping';
import PropertyMappingModelComponent from './PropertyMappingModelComponent';
interface PropertyMappingModelsComponentProps {
models?: PropertyMappingModelData[];
save: (model: PropertyMappingModelData) => void;
}
export default function PropertyMappingModelsComponent(props: PropertyMappingModelsComponentProps) {
return (
<div class="setting-item" style={{ display: 'flex', gap: '10px', 'flex-direction': 'column', 'align-items': 'stretch' }}>
<For each={props.models || []}>{model => <PropertyMappingModelComponent model={model} save={props.save} />}</For>
</div>
);
}

View file

@ -10,7 +10,6 @@ import { FolderSuggest } from 'packages/obsidian/src/settings/suggesters/FolderS
import { MediaType } from 'packages/obsidian/src/utils/MediaType';
import { MEDIA_TYPES } from 'packages/obsidian/src/utils/MediaTypeManager';
import { unCamelCase } from 'packages/obsidian/src/utils/Utils';
import { render } from 'solid-js/web';
function createDateFormatDescription(preview: string): DocumentFragment {
return createFragment(frag => {
@ -425,14 +424,25 @@ export function getDefaultSettings(plugin: MediaDbPlugin): MediaDbPluginSettings
// MARK: Settings Tab
export class MediaDbSettingTab extends PluginSettingTab {
plugin: MediaDbPlugin;
private propertyMappingModelsComponent?: PropertyMappingModelsComponent;
constructor(app: App, plugin: MediaDbPlugin) {
super(app, plugin);
this.plugin = plugin;
}
override hide(): void {
this.propertyMappingModelsComponent?.unload();
this.propertyMappingModelsComponent = undefined;
super.hide();
}
display(): void {
const { containerEl } = this;
if (this.propertyMappingModelsComponent) {
this.propertyMappingModelsComponent.unload();
this.propertyMappingModelsComponent = undefined;
}
containerEl.empty();
const mediaTypeSettings = MEDIA_TYPES.map(mt => new MediaTypeMappedSettings(mt));
@ -837,24 +847,21 @@ export class MediaDbSettingTab extends PluginSettingTab {
mappingGroup.setHeading('Property mappings');
mappingGroup.addSetting(setting => {
setting.setName('Property mappings explanation').setDesc(createPropertyMappingsDescription());
const propertyMappingsEl = setting.descEl.createDiv();
this.propertyMappingModelsComponent = new PropertyMappingModelsComponent(propertyMappingsEl, {
models: structuredClone(this.plugin.settings.propertyMappingModels),
save: (model: PropertyMappingModelData): void => {
// Update the matching model in settings (stored as plain data)
const index = this.plugin.settings.propertyMappingModels.findIndex(m => m.type === model.type);
if (index !== -1) {
this.plugin.settings.propertyMappingModels[index] = model;
}
render(
() =>
PropertyMappingModelsComponent({
models: structuredClone(this.plugin.settings.propertyMappingModels),
save: (model: PropertyMappingModelData): void => {
// Update the matching model in settings (stored as plain data)
const index = this.plugin.settings.propertyMappingModels.findIndex(m => m.type === model.type);
if (index !== -1) {
this.plugin.settings.propertyMappingModels[index] = model;
}
new Notice(`MDB: Property mappings for ${model.type} saved successfully.`);
void this.plugin.saveSettings();
},
}),
setting.descEl,
);
new Notice(`MDB: Property mappings for ${model.type} saved successfully.`);
void this.plugin.saveSettings();
},
});
this.propertyMappingModelsComponent.load();
});
}
}