diff --git a/src/Components/FileTree/Actions/FileTreeMoveAction.ts b/src/Components/FileTree/Actions/FileTreeMoveAction.ts new file mode 100644 index 0000000..5e575bd --- /dev/null +++ b/src/Components/FileTree/Actions/FileTreeMoveAction.ts @@ -0,0 +1,23 @@ +import {FileSystemPath} from "../FileSystemsTypes"; +import {useFileSystems} from "../Store/FileSystemsStore"; +import {useAppStore} from "../../../Stores/AppStore"; + +const updateCurrentlyLoadedFilesIfNeeded = (currentPath: FileSystemPath, newPath: FileSystemPath) => { + const fileSystemAppStore = useAppStore(); + if (fileSystemAppStore.selectedKsyInfo.isPartOf(currentPath)) { + const newCurrentlyLoadedFilePath = fileSystemAppStore.selectedKsyInfo.replacePathPart(currentPath, newPath); + fileSystemAppStore.updateSelectedKsyFile(newCurrentlyLoadedFilePath); + } + + if (fileSystemAppStore.selectedBinaryInfo.isPartOf(currentPath)) { + const newCurrentlyLoadedFilePath = fileSystemAppStore.selectedBinaryInfo.replacePathPart(currentPath, newPath); + fileSystemAppStore.updateSelectedKsyFile(newCurrentlyLoadedFilePath); + } +}; + +export const FileTreeMoveAction = async (oldPath: FileSystemPath, newPath: FileSystemPath) => { + const fileSystemsStore = useFileSystems(); + await fileSystemsStore.move(oldPath, newPath); + updateCurrentlyLoadedFilesIfNeeded(oldPath, newPath); + fileSystemsStore.selectPath(newPath) +} \ No newline at end of file diff --git a/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionCloseDirectory.ts b/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionCloseDirectory.ts index 9f13d31..e1d4a01 100644 --- a/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionCloseDirectory.ts +++ b/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionCloseDirectory.ts @@ -3,11 +3,12 @@ import {MenuItem} from "@imengyu/vue3-context-menu/lib/ContextMenuDefine"; import {h} from "vue"; import {useFileSystems} from "../../Store/FileSystemsStore"; import {FolderIcon} from "@heroicons/vue/16/solid"; +import {FileSystemPath} from "../../FileSystemsTypes"; export const FileTreeCtxActionCloseDirectory = (item: TreeNodeDisplay): MenuItem => { const action = () => { const fileSystemsStore = useFileSystems(); - fileSystemsStore.closePath(item.fullPathWithStore); + fileSystemsStore.closePath(FileSystemPath.fromFullPathWithStore(item.fullPathWithStore)); }; return { diff --git a/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionCreateDirectory.ts b/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionCreateDirectory.ts index 528bb62..f7c790a 100644 --- a/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionCreateDirectory.ts +++ b/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionCreateDirectory.ts @@ -4,6 +4,7 @@ import {h} from "vue"; import {useFileSystems} from "../../Store/FileSystemsStore"; import {FILE_SYSTEM_TYPE_KAITAI} from "../../FileSystems/KaitaiFileSystem"; import {FolderPlusIcon} from "@heroicons/vue/16/solid"; +import {FileSystemPath} from "../../FileSystemsTypes"; export const FileTreeCtxActionCreateDirectory = (item: TreeNodeDisplay): MenuItem => { const action = () => { @@ -12,10 +13,9 @@ export const FileTreeCtxActionCreateDirectory = (item: TreeNodeDisplay): MenuIte const fullPathToNewFolder = item.fullPath ? `${item.fullPath}/${newFolderName}` : newFolderName; - const fullPathToNewFolderWithStore = `${item.storeId}:${fullPathToNewFolder}`; fileStore.createDirectory(item.storeId, fullPathToNewFolder); - fileStore.openPath(item.fullPathWithStore); - fileStore.selectPath(fullPathToNewFolderWithStore); + fileStore.openPath(FileSystemPath.fromFullPathWithStore(item.fullPathWithStore)); + fileStore.selectPath(FileSystemPath.of(item.storeId, fullPathToNewFolder)); }; return { diff --git a/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionCreateKsy.ts b/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionCreateKsy.ts index aae5581..e97a1e1 100644 --- a/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionCreateKsy.ts +++ b/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionCreateKsy.ts @@ -6,6 +6,7 @@ import {FILE_SYSTEM_TYPE_KAITAI} from "../../FileSystems/KaitaiFileSystem"; import {createNewKsyAction} from "../../../../GlobalActions/CreateNewKsyAction"; import {useTextModalInputStore} from "../../../Modals/TextInputModal/TextInputModalStore"; import {DocumentPlusIcon} from "@heroicons/vue/16/solid"; +import {FileSystemPath} from "../../FileSystemsTypes"; export const FileTreeCtxActionCreateKsy = (item: TreeNodeDisplay): MenuItem => { const action = () => { @@ -13,7 +14,7 @@ export const FileTreeCtxActionCreateKsy = (item: TreeNodeDisplay): MenuItem => { store.open({ title: "Add new KSY", onAccept: (fileName) => { - createNewKsyAction(item.storeId, item.fullPath, fileName); + createNewKsyAction(FileSystemPath.fromFullPathWithStore(item.fullPathWithStore), fileName); }, }); }; diff --git a/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionDelete.ts b/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionDelete.ts index b4a6158..6324fdd 100644 --- a/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionDelete.ts +++ b/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionDelete.ts @@ -4,11 +4,12 @@ import {h} from "vue"; import {useFileSystems} from "../../Store/FileSystemsStore"; import {FILE_SYSTEM_TYPE_KAITAI} from "../../FileSystems/KaitaiFileSystem"; import {TrashIcon} from "@heroicons/vue/16/solid"; +import {FileSystemPath} from "../../FileSystemsTypes"; export const FileTreeCtxActionDelete = (item: TreeNodeDisplay): MenuItem => { const action = async () => { const fileSystemStore = useFileSystems(); - fileSystemStore.deletePath(item.storeId, item.fullPath); + fileSystemStore.deletePath(FileSystemPath.fromFullPathWithStore(item.fullPathWithStore)); }; return { diff --git a/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionDownload.ts b/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionDownload.ts index f380aa5..18fae16 100644 --- a/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionDownload.ts +++ b/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionDownload.ts @@ -3,7 +3,7 @@ import {MenuItem} from "@imengyu/vue3-context-menu/lib/ContextMenuDefine"; import {h} from "vue"; import {FileActionsWrapper} from "../../../../Utils/Files/FileActionsWrapper"; import {useFileSystems} from "../../Store/FileSystemsStore"; -import {BoltIcon, CloudArrowDownIcon} from "@heroicons/vue/16/solid"; +import {CloudArrowDownIcon} from "@heroicons/vue/16/solid"; export const FileTreeCtxActionDownload = (item: TreeNodeDisplay): MenuItem => { const action = async () => { diff --git a/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionGenerateParser.ts b/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionGenerateParser.ts index 892d899..18a6064 100644 --- a/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionGenerateParser.ts +++ b/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionGenerateParser.ts @@ -20,17 +20,32 @@ export const mapFileTreeDisplayNodeToYaml = async (item: TreeNodeDisplay): Promi export const FileTreeCtxActionGenerateParser = (item: TreeNodeDisplay): MenuItem => { + + const toSimpleSnakeCase = (name: string) => { + return name.replace(/\W+/g, " ") + .split(/ |\B(?=[A-Z])/) + .map(word => word.toLowerCase()) + .join("_"); + }; + const createOnlyMainFileTab = (compiled: CompilationTarget, language: SupportedLanguage) => { - const [name, content] = Object.entries(compiled.result)[0]; + const record = Object.entries(compiled.result) + .find(([name, content]) => { + const nameInSnakeCase = toSimpleSnakeCase(name); + return nameInSnakeCase.indexOf(compiled.mainClassId) !== -1; + }); + if (!record) { + console.error("[GenerateOnlyMainFile] Could not find main file using schema!"); + return; + } + const [name, content] = record; CurrentGoldenLayout.addDynamicCodeTab(name, content, language.monacoEditorLangCode); - return true; }; const createTabsForAllGeneratedFiles = (compiled: CompilationTarget, language: SupportedLanguage) => { - Object.entries(compiled.result).reverse().forEach(([name, content]) => { + Object.entries(compiled.result).forEach(([name, content]) => { CurrentGoldenLayout.addDynamicCodeTab(name, content, language.monacoEditorLangCode); }); - return true; }; const generateParserForLanguage = async (language: SupportedLanguage) => { diff --git a/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionOpenBinFile.ts b/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionOpenBinFile.ts index a283651..0686fdf 100644 --- a/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionOpenBinFile.ts +++ b/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionOpenBinFile.ts @@ -3,14 +3,12 @@ import {MenuItem} from "@imengyu/vue3-context-menu/lib/ContextMenuDefine"; import {h} from "vue"; import {useAppStore} from "../../../../Stores/AppStore"; import {DocumentIcon} from "@heroicons/vue/16/solid"; +import {FileSystemPath} from "../../FileSystemsTypes"; export const FileTreeCtxActionOpenBinFile = (item: TreeNodeDisplay): MenuItem => { const action = () => { const appStore = useAppStore(); - appStore.updateSelectedBinaryFile({ - storeId: item.storeId, - filePath: item.fullPath - }); + appStore.updateSelectedBinaryFile(FileSystemPath.fromFullPathWithStore(item.fullPathWithStore)); }; return { diff --git a/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionOpenDirectory.ts b/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionOpenDirectory.ts index 6dd5993..d705365 100644 --- a/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionOpenDirectory.ts +++ b/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionOpenDirectory.ts @@ -3,11 +3,12 @@ import {MenuItem} from "@imengyu/vue3-context-menu/lib/ContextMenuDefine"; import {h} from "vue"; import {useFileSystems} from "../../Store/FileSystemsStore"; import {FolderIcon} from "@heroicons/vue/16/solid"; +import {FileSystemPath} from "../../FileSystemsTypes"; export const FileTreeCtxActionOpenDirectory = (item: TreeNodeDisplay): MenuItem => { const action = () => { const fileSystemsStore = useFileSystems(); - fileSystemsStore.openPath(item.fullPathWithStore); + fileSystemsStore.openPath(FileSystemPath.fromFullPathWithStore(item.fullPathWithStore)); }; return { diff --git a/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionOpenKsy.ts b/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionOpenKsy.ts index 90fa8da..35b7417 100644 --- a/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionOpenKsy.ts +++ b/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionOpenKsy.ts @@ -3,14 +3,12 @@ import {MenuItem} from "@imengyu/vue3-context-menu/lib/ContextMenuDefine"; import {h} from "vue"; import {useAppStore} from "../../../../Stores/AppStore"; import {DocumentTextIcon} from "@heroicons/vue/16/solid"; +import {FileSystemPath} from "../../FileSystemsTypes"; export const FileTreeCtxActionOpenKsy = (item: TreeNodeDisplay): MenuItem => { const action = async () => { const appStore = useAppStore(); - appStore.updateSelectedKsyFile({ - storeId: item.storeId, - filePath: item.fullPath - }); + appStore.updateSelectedKsyFile(FileSystemPath.fromFullPathWithStore(item.fullPathWithStore)); }; return { diff --git a/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionRename.ts b/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionRename.ts new file mode 100644 index 0000000..3e3d769 --- /dev/null +++ b/src/Components/FileTree/ContextMenu/Actions/FileTreeCtxActionRename.ts @@ -0,0 +1,37 @@ +import {TreeNodeDisplay} from "../../FileSystemVisitors/FileSystemFileTreeMapper"; +import {MenuItem} from "@imengyu/vue3-context-menu/lib/ContextMenuDefine"; +import {h} from "vue"; +import {PencilIcon} from "@heroicons/vue/16/solid"; +import {useTextModalInputStore} from "../../../Modals/TextInputModal/TextInputModalStore"; +import {useAppStore} from "../../../../Stores/AppStore"; +import {FileSystemPath} from "../../FileSystemsTypes"; +import {FILE_SYSTEM_TYPE_KAITAI} from "../../FileSystems/KaitaiFileSystem"; +import {FileTreeMoveAction} from "../../Actions/FileTreeMoveAction"; + +export const FileTreeCtxActionRename = (item: TreeNodeDisplay): MenuItem => { + + + const action = async () => { + const modalInputStore = useTextModalInputStore(); + modalInputStore.open({ + title: "Rename", + initValue: item.fileName, + onAccept: async (newName) => { + const newFullPathString = item.fullPath.replace(item.fileName, newName); + const oldPath = FileSystemPath.fromFullPathWithStore(item.fullPathWithStore); + const newPath = FileSystemPath.of(item.storeId, newFullPathString); + + FileTreeMoveAction(oldPath, newPath); + }, + }); + }; + + return { + label: "Rename", + onClick: action, + // hidden: [TreeNodeDisplayType.KSY_FILE].indexOf(item.type) === -1, + customClass: "context-menu-item", + disabled: item.storeId === FILE_SYSTEM_TYPE_KAITAI || item.fullPath === "", + icon: () => h(PencilIcon), + }; +}; \ No newline at end of file diff --git a/src/Components/FileTree/ContextMenu/FileTreeNodeContextMenu.ts b/src/Components/FileTree/ContextMenu/FileTreeNodeContextMenu.ts index bacc58c..bbe773f 100644 --- a/src/Components/FileTree/ContextMenu/FileTreeNodeContextMenu.ts +++ b/src/Components/FileTree/ContextMenu/FileTreeNodeContextMenu.ts @@ -10,9 +10,9 @@ import {FileTreeCtxActionOpenDirectory} from "./Actions/FileTreeCtxActionOpenDir import {FileTreeCtxActionCloseDirectory} from "./Actions/FileTreeCtxActionCloseDirectory"; import {FileTreeCtxActionCreateDirectory} from "./Actions/FileTreeCtxActionCreateDirectory"; import {FileTreeCtxActionCreateKsy} from "./Actions/FileTreeCtxActionCreateKsy"; +import {FileTreeCtxActionRename} from "./Actions/FileTreeCtxActionRename"; export const prepareContextMenuOptions = (e: MouseEvent, item: TreeNodeDisplay): MenuOptions => { - return { x: e.x, y: e.y, @@ -20,12 +20,19 @@ export const prepareContextMenuOptions = (e: MouseEvent, item: TreeNodeDisplay): theme: "flat dark", clickCloseOnOutside: true, items: [ + // { + // label: "Restore old files", + // onClick: () => RestoreOldFileTreeAction(), + // customClass: "context-menu-item", + // hidden: item.storeId === FILE_SYSTEM_TYPE_KAITAI || item.fullPath !== "" + // }, FileTreeCtxActionOpenKsy(item), FileTreeCtxActionOpenBinFile(item), FileTreeCtxActionOpenDirectory(item), FileTreeCtxActionCloseDirectory(item), FileTreeCtxActionCreateDirectory(item), FileTreeCtxActionCreateKsy(item), + FileTreeCtxActionRename(item), FileTreeCtxActionClone(item), FileTreeCtxActionGenerateParser(item), FileTreeCtxActionDownload(item), diff --git a/src/Components/FileTree/FileSystems/KaitaiFileSystem.ts b/src/Components/FileTree/FileSystems/KaitaiFileSystem.ts index 68984c6..a1c55f5 100644 --- a/src/Components/FileTree/FileSystems/KaitaiFileSystem.ts +++ b/src/Components/FileTree/FileSystems/KaitaiFileSystem.ts @@ -7,7 +7,7 @@ export const FILE_SYSTEM_TYPE_KAITAI = "kaitai"; const initKaitaiFs = () => { const root = {fsType: FILE_SYSTEM_TYPE_KAITAI, children: {}, fn: "kaitai.io", type: ITEM_MODE_DIRECTORY}; - (kaitaiFsFiles || []).forEach(fn => FsItemHelper.createFileOrDirectoryFromPathInRoot(root, fn)); + (kaitaiFsFiles || []).forEach(path => FsItemHelper.createFileOrDirectoryFromPathInRoot(root, path)); return root; }; @@ -40,6 +40,11 @@ export class KaitaiFileSystem implements FileSystem { async delete(filePath: string): Promise { return Promise.reject("KaitaiFileSystem.delete is not supported!"); } + + move(oldPath: string, newPath: string): Promise { + return Promise.reject("KaitaiFileSystem.move is not supported!"); + + } } diff --git a/src/Components/FileTree/FileSystems/LocalStorageFileSystem.ts b/src/Components/FileTree/FileSystems/LocalStorageFileSystem.ts index 2faf804..7ccc5ed 100644 --- a/src/Components/FileTree/FileSystems/LocalStorageFileSystem.ts +++ b/src/Components/FileTree/FileSystems/LocalStorageFileSystem.ts @@ -1,5 +1,5 @@ import {FsItemHelper} from "../Utils/FsItemHelper"; -import {FileSystem, FileSystemItem, ITEM_MODE_DIRECTORY} from "../FileSystemsTypes"; +import {FileSystem, FileSystemItem, ITEM_MODE_DIRECTORY, ITEM_MODE_FILE} from "../FileSystemsTypes"; import {LocalForageForLocalStorageWrapper} from "../Utils/LocalForageForLocalStorageWrapper"; import {toRaw} from "vue"; @@ -77,4 +77,22 @@ export class LocalStorageFileSystem implements FileSystem { LocalForageForLocalStorageWrapper.deleteFile(LocalStorageFileSystem.fileLocalForageKey(fileName)); }); } + + async move(oldPath: string, newPath: string): Promise { + const oldPathInfo = FsItemHelper.getInfoAboutPath(this.root, oldPath); + FsItemHelper.createFileOrDirectoryFromPathInRoot(this.root, newPath, oldPathInfo.node.type === ITEM_MODE_DIRECTORY); + const newPathInfo = FsItemHelper.getInfoAboutPath(this.root, newPath); + delete oldPathInfo.parentNode.children[oldPathInfo.nodeName]; + newPathInfo.parentNode.children[newPathInfo.nodeName] = oldPathInfo.node; + if(oldPathInfo.node.type === ITEM_MODE_FILE) { + const oldFileForageKey = LocalStorageFileSystem.fileLocalForageKey(oldPathInfo.nodeName) + const newFileForageKey = LocalStorageFileSystem.fileLocalForageKey(newPathInfo.nodeName) + const content = await LocalForageForLocalStorageWrapper.getFile(oldFileForageKey) + await LocalForageForLocalStorageWrapper.saveFile(newFileForageKey, content) + } + await this.updateTreeInDatabase(); + return Promise.resolve(undefined); + } + + } \ No newline at end of file diff --git a/src/Components/FileTree/FileSystemsTypes.ts b/src/Components/FileTree/FileSystemsTypes.ts index 90a6f35..67b3b1c 100644 --- a/src/Components/FileTree/FileSystemsTypes.ts +++ b/src/Components/FileTree/FileSystemsTypes.ts @@ -10,6 +10,43 @@ export interface FileSystemItem { children?: { [key: string]: FileSystemItem; }; } +export class FileSystemPath { + + constructor(public storeId: string, public path: string) { + } + + public static of(storeId: string, path: string) { + return new FileSystemPath(storeId, path); + } + + public static fromFullPathWithStore(fullPathWithStore: string) { + const [storeId, path] = fullPathWithStore.split(":"); + return new FileSystemPath(storeId, path); + } + + public isInTheSameStore(other: FileSystemPath) { + return this.storeId === other.storeId + } + + public isTheSame(other: FileSystemPath) { + return this.storeId === other.storeId && + this.path === other.path; + } + + public isPartOf(other: FileSystemPath) { + return this.storeId === other.storeId && + this.path.startsWith(other.path); + } + + public replacePathPart(oldFragment: FileSystemPath, newFragment: FileSystemPath): FileSystemPath { + const newPath = this.path.replace(oldFragment.path, newFragment.path); + return FileSystemPath.of(this.storeId, newPath); + } + public toString() { + return `${this.storeId}:${this.path}`; + } +} + export interface FileSystem { storeId: string; @@ -22,4 +59,6 @@ export interface FileSystem { createDirectory(filePath: string): Promise; delete(filePath: string): Promise; + + move(oldPath: string, newPath: string): Promise; } diff --git a/src/Components/FileTree/FileTreeNode.vue b/src/Components/FileTree/FileTreeNode.vue index ea26988..3a34c2e 100644 --- a/src/Components/FileTree/FileTreeNode.vue +++ b/src/Components/FileTree/FileTreeNode.vue @@ -6,6 +6,7 @@ import {computed, toRaw} from "vue"; import {useAppStore} from "../../Stores/AppStore"; import ContextMenu from "@imengyu/vue3-context-menu"; import {prepareContextMenuOptions} from "./ContextMenu/FileTreeNodeContextMenu"; +import {FileSystemPath} from "./FileSystemsTypes"; const store = useFileSystems(); const appStore = useAppStore(); @@ -14,34 +15,28 @@ const props = defineProps<{ }>(); const activateRow = () => { - store.selectPath(props.item.fullPathWithStore); + store.selectPath(FileSystemPath.fromFullPathWithStore(props.item.fullPathWithStore)); }; const doubleClick = () => { switch (props.item.type) { case TreeNodeDisplayType.KSY_FILE: { - appStore.updateSelectedKsyFile({ - storeId: props.item.storeId, - filePath: props.item.fullPath - }); + appStore.updateSelectedKsyFile(FileSystemPath.fromFullPathWithStore(props.item.fullPathWithStore)); return; } case TreeNodeDisplayType.BINARY_FILE: { - appStore.updateSelectedBinaryFile({ - storeId: props.item.storeId, - filePath: props.item.fullPath - }); + appStore.updateSelectedBinaryFile(FileSystemPath.fromFullPathWithStore(props.item.fullPathWithStore)); return; } case TreeNodeDisplayType.EMPTY_FOLDER: { return; } case TreeNodeDisplayType.OPEN_FOLDER: { - store.closePath(props.item.fullPathWithStore); + store.closePath(FileSystemPath.fromFullPathWithStore(props.item.fullPathWithStore)); return; } case TreeNodeDisplayType.CLOSED_FOLDER: { - store.openPath(props.item.fullPathWithStore); + store.openPath(FileSystemPath.fromFullPathWithStore(props.item.fullPathWithStore)); return; } } @@ -49,7 +44,7 @@ const doubleClick = () => { const contextMenu = (e: MouseEvent) => { e.preventDefault(); - store.selectPath(props.item.fullPathWithStore); + store.selectPath(FileSystemPath.fromFullPathWithStore(props.item.fullPathWithStore)); const contextMenuOptions = prepareContextMenuOptions(e, toRaw(props.item)); ContextMenu.showContextMenu(contextMenuOptions); }; diff --git a/src/Components/FileTree/Store/FileSystemsStore.ts b/src/Components/FileTree/Store/FileSystemsStore.ts index 8b7d7f2..004ef20 100644 --- a/src/Components/FileTree/Store/FileSystemsStore.ts +++ b/src/Components/FileTree/Store/FileSystemsStore.ts @@ -1,5 +1,5 @@ import {defineStore} from "pinia"; -import {FileSystem} from "../FileSystemsTypes"; +import {FileSystem, FileSystemPath} from "../FileSystemsTypes"; export interface FileSystemsStore { fileSystems: FileSystem[]; @@ -16,6 +16,19 @@ const serializeConfigToLocalStorage = (store: FileSystemsStore) => { localStorage.setItem("FileTree", config); }; +const validateMoveInputs = (oldPath: FileSystemPath, newPath: FileSystemPath) => { + const isTryingToMoveRoot = oldPath.path === ""; + if (isTryingToMoveRoot) { + throw new Error(`[FileSystemsStore][move] Root cannot be moved!`) + } + if(!oldPath.isInTheSameStore(newPath)) { + throw new Error(`[FileSystemsStore][move] Changing paths between stores is not supported!`); + } + + if(oldPath.isPartOf(newPath)){ + throw new Error(`[FileSystemsStore][move] Trying to nest part of a path in it self! ${oldPath.path} in ${newPath.path}`); + } +} export const useFileSystems = defineStore("FileSystemsStore", { state: (): FileSystemsStore => { @@ -29,16 +42,17 @@ export const useFileSystems = defineStore("FileSystemsStore", { addFileSystem(fileSystem: FileSystem) { this.fileSystems.push(fileSystem); }, - openPath(pathToAdd: string) { - this.openPaths.push(pathToAdd); + openPath(pathToAdd: FileSystemPath) { + this.openPaths.push(pathToAdd.toString()); serializeConfigToLocalStorage(this); }, - closePath(pathToRemove: string) { - this.openPaths = this.openPaths.filter((path: string) => path !== pathToRemove); + closePath(pathToRemove: FileSystemPath) { + const pathToRemoveStr = pathToRemove.toString(); + this.openPaths = this.openPaths.filter((path: string) => path !== pathToRemoveStr); serializeConfigToLocalStorage(this); }, - selectPath(path: string) { - this.selectedPath = path; + selectPath(path: FileSystemPath) { + this.selectedPath = path.toString(); serializeConfigToLocalStorage(this); }, async addFile(storeId: string, path: string, content: string | ArrayBuffer) { @@ -55,12 +69,30 @@ export const useFileSystems = defineStore("FileSystemsStore", { ? await fileSystem.get(filePath) : ""; }, - deletePath(storeId: string, filePath: string): void { - const fileSystem: FileSystem = this.fileSystems.find((fs: FileSystem) => fs.storeId === storeId); + + deletePath(path: FileSystemPath): void { + const fileSystem: FileSystem = this.fileSystems.find((fs: FileSystem) => fs.storeId === path.storeId); + if (fileSystem) { + fileSystem.delete(path.path); + } else { + console.error(`[FileSystemsStore][deletePath] Could not find file system! [${path.storeId}]`); + } + }, + + async move(oldPath: FileSystemPath, newPath: FileSystemPath): Promise { + validateMoveInputs(oldPath, newPath); + const fileSystem: FileSystem = this.fileSystems.find((fs: FileSystem) => fs.storeId === oldPath.storeId); if (fileSystem) { - fileSystem.delete(filePath); + await fileSystem.move(oldPath.path, newPath.path); + this.openPaths = this.openPaths.map((path: string) => { + const openPath = FileSystemPath.fromFullPathWithStore(path) + if (openPath.isPartOf(oldPath)) { + return openPath.replacePathPart(oldPath, newPath).toString(); + } + return path; + }); } else { - console.error(`[FileSystemsStore][deletePath] Could not find file system! [${storeId}]`); + console.error(`[FileSystemsStore][deletePath] Could not find file system! [${oldPath.storeId}]`); } } } diff --git a/src/Components/FileTree/Utils/FsItemHelper.ts b/src/Components/FileTree/Utils/FsItemHelper.ts index 9618c15..a2effff 100644 --- a/src/Components/FileTree/Utils/FsItemHelper.ts +++ b/src/Components/FileTree/Utils/FsItemHelper.ts @@ -2,6 +2,14 @@ import {FileSystemItem, ITEM_MODE_DIRECTORY, ITEM_MODE_FILE} from "../FileSystem import {ArrayUtils} from "../../../Utils/ArrayUtils"; import {FsItemPathsCollector} from "../FileSystemVisitors/FsItemPathsCollector"; +export interface FileSystemItemPathInfo { + isRoot: boolean; + nodeName: string; + node: FileSystemItem; + parentNode?: FileSystemItem; + path: string; +} + export class FsItemHelper { public static createFileOrDirectoryFromPathInRoot = (root: FileSystemItem, filePath: string, createDirectoryMode: boolean = false) => { const pathParts = filePath.split("/"); @@ -25,9 +33,9 @@ export class FsItemHelper { }; public static deletePathAndReturnFilesToDelete = (root: FileSystemItem, filePath: string): string[] => { - const isDeletingFromRoot = filePath.length === 0; + const isDeletingRoot = filePath.length === 0; - if (isDeletingFromRoot) { + if (isDeletingRoot) { const filesInRoot = FsItemPathsCollector.collectFiles(root); Object.keys(root.children || {}).forEach(key => delete root.children[key]); return filesInRoot; @@ -50,4 +58,34 @@ export class FsItemHelper { return nodeToDelete; }; + public static findNodeInRoot = (root: FileSystemItem, filePathParts: string[]) => { + let currNode = root; + for (let i = 0; i < filePathParts.length; i++) { + const fnPart = filePathParts[i]; + currNode = currNode.children[fnPart]; + } + return currNode; + }; + + public static getInfoAboutPath(root: FileSystemItem, path: string): FileSystemItemPathInfo { + if (path === "") return { + nodeName: "", + isRoot: true, + node: root, + path: path + }; + const pathParts = path.split("/"); + const nodeName = pathParts[pathParts.length - 1]; + const parentFolder = [...pathParts]; + parentFolder.pop(); + const parentNode = FsItemHelper.findNodeInRoot(root, parentFolder); + return { + isRoot: false, + nodeName: nodeName, + node: parentNode.children[nodeName], + parentNode: parentNode, + path: path + }; + } + } \ No newline at end of file diff --git a/src/DataManipulation/CompilationModule/CompilerService.ts b/src/DataManipulation/CompilationModule/CompilerService.ts index 2c27fc0..9c3eab3 100644 --- a/src/DataManipulation/CompilationModule/CompilerService.ts +++ b/src/DataManipulation/CompilationModule/CompilerService.ts @@ -15,6 +15,7 @@ export interface KaitaiCompilationResult { export interface CompilationTarget { result: KaitaiCompilationResult; + mainClassId: string; jsMainClassName: string; ksySchema: KsySchema.IKsyFile; ksyTypes: IKsyTypes; @@ -55,6 +56,7 @@ export class CompilerService { return { result: compilationTarget, ksySchema: schema, + mainClassId: this.getMainClassId(schema), jsMainClassName: this.getJsMainClassNameFromSchema(schema), ksyTypes: types }; @@ -126,6 +128,7 @@ export class CompilerService { return Promise.resolve({ result: {}, + mainClassId: this.getMainClassId(schema), jsMainClassName: this.getJsMainClassNameFromSchema(schema), ksySchema: schema, ksyTypes: types @@ -137,6 +140,7 @@ export class CompilerService { return Promise.resolve({ debug: {}, release: {}, + mainClassId: this.getMainClassId(schema), jsMainClassName: this.getJsMainClassNameFromSchema(schema), ksySchema: schema, ksyTypes: types @@ -147,4 +151,8 @@ export class CompilerService { return schema.meta.id.split("_").map((x: string) => StringUtils.ucFirst(x)).join(""); } + private getMainClassId(schema: IKsyFile) { + return schema.meta.id; + } + } \ No newline at end of file diff --git a/src/DataManipulation/CompilationModule/JsImporter.ts b/src/DataManipulation/CompilationModule/JsImporter.ts index 259b8b4..828dfa9 100644 --- a/src/DataManipulation/CompilationModule/JsImporter.ts +++ b/src/DataManipulation/CompilationModule/JsImporter.ts @@ -1,7 +1,8 @@ import {YamlParser} from "./YamlParser"; import {JsImporterError} from "./JsImporterError"; -import {FileLocationInfo} from "../../Stores/AppStore"; import {useFileSystems} from "../../Components/FileTree/Store/FileSystemsStore"; +import {FileSystemPath} from "../../Components/FileTree/FileSystemsTypes"; +import {FILE_SYSTEM_TYPE_KAITAI} from "../../Components/FileTree/FileSystems/KaitaiFileSystem"; export interface IYamlImporter { importYaml(importFilePath: string, mode: string): Promise; @@ -36,7 +37,7 @@ export class JsImporter implements IYamlImporter { return YamlParser.parseIKsyFile(yamlFile); } catch (e) { const sourceAppendix = mode === "abs" ? "kaitai.io" : "local storage"; - const errorMessage = `failed to import spec ${fileLocation.filePath} from ${sourceAppendix}${e.message ? ": " + e.message : ""}`; + const errorMessage = `failed to import spec ${fileLocation.path} from ${sourceAppendix}${e.message ? ": " + e.message : ""}`; throw new JsImporterError(errorMessage); } } @@ -45,30 +46,27 @@ export class JsImporter implements IYamlImporter { return Object.values(this.importedFilesCache); } - private deductFileLocation(importFilePath: string, mode: string): FileLocationInfo { - let loadFn; + private deductFileLocation(importFilePath: string, mode: string): FileSystemPath { + let fileName; let storeId = this.initialYaml.storeId; if (mode === "abs") { - loadFn = "formats/" + importFilePath; - storeId = "kaitai"; + fileName = "formats/" + importFilePath; + storeId = FILE_SYSTEM_TYPE_KAITAI; } else { - var fnParts = this.initialYaml.filePath.split("/"); + const fnParts = this.initialYaml.filePath.split("/"); fnParts.pop(); - loadFn = fnParts.join("/") + "/" + importFilePath; + fileName = fnParts.join("/") + "/" + importFilePath; - if (loadFn.startsWith("/")) { - loadFn = loadFn.substring(1); + if (fileName.startsWith("/")) { + fileName = fileName.substring(1); } } - return { - storeId: storeId, - filePath: `${loadFn}.ksy` - }; + return FileSystemPath.of(storeId, `${fileName}.ksy`) } - private async getFileByLocationWithCache(fileLocation: FileLocationInfo): Promise { - const uniqueKey = `${fileLocation.storeId}:${fileLocation.filePath}`; + private async getFileByLocationWithCache(fileLocation: FileSystemPath): Promise { + const uniqueKey = `${fileLocation.storeId}:${fileLocation.path}`; if (this.importedFilesCache.hasOwnProperty(uniqueKey)) return this.importedFilesCache[uniqueKey]; const newYamlFile = await this.loadYamlWithFileManager(fileLocation); @@ -77,11 +75,11 @@ export class JsImporter implements IYamlImporter { } - private async loadYamlWithFileManager({storeId, filePath}: FileLocationInfo): Promise { - const ksyContent = await useFileSystems().getFile(storeId, filePath) as string; + private async loadYamlWithFileManager({storeId, path}: FileSystemPath): Promise { + const ksyContent = await useFileSystems().getFile(storeId, path) as string; return { storeId: storeId, - filePath: filePath, + filePath: path, fileContent: ksyContent }; } diff --git a/src/GlobalActions/CreateNewKsyAction.ts b/src/GlobalActions/CreateNewKsyAction.ts index a12dda3..a3cce99 100644 --- a/src/GlobalActions/CreateNewKsyAction.ts +++ b/src/GlobalActions/CreateNewKsyAction.ts @@ -1,6 +1,7 @@ import {useFileSystems} from "../Components/FileTree/Store/FileSystemsStore"; import {useAppStore} from "../Stores/AppStore"; import {FileActionsWrapper} from "../Utils/Files/FileActionsWrapper"; +import {FileSystemPath} from "../Components/FileTree/FileSystemsTypes"; const sanitizePath = (path: string) => { const pathIsRoot = path.length === 0; @@ -15,21 +16,20 @@ const prepareTemplate = async (fileNameWithoutExtension: string): Promise { - const store = useFileSystems(); +export const createNewKsyAction = async ({storeId, path}: FileSystemPath, fileName: string) => { const pathParts = sanitizePath(path); const fileNameWithoutExtension = fileName.replace(/\..*$/mi, ""); pathParts.push(`${fileNameWithoutExtension}.ksy`); const newFilePath = pathParts.join("/"); - const template= await prepareTemplate(fileNameWithoutExtension); - await store.addFile(storeId, newFilePath, template); - store.selectPath(`${storeId}:${newFilePath}`); - store.openPath(`${storeId}:${path}`); + const template = await prepareTemplate(fileNameWithoutExtension); + const fileSystemsStore = useFileSystems(); + await fileSystemsStore.addFile(storeId, newFilePath, template); + fileSystemsStore.openPath(FileSystemPath.of(storeId, path)); const appStore = useAppStore(); - appStore.updateSelectedKsyFile({ - storeId: storeId, - filePath: newFilePath - }); + const newFileSystemPath = FileSystemPath.of(storeId, newFilePath); + fileSystemsStore.selectPath(newFileSystemPath); + appStore.updateSelectedKsyFile(newFileSystemPath); + }; \ No newline at end of file diff --git a/src/GlobalActions/KsyEditorActions.ts b/src/GlobalActions/KsyEditorActions.ts index 0101d60..d05ddd9 100644 --- a/src/GlobalActions/KsyEditorActions.ts +++ b/src/GlobalActions/KsyEditorActions.ts @@ -8,8 +8,8 @@ import {FILE_SYSTEM_TYPE_LOCAL} from "../Components/FileTree/FileSystems/LocalSt import {FILE_SYSTEM_TYPE_KAITAI} from "../Components/FileTree/FileSystems/KaitaiFileSystem"; import {editor} from "monaco-editor"; -export const mainEditorOnChange = async (change: editor.IModelContentChangedEvent, editorContent: string) => { - const contentDidNotChange = change.changes.join("\n") === editorContent; +export const mainEditorOnChange = async (changedEvent: editor.IModelContentChangedEvent, editorContent: string) => { + const contentDidNotChange = changedEvent.changes.map(change => change.text).join("\n") === editorContent; if (contentDidNotChange) return; const yamlInfo = yamlInfoWithCurrentStoreStateAndNewContent(editorContent); @@ -36,7 +36,7 @@ const yamlInfoWithCurrentStoreStateAndNewContent = (content: string): YamlFileIn return { storeId: appStore.selectedKsyInfo.storeId, - filePath: appStore.selectedKsyInfo.filePath, + filePath: appStore.selectedKsyInfo.path, fileContent: content }; }; diff --git a/src/GlobalActions/LoadBinaryFile.ts b/src/GlobalActions/LoadBinaryFile.ts index 9b39d7b..fa6cfa2 100644 --- a/src/GlobalActions/LoadBinaryFile.ts +++ b/src/GlobalActions/LoadBinaryFile.ts @@ -1,13 +1,13 @@ -import {FileLocationInfo} from "../Stores/AppStore"; import {codeExecutionWorkerApi} from "../DataManipulation/ParsingModule/ParseWorkerApi"; import {useCurrentBinaryFileStore} from "../Stores/CurrentBinaryFileStore"; import {useFileSystems} from "../Components/FileTree/Store/FileSystemsStore"; import {CurrentGoldenLayout} from "../Components/GoldenLayout/GoldenLayoutUI"; +import {FileSystemPath} from "../Components/FileTree/FileSystemsTypes"; -export const loadBinaryFileAction = async (binaryFileLocation: FileLocationInfo) => { +export const loadBinaryFileAction = async (binaryFileLocation: FileSystemPath) => { const store = useCurrentBinaryFileStore(); - const fileContent = await useFileSystems().getFile(binaryFileLocation.storeId, binaryFileLocation.filePath) as ArrayBuffer; - CurrentGoldenLayout.updateHexViewerTitle(binaryFileLocation.filePath); - store.newBinaryFileSelected(binaryFileLocation.filePath, fileContent, "SOURCE_REPARSE"); + const fileContent = await useFileSystems().getFile(binaryFileLocation.storeId, binaryFileLocation.path) as ArrayBuffer; + CurrentGoldenLayout.updateHexViewerTitle(binaryFileLocation.path); + store.newBinaryFileSelected(binaryFileLocation.path, fileContent, "SOURCE_REPARSE"); codeExecutionWorkerApi.setInputAction(fileContent); }; \ No newline at end of file diff --git a/src/GlobalActions/LoadKsyFile.ts b/src/GlobalActions/LoadKsyFile.ts index b695e6f..148eda6 100644 --- a/src/GlobalActions/LoadKsyFile.ts +++ b/src/GlobalActions/LoadKsyFile.ts @@ -1,15 +1,15 @@ -import {FileLocationInfo} from "../Stores/AppStore"; import {YamlFileInfo} from "../DataManipulation/CompilationModule/JsImporter"; import {compileInternalDebugAndRelease} from "./CompileGrammar"; import {useFileSystems} from "../Components/FileTree/Store/FileSystemsStore"; import {CurrentGoldenLayout} from "../Components/GoldenLayout/GoldenLayoutUI"; import {useCurrentBinaryFileStore} from "../Stores/CurrentBinaryFileStore"; +import {FileSystemPath} from "../Components/FileTree/FileSystemsTypes"; -export const loadKsyFileAction = async (ksyFileLocation: FileLocationInfo) => { - const content = await useFileSystems().getFile(ksyFileLocation.storeId, ksyFileLocation.filePath) as string; +export const loadKsyFileAction = async (ksyFileLocation: FileSystemPath) => { + const content = await useFileSystems().getFile(ksyFileLocation.storeId, ksyFileLocation.path) as string; const yamlInfo: YamlFileInfo = { storeId: ksyFileLocation.storeId, - filePath: ksyFileLocation.filePath, + filePath: ksyFileLocation.path, fileContent: content }; diff --git a/src/Stores/AppStore.ts b/src/Stores/AppStore.ts index 19a4244..4f81499 100644 --- a/src/Stores/AppStore.ts +++ b/src/Stores/AppStore.ts @@ -1,27 +1,15 @@ import {defineStore} from "pinia"; import {FILE_SYSTEM_TYPE_KAITAI} from "../Components/FileTree/FileSystems/KaitaiFileSystem"; - -export interface FileLocationInfo { - storeId: string; - filePath: string; -} +import {FileSystemPath} from "../Components/FileTree/FileSystemsTypes"; export interface AppStore { - selectedKsyInfo: FileLocationInfo; - selectedBinaryInfo: FileLocationInfo; + selectedKsyInfo: FileSystemPath; + selectedBinaryInfo: FileSystemPath; } -const defaultKsyInfo: FileLocationInfo = { - storeId: FILE_SYSTEM_TYPE_KAITAI, - filePath: "formats/archive/zip.ksy" -}; - - -const defaultBinaryInfo: FileLocationInfo = { - storeId: FILE_SYSTEM_TYPE_KAITAI, - filePath: "samples/sample1.zip" -}; +const defaultKsyInfo: FileSystemPath = FileSystemPath.of(FILE_SYSTEM_TYPE_KAITAI, "formats/archive/zip.ksy"); +const defaultBinaryInfo: FileSystemPath = FileSystemPath.of(FILE_SYSTEM_TYPE_KAITAI, "samples/sample1.zip"); export const useAppStore = defineStore("AppStore", { @@ -32,11 +20,11 @@ export const useAppStore = defineStore("AppStore", { }; }, actions: { - updateSelectedBinaryFile(info: FileLocationInfo) { + updateSelectedBinaryFile(info: FileSystemPath) { this.selectedBinaryInfo = info; }, - updateSelectedKsyFile(info: FileLocationInfo) { + updateSelectedKsyFile(info: FileSystemPath) { this.selectedKsyInfo = info; }, }