mirror of
https://github.com/koloml/furbooru-tagging-assistant.git
synced 2025-12-24 07:12:57 +00:00
Merge pull request #50 from koloml/feature/moving-import-and-export-to-separate-class
Slightly reduced extension content scripts size by extracting import/export logic into separate class
This commit is contained in:
73
src/lib/extension/EntitiesTransporter.ts
Normal file
73
src/lib/extension/EntitiesTransporter.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import {validateImportedEntity} from "$lib/extension/transporting/validators.js";
|
||||
import {exportEntityToObject} from "$lib/extension/transporting/exporters.js";
|
||||
import StorageEntity from "./base/StorageEntity.js";
|
||||
import {compressToEncodedURIComponent, decompressFromEncodedURIComponent} from "lz-string";
|
||||
|
||||
type EntityConstructor<T extends StorageEntity> =
|
||||
(new (id: string, settings: Record<string, any>) => T)
|
||||
& typeof StorageEntity;
|
||||
|
||||
export default class EntitiesTransporter<EntityType extends StorageEntity> {
|
||||
readonly #targetEntityConstructor: EntityConstructor<EntityType>;
|
||||
|
||||
constructor(entityConstructor: EntityConstructor<EntityType>) {
|
||||
this.#targetEntityConstructor = entityConstructor;
|
||||
}
|
||||
|
||||
importFromJSON(jsonString: string): EntityType {
|
||||
const importedObject = this.#tryParsingAsJSON(jsonString);
|
||||
|
||||
if (!importedObject) {
|
||||
throw new Error('Invalid JSON!');
|
||||
}
|
||||
|
||||
validateImportedEntity(
|
||||
importedObject,
|
||||
this.#targetEntityConstructor._entityName
|
||||
);
|
||||
|
||||
return new this.#targetEntityConstructor(
|
||||
importedObject.id,
|
||||
importedObject
|
||||
);
|
||||
}
|
||||
|
||||
importFromCompressedJSON(compressedJsonString: string): EntityType {
|
||||
return this.importFromJSON(
|
||||
decompressFromEncodedURIComponent(compressedJsonString)
|
||||
)
|
||||
}
|
||||
|
||||
exportToJSON(entityObject: EntityType): string {
|
||||
if (!(entityObject instanceof this.#targetEntityConstructor)) {
|
||||
throw new TypeError('Transporter should be connected to the same entity to export!');
|
||||
}
|
||||
|
||||
const exportableObject = exportEntityToObject(
|
||||
entityObject,
|
||||
this.#targetEntityConstructor._entityName
|
||||
);
|
||||
|
||||
return JSON.stringify(exportableObject, null, 2);
|
||||
}
|
||||
|
||||
exportToCompressedJSON(entityObject: EntityType): string {
|
||||
return compressToEncodedURIComponent(this.exportToJSON(entityObject));
|
||||
}
|
||||
|
||||
#tryParsingAsJSON(jsonString: string): Record<string, any> | null {
|
||||
let jsonObject: Record<string, any> | null = null;
|
||||
|
||||
try {
|
||||
jsonObject = JSON.parse(jsonString);
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
|
||||
if (typeof jsonObject !== "object") {
|
||||
throw new TypeError("Should be an object!");
|
||||
}
|
||||
|
||||
return jsonObject
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import StorageEntity from "$lib/extension/base/StorageEntity.js";
|
||||
import EntitiesController from "$lib/extension/EntitiesController.js";
|
||||
import {compressToEncodedURIComponent, decompressFromEncodedURIComponent} from "lz-string";
|
||||
|
||||
/**
|
||||
* @typedef {Object} MaintenanceProfileSettings
|
||||
@@ -30,26 +29,6 @@ class MaintenanceProfile extends StorageEntity {
|
||||
return super.settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the profile to the formatted JSON.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
toJSON() {
|
||||
return JSON.stringify({
|
||||
v: 1,
|
||||
id: this.id,
|
||||
name: this.settings.name,
|
||||
tags: this.settings.tags,
|
||||
}, null, 2);
|
||||
}
|
||||
|
||||
toCompressedJSON() {
|
||||
return compressToEncodedURIComponent(
|
||||
this.toJSON()
|
||||
);
|
||||
}
|
||||
|
||||
static _entityName = "profiles";
|
||||
|
||||
/**
|
||||
@@ -79,62 +58,6 @@ class MaintenanceProfile extends StorageEntity {
|
||||
callback
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and import the profile from the JSON.
|
||||
* @param {string} exportedString JSON for profile.
|
||||
* @return {MaintenanceProfile} Maintenance profile imported from the JSON. Note that profile is not automatically
|
||||
* saved.
|
||||
* @throws {Error} When version is unsupported or format is invalid.
|
||||
*/
|
||||
static importFromJSON(exportedString) {
|
||||
let importedObject;
|
||||
|
||||
try {
|
||||
importedObject = JSON.parse(exportedString);
|
||||
} catch (e) {
|
||||
// Error will be sent later, since empty string could be parsed as nothing without raising the error.
|
||||
}
|
||||
|
||||
if (!importedObject) {
|
||||
throw new Error('Invalid JSON!');
|
||||
}
|
||||
|
||||
if (importedObject.v !== 1) {
|
||||
throw new Error('Unsupported version!');
|
||||
}
|
||||
|
||||
if (
|
||||
!importedObject.id
|
||||
|| typeof importedObject.id !== "string"
|
||||
|| !importedObject.name
|
||||
|| typeof importedObject.name !== "string"
|
||||
|| !importedObject.tags
|
||||
|| !Array.isArray(importedObject.tags)
|
||||
) {
|
||||
throw new Error('Invalid profile format detected!');
|
||||
}
|
||||
|
||||
return new MaintenanceProfile(
|
||||
importedObject.id,
|
||||
{
|
||||
name: importedObject.name,
|
||||
tags: importedObject.tags,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and import the profile from the compressed JSON string.
|
||||
* @param {string} compressedString
|
||||
* @return {MaintenanceProfile}
|
||||
* @throws {Error} When version is unsupported or format is invalid.
|
||||
*/
|
||||
static importFromCompressedJSON(compressedString) {
|
||||
return this.importFromJSON(
|
||||
decompressFromEncodedURIComponent(compressedString)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MaintenanceProfile;
|
||||
|
||||
26
src/lib/extension/transporting/exporters.js
Normal file
26
src/lib/extension/transporting/exporters.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @type {Map<string, ((entity: import('../base/StorageEntity.js').default) => Record<string, any>)>}
|
||||
*/
|
||||
const entitiesExporters = new Map([
|
||||
['profiles', /** @param {import('../entities/MaintenanceProfile.js').default} entity */entity => {
|
||||
return {
|
||||
v: 1,
|
||||
id: entity.id,
|
||||
name: entity.settings.name,
|
||||
tags: entity.settings.tags,
|
||||
}
|
||||
}]
|
||||
])
|
||||
|
||||
/**
|
||||
* @param entityInstance
|
||||
* @param {string} entityName
|
||||
* @returns {Record<string, *>}
|
||||
*/
|
||||
export function exportEntityToObject(entityInstance, entityName) {
|
||||
if (!entitiesExporters.has(entityName)) {
|
||||
throw new Error(`Missing exporter for entity: ${entityName}`);
|
||||
}
|
||||
|
||||
return entitiesExporters.get(entityName).call(null, entityInstance);
|
||||
}
|
||||
39
src/lib/extension/transporting/validators.js
Normal file
39
src/lib/extension/transporting/validators.js
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Map of validators for each entity. Function should throw the error if validation failed.
|
||||
* @type {Map<string, ((importedObject: Object) => void)>}
|
||||
*/
|
||||
const entitiesValidators = new Map([
|
||||
['profiles', importedObject => {
|
||||
if (importedObject.v !== 1) {
|
||||
throw new Error('Unsupported version!');
|
||||
}
|
||||
|
||||
if (
|
||||
!importedObject.id
|
||||
|| typeof importedObject.id !== "string"
|
||||
|| !importedObject.name
|
||||
|| typeof importedObject.name !== "string"
|
||||
|| !importedObject.tags
|
||||
|| !Array.isArray(importedObject.tags)
|
||||
) {
|
||||
throw new Error('Invalid profile format detected!');
|
||||
}
|
||||
}]
|
||||
])
|
||||
|
||||
/**
|
||||
* Validate the structure of the entity.
|
||||
* @param {Object} importedObject Object imported from JSON.
|
||||
* @param {string} entityName Name of the entity to validate. Should be loaded from the entity class.
|
||||
* @throws {Error} Error in case validation failed with the reason stored in the message.
|
||||
*/
|
||||
export function validateImportedEntity(importedObject, entityName) {
|
||||
if (!entitiesValidators.has(entityName)) {
|
||||
console.error(`Trying to validate entity without the validator present! Entity name: ${entityName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
entitiesValidators
|
||||
.get(entityName)
|
||||
.call(null, importedObject);
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
<script>
|
||||
import {page} from "$app/stores";
|
||||
import {goto} from "$app/navigation";
|
||||
import {maintenanceProfilesStore} from "$stores/maintenance-profiles-store.js";
|
||||
import { page } from "$app/stores";
|
||||
import { goto } from "$app/navigation";
|
||||
import { maintenanceProfilesStore } from "$stores/maintenance-profiles-store.js";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import FormContainer from "$components/ui/forms/FormContainer.svelte";
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import EntitiesTransporter from "$lib/extension/EntitiesTransporter.ts";
|
||||
import MaintenanceProfile from "$entities/MaintenanceProfile.js";
|
||||
|
||||
const profileId = $page.params.id;
|
||||
|
||||
@@ -14,6 +16,7 @@
|
||||
*/
|
||||
const profile = $maintenanceProfilesStore.find(profile => profile.id === profileId);
|
||||
|
||||
const profilesTransporter = new EntitiesTransporter(MaintenanceProfile);
|
||||
/** @type {string} */
|
||||
let exportedProfile = '';
|
||||
/** @type {string} */
|
||||
@@ -22,8 +25,8 @@
|
||||
if (!profile) {
|
||||
goto('/features/maintenance/');
|
||||
} else {
|
||||
exportedProfile = profile.toJSON();
|
||||
compressedProfile = profile.toCompressedJSON();
|
||||
exportedProfile = profilesTransporter.exportToJSON(profile);
|
||||
compressedProfile = profilesTransporter.exportToCompressedJSON(profile);
|
||||
}
|
||||
|
||||
let isCompressedProfileShown = true;
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
import ProfileView from "$components/maintenance/ProfileView.svelte";
|
||||
import {maintenanceProfilesStore} from "$stores/maintenance-profiles-store.js";
|
||||
import {goto} from "$app/navigation";
|
||||
import EntitiesTransporter from "$lib/extension/EntitiesTransporter.ts";
|
||||
|
||||
const profilesTransporter = new EntitiesTransporter(MaintenanceProfile);
|
||||
|
||||
/** @type {string} */
|
||||
let importedString = '';
|
||||
@@ -32,10 +35,10 @@
|
||||
|
||||
try {
|
||||
if (importedString.trim().startsWith('{')) {
|
||||
candidateProfile = MaintenanceProfile.importFromJSON(importedString);
|
||||
candidateProfile = profilesTransporter.importFromJSON(importedString);
|
||||
}
|
||||
|
||||
candidateProfile = MaintenanceProfile.importFromCompressedJSON(importedString);
|
||||
candidateProfile = profilesTransporter.importFromCompressedJSON(importedString);
|
||||
} catch (error) {
|
||||
errorMessage = error instanceof Error
|
||||
? error.message
|
||||
|
||||
Reference in New Issue
Block a user