Skip to content
This repository has been archived by the owner on Mar 5, 2021. It is now read-only.

Commit

Permalink
Implement exporting the project
Browse files Browse the repository at this point in the history
  • Loading branch information
bilou84 committed Jul 3, 2016
1 parent 93224fb commit 2e20345
Show file tree
Hide file tree
Showing 15 changed files with 270 additions and 19 deletions.
8 changes: 5 additions & 3 deletions player/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
/// <reference path="../../../../SupClient/typings/SupApp.d.ts" />

import * as querystring from "querystring";
let qs: { project: string; build: string; } = querystring.parse(window.location.search.slice(1));
const qs: { project: string; build: string; } = querystring.parse(window.location.search.slice(1));

const indexPath = `/builds/${qs.project}/${qs.build}/files/index.html`;

if ((window as any).SupApp != null) {
SupApp.openLink(`${window.location.origin}/builds/${qs.project}/${qs.build}/index.html`);
SupApp.openLink(`${window.location.origin}${indexPath}`);
window.close();
} else {
window.location.href = `/builds/${qs.project}/${qs.build}/index.html`;
window.location.href = indexPath;
}
10 changes: 7 additions & 3 deletions plugins/default/blob/data/BlobAsset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ export default class BlobAsset extends SupCore.Data.Base.Asset {
});
}

