Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MAPEX-200: Fix File Import Limitations #32

Merged
merged 7 commits into from
Feb 29, 2024
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import * as EditorCommands from "@/assets/scripts/MappingFileEditor/EditorCommands";
import { randomUUID } from "@/assets/scripts/Utilities";
import { AppCommand } from "../AppCommand";
import { MappingFileEditor, Reactivity } from "@/assets/scripts/MappingFileEditor";
import { Reactivity } from "@/assets/scripts/MappingFileAuthority";
import { MappingFileEditor } from "@/assets/scripts/MappingFileEditor";
import type { ListProperty } from "@/assets/scripts/MappingFile";
import type { ApplicationStore } from "@/stores/ApplicationStore";
import type { MappingFileAuthority, MappingFileImport } from "@/assets/scripts/MappingFileAuthority";

export class ImportFile extends AppCommand {

/**
* The editor to import into.
*/
public readonly editor: MappingFileEditor;

/**
* The mapping file authority to use.
* The mapping file authority.
*/
public readonly fileAuthority: MappingFileAuthority;

Expand All @@ -21,6 +19,11 @@ export class ImportFile extends AppCommand {
*/
public readonly importFile: MappingFileImport;

/**
* The editor to import into.
*/
public readonly editor: MappingFileEditor;


/**
* Imports a mapping file export into the active editor.
Expand All @@ -31,25 +34,26 @@ export class ImportFile extends AppCommand {
*/
constructor(context: ApplicationStore, importFile: MappingFileImport) {
super();
this.editor = context.activeEditor as MappingFileEditor;
this.fileAuthority = context.fileAuthority as MappingFileAuthority;
this.editor = context.activeEditor as MappingFileEditor;
const file = this.editor.file;
// Validate source framework
if(this.editor.file.sourceFramework !== importFile.source_framework) {
if(file.sourceFramework !== importFile.source_framework) {
throw new Error(
`The imported file's source framework ('${
importFile.source_framework
}') doesn't match this file's source framework ('${
this.editor.file.sourceFramework
file.sourceFramework
}').`
);
}
// Validate target framework
if(this.editor.file.targetFramework !== importFile.target_framework) {
if(file.targetFramework !== importFile.target_framework) {
throw new Error(
`The imported file's target framework ('${
importFile.target_framework
}') doesn't match this file's target framework ('${
this.editor.file.targetFramework
file.targetFramework
}').`
);
}
Expand All @@ -63,28 +67,83 @@ export class ImportFile extends AppCommand {
public execute(): void {
const file = this.editor.file;
const view = this.editor.view;
const rawEditor = Reactivity.toRaw(this.editor);
const rawFileAuthority = Reactivity.toRaw(this.fileAuthority);
// Compile mapping items
const objs = new Map();
for(const exp of this.importFile.mapping_objects ?? []) {
const obj = rawFileAuthority.initializeMappingObjectImport(exp, rawEditor.file);
objs.set(obj.id, obj);
const convert = Reactivity.toRaw(this.fileAuthority.convertMappingObjectImportToParams);
// Compile mapping objects
const objects = new Map<string, EditorCommands.IdentifiedMappingObjectParameters>();
for(const obj of this.importFile.mapping_objects ?? []) {
const objectId = randomUUID();
objects.set(objectId, { objectId, ...convert(obj) });
}
// Configure view command
// Compile insert command
const insertCmd = EditorCommands.createGroupCommand(
this.addMissingObjectItems(this.importFile.mapping_types, file.mappingTypes),
this.addMissingTextItems(this.importFile.capability_groups, file.capabilityGroups),
this.addMissingTextItems(this.importFile.mapping_statuses, file.mappingStatuses),
this.addMissingTextItems(this.importFile.score_categories, file.scoreCategories),
this.addMissingTextItems(this.importFile.score_values, file.scoreValues),
EditorCommands.importMappingObjects(file, [...objects.values()])
)
// Compile view command
const cmd = EditorCommands.createSplitPhaseViewCommand(
EditorCommands.insertMappingObjects(file, [...objs.values()]),
insertCmd,
() => [
EditorCommands.rebuildViewBreakouts(view),
EditorCommands.unselectAllMappingObjectViews(view),
EditorCommands.selectMappingObjectViewsById(view, [...objs.keys()])
EditorCommands.selectMappingObjectViewsById(view, [...objects.keys()])
]
)
// Execute insert
this.editor.execute(cmd);
// Move first item into view
const firstItem = view.getItems(o => objs.has(o.id)).next().value;
const firstItem = view.getItems(o => objects.has(o.id)).next().value;
view.moveToViewItem(firstItem.object.id, 0, true, false);
}

/**
* Inserts missing text items into a {@link ListProperty}.
* @param obj
* The full item list.
* @param prop
* The {@link ListProperty}.
* @returns
* A command that represents the action.
*/
private addMissingTextItems(obj: { [key: string]: string }, prop: ListProperty) {
const insertCmd = EditorCommands.createGroupCommand();
for(const id in obj) {
if(!prop.findListItemId(i => i.getAsString("id") === id)) {
const newItem = prop.createNewItem({
id : id,
name : obj[id]
});
insertCmd.do(EditorCommands.addItemToListProperty(prop, newItem));
}
}
return insertCmd;
}

/**
* Inserts missing object items into a {@link ListProperty}.
* @param obj
* The full item list.
* @param prop
* The {@link ListProperty}.
* @returns
* A command that represents the action.
*/
private addMissingObjectItems(obj: { [key: string]: { name: string, description: string } }, prop: ListProperty) {
const insertCmd = EditorCommands.createGroupCommand();
for(const id in obj) {
if(!prop.findListItemId(i => i.getAsString("id") === id)) {
const newItem = prop.createNewItem({
id : id,
name : obj[id].name,
description : obj[id].description
});
insertCmd.do(EditorCommands.addItemToListProperty(prop, newItem));
}
}
return insertCmd;
}

}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Configuration from "@/assets/configuration/app.config";
import { Browser } from "@/assets/scripts/Utilities/Browser";
import { LoadFile } from "./LoadFile";
import { ImportFile } from './ImportFile';
Expand Down Expand Up @@ -77,20 +78,28 @@ export async function importExistingFile(context: ApplicationStore, file: string
* A command that represents the action.
*/
export async function loadFileFromFileSystem(context: ApplicationStore): Promise<AppCommand> {
const { filename, contents } = await Browser.openTextFileDialog();
const { filename, contents } = await Browser.openTextFileDialog([Configuration.file_type_extension], false);
return loadExistingFile(context, contents as string, filename);
}

/**
* Imports a mapping file, from the file system, into the active editor.
* Imports mapping files, from the file system, into the active editor.
* @param context
* The application's context.
* @returns
* A command that represents the action.
*/
export async function importFileFromFileSystem(context: ApplicationStore): Promise<AppCommand> {
const { contents } = await Browser.openTextFileDialog();
return importExistingFile(context, contents as string);
const files = await Browser.openTextFileDialog([Configuration.file_type_extension], true);
// Deserialize files
const json = new Array<MappingFileImport>(files.length);
for(let i = 0; i < json.length; i++) {
json[i] = context.fileSerializer.deserialize(files[i].contents as string);
}
// Merge files
const file = context.fileAuthority.mergeMappingFileImports(json);
// Return command
return new ImportFile(context, file);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as EditorCommands from "@/assets/scripts/MappingFileEditor/EditorCommands"
import { randomUUID } from "@/assets/scripts/Utilities";
import { AppCommand } from "../AppCommand";
import { Reactivity, type MappingFileEditor } from "@/assets/scripts/MappingFileEditor";
import type { ApplicationStore } from "@/stores/ApplicationStore";
Expand Down Expand Up @@ -44,32 +45,29 @@ export class PasteMappingObjects extends AppCommand {
public execute(): void {
navigator.clipboard.readText().then((text: string) => {
const view = this.editor.view;
const rawEditor = Reactivity.toRaw(this.editor);
const rawFileAuthority = Reactivity.toRaw(this.fileAuthority);
const rawFileSerializer = Reactivity.toRaw(this.fileSerializer);
const convert = Reactivity.toRaw(this.fileAuthority.convertMappingObjectImportToParams);
// Deserialize items
const exports = rawFileSerializer.processPaste(text);
// Configure insert
const objs = new Map();
for(const exp of exports){
// Create object
const obj = rawFileAuthority.initializeMappingObjectImport(exp, rawEditor.file);
// Store object id
objs.set(obj.id, obj);
const imports = rawFileSerializer.processPaste(text);
// Compile mapping objects
const objects = new Map<string, EditorCommands.IdentifiedMappingObjectParameters>();
for(const obj of imports){
const objectId = randomUUID()
objects.set(objectId, { objectId, ...convert(obj) })
}
// Configure view command
// Compile view command
const cmd = EditorCommands.createSplitPhaseViewCommand(
EditorCommands.insertMappingObjects(this.editor.file, [...objs.values()]),
EditorCommands.importMappingObjects(this.editor.file, [...objects.values()]),
() => [
EditorCommands.rebuildViewBreakouts(view),
EditorCommands.unselectAllMappingObjectViews(view),
EditorCommands.selectMappingObjectViewsById(view, [...objs.keys()]),
EditorCommands.selectMappingObjectViewsById(view, [...objects.keys()]),
]
)
// Execute insert
this.editor.execute(cmd);
// Move first item into view
const firstItem = view.getItems(o => objs.has(o.id)).next().value;
const firstItem = view.getItems(o => objects.has(o.id)).next().value;
view.moveToViewItem(firstItem.object.id, 0, true, false);
}).catch(reason => {
console.error("Failed to read clipboard: ", reason);
Expand Down
Loading
Loading