diff --git a/README.md b/README.md index 25e4c8d2..f3d834bf 100644 --- a/README.md +++ b/README.md @@ -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. \ No newline at end of file diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index 211516d1..b1eff889 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -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.", @@ -28,6 +31,8 @@ "PROJECTS": "Projects", "SAVE": "Save", "DEPLOY": "Deploy", + "DOWNLOAD": "Download", + "UPLOAD": "Upload", "MANAGE": "Manage", "EXPLORER": "Explorer", "ROBOT": "Robot", diff --git a/src/i18n/locales/es/translation.json b/src/i18n/locales/es/translation.json index 1387113a..f3e7e07a 100644 --- a/src/i18n/locales/es/translation.json +++ b/src/i18n/locales/es/translation.json @@ -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.", @@ -25,6 +28,8 @@ "PROJECTS": "Proyectos", "SAVE": "Guardar", "DEPLOY": "Desplegar", + "DOWNLOAD": "Descargar", + "UPLOAD": "Cargar", "MANAGE": "Gestionar", "EXPLORER": "Explorador", "ROBOT": "Robot", diff --git a/src/i18n/locales/he/translation.json b/src/i18n/locales/he/translation.json index 71845b3a..292efc68 100644 --- a/src/i18n/locales/he/translation.json +++ b/src/i18n/locales/he/translation.json @@ -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": "אסור שיהיו רווחים בשם. כל מילה בשם צריכה להתחיל באות גדולה.", @@ -28,6 +31,8 @@ "PROJECTS": "פרויקטים", "SAVE": "שמור", "DEPLOY": "העלה לרובוט", + "DOWNLOAD": "הורד", + "UPLOAD": "העלה", "MANAGE": "ניהול", "EXPLORER": "סייר", "ROBOT": "רובוט", diff --git a/src/reactComponents/Menu.tsx b/src/reactComponents/Menu.tsx index 6cf0b3e1..239a5c38 100644 --- a/src/reactComponents/Menu.tsx +++ b/src/reactComponents/Menu.tsx @@ -123,6 +123,8 @@ function getMenuItems(t: (key: string) => string, project: storageProject.Projec getItem(t('PROJECT'), 'project', , [ getItem(t('SAVE'), 'save', ), getItem(t('DEPLOY'), 'deploy'), + getItem(t('DOWNLOAD'), 'download', ), + getItem(t('UPLOAD') + '...', 'upload', ), ]), getItem(t('MANAGE'), 'manage', , [ getItem(t('PROJECTS') + '...', 'manageProjects', ), @@ -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([]); const [menuItems, setMenuItems] = React.useState([]); @@ -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); @@ -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 => { + 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: ( +
+

{t('PROJECT_NAME_EXISTS', { projectName: preferredName })}

+ { + inputValue = e.target.value; + }} + /> +
+ ), + 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; @@ -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 => { setProjectModalOpen(false); + // Refresh project names to reflect any changes (deletions, renames, etc.) + if (props.storage) { + await fetchListOfProjectNames(); + } }; // Initialize project names when storage is available @@ -473,6 +605,7 @@ export function Component(props: MenuProps): React.JSX.Element { return ( <> + {contextHolder}