serverExport(buildPath: string, assetsById: { [id: string]: BlobAsset }, callback: (err: Error) => void) {
if (this.pub.buffer == null) { callback (null); return; }
serverExport(buildPath: string, assetsById: { [id: string]: BlobAsset }, callback: (err: Error, writtenFiles: string[]) => void) {
if (this.pub.buffer == null) { callback (null, []); return; }

let pathFromId = this.server.data.entries.getPathFromId(this.id);
if (pathFromId.lastIndexOf(".") <= pathFromId.lastIndexOf("/")) {
Expand All @@ -90,7 +90,11 @@ export default class BlobAsset extends SupCore.Data.Base.Asset {

let outputPath = `${buildPath}/${pathFromId}`;
let parentPath = outputPath.slice(0, outputPath.lastIndexOf("/"));
mkdirp(parentPath, () => { fs.writeFile(outputPath, this.pub.buffer, callback); });
mkdirp(parentPath, () => {
fs.writeFile(outputPath, this.pub.buffer, (err) => {
callback(err, [ pathFromId ]);
});
});
}

server_upload(client: any, mediaType: string, buffer: Buffer, callback: UploadCallback) {
Expand Down
3 changes: 3 additions & 0 deletions plugins/default/export/build/WebBuildSettings.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
interface WebBuildSettings {
outputFolder: string;
}
96 changes: 96 additions & 0 deletions plugins/default/export/build/WebBuildSettingsEditor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import * as TreeView from "dnd-tree-view";

let outputFolder: string;

export default class WebBuildSettingsEditor implements SupClient.BuildSettingsEditor {
private outputFolderTextfield: HTMLInputElement;
private outputFolderButton: HTMLButtonElement;
private errorRowElt: HTMLTableRowElement;
private errorInput: HTMLInputElement;

private table: HTMLTableElement;

constructor(container: HTMLDivElement, private entries: SupCore.Data.Entries, private entriesTreeView: TreeView) {
const { table, tbody } = SupClient.table.createTable(container);
this.table = table;
table.classList.add("properties");
table.hidden = true;

const outputFolderRow = SupClient.table.appendRow(tbody, SupClient.i18n.t("buildSettingsEditors:web.outputFolder"));
const inputs = SupClient.html("div", "inputs", { parent: outputFolderRow.valueCell });

const value = outputFolder != null ? outputFolder : "";
this.outputFolderTextfield = SupClient.html("input", { parent: inputs, type: "text", value, readOnly: true, style: { cursor: "pointer" } }) as HTMLInputElement;
this.outputFolderButton = SupClient.html("button", { parent: inputs, textContent: SupClient.i18n.t("common:actions.select") }) as HTMLButtonElement;

this.outputFolderTextfield.addEventListener("click", (event) => { event.preventDefault(); this.selectOutputfolder(); });
this.outputFolderButton.addEventListener("click", (event) => { event.preventDefault(); this.selectOutputfolder(); });

const errorRow = SupClient.table.appendRow(tbody, "Error");
this.errorRowElt = errorRow.row;
this.errorRowElt.hidden = true;
this.errorInput = SupClient.html("input", { parent: errorRow.valueCell, type: "text", readOnly: true }) as HTMLInputElement;
}

setVisible(visible: boolean) {
this.table.hidden = !visible;
}

getSettings(callback: (settings: WebBuildSettings) => void) {
this.ensureOutputFolderValid((outputFolderValid) => {
callback(outputFolderValid ? { outputFolder } : null);
});
}

private selectOutputfolder() {
SupApp.chooseFolder((folderPath) => {
if (folderPath == null) return;

outputFolder = this.outputFolderTextfield.value = folderPath;
this.ensureOutputFolderValid();
});
}

private ensureOutputFolderValid(callback?: (outputFolderValid: boolean) => void) {
if (outputFolder == null) {
this.displayError(SupClient.i18n.t("buildSettingsEditors:web.errors.selectDestionationFolder"));
if (callback != null) callback(false);
return;
}

SupApp.readDir(outputFolder, (err, files) => {
if (err != null) {
this.displayError(SupClient.i18n.t("buildSettingsEditors:web.errors.emptyDirectoryCheckFail"));
console.log(err);
if (callback != null) callback(false);
return;
}

let index = 0;
while (index < files.length) {
const item = files[index];
if (item[0] === "." || item === "Thumbs.db") files.splice(index, 1);
else index++;
}

if (files.length > 0) {
this.displayError(SupClient.i18n.t("buildSettingsEditors:web.errors.destinationFolderEmpty"));
if (callback != null) callback(false);
} else {
this.errorRowElt.hidden = true;
if (callback != null) callback(true);
}
});
}

private displayError(err: string) {
this.errorRowElt.hidden = false;
this.errorInput.value = err;

(this.errorRowElt as any).animate([
{ transform: "translateX(0)" },
{ transform: "translateX(1.5vh)" },
{ transform: "translateX(0)" }
], { duration: 100 });
}
}
72 changes: 72 additions & 0 deletions plugins/default/export/build/buildWeb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import * as async from "async";
import * as querystring from "querystring";
import supFetch from "../../../../../../SupClient/src/fetch";
import * as path from "path";

const qs = querystring.parse(window.location.search.slice(1));

let settings: WebBuildSettings;

const progress = { index: 0, total: 0, errors: 0 };
const statusElt = document.querySelector(".status");
const progressElt = document.querySelector("progress") as HTMLProgressElement;
const detailsListElt = document.querySelector(".details ol") as HTMLOListElement;

export default function build(socket: SocketIOClient.Socket, theSettings: WebBuildSettings, buildPort: number) {
settings = theSettings;

socket.emit("build:project", (err: string, buildId: string) => {
const buildPath = `${window.location.protocol}//${window.location.hostname}:${buildPort}/builds/${qs.project}/${buildId}/`;

supFetch(`${buildPath}files.json`, "json", (err, filesToDownload) => {
if (err != null) {
progress.errors++;
SupClient.html("li", { parent: detailsListElt, textContent: SupClient.i18n.t("builds:web.errors.exportFailed", { path: settings.outputFolder }) });
return;
}

progress.total = filesToDownload.length;
updateProgress();

async.each(filesToDownload as string[], (filePath, cb) => {
downloadFile(buildPath, filePath, (err) => {
if (err != null) {
progress.errors++;
SupClient.html("li", { parent: detailsListElt, textContent: SupClient.i18n.t("builds:web.errors.exportFailed", { path: filePath }) });
} else {
progress.index++;
updateProgress();
}

cb(err);
});
});
});
});
}

function updateProgress() {
progressElt.max = progress.total;
progressElt.value = progress.index;

if (progress.index < progress.total) {
statusElt.textContent = SupClient.i18n.t("builds:web.progress", { path: settings.outputFolder, index: progress.index, total: progress.total });
} else if (progress.errors > 0) {
statusElt.textContent = SupClient.i18n.t("builds:web.doneWithErrors", { path: settings.outputFolder, total: progress.total, errors: progress.errors });
} else {
statusElt.textContent = SupClient.i18n.t("builds:web.done", { path: settings.outputFolder, total: progress.total });
}
}

function downloadFile(buildPath: string, filePath: string, callback: ErrorCallback) {
const inputPath = `${buildPath}files/${filePath}`;
const outputPath = path.join(settings.outputFolder, filePath);

SupApp.mkdirp(path.dirname(outputPath), (err) => {
supFetch(inputPath, "arraybuffer", (err, data) => {
if (err != null) { callback(err); return; }

SupApp.writeFile(outputPath, new Buffer(data), callback);
});
});
}
9 changes: 9 additions & 0 deletions plugins/default/export/build/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/// <reference path="./WebBuildSettings.d.ts" />

import WebBuildSettingsEditor from "./WebBuildSettingsEditor";
import buildWeb from "./buildWeb";

SupClient.registerPlugin<SupClient.BuildPlugin>("build", "web", {
settingsEditor: WebBuildSettingsEditor,
build: buildWeb
});
2 changes: 2 additions & 0 deletions plugins/default/export/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// <reference path="../../../../../SupCore/SupCore.d.ts" />
/// <reference path="../../../../../SupClient/SupClient.d.ts" />
13 changes: 13 additions & 0 deletions plugins/default/export/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "superpowers-web-default-export-plugin",
"description": "Export plugin for Superpowers Web, the collaborative static site editor",
"version": "1.0.0",
"license": "ISC",
"repository": {
"type": "git",
"url": "https://github.com/superpowers/superpowers-web.git"
},
"scripts": {
"build": "gulp --gulpfile=../../../../../scripts/pluginGulpfile.js --cwd=."
}
}
13 changes: 13 additions & 0 deletions plugins/default/export/public/locales/en/buildSettingsEditors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"web": {
"label": "Default",
"description": "Export as HTML5",

"outputFolder": "Output folder",
"errors": {
"selectDestionationFolder": "Select a destination folder.",
"emptyDirectoryCheckFail": "Failed to check if the selected folder is empty.",
"destinationFolderEmpty": "The destination folder must be empty."
}
}
}
12 changes: 12 additions & 0 deletions plugins/default/export/public/locales/en/builds.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"web": {
"title": "HTML5 export",
"progress": "Exporting to ${path}... (${index}/${total})",
"doneWithErrors": "Encountered ${errors} errors while exporting ${total} elements to ${path}.",
"done": "Exported ${total} elements to ${path}.",

"errors": {
"exportFailed": "Failed to export ${path}."
}
}
}
7 changes: 7 additions & 0 deletions plugins/default/export/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": true
}
}
8 changes: 6 additions & 2 deletions plugins/default/jade/data/JadeAsset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export default class JadeAsset extends SupCore.Data.Base.Asset {
});
}

