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/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/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;
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",