This commit is contained in:
mProjectsCode 2022-09-23 19:10:19 +02:00
parent 72b7eb8982
commit e00c5ce2c2
13 changed files with 764 additions and 196 deletions

View file

@ -1,27 +0,0 @@
import {containsOnlyLettersAndUnderscores} from '../utils/Utils';
export class ModelPropertyConversionRule {
property: string;
newProperty: string;
constructor(conversionRule: string) {
const conversionRuleParts = conversionRule.split('->');
if (conversionRuleParts.length !== 2) {
throw Error(`Conversion rule "${conversionRule}" may only have exactly one "->"`);
}
let property = conversionRuleParts[0].trim();
let newProperty = conversionRuleParts[1].trim();
if (!property || !containsOnlyLettersAndUnderscores(property)) {
throw Error(`Error in conversion rule "${conversionRule}": property may not be empty and only contain letters and underscores.`);
}
if (!newProperty || !containsOnlyLettersAndUnderscores(newProperty)) {
throw Error(`Error in conversion rule "${conversionRule}": new property may not be empty and only contain letters and underscores.`);
}
this.property = property;
this.newProperty = newProperty;
}
}

View file

@ -1,130 +0,0 @@
import {MediaType} from '../utils/MediaType';
import {MediaDbPluginSettings} from './Settings';
import {ModelPropertyConversionRule} from './ModelPropertyConversionRule';
export class ModelPropertyMapper {
conversionRulesMap: Map<MediaType, string>;
constructor(settings: MediaDbPluginSettings) {
this.updateConversionRules(settings);
}
updateConversionRules(settings: MediaDbPluginSettings) {
this.conversionRulesMap = new Map<MediaType, string>();
this.conversionRulesMap.set(MediaType.Movie, settings.moviePropertyConversionRules);
this.conversionRulesMap.set(MediaType.Series, settings.seriesPropertyConversionRules);
this.conversionRulesMap.set(MediaType.Game, settings.gamePropertyConversionRules);
this.conversionRulesMap.set(MediaType.Wiki, settings.wikiPropertyConversionRules);
this.conversionRulesMap.set(MediaType.MusicRelease, settings.musicReleasePropertyConversionRules);
this.conversionRulesMap.set(MediaType.BoardGame, settings.boardgamePropertyConversionRules);
}
/**
* Converts an object using the conversion rules for its type.
* Returns an unaltered object if object.type is null or undefined or if there are no conversion rules for the type.
*
* @param obj
*/
convertObject(obj: object): object {
if (!obj.hasOwnProperty('type')) {
return obj;
}
// @ts-ignore
// get conversion rules from settings corresponding to the object type
const conversionRulesString: string = this.conversionRulesMap.get(obj['type']);
if (!conversionRulesString) {
return obj;
}
// parse the conversion rules
const conversionRules: ModelPropertyConversionRule[] = [];
for (const conversionRuleString of conversionRulesString.split('\n')) {
if (conversionRuleString) {
conversionRules.push(new ModelPropertyConversionRule(conversionRuleString));
}
}
const newObj: object = {};
for (const [key, value] of Object.entries(obj)) {
// property 'type' can not be remapped
if (key === 'type') {
// @ts-ignore
newObj[key] = value;
continue;
}
let hasConversionRule = false;
for (const conversionRule of conversionRules) {
if (conversionRule.property === key) {
hasConversionRule = true;
// if the conversion rule maps to 'x', then that means it should be ignored
if (conversionRule.newProperty.toLowerCase() !== 'x') {
// @ts-ignore
newObj[conversionRule.newProperty] = value;
}
}
}
if (!hasConversionRule) {
// @ts-ignore
newObj[key] = value;
}
}
return newObj;
}
/**
* Converts an object back using the conversion rules for its type.
* Returns an unaltered object if object.type is null or undefined or if there are no conversion rules for the type.
*
* @param obj
*/
convertObjectBack(obj: object): object {
if (!obj.hasOwnProperty('type')) {
return obj;
}
// @ts-ignore
// get conversion rules from settings corresponding to the object type
const conversionRulesString: string = this.conversionRulesMap.get(obj['type']);
if (!conversionRulesString) {
return obj;
}
const conversionRules: ModelPropertyConversionRule[] = [];
// parse the conversion rules
for (const conversionRuleString of conversionRulesString.split('\n')) {
if (conversionRuleString) {
conversionRules.push(new ModelPropertyConversionRule(conversionRuleString));
}
}
const originalObj: object = {};
for (const [key, value] of Object.entries(obj)) {
// property 'type' can not be remapped
if (key === 'type') {
// @ts-ignore
originalObj[key] = value;
continue;
}
let hasConversionRule = false;
for (const conversionRule of conversionRules) {
if (conversionRule.newProperty === key) {
hasConversionRule = true;
// @ts-ignore
originalObj[conversionRule.property] = value;
}
}
if (!hasConversionRule) {
// @ts-ignore
originalObj[key] = value;
}
}
return originalObj;
}
}

