From 73f37dceb3a08a2426ca9f0b0559cccaf4587bab Mon Sep 17 00:00:00 2001 From: Liz Looney Date: Sun, 10 Aug 2025 19:29:37 -0700 Subject: [PATCH 1/7] Moved listProjects function from Storage interface to projects.ts. Added listModules and fetchModuleDataModifiedMillis functions to Storage interface. --- src/reactComponents/Menu.tsx | 2 +- src/reactComponents/ProjectManageModal.tsx | 2 +- src/storage/client_side_storage.ts | 103 ++++++--------------- src/storage/common_storage.ts | 6 +- src/storage/project.ts | 80 ++++++++++++++++ 5 files changed, 111 insertions(+), 82 deletions(-) diff --git a/src/reactComponents/Menu.tsx b/src/reactComponents/Menu.tsx index 78b4e303..1ea15f55 100644 --- a/src/reactComponents/Menu.tsx +++ b/src/reactComponents/Menu.tsx @@ -199,7 +199,7 @@ export function Component(props: MenuProps): React.JSX.Element { return; } try { - const array = await props.storage.listProjects(); + const array = await storageProject.listProjects(props.storage); setProjects(array); resolve(array); } catch (e) { diff --git a/src/reactComponents/ProjectManageModal.tsx b/src/reactComponents/ProjectManageModal.tsx index c3a4285d..4b06b6f6 100644 --- a/src/reactComponents/ProjectManageModal.tsx +++ b/src/reactComponents/ProjectManageModal.tsx @@ -72,7 +72,7 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac /** Loads projects from storage and sorts them alphabetically. */ const loadProjects = async (storage: commonStorage.Storage): Promise => { - const projects = await storage.listProjects(); + const projects = await storageProject.listProjects(storage); // Sort projects alphabetically by name projects.sort((a, b) => a.projectName.localeCompare(b.projectName)); diff --git a/src/storage/client_side_storage.ts b/src/storage/client_side_storage.ts index 0dcb83ed..b78bf691 100644 --- a/src/storage/client_side_storage.ts +++ b/src/storage/client_side_storage.ts @@ -21,6 +21,7 @@ import * as commonStorage from './common_storage'; import * as storageModule from './module'; +import * as storageModuleContent from './module_content'; import * as storageNames from './names'; import * as storageProject from './project'; @@ -127,13 +128,9 @@ class ClientSideStorage implements commonStorage.Storage { }); } - async listProjects(): Promise { + async listModules(): Promise<{[path: string]: storageModuleContent.ModuleContent}> { return new Promise((resolve, reject) => { - const projects: {[key: string]: storageProject.Project} = {}; // key is project name, value is Project - // The mechanisms and opModes variables hold any Mechanisms and OpModes that - // are read before the Project to which they belong is read. - const mechanisms: {[key: string]: storageModule.Mechanism[]} = {}; // key is project name, value is list of Mechanisms - const opModes: {[key: string]: storageModule.OpMode[]} = {}; // key is project name, value is list of OpModes + const pathToModuleContent: {[path: string]: storageModuleContent.ModuleContent} = {}; const openCursorRequest = this.db.transaction([MODULES_STORE_NAME], 'readonly') .objectStore(MODULES_STORE_NAME) .openCursor(); @@ -146,81 +143,33 @@ class ClientSideStorage implements commonStorage.Storage { const cursor = openCursorRequest.result; if (cursor) { const value = cursor.value; - const path = value.path; - const moduleType = value.type; - const module: storageModule.Module = { - modulePath: path, - moduleType: moduleType, - projectName: storageNames.getProjectName(path), - className: storageNames.getClassName(path), - dateModifiedMillis: value.dateModifiedMillis, - } - if (moduleType === storageModule.MODULE_TYPE_ROBOT) { - const robot: storageModule.Robot = { - ...module, - }; - const project: storageProject.Project = { - projectName: module.projectName, - robot: robot, - mechanisms: [], - opModes: [], - }; - projects[project.projectName] = project; - // Add any Mechanisms that belong to this project that have already - // been read. - if (project.projectName in mechanisms) { - project.mechanisms = mechanisms[project.projectName]; - delete mechanisms[project.projectName]; - } - // Add any OpModes that belong to this project that have already been - // read. - if (project.projectName in opModes) { - project.opModes = opModes[project.projectName]; - delete opModes[project.projectName]; - } - } else if (moduleType === storageModule.MODULE_TYPE_MECHANISM) { - const mechanism: storageModule.Mechanism = { - ...module, - }; - if (mechanism.projectName in projects) { - // If the Project to which this Mechanism belongs has already been read, - // add this Mechanism to it. - projects[mechanism.projectName].mechanisms.push(mechanism); - } else { - // Otherwise, add this Mechanism to the mechanisms local variable. - if (mechanism.projectName in mechanisms) { - mechanisms[mechanism.projectName].push(mechanism); - } else { - mechanisms[mechanism.projectName] = [mechanism]; - } - } - } else if (moduleType === storageModule.MODULE_TYPE_OPMODE) { - const opMode: storageModule.OpMode = { - ...module, - }; - if (opMode.projectName in projects) { - // If the Project to which this OpMode belongs has already been read, - // add this OpMode to it. - projects[opMode.projectName].opModes.push(opMode); - } else { - // Otherwise, add this OpMode to the opModes local variable. - if (opMode.projectName in opModes) { - opModes[opMode.projectName].push(opMode); - } else { - opModes[opMode.projectName] = [opMode]; - } - } - } + const moduleContent = storageModuleContent.parseModuleContentText(value.content); + pathToModuleContent[value.path] = moduleContent; cursor.continue(); } else { // The cursor is done. We have finished reading all the modules. - const projectsToReturn: storageProject.Project[] = []; - const sortedProjectNames = Object.keys(projects).sort(); - sortedProjectNames.forEach((projectName) => { - projectsToReturn.push(projects[projectName]); - }); - resolve(projectsToReturn); + resolve(pathToModuleContent); + } + }; + }); + } + + async fetchModuleDateModifiedMillis(modulePath: string): Promise { + return new Promise((resolve, reject) => { + const getRequest = this.db.transaction([MODULES_STORE_NAME], 'readonly') + .objectStore(MODULES_STORE_NAME).get(modulePath); + getRequest.onerror = () => { + console.log('IndexedDB get request failed. getRequest.error is...'); + console.log(getRequest.error); + reject(new Error('IndexedDB get request failed.')); + }; + getRequest.onsuccess = () => { + if (getRequest.result === undefined) { + // Module does not exist. + reject(new Error('IndexedDB get request succeeded, but the module does not exist.')); + return; } + resolve(getRequest.result.dateModifiedMillis); }; }); } diff --git a/src/storage/common_storage.ts b/src/storage/common_storage.ts index 649124ca..3bd27c3c 100644 --- a/src/storage/common_storage.ts +++ b/src/storage/common_storage.ts @@ -19,12 +19,13 @@ * @author lizlooney@google.com (Liz Looney) */ -import * as storageProject from './project'; +import * as storageModuleContent from './module_content'; export interface Storage { saveEntry(entryKey: string, entryValue: string): Promise; fetchEntry(entryKey: string, defaultValue: string): Promise; - listProjects(): Promise; + listModules(): Promise<{[path: string]: storageModuleContent.ModuleContent}>; + fetchModuleDateModifiedMillis(modulePath: string): Promise; fetchModuleContentText(modulePath: string): Promise; createProject(projectName: string, robotContent: string, opmodeContent: string): Promise; createModule(moduleType: string, modulePath: string, moduleContentText: string): Promise; @@ -38,4 +39,3 @@ export interface Storage { downloadProject(projectName: string): Promise; uploadProject(projectName: string, blobUrl: string): Promise; } - diff --git a/src/storage/project.ts b/src/storage/project.ts index 999996b2..2b4c7727 100644 --- a/src/storage/project.ts +++ b/src/storage/project.ts @@ -35,6 +35,86 @@ export type Project = { opModes: storageModule.OpMode[], }; +export async function listProjects(storage: commonStorage.Storage): Promise { + const pathToModuleContent = await storage.listModules(); + + const projects: {[key: string]: Project} = {}; // key is project name, value is Project + // The mechanisms and opModes variables hold any Mechanisms and OpModes that + // are read before the Project to which they belong is read. + const mechanisms: {[key: string]: storageModule.Mechanism[]} = {}; // key is project name, value is list of Mechanisms + const opModes: {[key: string]: storageModule.OpMode[]} = {}; // key is project name, value is list of OpModes + + for (const modulePath in pathToModuleContent) { + const moduleContent = pathToModuleContent[modulePath]; + const moduleType = moduleContent.getModuleType(); + const dateModifiedMillis = await storage.fetchModuleDateModifiedMillis(modulePath); + const module: storageModule.Module = { + modulePath: modulePath, + moduleType: moduleType, + projectName: storageNames.getProjectName(modulePath), + className: storageNames.getClassName(modulePath), + dateModifiedMillis: dateModifiedMillis, + }; + if (moduleType === storageModule.MODULE_TYPE_ROBOT) { + const robot: storageModule.Robot = module as storageModule.Robot; + const project: Project = { + projectName: module.projectName, + robot: robot, + mechanisms: [], + opModes: [], + }; + projects[project.projectName] = project; + // Add any Mechanisms that belong to this project that have already + // been read. + if (project.projectName in mechanisms) { + project.mechanisms = mechanisms[project.projectName]; + delete mechanisms[project.projectName]; + } + // Add any OpModes that belong to this project that have already been + // read. + if (project.projectName in opModes) { + project.opModes = opModes[project.projectName]; + delete opModes[project.projectName]; + } + } else if (moduleType === storageModule.MODULE_TYPE_MECHANISM) { + const mechanism: storageModule.Mechanism = module as storageModule.Mechanism; + if (mechanism.projectName in projects) { + // If the Project to which this Mechanism belongs has already been read, + // add this Mechanism to it. + projects[mechanism.projectName].mechanisms.push(mechanism); + } else { + // Otherwise, add this Mechanism to the mechanisms local variable. + if (mechanism.projectName in mechanisms) { + mechanisms[mechanism.projectName].push(mechanism); + } else { + mechanisms[mechanism.projectName] = [mechanism]; + } + } + } else if (moduleType === storageModule.MODULE_TYPE_OPMODE) { + const opMode: storageModule.OpMode = module as storageModule.OpMode; + if (opMode.projectName in projects) { + // If the Project to which this OpMode belongs has already been read, + // add this OpMode to it. + projects[opMode.projectName].opModes.push(opMode); + } else { + // Otherwise, add this OpMode to the opModes local variable. + if (opMode.projectName in opModes) { + opModes[opMode.projectName].push(opMode); + } else { + opModes[opMode.projectName] = [opMode]; + } + } + } + } + + const projectsList: Project[] = []; + const sortedProjectNames = Object.keys(projects).sort(); + sortedProjectNames.forEach((projectName) => { + projectsList.push(projects[projectName]); + }); + return projectsList; +} + /** * Creates a new project. * @param storage The storage interface to use for creating the project. From 7570bee046dc1d1338a6145a1de032b175757eda Mon Sep 17 00:00:00 2001 From: Liz Looney Date: Sun, 10 Aug 2025 19:36:04 -0700 Subject: [PATCH 2/7] Removed createProject and createModule functions from Storage interface. --- src/storage/client_side_storage.ts | 37 ++---------------------------- src/storage/common_storage.ts | 2 -- src/storage/project.ts | 12 ++++++---- 3 files changed, 10 insertions(+), 41 deletions(-) diff --git a/src/storage/client_side_storage.ts b/src/storage/client_side_storage.ts index b78bf691..d02ab04a 100644 --- a/src/storage/client_side_storage.ts +++ b/src/storage/client_side_storage.ts @@ -144,6 +144,7 @@ class ClientSideStorage implements commonStorage.Storage { if (cursor) { const value = cursor.value; const moduleContent = storageModuleContent.parseModuleContentText(value.content); + // TODO(lizlooney): do we need value.path? Is there another way to get the path? pathToModuleContent[value.path] = moduleContent; cursor.continue(); } else { @@ -194,26 +195,7 @@ class ClientSideStorage implements commonStorage.Storage { }); } - async createProject(projectName: string, robotContent: string, opmodeContent : string): Promise { - const modulePath = storageNames.makeRobotPath(projectName); - const opmodePath = storageNames.makeModulePath(projectName, storageNames.CLASS_NAME_TELEOP); - - await this._saveModule(storageModule.MODULE_TYPE_ROBOT, modulePath, robotContent); - await this._saveModule(storageModule.MODULE_TYPE_OPMODE, opmodePath, opmodeContent); - } - - async createModule(moduleType: string, modulePath: string, moduleContentText: string): Promise { - return this._saveModule(moduleType, modulePath, moduleContentText); - } - async saveModule(modulePath: string, moduleContentText: string): Promise { - return this._saveModule('', modulePath, moduleContentText); - } - - private async _saveModule(moduleType: string, modulePath: string, moduleContentText: string) - : Promise { - // When creating a new module, moduleType must be truthy. - // When saving an existing module, the moduleType must be falsy. return new Promise((resolve, reject) => { const transaction = this.db.transaction([MODULES_STORE_NAME], 'readwrite'); transaction.oncomplete = () => { @@ -233,26 +215,11 @@ class ClientSideStorage implements commonStorage.Storage { getRequest.onsuccess = () => { let value; if (getRequest.result === undefined) { - // The module does not exist. - // Let's make sure that's what we expected. - if (!moduleType) { - // If moduleType is not truthy, we are trying to save an existing module. - // It is unexpected that the module does not exist. - console.log('IndexedDB get request succeeded, but the module does not exist.'); - throw new Error('IndexedDB get request succeeded, but the module does not exist.'); - } + // The module does not exist. Create it now. value = Object.create(null); value.path = modulePath; - value.type = moduleType; } else { // The module already exists. - // Let's make sure if that's what we expected. - if (moduleType) { - // Since moduleType is truthy, we are trying to create a new module. - // It is unexpected that the module already exists. - console.log('IndexedDB get request succeeded, but the module already exist.'); - throw new Error('IndexedDB get request succeeded, but the module already exists.'); - } value = getRequest.result; } value.content = moduleContentText; diff --git a/src/storage/common_storage.ts b/src/storage/common_storage.ts index 3bd27c3c..b3acdefe 100644 --- a/src/storage/common_storage.ts +++ b/src/storage/common_storage.ts @@ -27,8 +27,6 @@ export interface Storage { listModules(): Promise<{[path: string]: storageModuleContent.ModuleContent}>; fetchModuleDateModifiedMillis(modulePath: string): Promise; fetchModuleContentText(modulePath: string): Promise; - createProject(projectName: string, robotContent: string, opmodeContent: string): Promise; - createModule(moduleType: string, modulePath: string, moduleContentText: string): Promise; saveModule(modulePath: string, moduleContentText: string): Promise; renameProject(oldProjectName: string, newProjectName: string): Promise; copyProject(oldProjectName: string, newProjectName: string): Promise; diff --git a/src/storage/project.ts b/src/storage/project.ts index 2b4c7727..473d9730 100644 --- a/src/storage/project.ts +++ b/src/storage/project.ts @@ -122,10 +122,14 @@ export async function listProjects(storage: commonStorage.Storage): Promise { + storage: commonStorage.Storage, newProjectName: string): Promise { + const modulePath = storageNames.makeRobotPath(newProjectName); const robotContent = storageModuleContent.newRobotContent(newProjectName); + await storage.saveModule(modulePath, robotContent); + + const opmodePath = storageNames.makeModulePath(newProjectName, storageNames.CLASS_NAME_TELEOP); const opmodeContent = storageModuleContent.newOpModeContent(newProjectName, storageNames.CLASS_NAME_TELEOP); - await storage.createProject(newProjectName, robotContent, opmodeContent); + await storage.saveModule(opmodePath, opmodeContent); } /** @@ -176,7 +180,7 @@ export async function addModuleToProject( if (moduleType === storageModule.MODULE_TYPE_MECHANISM) { const mechanismContent = storageModuleContent.newMechanismContent(project.projectName, newClassName); - await storage.createModule(storageModule.MODULE_TYPE_MECHANISM, newModulePath, mechanismContent); + await storage.saveModule(newModulePath, mechanismContent); project.mechanisms.push({ modulePath: newModulePath, moduleType: storageModule.MODULE_TYPE_MECHANISM, @@ -185,7 +189,7 @@ export async function addModuleToProject( } as storageModule.Mechanism); } else if (moduleType === storageModule.MODULE_TYPE_OPMODE) { const opModeContent = storageModuleContent.newOpModeContent(project.projectName, newClassName); - await storage.createModule(storageModule.MODULE_TYPE_OPMODE, newModulePath, opModeContent); + await storage.saveModule(newModulePath, opModeContent); project.opModes.push({ modulePath: newModulePath, moduleType: storageModule.MODULE_TYPE_OPMODE, From 8af4cb539a7699121cef595bd05c2224bbc845c1 Mon Sep 17 00:00:00 2001 From: Liz Looney Date: Sun, 10 Aug 2025 20:40:38 -0700 Subject: [PATCH 3/7] In common_storage, modified Storage interface: Added modulePathFilter parameter to listModules. Removed renameProject and copyProject. Removed moduleType parameter from deleteModule. In client_side_storage: Updated implementation of Storage interface (see above). Removed _renameOrCopyProject. In names: Added makeModulePathPrefix. In project: Updated renameProject and copyProject functions. Added renameOrCopyProject function. --- src/storage/client_side_storage.ts | 101 +++-------------------------- src/storage/common_storage.ts | 8 +-- src/storage/names.ts | 9 ++- src/storage/project.ts | 28 ++++++-- 4 files changed, 44 insertions(+), 102 deletions(-) diff --git a/src/storage/client_side_storage.ts b/src/storage/client_side_storage.ts index d02ab04a..ef73da07 100644 --- a/src/storage/client_side_storage.ts +++ b/src/storage/client_side_storage.ts @@ -128,7 +128,9 @@ class ClientSideStorage implements commonStorage.Storage { }); } - async listModules(): Promise<{[path: string]: storageModuleContent.ModuleContent}> { + async listModules( + opt_modulePathFilter?: commonStorage.ModulePathFilter): + Promise<{[path: string]: storageModuleContent.ModuleContent}> { return new Promise((resolve, reject) => { const pathToModuleContent: {[path: string]: storageModuleContent.ModuleContent} = {}; const openCursorRequest = this.db.transaction([MODULES_STORE_NAME], 'readonly') @@ -143,9 +145,12 @@ class ClientSideStorage implements commonStorage.Storage { const cursor = openCursorRequest.result; if (cursor) { const value = cursor.value; - const moduleContent = storageModuleContent.parseModuleContentText(value.content); // TODO(lizlooney): do we need value.path? Is there another way to get the path? - pathToModuleContent[value.path] = moduleContent; + const modulePath = value.path; + if (!opt_modulePathFilter || opt_modulePathFilter(modulePath)) { + const moduleContent = storageModuleContent.parseModuleContentText(value.content); + pathToModuleContent[modulePath] = moduleContent; + } cursor.continue(); } else { // The cursor is done. We have finished reading all the modules. @@ -234,90 +239,6 @@ class ClientSideStorage implements commonStorage.Storage { }); } - private async _renameOrCopyProject(oldProjectName: string, newProjectName: string, copy: boolean): Promise { - return new Promise((resolve, reject) => { - const transaction = this.db.transaction([MODULES_STORE_NAME], 'readwrite'); - transaction.oncomplete = () => { - resolve(); - }; - transaction.onabort = () => { - console.log('IndexedDB transaction aborted.'); - reject(new Error('IndexedDB transaction aborted.')); - }; - const modulesObjectStore = transaction.objectStore(MODULES_STORE_NAME); - // First get the list of modules in the project. - const oldToNewModulePaths: {[key: string]: string} = {}; - const openCursorRequest = modulesObjectStore.openCursor(); - openCursorRequest.onerror = () => { - console.log('IndexedDB openCursor request failed. openCursorRequest.error is...'); - console.log(openCursorRequest.error); - throw new Error('IndexedDB openCursor request failed.'); - }; - openCursorRequest.onsuccess = () => { - const cursor = openCursorRequest.result; - if (cursor) { - const value = cursor.value; - const path = value.path; - const moduleType = value.type; - if (storageNames.getProjectName(path) === oldProjectName) { - let newPath; - if (moduleType === storageModule.MODULE_TYPE_ROBOT) { - newPath = storageNames.makeRobotPath(newProjectName); - } else { - const className = storageNames.getClassName(path); - newPath = storageNames.makeModulePath(newProjectName, className); - } - oldToNewModulePaths[path] = newPath; - } - cursor.continue(); - } else { - // Now rename the project for each of the modules. - Object.entries(oldToNewModulePaths).forEach(([oldModulePath, newModulePath]) => { - const getRequest = modulesObjectStore.get(oldModulePath); - getRequest.onerror = () => { - console.log('IndexedDB get request failed. getRequest.error is...'); - console.log(getRequest.error); - throw new Error('IndexedDB get request failed.'); - }; - getRequest.onsuccess = () => { - if (getRequest.result === undefined) { - console.log('IndexedDB get request succeeded, but the module does not exist.'); - throw new Error('IndexedDB get request succeeded, but the module does not exist.'); - } - const value = getRequest.result; - value.path = newModulePath; - value.dateModifiedMillis = Date.now(); - const putRequest = modulesObjectStore.put(value); - putRequest.onerror = () => { - console.log('IndexedDB put request failed. putRequest.error is...'); - console.log(putRequest.error); - throw new Error('IndexedDB put request failed.'); - }; - putRequest.onsuccess = () => { - if (!copy) { - const deleteRequest = modulesObjectStore.delete(oldModulePath); - deleteRequest.onerror = () => { - console.log('IndexedDB delete request failed. deleteRequest.error is...'); - console.log(deleteRequest.error); - throw new Error('IndexedDB delete request failed.'); - }; - } - }; - }; - }); - } - }; - }); - } - - async renameProject(oldProjectName: string, newProjectName: string): Promise { - return this._renameOrCopyProject(oldProjectName, newProjectName, false); - } - - async copyProject(oldProjectName: string, newProjectName: string): Promise { - return this._renameOrCopyProject(oldProjectName, newProjectName, true); - } - async renameModule( moduleType: string, projectName: string, oldClassName: string, newClassName: string): Promise { @@ -436,11 +357,7 @@ class ClientSideStorage implements commonStorage.Storage { }); } - async deleteModule(moduleType: string, modulePath: string): Promise { - if (moduleType == storageModule.MODULE_TYPE_ROBOT) { - throw new Error('Deleting the robot module is not allowed. Call deleteProject to delete the project.'); - } - + async deleteModule(modulePath: string): Promise { return new Promise((resolve, reject) => { const transaction = this.db.transaction([MODULES_STORE_NAME], 'readwrite'); transaction.oncomplete = () => { diff --git a/src/storage/common_storage.ts b/src/storage/common_storage.ts index b3acdefe..aa293463 100644 --- a/src/storage/common_storage.ts +++ b/src/storage/common_storage.ts @@ -21,19 +21,19 @@ import * as storageModuleContent from './module_content'; +export type ModulePathFilter = (modulePath: string) => boolean; + export interface Storage { saveEntry(entryKey: string, entryValue: string): Promise; fetchEntry(entryKey: string, defaultValue: string): Promise; - listModules(): Promise<{[path: string]: storageModuleContent.ModuleContent}>; + listModules(opt_modulePathFilter?: ModulePathFilter): Promise<{[path: string]: storageModuleContent.ModuleContent}>; fetchModuleDateModifiedMillis(modulePath: string): Promise; fetchModuleContentText(modulePath: string): Promise; saveModule(modulePath: string, moduleContentText: string): Promise; - renameProject(oldProjectName: string, newProjectName: string): Promise; - copyProject(oldProjectName: string, newProjectName: string): Promise; renameModule(moduleType: string, projectName: string, oldClassName: string, newClassName: string): Promise; copyModule(moduleType: string, projectName: string, oldClassName: string, newClassName: string): Promise; deleteProject(projectName: string): Promise; - deleteModule(moduleType: string, modulePath: string): Promise; + deleteModule(modulePath: string): Promise; downloadProject(projectName: string): Promise; uploadProject(projectName: string, blobUrl: string): Promise; } diff --git a/src/storage/names.ts b/src/storage/names.ts index f15ff361..29ebcfa5 100644 --- a/src/storage/names.ts +++ b/src/storage/names.ts @@ -75,11 +75,18 @@ export function snakeCaseToPascalCase(snakeCaseName: string): string { return pascalCaseName; } +/** + * Returns the module path prefix for the given project name. + */ +export function makeModulePathPrefix(projectName: string): string { + return projectName + '/'; +} + /** * Returns the module path for the given project name and class name. */ export function makeModulePath(projectName: string, className: string): string { - return projectName + '/' + className + JSON_FILE_EXTENSION;; + return projectName + '/' + className + JSON_FILE_EXTENSION; } /** diff --git a/src/storage/project.ts b/src/storage/project.ts index 473d9730..439b3f54 100644 --- a/src/storage/project.ts +++ b/src/storage/project.ts @@ -140,8 +140,8 @@ export async function createProject( * @returns A promise that resolves when the project has been renamed. */ export async function renameProject( - storage: commonStorage.Storage, project: Project, newProjectName: string): Promise { - await storage.renameProject(project.projectName, newProjectName); + storage: commonStorage.Storage, project: Project, newProjectName: string): Promise { + await renameOrCopyProject(storage, project, newProjectName, true); } /** @@ -152,8 +152,26 @@ export async function renameProject( * @returns A promise that resolves when the project has been copied. */ export async function copyProject( - storage: commonStorage.Storage, project: Project, newProjectName: string): Promise { - await storage.copyProject(project.projectName, newProjectName); + storage: commonStorage.Storage, project: Project, newProjectName: string): Promise { + await renameOrCopyProject(storage, project, newProjectName, false); +} + +async function renameOrCopyProject( + storage: commonStorage.Storage, project: Project, newProjectName: string, + rename: boolean): Promise { + const modulePathPrefix = storageNames.makeModulePathPrefix(project.projectName); + const pathToModuleContent = await storage.listModules( + (modulePath: string) => modulePath.startsWith(modulePathPrefix)); + + for (const modulePath in pathToModuleContent) { + const className = storageNames.getClassName(modulePath); + const newModulePath = storageNames.makeModulePath(newProjectName, className); + const moduleContentText = pathToModuleContent[modulePath].getModuleContentText(); + storage.saveModule(newModulePath, moduleContentText); + if (rename) { + storage.deleteModule(modulePath); + } + } } /** @@ -211,7 +229,7 @@ export async function removeModuleFromProject( if (module.moduleType == storageModule.MODULE_TYPE_ROBOT) { throw new Error('Removing the robot module from the project is not allowed.'); } - await storage.deleteModule(module.moduleType, modulePath); + await storage.deleteModule(modulePath); if (module.moduleType === storageModule.MODULE_TYPE_MECHANISM) { project.mechanisms = project.mechanisms.filter(m => m.modulePath !== modulePath); } else if (module.moduleType === storageModule.MODULE_TYPE_OPMODE) { From f4d8e72495b78b92eeb2dad4763abdbb2692ff72 Mon Sep 17 00:00:00 2001 From: Liz Looney Date: Sun, 10 Aug 2025 21:43:39 -0700 Subject: [PATCH 4/7] Removed renameModule and copyModule from Storage interface. Removed renameModule, copyModule, and _renameOrCopyModule from client_side_storage In project.ts, updated implementation of renameModuleInProject and copyModuleInProject. Added renameOrCopyModule function. --- src/storage/client_side_storage.ts | 74 --------------------- src/storage/common_storage.ts | 2 - src/storage/project.ts | 101 ++++++++++++++++------------- 3 files changed, 57 insertions(+), 120 deletions(-) diff --git a/src/storage/client_side_storage.ts b/src/storage/client_side_storage.ts index ef73da07..1ee9a231 100644 --- a/src/storage/client_side_storage.ts +++ b/src/storage/client_side_storage.ts @@ -20,7 +20,6 @@ */ import * as commonStorage from './common_storage'; -import * as storageModule from './module'; import * as storageModuleContent from './module_content'; import * as storageNames from './names'; import * as storageProject from './project'; @@ -239,79 +238,6 @@ class ClientSideStorage implements commonStorage.Storage { }); } - async renameModule( - moduleType: string, projectName: string, - oldClassName: string, newClassName: string): Promise { - if (moduleType == storageModule.MODULE_TYPE_ROBOT) { - throw new Error('Renaming the robot module is not allowed. Call renameProject to rename the project.'); - } - return this._renameOrCopyModule( - projectName, oldClassName, newClassName, false); - } - - async copyModule( - moduleType: string, projectName: string, - oldClassName: string, newClassName: string): Promise { - if (moduleType == storageModule.MODULE_TYPE_ROBOT) { - throw new Error('Copying the robot module is not allowed. Call copyProject to rename the project.'); - } - return this._renameOrCopyModule( - projectName, oldClassName, newClassName, true); - } - - private async _renameOrCopyModule( - projectName: string, - oldClassName: string, newClassName: string, copy: boolean): Promise { - - return new Promise((resolve, reject) => { - const transaction = this.db.transaction([MODULES_STORE_NAME], 'readwrite'); - transaction.oncomplete = () => { - resolve(); - }; - transaction.onabort = () => { - console.log('IndexedDB transaction aborted.'); - reject(new Error('IndexedDB transaction aborted.')); - }; - const modulesObjectStore = transaction.objectStore(MODULES_STORE_NAME); - const oldModulePath = storageNames.makeModulePath(projectName, oldClassName); - const newModulePath = storageNames.makeModulePath(projectName, newClassName); - const getRequest = modulesObjectStore.get(oldModulePath); - getRequest.onerror = () => { - console.log('IndexedDB get request failed. getRequest.error is...'); - console.log(getRequest.error); - throw new Error('IndexedDB get request failed.'); - }; - getRequest.onsuccess = () => { - if (getRequest.result === undefined) { - console.log('IndexedDB get request succeeded, but the module does not exist.'); - throw new Error('IndexedDB get request succeeded, but the module does not exist.'); - return; - } - const value = getRequest.result; - value.path = newModulePath; - value.dateModifiedMillis = Date.now(); - const putRequest = modulesObjectStore.put(value); - putRequest.onerror = () => { - console.log('IndexedDB put request failed. putRequest.error is...'); - console.log(putRequest.error); - throw new Error('IndexedDB put request failed.'); - }; - putRequest.onsuccess = () => { - if (!copy) { - const deleteRequest = modulesObjectStore.delete(oldModulePath); - deleteRequest.onerror = () => { - console.log('IndexedDB delete request failed. deleteRequest.error is...'); - console.log(deleteRequest.error); - throw new Error('IndexedDB delete request failed.'); - }; - deleteRequest.onsuccess = () => { - }; - } - }; - }; - }); - } - async deleteProject(projectName: string): Promise { return new Promise((resolve, reject) => { const transaction = this.db.transaction([MODULES_STORE_NAME], 'readwrite'); diff --git a/src/storage/common_storage.ts b/src/storage/common_storage.ts index aa293463..ad026552 100644 --- a/src/storage/common_storage.ts +++ b/src/storage/common_storage.ts @@ -30,8 +30,6 @@ export interface Storage { fetchModuleDateModifiedMillis(modulePath: string): Promise; fetchModuleContentText(modulePath: string): Promise; saveModule(modulePath: string, moduleContentText: string): Promise; - renameModule(moduleType: string, projectName: string, oldClassName: string, newClassName: string): Promise; - copyModule(moduleType: string, projectName: string, oldClassName: string, newClassName: string): Promise; deleteProject(projectName: string): Promise; deleteModule(modulePath: string): Promise; downloadProject(projectName: string): Promise; diff --git a/src/storage/project.ts b/src/storage/project.ts index 439b3f54..318df65c 100644 --- a/src/storage/project.ts +++ b/src/storage/project.ts @@ -167,9 +167,9 @@ async function renameOrCopyProject( const className = storageNames.getClassName(modulePath); const newModulePath = storageNames.makeModulePath(newProjectName, className); const moduleContentText = pathToModuleContent[modulePath].getModuleContentText(); - storage.saveModule(newModulePath, moduleContentText); + await storage.saveModule(newModulePath, moduleContentText); if (rename) { - storage.deleteModule(modulePath); + await storage.deleteModule(modulePath); } } } @@ -244,67 +244,80 @@ export async function removeModuleFromProject( * @param project The project containing the module to rename. * @param newClassName The new name for the module. For example, GamePieceShooter. * @param oldModulePath The current path of the module. - * @returns A promise that resolves when the module has been renamed. + * @returns The new path of the module, as a promise that resolves when the module has been copied. */ export async function renameModuleInProject( - storage: commonStorage.Storage, project: Project, newClassName: string, oldModulePath: string): Promise { + storage: commonStorage.Storage, project: Project, newClassName: string, oldModulePath: string): Promise { const module = findModuleByModulePath(project, oldModulePath); - if (module) { - if (module.moduleType == storageModule.MODULE_TYPE_ROBOT) { - throw new Error('Renaming the robot module is not allowed.'); - } - const newModulePath = storageNames.makeModulePath(project.projectName, newClassName); - await storage.renameModule(module.moduleType, project.projectName, module.className, newClassName); - module.modulePath = newModulePath; - module.className = newClassName; - module.className = newClassName; - - if (module.moduleType === storageModule.MODULE_TYPE_MECHANISM) { - const mechanism = project.mechanisms.find(m => m.modulePath === module.modulePath); - if (mechanism) { - mechanism.modulePath = newModulePath; - mechanism.className = newClassName; - mechanism.className = newClassName; - } - return newModulePath; - } else if (module.moduleType === storageModule.MODULE_TYPE_OPMODE) { - const opMode = project.opModes.find(o => o.modulePath === module.modulePath); - if (opMode) { - opMode.modulePath = newModulePath; - opMode.className = newClassName; - opMode.className = newClassName; - } - return newModulePath - } + if (!module) { + throw new Error('Failed to find module with path ' + oldModulePath); + } + if (module.moduleType == storageModule.MODULE_TYPE_ROBOT) { + throw new Error('Renaming the robot module is not allowed.'); } - return ''; + return await renameOrCopyModule(storage, project, newClassName, module, true); } + /** * Copies a module in the project. * @param storage The storage interface to use for copying the module. * @param project The project containing the module to copy. * @param newClassName The new name for the module. For example, GamePieceShooter. * @param oldModulePath The current path of the module. - * @returns A promise that resolves when the module has been copied. + * @returns The new path of the module, as a promise that resolves when the module has been copied. */ export async function copyModuleInProject( - storage: commonStorage.Storage, project: Project, newClassName: string, oldModulePath: string): Promise { + storage: commonStorage.Storage, project: Project, newClassName: string, oldModulePath: string): Promise { const module = findModuleByModulePath(project, oldModulePath); - if (module) { - if (module.moduleType == storageModule.MODULE_TYPE_ROBOT) { - throw new Error('Copying the robot module is not allowed.'); - } - const newModulePath = storageNames.makeModulePath(project.projectName, newClassName); - await storage.copyModule(module.moduleType, project.projectName, module.className, newClassName); + if (!module) { + throw new Error('Failed to find module with path ' + oldModulePath); + } + if (module.moduleType == storageModule.MODULE_TYPE_ROBOT) { + throw new Error('Copying the robot module is not allowed.'); + } + return await renameOrCopyModule(storage, project, newClassName, module, false); +} - if (module.moduleType === storageModule.MODULE_TYPE_MECHANISM) { +async function renameOrCopyModule( + storage: commonStorage.Storage, project: Project, newClassName: string, + oldModule: storageModule.Module, rename: boolean): Promise { + const pathToModuleContent = await storage.listModules( + (modulePath: string) => modulePath === oldModule.modulePath); + if (! (oldModule.modulePath in pathToModuleContent)) { + throw new Error('Failed to find module with path ' + oldModule.modulePath); + } + + const newModulePath = storageNames.makeModulePath(project.projectName, newClassName); + const moduleContentText = pathToModuleContent[oldModule.modulePath].getModuleContentText(); + await storage.saveModule(newModulePath, moduleContentText); + if (rename) { + // For rename, delete the old module. + await storage.deleteModule(oldModule.modulePath); + + // Update the project's mechanisms or opModes. + if (oldModule.moduleType === storageModule.MODULE_TYPE_MECHANISM) { + const mechanism = project.mechanisms.find(m => m.modulePath === oldModule.modulePath); + if (mechanism) { + mechanism.modulePath = newModulePath; + mechanism.className = newClassName; + } + } else if (oldModule.moduleType === storageModule.MODULE_TYPE_OPMODE) { + const opMode = project.opModes.find(o => o.modulePath === oldModule.modulePath); + if (opMode) { + opMode.modulePath = newModulePath; + opMode.className = newClassName; + } + } + } else { + // Update the project's mechanisms or opModes. + if (oldModule.moduleType === storageModule.MODULE_TYPE_MECHANISM) { project.mechanisms.push({ modulePath: newModulePath, moduleType: storageModule.MODULE_TYPE_MECHANISM, projectName: project.projectName, className: newClassName } as storageModule.Mechanism); - } else if (module.moduleType === storageModule.MODULE_TYPE_OPMODE) { + } else if (oldModule.moduleType === storageModule.MODULE_TYPE_OPMODE) { project.opModes.push({ modulePath: newModulePath, moduleType: storageModule.MODULE_TYPE_OPMODE, @@ -312,9 +325,9 @@ export async function copyModuleInProject( className: newClassName } as storageModule.OpMode); } - return newModulePath; } - return ''; + + return newModulePath; } /** From 0284cdd1fbc62932f2d93ac09e0eb53f55ae7720 Mon Sep 17 00:00:00 2001 From: Liz Looney Date: Sun, 10 Aug 2025 22:40:28 -0700 Subject: [PATCH 5/7] Removed deleteProject from Storage interface. Removed deleteProject from client_side_storage. In project.ts, updated implementation of deleteProject. --- src/storage/client_side_storage.ts | 45 ------------------------------ src/storage/common_storage.ts | 1 - src/storage/project.ts | 9 ++++-- 3 files changed, 7 insertions(+), 48 deletions(-) diff --git a/src/storage/client_side_storage.ts b/src/storage/client_side_storage.ts index 1ee9a231..94d90a72 100644 --- a/src/storage/client_side_storage.ts +++ b/src/storage/client_side_storage.ts @@ -238,51 +238,6 @@ class ClientSideStorage implements commonStorage.Storage { }); } - async deleteProject(projectName: string): Promise { - return new Promise((resolve, reject) => { - const transaction = this.db.transaction([MODULES_STORE_NAME], 'readwrite'); - transaction.oncomplete = () => { - resolve(); - }; - transaction.onabort = () => { - console.log('IndexedDB transaction aborted.'); - reject(new Error('IndexedDB transaction aborted.')); - }; - const modulesObjectStore = transaction.objectStore(MODULES_STORE_NAME); - // First get the list of modulePaths in the project. - const modulePaths: string[] = []; - const openCursorRequest = modulesObjectStore.openCursor(); - openCursorRequest.onerror = () => { - console.log('IndexedDB openCursor request failed. openCursorRequest.error is...'); - console.log(openCursorRequest.error); - throw new Error('IndexedDB openCursor request failed.'); - }; - openCursorRequest.onsuccess = () => { - const cursor = openCursorRequest.result; - if (cursor) { - const value = cursor.value; - const path = value.path; - if (storageNames.getProjectName(path) === projectName) { - modulePaths.push(path); - } - cursor.continue(); - } else { - // Now delete each of the modules. - modulePaths.forEach((modulePath) => { - const deleteRequest = modulesObjectStore.delete(modulePath); - deleteRequest.onerror = () => { - console.log('IndexedDB delete request failed. deleteRequest.error is...'); - console.log(deleteRequest.error); - throw new Error('IndexedDB delete request failed.'); - }; - deleteRequest.onsuccess = () => { - }; - }); - } - }; - }); - } - async deleteModule(modulePath: string): Promise { return new Promise((resolve, reject) => { const transaction = this.db.transaction([MODULES_STORE_NAME], 'readwrite'); diff --git a/src/storage/common_storage.ts b/src/storage/common_storage.ts index ad026552..e026a51a 100644 --- a/src/storage/common_storage.ts +++ b/src/storage/common_storage.ts @@ -30,7 +30,6 @@ export interface Storage { fetchModuleDateModifiedMillis(modulePath: string): Promise; fetchModuleContentText(modulePath: string): Promise; saveModule(modulePath: string, moduleContentText: string): Promise; - deleteProject(projectName: string): Promise; deleteModule(modulePath: string): Promise; downloadProject(projectName: string): Promise; uploadProject(projectName: string, blobUrl: string): Promise; diff --git a/src/storage/project.ts b/src/storage/project.ts index 318df65c..a85f4530 100644 --- a/src/storage/project.ts +++ b/src/storage/project.ts @@ -181,8 +181,13 @@ async function renameOrCopyProject( * @returns A promise that resolves when the project has been deleted. */ export async function deleteProject( - storage: commonStorage.Storage, project: Project): Promise { - await storage.deleteProject(project.projectName); + storage: commonStorage.Storage, project: Project): Promise { + const modulePathPrefix = storageNames.makeModulePathPrefix(project.projectName); + const pathToModuleContent = await storage.listModules( + (modulePath: string) => modulePath.startsWith(modulePathPrefix)); + for (const modulePath in pathToModuleContent) { + await storage.deleteModule(modulePath); + } } /** From 1236397f98d95d785ad7111efe704568a5a99e99 Mon Sep 17 00:00:00 2001 From: Liz Looney Date: Sun, 10 Aug 2025 22:52:17 -0700 Subject: [PATCH 6/7] Removed downloadProject from Storage interface. Removed downloadProject from client_side_storage. In project.ts, replaced produceDownloadProjectBlob with downloadProject. In Menu.tsx, call storageProject.downloadProject instead of storage.downloadProject. --- src/reactComponents/Menu.tsx | 2 +- src/storage/client_side_storage.ts | 31 ------------------------------ src/storage/common_storage.ts | 1 - src/storage/project.ts | 18 +++++++++++++---- 4 files changed, 15 insertions(+), 37 deletions(-) diff --git a/src/reactComponents/Menu.tsx b/src/reactComponents/Menu.tsx index 1ea15f55..c2739bac 100644 --- a/src/reactComponents/Menu.tsx +++ b/src/reactComponents/Menu.tsx @@ -336,7 +336,7 @@ export function Component(props: MenuProps): React.JSX.Element { } try { - const blobUrl = await props.storage.downloadProject(props.project.projectName); + const blobUrl = await storageProject.downloadProject(props.storage, props.project.projectName); const filename = props.project.projectName + storageNames.UPLOAD_DOWNLOAD_FILE_EXTENSION; // Create a temporary link to download the file diff --git a/src/storage/client_side_storage.ts b/src/storage/client_side_storage.ts index 94d90a72..ef42589c 100644 --- a/src/storage/client_side_storage.ts +++ b/src/storage/client_side_storage.ts @@ -260,37 +260,6 @@ class ClientSideStorage implements commonStorage.Storage { }); } - async downloadProject(projectName: string): Promise { - return new Promise((resolve, reject) => { - // Collect all the modules in the project. - const classNameToModuleContentText: {[className: string]: string} = {}; // key is class name, value is module content - const openCursorRequest = this.db.transaction([MODULES_STORE_NAME], 'readonly') - .objectStore(MODULES_STORE_NAME) - .openCursor(); - openCursorRequest.onerror = () => { - console.log('IndexedDB openCursor request failed. openCursorRequest.error is...'); - console.log(openCursorRequest.error); - reject(new Error('IndexedDB openCursor request failed.')); - }; - openCursorRequest.onsuccess = async () => { - const cursor = openCursorRequest.result; - if (cursor) { - const value = cursor.value; - if (storageNames.getProjectName(value.path) === projectName) { - const className = storageNames.getClassName(value.path); - classNameToModuleContentText[className] = value.content; - } - cursor.continue(); - } else { - // The cursor is done. We have finished collecting all the modules in the project. - // Now create the blob for download. - const blobUrl = await storageProject.produceDownloadProjectBlob(classNameToModuleContentText); - resolve(blobUrl); - } - }; - }); - } - async uploadProject(projectName: string, blobUrl: string): Promise { return new Promise(async (resolve, reject) => { // Process the uploaded blob to get the module types and contents. diff --git a/src/storage/common_storage.ts b/src/storage/common_storage.ts index e026a51a..797f2b2c 100644 --- a/src/storage/common_storage.ts +++ b/src/storage/common_storage.ts @@ -31,6 +31,5 @@ export interface Storage { fetchModuleContentText(modulePath: string): Promise; saveModule(modulePath: string, moduleContentText: string): Promise; deleteModule(modulePath: string): Promise; - downloadProject(projectName: string): Promise; uploadProject(projectName: string, blobUrl: string): Promise; } diff --git a/src/storage/project.ts b/src/storage/project.ts index a85f4530..5c364bf3 100644 --- a/src/storage/project.ts +++ b/src/storage/project.ts @@ -402,8 +402,19 @@ export function findModuleByModulePath(project: Project, modulePath: string): st /** * Produce the blob for downloading a project. */ -export async function produceDownloadProjectBlob( - classNameToModuleContentText: { [key: string]: string }): Promise { +export async function downloadProject( + storage: commonStorage.Storage, projectName: string): Promise { + const modulePathPrefix = storageNames.makeModulePathPrefix(projectName); + const pathToModuleContent = await storage.listModules( + (modulePath: string) => modulePath.startsWith(modulePathPrefix)); + + const classNameToModuleContentText: {[className: string]: string} = {}; // value is module content text + for (const modulePath in pathToModuleContent) { + const className = storageNames.getClassName(modulePath); + const moduleContentText = pathToModuleContent[modulePath].getModuleContentText(); + classNameToModuleContentText[className] = moduleContentText; + } + const zip = new JSZip(); for (const className in classNameToModuleContentText) { const moduleContentText = classNameToModuleContentText[className]; @@ -411,8 +422,7 @@ export async function produceDownloadProjectBlob( zip.file(filename, moduleContentText); } const content = await zip.generateAsync({ type: "blob" }); - const blobUrl = URL.createObjectURL(content); - return blobUrl; + return URL.createObjectURL(content); } /** From 4b9ce13360a2cf9075a2d8b8261e6c9e4d4cd58c Mon Sep 17 00:00:00 2001 From: Liz Looney Date: Sun, 10 Aug 2025 23:20:58 -0700 Subject: [PATCH 7/7] Removed uploadProject from Storage interface. Removed uploadProject from client_side_storage. In project.ts: Added uploadProject function. Modified processUploadedBlob to return just classNameToModuleContentText. Moved code from processUploadedModule to processUploadedBlob. In Menu.tsx, call storageProject.uploadProject instead of storage.uploadProject. --- src/reactComponents/Menu.tsx | 2 +- src/storage/client_side_storage.ts | 59 ------------------------------ src/storage/common_storage.ts | 1 - src/storage/project.ts | 53 ++++++++++++++------------- 4 files changed, 29 insertions(+), 86 deletions(-) diff --git a/src/reactComponents/Menu.tsx b/src/reactComponents/Menu.tsx index c2739bac..64f2a9ff 100644 --- a/src/reactComponents/Menu.tsx +++ b/src/reactComponents/Menu.tsx @@ -389,7 +389,7 @@ export function Component(props: MenuProps): React.JSX.Element { const file = options.file as RcFile; const uploadProjectName = storageProject.makeUploadProjectName(file.name, existingProjectNames); if (props.storage) { - props.storage.uploadProject(uploadProjectName, dataUrl); + storageProject.uploadProject(props.storage, uploadProjectName, dataUrl); } }; reader.onerror = (_error) => { diff --git a/src/storage/client_side_storage.ts b/src/storage/client_side_storage.ts index ef42589c..57911d43 100644 --- a/src/storage/client_side_storage.ts +++ b/src/storage/client_side_storage.ts @@ -21,8 +21,6 @@ import * as commonStorage from './common_storage'; import * as storageModuleContent from './module_content'; -import * as storageNames from './names'; -import * as storageProject from './project'; // Functions for saving blocks modules to client side storage. @@ -259,61 +257,4 @@ class ClientSideStorage implements commonStorage.Storage { }; }); } - - async uploadProject(projectName: string, blobUrl: string): Promise { - return new Promise(async (resolve, reject) => { - // Process the uploaded blob to get the module types and contents. - let classNameToModuleType: {[className: string]: string}; // key is class name, value is module type - let classNameToModuleContentText: {[className: string]: string}; // key is class name, value is module content - try { - [classNameToModuleType, classNameToModuleContentText] = await storageProject.processUploadedBlob( - blobUrl); - } catch (e) { - console.log('storageProject.processUploadedBlob failed.'); - reject(new Error('storageProject.processUploadedBlob failed.')); - return; - } - - // Save each module. - const transaction = this.db.transaction([MODULES_STORE_NAME], 'readwrite'); - transaction.oncomplete = () => { - resolve(); - }; - transaction.onabort = () => { - console.log('IndexedDB transaction aborted.'); - reject(new Error('IndexedDB transaction aborted.')); - }; - const modulesObjectStore = transaction.objectStore(MODULES_STORE_NAME); - - for (const className in classNameToModuleType) { - const moduleType = classNameToModuleType[className]; - const moduleContentText = classNameToModuleContentText[className]; - const modulePath = storageNames.makeModulePath(projectName, className); - const getRequest = modulesObjectStore.get(modulePath); - getRequest.onerror = () => { - console.log('IndexedDB get request failed. getRequest.error is...'); - console.log(getRequest.error); - throw new Error('IndexedDB get request failed.'); - }; - getRequest.onsuccess = () => { - if (getRequest.result !== undefined) { - // The module already exists. That is not expected! - console.log('IndexedDB get request succeeded, but the module already exists.'); - throw new Error('IndexedDB get request succeeded, but the module already exists.'); - } - const value = Object.create(null); - value.path = modulePath; - value.type = moduleType; - value.content = moduleContentText; - value.dateModifiedMillis = Date.now(); - const putRequest = modulesObjectStore.put(value); - putRequest.onerror = () => { - console.log('IndexedDB put request failed. putRequest.error is...'); - console.log(putRequest.error); - throw new Error('IndexedDB put request failed.'); - }; - }; - } - }); - } } diff --git a/src/storage/common_storage.ts b/src/storage/common_storage.ts index 797f2b2c..e0395242 100644 --- a/src/storage/common_storage.ts +++ b/src/storage/common_storage.ts @@ -31,5 +31,4 @@ export interface Storage { fetchModuleContentText(modulePath: string): Promise; saveModule(modulePath: string, moduleContentText: string): Promise; deleteModule(modulePath: string): Promise; - uploadProject(projectName: string, blobUrl: string): Promise; } diff --git a/src/storage/project.ts b/src/storage/project.ts index 5c364bf3..1187a19c 100644 --- a/src/storage/project.ts +++ b/src/storage/project.ts @@ -435,13 +435,23 @@ export function makeUploadProjectName( return storageNames.makeUniqueName(preferredName, existingProjectNames); } +export async function uploadProject( + storage: commonStorage.Storage, projectName: string, blobUrl: string): Promise { + // Process the uploaded blob to get the module types and contents. + const classNameToModuleContentText = await processUploadedBlob(blobUrl); + + // Save each module. + for (const className in classNameToModuleContentText) { + const moduleContentText = classNameToModuleContentText[className]; + const modulePath = storageNames.makeModulePath(projectName, className); + await storage.saveModule(modulePath, moduleContentText); + } +} + /** - * Process the uploaded blob to get the module types and contents. - * Returns a promise of classNameToModuleType and classNameToModuleContentText. + * Process the uploaded blob to get the module class names and contents. */ -export async function processUploadedBlob( - blobUrl: string) - : Promise<[{ [className: string]: string }, { [className: string]: string }]> { +async function processUploadedBlob(blobUrl: string): Promise<{ [className: string]: string }> { const prefix = 'data:application/octet-stream;base64,'; if (!blobUrl.startsWith(prefix)) { @@ -467,28 +477,21 @@ export async function processUploadedBlob( ); // Process each module's content. - const classNameToModuleType: { [className: string]: string } = {}; // key is class name, value is module type - const classNameToModuleContentText: { [className: string]: string } = {}; // key is class name, value is module content text + let foundRobot = false; + const classNameToModuleContentText: { [className: string]: string } = {}; // value is module content text for (const filename in files) { - const uploadedContent = files[filename]; - const [className, moduleType, moduleContent] = processUploadedModule( - filename, uploadedContent); - classNameToModuleType[className] = moduleType; - classNameToModuleContentText[className] = moduleContent; + const className = filename; + if (className === storageNames.CLASS_NAME_ROBOT) { + foundRobot = true; + } + // Make sure we can parse the content. + const moduleContent = storageModuleContent.parseModuleContentText(files[filename]); + classNameToModuleContentText[className] = moduleContent.getModuleContentText(); } - return [classNameToModuleType, classNameToModuleContentText]; -} + if (!foundRobot) { + throw new Error('Uploaded file did not contain a Robot.'); + } -/** - * Processes an uploaded module to get the class name, type, and content text. - */ -function processUploadedModule( - filename: string, uploadedContent: string): [string, string, string] { - - const moduleContent = storageModuleContent.parseModuleContentText(uploadedContent); - const moduleType = moduleContent.getModuleType(); - const className = filename; - const moduleContentText = moduleContent.getModuleContentText(); - return [className, moduleType, moduleContentText]; + return classNameToModuleContentText; }