seitime-frappe/frappe/build.js
Achilles Rasquinha 005cfe3dc8 🎉 NEW Frappe Chat (#4612)
* added doctypes, created frappe chat ui

* added component layout with state-like abilities, added apis

* updated user doctype, moved from state-like feature and component abstraction

* added room component

* fixed publish_realtime with after_commit = True

* created room component and searchbar

* minor fix

* functional message parsing

* update

* Added Chat Profile

* added chat message

* more changes into chat room

* fixed APIs, added client side scripting

* added chat message attachements, more doc updates

* Brand New UI with socket io room integration

* completed socketio integration. off to room subscription and publish

* realtime room update

* raw update

* initialized docs, added p2p connection for call tests

* updated docs

* added coverage, updated api for ease of use

* raw commit

* added test cases

* Chat Room updates and new room creation

* added chat group creation

* added collapsible plugin

* toggable room view

* updated

* [RAW]

* updated UI for chat

* Deleted Previous Chat Page

* moved from frappe.Chat.Widget to frappe.Chat

* modularized frappe-fab

* added more docstrings

* tried adding conversation tones

* Added conversation_tones and refurbished chat popper

* modified frappe.ui.Dialog, moved from AppBar to ActionBar, responsive for Mobile 💃

* moved RoomList item namespace

* Configurable Desktop update, moved profile updates to on_update

* added state change listeners

* removed AppBar to ActionBar customizable 💃

* added destroy method

* removed coverage, refactored group creation

* Successful Chat Rooms and Group creation

* sort rows based on last_message_timestamp or creation

* added frappe._.compare

* removed redundant less variables

* Chat Room back button with custom routing and destroy methods

* Added EmojiPicker

* fixed multiple dialog render

* setup quick access

* added chat chime, functional chat message list updates at room list

* deleted package-lock.json

* realtime date updates

* updated chat message list

* functional message render and updates

* added track seen

* added typing status

* updated typing status

* valid typing statuses and quick search

* Functional Quick Search

* reverted fix

* some more cleanup and promisifed

* fixed hints close on click

* updated fab boldness

* close popper on click panel

* close popper on click panel

* reverted octicon-lg, fixed popper heading click

* new frappe capture

* removed webcamjs

* added uploader and capture

* removed chat FAB, added as notification instead

* on message update
2017-12-28 18:58:43 +05:30

360 lines
9.1 KiB
JavaScript

/*eslint-disable no-console */
const path = require('path');
const fs = require('fs');
const babel = require('babel-core');
const less = require('less');
const chokidar = require('chokidar');
const path_join = path.resolve;
// for file watcher
const app = require('express')();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const touch = require("touch");
// basic setup
const sites_path = path_join(__dirname, '..', '..', '..', 'sites');
const apps_path = path_join(__dirname, '..', '..', '..', 'apps'); // the apps folder
const apps_contents = fs.readFileSync(path_join(sites_path, 'apps.txt'), 'utf8');
const apps = apps_contents.split('\n');
const app_paths = apps.map(app => path_join(apps_path, app, app)) // base_path of each app
const assets_path = path_join(sites_path, 'assets');
let build_map = make_build_map();
let compiled_js_cache = {}; // cache each js file after it is compiled
const file_watcher_port = get_conf().file_watcher_port;
// command line args
const action = process.argv[2] || '--build';
if (['--build', '--watch'].indexOf(action) === -1) {
console.log('Invalid argument: ', action);
process.exit();
}
if (action === '--build') {
const minify = process.argv[3] === '--minify' ? true : false;
build(minify);
}
if (action === '--watch') {
watch();
}
function build(minify) {
for (const output_path in build_map) {
pack(output_path, build_map[output_path], minify);
}
touch(path_join(sites_path, '.build'), {force:true});
}
let socket_connection = false;
function watch() {
http.listen(file_watcher_port, function () {
console.log('file watching on *:', file_watcher_port);
});
if (process.env.CI) {
// don't watch inside CI
return;
}
compile_less().then(() => {
build();
watch_less(function (filename) {
if(socket_connection) {
io.emit('reload_css', filename);
}
});
watch_js(//function (filename) {
// if(socket_connection) {
// io.emit('reload_js', filename);
// }
//}
);
watch_build_json();
});
io.on('connection', function (socket) {
socket_connection = true;
socket.on('disconnect', function() {
socket_connection = false;
})
});
}
function pack(output_path, inputs, minify, file_changed) {
let output_txt = '';
for (const file of inputs) {
if (!fs.existsSync(file)) {
console.log('File not found: ', file);
continue;
}
let force_compile = false;
if (file_changed) {
// if file_changed is passed and is equal to file, force_compile it
force_compile = file_changed === file;
}
let file_content = get_compiled_file(file, output_path, minify, force_compile);
if(!minify) {
output_txt += `\n/*\n *\t${file}\n */\n`
}
output_txt += file_content;
output_txt = output_txt.replace(/['"]use strict['"];/, '');
}
const target = path_join(assets_path, output_path);
try {
fs.writeFileSync(target, output_txt);
console.log(`Wrote ${output_path} - ${get_file_size(target)}`);
return target;
} catch (e) {
console.log('Error writing to file', output_path);
console.log(e);
}
}
function get_compiled_file(file, output_path, minify, force_compile) {
const output_type = output_path.split('.').pop();
let file_content;
if (force_compile === false) {
// force compile is false
// attempt to get from cache
file_content = compiled_js_cache[file];
if (file_content) {
return file_content;
}
}
file_content = fs.readFileSync(file, 'utf-8');
if (file.endsWith('.html') && output_type === 'js') {
file_content = html_to_js_template(file, file_content);
}
if(file.endsWith('class.js')) {
file_content = minify_js(file_content, file);
}
if (minify && file.endsWith('.js') && !file.includes('/lib/') && output_type === 'js' && !file.endsWith('class.js')) {
file_content = babelify(file_content, file, minify);
}
compiled_js_cache[file] = file_content;
return file_content;
}
function babelify(content, path, minify) {
let presets = ['env'];
const plugins = ['transform-object-rest-spread']
// Minification doesn't work when loading Frappe Desk
// Avoid for now, trace the error and come back.
try {
return babel.transform(content, {
presets: presets,
plugins: plugins,
comments: false
}).code;
} catch (e) {
console.log('Cannot babelify', path);
console.log(e);
return content;
}
}
function minify_js(content, path) {
try {
return babel.transform(content, {
comments: false
}).code;
} catch (e) {
console.log('Cannot minify', path);
console.log(e);
return content;
}
}
function make_build_map() {
const build_map = {};
for (const app_path of app_paths) {
const build_json_path = path_join(app_path, 'public', 'build.json');
if (!fs.existsSync(build_json_path)) continue;
let build_json = fs.readFileSync(build_json_path);
try {
build_json = JSON.parse(build_json);
} catch (e) {
console.log(e);
continue;
}
for (const target in build_json) {
const sources = build_json[target];
const new_sources = [];
for (const source of sources) {
const s = path_join(app_path, source);
new_sources.push(s);
}
if (new_sources.length)
build_json[target] = new_sources;
else
delete build_json[target];
}
Object.assign(build_map, build_json);
}
return build_map;
}
function compile_less() {
return new Promise(function (resolve) {
const promises = [];
for (const app_path of app_paths) {
const public_path = path_join(app_path, 'public');
const less_path = path_join(public_path, 'less');
if (!fs.existsSync(less_path)) continue;
const files = fs.readdirSync(less_path);
for (const file of files) {
if(file.includes('variables.less')) continue;
promises.push(compile_less_file(file, less_path, public_path))
}
}
Promise.all(promises).then(() => {
console.log('Less files compiled');
resolve();
});
});
}
function compile_less_file(file, less_path, public_path) {
const file_content = fs.readFileSync(path_join(less_path, file), 'utf8');
const output_file = file.split('.')[0] + '.css';
console.log('compiling', file);
return less.render(file_content, {
paths: [less_path],
filename: file,
sourceMap: false
}).then(output => {
const out_css = path_join(public_path, 'css', output_file);
fs.writeFileSync(out_css, output.css);
return out_css;
}).catch(e => {
console.log('Error compiling ', file);
console.log(e);
});
}
function watch_less(ondirty) {
const less_paths = app_paths.map(path => path_join(path, 'public', 'less'));
const to_watch = filter_valid_paths(less_paths);
chokidar.watch(to_watch).on('change', (filename) => {
console.log(filename, 'dirty');
var last_index = filename.lastIndexOf('/');
const less_path = filename.slice(0, last_index);
const public_path = path_join(less_path, '..');
filename = filename.split('/').pop();
compile_less_file(filename, less_path, public_path)
.then(css_file_path => {
// build the target css file for which this css file is input
for (const target in build_map) {
const sources = build_map[target];
if (sources.includes(css_file_path)) {
pack(target, sources);
ondirty && ondirty(target);
break;
}
}
});
touch(path_join(sites_path, '.build'), {force:true});
});
}
function watch_js(ondirty) {
chokidar.watch([
path_join(apps_path, '**', '*.js'),
path_join(apps_path, '**', '*.html')
]).on('change', (filename) => {
// build the target js file for which this js/html file is input
for (const target in build_map) {
const sources = build_map[target];
if (sources.includes(filename)) {
console.log(filename, 'dirty');
pack(target, sources, null, filename);
ondirty && ondirty(target);
// break;
}
}
touch(path_join(sites_path, '.build'), {force:true});
});
}
function watch_build_json() {
const build_json_paths = app_paths.map(path => path_join(path, 'public', 'build.json'));
const to_watch = filter_valid_paths(build_json_paths);
chokidar.watch(to_watch).on('change', (filename) => {
console.log(filename, 'updated');
build_map = make_build_map();
});
}
function filter_valid_paths(paths) {
return paths.filter(path => fs.existsSync(path));
}
function html_to_js_template(path, content) {
let key = path.split('/');
key = key[key.length - 1];
key = key.split('.')[0];
content = scrub_html_template(content);
return `frappe.templates['${key}'] = '${content}';\n`;
}
function scrub_html_template(content) {
content = content.replace(/\s/g, ' ');
content = content.replace(/(<!--.*?-->)/g, '');
return content.replace("'", "\'");
}
function get_file_size(filepath) {
const stats = fs.statSync(filepath);
const size = stats.size;
// convert it to humanly readable format.
const i = Math.floor(Math.log(size) / Math.log(1024));
return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'KB', 'MB', 'GB', 'TB'][i];
}
function get_conf() {
// defaults
var conf = {
file_watcher_port: 6787
};
var read_config = function(path) {
if (!fs.existsSync(path)) return;
var bench_config = JSON.parse(fs.readFileSync(path));
for (var key in bench_config) {
if (bench_config[key]) {
conf[key] = bench_config[key];
}
}
}
read_config(path_join(sites_path, 'common_site_config.json'));
return conf;
}