From 8e0a58c7ecb64a8d252f8a341ec5e5619c1e585a Mon Sep 17 00:00:00 2001 From: Liz Looney Date: Fri, 4 Jul 2025 12:31:03 -0700 Subject: [PATCH 1/3] Separate the concepts of project (which is not a module) and a robot, which is a module. Renamed project to robot where appropriate. Renamed module to project where appropriate. --- public/locales/en/translation.json | 16 +-- src/App.tsx | 4 +- src/blocks/mrc_call_python_function.ts | 14 ++- src/blocks/mrc_get_python_variable.ts | 3 +- src/blocks/mrc_set_python_variable.ts | 3 +- src/blocks/utils/python.ts | 2 +- src/blocks/utils/python_json_types.ts | 2 +- src/editor/editor.ts | 41 +++---- src/editor/extended_python_generator.ts | 4 +- src/editor/generator_context.ts | 11 +- src/reactComponents/AddTabDialog.tsx | 2 +- src/reactComponents/FileManageModal.tsx | 2 +- src/reactComponents/Header.tsx | 2 +- src/reactComponents/Menu.tsx | 45 ++++---- src/reactComponents/ProjectManageModal.tsx | 112 +++++++++---------- src/reactComponents/ProjectNameComponent.tsx | 2 +- src/reactComponents/Tabs.tsx | 4 +- src/storage/client_side_storage.ts | 50 ++++++--- src/storage/common_storage.ts | 84 +++++++------- src/toolbox/hardware_category.ts | 2 +- src/toolbox/methods_category.ts | 4 +- src/toolbox/toolbox.ts | 2 +- 22 files changed, 219 insertions(+), 192 deletions(-) diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index b47a6209..fb911e00 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -1,14 +1,14 @@ { - "mechanism_delete": "Delete Project", - "mechanism_rename": "Rename Project", - "mechanism_copy": "Copy Project", - "opmode_delete": "Delete Project", - "opmode_rename": "Rename Project", - "opmode_copy": "Copy Project", + "mechanism_delete": "Delete Mechanism", + "mechanism_rename": "Rename Mechanism", + "mechanism_copy": "Copy Mechanism", + "opmode_delete": "Delete OpMode", + "opmode_rename": "Rename OpMode", + "opmode_copy": "Copy OpMode", "project_delete": "Delete Project", "project_rename": "Rename Project", "project_copy": "Copy Project", - "fail_list_modules": "Failed to load the list of modules.", + "fail_list_projects": "Failed to load the list of projects.", "mechanism": "Mechanism", "opmode": "OpMode", "class_rule_description": "No spaces are allowed in the name. Each word in the name should start with a capital letter.", @@ -21,4 +21,4 @@ "search": "Search..." } -} \ No newline at end of file +} diff --git a/src/App.tsx b/src/App.tsx index 1dd0cfb5..a5dd4425 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -229,7 +229,7 @@ const App: React.FC = (): React.JSX.Element => { const createTabItemsFromProject = (projectData: commonStorage.Project): Tabs.TabItem[] => { const tabs: Tabs.TabItem[] = [ { - key: projectData.modulePath, + key: projectData.robot.modulePath, title: 'Robot', type: TabType.ROBOT, }, @@ -332,7 +332,7 @@ const App: React.FC = (): React.JSX.Element => { if (project) { const tabs = createTabItemsFromProject(project); setTabItems(tabs); - setActiveTab(project.modulePath); + setActiveTab(project.robot.modulePath); } }, [project]); diff --git a/src/blocks/mrc_call_python_function.ts b/src/blocks/mrc_call_python_function.ts index 66dfa2fb..c82f6cc0 100644 --- a/src/blocks/mrc_call_python_function.ts +++ b/src/blocks/mrc_call_python_function.ts @@ -28,7 +28,6 @@ import { getClassData, getAllowedTypesForSetCheck, getOutputCheck } from './util import { FunctionData, findSuperFunctionData } from './utils/python_json_types'; import * as value from './utils/value'; import * as variable from './utils/variable'; -import { Editor } from '../editor/editor'; import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; import { createFieldDropdown } from '../fields/FieldDropdown'; import { createFieldNonEditableText } from '../fields/FieldNonEditableText'; @@ -224,9 +223,15 @@ export function addInstanceComponentBlocks( contents: toolboxItems.ContentsType[]) { const classData = getClassData(componentType); + if (!classData) { + throw new Error('Could not find classData for ' + componentType); + } const functions = classData.instanceMethods; const componentClassData = getClassData('component.Component'); + if (!componentClassData) { + throw new Error('Could not find classData for component.Component'); + } const componentFunctions = componentClassData.instanceMethods; for (const functionData of functions) { @@ -331,8 +336,7 @@ type CallPythonFunctionExtraState = { */ actualFunctionName?: string, /** - * True if this blocks refers to an exported function (for example, from a - * user's Project). + * True if this blocks refers to an exported function (for example, from the Robot). */ exportedFunction?: boolean, /** @@ -540,7 +544,7 @@ const CALL_PYTHON_FUNCTION = { case FunctionKind.INSTANCE_COMPONENT: { // TODO: We need the list of component names for this.mrcComponentClassName so we can // create a dropdown that has the appropriate component names. - const componentNames = []; + const componentNames: string[] = []; const componentName = this.getComponentName(); if (!componentNames.includes(componentName)) { componentNames.push(componentName); @@ -679,7 +683,7 @@ export const pythonFromBlock = function( : block.getFieldValue(FIELD_FUNCTION_NAME); // Generate the correct code depending on the module type. switch (generator.getModuleType()) { - case commonStorage.MODULE_TYPE_PROJECT: + case commonStorage.MODULE_TYPE_ROBOT: case commonStorage.MODULE_TYPE_MECHANISM: code = 'self.'; break; diff --git a/src/blocks/mrc_get_python_variable.ts b/src/blocks/mrc_get_python_variable.ts index 5539b7c7..a7196c32 100644 --- a/src/blocks/mrc_get_python_variable.ts +++ b/src/blocks/mrc_get_python_variable.ts @@ -228,8 +228,7 @@ type GetPythonVariableExtraState = { */ actualVariableName?: string, /** - * True if this blocks refers to an exported variable (for example, from a - * user's Project). + * True if this blocks refers to an exported variable (for example, from the Robot). */ exportedVariable?: boolean, }; diff --git a/src/blocks/mrc_set_python_variable.ts b/src/blocks/mrc_set_python_variable.ts index d43d9c7f..218decf2 100644 --- a/src/blocks/mrc_set_python_variable.ts +++ b/src/blocks/mrc_set_python_variable.ts @@ -178,8 +178,7 @@ type SetPythonVariableExtraState = { */ actualVariableName?: string, /** - * True if this blocks refers to an exported variable (for example, from a - * user's Project). + * True if this blocks refers to an exported variable (for example, from the Robot). */ exportedVariable?: boolean, }; diff --git a/src/blocks/utils/python.ts b/src/blocks/utils/python.ts index a8afce86..f0f895fb 100644 --- a/src/blocks/utils/python.ts +++ b/src/blocks/utils/python.ts @@ -19,7 +19,7 @@ * @author lizlooney@google.com (Liz Looney) */ -import { PythonData, organizeVarDataByType, VariableGettersAndSetters } from './python_json_types'; +import { ClassData, PythonData, organizeVarDataByType, VariableGettersAndSetters } from './python_json_types'; import { robotPyData } from './robotpy_data'; import { externalSamplesData } from './external_samples_data'; diff --git a/src/blocks/utils/python_json_types.ts b/src/blocks/utils/python_json_types.ts index 514c767b..407893d6 100644 --- a/src/blocks/utils/python_json_types.ts +++ b/src/blocks/utils/python_json_types.ts @@ -123,7 +123,7 @@ function isSuperFunction(f1: FunctionData, f2: FunctionData): boolean { return true; } -export function findSuperFunctionData(functionData: FunctionData, superClassFunctions: FunctionData): FunctionData | null { +export function findSuperFunctionData(functionData: FunctionData, superClassFunctions: FunctionData[]): FunctionData | null { for (const superClassFunctionData of superClassFunctions) { if (isSuperFunction(superClassFunctionData, functionData)) { return superClassFunctionData; diff --git a/src/editor/editor.ts b/src/editor/editor.ts index 86d6f5a5..204bd517 100644 --- a/src/editor/editor.ts +++ b/src/editor/editor.ts @@ -45,9 +45,9 @@ export class Editor { private eventsCategory: EventsCategory; private currentModule: commonStorage.Module | null = null; private modulePath: string = ''; - private projectPath: string = ''; + private robotPath: string = ''; private moduleContent: string = ''; - private projectContent: string = ''; + private robotContent: string = ''; private bindedOnChange: any = null; private toolbox: Blockly.utils.toolbox.ToolboxDefinition = EMPTY_TOOLBOX; @@ -73,11 +73,11 @@ export class Editor { // TODO(lizlooney): As blocks are loaded, determine whether any blocks // are accessing variable or calling functions thar are defined in another - // blocks file (like a Project) and check whether the variable or function + // blocks file (like the Robot) and check whether the variable or function // definition has changed. This might happen if the user defines a variable - // or function in the Project, uses the variable or function in the + // or function in the Robot, uses the variable or function in the // OpMode, and then removes or changes the variable or function in the - // Project. + // Robot. // TODO(lizlooney): We will need a way to identify which variable or // function, other than by the variable name or function name, because the @@ -88,7 +88,7 @@ export class Editor { // TODO(lizlooney): Look at blocks with type 'mrc_get_python_variable' or // 'mrc_set_python_variable', and where block.mrcExportedVariable === true. // Look at block.mrcImportModule and get the exported blocks for that module. - // (It should be the project and we already have the project content.) + // It could be from the Robot (or a Mechanism?) and we already have the Robot content. // Check whether block.mrcActualVariableName matches any exportedBlock's // extraState.actualVariableName. If there is no match, put a warning on the // block. @@ -96,7 +96,7 @@ export class Editor { // TODO(lizlooney): Look at blocks with type 'mrc_call_python_function' and // where block.mrcExportedFunction === true. // Look at block.mrcImportModule and get the exported blocks for that module. - // (It should be the project and we already have the project content.) + // It could be from the Robot (or a Mechanism?) and we already have the Robot content. // Check whether block.mrcActualFunctionName matches any exportedBlock's // extraState.actualFunctionName. If there is no match, put a warning on the block. // If there is a match, check whether @@ -124,21 +124,23 @@ export class Editor { if (currentModule) { this.modulePath = currentModule.modulePath; - this.projectPath = commonStorage.makeProjectPath(currentModule.projectName); + this.robotPath = commonStorage.makeRobotPath(currentModule.projectName); } else { this.modulePath = ''; - this.projectPath = ''; + this.robotPath = ''; } this.moduleContent = ''; - this.projectContent = ''; + this.robotContent = ''; this.clearBlocklyWorkspace(); if (currentModule) { + // Fetch the content for the current module and the robot. + // TODO: Also fetch the content for the mechanisms? const promises: {[key: string]: Promise} = {}; // key is module path, value is promise of module content. promises[this.modulePath] = this.storage.fetchModuleContent(this.modulePath); - if (this.projectPath !== this.modulePath) { - // Also fetch the project module content. It contains exported blocks that can be used. - promises[this.projectPath] = this.storage.fetchModuleContent(this.projectPath) + if (this.robotPath !== this.modulePath) { + // Also fetch the robot module content. It contains components, etc, that can be used. + promises[this.robotPath] = this.storage.fetchModuleContent(this.robotPath) } const moduleContents: {[key: string]: string} = {}; // key is module path, value is module content @@ -148,10 +150,10 @@ export class Editor { }) ); this.moduleContent = moduleContents[this.modulePath]; - if (this.projectPath === this.modulePath) { - this.projectContent = this.moduleContent + if (this.robotPath === this.modulePath) { + this.robotContent = this.moduleContent } else { - this.projectContent = moduleContents[this.projectPath]; + this.robotContent = moduleContents[this.robotPath]; } this.loadBlocksIntoBlocklyWorkspace(); } @@ -187,8 +189,8 @@ export class Editor { public updateToolbox(shownPythonToolboxCategories: Set): void { if (this.currentModule) { - if (!this.projectContent) { - // The Project content hasn't been fetched yet. Try again in a bit. + if (!this.robotContent) { + // The Robot content hasn't been fetched yet. Try again in a bit. setTimeout(() => { this.updateToolbox(shownPythonToolboxCategories) }, 50); @@ -229,7 +231,8 @@ export class Editor { private getComponents(): commonStorage.Component[] { const components: commonStorage.Component[] = []; - if (this.currentModule?.moduleType === commonStorage.MODULE_TYPE_PROJECT) { + if (this.currentModule?.moduleType === commonStorage.MODULE_TYPE_ROBOT || + this.currentModule?.moduleType === commonStorage.MODULE_TYPE_MECHANISM) { // TODO(lizlooney): Fill the components array. } return components; diff --git a/src/editor/extended_python_generator.ts b/src/editor/extended_python_generator.ts index 1812a575..1a65a2c3 100644 --- a/src/editor/extended_python_generator.ts +++ b/src/editor/extended_python_generator.ts @@ -122,8 +122,8 @@ export class ExtendedPythonGenerator extends PythonGenerator { } getModuleType(): string | null { - if (this.context && this.context.module) { - return this.context.module.moduleType; + if (this.context) { + return this.context.getModuleType(); } return null; } diff --git a/src/editor/generator_context.ts b/src/editor/generator_context.ts index e6007ade..dbffa3a5 100644 --- a/src/editor/generator_context.ts +++ b/src/editor/generator_context.ts @@ -41,6 +41,13 @@ export class GeneratorContext { this.clear(); } + getModuleType(): string | null { + if (this.module) { + return this.module.moduleType; + } + return null; + } + clear(): void { this.clearExportedBlocks(); this.hasHardware= false; @@ -58,7 +65,7 @@ export class GeneratorContext { if (!this.module) { throw new Error('getClassName: this.module is null.'); } - if (this.module.moduleType === commonStorage.MODULE_TYPE_PROJECT) { + if (this.module.moduleType === commonStorage.MODULE_TYPE_ROBOT) { return 'Robot'; } @@ -69,7 +76,7 @@ export class GeneratorContext { if (!this.module) { throw new Error('getClassParent: this.module is null.'); } - if (this.module.moduleType === commonStorage.MODULE_TYPE_PROJECT) { + if (this.module.moduleType === commonStorage.MODULE_TYPE_ROBOT) { return 'RobotBase'; } if (this.module.moduleType === commonStorage.MODULE_TYPE_OPMODE) { diff --git a/src/reactComponents/AddTabDialog.tsx b/src/reactComponents/AddTabDialog.tsx index 519b8f9e..bc00ae69 100644 --- a/src/reactComponents/AddTabDialog.tsx +++ b/src/reactComponents/AddTabDialog.tsx @@ -134,7 +134,7 @@ export default function AddTabDialog(props: AddTabDialogProps) { await commonStorage.addModuleToProject( props.storage, props.project, storageType, trimmedName); - const newModule = commonStorage.getClassInProject(props.project, trimmedName); + const newModule = commonStorage.findModuleByClassName(props.project, trimmedName); if (newModule) { const module: Module = { path: newModule.modulePath, diff --git a/src/reactComponents/FileManageModal.tsx b/src/reactComponents/FileManageModal.tsx index 14a2f714..a1ec6edf 100644 --- a/src/reactComponents/FileManageModal.tsx +++ b/src/reactComponents/FileManageModal.tsx @@ -186,7 +186,7 @@ export default function FileManageModal(props: FileManageModalProps) { trimmedName ); - const newModule = commonStorage.getClassInProject(props.project, trimmedName); + const newModule = commonStorage.findModuleByClassName(props.project, trimmedName); if (newModule) { const module: Module = { path: newModule.modulePath, diff --git a/src/reactComponents/Header.tsx b/src/reactComponents/Header.tsx index ceb8edf1..009727a3 100644 --- a/src/reactComponents/Header.tsx +++ b/src/reactComponents/Header.tsx @@ -72,7 +72,7 @@ export default function Header(props: HeaderProps): React.JSX.Element { /** Gets the project name or fallback text. */ const getProjectName = (): string => { - return props.project?.className || 'No Project Selected'; + return props.project?.robot.className || 'No Project Selected'; }; return ( diff --git a/src/reactComponents/Menu.tsx b/src/reactComponents/Menu.tsx index 25bc82b8..eadc16fe 100644 --- a/src/reactComponents/Menu.tsx +++ b/src/reactComponents/Menu.tsx @@ -127,7 +127,7 @@ function getMenuItems(t: (key: string) => string, project: commonStorage.Project getItem(t('Manage') + '...', 'manageProjects'), ]), getItem(t('Explorer'), 'explorer', , [ - getItem(t('Robot'), project.modulePath, ), + getItem(t('Robot'), project.robot.modulePath, ), getItem(t('Mechanisms'), 'mechanisms', , mechanisms), getItem(t('OpModes'), 'opmodes', , opmodes), ]), @@ -149,7 +149,7 @@ function getMenuItems(t: (key: string) => string, project: commonStorage.Project export function Component(props: MenuProps): React.JSX.Element { const {t} = I18Next.useTranslation(); - const [modules, setModules] = React.useState([]); + const [projects, setProjects] = React.useState([]); const [menuItems, setMenuItems] = React.useState([]); const [fileModalOpen, setFileModalOpen] = React.useState(false); const [projectModalOpen, setProjectModalOpen] = React.useState(false); @@ -157,28 +157,28 @@ export function Component(props: MenuProps): React.JSX.Element { const [noProjects, setNoProjects] = React.useState(false); const [aboutDialogVisible, setAboutDialogVisible] = React.useState(false); - /** Fetches the list of modules from storage. */ - const fetchListOfModules = async (): Promise => { + /** Fetches the list of projects from storage. */ + const fetchListOfProjects = async (): Promise => { return new Promise(async (resolve, reject) => { if (!props.storage) { reject(new Error('Storage not available')); return; } try { - const array = await props.storage.listModules(); - setModules(array); + const array = await props.storage.listProjects(); + setProjects(array); resolve(array); } catch (e) { - console.error('Failed to load the list of modules:', e); - props.setAlertErrorMessage(t('fail_list_modules')); - reject(new Error(t('fail_list_modules'))); + console.error('Failed to load the list of projects:', e); + props.setAlertErrorMessage(t('fail_list_projects')); + reject(new Error(t('fail_list_projects'))); } }); }; - /** Initializes the modules and handles empty project state. */ - const initializeModules = async (): Promise => { - const array = await fetchListOfModules(); + /** Initializes the projects and handles empty project state. */ + const initializeProjects = async (): Promise => { + const array = await fetchListOfProjects(); if (array.length === 0) { setNoProjects(true); setProjectModalOpen(true); @@ -194,14 +194,14 @@ export function Component(props: MenuProps): React.JSX.Element { MOST_RECENT_PROJECT_KEY, '' ); - modules.forEach((module) => { - if (module.projectName === mostRecentProject) { - props.setProject(module); + projects.forEach((project) => { + if (project.projectName === mostRecentProject) { + props.setProject(project); found = true; } }); - if (!found && modules.length > 0) { - props.setProject(modules[0]); + if (!found && projects.length > 0) { + props.setProject(projects[0]); } } }; @@ -219,7 +219,7 @@ export function Component(props: MenuProps): React.JSX.Element { /** Handles menu item clicks. */ const handleClick: Antd.MenuProps['onClick'] = ({key}): void => { const newModule = props.project ? - commonStorage.findModuleInProject(props.project, key) : + commonStorage.findModuleByModulePath(props.project, key) : null; if (newModule) { @@ -268,18 +268,18 @@ export function Component(props: MenuProps): React.JSX.Element { setProjectModalOpen(false); }; - // Initialize modules when storage is available + // Initialize projects when storage is available React.useEffect(() => { if (!props.storage) { return; } - initializeModules(); + initializeProjects(); }, [props.storage]); - // Fetch most recent project when modules change + // Fetch most recent project when projects change React.useEffect(() => { fetchMostRecentProject(); - }, [modules]); + }, [projects]); // Update menu items and save project when project changes React.useEffect(() => { @@ -307,7 +307,6 @@ export function Component(props: MenuProps): React.JSX.Element { isOpen={projectModalOpen} onCancel={handleProjectModalClose} storage={props.storage} - moduleType={moduleType} setProject={props.setProject} setAlertErrorMessage={props.setAlertErrorMessage} /> diff --git a/src/reactComponents/ProjectManageModal.tsx b/src/reactComponents/ProjectManageModal.tsx index 047258af..78b636b3 100644 --- a/src/reactComponents/ProjectManageModal.tsx +++ b/src/reactComponents/ProjectManageModal.tsx @@ -34,7 +34,6 @@ interface ProjectManageModalProps { setProject: (project: commonStorage.Project | null) => void; setAlertErrorMessage: (message: string) => void; storage: commonStorage.Storage | null; - moduleType: TabType; } /** Default page size for table pagination. */ @@ -64,20 +63,20 @@ const CONTAINER_PADDING = '12px'; */ export default function ProjectManageModal(props: ProjectManageModalProps): React.JSX.Element { const {t} = I18Next.useTranslation(); - const [modules, setModules] = React.useState([]); + const [allProjects, setAllProjects] = React.useState([]); const [newItemName, setNewItemName] = React.useState(''); const [currentRecord, setCurrentRecord] = React.useState(null); const [renameModalOpen, setRenameModalOpen] = React.useState(false); const [name, setName] = React.useState(''); const [copyModalOpen, setCopyModalOpen] = React.useState(false); - /** Loads modules from storage and sorts them alphabetically. */ - const loadModules = async (storage: commonStorage.Storage): Promise => { - const projects = await storage.listModules(); + /** Loads projects from storage and sorts them alphabetically. */ + const loadProjects = async (storage: commonStorage.Storage): Promise => { + const projects = await storage.listProjects(); - // Sort modules alphabetically by class name - projects.sort((a, b) => a.className.localeCompare(b.className)); - setModules(projects); + // Sort projects alphabetically by class name + projects.sort((a, b) => a.robot.className.localeCompare(b.robot.className)); + setAllProjects(projects); if (projects.length > 0 && props.noProjects) { props.setProject(projects[0]); // Set the first project as the current project @@ -86,44 +85,45 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac }; /** Handles renaming a project. */ - const handleRename = async (origModule: commonStorage.Project, newName: string): Promise => { + const handleRename = async (origProject: commonStorage.Project, newName: string): Promise => { if (!props.storage) { return; } + const newProjectName = commonStorage.classNameToModuleName(newName); + try { - await props.storage.renameModule( - commonStorage.MODULE_TYPE_PROJECT, - origModule.className, - origModule.className, - newName + await props.storage.renameProject( + origProject.projectName, + newProjectName ); - await loadModules(props.storage); + await loadProjects(props.storage); } catch (error) { - console.error('Error renaming module:', error); - props.setAlertErrorMessage('Failed to rename module'); + console.error('Error renaming project:', error); + props.setAlertErrorMessage('Failed to rename project'); } setRenameModalOpen(false); }; /** Handles copying a project. */ - const handleCopy = async (origModule: commonStorage.Project, newName: string): Promise => { + const handleCopy = async (origProject: commonStorage.Project, newName: string): Promise => { if (!props.storage) { return; } + // + const newProjectName = commonStorage.classNameToModuleName(newName); + try { - await props.storage.copyModule( - commonStorage.MODULE_TYPE_PROJECT, - origModule.className, - origModule.className, - newName + await props.storage.copyProject( + origProject.projectName, + newProjectName ); - await loadModules(props.storage); + await loadProjects(props.storage); } catch (error) { - console.error('Error copying module:', error); - props.setAlertErrorMessage('Failed to copy module'); + console.error('Error copying project:', error); + props.setAlertErrorMessage('Failed to copy project'); } setCopyModalOpen(false); @@ -137,14 +137,12 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac } const newProjectName = commonStorage.classNameToModuleName(trimmedName); - const newProjectPath = commonStorage.makeProjectPath(newProjectName); - const projectContent = commonStorage.newProjectContent(newProjectName); + const robotContent = commonStorage.newRobotContent(newProjectName); try { - await props.storage.createModule( - commonStorage.MODULE_TYPE_PROJECT, - newProjectPath, - projectContent + await props.storage.createProject( + newProjectName, + robotContent ); } catch (e) { console.error('Failed to create a new project:', e); @@ -152,7 +150,7 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac } setNewItemName(''); - await loadModules(props.storage); + await loadProjects(props.storage); }; /** Handles project deletion with proper cleanup. */ @@ -161,13 +159,13 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac return; } - const newModules = modules.filter((m) => m.modulePath !== record.modulePath); - setModules(newModules); + const newProjects = allProjects.filter((m) => m.projectName !== record.projectName); + setAllProjects(newProjects); // Find another project to set as current let foundAnotherProject = false; - for (const project of modules) { - if (project.modulePath !== record.modulePath) { + for (const project of allProjects) { + if (project.projectName !== record.projectName) { props.setProject(project); foundAnotherProject = true; break; @@ -179,7 +177,7 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac } try { - await props.storage.deleteModule(commonStorage.MODULE_TYPE_PROJECT, record.modulePath); + await props.storage.deleteProject(record.projectName); } catch (e) { console.error('Failed to delete the project:', e); props.setAlertErrorMessage('Failed to delete the project.'); @@ -195,25 +193,25 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac /** Opens the rename modal for a specific project. */ const openRenameModal = (record: commonStorage.Project): void => { setCurrentRecord(record); - setName(record.className); + setName(record.robot.className); setRenameModalOpen(true); }; /** Opens the copy modal for a specific project. */ const openCopyModal = (record: commonStorage.Project): void => { setCurrentRecord(record); - setName(record.className + COPY_SUFFIX); + setName(record.robot.className + COPY_SUFFIX); setCopyModalOpen(true); }; /** Gets the rename modal title. */ const getRenameModalTitle = (): string => { - return `Rename Project: ${currentRecord ? currentRecord.className : ''}`; + return `Rename Project: ${currentRecord ? currentRecord.robot.className : ''}`; }; /** Gets the copy modal title. */ const getCopyModalTitle = (): string => { - return `Copy Project: ${currentRecord ? currentRecord.className : ''}`; + return `Copy Project: ${currentRecord ? currentRecord.robot.className : ''}`; }; /** Creates the container style object. */ @@ -233,8 +231,8 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac const columns: Antd.TableProps['columns'] = [ { title: 'Name', - dataIndex: 'className', - key: 'className', + dataIndex: 'robot.className', + key: 'robot.className', ellipsis: { showTitle: false, }, @@ -274,10 +272,10 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac onClick={() => openCopyModal(record)} /> - {modules.length > 1 && ( + {allProjects.length > 1 && ( handleDeleteProject(record)} okText={t('Delete')} @@ -298,10 +296,10 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac }, ]; - // Load modules when storage becomes available or modal opens + // Load projects when storage becomes available or modal opens React.useEffect(() => { if (props.storage) { - loadModules(props.storage); + loadProjects(props.storage); } }, [props.storage, props.isOpen]); @@ -328,8 +326,8 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac handleRename(currentRecord, name); } }} - projects={modules} - setProjects={setModules} + projects={allProjects} + setProjects={setAllProjects} /> )} @@ -355,8 +353,8 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac handleCopy(currentRecord, name); } }} - projects={modules} - setProjects={setModules} + projects={allProjects} + setProjects={setAllProjects} /> )} @@ -386,17 +384,17 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac newItemName={newItemName} setNewItemName={setNewItemName} onAddNewItem={handleAddNewItem} - projects={modules} - setProjects={setModules} + projects={allProjects} + setProjects={setAllProjects} /> {!props.noProjects && ( columns={columns} - dataSource={modules} - rowKey="modulePath" + dataSource={allProjects} + rowKey="robot.className" size="small" - pagination={modules.length > DEFAULT_PAGE_SIZE ? { + pagination={allProjects.length > DEFAULT_PAGE_SIZE ? { pageSize: DEFAULT_PAGE_SIZE, showSizeChanger: false, showQuickJumper: false, diff --git a/src/reactComponents/ProjectNameComponent.tsx b/src/reactComponents/ProjectNameComponent.tsx index 3e8c0b82..f21ade8b 100644 --- a/src/reactComponents/ProjectNameComponent.tsx +++ b/src/reactComponents/ProjectNameComponent.tsx @@ -68,7 +68,7 @@ export default function ProjectNameComponent(props: ProjectNameComponentProps): return; } - if (props.projects.some((project) => project.className === trimmedName)) { + if (props.projects.some((project) => project.robot.className === trimmedName)) { showError(DUPLICATE_NAME_MESSAGE_PREFIX + trimmedName + DUPLICATE_NAME_MESSAGE_SUFFIX); return; } diff --git a/src/reactComponents/Tabs.tsx b/src/reactComponents/Tabs.tsx index 253e1147..2ace6589 100644 --- a/src/reactComponents/Tabs.tsx +++ b/src/reactComponents/Tabs.tsx @@ -84,7 +84,7 @@ export function Component(props: TabsProps): React.JSX.Element { /** Handles tab change and updates current module. */ const handleTabChange = (key: string): void => { if (props.project) { - props.setCurrentModule(commonStorage.findModuleInProject(props.project, key)); + props.setCurrentModule(commonStorage.findModuleByModulePath(props.project, key)); setActiveKey(key); } }; @@ -101,7 +101,7 @@ export function Component(props: TabsProps): React.JSX.Element { return; } - const module = commonStorage.findModuleInProject(props.project, key); + const module = commonStorage.findModuleByModulePath(props.project, key); if (!module) { return; } diff --git a/src/storage/client_side_storage.ts b/src/storage/client_side_storage.ts index 9540fb6f..65a85239 100644 --- a/src/storage/client_side_storage.ts +++ b/src/storage/client_side_storage.ts @@ -118,7 +118,7 @@ class ClientSideStorage implements commonStorage.Storage { }); } - async listModules(): Promise { + async listProjects(): Promise { return new Promise((resolve, reject) => { const projects: {[key: string]: commonStorage.Project} = {}; // key is project name, value is Project // The mechanisms and opModes variables hold any Mechanisms and OpModes that @@ -148,9 +148,13 @@ class ClientSideStorage implements commonStorage.Storage { dateModifiedMillis: value.dateModifiedMillis, className: commonStorage.moduleNameToClassName(moduleName), } - if (moduleType === commonStorage.MODULE_TYPE_PROJECT) { - const project: commonStorage.Project = { + if (moduleType === commonStorage.MODULE_TYPE_ROBOT) { + const robot: commonStorage.Robot = { ...module, + }; + const project: commonStorage.Project = { + projectName: moduleName, + robot: robot, mechanisms: [], opModes: [], }; @@ -203,12 +207,12 @@ class ClientSideStorage implements commonStorage.Storage { cursor.continue(); } else { // The cursor is done. We have finished reading all the modules. - const modules: commonStorage.Project[] = []; + const projectsToReturn: commonStorage.Project[] = []; const sortedProjectNames = Object.keys(projects).sort(); sortedProjectNames.forEach((projectName) => { - modules.push(projects[projectName]); + projectsToReturn.push(projects[projectName]); }); - resolve(modules); + resolve(projectsToReturn); } }; }); @@ -234,6 +238,11 @@ class ClientSideStorage implements commonStorage.Storage { }); } + async createProject(projectName: string, robotContent: string): Promise { + const modulePath = commonStorage.makeRobotPath(projectName); + return this._saveModule(commonStorage.MODULE_TYPE_ROBOT, modulePath, robotContent); + } + async createModule(moduleType: string, modulePath: string, moduleContent: string): Promise { return this._saveModule(moduleType, modulePath, moduleContent); } @@ -326,8 +335,8 @@ class ClientSideStorage implements commonStorage.Storage { const moduleType = value.type; if (commonStorage.getProjectName(path) === oldProjectName) { let newPath; - if (moduleType === commonStorage.MODULE_TYPE_PROJECT) { - newPath = commonStorage.makeProjectPath(newProjectName); + if (moduleType === commonStorage.MODULE_TYPE_ROBOT) { + newPath = commonStorage.makeRobotPath(newProjectName); } else { const moduleName = commonStorage.getModuleName(path); newPath = commonStorage.makeModulePath(newProjectName, moduleName); @@ -375,9 +384,20 @@ class ClientSideStorage implements commonStorage.Storage { }); } + 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, oldModuleName: string, newModuleName: string): Promise { + if (moduleType == commonStorage.MODULE_TYPE_ROBOT) { + throw new Error('Renaming the robot module is not allowed. Call renameProject to rename the project.'); + } return this._renameOrCopyModule( moduleType, projectName, oldModuleName, newModuleName, false); } @@ -385,6 +405,9 @@ class ClientSideStorage implements commonStorage.Storage { async copyModule( moduleType: string, projectName: string, oldModuleName: string, newModuleName: string): Promise { + if (moduleType == commonStorage.MODULE_TYPE_ROBOT) { + throw new Error('Copying the robot module is not allowed. Call copyProject to rename the project.'); + } return this._renameOrCopyModule( moduleType, projectName, oldModuleName, newModuleName, true); } @@ -393,10 +416,6 @@ class ClientSideStorage implements commonStorage.Storage { moduleType: string, projectName: string, oldModuleName: string, newModuleName: string, copy: boolean): Promise { - if (moduleType == commonStorage.MODULE_TYPE_PROJECT) { - return this._renameOrCopyProject(oldModuleName, newModuleName, copy); - } - return new Promise((resolve, reject) => { const transaction = this.db.transaction(['modules'], 'readwrite'); transaction.oncomplete = () => { @@ -446,7 +465,7 @@ class ClientSideStorage implements commonStorage.Storage { }); } - private async _deleteProject(projectName: string): Promise { + async deleteProject(projectName: string): Promise { return new Promise((resolve, reject) => { const transaction = this.db.transaction(['modules'], 'readwrite'); transaction.oncomplete = () => { @@ -492,9 +511,8 @@ class ClientSideStorage implements commonStorage.Storage { } async deleteModule(moduleType: string, modulePath: string): Promise { - if (moduleType == commonStorage.MODULE_TYPE_PROJECT) { - const projectName = commonStorage.getProjectName(modulePath); - return this._deleteProject(projectName); + if (moduleType == commonStorage.MODULE_TYPE_ROBOT) { + throw new Error('Deleting the robot module is not allowed. Call deleteProject to delete the project.'); } return new Promise((resolve, reject) => { diff --git a/src/storage/common_storage.ts b/src/storage/common_storage.ts index 76912caf..12088382 100644 --- a/src/storage/common_storage.ts +++ b/src/storage/common_storage.ts @@ -42,10 +42,13 @@ export type Module = { className: string, }; +export type Robot = Module; export type Mechanism = Module; export type OpMode = Module; -export type Project = Module & { +export type Project = { + projectName: string, + robot: Robot, mechanisms: Mechanism[] opModes: OpMode[], }; @@ -56,7 +59,7 @@ export type Component = { } export const MODULE_TYPE_UNKNOWN = 'unknown'; -export const MODULE_TYPE_PROJECT = 'project'; +export const MODULE_TYPE_ROBOT = 'robot'; export const MODULE_TYPE_MECHANISM = 'mechanism'; export const MODULE_TYPE_OPMODE = 'opmode'; @@ -78,12 +81,16 @@ export const UPLOAD_DOWNLOAD_FILE_EXTENSION = '.blocks'; export interface Storage { saveEntry(entryKey: string, entryValue: string): Promise; fetchEntry(entryKey: string, defaultValue: string): Promise; - listModules(): Promise; + listProjects(): Promise; fetchModuleContent(modulePath: string): Promise; + createProject(projectName: string, robotContent: string): Promise; createModule(moduleType: string, modulePath: string, moduleContent: string): Promise; saveModule(modulePath: string, moduleContent: string): Promise; + renameProject(oldProjectName: string, newProjectName: string): Promise; + copyProject(oldProjectName: string, newProjectName: string): Promise; renameModule(moduleType: string, projectName: string, oldModuleName: string, newModuleName: string): Promise; copyModule(moduleType: string, projectName: string, oldModuleName: string, newModuleName: string): Promise; + deleteProject(projectName: string): Promise; deleteModule(moduleType: string, modulePath: string): Promise; downloadProject(projectName: string): Promise; uploadProject(projectName: string, blobUrl: string): Promise; @@ -131,8 +138,11 @@ export async function addModuleToProject( */ export async function removeModuleFromProject( storage: Storage, project: Project, modulePath: string): Promise { - const module = findModuleInProject(project, modulePath); + const module = findModuleByModulePath(project, modulePath); if (module) { + if (module.moduleType == MODULE_TYPE_ROBOT) { + throw new Error('Removing the robot module from the project is not allowed.'); + } await storage.deleteModule(module.moduleType, modulePath); if (module.moduleType === MODULE_TYPE_MECHANISM) { project.mechanisms = project.mechanisms.filter(m => m.modulePath !== modulePath); @@ -152,8 +162,11 @@ export async function removeModuleFromProject( */ export async function renameModuleInProject( storage: Storage, project: Project, proposedName: string, oldModulePath: string): Promise { - const module = findModuleInProject(project, oldModulePath); + const module = findModuleByModulePath(project, oldModulePath); if (module) { + if (module.moduleType == MODULE_TYPE_ROBOT) { + throw new Error('Renaming the robot module is not allowed.'); + } const newModuleName = classNameToModuleName(proposedName); const newModulePath = makeModulePath(project.projectName, newModuleName); await storage.renameModule(module.moduleType, project.projectName, module.moduleName, newModuleName); @@ -190,8 +203,11 @@ export async function renameModuleInProject( */ export async function copyModuleInProject( storage: Storage, project: Project, proposedName: string, oldModulePath: string): Promise { - const module = findModuleInProject(project, oldModulePath); + const module = findModuleByModulePath(project, oldModulePath); if (module) { + if (module.moduleType == MODULE_TYPE_ROBOT) { + throw new Error('Copying the robot module is not allowed.'); + } const newModuleName = classNameToModuleName(proposedName); const newModulePath = makeModulePath(project.projectName, newModuleName); await storage.copyModule(module.moduleType, project.projectName, module.moduleName, newModuleName); @@ -231,12 +247,7 @@ export function isClassNameOk(project: Project, proposedName: string) { if (!isValidClassName(proposedName)) { ok = false; error = proposedName + ' is not a valid name. Please enter a different name.'; - } - else if (proposedName == project.className) { - ok = false; - error = 'The project is already named ' + proposedName + '. Please enter a different name.'; - } - else if (getClassInProject(project, proposedName) != null) { + } else if (findModuleByClassName(project, proposedName) != null) { ok = false; error = 'Another Mechanism or OpMode is already named ' + proposedName + '. Please enter a different name.' } @@ -248,42 +259,31 @@ export function isClassNameOk(project: Project, proposedName: string) { } /** - * Returns true if the given classname is in the project + * Returns the module in the given project with the given class name. */ -export function getClassInProject(project: Project, name: string): Module | null { +export function findModuleByClassName(project: Project, className: string): Module | null { + if (project.robot.className === className) { + return project.robot; + } for (const mechanism of project.mechanisms) { - if (mechanism.className === name) { + if (mechanism.className === className) { return mechanism; } } for (const opMode of project.opModes) { - if (opMode.className === name) { + if (opMode.className === className) { return opMode; } } return null; } -/** - * Returns the module with the given module path, or null if it is not found. - */ -export function findModule(modules: Project[], modulePath: string): Module | null { - for (const project of modules) { - const result = findModuleInProject(project, modulePath); - if (result) { - return result; - } - } - - return null; -} - /** * Returns the module with the given module path inside the given project, or null if it is not found. */ -export function findModuleInProject(project: Project, modulePath: string): Module | null { - if (project.modulePath === modulePath) { - return project; +export function findModuleByModulePath(project: Project, modulePath: string): Module | null { + if (project.robot.modulePath === modulePath) { + return project.robot; } for (const mechanism of project.mechanisms) { if (mechanism.modulePath === modulePath) { @@ -343,7 +343,7 @@ export function isValidClassName(name: string): boolean { } /** - * Returns the module name for the given class name. + * Returns the module name (snake_case) for the given class name (PascalCase). */ export function classNameToModuleName(className: string): string { let moduleName = ''; @@ -362,7 +362,7 @@ export function classNameToModuleName(className: string): string { } /** - * Returns the class name for the given module name. + * Returns the class name (PascalCase) for the given module name (snake_case). */ export function moduleNameToClassName(moduleName: string): string { let className = ''; @@ -395,9 +395,9 @@ export function makeModulePath(projectName: string, moduleName: string): string } /** - * Returns the project path for the given project names. + * Returns the robot module path for the given project names. */ -export function makeProjectPath(projectName: string): string { +export function makeRobotPath(projectName: string): string { return makeModulePath(projectName, projectName); } @@ -446,12 +446,12 @@ function startingBlocksToModuleContent( } /** - * Returns the module content for a new Project. + * Returns the robot module content for a new Project. */ -export function newProjectContent(projectName: string): string { +export function newRobotContent(projectName: string): string { const module: Module = { - modulePath: makeProjectPath(projectName), - moduleType: MODULE_TYPE_PROJECT, + modulePath: makeRobotPath(projectName), + moduleType: MODULE_TYPE_ROBOT, projectName: projectName, moduleName: projectName, dateModifiedMillis: 0, @@ -754,7 +754,7 @@ export function _processUploadedModule( moduleType = moduleType.substring(MARKER_MODULE_TYPE.length); } - const moduleName = (moduleType === MODULE_TYPE_PROJECT) + const moduleName = (moduleType === MODULE_TYPE_ROBOT) ? projectName : filename; const module: Module = { diff --git a/src/toolbox/hardware_category.ts b/src/toolbox/hardware_category.ts index a19c1a96..133e2363 100644 --- a/src/toolbox/hardware_category.ts +++ b/src/toolbox/hardware_category.ts @@ -43,7 +43,7 @@ export function getHardwareCategory(currentModule: commonStorage.Module) { ] }; } - if (currentModule.moduleType === commonStorage.MODULE_TYPE_PROJECT) { + if (currentModule.moduleType === commonStorage.MODULE_TYPE_ROBOT) { return { kind: 'category', name: 'Hardware', diff --git a/src/toolbox/methods_category.ts b/src/toolbox/methods_category.ts index d41ced35..4e72acba 100644 --- a/src/toolbox/methods_category.ts +++ b/src/toolbox/methods_category.ts @@ -67,8 +67,8 @@ export class MethodsCategory { } }); - if (this.currentModule.moduleType == commonStorage.MODULE_TYPE_PROJECT) { - // Add the methods for a Project (Robot). + if (this.currentModule.moduleType == commonStorage.MODULE_TYPE_ROBOT) { + // Add the methods for a Robot. this.addClassBlocksForCurrentModule( 'More Robot Methods', robot_class_blocks, methodNamesAlreadyUsed, contents); diff --git a/src/toolbox/toolbox.ts b/src/toolbox/toolbox.ts index 1509966a..2aec4211 100644 --- a/src/toolbox/toolbox.ts +++ b/src/toolbox/toolbox.ts @@ -8,7 +8,7 @@ export function getToolboxJSON( shownPythonToolboxCategories: Set | null, currentModule: commonStorage.Module): Blockly.utils.toolbox.ToolboxDefinition { switch (currentModule.moduleType) { - case commonStorage.MODULE_TYPE_PROJECT: + case commonStorage.MODULE_TYPE_ROBOT: case commonStorage.MODULE_TYPE_MECHANISM: return { kind: 'categoryToolbox', From 9bdbefcd3eb8d5a228a3b5566f16e23f9ab2d414 Mon Sep 17 00:00:00 2001 From: Liz Looney Date: Fri, 4 Jul 2025 15:12:59 -0700 Subject: [PATCH 2/3] Make the robot's className be "Robot" everywhere, not just in the generated python code. Added userVisibleName to the commonStorage.Project definition. --- README.md | 1 - src/editor/generator_context.ts | 4 - src/reactComponents/AddTabDialog.tsx | 10 +- src/reactComponents/FileManageModal.tsx | 26 ++-- src/reactComponents/Header.tsx | 2 +- src/reactComponents/ModuleNameComponent.tsx | 6 +- src/reactComponents/ProjectManageModal.tsx | 58 ++++----- src/reactComponents/ProjectNameComponent.tsx | 12 +- src/reactComponents/Tabs.tsx | 34 ++--- src/storage/client_side_storage.ts | 9 +- src/storage/common_storage.ts | 125 ++++++++++++++----- 11 files changed, 172 insertions(+), 115 deletions(-) diff --git a/README.md b/README.md index 0db71dcf..9ccb42bf 100644 --- a/README.md +++ b/README.md @@ -15,4 +15,3 @@ WARNING! This is not ready for use and is under heavy development of basic featu 1. Mechanisms aren't limited to init 2. Mechanisms aren't limited to only Robot or Mechanism class 3. No way to specify whether an opmode is auto or teleop -4. Since we changed the "Workspace" terminology to "Project", existing Workspaces are no longer supported. They can be deleted via the browser's Developer Tools - Application tab. diff --git a/src/editor/generator_context.ts b/src/editor/generator_context.ts index dbffa3a5..4dc1d5a5 100644 --- a/src/editor/generator_context.ts +++ b/src/editor/generator_context.ts @@ -65,10 +65,6 @@ export class GeneratorContext { if (!this.module) { throw new Error('getClassName: this.module is null.'); } - if (this.module.moduleType === commonStorage.MODULE_TYPE_ROBOT) { - return 'Robot'; - } - return this.module.className; } diff --git a/src/reactComponents/AddTabDialog.tsx b/src/reactComponents/AddTabDialog.tsx index bc00ae69..2a76a064 100644 --- a/src/reactComponents/AddTabDialog.tsx +++ b/src/reactComponents/AddTabDialog.tsx @@ -106,14 +106,14 @@ export default function AddTabDialog(props: AddTabDialogProps) { /** Handles adding a new item or selecting an existing one. */ const handleAddNewItem = async (): Promise => { - const trimmedName = newItemName.trim(); - if (!trimmedName) { + const newClassName = newItemName.trim(); + if (!newClassName) { return; } // Check if there's an exact match in available items const matchingItem = availableItems.find((item) => - item.title.toLowerCase() === trimmedName.toLowerCase() + item.title.toLowerCase() === newClassName.toLowerCase() ); if (matchingItem) { @@ -132,9 +132,9 @@ export default function AddTabDialog(props: AddTabDialogProps) { commonStorage.MODULE_TYPE_OPMODE; await commonStorage.addModuleToProject( - props.storage, props.project, storageType, trimmedName); + props.storage, props.project, storageType, newClassName); - const newModule = commonStorage.findModuleByClassName(props.project, trimmedName); + const newModule = commonStorage.findModuleByClassName(props.project, newClassName); if (newModule) { const module: Module = { path: newModule.modulePath, diff --git a/src/reactComponents/FileManageModal.tsx b/src/reactComponents/FileManageModal.tsx index a1ec6edf..cda4041d 100644 --- a/src/reactComponents/FileManageModal.tsx +++ b/src/reactComponents/FileManageModal.tsx @@ -100,22 +100,22 @@ export default function FileManageModal(props: FileManageModalProps) { }, [props.project, props.moduleType]); /** Handles renaming a module. */ - const handleRename = async (origModule: Module, newName: string): Promise => { + const handleRename = async (origModule: Module, newClassName: string): Promise => { if (!props.storage || !props.project) { return; } try { - const newPath = await commonStorage.renameModuleInProject( + const newModulePath = await commonStorage.renameModuleInProject( props.storage, props.project, - newName, + newClassName, origModule.path ); const newModules = modules.map((module) => { if (module.path === origModule.path) { - return {...module, title: newName, path: newPath}; + return {...module, title: newClassName, path: newModulePath}; } return module; }); @@ -131,16 +131,16 @@ export default function FileManageModal(props: FileManageModalProps) { }; /** Handles copying a module. */ - const handleCopy = async (origModule: Module, newName: string): Promise => { + const handleCopy = async (origModule: Module, newClassName: string): Promise => { if (!props.storage || !props.project) { return; } try { - const newPath = await commonStorage.copyModuleInProject( + const newModulePath = await commonStorage.copyModuleInProject( props.storage, props.project, - newName, + newClassName, origModule.path ); @@ -153,8 +153,8 @@ export default function FileManageModal(props: FileManageModalProps) { const newModules = [...modules]; newModules.push({ - path: newPath, - title: newName, + path: newModulePath, + title: newClassName, type: originalModule.type, }); @@ -170,8 +170,8 @@ export default function FileManageModal(props: FileManageModalProps) { /** Handles adding a new module. */ const handleAddNewItem = async (): Promise => { - const trimmedName = newItemName.trim(); - if (!trimmedName || !props.storage || !props.project) { + const newClassName = newItemName.trim(); + if (!newClassName || !props.storage || !props.project) { return; } @@ -183,10 +183,10 @@ export default function FileManageModal(props: FileManageModalProps) { props.storage, props.project, storageType, - trimmedName + newClassName ); - const newModule = commonStorage.findModuleByClassName(props.project, trimmedName); + const newModule = commonStorage.findModuleByClassName(props.project, newClassName); if (newModule) { const module: Module = { path: newModule.modulePath, diff --git a/src/reactComponents/Header.tsx b/src/reactComponents/Header.tsx index 0d01facd..3a34f17d 100644 --- a/src/reactComponents/Header.tsx +++ b/src/reactComponents/Header.tsx @@ -76,7 +76,7 @@ export default function Header(props: HeaderProps): React.JSX.Element { /** Gets the project name or fallback text. */ const getProjectName = (): string => { - return props.project?.robot.className || 'No Project Selected'; + return props.project?.userVisibleName || 'No Project Selected'; }; return ( diff --git a/src/reactComponents/ModuleNameComponent.tsx b/src/reactComponents/ModuleNameComponent.tsx index 74ba0307..6beb5c9f 100644 --- a/src/reactComponents/ModuleNameComponent.tsx +++ b/src/reactComponents/ModuleNameComponent.tsx @@ -56,8 +56,8 @@ export default function ModuleNameComponent(props: ModuleNameComponentProps): Re /** Handles adding a new item with validation. */ const handleAddNewItem = (): void => { - const trimmedName = props.newItemName.trim(); - if (!trimmedName) { + const newClassName = props.newItemName.trim(); + if (!newClassName) { return; } @@ -65,7 +65,7 @@ export default function ModuleNameComponent(props: ModuleNameComponentProps): Re return; } - const {ok, error} = commonStorage.isClassNameOk(props.project, trimmedName); + const {ok, error} = commonStorage.isClassNameOk(props.project, newClassName); if (ok) { clearError(); props.onAddNewItem(); diff --git a/src/reactComponents/ProjectManageModal.tsx b/src/reactComponents/ProjectManageModal.tsx index 78b636b3..cf308959 100644 --- a/src/reactComponents/ProjectManageModal.tsx +++ b/src/reactComponents/ProjectManageModal.tsx @@ -74,8 +74,8 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac const loadProjects = async (storage: commonStorage.Storage): Promise => { const projects = await storage.listProjects(); - // Sort projects alphabetically by class name - projects.sort((a, b) => a.robot.className.localeCompare(b.robot.className)); + // Sort projects alphabetically by name + projects.sort((a, b) => a.userVisibleName.localeCompare(b.userVisibleName)); setAllProjects(projects); if (projects.length > 0 && props.noProjects) { @@ -85,17 +85,16 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac }; /** Handles renaming a project. */ - const handleRename = async (origProject: commonStorage.Project, newName: string): Promise => { + const handleRename = async (origProject: commonStorage.Project, newUserVisibleName: string): Promise => { if (!props.storage) { return; } - const newProjectName = commonStorage.classNameToModuleName(newName); - try { - await props.storage.renameProject( - origProject.projectName, - newProjectName + await commonStorage.renameProject( + props.storage, + origProject, + newUserVisibleName ); await loadProjects(props.storage); } catch (error) { @@ -107,18 +106,16 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac }; /** Handles copying a project. */ - const handleCopy = async (origProject: commonStorage.Project, newName: string): Promise => { + const handleCopy = async (origProject: commonStorage.Project, newUserVisibleName: string): Promise => { if (!props.storage) { return; } - // - const newProjectName = commonStorage.classNameToModuleName(newName); - try { - await props.storage.copyProject( - origProject.projectName, - newProjectName + await commonStorage.copyProject( + props.storage, + origProject, + newUserVisibleName ); await loadProjects(props.storage); } catch (error) { @@ -131,18 +128,15 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac /** Handles adding a new project. */ const handleAddNewItem = async (): Promise => { - const trimmedName = newItemName.trim(); - if (!trimmedName || !props.storage) { + const newUserVisibleName = newItemName.trim(); + if (!newUserVisibleName || !props.storage) { return; } - const newProjectName = commonStorage.classNameToModuleName(trimmedName); - const robotContent = commonStorage.newRobotContent(newProjectName); - try { - await props.storage.createProject( - newProjectName, - robotContent + await commonStorage.createProject( + props.storage, + newUserVisibleName ); } catch (e) { console.error('Failed to create a new project:', e); @@ -177,7 +171,7 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac } try { - await props.storage.deleteProject(record.projectName); + await commonStorage.deleteProject(props.storage, record); } catch (e) { console.error('Failed to delete the project:', e); props.setAlertErrorMessage('Failed to delete the project.'); @@ -193,25 +187,25 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac /** Opens the rename modal for a specific project. */ const openRenameModal = (record: commonStorage.Project): void => { setCurrentRecord(record); - setName(record.robot.className); + setName(record.userVisibleName); setRenameModalOpen(true); }; /** Opens the copy modal for a specific project. */ const openCopyModal = (record: commonStorage.Project): void => { setCurrentRecord(record); - setName(record.robot.className + COPY_SUFFIX); + setName(record.userVisibleName + COPY_SUFFIX); setCopyModalOpen(true); }; /** Gets the rename modal title. */ const getRenameModalTitle = (): string => { - return `Rename Project: ${currentRecord ? currentRecord.robot.className : ''}`; + return `Rename Project: ${currentRecord ? currentRecord.userVisibleName : ''}`; }; /** Gets the copy modal title. */ const getCopyModalTitle = (): string => { - return `Copy Project: ${currentRecord ? currentRecord.robot.className : ''}`; + return `Copy Project: ${currentRecord ? currentRecord.userVisibleName : ''}`; }; /** Creates the container style object. */ @@ -231,8 +225,8 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac const columns: Antd.TableProps['columns'] = [ { title: 'Name', - dataIndex: 'robot.className', - key: 'robot.className', + dataIndex: 'userVisibleName', + key: 'userVisibleName', ellipsis: { showTitle: false, }, @@ -275,7 +269,7 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac {allProjects.length > 1 && ( handleDeleteProject(record)} okText={t('Delete')} @@ -392,7 +386,7 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac columns={columns} dataSource={allProjects} - rowKey="robot.className" + rowKey="userVisibleName" size="small" pagination={allProjects.length > DEFAULT_PAGE_SIZE ? { pageSize: DEFAULT_PAGE_SIZE, diff --git a/src/reactComponents/ProjectNameComponent.tsx b/src/reactComponents/ProjectNameComponent.tsx index f21ade8b..3856a01a 100644 --- a/src/reactComponents/ProjectNameComponent.tsx +++ b/src/reactComponents/ProjectNameComponent.tsx @@ -58,18 +58,18 @@ export default function ProjectNameComponent(props: ProjectNameComponentProps): /** Handles adding a new item with validation. */ const handleAddNewItem = (): void => { - const trimmedName = props.newItemName.trim(); - if (!trimmedName || !props.projects) { + const newUserVisibleName = props.newItemName.trim(); + if (!newUserVisibleName || !props.projects) { return; } - if (!commonStorage.isValidClassName(trimmedName)) { - showError(trimmedName + INVALID_NAME_MESSAGE_SUFFIX); + if (!commonStorage.isValidClassName(newUserVisibleName)) { + showError(newUserVisibleName + INVALID_NAME_MESSAGE_SUFFIX); return; } - if (props.projects.some((project) => project.robot.className === trimmedName)) { - showError(DUPLICATE_NAME_MESSAGE_PREFIX + trimmedName + DUPLICATE_NAME_MESSAGE_SUFFIX); + if (props.projects.some((project) => project.userVisibleName === newUserVisibleName)) { + showError(DUPLICATE_NAME_MESSAGE_PREFIX + newUserVisibleName + DUPLICATE_NAME_MESSAGE_SUFFIX); return; } diff --git a/src/reactComponents/Tabs.tsx b/src/reactComponents/Tabs.tsx index 2ace6589..368c6082 100644 --- a/src/reactComponents/Tabs.tsx +++ b/src/reactComponents/Tabs.tsx @@ -84,7 +84,8 @@ export function Component(props: TabsProps): React.JSX.Element { /** Handles tab change and updates current module. */ const handleTabChange = (key: string): void => { if (props.project) { - props.setCurrentModule(commonStorage.findModuleByModulePath(props.project, key)); + const modulePath = key; + props.setCurrentModule(commonStorage.findModuleByModulePath(props.project, modulePath)); setActiveKey(key); } }; @@ -101,7 +102,8 @@ export function Component(props: TabsProps): React.JSX.Element { return; } - const module = commonStorage.findModuleByModulePath(props.project, key); + const modulePath = key; + const module = commonStorage.findModuleByModulePath(props.project, modulePath); if (!module) { return; } @@ -156,28 +158,30 @@ export function Component(props: TabsProps): React.JSX.Element { }; /** Handles renaming a module tab. */ - const handleRename = async (key: string, newName: string): Promise => { + const handleRename = async (key: string, newClassName: string): Promise => { if (!props.storage || !props.project) { return; } + const oldModulePath = key; + try { - const newPath = await commonStorage.renameModuleInProject( + const newModulePath = await commonStorage.renameModuleInProject( props.storage, props.project, - newName, - key + newClassName, + oldModulePath, ); const newTabs = props.tabList.map((tab) => { if (tab.key === key) { - return { ...tab, title: newName, key: newPath }; + return { ...tab, title: newClassName, key: newModulePath }; } return tab; }); props.setTabList(newTabs); - setActiveKey(newPath); + setActiveKey(newModulePath); triggerProjectUpdate(); } catch (error) { console.error('Error renaming module:', error); @@ -188,17 +192,19 @@ export function Component(props: TabsProps): React.JSX.Element { }; /** Handles copying a module tab. */ - const handleCopy = async (key: string, newName: string): Promise => { + const handleCopy = async (key: string, newClassName: string): Promise => { if (!props.storage || !props.project) { return; } + const oldModulePath = key; + try { - const newPath = await commonStorage.copyModuleInProject( + const newModulePath = await commonStorage.copyModuleInProject( props.storage, props.project, - newName, - key + newClassName, + oldModulePath, ); const newTabs = [...props.tabList]; @@ -210,9 +216,9 @@ export function Component(props: TabsProps): React.JSX.Element { return; } - newTabs.push({ key: newPath, title: newName, type: originalTab.type }); + newTabs.push({ key: newModulePath, title: newClassName, type: originalTab.type }); props.setTabList(newTabs); - setActiveKey(newPath); + setActiveKey(newModulePath); triggerProjectUpdate(); } catch (error) { console.error('Error copying module:', error); diff --git a/src/storage/client_side_storage.ts b/src/storage/client_side_storage.ts index 65a85239..5f448775 100644 --- a/src/storage/client_side_storage.ts +++ b/src/storage/client_side_storage.ts @@ -138,7 +138,8 @@ class ClientSideStorage implements commonStorage.Storage { if (cursor) { const value = cursor.value; const path = value.path; - const moduleType = value.type; + // Before PR #143, robot modules were stored with the type 'project'. + const moduleType = (value.type == 'project') ? commonStorage.MODULE_TYPE_ROBOT : value.type; const moduleName = commonStorage.getModuleName(path); const module: commonStorage.Module = { modulePath: path, @@ -146,7 +147,7 @@ class ClientSideStorage implements commonStorage.Storage { projectName: commonStorage.getProjectName(path), moduleName: moduleName, dateModifiedMillis: value.dateModifiedMillis, - className: commonStorage.moduleNameToClassName(moduleName), + className: commonStorage.getClassNameForModule(moduleType, moduleName), } if (moduleType === commonStorage.MODULE_TYPE_ROBOT) { const robot: commonStorage.Robot = { @@ -154,6 +155,7 @@ class ClientSideStorage implements commonStorage.Storage { }; const project: commonStorage.Project = { projectName: moduleName, + userVisibleName: commonStorage.snakeCaseToPascalCase(moduleName), robot: robot, mechanisms: [], opModes: [], @@ -332,7 +334,8 @@ class ClientSideStorage implements commonStorage.Storage { if (cursor) { const value = cursor.value; const path = value.path; - const moduleType = value.type; + // Before PR #143, robot modules were stored with the type 'project'. + const moduleType = (value.type == 'project') ? commonStorage.MODULE_TYPE_ROBOT : value.type; if (commonStorage.getProjectName(path) === oldProjectName) { let newPath; if (moduleType === commonStorage.MODULE_TYPE_ROBOT) { diff --git a/src/storage/common_storage.ts b/src/storage/common_storage.ts index 12088382..5d729288 100644 --- a/src/storage/common_storage.ts +++ b/src/storage/common_storage.ts @@ -47,7 +47,8 @@ export type Mechanism = Module; export type OpMode = Module; export type Project = { - projectName: string, + projectName: string, // snake_case + userVisibleName: string, // PascalCase robot: Robot, mechanisms: Mechanism[] opModes: OpMode[], @@ -63,6 +64,8 @@ export const MODULE_TYPE_ROBOT = 'robot'; export const MODULE_TYPE_MECHANISM = 'mechanism'; export const MODULE_TYPE_OPMODE = 'opmode'; +export const ROBOT_CLASS_NAME = 'Robot'; + export const MODULE_NAME_PLACEHOLDER = '%module_name%'; const DELIMITER_PREFIX = 'BlocksContent'; @@ -96,16 +99,66 @@ export interface Storage { uploadProject(projectName: string, blobUrl: string): Promise; } +/** + * Creates a new project. + * @param storage The storage interface to use for creating the project. + * @param proposedUserVisibleName The name for the new project. + * @returns A promise that resolves when the project has been created. + */ +export async function createProject( + storage: Storage, proposedUserVisibleName: string): Promise { + const newProjectName = pascalCaseToSnakeCase(proposedUserVisibleName); + const robotContent = newRobotContent(newProjectName); + await storage.createProject(newProjectName, robotContent); +} + +/** + * Renames a project. + * @param storage The storage interface to use for renaming the project. + * @param project The project to rename + * @param proposedUserVisibleName The new name for the project. + * @returns A promise that resolves when the project has been renamed. + */ +export async function renameProject( + storage: Storage, project: Project, proposedUserVisibleName: string): Promise { + const newProjectName = pascalCaseToSnakeCase(proposedUserVisibleName); + await storage.renameProject(project.projectName, newProjectName); +} + +/** + * Copies a project. + * @param storage The storage interface to use for copying the project. + * @param project The project to copy + * @param proposedUserVisibleName The name for the new project. + * @returns A promise that resolves when the project has been copied. + */ +export async function copyProject( + storage: Storage, project: Project, proposedUserVisibleName: string): Promise { + const newProjectName = pascalCaseToSnakeCase(proposedUserVisibleName); + await storage.copyProject(project.projectName, newProjectName); +} + +/** + * Deletes a project. + * @param storage The storage interface to use for deleting the project. + * @param project The project to delete. + * @returns A promise that resolves when the project has been deleted. + */ +export async function deleteProject( + storage: Storage, project: Project): Promise { + await storage.deleteProject(project.projectName); +} + /** * Adds a new module to the project. * @param storage The storage interface to use for creating the module. * @param project The project to add the module to. * @param moduleType The type of the module (e.g., 'mechanism', 'opmode'). - * @param className The name of the class. + * @param newClassName The name of the class. */ export async function addModuleToProject( - storage: Storage, project: Project, moduleType: string, className: string): Promise { - let newModuleName = classNameToModuleName(className); + storage: Storage, project: Project, moduleType: string, newClassName: string): Promise { + let newModuleName = pascalCaseToSnakeCase(newClassName); let newModulePath = makeModulePath(project.projectName, newModuleName); if (moduleType === MODULE_TYPE_MECHANISM) { @@ -116,7 +169,7 @@ export async function addModuleToProject( moduleType: MODULE_TYPE_MECHANISM, projectName: project.projectName, moduleName: newModuleName, - className: className + className: newClassName } as Mechanism); } else if (moduleType === MODULE_TYPE_OPMODE) { const opModeContent = newOpModeContent(project.projectName, newModuleName); @@ -126,7 +179,7 @@ export async function addModuleToProject( moduleType: MODULE_TYPE_OPMODE, projectName: project.projectName, moduleName: newModuleName, - className: className + className: newClassName } as OpMode); } } @@ -156,37 +209,37 @@ export async function removeModuleFromProject( * Renames a module in the project. * @param storage The storage interface to use for renaming the module. * @param project The project containing the module to rename. - * @param proposedName The new name for the module. - * @param oldModuleName The current name of the module. + * @param proposedClassName The new class name for the module. + * @param oldModulePath The current path of the module. * @returns A promise that resolves when the module has been renamed. */ export async function renameModuleInProject( - storage: Storage, project: Project, proposedName: string, oldModulePath: string): Promise { + storage: Storage, project: Project, proposedClassName: string, oldModulePath: string): Promise { const module = findModuleByModulePath(project, oldModulePath); if (module) { if (module.moduleType == MODULE_TYPE_ROBOT) { throw new Error('Renaming the robot module is not allowed.'); } - const newModuleName = classNameToModuleName(proposedName); + const newModuleName = pascalCaseToSnakeCase(proposedClassName); const newModulePath = makeModulePath(project.projectName, newModuleName); await storage.renameModule(module.moduleType, project.projectName, module.moduleName, newModuleName); module.modulePath = newModulePath; module.moduleName = newModuleName; - module.className = proposedName; + module.className = proposedClassName; if (module.moduleType === MODULE_TYPE_MECHANISM) { const mechanism = project.mechanisms.find(m => m.modulePath === module.modulePath); if (mechanism) { mechanism.modulePath = newModulePath; mechanism.moduleName = newModuleName; - mechanism.className = proposedName; + mechanism.className = proposedClassName; } } else if (module.moduleType === MODULE_TYPE_OPMODE) { const opMode = project.opModes.find(o => o.modulePath === module.modulePath); if (opMode) { opMode.modulePath = newModulePath; opMode.moduleName = newModuleName; - opMode.className = proposedName; + opMode.className = proposedClassName; } return newModulePath } @@ -197,18 +250,18 @@ export async function renameModuleInProject( * 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 proposedName The new name for the module. + * @param proposedClassName The new name for the module. * @param oldModuleName The current name of the module. * @returns A promise that resolves when the module has been copied. */ export async function copyModuleInProject( - storage: Storage, project: Project, proposedName: string, oldModulePath: string): Promise { + storage: Storage, project: Project, proposedClassName: string, oldModulePath: string): Promise { const module = findModuleByModulePath(project, oldModulePath); if (module) { if (module.moduleType == MODULE_TYPE_ROBOT) { throw new Error('Copying the robot module is not allowed.'); } - const newModuleName = classNameToModuleName(proposedName); + const newModuleName = pascalCaseToSnakeCase(proposedClassName); const newModulePath = makeModulePath(project.projectName, newModuleName); await storage.copyModule(module.moduleType, project.projectName, module.moduleName, newModuleName); @@ -218,7 +271,7 @@ export async function copyModuleInProject( moduleType: MODULE_TYPE_MECHANISM, projectName: project.projectName, moduleName: newModuleName, - className: proposedName + className: proposedClassName } as Mechanism); } else if (module.moduleType === MODULE_TYPE_OPMODE) { project.opModes.push({ @@ -226,7 +279,7 @@ export async function copyModuleInProject( moduleType: MODULE_TYPE_OPMODE, projectName: project.projectName, moduleName: newModuleName, - className: proposedName + className: proposedClassName } as OpMode); } return newModulePath; @@ -237,19 +290,19 @@ export async function copyModuleInProject( /** * Checks if the proposed class name is valid and does not conflict with existing names in the project. * @param project The project to check against. - * @param proposedName The proposed class name to validate. + * @param proposedClassName The proposed class name to validate. * @returns An object containing a boolean `ok` indicating if the name is valid, and an `error` message if it is not. */ -export function isClassNameOk(project: Project, proposedName: string) { +export function isClassNameOk(project: Project, proposedClassName: string) { let ok = true; let error = ''; - if (!isValidClassName(proposedName)) { + if (!isValidClassName(proposedClassName)) { ok = false; - error = proposedName + ' is not a valid name. Please enter a different name.'; - } else if (findModuleByClassName(project, proposedName) != null) { + error = proposedClassName + ' is not a valid name. Please enter a different name.'; + } else if (findModuleByClassName(project, proposedClassName) != null) { ok = false; - error = 'Another Mechanism or OpMode is already named ' + proposedName + '. Please enter a different name.' + error = 'Another Mechanism or OpMode is already named ' + proposedClassName + '. Please enter a different name.' } return { @@ -345,7 +398,7 @@ export function isValidClassName(name: string): boolean { /** * Returns the module name (snake_case) for the given class name (PascalCase). */ -export function classNameToModuleName(className: string): string { +export function pascalCaseToSnakeCase(className: string): string { let moduleName = ''; for (let i = 0; i < className.length; i++) { const char = className.charAt(i); @@ -364,7 +417,7 @@ export function classNameToModuleName(className: string): string { /** * Returns the class name (PascalCase) for the given module name (snake_case). */ -export function moduleNameToClassName(moduleName: string): string { +export function snakeCaseToPascalCase(moduleName: string): string { let className = ''; let nextCharUpper = true; for (let i = 0; i < moduleName.length; i++) { @@ -402,7 +455,7 @@ export function makeRobotPath(projectName: string): string { } /** - * Returns the project name for given module path. + * Returns the project path for given module path. */ export function getProjectName(modulePath: string): string { const regex = new RegExp('^([a-z_A-Z][a-z0-9_]*)/([a-z_A-Z][a-z0-9_]*).py$'); @@ -449,13 +502,13 @@ function startingBlocksToModuleContent( * Returns the robot module content for a new Project. */ export function newRobotContent(projectName: string): string { - const module: Module = { + const module: Robot = { modulePath: makeRobotPath(projectName), moduleType: MODULE_TYPE_ROBOT, projectName: projectName, moduleName: projectName, dateModifiedMillis: 0, - className: moduleNameToClassName(projectName), + className: ROBOT_CLASS_NAME, }; return startingBlocksToModuleContent(module, startingRobotBlocks); @@ -471,7 +524,7 @@ export function newMechanismContent(projectName: string, mechanismName: string): projectName: projectName, moduleName: mechanismName, dateModifiedMillis: 0, - className: moduleNameToClassName(mechanismName), + className: snakeCaseToPascalCase(mechanismName), }; return startingBlocksToModuleContent(module, startingMechanismBlocks); @@ -487,7 +540,7 @@ export function newOpModeContent(projectName: string, opModeName: string): strin projectName: projectName, moduleName: opModeName, dateModifiedMillis: 0, - className: moduleNameToClassName(opModeName), + className: snakeCaseToPascalCase(opModeName), }; return startingBlocksToModuleContent(module, startingOpModeBlocks); @@ -640,6 +693,12 @@ export async function produceDownloadProjectBlob( return blobUrl; } +export function getClassNameForModule(moduleType: string, moduleName: string) { + return (moduleType == MODULE_TYPE_ROBOT) + ? ROBOT_CLASS_NAME + : snakeCaseToPascalCase(moduleName); +} + /** * Process the module content so it can be downloaded. */ @@ -661,7 +720,7 @@ function _processModuleContentForDownload( projectName: projectName, moduleName: moduleName, dateModifiedMillis: 0, - className: moduleNameToClassName(moduleName), + className: getClassName(moduleType, moduleName), }; // Clear out the python content and exported blocks. @@ -763,7 +822,7 @@ export function _processUploadedModule( projectName: projectName, moduleName: moduleName, dateModifiedMillis: 0, - className: moduleNameToClassName(moduleName), + className: snakeCaseToPascalCase(moduleName), }; // Generate the python content and exported blocks. From 0dcfba0481ed4f024cd4c17260c88562cf3cd925 Mon Sep 17 00:00:00 2001 From: Liz Looney Date: Fri, 4 Jul 2025 15:42:21 -0700 Subject: [PATCH 3/3] Fixed a few errors and warnings shown in vscode. --- src/App.tsx | 8 ++++++-- src/blocks/utils/external_samples_data.ts | 2 +- src/reactComponents/ProjectManageModal.tsx | 1 - src/storage/client_side_storage.ts | 6 +++--- src/storage/common_storage.ts | 2 +- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index b27b1fa8..9ce3bf2c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -134,8 +134,8 @@ const App: React.FC = (): React.JSX.Element => { try { const value = await storage.fetchEntry(SHOWN_TOOLBOX_CATEGORIES_KEY, DEFAULT_TOOLBOX_CATEGORIES_JSON); - const shownCategories: string[] = JSON.parse(value); - setShownPythonToolboxCategories(new Set(shownCategories)); + const shownCategories: Set = new Set(JSON.parse(value)); + setShownPythonToolboxCategories(shownCategories); } catch (e) { console.error(TOOLBOX_FETCH_ERROR_MESSAGE); console.error(e); @@ -278,6 +278,10 @@ const App: React.FC = (): React.JSX.Element => { initializeBlocks(); }, []); + React.useEffect(() => { + initializeShownPythonToolboxCategories(); + }, [storage]); + // Update generator context and load module blocks when current module changes React.useEffect(() => { if (generatorContext.current) { diff --git a/src/blocks/utils/external_samples_data.ts b/src/blocks/utils/external_samples_data.ts index 3f796aa9..66f7cfd7 100644 --- a/src/blocks/utils/external_samples_data.ts +++ b/src/blocks/utils/external_samples_data.ts @@ -19,7 +19,7 @@ * @author lizlooney@google.com (Liz Looney) */ -import { PythonData, ClassData } from './python_json_types'; +import { PythonData } from './python_json_types'; import generatedExternalSamplesData from './generated/external_samples_data.json'; export const externalSamplesData = generatedExternalSamplesData as PythonData; diff --git a/src/reactComponents/ProjectManageModal.tsx b/src/reactComponents/ProjectManageModal.tsx index cf308959..cb4a3b90 100644 --- a/src/reactComponents/ProjectManageModal.tsx +++ b/src/reactComponents/ProjectManageModal.tsx @@ -18,7 +18,6 @@ /** * @author alan@porpoiseful.com (Alan Smith) */ -import {TabType} from '../types/TabType'; import * as Antd from 'antd'; import * as I18Next from 'react-i18next'; import * as React from 'react'; diff --git a/src/storage/client_side_storage.ts b/src/storage/client_side_storage.ts index 5f448775..ec18a595 100644 --- a/src/storage/client_side_storage.ts +++ b/src/storage/client_side_storage.ts @@ -402,7 +402,7 @@ class ClientSideStorage implements commonStorage.Storage { throw new Error('Renaming the robot module is not allowed. Call renameProject to rename the project.'); } return this._renameOrCopyModule( - moduleType, projectName, oldModuleName, newModuleName, false); + projectName, oldModuleName, newModuleName, false); } async copyModule( @@ -412,11 +412,11 @@ class ClientSideStorage implements commonStorage.Storage { throw new Error('Copying the robot module is not allowed. Call copyProject to rename the project.'); } return this._renameOrCopyModule( - moduleType, projectName, oldModuleName, newModuleName, true); + projectName, oldModuleName, newModuleName, true); } private async _renameOrCopyModule( - moduleType: string, projectName: string, + projectName: string, oldModuleName: string, newModuleName: string, copy: boolean): Promise { return new Promise((resolve, reject) => { diff --git a/src/storage/common_storage.ts b/src/storage/common_storage.ts index 5d729288..7efcb617 100644 --- a/src/storage/common_storage.ts +++ b/src/storage/common_storage.ts @@ -720,7 +720,7 @@ function _processModuleContentForDownload( projectName: projectName, moduleName: moduleName, dateModifiedMillis: 0, - className: getClassName(moduleType, moduleName), + className: getClassNameForModule(moduleType, moduleName), }; // Clear out the python content and exported blocks.