Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,4 @@ WARNING! This is not ready for use and is under heavy development of basic featu
7. Make sure any python backends are running on your PC.

## Known Issues
In addition to those listed in the issues on github, there are some major pieces not ready yet.
1. Deploy doesn't connect to backend
1. No ability to download or upload blocks code
Please check the issues on github for known issues.
5 changes: 5 additions & 0 deletions src/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
"DOWNLOAD_FAILED": "Failed to download project.",
"UPLOAD_FILE_NOT_BLOCKS": "{{filename}} is not a blocks file.",
"UPLOAD_FAILED": "Failed to upload project.",
"PROJECT_NAME_CONFLICT": "Project Name Conflict",
"PROJECT_NAME_EXISTS": "A project named '{{projectName}}' already exists. Please choose a different name.",
"UPLOAD_SUCCESS": "Project uploaded successfully as '{{projectName}}'.",
"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.",
Expand All @@ -28,6 +31,8 @@
"PROJECTS": "Projects",
"SAVE": "Save",
"DEPLOY": "Deploy",
"DOWNLOAD": "Download",
"UPLOAD": "Upload",
"MANAGE": "Manage",
"EXPLORER": "Explorer",
"ROBOT": "Robot",
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
"DOWNLOAD_FAILED": "Error al descargar el proyecto.",
"UPLOAD_FILE_NOT_BLOCKS": "{{filename}} no es un archivo de bloques.",
"UPLOAD_FAILED": "Error al cargar el proyecto.",
"PROJECT_NAME_CONFLICT": "Conflicto de Nombre de Proyecto",
"PROJECT_NAME_EXISTS": "Ya existe un proyecto llamado '{{projectName}}'. Por favor, elija un nombre diferente.",
"UPLOAD_SUCCESS": "Proyecto cargado exitosamente como '{{projectName}}'.",
"MECHANISM": "Mecanismo",
"OPMODE": "OpMode",
"class_rule_description": "No se permiten espacios en el nombre. Cada palabra en el nombre debe comenzar con una letra mayúscula.",
Expand All @@ -25,6 +28,8 @@
"PROJECTS": "Proyectos",
"SAVE": "Guardar",
"DEPLOY": "Desplegar",
"DOWNLOAD": "Descargar",
"UPLOAD": "Cargar",
"MANAGE": "Gestionar",
"EXPLORER": "Explorador",
"ROBOT": "Robot",
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/locales/he/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
"DOWNLOAD_FAILED": "הורדת הפרויקט נכשלה.",
"UPLOAD_FILE_NOT_BLOCKS": "{{filename}} אינו קובץ בלוקים.",
"UPLOAD_FAILED": "העלאת הפרויקט נכשלה.",
"PROJECT_NAME_CONFLICT": "התנגשות בשם הפרויקט",
"PROJECT_NAME_EXISTS": "פרויקט בשם '{{projectName}}' כבר קיים. אנא בחר שם אחר.",
"UPLOAD_SUCCESS": "הפרויקט הועלה בהצלחה בשם '{{projectName}}'.",
"MECHANISM": "מנגנון",
"OPMODE": "אופמוד",
"class_rule_description": "אסור שיהיו רווחים בשם. כל מילה בשם צריכה להתחיל באות גדולה.",
Expand All @@ -28,6 +31,8 @@
"PROJECTS": "פרויקטים",
"SAVE": "שמור",
"DEPLOY": "העלה לרובוט",
"DOWNLOAD": "הורד",
"UPLOAD": "העלה",
"MANAGE": "ניהול",
"EXPLORER": "סייר",
"ROBOT": "רובוט",
Expand Down
135 changes: 134 additions & 1 deletion src/reactComponents/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ function getMenuItems(t: (key: string) => string, project: storageProject.Projec
getItem(t('PROJECT'), 'project', <FolderOutlined />, [
getItem(t('SAVE'), 'save', <SaveOutlined />),
getItem(t('DEPLOY'), 'deploy'),
getItem(t('DOWNLOAD'), 'download', <DownloadOutlined />),
getItem(t('UPLOAD') + '...', 'upload', <UploadOutlined />),
]),
getItem(t('MANAGE'), 'manage', <ControlOutlined />, [
getItem(t('PROJECTS') + '...', 'manageProjects', <FolderOutlined />),
Expand Down Expand Up @@ -167,6 +169,7 @@ function getMenuItems(t: (key: string) => string, project: storageProject.Projec
*/
export function Component(props: MenuProps): React.JSX.Element {
const {t, i18n} = I18Next.useTranslation();
const [modal, contextHolder] = Antd.Modal.useModal();

const [projectNames, setProjectNames] = React.useState<string[]>([]);
const [menuItems, setMenuItems] = React.useState<MenuItem[]>([]);
Expand Down Expand Up @@ -276,6 +279,10 @@ export function Component(props: MenuProps): React.JSX.Element {
handleDeploy();
} else if (key == 'save') {
handleSave();
} else if (key == 'download') {
handleDownload();
} else if (key == 'upload') {
handleUploadClick();
} else if (key.startsWith('setlang:')) {
const lang = key.split(':')[1];
i18n.changeLanguage(lang);
Expand Down Expand Up @@ -389,6 +396,127 @@ export function Component(props: MenuProps): React.JSX.Element {

// TODO: Add UI for the upload action.
/** Handles the upload action to upload a previously downloaded project. */
const handleUploadClick = (): void => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have handleUpload (line 520). Can you update the existing handleUpload (if necessary) instead of adding this new function?

const input = document.createElement('input');
input.type = 'file';
input.accept = storageNames.UPLOAD_DOWNLOAD_FILE_EXTENSION;

input.onchange = async (e: Event) => {
const target = e.target as HTMLInputElement;
const file = target.files?.[0];

if (!file) {
return;
}

const isBlocks = file.name.endsWith(storageNames.UPLOAD_DOWNLOAD_FILE_EXTENSION);
if (!isBlocks) {
props.setAlertErrorMessage(t('UPLOAD_FILE_NOT_BLOCKS', { filename: file.name }));
return;
}

try {
const reader = new FileReader();
reader.onload = async (event) => {
if (!event.target || !props.storage) {
return;
}

const dataUrl = event.target.result as string;
const existingProjectNames: string[] = projectNames;

// Generate the initial project name
const preferredName = file.name.substring(
0, file.name.length - storageNames.UPLOAD_DOWNLOAD_FILE_EXTENSION.length);

// Smart name conflict resolution: extract base name and trailing number
// e.g., "PrSimplify2" -> base="PrSimplify", num=2
const match = preferredName.match(/^(.+?)(\d+)$/);
let uploadProjectName: string;

if (match && existingProjectNames.includes(preferredName)) {
// Name has a trailing number and conflicts - find next available
const baseName = match[1];
const startNum = parseInt(match[2], 10);
let num = startNum + 1;
while (existingProjectNames.includes(baseName + num)) {
num++;
}
uploadProjectName = baseName + num;
} else {
// No trailing number or no conflict - use standard logic
uploadProjectName = storageNames.makeUniqueName(preferredName, existingProjectNames);
}

// Check if there's a conflict (meaning we had to change the name)
if (existingProjectNames.includes(preferredName)) {
// Show a modal to let the user rename the project
let inputValue = uploadProjectName;

modal.confirm({
title: t('PROJECT_NAME_CONFLICT'),
content: (
<div>
<p>{t('PROJECT_NAME_EXISTS', { projectName: preferredName })}</p>
<Antd.Input
defaultValue={uploadProjectName}
onChange={(e) => {
inputValue = e.target.value;
}}
/>
</div>
),
okText: t('UPLOAD'),
cancelText: t('CANCEL'),
onOk: async () => {
try {
if (props.storage) {
await storageProject.uploadProject(props.storage, inputValue, dataUrl);
await fetchListOfProjectNames();
const project = await storageProject.fetchProject(props.storage, inputValue);
props.setCurrentProject(project);
await props.onProjectChanged();
Antd.message.success(t('UPLOAD_SUCCESS', { projectName: inputValue }));
}
} catch (error) {
console.error('Error uploading file:', error);
props.setAlertErrorMessage(t('UPLOAD_FAILED'));
}
},
});
} else {
// No conflict, upload directly
try {
if (props.storage) {
await storageProject.uploadProject(props.storage, uploadProjectName, dataUrl);
await fetchListOfProjectNames();
const project = await storageProject.fetchProject(props.storage, uploadProjectName);
props.setCurrentProject(project);
await props.onProjectChanged();
Antd.message.success(t('UPLOAD_SUCCESS', { projectName: uploadProjectName }));
}
} catch (error) {
console.error('Error uploading file:', error);
props.setAlertErrorMessage(t('UPLOAD_FAILED'));
}
}
};

reader.onerror = (_error) => {
console.log('Error reading file: ' + reader.error);
props.setAlertErrorMessage(t('UPLOAD_FAILED'));
};

reader.readAsDataURL(file);
} catch (error) {
console.error('Error handling upload:', error);
props.setAlertErrorMessage(t('UPLOAD_FAILED'));
}
};

input.click();
};

const handleUpload = (): Antd.UploadProps | null => {
if (!props.storage) {
return null;
Expand Down Expand Up @@ -445,8 +573,12 @@ export function Component(props: MenuProps): React.JSX.Element {
};

/** Handles closing the project management modal. */
const handleProjectModalClose = (): void => {
const handleProjectModalClose = async (): Promise<void> => {
setProjectModalOpen(false);
// Refresh project names to reflect any changes (deletions, renames, etc.)
if (props.storage) {
await fetchListOfProjectNames();
}
};

// Initialize project names when storage is available
Expand All @@ -473,6 +605,7 @@ export function Component(props: MenuProps): React.JSX.Element {

return (
<>
{contextHolder}
<FileManageModal
isOpen={fileModalOpen}
onClose={handleFileModalClose}
Expand Down