serverExport(buildPath: string, assetsById: { [id: string]: JadeAsset }, callback: (err: Error) => void) {
serverExport(buildPath: string, assetsById: { [id: string]: JadeAsset }, callback: (err: Error, writtenFiles: string[]) => void) {
let pathFromId = this.server.data.entries.getPathFromId(this.id);
if (pathFromId.lastIndexOf(".jade") === pathFromId.length - 5) pathFromId = pathFromId.slice(0, -5);
let outputPath = `${buildPath}/${pathFromId}.html`;
Expand Down Expand Up @@ -139,7 +139,11 @@ export default class JadeAsset extends SupCore.Data.Base.Asset {
}
fs.readFileSync = oldReadFileSync;

mkdirp(parentPath, () => { fs.writeFile(outputPath, html, callback); });
mkdirp(parentPath, () => {
fs.writeFile(outputPath, html, (err) => {
callback(err, [ `${pathFromId}.html` ]);
});
});
}

server_editText(client: any, operationData: OperationData, revisionIndex: number, callback: EditTextCallback) {
Expand Down
8 changes: 6 additions & 2 deletions plugins/default/json/data/JSONAsset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,18 @@ export default class JSONAsset extends SupCore.Data.Base.Asset {
});
}

serverExport(buildPath: string, assetsById: { [id: string]: JSONAsset }, callback: (err: Error) => void) {
serverExport(buildPath: string, assetsById: { [id: string]: JSONAsset }, callback: (err: Error, writtenFiles: string[]) => void) {
let pathFromId = this.server.data.entries.getPathFromId(this.id);
if (pathFromId.lastIndexOf(".json") === pathFromId.length - 5) pathFromId = pathFromId.slice(0, -5);
let outputPath = `${buildPath}/${pathFromId}.json`;
let parentPath = outputPath.slice(0, outputPath.lastIndexOf("/"));

let text = this.pub.text;
mkdirp(parentPath, () => { fs.writeFile(outputPath, text, callback); });
mkdirp(parentPath, () => {
fs.writeFile(outputPath, text, (err) => {
callback(err, [ `${pathFromId}.json` ]);
});
});
}

server_editText(client: any, operationData: OperationData, revisionIndex: number, callback: EditTextCallback) {
Expand Down
8 changes: 6 additions & 2 deletions plugins/default/stylus/data/StylusAsset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export default class StylusAsset extends SupCore.Data.Base.Asset {
});
}

