Switch to node-pty-prebuilt-multiarch and upgrade Electron
This commit is contained in:
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 120
|
||||||
|
}
|
||||||
2
app.js
2
app.js
@@ -41,7 +41,7 @@ di.require('progress', 'ProgressBar');
|
|||||||
di.require('gif-encoder', 'GIFEncoder');
|
di.require('gif-encoder', 'GIFEncoder');
|
||||||
di.require('inquirer');
|
di.require('inquirer');
|
||||||
|
|
||||||
di.set('pty', require('@faressoft/node-pty-prebuilt'));
|
di.set('pty', require('node-pty-prebuilt-multiarch'));
|
||||||
di.set('PNG', require('pngjs').PNG);
|
di.set('PNG', require('pngjs').PNG);
|
||||||
di.set('spawn', require('child_process').spawn);
|
di.set('spawn', require('child_process').spawn);
|
||||||
di.set('utility', require('./utility.js'));
|
di.set('utility', require('./utility.js'));
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Render
|
* Render
|
||||||
* Render a recording file as an animated gif image
|
* Render a recording file as an animated gif image
|
||||||
*
|
*
|
||||||
* @author Mohammad Fares <faressoft.com@gmail.com>
|
* @author Mohammad Fares <faressoft.com@gmail.com>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -13,37 +13,40 @@
|
|||||||
* @return {ProgressBar}
|
* @return {ProgressBar}
|
||||||
*/
|
*/
|
||||||
function getProgressBar(operation, framesCount) {
|
function getProgressBar(operation, framesCount) {
|
||||||
|
return new di.ProgressBar(
|
||||||
return new di.ProgressBar(operation + ' ' + di.chalk.magenta('frame :current/:total') + ' :percent [:bar] :etas', {
|
operation +
|
||||||
width: 30,
|
" " +
|
||||||
total: framesCount
|
di.chalk.magenta("frame :current/:total") +
|
||||||
});
|
" :percent [:bar] :etas",
|
||||||
|
{
|
||||||
|
width: 30,
|
||||||
|
total: framesCount,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write the recording data into render/data.json
|
* Write the recording data into render/data.json
|
||||||
*
|
*
|
||||||
* @param {Object} recordingFile
|
* @param {Object} recordingFile
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
function writeRecordingData(recordingFile) {
|
function writeRecordingData(recordingFile) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
|
|
||||||
// Write the data into data.json file in the root path of the app
|
// Write the data into data.json file in the root path of the app
|
||||||
di.fs.writeFile(di.path.join(ROOT_PATH, 'render/data.json'), JSON.stringify(recordingFile.json), 'utf8', function(error) {
|
di.fs.writeFile(
|
||||||
|
di.path.join(ROOT_PATH, "render/data.json"),
|
||||||
|
JSON.stringify(recordingFile.json),
|
||||||
|
"utf8",
|
||||||
|
function (error) {
|
||||||
|
if (error) {
|
||||||
|
return reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
if (error) {
|
resolve();
|
||||||
return reject(error);
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
resolve();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,80 +56,72 @@ function writeRecordingData(recordingFile) {
|
|||||||
* @return {Promise} resolve with the parsed PNG image
|
* @return {Promise} resolve with the parsed PNG image
|
||||||
*/
|
*/
|
||||||
function loadPNG(path) {
|
function loadPNG(path) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
return new Promise(function(resolve, reject) {
|
di.fs.readFile(path, function (error, imageData) {
|
||||||
|
|
||||||
di.fs.readFile(path, function(error, imageData) {
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return reject(error);
|
return reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
new di.PNG().parse(imageData, function(error, data) {
|
new di.PNG().parse(imageData, function (error, data) {
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return reject(error);
|
return reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(data);
|
resolve(data);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the dimensions of the first rendered frame
|
* Get the dimensions of the first rendered frame
|
||||||
*
|
*
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
function getFrameDimensions() {
|
function getFrameDimensions() {
|
||||||
|
|
||||||
// The path of the first rendered frame
|
// The path of the first rendered frame
|
||||||
var framePath = di.path.join(ROOT_PATH, 'render/frames/0.png');
|
var framePath = di.path.join(ROOT_PATH, "render/frames/0.png");
|
||||||
|
|
||||||
// Read and parse a PNG image file
|
// Read and parse a PNG image file
|
||||||
return loadPNG(framePath).then(function(png) {
|
return loadPNG(framePath).then(function (png) {
|
||||||
|
return {
|
||||||
return({
|
|
||||||
width: png.width,
|
width: png.width,
|
||||||
height: png.height
|
height: png.height,
|
||||||
});
|
};
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the frames into PNG images
|
* Render the frames into PNG images
|
||||||
*
|
*
|
||||||
* @param {Array} records [{delay, content}, ...]
|
* @param {Array} records [{delay, content}, ...]
|
||||||
* @param {Object} options {step}
|
* @param {Object} options {step}
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
function renderFrames(records, options) {
|
function renderFrames(records, options) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
|
|
||||||
// The number of frames
|
// The number of frames
|
||||||
var framesCount = records.length;
|
var framesCount = records.length;
|
||||||
|
|
||||||
// Create a progress bar
|
// Create a progress bar
|
||||||
var progressBar = getProgressBar('Rendering', Math.ceil(framesCount / options.step));
|
var progressBar = getProgressBar(
|
||||||
|
"Rendering",
|
||||||
|
Math.ceil(framesCount / options.step)
|
||||||
|
);
|
||||||
|
|
||||||
// Execute the rendering process
|
// Execute the rendering process
|
||||||
var render = di.spawn(di.electron, [di.path.join(ROOT_PATH, 'render/index.js'), options.step], {detached: false});
|
var render = di.spawn(
|
||||||
|
di.electron,
|
||||||
|
[di.path.join(ROOT_PATH, "render/index.js"), options.step],
|
||||||
|
{ detached: false }
|
||||||
|
);
|
||||||
|
|
||||||
render.stderr.on('data', function(error) {
|
render.stderr.on("data", function (error) {
|
||||||
render.kill();
|
render.kill();
|
||||||
reject(new Error(error));
|
reject(new Error(error));
|
||||||
});
|
});
|
||||||
|
|
||||||
render.stdout.on('data', function(data) {
|
render.stdout.on("data", function (data) {
|
||||||
|
|
||||||
// Is not a recordIndex (to skip Electron's logs or new lines)
|
// Is not a recordIndex (to skip Electron's logs or new lines)
|
||||||
if (di.is.not.number(parseInt(data.toString()))) {
|
if (di.is.not.number(parseInt(data.toString()))) {
|
||||||
return;
|
return;
|
||||||
@@ -138,25 +133,20 @@ function renderFrames(records, options) {
|
|||||||
if (progressBar.complete) {
|
if (progressBar.complete) {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merge the rendered frames into an animated GIF image
|
* Merge the rendered frames into an animated GIF image
|
||||||
*
|
*
|
||||||
* @param {Array} records [{delay, content}, ...]
|
* @param {Array} records [{delay, content}, ...]
|
||||||
* @param {Object} options {quality, repeat, step, outputFile}
|
* @param {Object} options {quality, repeat, step, outputFile}
|
||||||
* @param {Object} frameDimensions {width, height}
|
* @param {Object} frameDimensions {width, height}
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
function mergeFrames(records, options, frameDimensions) {
|
function mergeFrames(records, options, frameDimensions) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
|
|
||||||
// The number of frames
|
// The number of frames
|
||||||
var framesCount = records.length;
|
var framesCount = records.length;
|
||||||
|
|
||||||
@@ -164,11 +154,14 @@ function mergeFrames(records, options, frameDimensions) {
|
|||||||
var stepsCounter = 0;
|
var stepsCounter = 0;
|
||||||
|
|
||||||
// Create a progress bar
|
// Create a progress bar
|
||||||
var progressBar = getProgressBar('Merging', Math.ceil(framesCount / options.step));
|
var progressBar = getProgressBar(
|
||||||
|
"Merging",
|
||||||
|
Math.ceil(framesCount / options.step)
|
||||||
|
);
|
||||||
|
|
||||||
// The gif image
|
// The gif image
|
||||||
var gif = new di.GIFEncoder(frameDimensions.width, frameDimensions.height, {
|
var gif = new di.GIFEncoder(frameDimensions.width, frameDimensions.height, {
|
||||||
highWaterMark: 5 * 1024 * 1024
|
highWaterMark: 5 * 1024 * 1024,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Pipe
|
// Pipe
|
||||||
@@ -184,101 +177,91 @@ function mergeFrames(records, options, frameDimensions) {
|
|||||||
gif.writeHeader();
|
gif.writeHeader();
|
||||||
|
|
||||||
// Foreach frame
|
// Foreach frame
|
||||||
di.async.eachOfSeries(records, function(frame, index, callback) {
|
di.async.eachOfSeries(
|
||||||
|
records,
|
||||||
|
function (frame, index, callback) {
|
||||||
|
if (stepsCounter != 0) {
|
||||||
|
stepsCounter = (stepsCounter + 1) % options.step;
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
|
||||||
if (stepsCounter != 0) {
|
|
||||||
stepsCounter = (stepsCounter + 1) % options.step;
|
stepsCounter = (stepsCounter + 1) % options.step;
|
||||||
return callback();
|
|
||||||
|
// The path of the rendered frame
|
||||||
|
var framePath = di.path.join(
|
||||||
|
ROOT_PATH,
|
||||||
|
"render/frames",
|
||||||
|
index + ".png"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Read and parse the rendered frame
|
||||||
|
loadPNG(framePath)
|
||||||
|
.then(function (png) {
|
||||||
|
progressBar.tick();
|
||||||
|
|
||||||
|
// Set the duration (the delay of the next frame)
|
||||||
|
// The % is used to take the delay of the first frame
|
||||||
|
// as the duration of the last frame
|
||||||
|
gif.setDelay(records[(index + 1) % framesCount].delay);
|
||||||
|
|
||||||
|
// Add frames
|
||||||
|
gif.addFrame(png.data);
|
||||||
|
|
||||||
|
// Next
|
||||||
|
callback();
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
callback(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function (error) {
|
||||||
|
if (error) {
|
||||||
|
return reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the footer
|
||||||
|
gif.finish();
|
||||||
|
resolve();
|
||||||
}
|
}
|
||||||
|
);
|
||||||
stepsCounter = (stepsCounter + 1) % options.step;
|
|
||||||
|
|
||||||
// The path of the rendered frame
|
|
||||||
var framePath = di.path.join(ROOT_PATH, 'render/frames', index + '.png');
|
|
||||||
|
|
||||||
// Read and parse the rendered frame
|
|
||||||
loadPNG(framePath).then(function(png) {
|
|
||||||
|
|
||||||
progressBar.tick();
|
|
||||||
|
|
||||||
// Set the duration (the delay of the next frame)
|
|
||||||
// The % is used to take the delay of the first frame
|
|
||||||
// as the duration of the last frame
|
|
||||||
gif.setDelay(records[(index + 1) % framesCount].delay);
|
|
||||||
|
|
||||||
// Add frames
|
|
||||||
gif.addFrame(png.data);
|
|
||||||
|
|
||||||
// Next
|
|
||||||
callback();
|
|
||||||
|
|
||||||
}).catch(function(error) {
|
|
||||||
|
|
||||||
callback(error);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}, function(error) {
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return reject(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the footer
|
|
||||||
gif.finish();
|
|
||||||
resolve();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete the temporary rendered PNG images
|
* Delete the temporary rendered PNG images
|
||||||
*
|
*
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
function cleanup() {
|
function cleanup() {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
return new Promise(function(resolve, reject) {
|
di.fs.emptyDir(di.path.join(ROOT_PATH, "render/frames"), function (error) {
|
||||||
|
|
||||||
di.fs.emptyDir(di.path.join(ROOT_PATH, 'render/frames'), function(error) {
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return reject(error);
|
return reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executed after the command completes its task
|
* Executed after the command completes its task
|
||||||
*
|
*
|
||||||
* @param {String} outputFile the path of the rendered image
|
* @param {String} outputFile the path of the rendered image
|
||||||
*/
|
*/
|
||||||
function done(outputFile) {
|
function done(outputFile) {
|
||||||
|
console.log("\n" + di.chalk.green("Successfully Rendered"));
|
||||||
console.log('\n' + di.chalk.green('Successfully Rendered'));
|
console.log("The animated GIF image is saved into the file:");
|
||||||
console.log('The animated GIF image is saved into the file:');
|
|
||||||
console.log(di.chalk.magenta(outputFile));
|
console.log(di.chalk.magenta(outputFile));
|
||||||
process.exit();
|
process.exit();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The command's main function
|
* The command's main function
|
||||||
*
|
*
|
||||||
* @param {Object} argv
|
* @param {Object} argv
|
||||||
*/
|
*/
|
||||||
function command(argv) {
|
function command(argv) {
|
||||||
|
|
||||||
// Frames
|
// Frames
|
||||||
var records = argv.recordingFile.json.records;
|
var records = argv.recordingFile.json.records;
|
||||||
var config = argv.recordingFile.json.config;
|
var config = argv.recordingFile.json.config;
|
||||||
@@ -287,17 +270,20 @@ function command(argv) {
|
|||||||
var framesCount = records.length;
|
var framesCount = records.length;
|
||||||
|
|
||||||
// The path of the output file
|
// The path of the output file
|
||||||
var outputFile = di.utility.resolveFilePath('render' + (new Date()).getTime(), 'gif');
|
var outputFile = di.utility.resolveFilePath(
|
||||||
|
"render" + new Date().getTime(),
|
||||||
|
"gif"
|
||||||
|
);
|
||||||
|
|
||||||
// For adjusting (calculating) the frames delays
|
// For adjusting (calculating) the frames delays
|
||||||
var adjustFramesDelaysOptions = {
|
var adjustFramesDelaysOptions = {
|
||||||
frameDelay: config.frameDelay,
|
frameDelay: config.frameDelay,
|
||||||
maxIdleTime: config.maxIdleTime
|
maxIdleTime: config.maxIdleTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
// For rendering the frames into PNG images
|
// For rendering the frames into PNG images
|
||||||
var renderingOptions = {
|
var renderingOptions = {
|
||||||
step: argv.step
|
step: argv.step,
|
||||||
};
|
};
|
||||||
|
|
||||||
// For merging the rendered frames into an animated GIF image
|
// For merging the rendered frames into an animated GIF image
|
||||||
@@ -305,7 +291,7 @@ function command(argv) {
|
|||||||
quality: config.quality,
|
quality: config.quality,
|
||||||
repeat: config.repeat,
|
repeat: config.repeat,
|
||||||
step: argv.step,
|
step: argv.step,
|
||||||
outputFile: outputFile
|
outputFile: outputFile,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Overwrite the quality of the rendered image
|
// Overwrite the quality of the rendered image
|
||||||
@@ -320,35 +306,37 @@ function command(argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tasks
|
// Tasks
|
||||||
di.asyncPromises.waterfall([
|
di.asyncPromises
|
||||||
|
.waterfall([
|
||||||
|
// Remove all previously rendered frames
|
||||||
|
cleanup,
|
||||||
|
|
||||||
// Remove all previously rendered frames
|
// Write the recording data into render/data.json
|
||||||
cleanup,
|
di._.partial(writeRecordingData, argv.recordingFile),
|
||||||
|
|
||||||
// Write the recording data into render/data.json
|
// Render the frames into PNG images
|
||||||
di._.partial(writeRecordingData, argv.recordingFile),
|
di._.partial(renderFrames, records, renderingOptions),
|
||||||
|
|
||||||
// Render the frames into PNG images
|
// Adjust frames delays
|
||||||
di._.partial(renderFrames, records, renderingOptions),
|
di._.partial(
|
||||||
|
di.commands.play.adjustFramesDelays,
|
||||||
|
records,
|
||||||
|
adjustFramesDelaysOptions
|
||||||
|
),
|
||||||
|
|
||||||
// Adjust frames delays
|
// Get the dimensions of the first rendered frame
|
||||||
di._.partial(di.commands.play.adjustFramesDelays, records, adjustFramesDelaysOptions),
|
di._.partial(getFrameDimensions),
|
||||||
|
|
||||||
// Get the dimensions of the first rendered frame
|
// Merge the rendered frames into an animated GIF image
|
||||||
di._.partial(getFrameDimensions),
|
di._.partial(mergeFrames, records, mergingOptions),
|
||||||
|
|
||||||
// Merge the rendered frames into an animated GIF image
|
|
||||||
di._.partial(mergeFrames, records, mergingOptions),
|
|
||||||
|
|
||||||
// Delete the temporary rendered PNG images
|
|
||||||
cleanup
|
|
||||||
|
|
||||||
]).then(function() {
|
|
||||||
|
|
||||||
done(outputFile);
|
|
||||||
|
|
||||||
}).catch(di.errorHandler);
|
|
||||||
|
|
||||||
|
// Delete the temporary rendered PNG images
|
||||||
|
cleanup,
|
||||||
|
])
|
||||||
|
.then(function () {
|
||||||
|
done(outputFile);
|
||||||
|
})
|
||||||
|
.catch(di.errorHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////
|
////////////////////////////////////////////////////
|
||||||
@@ -359,13 +347,13 @@ function command(argv) {
|
|||||||
* Command's usage
|
* Command's usage
|
||||||
* @type {String}
|
* @type {String}
|
||||||
*/
|
*/
|
||||||
module.exports.command = 'render <recordingFile>';
|
module.exports.command = "render <recordingFile>";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command's description
|
* Command's description
|
||||||
* @type {String}
|
* @type {String}
|
||||||
*/
|
*/
|
||||||
module.exports.describe = 'Render a recording file as an animated gif image';
|
module.exports.describe = "Render a recording file as an animated gif image";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command's handler function
|
* Command's handler function
|
||||||
@@ -375,42 +363,40 @@ module.exports.handler = command;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Builder
|
* Builder
|
||||||
*
|
*
|
||||||
* @param {Object} yargs
|
* @param {Object} yargs
|
||||||
*/
|
*/
|
||||||
module.exports.builder = function(yargs) {
|
module.exports.builder = function (yargs) {
|
||||||
|
|
||||||
// Define the recordingFile argument
|
// Define the recordingFile argument
|
||||||
yargs.positional('recordingFile', {
|
yargs.positional("recordingFile", {
|
||||||
describe: 'The recording file',
|
describe: "The recording file",
|
||||||
type: 'string',
|
type: "string",
|
||||||
coerce: di.utility.loadYAML
|
coerce: di.utility.loadYAML,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Define the output option
|
// Define the output option
|
||||||
yargs.option('o', {
|
yargs.option("o", {
|
||||||
alias: 'output',
|
alias: "output",
|
||||||
type: 'string',
|
type: "string",
|
||||||
describe: 'A name for the output file',
|
describe: "A name for the output file",
|
||||||
requiresArg: true,
|
requiresArg: true,
|
||||||
coerce: di._.partial(di.utility.resolveFilePath, di._, 'gif')
|
coerce: di._.partial(di.utility.resolveFilePath, di._, "gif"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Define the quality option
|
// Define the quality option
|
||||||
yargs.option('q', {
|
yargs.option("q", {
|
||||||
alias: 'quality',
|
alias: "quality",
|
||||||
type: 'number',
|
type: "number",
|
||||||
describe: 'The quality of the rendered image (1 - 100)',
|
describe: "The quality of the rendered image (1 - 100)",
|
||||||
requiresArg: true
|
requiresArg: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Define the quality option
|
// Define the quality option
|
||||||
yargs.option('s', {
|
yargs.option("s", {
|
||||||
alias: 'step',
|
alias: "step",
|
||||||
type: 'number',
|
type: "number",
|
||||||
describe: 'To reduce the number of rendered frames (step > 1)',
|
describe: "To reduce the number of rendered frames (step > 1)",
|
||||||
requiresArg: true,
|
requiresArg: true,
|
||||||
default: 1
|
default: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
16
package.json
16
package.json
@@ -14,8 +14,8 @@
|
|||||||
"terminalizer": "bin/app.js"
|
"terminalizer": "bin/app.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "NODE_ENV=development webpack --colors --watch",
|
"dev": "NODE_ENV=development webpack --watch",
|
||||||
"build": "NODE_ENV=production webpack --optimize-minimize --progress --colors",
|
"build": "NODE_ENV=production webpack --progress",
|
||||||
"prepublish": "npm run build"
|
"prepublish": "npm run build"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -41,13 +41,12 @@
|
|||||||
"pty"
|
"pty"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@faressoft/node-pty-prebuilt": "^0.9.0",
|
|
||||||
"async": "^2.6.3",
|
"async": "^2.6.3",
|
||||||
"async-promises": "^0.2.2",
|
"async-promises": "^0.2.2",
|
||||||
"chalk": "^2.4.2",
|
"chalk": "^2.4.2",
|
||||||
"death": "^1.1.0",
|
"death": "^1.1.0",
|
||||||
"deepmerge": "^2.2.1",
|
"deepmerge": "^2.2.1",
|
||||||
"electron": "^15.5.5",
|
"electron": "17",
|
||||||
"flowa": "^4.0.2",
|
"flowa": "^4.0.2",
|
||||||
"fs-extra": "^5.0.0",
|
"fs-extra": "^5.0.0",
|
||||||
"gif-encoder": "^0.6.1",
|
"gif-encoder": "^0.6.1",
|
||||||
@@ -55,6 +54,7 @@
|
|||||||
"is_js": "^0.9.0",
|
"is_js": "^0.9.0",
|
||||||
"js-yaml": "^3.13.1",
|
"js-yaml": "^3.13.1",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
|
"node-pty-prebuilt-multiarch": "^0.10.1-pre.5",
|
||||||
"performance-now": "^2.1.0",
|
"performance-now": "^2.1.0",
|
||||||
"pngjs": "^3.4.0",
|
"pngjs": "^3.4.0",
|
||||||
"progress": "^2.0.3",
|
"progress": "^2.0.3",
|
||||||
@@ -66,13 +66,13 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"ajv": "^6.12.0",
|
"ajv": "^6.12.0",
|
||||||
"clean-webpack-plugin": "^0.1.19",
|
"clean-webpack-plugin": "^4.0.0",
|
||||||
"css-loader": "^1.0.0",
|
"css-loader": "^1.0.0",
|
||||||
"jquery": "^3.4.1",
|
"jquery": "^3.4.1",
|
||||||
"mini-css-extract-plugin": "^0.4.5",
|
"mini-css-extract-plugin": "^2.6.1",
|
||||||
"terminalizer-player": "^0.4.1",
|
"terminalizer-player": "^0.4.1",
|
||||||
"webpack": "^4.42.0",
|
"webpack": "^5.74.0",
|
||||||
"webpack-cli": "^3.3.11",
|
"webpack-cli": "^4.10.0",
|
||||||
"xterm": "^3.14.5"
|
"xterm": "^3.14.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,31 @@
|
|||||||
/**
|
/**
|
||||||
* Render the frames into PNG images
|
* Render the frames into PNG images
|
||||||
* An electron app, takes one command line argument `step`
|
* An electron app, takes one command line argument `step`
|
||||||
*
|
*
|
||||||
* @author Mohammad Fares <faressoft.com@gmail.com>
|
* @author Mohammad Fares <faressoft.com@gmail.com>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var path = require('path'),
|
const fs = require('fs');
|
||||||
app = require('electron').app,
|
const path = require('path');
|
||||||
BrowserWindow = require('electron').BrowserWindow,
|
const { app } = require('electron');
|
||||||
ipcMain = require('electron').ipcMain,
|
const { BrowserWindow } = require('electron');
|
||||||
os = require('os');
|
const ipcMain = require('electron').ipcMain;
|
||||||
|
const os = require('os');
|
||||||
|
|
||||||
|
let mainWindow = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The temporary rendering directory's path
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
var renderDir = path.join(__dirname, 'frames');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The step option
|
* The step option
|
||||||
* To reduce the number of rendered frames (step > 1)
|
* To reduce the number of rendered frames (step > 1)
|
||||||
* @type {Number}
|
* @type {Number}
|
||||||
*/
|
*/
|
||||||
global.step = process.argv[2] || 1;
|
var step = process.argv[2] || 1;
|
||||||
|
|
||||||
/**
|
|
||||||
* The temporary rendering directory's path
|
|
||||||
* @type {String}
|
|
||||||
*/
|
|
||||||
global.renderDir = path.join(__dirname, 'frames');
|
|
||||||
|
|
||||||
// Hide the Dock for macOS
|
// Hide the Dock for macOS
|
||||||
if (os.platform() == 'darwin') {
|
if (os.platform() == 'darwin') {
|
||||||
@@ -36,33 +39,53 @@ app.on('ready', createWindow);
|
|||||||
* Create a hidden browser window and load the rendering page
|
* Create a hidden browser window and load the rendering page
|
||||||
*/
|
*/
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
|
|
||||||
// Create a browser window
|
// Create a browser window
|
||||||
var win = new BrowserWindow({
|
mainWindow = new BrowserWindow({
|
||||||
show: false,
|
show: false,
|
||||||
width: 8000,
|
width: 8000,
|
||||||
height: 8000,
|
height: 8000,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: true
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load index.html
|
// Load index.html
|
||||||
win.loadURL('file://' + __dirname + '/index.html');
|
mainWindow.loadURL('file://' + __dirname + '/index.html');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A callback function for the event:
|
* A callback function for the event:
|
||||||
* When a frame is captured
|
* getOptions to request the options that need
|
||||||
*
|
* to be passed to the renderer
|
||||||
|
*
|
||||||
* @param {Object} event
|
* @param {Object} event
|
||||||
* @param {Number} recordIndex
|
|
||||||
*/
|
*/
|
||||||
ipcMain.on('captured', function(event, recordIndex) {
|
ipcMain.handle('getOptions', function () {
|
||||||
|
return { step };
|
||||||
|
});
|
||||||
|
|
||||||
console.log(recordIndex);
|
/**
|
||||||
|
* A callback function for the event:
|
||||||
|
* capturePage
|
||||||
|
*
|
||||||
|
* @param {Object} event
|
||||||
|
*/
|
||||||
|
ipcMain.handle('capturePage', async function (event, captureRect, frameIndex) {
|
||||||
|
const img = await mainWindow.webContents.capturePage(captureRect);
|
||||||
|
const outputPath = path.join(renderDir, frameIndex + '.png');
|
||||||
|
fs.writeFileSync(outputPath, img.toPNG());
|
||||||
|
console.log(frameIndex);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback function for the event:
|
||||||
|
* Close
|
||||||
|
*
|
||||||
|
* @param {Object} event
|
||||||
|
* @param {String} error
|
||||||
|
*/
|
||||||
|
ipcMain.on('close', function (event, error) {
|
||||||
|
mainWindow.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,8 +95,6 @@ ipcMain.on('captured', function(event, recordIndex) {
|
|||||||
* @param {Object} event
|
* @param {Object} event
|
||||||
* @param {String} error
|
* @param {String} error
|
||||||
*/
|
*/
|
||||||
ipcMain.on('error', function(event, error) {
|
ipcMain.on('error', function (event, error) {
|
||||||
|
|
||||||
process.stderr.write(error);
|
process.stderr.write(error);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
19
render/preload.js
Normal file
19
render/preload.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
const { contextBridge, ipcRenderer } = require('electron');
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('app', {
|
||||||
|
close() {
|
||||||
|
return ipcRenderer.send('close');
|
||||||
|
},
|
||||||
|
getOptions() {
|
||||||
|
return ipcRenderer.invoke('getOptions');
|
||||||
|
},
|
||||||
|
capturePage(captureRect, frameIndex) {
|
||||||
|
console.log('prelaod > capturePage');
|
||||||
|
return ipcRenderer.invoke('capturePage', captureRect, frameIndex);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Catch all unhandled errors
|
||||||
|
window.onerror = function (error) {
|
||||||
|
ipcRenderer.send('error', error);
|
||||||
|
};
|
||||||
@@ -1,19 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* Terminalizer
|
* Terminalizer
|
||||||
*
|
*
|
||||||
* @author Mohammad Fares <faressoft.com@gmail.com>
|
* @author Mohammad Fares <faressoft.com@gmail.com>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var fs = require('fs'),
|
import async from 'async';
|
||||||
path = require('path'),
|
import 'terminalizer-player';
|
||||||
async = require('async'),
|
|
||||||
remote = require('electron').remote,
|
|
||||||
ipcRenderer = require('electron').ipcRenderer,
|
|
||||||
terminalizerPlayer = require('terminalizer-player');
|
|
||||||
var currentWindow = remote.getCurrentWindow(),
|
|
||||||
capturePage = currentWindow.webContents.capturePage,
|
|
||||||
step = remote.getGlobal('step'),
|
|
||||||
renderDir = remote.getGlobal('renderDir');
|
|
||||||
|
|
||||||
// Styles
|
// Styles
|
||||||
import '../css/app.css';
|
import '../css/app.css';
|
||||||
@@ -26,119 +18,98 @@ import 'xterm/dist/xterm.css';
|
|||||||
*/
|
*/
|
||||||
var stepsCounter = 0;
|
var stepsCounter = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rendering options
|
||||||
|
*/
|
||||||
|
var options = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A callback function for the event:
|
* A callback function for the event:
|
||||||
* When the document is loaded
|
* When the document is loaded
|
||||||
*/
|
*/
|
||||||
$(document).ready(function() {
|
$(document).ready(async () => {
|
||||||
|
options = await app.getOptions();
|
||||||
|
|
||||||
// Initialize the terminalizer plugin
|
// Initialize the terminalizer plugin
|
||||||
$('#terminal').terminalizer({
|
$('#terminal').terminalizer({
|
||||||
recordingFile: 'data.json',
|
recordingFile: 'data.json',
|
||||||
autoplay: true,
|
autoplay: true,
|
||||||
controls: false
|
controls: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A callback function for the event:
|
* A callback function for the event:
|
||||||
* When the terminal playing is started
|
* When the terminal playing is started
|
||||||
*/
|
*/
|
||||||
$('#terminal').one('playingStarted', function() {
|
$('#terminal').one('playingStarted', function () {
|
||||||
|
|
||||||
var terminalizer = $('#terminal').data('terminalizer');
|
var terminalizer = $('#terminal').data('terminalizer');
|
||||||
|
|
||||||
// Pause the playing
|
// Pause the playing
|
||||||
terminalizer.pause();
|
terminalizer.pause();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A callback function for the event:
|
* A callback function for the event:
|
||||||
* When the terminal playing is paused
|
* When the terminal playing is paused
|
||||||
*/
|
*/
|
||||||
$('#terminal').one('playingPaused', function() {
|
$('#terminal').one('playingPaused', function () {
|
||||||
|
|
||||||
var terminalizer = $('#terminal').data('terminalizer');
|
var terminalizer = $('#terminal').data('terminalizer');
|
||||||
|
|
||||||
// Reset the terminal
|
// Reset the terminal
|
||||||
terminalizer._terminal.reset();
|
terminalizer._terminal.reset();
|
||||||
|
|
||||||
// When the terminal's reset is done
|
// When the terminal's reset is done
|
||||||
$('#terminal').one('rendered', render);
|
$('#terminal').one('rendered', render);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render each frame and capture it
|
* Render each frame and capture it
|
||||||
*/
|
*/
|
||||||
function render() {
|
function render() {
|
||||||
|
|
||||||
var terminalizer = $('#terminal').data('terminalizer');
|
var terminalizer = $('#terminal').data('terminalizer');
|
||||||
var framesCount = terminalizer.getFramesCount();
|
var framesCount = terminalizer.getFramesCount();
|
||||||
|
|
||||||
// Foreach frame
|
// Foreach frame
|
||||||
async.timesSeries(framesCount, function(frameIndex, next) {
|
async.timesSeries(
|
||||||
|
framesCount,
|
||||||
|
function (frameIndex, next) {
|
||||||
|
terminalizer._renderFrame(frameIndex, true, function () {
|
||||||
|
capture(frameIndex, next);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function (error) {
|
||||||
|
if (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
|
||||||
terminalizer._renderFrame(frameIndex, true, function() {
|
app.close();
|
||||||
capture(frameIndex, next);
|
|
||||||
});
|
|
||||||
|
|
||||||
}, function(error) {
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
throw new Error(error);
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
currentWindow.close();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Capture the current frame
|
* Capture the current frame
|
||||||
*
|
*
|
||||||
* @param {Number} frameIndex
|
* @param {Number} frameIndex
|
||||||
* @param {Function} callback
|
* @param {Function} callback
|
||||||
*/
|
*/
|
||||||
function capture(frameIndex, callback) {
|
function capture(frameIndex, callback) {
|
||||||
|
|
||||||
var width = $('#terminal').width();
|
var width = $('#terminal').width();
|
||||||
var height = $('#terminal').height();
|
var height = $('#terminal').height();
|
||||||
var captureRect = {x: 0, y: 0, width: width, height: height};
|
var captureRect = { x: 0, y: 0, width: width, height: height };
|
||||||
|
|
||||||
if (stepsCounter != 0) {
|
if (stepsCounter != 0) {
|
||||||
stepsCounter = (stepsCounter + 1) % step;
|
stepsCounter = (stepsCounter + 1) % options.step;
|
||||||
return callback();
|
return callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
stepsCounter = (stepsCounter + 1) % step;
|
stepsCounter = (stepsCounter + 1) % options.step;
|
||||||
|
|
||||||
capturePage(captureRect).then((img) => {
|
|
||||||
|
|
||||||
var outputPath = path.join(renderDir, frameIndex + '.png');
|
|
||||||
|
|
||||||
fs.writeFileSync(outputPath, img.toPNG());
|
|
||||||
ipcRenderer.send('captured', frameIndex);
|
|
||||||
callback();
|
|
||||||
|
|
||||||
}).catch((err) => {
|
|
||||||
|
|
||||||
throw new err;
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
app
|
||||||
|
.capturePage(captureRect, frameIndex)
|
||||||
|
.then(callback)
|
||||||
|
.catch((err) => {
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Catch all unhandled errors
|
|
||||||
*
|
|
||||||
* @param {String} error
|
|
||||||
*/
|
|
||||||
window.onerror = function(error) {
|
|
||||||
|
|
||||||
ipcRenderer.send('error', error);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const path = require('path');
|
|||||||
|
|
||||||
// Extract CSS into separate files
|
// Extract CSS into separate files
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||||
|
|
||||||
// Global variables
|
// Global variables
|
||||||
const globals = {
|
const globals = {
|
||||||
@@ -11,36 +11,35 @@ const globals = {
|
|||||||
jQuery: 'jquery',
|
jQuery: 'jquery',
|
||||||
Terminal: ['xterm', 'Terminal'],
|
Terminal: ['xterm', 'Terminal'],
|
||||||
'window.jQuery': 'jquery',
|
'window.jQuery': 'jquery',
|
||||||
'window.$': 'jquery'
|
'window.$': 'jquery',
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
target: 'electron-renderer',
|
target: 'electron-renderer',
|
||||||
entry: {
|
entry: {
|
||||||
app: './render/src/js/app.js'
|
app: './render/src/js/app.js',
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
filename: 'js/[name].js',
|
filename: 'js/[name].js',
|
||||||
path: path.resolve(__dirname, 'render/dist'),
|
path: path.resolve(__dirname, 'render/dist'),
|
||||||
publicPath: '/dist/'
|
publicPath: '/dist/',
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new CleanWebpackPlugin(['./render/dist'], {verbose: false}),
|
new CleanWebpackPlugin({
|
||||||
|
cleanBeforeEveryBuildPatterns: [path.join(__dirname, 'render/dist')],
|
||||||
|
}),
|
||||||
new webpack.ProvidePlugin(globals),
|
new webpack.ProvidePlugin(globals),
|
||||||
new MiniCssExtractPlugin({filename: 'css/[name].css'}),
|
new MiniCssExtractPlugin({ filename: 'css/[name].css' }),
|
||||||
new webpack.NoEmitOnErrorsPlugin()
|
new webpack.NoEmitOnErrorsPlugin(),
|
||||||
],
|
],
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
// CSS
|
// CSS
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
use: [
|
use: [{ loader: MiniCssExtractPlugin.loader }, { loader: 'css-loader' }],
|
||||||
{loader: MiniCssExtractPlugin.loader},
|
},
|
||||||
{loader: 'css-loader'}
|
],
|
||||||
],
|
},
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user