Skip to content

Commit 8114499

Browse files
committed
Save modules with names like ProjectName.ClassName.json.
Save modules with multi-line formatted json. Removed moduleName field from Module (and Robot, Mechanism, and OpMode). Removed userVisibleName from Project. Renamed ModuleNameComponent.tsx to ClassNameComponent.tsx. In Menu.tsx, added handleDownload and handleUpload. I didn't add the actual UI element (menu or button, etc) to trigger the handlers.
1 parent db01671 commit 8114499

File tree

13 files changed

+312
-238
lines changed

13 files changed

+312
-238
lines changed

src/blocks/mrc_mechanism.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,9 @@ const MECHANISM = {
186186
if (this.getFieldValue(FIELD_TYPE) !== foundMechanism.className) {
187187
this.setFieldValue(foundMechanism.className, FIELD_TYPE);
188188
}
189-
// If the mechanism module name has changed, update this block.
190-
if (this.mrcImportModule !== foundMechanism.moduleName) {
191-
this.mrcImportModule = foundMechanism.moduleName;
189+
const importModule = commonStorage.pascalCaseToSnakeCase(foundMechanism.className);
190+
if (this.mrcImportModule !== importModule) {
191+
this.mrcImportModule = importModule;
192192
}
193193
this.mrcParameters = [];
194194
components.forEach(component => {
@@ -245,12 +245,10 @@ export const pythonFromBlock = function (
245245

246246
export function createMechanismBlock(
247247
mechanism: commonStorage.Mechanism, components: commonStorage.Component[]): toolboxItems.Block {
248-
const lastDot = mechanism.className.lastIndexOf('.');
249-
const mechanismName = (
250-
'my_' +
251-
commonStorage.pascalCaseToSnakeCase(mechanism.className.substring(lastDot + 1)));
248+
const snakeCaseName = commonStorage.pascalCaseToSnakeCase(mechanism.className);
249+
const mechanismName = 'my_' + snakeCaseName;
252250
const extraState: MechanismExtraState = {
253-
importModule: mechanism.moduleName,
251+
importModule: snakeCaseName,
254252
parameters: [],
255253
};
256254
const inputs: {[key: string]: any} = {};

src/blocks/utils/python.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,8 @@ export function getOutputCheck(type: string): string {
268268
// This is a legal name for python methods and variables.
269269
export function getLegalName(proposedName: string, existingNames: string[]){
270270
let newName = proposedName.trim().replace(' ', '_');
271+
272+
// TODO: Allow the user to put numbers in the name.
271273

272274
if (!/^[A-Za-z_]/.test(newName)){
273275
newName = "_" + newName;

src/reactComponents/AddTabDialog.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import * as Antd from 'antd';
2424
import * as I18Next from 'react-i18next';
2525
import * as React from 'react';
2626
import * as commonStorage from '../storage/common_storage';
27-
import ModuleNameComponent from './ModuleNameComponent';
27+
import ClassNameComponent from './ClassNameComponent';
2828

2929
/** Represents a module item in the dialog. */
3030
interface Module {
@@ -306,7 +306,7 @@ export default function AddTabDialog(props: AddTabDialogProps) {
306306
</Antd.Radio.Button>
307307
</Antd.Radio.Group>
308308

309-
<ModuleNameComponent
309+
<ClassNameComponent
310310
tabType={tabType}
311311
newItemName={newItemName}
312312
setNewItemName={setNewItemName}

src/reactComponents/ModuleNameComponent.tsx renamed to src/reactComponents/ClassNameComponent.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ import * as I18Next from 'react-i18next';
2525
import * as React from 'react';
2626
import * as commonStorage from '../storage/common_storage';
2727

28-
/** Props for the ModuleNameComponent. */
29-
interface ModuleNameComponentProps {
28+
/** Props for the ClassNameComponent. */
29+
interface ClassNameComponentProps {
3030
tabType: TabType;
3131
newItemName: string;
3232
setNewItemName: (name: string) => void;
@@ -46,10 +46,10 @@ const INPUT_WIDTH_FULL = '100%';
4646
const ERROR_ALERT_MARGIN_TOP = 8;
4747

4848
/**
49-
* Component for entering and validating module names.
49+
* Component for entering and validating class names.
5050
* Provides input validation, error display, and automatic capitalization.
5151
*/
52-
export default function ModuleNameComponent(props: ModuleNameComponentProps): React.JSX.Element {
52+
export default function ClassNameComponent(props: ClassNameComponentProps): React.JSX.Element {
5353
const {t} = I18Next.useTranslation();
5454
const [alertErrorMessage, setAlertErrorMessage] = React.useState('');
5555
const [alertErrorVisible, setAlertErrorVisible] = React.useState(false);

src/reactComponents/FileManageModal.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import * as I18Next from 'react-i18next';
2424
import * as React from 'react';
2525
import * as commonStorage from '../storage/common_storage';
2626
import {EditOutlined, DeleteOutlined, CopyOutlined} from '@ant-design/icons';
27-
import ModuleNameComponent from './ModuleNameComponent';
27+
import ClassNameComponent from './ClassNameComponent';
2828

2929
/** Represents a module in the file management system. */
3030
interface Module {
@@ -347,7 +347,7 @@ export default function FileManageModal(props: FileManageModalProps) {
347347
cancelText={t('Cancel')}
348348
>
349349
{currentRecord && (
350-
<ModuleNameComponent
350+
<ClassNameComponent
351351
tabType={currentRecord.type}
352352
newItemName={name}
353353
setNewItemName={setName}
@@ -376,7 +376,7 @@ export default function FileManageModal(props: FileManageModalProps) {
376376
cancelText={t('Cancel')}
377377
>
378378
{currentRecord && (
379-
<ModuleNameComponent
379+
<ClassNameComponent
380380
tabType={currentRecord.type}
381381
newItemName={name}
382382
setNewItemName={setName}
@@ -409,7 +409,7 @@ export default function FileManageModal(props: FileManageModalProps) {
409409
borderRadius: '6px',
410410
padding: '12px',
411411
}}>
412-
<ModuleNameComponent
412+
<ClassNameComponent
413413
tabType={props.moduleType}
414414
newItemName={newItemName}
415415
setNewItemName={setNewItemName}

src/reactComponents/Header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export default function Header(props: HeaderProps): React.JSX.Element {
7878

7979
/** Gets the project name or fallback text. */
8080
const getProjectName = (): string => {
81-
return props.project?.userVisibleName || 'No Project Selected';
81+
return props.project?.projectName || 'No Project Selected';
8282
};
8383

8484
return (

src/reactComponents/Menu.tsx

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,92 @@ export function Component(props: MenuProps): React.JSX.Element {
322322
}
323323
};
324324

325+
// TODO: Add menu or button or something for the download action.
326+
/** Handles the download action to generate and download json files. */
327+
const handleDownload = async (): Promise<void> => {
328+
if (!props.project || !props.storage) {
329+
return;
330+
}
331+
332+
try {
333+
const blobUrl = await props.storage.downloadProject(props.project.projectName);
334+
const filename = props.project.projectName + commonStorage.UPLOAD_DOWNLOAD_FILE_EXTENSION;
335+
336+
// Create a temporary link to download the file
337+
const link = document.createElement('a');
338+
link.href = blobUrl;
339+
link.download = filename;
340+
document.body.appendChild(link);
341+
link.click();
342+
document.body.removeChild(link);
343+
344+
// Clean up the blob URL
345+
URL.revokeObjectURL(blobUrl);
346+
} catch (error) {
347+
console.error('Failed to download project:', error);
348+
props.setAlertErrorMessage(t('DOWNLOAD_FAILED') || 'Failed to download project');
349+
}
350+
}
351+
352+
// TODO: Add menu or button or something for the upload action.
353+
/** Handles the upload action to upload a previously downloaded project. */
354+
const handleUpload = (): Antd.UploadProps => {
355+
if (!props.storage) {
356+
return;
357+
}
358+
359+
const uploadProps: Antd.UploadProps = {
360+
accept: commonStorage.UPLOAD_DOWNLOAD_FILE_EXTENSION,
361+
beforeUpload: (file) => {
362+
const isBlocks = file.name.endsWith(commonStorage.UPLOAD_DOWNLOAD_FILE_EXTENSION)
363+
if (!isBlocks) {
364+
// TODO: i18n
365+
props.setAlertErrorMessage(file.name + ' is not a blocks file');
366+
setAlertErrorVisible(true);
367+
return false;
368+
}
369+
return isBlocks || Antd.Upload.LIST_IGNORE;
370+
},
371+
onChange: (info) => {
372+
},
373+
customRequest: ({ file, onSuccess, onError }) => {
374+
const reader = new FileReader();
375+
reader.onload = (event) => {
376+
const dataUrl = event.target.result;
377+
const existingProjectNames: string[] = [];
378+
projects.forEach(project => {
379+
existingProjectNames.push(project.projectName);
380+
});
381+
const uploadProjectName = commonStorage.makeUploadProjectName(file.name, existingProjectNames);
382+
if (props.storage) {
383+
props.storage.uploadProject(
384+
uploadProjectName, dataUrl,
385+
(success: boolean, errorMessage: string) => {
386+
if (success) {
387+
// TODO: i18n
388+
onSuccess('Upload successful');
389+
// TODO: Select the project that was just uploaded.
390+
} else {
391+
onError(errorMessage);
392+
// TODO: i18n
393+
props.setAlertErrorMessage('Unable to upload the project');
394+
}
395+
}
396+
);
397+
}
398+
};
399+
reader.onerror = (error) => {
400+
onError(error);
401+
// TODO: i18n
402+
props.setAlertErrorMessage(t('UPLOAD_FAILED') || 'Failed to upload project');
403+
setAlertErrorVisible(true);
404+
};
405+
reader.readAsDataURL(file);
406+
},
407+
};
408+
return uploadProps;
409+
};
410+
325411
/** Handles closing the file management modal. */
326412
const handleFileModalClose = (): void => {
327413
console.log('Modal onCancel called');

src/reactComponents/ProjectManageModal.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac
7474
const projects = await storage.listProjects();
7575

7676
// Sort projects alphabetically by name
77-
projects.sort((a, b) => a.userVisibleName.localeCompare(b.userVisibleName));
77+
projects.sort((a, b) => a.projectName.localeCompare(b.projectName));
7878
setAllProjects(projects);
7979

8080
if (projects.length > 0 && props.noProjects) {
@@ -186,25 +186,25 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac
186186
/** Opens the rename modal for a specific project. */
187187
const openRenameModal = (record: commonStorage.Project): void => {
188188
setCurrentRecord(record);
189-
setName(record.userVisibleName);
189+
setName(record.projectName);
190190
setRenameModalOpen(true);
191191
};
192192

193193
/** Opens the copy modal for a specific project. */
194194
const openCopyModal = (record: commonStorage.Project): void => {
195195
setCurrentRecord(record);
196-
setName(record.userVisibleName + COPY_SUFFIX);
196+
setName(record.projectName + COPY_SUFFIX);
197197
setCopyModalOpen(true);
198198
};
199199

200200
/** Gets the rename modal title. */
201201
const getRenameModalTitle = (): string => {
202-
return `Rename Project: ${currentRecord ? currentRecord.userVisibleName : ''}`;
202+
return `Rename Project: ${currentRecord ? currentRecord.projectName : ''}`;
203203
};
204204

205205
/** Gets the copy modal title. */
206206
const getCopyModalTitle = (): string => {
207-
return `Copy Project: ${currentRecord ? currentRecord.userVisibleName : ''}`;
207+
return `Copy Project: ${currentRecord ? currentRecord.projectName : ''}`;
208208
};
209209

210210
/** Creates the container style object. */
@@ -224,8 +224,8 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac
224224
const columns: Antd.TableProps<commonStorage.Project>['columns'] = [
225225
{
226226
title: 'Name',
227-
dataIndex: 'userVisibleName',
228-
key: 'userVisibleName',
227+
dataIndex: 'projectName',
228+
key: 'projectName',
229229
ellipsis: {
230230
showTitle: false,
231231
},
@@ -268,7 +268,7 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac
268268
{allProjects.length > 1 && (
269269
<Antd.Tooltip title={t('Delete')}>
270270
<Antd.Popconfirm
271-
title={`Delete ${record.userVisibleName}?`}
271+
title={`Delete ${record.projectName}?`}
272272
description="This action cannot be undone."
273273
onConfirm={() => handleDeleteProject(record)}
274274
okText={t('Delete')}
@@ -385,7 +385,7 @@ export default function ProjectManageModal(props: ProjectManageModalProps): Reac
385385
<Antd.Table<commonStorage.Project>
386386
columns={columns}
387387
dataSource={allProjects}
388-
rowKey="userVisibleName"
388+
rowKey="projectName"
389389
size="small"
390390
pagination={allProjects.length > DEFAULT_PAGE_SIZE ? {
391391
pageSize: DEFAULT_PAGE_SIZE,

src/reactComponents/ProjectNameComponent.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,18 @@ export default function ProjectNameComponent(props: ProjectNameComponentProps):
5858

5959
/** Handles adding a new item with validation. */
6060
const handleAddNewItem = (): void => {
61-
const newUserVisibleName = props.newItemName.trim();
62-
if (!newUserVisibleName || !props.projects) {
61+
const newProjectName = props.newItemName.trim();
62+
if (!newProjectName || !props.projects) {
6363
return;
6464
}
6565

66-
if (!commonStorage.isValidClassName(newUserVisibleName)) {
67-
showError(newUserVisibleName + INVALID_NAME_MESSAGE_SUFFIX);
66+
if (!commonStorage.isValidClassName(newProjectName)) {
67+
showError(newProjectName + INVALID_NAME_MESSAGE_SUFFIX);
6868
return;
6969
}
7070

71-
if (props.projects.some((project) => project.userVisibleName === newUserVisibleName)) {
72-
showError(DUPLICATE_NAME_MESSAGE_PREFIX + newUserVisibleName + DUPLICATE_NAME_MESSAGE_SUFFIX);
71+
if (props.projects.some((project) => project.projectName === newProjectName)) {
72+
showError(DUPLICATE_NAME_MESSAGE_PREFIX + newProjectName + DUPLICATE_NAME_MESSAGE_SUFFIX);
7373
return;
7474
}
7575

src/reactComponents/Tabs.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import {
3030
CloseCircleOutlined,
3131
} from '@ant-design/icons';
3232
import AddTabDialog from './AddTabDialog';
33-
import ModuleNameComponent from './ModuleNameComponent';
33+
import ClassNameComponent from './ClassNameComponent';
3434
import { TabType, TabTypeUtils } from '../types/TabType';
3535

3636
/** Represents a tab item in the tab bar. */
@@ -375,7 +375,7 @@ export function Component(props: TabsProps): React.JSX.Element {
375375
cancelText={t('Cancel')}
376376
>
377377
{currentTab && (
378-
<ModuleNameComponent
378+
<ClassNameComponent
379379
tabType={currentTab.type}
380380
newItemName={name}
381381
setNewItemName={setName}
@@ -405,7 +405,7 @@ export function Component(props: TabsProps): React.JSX.Element {
405405
cancelText={t('Cancel')}
406406
>
407407
{currentTab && (
408-
<ModuleNameComponent
408+
<ClassNameComponent
409409
tabType={currentTab.type}
410410
newItemName={name}
411411
setNewItemName={setName}

0 commit comments

Comments
 (0)