From 45832fad30bb5ae2b7c4f359afb873afdbbf0147 Mon Sep 17 00:00:00 2001 From: mhsh312 Date: Sun, 10 Dec 2023 16:32:41 +0530 Subject: [PATCH 1/2] frontend changes --- client/modules/IDE/hooks/useSketchActions.js | 6 ++++++ client/modules/User/pages/DashboardView.jsx | 10 ++++++++++ translations/locales/en-US/translations.json | 3 ++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/client/modules/IDE/hooks/useSketchActions.js b/client/modules/IDE/hooks/useSketchActions.js index 3c7b0e3d87..0a3dc1b141 100644 --- a/client/modules/IDE/hooks/useSketchActions.js +++ b/client/modules/IDE/hooks/useSketchActions.js @@ -4,6 +4,7 @@ import { useParams } from 'react-router'; import { autosaveProject, exportProjectAsZip, + exportAllProjectsAsZip, newProject, saveProject, setProjectName @@ -44,6 +45,10 @@ const useSketchActions = () => { exportProjectAsZip(project.id); } + function downloadAllSketches() { + exportAllProjectsAsZip(params.username); + } + function shareSketch() { const { username } = params; dispatch(showShareModal(project.id, project.name, username)); @@ -61,6 +66,7 @@ const useSketchActions = () => { newSketch, saveSketch, downloadSketch, + downloadAllSketches, shareSketch, changeSketchName, canEditProjectName diff --git a/client/modules/User/pages/DashboardView.jsx b/client/modules/User/pages/DashboardView.jsx index f0b6d249b0..111bbc190d 100644 --- a/client/modules/User/pages/DashboardView.jsx +++ b/client/modules/User/pages/DashboardView.jsx @@ -13,6 +13,7 @@ import CollectionList from '../../IDE/components/CollectionList'; import SketchList from '../../IDE/components/SketchList'; import RootPage from '../../../components/RootPage'; import { newProject } from '../../IDE/actions/project'; +import { useSketchActions } from '../../IDE/hooks'; import { CollectionSearchbar, SketchSearchbar @@ -35,10 +36,16 @@ const DashboardView = () => { const [collectionCreateVisible, setCollectionCreateVisible] = useState(false); + const { downloadAllSketches } = useSketchActions(); + const createNewSketch = () => { dispatch(newProject()); }; + const downloadAll = () => { + downloadAllSketches(params.username); + }; + const selectedTabKey = useCallback(() => { const path = location.pathname; @@ -89,6 +96,9 @@ const DashboardView = () => { {t('DashboardView.NewSketch')} )} + ); diff --git a/translations/locales/en-US/translations.json b/translations/locales/en-US/translations.json index e6877b0f58..3060a523fe 100644 --- a/translations/locales/en-US/translations.json +++ b/translations/locales/en-US/translations.json @@ -465,7 +465,8 @@ "DashboardView": { "CreateCollection": "Create collection", "NewSketch": "New sketch", - "CreateCollectionOverlay": "Create collection" + "CreateCollectionOverlay": "Create collection", + "DownloadAll": "Download all Sketches" }, "DashboardTabSwitcher": { "Sketches": "Sketches", From 6a6fd5df6f25f3c59ff7b1649bf018767b1aafad Mon Sep 17 00:00:00 2001 From: mhsh312 Date: Sun, 10 Dec 2023 16:33:46 +0530 Subject: [PATCH 2/2] backend changes --- client/modules/IDE/actions/project.js | 5 ++ server/controllers/project.controller.js | 73 ++++++++++++++++++- .../project.controller/getProjectsForUser.js | 2 +- server/routes/project.routes.js | 5 ++ 4 files changed, 82 insertions(+), 3 deletions(-) diff --git a/client/modules/IDE/actions/project.js b/client/modules/IDE/actions/project.js index 1d8943336b..7634fa8eda 100644 --- a/client/modules/IDE/actions/project.js +++ b/client/modules/IDE/actions/project.js @@ -262,6 +262,11 @@ export function exportProjectAsZip(projectId) { win.focus(); } +export function exportAllProjectsAsZip(username) { + const win = window.open(`${ROOT_URL}/${username}/downloadall`, '_blank'); + win.focus(); +} + export function resetProject() { return { type: ActionTypes.RESET_PROJECT diff --git a/server/controllers/project.controller.js b/server/controllers/project.controller.js index fd48b7558d..8a51aaecfc 100644 --- a/server/controllers/project.controller.js +++ b/server/controllers/project.controller.js @@ -9,6 +9,7 @@ import Project from '../models/project'; import User from '../models/user'; import { resolvePathToFile } from '../utils/filePath'; import generateFileSystemSafeName from '../utils/generateFileSystemSafeName'; +import { getProjectsForUserName } from './project.controller/getProjectsForUser'; export { default as createProject, @@ -215,6 +216,41 @@ function bundleExternalLibs(project) { }); } +function bundleExternalLibsForCompiledProject(compilation) { + const { files } = compilation; + const htmlFiles = []; + const projectFolders = []; + files.forEach((file) => { + if (file.name === 'index.html') htmlFiles.push(file); + else if (file.fileType === 'folder' && file.name !== 'root') + projectFolders.push(file); + }); + htmlFiles.forEach((indexHtml) => { + const { window } = new JSDOM(indexHtml.content); + const scriptTags = window.document.getElementsByTagName('script'); + + const parentFolder = projectFolders.filter((folder) => + folder.children.includes(indexHtml.id) + ); + + Object.values(scriptTags).forEach(async ({ src }, i) => { + if (!isUrl(src)) return; + + const path = src.split('/'); + const filename = path[path.length - 1]; + const libId = `${filename}-${parentFolder[0].name}`; + + compilation.files.push({ + name: filename, + url: src, + id: libId + }); + + parentFolder[0].children.push(libId); + }); + }); +} + function addFileToZip(file, files, zip, path = '') { return new Promise((resolve, reject) => { if (file.fileType === 'folder') { @@ -259,7 +295,7 @@ function addFileToZip(file, files, zip, path = '') { }); } -async function buildZip(project, req, res) { +async function buildZip(project, req, res, isSingleProject = true) { try { const zip = new JSZip(); const currentTime = format(new Date(), 'yyyy_MM_dd_HH_mm_ss'); @@ -270,7 +306,11 @@ async function buildZip(project, req, res) { const { files } = project; const root = files.find((file) => file.name === 'root'); - bundleExternalLibs(project); + if (isSingleProject) { + bundleExternalLibs(project); + } else { + bundleExternalLibsForCompiledProject(project); + } await addFileToZip(root, files, zip); const base64 = await zip.generateAsync({ type: 'base64' }); @@ -292,3 +332,32 @@ export function downloadProjectAsZip(req, res) { buildZip(project, req, res); }); } + +function compileProjects(projects, compiledName) { + const compiledFolder = { + name: 'root', + fileType: 'folder', + children: [] + }; + + const compiledProject = { + name: compiledName, + files: [compiledFolder] + }; + + projects.forEach((project) => { + const rootFile = project.files.find((file) => file.name === 'root'); + rootFile.name = project.name; + compiledProject.files.push(...project.files); + compiledFolder.children.push(rootFile.id); + }); + + return compiledProject; +} + +export function downloadAllProjectsAsZip(req, res) { + getProjectsForUserName(req.params.username).then((projects) => { + const compilation = compileProjects(projects, req.params.username); + buildZip(compilation, req, res, false); + }); +} diff --git a/server/controllers/project.controller/getProjectsForUser.js b/server/controllers/project.controller/getProjectsForUser.js index 5579559915..c9c0661718 100644 --- a/server/controllers/project.controller/getProjectsForUser.js +++ b/server/controllers/project.controller/getProjectsForUser.js @@ -5,7 +5,7 @@ import createApplicationErrorClass from '../../utils/createApplicationErrorClass const UserNotFoundError = createApplicationErrorClass('UserNotFoundError'); -function getProjectsForUserName(username) { +export function getProjectsForUserName(username) { return new Promise((resolve, reject) => { User.findByUsername(username, (err, user) => { if (err) { diff --git a/server/routes/project.routes.js b/server/routes/project.routes.js index 469eb43e92..c8d6280f2a 100644 --- a/server/routes/project.routes.js +++ b/server/routes/project.routes.js @@ -26,4 +26,9 @@ router.get('/:username/projects', ProjectController.getProjectsForUser); router.get('/projects/:project_id/zip', ProjectController.downloadProjectAsZip); +router.get( + '/:username/downloadall', + ProjectController.downloadAllProjectsAsZip +); + export default router;