View file

@ -0,0 +1,74 @@
<script lang="ts">
import {capitalizeFirstLetter} from '../utils/Utils';
import {PropertyMappingModel, PropertyMappingOption, propertyMappingOptions} from './PropertyMapping';
export let models: PropertyMappingModel[] = [];
export let save: (models: PropertyMappingModel[]) => void;
</script>
<style>
.media-db-plugin-property-mappings-container {
margin: 10px 0;
display: flex;
flex-direction: column;
gap: 10px;
}
.media-db-plugin-property-mapping-element {
display: flex;
flex-direction: row;
gap: 10px;
}
.media-db-plugin-property-mapping-element-property-name-wrapper {
min-width: 160px;
background: var(--background-modifier-form-field);
padding: 2px 5px;
border-radius: 5px;
}
.media-db-plugin-property-mapping-element-property-name {
margin: 0;
}
</style>
<div class="setting-item" style="display: block;">
{ #each models as model }
<div class="setting-item-name">{capitalizeFirstLetter(model.type)}</div>
<div class="media-db-plugin-property-mappings-container">
{ #each model.properties as property }
<div class="media-db-plugin-property-mapping-element">
<div class="media-db-plugin-property-mapping-element-property-name-wrapper">
<pre
class="media-db-plugin-property-mapping-element-property-name"><code>{property.property}</code></pre>
</div>
{#if property.locked}
<div class="media-db-plugin-property-binding-text">
property can not be remapped
</div>
{:else}
<select bind:value={property.mapping}>
{#each propertyMappingOptions as remappingOption}
<option value={remappingOption}>
{remappingOption}
</option>
{/each}
</select>
{#if property.mapping === PropertyMappingOption.Map}
<div class="media-db-plugin-property-mapping-text">
->
</div>
<div class="media-db-plugin-property-binding-to">
<input type="text" spellcheck="false" bind:value="{property.newProperty}">
</div>
{/if}
{/if}
</div>
{ /each }
</div>
{ /each }
<pre>{JSON.stringify(models, null, 4)}</pre>
</div>

View file

@ -0,0 +1,98 @@
import {PropertyMappingOption} from './PropertyMapping';
import {MEDIA_TYPES} from '../utils/MediaTypeManager';
import MediaDbPlugin from '../main';
export class PropertyMapper {
plugin: MediaDbPlugin;
constructor(plugin: MediaDbPlugin) {
this.plugin = plugin;
}
/**
* Converts an object using the conversion rules for its type.
* Returns an unaltered object if object.type is null or undefined or if there are no conversion rules for the type.
*
* @param obj
*/
convertObject(obj: object): object {
if (!obj.hasOwnProperty('type')) {
return obj;
}
// @ts-ignore
if (MEDIA_TYPES.contains(obj.type)) {
return obj;
}
// @ts-ignore
const propertyMappings = this.plugin.settings.propertyMappings.find(x => x.type === obj.type).properties;
const newObj: object = {};
for (const [key, value] of Object.entries(obj)) {
for (const propertyMapping of propertyMappings) {
if (propertyMapping.property === key) {
if (propertyMapping.mapping === PropertyMappingOption.Map) {
// @ts-ignore
newObj[propertyMapping.newProperty] = value;
} else if (propertyMapping.mapping === PropertyMappingOption.Remove) {
} else if (propertyMapping.mapping === PropertyMappingOption.None) {
// @ts-ignore
newObj[key] = value;
}
break;
}
}
}
return newObj;
}
/**
* Converts an object back using the conversion rules for its type.
* Returns an unaltered object if object.type is null or undefined or if there are no conversion rules for the type.
*
* @param obj
*/
convertObjectBack(obj: object): object {
if (!obj.hasOwnProperty('type')) {
return obj;
}
// @ts-ignore
if (MEDIA_TYPES.contains(obj.type)) {
return obj;
}
// @ts-ignore
const propertyMappings = this.plugin.settings.propertyMappings.find(x => x.type === obj.type).properties;
const originalObj: object = {};
objLoop: for (const [key, value] of Object.entries(obj)) {
// first try if it is a normal property
for (const propertyMapping of propertyMappings) {
if (propertyMapping.property === key) {
// @ts-ignore
originalObj[key] = value;
continue objLoop;
}
}
// otherwise see if it is a mapped property
for (const propertyMapping of propertyMappings) {
if (propertyMapping.newProperty === key) {
// @ts-ignore
originalObj[propertyMapping.property] = value;
continue objLoop;
}
}
}
return originalObj;
}
}

View file

@ -0,0 +1,75 @@
import {containsOnlyLettersAndUnderscores} from '../utils/Utils';
import {MediaType} from '../utils/MediaType';
export enum PropertyMappingOption {
None = 'none',
Map = 'remap',
Remove = 'remove',
}
export const propertyMappingOptions = [PropertyMappingOption.None, PropertyMappingOption.Map, PropertyMappingOption.Remove];
export interface PropertyMappingModel {
type: MediaType,
properties: PropertyMapping[],
}
export class PropertyMapping {
property: string;
newProperty: string;
locked: boolean;
mapping: PropertyMappingOption;
constructor(property: string, newProperty: string, mapping: PropertyMappingOption, locked?: boolean) {
this.property = property;
this.newProperty = newProperty;
this.mapping = mapping;
this.locked = locked ?? false;
/*
const conversionRuleParts = conversionRule.split('->');
if (conversionRuleParts.length !== 2) {
throw Error(`Conversion rule "${conversionRule}" may only have exactly one "->"`);
}
let property = conversionRuleParts[0].trim();
let newProperty = conversionRuleParts[1].trim();
if (!property || !containsOnlyLettersAndUnderscores(property)) {
throw Error(`Error in conversion rule "${conversionRule}": property may not be empty and only contain letters and underscores.`);
}
if (!newProperty || !containsOnlyLettersAndUnderscores(newProperty)) {
throw Error(`Error in conversion rule "${conversionRule}": new property may not be empty and only contain letters and underscores.`);
}
this.property = property;
this.newProperty = newProperty;
*/
}
validate(): string {
if (!this.property || !containsOnlyLettersAndUnderscores(this.property)) {
return `Error in conversion rule "${this.toString()}": property may not be empty and only contain letters and underscores.`;
}
if (!this.newProperty || !containsOnlyLettersAndUnderscores(this.newProperty)) {
return `Error in conversion rule "${this.toString()}": new property may not be empty and only contain letters and underscores.`;
}
return '';
}
toString(): string {
if (this.mapping === PropertyMappingOption.None) {
return this.property;
} else if (this.mapping === PropertyMappingOption.Map) {
return `${this.property} -> ${this.newProperty}`;
} else if (this.mapping === PropertyMappingOption.Remove) {
return `remove ${this.property}`;
}
return this.property;
}
}

View file

@ -3,6 +3,9 @@ import {App, PluginSettingTab, Setting} from 'obsidian';
import MediaDbPlugin from '../main';
import {FolderSuggest} from './suggesters/FolderSuggest';
import {FileSuggest} from './suggesters/FileSuggest';
import PropertyBindingsComponent from './PropertyBindingsComponent.svelte';
import {PropertyMapping, PropertyMappingModel, PropertyMappingOption} from './PropertyMapping';
import {MediaType} from '../utils/MediaType';
export interface MediaDbPluginSettings {
@ -34,6 +37,8 @@ export interface MediaDbPluginSettings {
musicReleasePropertyConversionRules: string,
boardgamePropertyConversionRules: string,
propertyMappings: PropertyMappingModel[],
}
export const DEFAULT_SETTINGS: MediaDbPluginSettings = {
@ -64,6 +69,60 @@ export const DEFAULT_SETTINGS: MediaDbPluginSettings = {
musicReleasePropertyConversionRules: '',
boardgamePropertyConversionRules: '',
propertyMappings: [
{
type: MediaType.Movie,
properties: [
new PropertyMapping('type', '', PropertyMappingOption.None, true),
new PropertyMapping('subType', '', PropertyMappingOption.None),
new PropertyMapping('title', '', PropertyMappingOption.None),
new PropertyMapping('englishTitle', '', PropertyMappingOption.None),
new PropertyMapping('year', '', PropertyMappingOption.None),
new PropertyMapping('dataSource', '', PropertyMappingOption.None, true),
new PropertyMapping('url', '', PropertyMappingOption.None),
new PropertyMapping('id', '', PropertyMappingOption.None, true),
new PropertyMapping('genres', '', PropertyMappingOption.None),
new PropertyMapping('producer', '', PropertyMappingOption.None),
new PropertyMapping('duration', '', PropertyMappingOption.None),
new PropertyMapping('onlineRating', '', PropertyMappingOption.None),
new PropertyMapping('image', '', PropertyMappingOption.None),
new PropertyMapping('released', '', PropertyMappingOption.None),
new PropertyMapping('premiere', '', PropertyMappingOption.None),
new PropertyMapping('watched', '', PropertyMappingOption.None),
new PropertyMapping('lastWatched', '', PropertyMappingOption.None),
new PropertyMapping('personalRating', '', PropertyMappingOption.None),
],
},
{
type: MediaType.Series,
properties: [
new PropertyMapping('type', '', PropertyMappingOption.None, true),
new PropertyMapping('subType', '', PropertyMappingOption.None),
new PropertyMapping('title', '', PropertyMappingOption.None),
new PropertyMapping('englishTitle', '', PropertyMappingOption.None),
new PropertyMapping('year', '', PropertyMappingOption.None),
new PropertyMapping('dataSource', '', PropertyMappingOption.None, true),
new PropertyMapping('url', '', PropertyMappingOption.None),
new PropertyMapping('id', '', PropertyMappingOption.None, true),
new PropertyMapping('genres', '', PropertyMappingOption.None),
new PropertyMapping('studios', '', PropertyMappingOption.None),
new PropertyMapping('episodes', '', PropertyMappingOption.None),
new PropertyMapping('duration', '', PropertyMappingOption.None),
new PropertyMapping('onlineRating', '', PropertyMappingOption.None),
new PropertyMapping('image', '', PropertyMappingOption.None),
new PropertyMapping('released', '', PropertyMappingOption.None),
new PropertyMapping('airing', '', PropertyMappingOption.None),
new PropertyMapping('airedFrom', '', PropertyMappingOption.None),
new PropertyMapping('airedTo', '', PropertyMappingOption.None),
new PropertyMapping('watched', '', PropertyMappingOption.None),
new PropertyMapping('lastWatched', '', PropertyMappingOption.None),
new PropertyMapping('personalRating', '', PropertyMappingOption.None),
],
},
],
};
export class MediaDbSettingTab extends PluginSettingTab {
@ -298,6 +357,7 @@ export class MediaDbSettingTab extends PluginSettingTab {
containerEl.createEl('h3', {text: 'Property Mappings'});
// region Property Mappings
/*
new Setting(containerEl)
.setName('Movie model property mappings')
.setDesc('Mappings for the property names of a movie.')
@ -369,8 +429,23 @@ export class MediaDbSettingTab extends PluginSettingTab {
this.plugin.saveSettings();
});
});
*/
// endregion
console.log(this.plugin.settings.propertyMappings);
new PropertyBindingsComponent({
target: this.containerEl,
props: {
models: this.plugin.settings.propertyMappings,
save: (models: PropertyMappingModel[]) => {
this.plugin.settings.propertyMappings = models;
this.plugin.saveSettings();
},
},
});
}
}