serverExport(buildPath: string, assetsById: { [id: string]: StylusAsset }, callback: (err: Error) => void) {
serverExport(buildPath: string, assetsById: { [id: string]: StylusAsset }, callback: (err: Error, writtenFiles: string[]) => void) {
let pathFromId = this.server.data.entries.getPathFromId(this.id);
if (pathFromId.lastIndexOf(".styl") === pathFromId.length - 5) pathFromId = pathFromId.slice(0, -5);
let outputPath = `${buildPath}/${pathFromId}.css`;
Expand All @@ -116,7 +116,11 @@ export default class StylusAsset extends SupCore.Data.Base.Asset {
};
let css = stylus(this.pub.text).set("filename", `${pathFromId}.styl`).set("cache", false).render();
fs.readFileSync = oldReadFileSync;
mkdirp(parentPath, () => { fs.writeFile(outputPath, css, callback); });
mkdirp(parentPath, () => {
fs.writeFile(outputPath, css, (err) => {
callback(err, [ `${pathFromId}.css` ]);
});
});
}

server_editText(client: any, operationData: OperationData, revisionIndex: number, callback: EditTextCallback) {
Expand Down
20 changes: 13 additions & 7 deletions server/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
/// <reference path="./index.d.ts" />

import * as async from "async";
import * as fs from "fs";

interface ExportableAsset extends SupCore.Data.Base.Asset {
serverExport: (outputPath: string, assetsById: { [id: string]: ExportableAsset }, callback: (err: Error) => void) => void;
interface ServerExportableAsset extends SupCore.Data.Base.Asset {
serverExport: (outputPath: string, assetsById: { [id: string]: ServerExportableAsset }, callback: (err: Error, writtenFiles: string[]) => void) => void;
}

SupCore.system.serverBuild = (server: ProjectServer, buildPath: string, callback: (err: string) => void) => {
const assetIdsToExport: string[] = [];
let files: string[] = [];
server.data.entries.walk((entry: SupCore.Data.EntryNode, parent: SupCore.Data.EntryNode) => {
if (entry.type != null) assetIdsToExport.push(entry.id);
if (entry.type == null) return;

assetIdsToExport.push(entry.id);
});

const assetsById: { [id: string]: ExportableAsset } = {};
const assetsById: { [id: string]: ServerExportableAsset } = {};

async.series([

// Acquire all assets
(cb) => {
async.each(assetIdsToExport, (assetId, cb) => {
server.data.assets.acquire(assetId, null, (err: Error, asset: ExportableAsset) => {
server.data.assets.acquire(assetId, null, (err: Error, asset: ServerExportableAsset) => {
assetsById[assetId] = asset;
cb();
});
Expand All @@ -29,7 +33,8 @@ SupCore.system.serverBuild = (server: ProjectServer, buildPath: string, callback
// Export all assets
(cb) => {
async.each(assetIdsToExport, (assetId, cb) => {
assetsById[assetId].serverExport(buildPath, assetsById, () => {
assetsById[assetId].serverExport(`${buildPath}/files`, assetsById, (err, writtenFiles) => {
files = files.concat(writtenFiles);
cb();
});
}, cb);
Expand All @@ -39,6 +44,7 @@ SupCore.system.serverBuild = (server: ProjectServer, buildPath: string, callback
// Release all assets
for (const assetId of assetIdsToExport) server.data.assets.release(assetId, null);

callback(null);
// Write files.json
fs.writeFile(`${buildPath}/files.json`, JSON.stringify(files), callback);
});
};

0 comments on commit 2e20345

Please sign in to comment.