[utils]load_image() to handle broken images, ReviewArea, remove old files (#5263)
* [image] load_image() to handle broken images * add reviewArea component * remove frappe.ui.BaseList * remove old build.js * title in page and list
This commit is contained in:
parent
bc12dc57d4
commit
747e012d59
9 changed files with 175 additions and 913 deletions
360
frappe/build.js
360
frappe/build.js
|
|
@ -1,360 +0,0 @@
|
|||
/*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;
|
||||
}
|
||||
|
|
@ -223,6 +223,17 @@ frappe.run_serially = function(tasks) {
|
|||
return result;
|
||||
};
|
||||
|
||||
frappe.load_image = (src, onload, onerror, preprocess = () => {}) => {
|
||||
var tester = new Image();
|
||||
tester.onload = function() {
|
||||
onload(this);
|
||||
};
|
||||
tester.onerror = onerror;
|
||||
|
||||
preprocess(tester);
|
||||
tester.src = src;
|
||||
}
|
||||
|
||||
frappe.timeout = seconds => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve(), seconds * 1000);
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ frappe.ui.form.ControlButton = frappe.ui.form.ControlData.extend({
|
|||
this.df.click();
|
||||
}
|
||||
},
|
||||
hide() {
|
||||
this.$input.hide();
|
||||
},
|
||||
set_input_areas: function() {
|
||||
this._super();
|
||||
$(this.disp_area).removeClass().addClass("hide");
|
||||
|
|
|
|||
|
|
@ -144,11 +144,15 @@ frappe.views.BaseList = class BaseList {
|
|||
}
|
||||
|
||||
setup_page_head() {
|
||||
this.page.set_title(this.page_title);
|
||||
this.set_title();
|
||||
this.set_menu_items();
|
||||
this.set_breadcrumbs();
|
||||
}
|
||||
|
||||
set_title() {
|
||||
this.page.set_title(this.page_title);
|
||||
}
|
||||
|
||||
set_menu_items() {
|
||||
const $secondary_action = this.page.set_secondary_action(
|
||||
this.secondary_action.label,
|
||||
|
|
@ -309,7 +313,9 @@ frappe.views.BaseList = class BaseList {
|
|||
get_filters_for_args() {
|
||||
// filters might have a fifth param called hidden,
|
||||
// we don't want to pass that server side
|
||||
return this.filter_area.get().map(filter => filter.slice(0, 4));
|
||||
return this.filter_area
|
||||
? this.filter_area.get().map(filter => filter.slice(0, 4))
|
||||
: [];
|
||||
}
|
||||
|
||||
get_args() {
|
||||
|
|
|
|||
|
|
@ -624,7 +624,7 @@ frappe.utils = {
|
|||
return result;
|
||||
};
|
||||
},
|
||||
debounce: function debounce(func, wait, immediate) {
|
||||
debounce: function(func, wait, immediate) {
|
||||
var timeout;
|
||||
return function() {
|
||||
var context = this, args = arguments;
|
||||
|
|
|
|||
|
|
@ -1,532 +0,0 @@
|
|||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// MIT License. See license.txt
|
||||
|
||||
// new re-re-factored Listing object
|
||||
// now called BaseList
|
||||
//
|
||||
// opts:
|
||||
// parent
|
||||
|
||||
// method (method to call on server)
|
||||
// args (additional args to method)
|
||||
// get_args (method to return args as dict)
|
||||
|
||||
// show_filters [false]
|
||||
// doctype
|
||||
// filter_fields (if given, this list is rendered, else built from doctype)
|
||||
|
||||
// query or get_query (will be deprecated)
|
||||
// query_max
|
||||
// buttons_in_frame
|
||||
|
||||
// no_result_message ("No result")
|
||||
|
||||
// page_length (20)
|
||||
// hide_refresh (False)
|
||||
// no_toolbar
|
||||
// new_doctype
|
||||
// [function] render_row(parent, data)
|
||||
// [function] onrun
|
||||
// no_loading (no ajax indicator)
|
||||
|
||||
frappe.provide('frappe.ui');
|
||||
|
||||
frappe.ui.BaseList = Class.extend({
|
||||
init: function (opts) {
|
||||
this.opts = opts || {};
|
||||
this.set_defaults();
|
||||
if (opts) {
|
||||
this.make();
|
||||
}
|
||||
},
|
||||
set_defaults: function () {
|
||||
this.page_length = 20;
|
||||
this.start = 0;
|
||||
this.data = [];
|
||||
},
|
||||
make: function (opts) {
|
||||
if (opts) {
|
||||
this.opts = opts;
|
||||
}
|
||||
this.prepare_opts();
|
||||
|
||||
$.extend(this, this.opts);
|
||||
|
||||
// make dom
|
||||
this.wrapper = $(frappe.render_template('listing', this.opts));
|
||||
this.parent.append(this.wrapper);
|
||||
|
||||
this.set_events();
|
||||
|
||||
if (this.page) {
|
||||
this.wrapper.find('.list-toolbar-wrapper').hide();
|
||||
}
|
||||
|
||||
if (this.show_filters) {
|
||||
this.make_filters();
|
||||
}
|
||||
},
|
||||
prepare_opts: function () {
|
||||
if (this.opts.new_doctype) {
|
||||
if (!frappe.boot.user.can_create.includes(this.opts.new_doctype)) {
|
||||
this.opts.new_doctype = null;
|
||||
}
|
||||
}
|
||||
if (!this.opts.no_result_message) {
|
||||
this.opts.no_result_message = __('Nothing to show');
|
||||
}
|
||||
if (!this.opts.page_length) {
|
||||
this.opts.page_length = this.user_settings && this.user_settings.limit || 20;
|
||||
}
|
||||
this.opts._more = __('More');
|
||||
},
|
||||
add_button: function (label, click, icon) {
|
||||
if (this.page) {
|
||||
return this.page.add_menu_item(label, click, icon)
|
||||
} else {
|
||||
this.wrapper.find('.list-toolbar-wrapper').removeClass('hide');
|
||||
return $('<button class="btn btn-default"></button>')
|
||||
.appendTo(this.wrapper.find('.list-toolbar'))
|
||||
.html((icon ? ('<i class="' + icon + '"></i> ') : '') + label)
|
||||
.click(click);
|
||||
}
|
||||
},
|
||||
set_events: function () {
|
||||
var me = this;
|
||||
|
||||
// next page
|
||||
this.wrapper.find('.btn-more').click(function () {
|
||||
me.run(true);
|
||||
});
|
||||
|
||||
this.wrapper.find(".btn-group-paging").on('click', '.btn', function () {
|
||||
me.page_length = cint($(this).attr("data-value"));
|
||||
|
||||
me.wrapper.find(".btn-group-paging .btn-info").removeClass("btn-info");
|
||||
$(this).addClass("btn-info");
|
||||
|
||||
// always reset when changing list page length
|
||||
me.run();
|
||||
});
|
||||
|
||||
// select the correct page length
|
||||
if (this.opts.page_length !== 20) {
|
||||
this.wrapper.find(".btn-group-paging .btn-info").removeClass("btn-info");
|
||||
this.wrapper
|
||||
.find(".btn-group-paging .btn[data-value='" + this.opts.page_length + "']")
|
||||
.addClass('btn-info');
|
||||
}
|
||||
|
||||
// title
|
||||
if (this.title) {
|
||||
this.wrapper.find('h3').html(this.title).show();
|
||||
}
|
||||
|
||||
// new
|
||||
this.set_primary_action();
|
||||
|
||||
if (me.no_toolbar || me.hide_toolbar) {
|
||||
me.wrapper.find('.list-toolbar-wrapper').hide();
|
||||
}
|
||||
},
|
||||
|
||||
set_primary_action: function () {
|
||||
var me = this;
|
||||
if (this.new_doctype) {
|
||||
this.page.set_primary_action(
|
||||
__("New"),
|
||||
me.make_new_doc.bind(me, me.new_doctype),
|
||||
"octicon octicon-plus"
|
||||
);
|
||||
} else {
|
||||
this.page.clear_primary_action();
|
||||
}
|
||||
},
|
||||
|
||||
make_new_doc: function (doctype) {
|
||||
var me = this;
|
||||
frappe.model.with_doctype(doctype, function () {
|
||||
if (me.custom_new_doc) {
|
||||
me.custom_new_doc(doctype);
|
||||
} else {
|
||||
if (me.filter_list) {
|
||||
frappe.route_options = {};
|
||||
me.filter_list.get_filters().forEach(function (f, i) {
|
||||
if (f[2] === "=" && !frappe.model.std_fields_list.includes(f[1])) {
|
||||
frappe.route_options[f[1]] = f[3];
|
||||
}
|
||||
});
|
||||
}
|
||||
frappe.new_doc(doctype, true);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
make_filters: function () {
|
||||
this.make_standard_filters();
|
||||
|
||||
this.filter_list = new frappe.ui.FilterList({
|
||||
base_list: this,
|
||||
parent: this.wrapper.find('.list-filters').show(),
|
||||
doctype: this.doctype,
|
||||
filter_fields: this.filter_fields,
|
||||
default_filters: this.default_filters || []
|
||||
});
|
||||
// default filter for submittable doctype
|
||||
if (frappe.model.is_submittable(this.doctype)) {
|
||||
this.filter_list.add_filter(this.doctype, "docstatus", "!=", 2);
|
||||
}
|
||||
},
|
||||
|
||||
make_standard_filters: function() {
|
||||
var me = this;
|
||||
if (this.standard_filters_added) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.meta) {
|
||||
var filter_count = 1;
|
||||
if(this.is_list_view) {
|
||||
$(`<span class="octicon octicon-search text-muted small"></span>`)
|
||||
.prependTo(this.page.page_form);
|
||||
}
|
||||
this.page.add_field({
|
||||
fieldtype: 'Data',
|
||||
label: 'ID',
|
||||
condition: 'like',
|
||||
fieldname: 'name',
|
||||
onchange: () => { me.refresh(true); }
|
||||
});
|
||||
|
||||
this.meta.fields.forEach(function(df, i) {
|
||||
if(df.in_standard_filter && !frappe.model.no_value_type.includes(df.fieldtype)) {
|
||||
let options = df.options;
|
||||
let condition = '=';
|
||||
let fieldtype = df.fieldtype;
|
||||
if (['Text', 'Small Text', 'Text Editor', 'Data'].includes(fieldtype)) {
|
||||
fieldtype = 'Data';
|
||||
condition = 'like';
|
||||
}
|
||||
if(df.fieldtype == "Select" && df.options) {
|
||||
options = df.options.split("\n");
|
||||
if(options.length > 0 && options[0] != "") {
|
||||
options.unshift("");
|
||||
options = options.join("\n");
|
||||
}
|
||||
}
|
||||
let f = me.page.add_field({
|
||||
fieldtype: fieldtype,
|
||||
label: __(df.label),
|
||||
options: options,
|
||||
fieldname: df.fieldname,
|
||||
condition: condition,
|
||||
onchange: () => {me.refresh(true);}
|
||||
});
|
||||
filter_count ++;
|
||||
if (filter_count > 3) {
|
||||
$(f.wrapper).addClass('hidden-sm').addClass('hidden-xs');
|
||||
}
|
||||
if (filter_count > 5) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.standard_filters_added = true;
|
||||
},
|
||||
|
||||
update_standard_filters: function(filters) {
|
||||
let me = this;
|
||||
for(let key in this.page.fields_dict) {
|
||||
let field = this.page.fields_dict[key];
|
||||
let value = field.get_value();
|
||||
if (value) {
|
||||
if (field.df.condition==='like' && !value.includes('%')) {
|
||||
value = '%' + value + '%';
|
||||
}
|
||||
filters.push([
|
||||
me.doctype,
|
||||
field.df.fieldname,
|
||||
field.df.condition || '=',
|
||||
value
|
||||
]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
clear: function () {
|
||||
this.data = [];
|
||||
this.wrapper.find('.result-list').empty();
|
||||
this.wrapper.find('.result').show();
|
||||
this.wrapper.find('.no-result').hide();
|
||||
this.start = 0;
|
||||
this.onreset && this.onreset();
|
||||
},
|
||||
|
||||
set_filters_from_route_options: function ({clear_filters=true} = {}) {
|
||||
var me = this;
|
||||
if(this.filter_list && clear_filters) {
|
||||
this.filter_list.clear_filters();
|
||||
}
|
||||
|
||||
for(var field in frappe.route_options) {
|
||||
var value = frappe.route_options[field];
|
||||
var doctype = null;
|
||||
|
||||
// if `Child DocType.fieldname`
|
||||
if (field.includes(".")) {
|
||||
doctype = field.split(".")[0];
|
||||
field = field.split(".")[1];
|
||||
}
|
||||
|
||||
// find the table in which the key exists
|
||||
// for example the filter could be {"item_code": "X"}
|
||||
// where item_code is in the child table.
|
||||
|
||||
// we can search all tables for mapping the doctype
|
||||
if (!doctype) {
|
||||
doctype = frappe.meta.get_doctype_for_field(me.doctype, field);
|
||||
}
|
||||
|
||||
if (doctype && me.filter_list) {
|
||||
if ($.isArray(value)) {
|
||||
me.filter_list.add_filter(doctype, field, value[0], value[1]);
|
||||
} else {
|
||||
me.filter_list.add_filter(doctype, field, "=", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
frappe.route_options = null;
|
||||
},
|
||||
|
||||
run: function(more) {
|
||||
setTimeout(() => this._run(more), 100);
|
||||
},
|
||||
|
||||
_run: function (more) {
|
||||
var me = this;
|
||||
if (!more) {
|
||||
this.start = 0;
|
||||
this.onreset && this.onreset();
|
||||
}
|
||||
|
||||
var args = this.get_call_args();
|
||||
this.save_user_settings_locally(args);
|
||||
|
||||
// user_settings are saved by db_query.py when dirty
|
||||
$.extend(args, {
|
||||
user_settings: frappe.model.user_settings[this.doctype]
|
||||
});
|
||||
|
||||
return frappe.call({
|
||||
method: this.opts.method || 'frappe.desk.query_builder.runquery',
|
||||
type: "GET",
|
||||
freeze: this.opts.freeze !== undefined ? this.opts.freeze : true,
|
||||
args: args,
|
||||
callback: function (r) {
|
||||
me.dirty = false;
|
||||
me.render_results(r);
|
||||
},
|
||||
no_spinner: this.opts.no_loading
|
||||
});
|
||||
},
|
||||
save_user_settings_locally: function (args) {
|
||||
if (this.opts.save_user_settings && this.doctype && !this.docname) {
|
||||
// save list settings locally
|
||||
var user_settings = frappe.model.user_settings[this.doctype];
|
||||
var different = false;
|
||||
|
||||
if (!user_settings) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!frappe.utils.arrays_equal(args.filters, user_settings.filters)) {
|
||||
// settings are dirty if filters change
|
||||
user_settings.filters = args.filters;
|
||||
different = true;
|
||||
}
|
||||
|
||||
if (user_settings.order_by !== args.order_by) {
|
||||
user_settings.order_by = args.order_by;
|
||||
different = true;
|
||||
}
|
||||
|
||||
if (user_settings.limit !== args.limit_page_length) {
|
||||
user_settings.limit = args.limit_page_length || 20
|
||||
different = true;
|
||||
}
|
||||
|
||||
// save fields in list settings
|
||||
if (args.save_user_settings_fields) {
|
||||
user_settings.fields = args.fields;
|
||||
}
|
||||
|
||||
if (different) {
|
||||
user_settings.updated_on = moment().toString();
|
||||
}
|
||||
}
|
||||
},
|
||||
get_call_args: function () {
|
||||
// load query
|
||||
if (!this.method) {
|
||||
var query = this.get_query && this.get_query() || this.query;
|
||||
query = this.add_limits(query);
|
||||
var args = {
|
||||
query_max: this.query_max,
|
||||
as_dict: 1
|
||||
}
|
||||
args.simple_query = query;
|
||||
} else {
|
||||
var args = {
|
||||
start: this.start,
|
||||
page_length: this.page_length
|
||||
}
|
||||
}
|
||||
|
||||
// append user-defined arguments
|
||||
if (this.args)
|
||||
$.extend(args, this.args)
|
||||
|
||||
if (this.get_args) {
|
||||
$.extend(args, this.get_args());
|
||||
}
|
||||
return args;
|
||||
},
|
||||
render_results: function (r) {
|
||||
if (this.start === 0)
|
||||
this.clear();
|
||||
|
||||
this.wrapper.find('.btn-more, .list-loading').hide();
|
||||
|
||||
var values = [];
|
||||
|
||||
if (r.message) {
|
||||
values = this.get_values_from_response(r.message);
|
||||
}
|
||||
|
||||
var show_results = true;
|
||||
if(this.show_no_result) {
|
||||
if($.isFunction(this.show_no_result)) {
|
||||
show_results = !this.show_no_result()
|
||||
} else {
|
||||
show_results = !this.show_no_result;
|
||||
}
|
||||
}
|
||||
|
||||
// render result view when
|
||||
// length > 0 OR
|
||||
// explicitly set by flag
|
||||
if (values.length || show_results) {
|
||||
this.data = this.data.concat(values);
|
||||
this.render_view(values);
|
||||
this.update_paging(values);
|
||||
} else if (this.start === 0) {
|
||||
// show no result message
|
||||
this.wrapper.find('.result').hide();
|
||||
|
||||
var msg = '';
|
||||
var no_result_message = this.no_result_message;
|
||||
if(no_result_message && $.isFunction(no_result_message)) {
|
||||
msg = no_result_message();
|
||||
} else if(typeof no_result_message === 'string') {
|
||||
msg = no_result_message;
|
||||
} else {
|
||||
msg = __('No Results')
|
||||
}
|
||||
|
||||
this.wrapper.find('.no-result').html(msg).show();
|
||||
}
|
||||
|
||||
this.wrapper.find('.list-paging-area')
|
||||
.toggle(values.length > 0|| this.start > 0);
|
||||
|
||||
// callbacks
|
||||
if (this.onrun) this.onrun();
|
||||
if (this.callback) this.callback(r);
|
||||
this.wrapper.trigger("render-complete");
|
||||
},
|
||||
|
||||
get_values_from_response: function (data) {
|
||||
// make dictionaries from keys and values
|
||||
if (data.keys && $.isArray(data.keys)) {
|
||||
return frappe.utils.dict(data.keys, data.values);
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
},
|
||||
|
||||
render_view: function (values) {
|
||||
// override this method in derived class
|
||||
},
|
||||
|
||||
update_paging: function (values) {
|
||||
if (values.length >= this.page_length) {
|
||||
this.wrapper.find('.btn-more').show();
|
||||
this.start += this.page_length;
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function () {
|
||||
this.run();
|
||||
},
|
||||
add_limits: function (query) {
|
||||
return query + ' LIMIT ' + this.start + ',' + (this.page_length + 1);
|
||||
},
|
||||
set_filter: function (fieldname, label, no_run, no_duplicate) {
|
||||
var filter = this.filter_list.get_filter(fieldname);
|
||||
if (filter) {
|
||||
var value = cstr(filter.field.get_value());
|
||||
if (value.includes(label)) {
|
||||
// already set
|
||||
return false
|
||||
|
||||
} else if (no_duplicate) {
|
||||
filter.set_values(this.doctype, fieldname, "=", label);
|
||||
} else {
|
||||
// second filter set for this field
|
||||
if (fieldname == '_user_tags' || fieldname == "_liked_by") {
|
||||
// and for tags
|
||||
this.filter_list.add_filter(this.doctype, fieldname, 'like', '%' + label + '%');
|
||||
} else {
|
||||
// or for rest using "in"
|
||||
filter.set_values(this.doctype, fieldname, 'in', value + ', ' + label);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// no filter for this item,
|
||||
// setup one
|
||||
if (['_user_tags', '_comments', '_assign', '_liked_by'].includes(fieldname)) {
|
||||
this.filter_list.add_filter(this.doctype, fieldname, 'like', '%' + label + '%');
|
||||
} else {
|
||||
this.filter_list.add_filter(this.doctype, fieldname, '=', label);
|
||||
}
|
||||
}
|
||||
if (!no_run)
|
||||
this.run();
|
||||
},
|
||||
init_user_settings: function () {
|
||||
this.user_settings = frappe.model.user_settings[this.doctype] || {};
|
||||
},
|
||||
call_for_selected_items: function (method, args) {
|
||||
var me = this;
|
||||
args.names = this.get_checked_items().map(function (item) {
|
||||
return item.name;
|
||||
});
|
||||
|
||||
frappe.call({
|
||||
method: method,
|
||||
args: args,
|
||||
freeze: true,
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
if (me.list_header) {
|
||||
me.list_header.find(".list-select-all").prop("checked", false);
|
||||
}
|
||||
me.refresh(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -18,7 +18,7 @@ frappe.ui.CommentArea = class CommentArea {
|
|||
this.no_wrapper = no_wrapper;
|
||||
|
||||
this.make();
|
||||
|
||||
|
||||
// Load emojis initially from https://git.io/frappe-emoji
|
||||
frappe.chat.emoji();
|
||||
// All good.
|
||||
|
|
@ -106,15 +106,7 @@ frappe.ui.CommentArea = class CommentArea {
|
|||
},
|
||||
callbacks: {
|
||||
onChange: () => {
|
||||
if(input.summernote('isEmpty')) {
|
||||
button
|
||||
.removeClass('btn-primary')
|
||||
.addClass('btn-default');
|
||||
} else {
|
||||
button
|
||||
.removeClass('btn-default')
|
||||
.addClass('btn-primary');
|
||||
}
|
||||
this.set_state();
|
||||
},
|
||||
onKeydown: (e) => {
|
||||
var key = frappe.ui.keys.get_key(e);
|
||||
|
|
@ -179,6 +171,22 @@ frappe.ui.CommentArea = class CommentArea {
|
|||
this.note_editor.on('click', () => input.summernote('focus'));
|
||||
}
|
||||
|
||||
check_state() {
|
||||
return !(this.input.summernote('isEmpty'));
|
||||
}
|
||||
|
||||
set_state() {
|
||||
if(this.check_state()) {
|
||||
this.button
|
||||
.removeClass('btn-default')
|
||||
.addClass('btn-primary');
|
||||
} else {
|
||||
this.button
|
||||
.removeClass('btn-primary')
|
||||
.addClass('btn-default');
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.input.summernote('destroy');
|
||||
}
|
||||
|
|
@ -201,3 +209,115 @@ frappe.ui.CommentArea = class CommentArea {
|
|||
this.on_submit && this.on_submit(this.val());
|
||||
}
|
||||
};
|
||||
|
||||
frappe.ui.ReviewArea = class ReviewArea extends frappe.ui.CommentArea {
|
||||
setup_dom() {
|
||||
const header = !this.no_wrapper ?
|
||||
`<div class="comment-input-header">
|
||||
<span class="small text-muted">${__("Add your review")}</span>
|
||||
<button class="btn btn-default btn-comment btn-xs disabled pull-right">
|
||||
${__("Submit Review")}
|
||||
</button>
|
||||
</div>` : '';
|
||||
|
||||
const footer = !this.no_wrapper ?
|
||||
`<div class="text-muted small">
|
||||
${__("Ctrl+Enter to submit")}
|
||||
</div>` : '';
|
||||
|
||||
const ratingArea = !this.no_wrapper ?
|
||||
`<div class="rating-area text-muted small" style="margin-bottom: 5px">
|
||||
${ __("Your rating: ") }
|
||||
<i class='fa fa-fw fa-star-o star-icon' data-index=0></i>
|
||||
<i class='fa fa-fw fa-star-o star-icon' data-index=1></i>
|
||||
<i class='fa fa-fw fa-star-o star-icon' data-index=2></i>
|
||||
<i class='fa fa-fw fa-star-o star-icon' data-index=3></i>
|
||||
<i class='fa fa-fw fa-star-o star-icon' data-index=4></i>
|
||||
</div>` : '';
|
||||
|
||||
this.wrapper = $(`
|
||||
<div class="comment-input-wrapper">
|
||||
${ header }
|
||||
<div class="comment-input-container">
|
||||
${ ratingArea }
|
||||
<input class="form-control review-subject" type="text"
|
||||
placeholder="${__('Subject')}"
|
||||
style="border-radius: 3px; border-color: #ebeff2">
|
||||
</input>
|
||||
<div class="form-control comment-input"></div>
|
||||
${ footer }
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
this.wrapper.appendTo(this.parent);
|
||||
this.input = this.parent.find('.comment-input');
|
||||
this.subject = this.parent.find('.review-subject');
|
||||
this.button = this.parent.find('.btn-comment');
|
||||
this.ratingArea = this.parent.find('.rating-area');
|
||||
|
||||
this.rating = 0;
|
||||
}
|
||||
|
||||
check_state() {
|
||||
return !(this.input.summernote('isEmpty') ||
|
||||
this.rating === 0 || !this.subject.val().length);
|
||||
}
|
||||
|
||||
set_state() {
|
||||
if(this.check_state()) {
|
||||
this.button
|
||||
.removeClass('btn-default disabled')
|
||||
.addClass('btn-primary');
|
||||
} else {
|
||||
this.button
|
||||
.removeClass('btn-primary')
|
||||
.addClass('btn-default disabled');
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.set_rating(0);
|
||||
this.subject.val('');
|
||||
this.input.summernote('code', '');
|
||||
}
|
||||
|
||||
bind_events() {
|
||||
super.bind_events();
|
||||
this.ratingArea.on('click', '.star-icon', (e) => {
|
||||
let index = $(e.target).attr('data-index');
|
||||
this.set_rating(parseInt(index) + 1);
|
||||
})
|
||||
|
||||
this.subject.on('change', () => {
|
||||
this.set_state();
|
||||
})
|
||||
}
|
||||
|
||||
set_rating(rating) {
|
||||
this.ratingArea.find('.star-icon').each((i, icon) => {
|
||||
let star = $(icon);
|
||||
if(i < rating) {
|
||||
star.removeClass('fa-star-o');
|
||||
star.addClass('fa-star');
|
||||
} else {
|
||||
star.removeClass('fa-star');
|
||||
star.addClass('fa-star-o');
|
||||
}
|
||||
})
|
||||
|
||||
this.rating = rating;
|
||||
this.set_state();
|
||||
}
|
||||
|
||||
val(value) {
|
||||
if(value === undefined) {
|
||||
return {
|
||||
rating: this.rating,
|
||||
subject: this.subject.val(),
|
||||
content: this.input.summernote('code')
|
||||
}
|
||||
}
|
||||
// Set html if value is specified
|
||||
this.input.summernote('code', value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -444,14 +444,15 @@ frappe.ui.Page = Class.extend({
|
|||
return this.$title_area;
|
||||
},
|
||||
|
||||
set_title: function(txt, icon) {
|
||||
set_title: function(txt, icon = '', stripHtml = true, tabTitle = '') {
|
||||
if(!txt) txt = "";
|
||||
|
||||
// strip html
|
||||
txt = strip_html(txt);
|
||||
if(stripHtml) {
|
||||
txt = strip_html(txt);
|
||||
}
|
||||
this.title = txt;
|
||||
|
||||
frappe.utils.set_title(txt);
|
||||
frappe.utils.set_title(tabTitle || txt);
|
||||
if(icon) {
|
||||
txt = '<span class="'+ icon +' text-muted" style="font-size: inherit;"></span> ' + txt;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ frappe.ui.Sidebar = class Sidebar {
|
|||
constructor({ wrapper, css_class }) {
|
||||
this.wrapper = wrapper;
|
||||
this.css_class = css_class;
|
||||
this.items = {};
|
||||
this.make_dom();
|
||||
}
|
||||
|
||||
|
|
@ -16,7 +17,7 @@ frappe.ui.Sidebar = class Sidebar {
|
|||
this.$sidebar = this.wrapper.find('.' + this.css_class);
|
||||
}
|
||||
|
||||
add_item(item, section) {
|
||||
add_item(item, section, h6=false) {
|
||||
let $section, $li_item;
|
||||
if(!section && this.wrapper.find('.sidebar-menu').length === 0) {
|
||||
// if no section, add section with no heading
|
||||
|
|
@ -29,14 +30,26 @@ frappe.ui.Sidebar = class Sidebar {
|
|||
$li_item = $(`<li>`);
|
||||
item.appendTo($li_item);
|
||||
} else {
|
||||
$li_item = $(`
|
||||
<li><a ${item.href ? `href="${item.href}"` : ''}>${item.label}</a></li>
|
||||
`).click(
|
||||
const className = h6 ? 'h6' : '';
|
||||
const html = `<li class=${className}>
|
||||
<a ${item.href ? `href="${item.href}"` : ''}>${item.label}</a>
|
||||
</li>`;
|
||||
$li_item = $(html).click(
|
||||
() => item.on_click && item.on_click()
|
||||
);
|
||||
}
|
||||
|
||||
$section.append($li_item);
|
||||
|
||||
if(item.name) {
|
||||
this.items[item.name] = $li_item;
|
||||
}
|
||||
}
|
||||
|
||||
remove_item(name) {
|
||||
if(this.items[name]) {
|
||||
this.items[name].remove();
|
||||
}
|
||||
}
|
||||
|
||||
get_section(section_heading="") {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue