diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000..4faece896a --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,12 @@ +# Since version 2.23 (released in August 2019), git-blame has a feature +# to ignore or bypass certain commits. +# +# This file contains a list of commits that are not likely what you +# are looking for in a blame, such as mass reformatting or renaming. +# You can set this file as a default ignore file for blame by running +# the following command. +# +# $ git config blame.ignoreRevsFile .git-blame-ignore-revs + +# Replace use of Class.extend with native JS class +fe20515c23a3ac41f1092bf0eaf0a0a452ec2e85 diff --git a/.github/workflows/publish-assets-develop.yml b/.github/workflows/publish-assets-develop.yml index 2a934a6795..a23885b508 100644 --- a/.github/workflows/publish-assets-develop.yml +++ b/.github/workflows/publish-assets-develop.yml @@ -15,11 +15,11 @@ jobs: path: 'frappe' - uses: actions/setup-node@v1 with: - python-version: '12.x' + node-version: 14 - uses: actions/setup-python@v2 with: python-version: '3.6' - - name: Set up bench for current push + - name: Set up bench and build assets run: | npm install -g yarn pip3 install -U frappe-bench @@ -29,7 +29,7 @@ jobs: - name: Package assets run: | mkdir -p $GITHUB_WORKSPACE/build - tar -cvpzf $GITHUB_WORKSPACE/build/$GITHUB_SHA.tar.gz ./frappe-bench/sites/assets/js ./frappe-bench/sites/assets/css + tar -cvpzf $GITHUB_WORKSPACE/build/$GITHUB_SHA.tar.gz ./frappe-bench/sites/assets/frappe/dist - name: Publish assets to S3 uses: jakejarvis/s3-sync-action@master diff --git a/.github/workflows/publish-assets-releases.yml b/.github/workflows/publish-assets-releases.yml index e86f884f35..a697517c23 100644 --- a/.github/workflows/publish-assets-releases.yml +++ b/.github/workflows/publish-assets-releases.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/setup-python@v2 with: python-version: '3.6' - - name: Set up bench for current push + - name: Set up bench and build assets run: | npm install -g yarn pip3 install -U frappe-bench @@ -32,7 +32,7 @@ jobs: - name: Package assets run: | mkdir -p $GITHUB_WORKSPACE/build - tar -cvpzf $GITHUB_WORKSPACE/build/assets.tar.gz ./frappe-bench/sites/assets/js ./frappe-bench/sites/assets/css + tar -cvpzf $GITHUB_WORKSPACE/build/assets.tar.gz ./frappe-bench/sites/assets/frappe/dist - name: Get release id: get_release diff --git a/.github/workflows/server-mariadb-tests.yml b/.github/workflows/server-mariadb-tests.yml index 075b76e8a1..1742e813c6 100644 --- a/.github/workflows/server-mariadb-tests.yml +++ b/.github/workflows/server-mariadb-tests.yml @@ -35,7 +35,7 @@ jobs: - uses: actions/setup-node@v2 with: - node-version: '14' + node-version: 14 check-latest: true - name: Add to Hosts diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 9eea128cd1..d9ccb07da0 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -35,7 +35,7 @@ jobs: - uses: actions/setup-node@v2 with: - node-version: '12' + node-version: 14 check-latest: true - name: Add to Hosts diff --git a/.gitignore b/.gitignore index 766288fe2e..1ff3122d70 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ locale dist/ # build/ frappe/docs/current +frappe/public/dist .vscode node_modules .kdev4/ diff --git a/cypress/integration/recorder.js b/cypress/integration/recorder.js index d30cc3568c..5b7692d8ff 100644 --- a/cypress/integration/recorder.js +++ b/cypress/integration/recorder.js @@ -50,7 +50,7 @@ context('Recorder', () => { cy.get('.result-list').should('contain', '/api/method/frappe.desk.reportview.get'); }); - it.only('Recorder View Request', () => { + it('Recorder View Request', () => { cy.get('.primary-action').should('contain', 'Start').click(); cy.visit('/app/List/DocType/List'); diff --git a/esbuild/esbuild.js b/esbuild/esbuild.js new file mode 100644 index 0000000000..ecf0d49511 --- /dev/null +++ b/esbuild/esbuild.js @@ -0,0 +1,486 @@ +/* eslint-disable no-console */ +let path = require("path"); +let fs = require("fs"); +let glob = require("fast-glob"); +let esbuild = require("esbuild"); +let vue = require("esbuild-vue"); +let yargs = require("yargs"); +let cliui = require("cliui")(); +let chalk = require("chalk"); +let html_plugin = require("./frappe-html"); +let postCssPlugin = require("esbuild-plugin-postcss2").default; +let ignore_assets = require("./ignore-assets"); +let sass_options = require("./sass_options"); +let { + app_list, + assets_path, + apps_path, + sites_path, + get_app_path, + get_public_path, + log, + log_warn, + log_error, + bench_path, + get_redis_subscriber +} = require("./utils"); + +let argv = yargs + .usage("Usage: node esbuild [options]") + .option("apps", { + type: "string", + description: "Run build for specific apps" + }) + .option("skip_frappe", { + type: "boolean", + description: "Skip building frappe assets" + }) + .option("files", { + type: "string", + description: "Run build for specified bundles" + }) + .option("watch", { + type: "boolean", + description: "Run in watch mode and rebuild on file changes" + }) + .option("production", { + type: "boolean", + description: "Run build in production mode" + }) + .option("run-build-command", { + type: "boolean", + description: "Run build command for apps" + }) + .example( + "node esbuild --apps frappe,erpnext", + "Run build only for frappe and erpnext" + ) + .example( + "node esbuild --files frappe/website.bundle.js,frappe/desk.bundle.js", + "Run build only for specified bundles" + ) + .version(false).argv; + +const APPS = (!argv.apps ? app_list : argv.apps.split(",")).filter( + app => !(argv.skip_frappe && app == "frappe") +); +const FILES_TO_BUILD = argv.files ? argv.files.split(",") : []; +const WATCH_MODE = Boolean(argv.watch); +const PRODUCTION = Boolean(argv.production); +const RUN_BUILD_COMMAND = !WATCH_MODE && Boolean(argv["run-build-command"]); + +const TOTAL_BUILD_TIME = `${chalk.black.bgGreen(" DONE ")} Total Build Time`; +const NODE_PATHS = [].concat( + // node_modules of apps directly importable + app_list + .map(app => path.resolve(get_app_path(app), "../node_modules")) + .filter(fs.existsSync), + // import js file of any app if you provide the full path + app_list + .map(app => path.resolve(get_app_path(app), "..")) + .filter(fs.existsSync) +); + +execute() + .then(() => RUN_BUILD_COMMAND && run_build_command_for_apps(APPS)) + .catch(e => console.error(e)); + +if (WATCH_MODE) { + // listen for open files in editor event + open_in_editor(); +} + +async function execute() { + console.time(TOTAL_BUILD_TIME); + if (!FILES_TO_BUILD.length) { + await clean_dist_folders(APPS); + } + + let result; + try { + result = await build_assets_for_apps(APPS, FILES_TO_BUILD); + } catch (e) { + log_error("There were some problems during build"); + log(); + log(chalk.dim(e.stack)); + return; + } + + if (!WATCH_MODE) { + log_built_assets(result.metafile); + console.timeEnd(TOTAL_BUILD_TIME); + log(); + } else { + log("Watching for changes..."); + } + return await write_assets_json(result.metafile); +} + +function build_assets_for_apps(apps, files) { + let { include_patterns, ignore_patterns } = files.length + ? get_files_to_build(files) + : get_all_files_to_build(apps); + + return glob(include_patterns, { ignore: ignore_patterns }).then(files => { + let output_path = assets_path; + + let file_map = {}; + for (let file of files) { + let relative_app_path = path.relative(apps_path, file); + let app = relative_app_path.split(path.sep)[0]; + + let extension = path.extname(file); + let output_name = path.basename(file, extension); + if ( + [".css", ".scss", ".less", ".sass", ".styl"].includes(extension) + ) { + output_name = path.join("css", output_name); + } else if ([".js", ".ts"].includes(extension)) { + output_name = path.join("js", output_name); + } + output_name = path.join(app, "dist", output_name); + + if (Object.keys(file_map).includes(output_name)) { + log_warn( + `Duplicate output file ${output_name} generated from ${file}` + ); + } + + file_map[output_name] = file; + } + + return build_files({ + files: file_map, + outdir: output_path + }); + }); +} + +function get_all_files_to_build(apps) { + let include_patterns = []; + let ignore_patterns = []; + + for (let app of apps) { + let public_path = get_public_path(app); + include_patterns.push( + path.resolve( + public_path, + "**", + "*.bundle.{js,ts,css,sass,scss,less,styl}" + ) + ); + ignore_patterns.push( + path.resolve(public_path, "node_modules"), + path.resolve(public_path, "dist") + ); + } + + return { + include_patterns, + ignore_patterns + }; +} + +function get_files_to_build(files) { + // files: ['frappe/website.bundle.js', 'erpnext/main.bundle.js'] + let include_patterns = []; + let ignore_patterns = []; + + for (let file of files) { + let [app, bundle] = file.split("/"); + let public_path = get_public_path(app); + include_patterns.push(path.resolve(public_path, "**", bundle)); + ignore_patterns.push( + path.resolve(public_path, "node_modules"), + path.resolve(public_path, "dist") + ); + } + + return { + include_patterns, + ignore_patterns + }; +} + +function build_files({ files, outdir }) { + return esbuild.build({ + entryPoints: files, + entryNames: "[dir]/[name].[hash]", + outdir, + sourcemap: true, + bundle: true, + metafile: true, + minify: PRODUCTION, + nodePaths: NODE_PATHS, + define: { + "process.env.NODE_ENV": JSON.stringify( + PRODUCTION ? "production" : "development" + ) + }, + plugins: [ + html_plugin, + ignore_assets, + vue(), + postCssPlugin({ + plugins: [require("autoprefixer")], + sassOptions: sass_options + }) + ], + watch: get_watch_config() + }); +} + +function get_watch_config() { + if (WATCH_MODE) { + return { + async onRebuild(error, result) { + if (error) { + log_error("There was an error during rebuilding changes."); + log(); + log(chalk.dim(error.stack)); + notify_redis({ error }); + } else { + let { + assets_json, + prev_assets_json + } = await write_assets_json(result.metafile); + if (prev_assets_json) { + log_rebuilt_assets(prev_assets_json, assets_json); + } + notify_redis({ success: true }); + } + } + }; + } + return null; +} + +async function clean_dist_folders(apps) { + for (let app of apps) { + let public_path = get_public_path(app); + await fs.promises.rmdir(path.resolve(public_path, "dist", "js"), { + recursive: true + }); + await fs.promises.rmdir(path.resolve(public_path, "dist", "css"), { + recursive: true + }); + } +} + +function log_built_assets(metafile) { + let column_widths = [60, 20]; + cliui.div( + { + text: chalk.cyan.bold("File"), + width: column_widths[0] + }, + { + text: chalk.cyan.bold("Size"), + width: column_widths[1] + } + ); + cliui.div(""); + + let output_by_dist_path = {}; + for (let outfile in metafile.outputs) { + if (outfile.endsWith(".map")) continue; + let data = metafile.outputs[outfile]; + outfile = path.resolve(outfile); + outfile = path.relative(assets_path, outfile); + let filename = path.basename(outfile); + let dist_path = outfile.replace(filename, ""); + output_by_dist_path[dist_path] = output_by_dist_path[dist_path] || []; + output_by_dist_path[dist_path].push({ + name: filename, + size: (data.bytes / 1000).toFixed(2) + " Kb" + }); + } + + for (let dist_path in output_by_dist_path) { + let files = output_by_dist_path[dist_path]; + cliui.div({ + text: dist_path, + width: column_widths[0] + }); + + for (let i in files) { + let file = files[i]; + let branch = ""; + if (i < files.length - 1) { + branch = "├─ "; + } else { + branch = "└─ "; + } + let color = file.name.endsWith(".js") ? "green" : "blue"; + cliui.div( + { + text: branch + chalk[color]("" + file.name), + width: column_widths[0] + }, + { + text: file.size, + width: column_widths[1] + } + ); + } + cliui.div(""); + } + log(cliui.toString()); +} + +// to store previous build's assets.json for comparison +let prev_assets_json; +let curr_assets_json; + +async function write_assets_json(metafile) { + prev_assets_json = curr_assets_json; + let out = {}; + for (let output in metafile.outputs) { + let info = metafile.outputs[output]; + let asset_path = "/" + path.relative(sites_path, output); + if (info.entryPoint) { + out[path.basename(info.entryPoint)] = asset_path; + } + } + + let assets_json_path = path.resolve( + assets_path, + "frappe", + "dist", + "assets.json" + ); + let assets_json; + try { + assets_json = await fs.promises.readFile(assets_json_path, "utf-8"); + } catch (error) { + assets_json = "{}"; + } + assets_json = JSON.parse(assets_json); + // update with new values + assets_json = Object.assign({}, assets_json, out); + curr_assets_json = assets_json; + + await fs.promises.writeFile( + assets_json_path, + JSON.stringify(assets_json, null, 4) + ); + await update_assets_json_in_cache(assets_json); + return { + assets_json, + prev_assets_json + }; +} + +function update_assets_json_in_cache(assets_json) { + // update assets_json cache in redis, so that it can be read directly by python + return new Promise(resolve => { + let client = get_redis_subscriber("redis_cache"); + // handle error event to avoid printing stack traces + client.on("error", _ => { + log_warn("Cannot connect to redis_cache to update assets_json"); + }); + client.set("assets_json", JSON.stringify(assets_json), err => { + client.unref(); + resolve(); + }); + }); +} + +function run_build_command_for_apps(apps) { + let cwd = process.cwd(); + let { execSync } = require("child_process"); + + for (let app of apps) { + if (app === "frappe") continue; + + let root_app_path = path.resolve(get_app_path(app), ".."); + let package_json = path.resolve(root_app_path, "package.json"); + if (fs.existsSync(package_json)) { + let { scripts } = require(package_json); + if (scripts && scripts.build) { + log("\nRunning build command for", chalk.bold(app)); + process.chdir(root_app_path); + execSync("yarn build", { encoding: "utf8", stdio: "inherit" }); + } + } + } + + process.chdir(cwd); +} + +async function notify_redis({ error, success }) { + // notify redis which in turns tells socketio to publish this to browser + let subscriber = get_redis_subscriber("redis_socketio"); + subscriber.on("error", _ => { + log_warn("Cannot connect to redis_socketio for browser events"); + }); + + let payload = null; + if (error) { + let formatted = await esbuild.formatMessages(error.errors, { + kind: "error", + terminalWidth: 100 + }); + let stack = error.stack.replace(new RegExp(bench_path, "g"), ""); + payload = { + error, + formatted, + stack + }; + } + if (success) { + payload = { + success: true + }; + } + + subscriber.publish( + "events", + JSON.stringify({ + event: "build_event", + message: payload + }) + ); +} + +function open_in_editor() { + let subscriber = get_redis_subscriber("redis_socketio"); + subscriber.on("error", _ => { + log_warn("Cannot connect to redis_socketio for open_in_editor events"); + }); + subscriber.on("message", (event, file) => { + if (event === "open_in_editor") { + file = JSON.parse(file); + let file_path = path.resolve(file.file); + log("Opening file in editor:", file_path); + let launch = require("launch-editor"); + launch(`${file_path}:${file.line}:${file.column}`); + } + }); + subscriber.subscribe("open_in_editor"); +} + +function log_rebuilt_assets(prev_assets, new_assets) { + let added_files = []; + let old_files = Object.values(prev_assets); + let new_files = Object.values(new_assets); + + for (let filepath of new_files) { + if (!old_files.includes(filepath)) { + added_files.push(filepath); + } + } + + log( + chalk.yellow( + `${new Date().toLocaleTimeString()}: Compiled ${ + added_files.length + } files...` + ) + ); + for (let filepath of added_files) { + let filename = path.basename(filepath); + log(" " + filename); + } + log(); +} diff --git a/esbuild/frappe-html.js b/esbuild/frappe-html.js new file mode 100644 index 0000000000..8c4b7ca3d7 --- /dev/null +++ b/esbuild/frappe-html.js @@ -0,0 +1,43 @@ +module.exports = { + name: "frappe-html", + setup(build) { + let path = require("path"); + let fs = require("fs/promises"); + + build.onResolve({ filter: /\.html$/ }, args => { + return { + path: path.join(args.resolveDir, args.path), + namespace: "frappe-html" + }; + }); + + build.onLoad({ filter: /.*/, namespace: "frappe-html" }, args => { + let filepath = args.path; + let filename = path.basename(filepath).split(".")[0]; + + return fs + .readFile(filepath, "utf-8") + .then(content => { + content = scrub_html_template(content); + return { + contents: `\n\tfrappe.templates['${filename}'] = \`${content}\`;\n` + }; + }) + .catch(() => { + return { + contents: "", + warnings: [ + { + text: `There was an error importing ${filepath}` + } + ] + }; + }); + }); + } +}; + +function scrub_html_template(content) { + content = content.replace(/`/g, "\\`"); + return content; +} diff --git a/esbuild/ignore-assets.js b/esbuild/ignore-assets.js new file mode 100644 index 0000000000..5edfef2110 --- /dev/null +++ b/esbuild/ignore-assets.js @@ -0,0 +1,11 @@ +module.exports = { + name: "frappe-ignore-asset", + setup(build) { + build.onResolve({ filter: /^\/assets\// }, args => { + return { + path: args.path, + external: true + }; + }); + } +}; diff --git a/esbuild/index.js b/esbuild/index.js new file mode 100644 index 0000000000..2721673702 --- /dev/null +++ b/esbuild/index.js @@ -0,0 +1 @@ +require("./esbuild"); diff --git a/esbuild/sass_options.js b/esbuild/sass_options.js new file mode 100644 index 0000000000..fcc7e04ccd --- /dev/null +++ b/esbuild/sass_options.js @@ -0,0 +1,29 @@ +let path = require("path"); +let { get_app_path, app_list } = require("./utils"); + +let node_modules_path = path.resolve( + get_app_path("frappe"), + "..", + "node_modules" +); +let app_paths = app_list + .map(get_app_path) + .map(app_path => path.resolve(app_path, "..")); + +module.exports = { + includePaths: [node_modules_path, ...app_paths], + importer: function(url) { + if (url.startsWith("~")) { + // strip ~ so that it can resolve from node_modules + url = url.slice(1); + } + if (url.endsWith(".css")) { + // strip .css from end of path + url = url.slice(0, -4); + } + // normal file, let it go + return { + file: url + }; + } +}; diff --git a/esbuild/utils.js b/esbuild/utils.js new file mode 100644 index 0000000000..82490adb36 --- /dev/null +++ b/esbuild/utils.js @@ -0,0 +1,145 @@ +const path = require("path"); +const fs = require("fs"); +const chalk = require("chalk"); + +const frappe_path = path.resolve(__dirname, ".."); +const bench_path = path.resolve(frappe_path, "..", ".."); +const sites_path = path.resolve(bench_path, "sites"); +const apps_path = path.resolve(bench_path, "apps"); +const assets_path = path.resolve(sites_path, "assets"); +const app_list = get_apps_list(); + +const app_paths = app_list.reduce((out, app) => { + out[app] = path.resolve(apps_path, app, app); + return out; +}, {}); +const public_paths = app_list.reduce((out, app) => { + out[app] = path.resolve(app_paths[app], "public"); + return out; +}, {}); +const public_js_paths = app_list.reduce((out, app) => { + out[app] = path.resolve(app_paths[app], "public/js"); + return out; +}, {}); + +const bundle_map = app_list.reduce((out, app) => { + const public_js_path = public_js_paths[app]; + if (fs.existsSync(public_js_path)) { + const all_files = fs.readdirSync(public_js_path); + const js_files = all_files.filter(file => file.endsWith(".js")); + + for (let js_file of js_files) { + const filename = path.basename(js_file).split(".")[0]; + out[path.join(app, "js", filename)] = path.resolve( + public_js_path, + js_file + ); + } + } + + return out; +}, {}); + +const get_public_path = app => public_paths[app]; + +const get_build_json_path = app => + path.resolve(get_public_path(app), "build.json"); + +function get_build_json(app) { + try { + return require(get_build_json_path(app)); + } catch (e) { + // build.json does not exist + return null; + } +} + +function delete_file(path) { + if (fs.existsSync(path)) { + fs.unlinkSync(path); + } +} + +function run_serially(tasks) { + let result = Promise.resolve(); + tasks.forEach(task => { + if (task) { + result = result.then ? result.then(task) : Promise.resolve(); + } + }); + return result; +} + +const get_app_path = app => app_paths[app]; + +function get_apps_list() { + return fs + .readFileSync(path.resolve(sites_path, "apps.txt"), { + encoding: "utf-8" + }) + .split("\n") + .filter(Boolean); +} + +function get_cli_arg(name) { + let args = process.argv.slice(2); + let arg = `--${name}`; + let index = args.indexOf(arg); + + let value = null; + if (index != -1) { + value = true; + } + if (value && args[index + 1]) { + value = args[index + 1]; + } + return value; +} + +function log_error(message, badge = "ERROR") { + badge = chalk.white.bgRed(` ${badge} `); + console.error(`${badge} ${message}`); // eslint-disable-line no-console +} + +function log_warn(message, badge = "WARN") { + badge = chalk.black.bgYellowBright(` ${badge} `); + console.warn(`${badge} ${message}`); // eslint-disable-line no-console +} + +function log(...args) { + console.log(...args); // eslint-disable-line no-console +} + +function get_redis_subscriber(kind) { + // get redis subscriber that aborts after 10 connection attempts + let { get_redis_subscriber: get_redis } = require("../node_utils"); + return get_redis(kind, { + retry_strategy: function(options) { + // abort after 10 connection attempts + if (options.attempt > 10) { + return undefined; + } + return Math.min(options.attempt * 100, 2000); + } + }); +} + +module.exports = { + app_list, + bench_path, + assets_path, + sites_path, + apps_path, + bundle_map, + get_public_path, + get_build_json_path, + get_build_json, + get_app_path, + delete_file, + run_serially, + get_cli_arg, + log, + log_warn, + log_error, + get_redis_subscriber +}; diff --git a/frappe/__init__.py b/frappe/__init__.py index 02b8d71e40..9b208f7c2d 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -10,9 +10,16 @@ be used to build database driven apps. Read the documentation: https://frappeframework.com/docs """ +import os, warnings + +_dev_server = os.environ.get('DEV_SERVER', False) + +if _dev_server: + warnings.simplefilter('always', DeprecationWarning) + warnings.simplefilter('always', PendingDeprecationWarning) from werkzeug.local import Local, release_local -import os, sys, importlib, inspect, json, warnings +import sys, importlib, inspect, json import typing from past.builtins import cmp import click @@ -31,8 +38,6 @@ __title__ = "Frappe Framework" local = Local() controllers = {} -warnings.simplefilter('always', DeprecationWarning) -warnings.simplefilter('always', PendingDeprecationWarning) class _dict(dict): """dict like object that exposes keys as attributes""" @@ -197,7 +202,7 @@ def init(site, sites_path=None, new_site=False): local.meta_cache = {} local.form_dict = _dict() local.session = _dict() - local.dev_server = os.environ.get('DEV_SERVER', False) + local.dev_server = _dev_server setup_module_map() diff --git a/frappe/build.py b/frappe/build.py index 321a9bf734..c970ae3a28 100644 --- a/frappe/build.py +++ b/frappe/build.py @@ -5,6 +5,7 @@ import os import re import json import shutil +import subprocess from tempfile import mkdtemp, mktemp from distutils.spawn import find_executable @@ -15,6 +16,7 @@ import click import psutil from urllib.parse import urlparse from simple_chalk import green +from semantic_version import Version timestamps = {} @@ -36,35 +38,36 @@ def download_file(url, prefix): def build_missing_files(): - # check which files dont exist yet from the build.json and tell build.js to build only those! + '''Check which files dont exist yet from the assets.json and run build for those files''' + missing_assets = [] current_asset_files = [] - frappe_build = os.path.join("..", "apps", "frappe", "frappe", "public", "build.json") for type in ["css", "js"]: - current_asset_files.extend( - [ - "{0}/{1}".format(type, name) - for name in os.listdir(os.path.join(sites_path, "assets", type)) - ] - ) + folder = os.path.join(sites_path, "assets", "frappe", "dist", type) + current_asset_files.extend(os.listdir(folder)) - with open(frappe_build) as f: - all_asset_files = json.load(f).keys() + development = frappe.local.conf.developer_mode or frappe.local.dev_server + build_mode = "development" if development else "production" - for asset in all_asset_files: - if asset.replace("concat:", "") not in current_asset_files: - missing_assets.append(asset) + assets_json = frappe.read_file(frappe.get_app_path('frappe', 'public', 'dist', 'assets.json')) + if assets_json: + assets_json = frappe.parse_json(assets_json) - if missing_assets: - from subprocess import check_call - from shlex import split + for bundle_file, output_file in assets_json.items(): + if not output_file.startswith('/assets/frappe'): + continue - click.secho("\nBuilding missing assets...\n", fg="yellow") - command = split( - "node rollup/build.js --files {0} --no-concat".format(",".join(missing_assets)) - ) - check_call(command, cwd=os.path.join("..", "apps", "frappe")) + if os.path.basename(output_file) not in current_asset_files: + missing_assets.append(bundle_file) + + if missing_assets: + click.secho("\nBuilding missing assets...\n", fg="yellow") + files_to_build = ["frappe/" + name for name in missing_assets] + bundle(build_mode, files=files_to_build) + else: + # no assets.json, run full build + bundle(build_mode, apps="frappe") def get_assets_link(frappe_head): @@ -200,49 +203,51 @@ def setup(): assets_path = os.path.join(frappe.local.sites_path, "assets") -def get_node_pacman(): - exec_ = find_executable("yarn") - if exec_: - return exec_ - raise ValueError("Yarn not found") - - -def bundle(no_compress, app=None, hard_link=False, verbose=False, skip_frappe=False): +def bundle(mode, apps=None, hard_link=False, make_copy=False, restore=False, verbose=False, skip_frappe=False, files=None): """concat / minify js files""" setup() make_asset_dirs(hard_link=hard_link) - pacman = get_node_pacman() - mode = "build" if no_compress else "production" - command = "{pacman} run {mode}".format(pacman=pacman, mode=mode) + mode = "production" if mode == "production" else "build" + command = "yarn run {mode}".format(mode=mode) - if app: - command += " --app {app}".format(app=app) + if apps: + command += " --apps {apps}".format(apps=apps) if skip_frappe: command += " --skip_frappe" - frappe_app_path = os.path.abspath(os.path.join(app_paths[0], "..")) - check_yarn() + if files: + command += " --files {files}".format(files=','.join(files)) + + command += " --run-build-command" + + check_node_executable() + frappe_app_path = frappe.get_app_path("frappe", "..") frappe.commands.popen(command, cwd=frappe_app_path, env=get_node_env()) -def watch(no_compress): +def watch(apps=None): """watch and rebuild if necessary""" setup() - pacman = get_node_pacman() + command = "yarn run watch" + if apps: + command += " --apps {apps}".format(apps=apps) - frappe_app_path = os.path.abspath(os.path.join(app_paths[0], "..")) - check_yarn() + check_node_executable() frappe_app_path = frappe.get_app_path("frappe", "..") - frappe.commands.popen("{pacman} run watch".format(pacman=pacman), - cwd=frappe_app_path, env=get_node_env()) + frappe.commands.popen(command, cwd=frappe_app_path, env=get_node_env()) -def check_yarn(): +def check_node_executable(): + node_version = Version(subprocess.getoutput('node -v')[1:]) + warn = '⚠️ ' + if node_version.major < 14: + click.echo(f"{warn} Please update your node version to 14") if not find_executable("yarn"): - print("Please install yarn using below command and try again.\nnpm install -g yarn") + click.echo(f"{warn} Please install yarn using below command and try again.\nnpm install -g yarn") + click.echo() def get_node_env(): node_env = { @@ -312,13 +317,20 @@ def clear_broken_symlinks(): -def unstrip(message): +def unstrip(message: str) -> str: + """Pads input string on the right side until the last available column in the terminal + """ + _len = len(message) try: max_str = os.get_terminal_size().columns except Exception: max_str = 80 - _len = len(message) - _rem = max_str - _len + + if _len < max_str: + _rem = max_str - _len + else: + _rem = max_str % _len + return f"{message}{' ' * _rem}" @@ -331,6 +343,7 @@ def make_asset_dirs(hard_link=False): start_message = unstrip(f"{'Copying assets from' if hard_link else 'Linking'} {source} to {target}") fail_message = unstrip(f"Cannot {'copy' if hard_link else 'link'} {source} to {target}") + # Used '\r' instead of '\x1b[1K\r' to print entire lines in smaller terminal sizes try: print(start_message, end="\r") link_assets_dir(source, target, hard_link=hard_link) diff --git a/frappe/cache_manager.py b/frappe/cache_manager.py index 4e0fe0cf44..7330c83102 100644 --- a/frappe/cache_manager.py +++ b/frappe/cache_manager.py @@ -13,6 +13,8 @@ common_default_keys = ["__default", "__global"] doctype_map_keys = ('energy_point_rule_map', 'assignment_rule_map', 'milestone_tracker_map', 'event_consumer_document_type_map') +bench_cache_keys = ('assets_json',) + global_cache_keys = ("app_hooks", "installed_apps", 'all_apps', "app_modules", "module_app", "system_settings", 'scheduler_events', 'time_zone', 'webhooks', 'active_domains', @@ -58,6 +60,7 @@ def clear_global_cache(): clear_doctype_cache() clear_website_cache() frappe.cache().delete_value(global_cache_keys) + frappe.cache().delete_value(bench_cache_keys) frappe.setup_module_map() def clear_defaults_cache(user=None): diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 1ee2a7ec00..4da0f6bb78 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -16,24 +16,34 @@ from frappe.utils import get_bench_path, update_progress_bar, cint @click.command('build') @click.option('--app', help='Build assets for app') +@click.option('--apps', help='Build assets for specific apps') @click.option('--hard-link', is_flag=True, default=False, help='Copy the files instead of symlinking') @click.option('--make-copy', is_flag=True, default=False, help='[DEPRECATED] Copy the files instead of symlinking') @click.option('--restore', is_flag=True, default=False, help='[DEPRECATED] Copy the files instead of symlinking with force') +@click.option('--production', is_flag=True, default=False, help='Build assets in production mode') @click.option('--verbose', is_flag=True, default=False, help='Verbose') @click.option('--force', is_flag=True, default=False, help='Force build assets instead of downloading available') -def build(app=None, hard_link=False, make_copy=False, restore=False, verbose=False, force=False): - "Minify + concatenate JS and CSS files, build translations" +def build(app=None, apps=None, hard_link=False, make_copy=False, restore=False, production=False, verbose=False, force=False): + "Compile JS and CSS source files" + from frappe.build import bundle, download_frappe_assets frappe.init('') - # don't minify in developer_mode for faster builds - no_compress = frappe.local.conf.developer_mode or False + + if not apps and app: + apps = app # dont try downloading assets if force used, app specified or running via CI - if not (force or app or os.environ.get('CI')): + if not (force or apps or os.environ.get('CI')): # skip building frappe if assets exist remotely - skip_frappe = frappe.build.download_frappe_assets(verbose=verbose) + skip_frappe = download_frappe_assets(verbose=verbose) else: skip_frappe = False + # don't minify in developer_mode for faster builds + development = frappe.local.conf.developer_mode or frappe.local.dev_server + mode = "development" if development else "production" + if production: + mode = "production" + if make_copy or restore: hard_link = make_copy or restore click.secho( @@ -41,21 +51,17 @@ def build(app=None, hard_link=False, make_copy=False, restore=False, verbose=Fal fg="yellow", ) - frappe.build.bundle( - skip_frappe=skip_frappe, - no_compress=no_compress, - hard_link=hard_link, - verbose=verbose, - app=app, - ) + bundle(mode, apps=apps, hard_link=hard_link, verbose=verbose, skip_frappe=skip_frappe) + @click.command('watch') -def watch(): - "Watch and concatenate JS and CSS files as and when they change" - import frappe.build +@click.option('--apps', help='Watch assets for specific apps') +def watch(apps=None): + "Watch and compile JS and CSS files as and when they change" + from frappe.build import watch frappe.init('') - frappe.build.watch(True) + watch(apps) @click.command('clear-cache') @@ -501,8 +507,6 @@ frappe.db.connect() @pass_context def console(context): "Start ipython console for a site" - import warnings - site = get_site(context) frappe.init(site=site) frappe.connect() @@ -523,7 +527,6 @@ def console(context): if failed_to_import: print("\nFailed to import:\n{}".format(", ".join(failed_to_import))) - warnings.simplefilter('ignore') IPython.embed(display_banner="", header="", colors="neutral") diff --git a/frappe/core/doctype/data_import/data_import.js b/frappe/core/doctype/data_import/data_import.js index e03c22a898..079bdaa09c 100644 --- a/frappe/core/doctype/data_import/data_import.js +++ b/frappe/core/doctype/data_import/data_import.js @@ -203,7 +203,7 @@ frappe.ui.form.on('Data Import', { }, download_template(frm) { - frappe.require('/assets/js/data_import_tools.min.js', () => { + frappe.require('data_import_tools.bundle.js', () => { frm.data_exporter = new frappe.data_import.DataExporter( frm.doc.reference_doctype, frm.doc.import_type @@ -287,7 +287,7 @@ frappe.ui.form.on('Data Import', { return; } - frappe.require('/assets/js/data_import_tools.min.js', () => { + frappe.require('data_import_tools.bundle.js', () => { frm.import_preview = new frappe.data_import.ImportPreview({ wrapper: frm.get_field('import_preview').$wrapper, doctype: frm.doc.reference_doctype, diff --git a/frappe/core/page/recorder/recorder.js b/frappe/core/page/recorder/recorder.js index fdca93e8b9..f1f74daf71 100644 --- a/frappe/core/page/recorder/recorder.js +++ b/frappe/core/page/recorder/recorder.js @@ -11,7 +11,7 @@ frappe.pages['recorder'].on_page_load = function(wrapper) { frappe.recorder.show(); }); - frappe.require('/assets/js/frappe-recorder.min.js'); + frappe.require('recorder.bundle.js'); }; class Recorder { diff --git a/frappe/desk/page/activity/activity.js b/frappe/desk/page/activity/activity.js index 39de414122..7b4e8ddc1a 100644 --- a/frappe/desk/page/activity/activity.js +++ b/frappe/desk/page/activity/activity.js @@ -67,8 +67,8 @@ frappe.pages['activity'].on_page_show = function () { } frappe.activity.last_feed_date = false; -frappe.activity.Feed = Class.extend({ - init: function (row, data) { +frappe.activity.Feed = class Feed { + constructor(row, data) { this.scrub_data(data); this.add_date_separator(row, data); if (!data.add_class) @@ -97,8 +97,9 @@ frappe.activity.Feed = Class.extend({ $(row) .append(frappe.render_template("activity_row", data)) .find("a").addClass("grey"); - }, - scrub_data: function (data) { + } + + scrub_data(data) { data.by = frappe.user.full_name(data.owner); data.avatar = frappe.avatar(data.owner); @@ -113,9 +114,9 @@ frappe.activity.Feed = Class.extend({ data.when = comment_when(data.creation); data.feed_type = data.comment_type || data.communication_medium; - }, + } - add_date_separator: function (row, data) { + add_date_separator(row, data) { var date = frappe.datetime.str_to_obj(data.creation); var last = frappe.activity.last_feed_date; @@ -137,7 +138,7 @@ frappe.activity.Feed = Class.extend({ } frappe.activity.last_feed_date = date; } -}); +}; frappe.activity.render_heatmap = function (page) { $('
\ diff --git a/frappe/email/email_body.py b/frappe/email/email_body.py index 45888119ea..3b03c42b95 100755 --- a/frappe/email/email_body.py +++ b/frappe/email/email_body.py @@ -292,18 +292,12 @@ def inline_style_in_html(html): ''' Convert email.css and html to inline-styled html ''' from premailer import Premailer + from frappe.utils.jinja_globals import bundled_asset - apps = frappe.get_installed_apps() - - # add frappe email css file - css_files = ['assets/css/email.css'] - if 'frappe' in apps: - apps.remove('frappe') - - for app in apps: - path = 'assets/{0}/css/email.css'.format(app) - css_files.append(path) - + # get email css files from hooks + css_files = frappe.get_hooks('email_css') + css_files = [bundled_asset(path) for path in css_files] + css_files = [path.lstrip('/') for path in css_files] css_files = [css_file for css_file in css_files if os.path.exists(os.path.abspath(css_file))] p = Premailer(html=html, external_styles=css_files, strip_important=False) diff --git a/frappe/hooks.py b/frappe/hooks.py index b999d2cd95..d0968ce051 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -29,16 +29,16 @@ page_js = { # website app_include_js = [ - "/assets/js/libs.min.js", - "/assets/js/desk.min.js", - "/assets/js/list.min.js", - "/assets/js/form.min.js", - "/assets/js/control.min.js", - "/assets/js/report.min.js", + "libs.bundle.js", + "desk.bundle.js", + "list.bundle.js", + "form.bundle.js", + "controls.bundle.js", + "report.bundle.js", ] app_include_css = [ - "/assets/css/desk.min.css", - "/assets/css/report.min.css", + "desk.bundle.css", + "report.bundle.css", ] doctype_js = { @@ -52,6 +52,8 @@ web_include_js = [ web_include_css = [] +email_css = ['email.bundle.css'] + website_route_rules = [ {"from_route": "/blog/", "to_route": "Blog Post"}, {"from_route": "/kb/", "to_route": "Help Article"}, diff --git a/frappe/printing/page/print/print.js b/frappe/printing/page/print/print.js index dfd93c4efa..233bbe0ce7 100644 --- a/frappe/printing/page/print/print.js +++ b/frappe/printing/page/print/print.js @@ -408,14 +408,17 @@ frappe.ui.form.PrintView = class { setup_print_format_dom(out, $print_format) { this.print_wrapper.find('.print-format-skeleton').remove(); + let base_url = frappe.urllib.get_base_url(); + let print_css = frappe.assets.bundled_asset('print.bundle.css'); this.$print_format_body.find('head').html( ` - ` + ` ); if (frappe.utils.is_rtl(this.lang_code)) { + let rtl_css = frappe.assets.bundled_asset('frappe-rtl.bundle.css'); this.$print_format_body.find('head').append( - `` + `` ); } diff --git a/frappe/printing/page/print_format_builder/print_format_builder.js b/frappe/printing/page/print_format_builder/print_format_builder.js index 7e58e295b5..ca2a8bc378 100644 --- a/frappe/printing/page/print_format_builder/print_format_builder.js +++ b/frappe/printing/page/print_format_builder/print_format_builder.js @@ -23,13 +23,13 @@ frappe.pages['print-format-builder'].on_page_show = function(wrapper) { } } -frappe.PrintFormatBuilder = Class.extend({ - init: function(parent) { +frappe.PrintFormatBuilder = class PrintFormatBuilder { + constructor(parent) { this.parent = parent; this.make(); this.refresh(); - }, - refresh: function() { + } + refresh() { this.custom_html_count = 0; if(!this.print_format) { this.show_start(); @@ -37,8 +37,8 @@ frappe.PrintFormatBuilder = Class.extend({ this.page.set_title(this.print_format.name); this.setup_print_format(); } - }, - make: function() { + } + make() { this.page = frappe.ui.make_app_page({ parent: this.parent, title: __("Print Format Builder"), @@ -56,15 +56,15 @@ frappe.PrintFormatBuilder = Class.extend({ this.setup_edit_custom_html(); // $(this.page.sidebar).css({"position": 'fixed'}); // $(this.page.main).parent().css({"margin-left": '16.67%'}); - }, - show_start: function() { + } + show_start() { this.page.main.html(frappe.render_template("print_format_builder_start", {})); this.page.clear_actions(); this.page.set_title(__("Print Format Builder")); this.start_edit_print_format(); this.start_new_print_format(); - }, - start_edit_print_format: function() { + } + start_edit_print_format() { // print format control var me = this; this.print_format_input = frappe.ui.form.make_control({ @@ -89,8 +89,8 @@ frappe.PrintFormatBuilder = Class.extend({ frappe.set_route('print-format-builder', name); }); }); - }, - start_new_print_format: function() { + } + start_new_print_format() { var me = this; this.doctype_input = frappe.ui.form.make_control({ parent: this.page.main.find(".doctype-selector"), @@ -125,8 +125,8 @@ frappe.PrintFormatBuilder = Class.extend({ me.setup_new_print_format(doctype, name); }); - }, - setup_new_print_format: function(doctype, name, based_on) { + } + setup_new_print_format(doctype, name, based_on) { frappe.call({ method: 'frappe.printing.page.print_format_builder.print_format_builder.create_custom_format', args: { @@ -143,8 +143,8 @@ frappe.PrintFormatBuilder = Class.extend({ } }, }); - }, - setup_print_format: function() { + } + setup_print_format() { var me = this; frappe.model.with_doctype(this.print_format.doc_type, function(doctype) { me.meta = frappe.get_meta(me.print_format.doc_type); @@ -163,23 +163,23 @@ frappe.PrintFormatBuilder = Class.extend({ frappe.set_route("Form", "Print Format", me.print_format.name); }); }); - }, - setup_sidebar: function() { + } + setup_sidebar() { // prepend custom HTML field var fields = [this.get_custom_html_field()].concat(this.meta.fields); this.page.sidebar.html( $(frappe.render_template("print_format_builder_sidebar", {fields: fields})) ); this.setup_field_filter(); - }, - get_custom_html_field: function() { + } + get_custom_html_field() { return { fieldtype: "Custom HTML", fieldname: "_custom_html", label: __("Custom HTML") - } - }, - render_layout: function() { + }; + } + render_layout() { this.page.main.empty(); this.prepare_data(); $(frappe.render_template("print_format_builder_layout", { @@ -190,8 +190,8 @@ frappe.PrintFormatBuilder = Class.extend({ this.setup_edit_heading(); this.setup_field_settings(); this.setup_html_data(); - }, - prepare_data: function() { + } + prepare_data() { this.print_heading_template = null; this.data = JSON.parse(this.print_format.format_data || "[]"); if(!this.data.length) { @@ -280,22 +280,22 @@ frappe.PrintFormatBuilder = Class.extend({ this.layout_data = $.map(this.layout_data, function(s) { return s.has_fields ? s : null }); - }, - get_new_section: function() { + } + get_new_section() { return {columns: [], no_of_columns: 0, label:''}; - }, - get_new_column: function() { + } + get_new_column() { return {fields: []} - }, - add_table_properties: function(f) { + } + add_table_properties(f) { // build table columns and widths in a dict // visible_columns var me = this; if(!f.visible_columns) { me.init_visible_columns(f); } - }, - init_visible_columns: function(f) { + } + init_visible_columns(f) { f.visible_columns = [] $.each(frappe.get_meta(f.options).fields, function(i, _f) { if(!in_list(["Section Break", "Column Break"], _f.fieldtype) && @@ -306,8 +306,8 @@ frappe.PrintFormatBuilder = Class.extend({ print_width: (_f.width || ""), print_hide:0}); } }); - }, - setup_sortable: function() { + } + setup_sortable() { var me = this; // drag from fields library @@ -332,8 +332,8 @@ frappe.PrintFormatBuilder = Class.extend({ Sortable.create(this.page.main.find(".print-format-builder-layout").get(0), { handle: ".print-format-builder-section-head" } ); - }, - setup_sortable_for_column: function(col) { + } + setup_sortable_for_column(col) { var me = this; Sortable.create(col, { group: { @@ -363,8 +363,8 @@ frappe.PrintFormatBuilder = Class.extend({ } }); - }, - setup_field_filter: function() { + } + setup_field_filter() { var me = this; this.page.sidebar.find(".filter-fields").on("keyup", function() { var text = $(this).val(); @@ -373,8 +373,8 @@ frappe.PrintFormatBuilder = Class.extend({ $(this).parent().toggle(show); }) }); - }, - setup_section_settings: function() { + } + setup_section_settings() { var me = this; this.page.main.on("click", ".section-settings", function() { var section = $(this).parent().parent(); @@ -431,8 +431,8 @@ frappe.PrintFormatBuilder = Class.extend({ return false; }); - }, - setup_field_settings: function() { + } + setup_field_settings() { this.page.main.find(".field-settings").on("click", e => { const field = $(e.currentTarget).parent(); // new dialog @@ -482,8 +482,8 @@ frappe.PrintFormatBuilder = Class.extend({ return false; }); - }, - setup_html_data: function() { + } + setup_html_data() { // set JQuery `data` for Custom HTML fields, since editing the HTML // directly causes problem becuase of HTML reformatting // @@ -496,8 +496,8 @@ frappe.PrintFormatBuilder = Class.extend({ var html = me.custom_html_dict[parseInt(content.attr('data-custom-html-id'))].options; content.data('content', html); }) - }, - update_columns_in_section: function(section, no_of_columns, new_no_of_columns) { + } + update_columns_in_section(section, no_of_columns, new_no_of_columns) { var col_size = 12 / new_no_of_columns, me = this, resize = function() { @@ -539,8 +539,8 @@ frappe.PrintFormatBuilder = Class.extend({ resize(); } - }, - setup_add_section: function() { + } + setup_add_section() { var me = this; this.page.main.find(".print-format-builder-add-section").on("click", function() { // boostrap new section info @@ -554,8 +554,8 @@ frappe.PrintFormatBuilder = Class.extend({ me.setup_sortable_for_column($section.find(".print-format-builder-column").get(0)); }); - }, - setup_edit_heading: function() { + } + setup_edit_heading() { var me = this; var $heading = this.page.main.find(".print-format-builder-print-heading"); @@ -565,8 +565,8 @@ frappe.PrintFormatBuilder = Class.extend({ this.page.main.find(".edit-heading").on("click", function() { var d = me.get_edit_html_dialog(__("Edit Heading"), __("Heading"), $heading); }) - }, - setup_column_selector: function() { + } + setup_column_selector() { var me = this; this.page.main.on("click", ".select-columns", function() { var parent = $(this).parents(".print-format-builder-field:first"), @@ -657,24 +657,24 @@ frappe.PrintFormatBuilder = Class.extend({ return false; }); - }, - get_visible_columns_string: function(f) { + } + get_visible_columns_string(f) { if(!f.visible_columns) { this.init_visible_columns(f); } return $.map(f.visible_columns, function(v) { return v.fieldname + "|" + (v.print_width || "") }).join(","); - }, - get_no_content: function() { + } + get_no_content() { return __("Edit to add content") - }, - setup_edit_custom_html: function() { + } + setup_edit_custom_html() { var me = this; this.page.main.on("click", ".edit-html", function() { me.get_edit_html_dialog(__("Edit Custom HTML"), __("Custom HTML"), $(this).parents(".print-format-builder-field:first").find(".html-content")); }); - }, - get_edit_html_dialog: function(title, label, $content) { + } + get_edit_html_dialog(title, label, $content) { var me = this; var d = new frappe.ui.Dialog({ title: title, @@ -710,8 +710,8 @@ frappe.PrintFormatBuilder = Class.extend({ d.show(); return d; - }, - save_print_format: function() { + } + save_print_format() { var data = [], me = this; @@ -789,4 +789,4 @@ frappe.PrintFormatBuilder = Class.extend({ } }); } -}); +}; diff --git a/frappe/public/html/print_template.html b/frappe/public/html/print_template.html index bdb09541c9..721bec7fa7 100644 --- a/frappe/public/html/print_template.html +++ b/frappe/public/html/print_template.html @@ -7,7 +7,7 @@ {{ title }} - + diff --git a/frappe/public/js/barcode_scanner.bundle.js b/frappe/public/js/barcode_scanner.bundle.js new file mode 100644 index 0000000000..294f20c08f --- /dev/null +++ b/frappe/public/js/barcode_scanner.bundle.js @@ -0,0 +1 @@ +import "./frappe/barcode_scanner/quagga"; diff --git a/frappe/public/js/bootstrap-4-web.bundle.js b/frappe/public/js/bootstrap-4-web.bundle.js new file mode 100644 index 0000000000..2e3c4d7145 --- /dev/null +++ b/frappe/public/js/bootstrap-4-web.bundle.js @@ -0,0 +1,64 @@ + +// multilevel dropdown +$('.dropdown-menu a.dropdown-toggle').on('click', function (e) { + e.preventDefault(); + e.stopImmediatePropagation(); + if (!$(this).next().hasClass('show')) { + $(this).parents('.dropdown-menu').first().find('.show').removeClass("show"); + } + var $subMenu = $(this).next(".dropdown-menu"); + $subMenu.toggleClass('show'); + + + $(this).parents('li.nav-item.dropdown.show').on('hidden.bs.dropdown', function () { + $('.dropdown-submenu .show').removeClass("show"); + }); + + return false; +}); + +frappe.get_modal = function (title, content) { + return $( + `` + ); +}; + +frappe.ui.Dialog = class Dialog extends frappe.ui.Dialog { + get_primary_btn() { + return this.$wrapper.find(".modal-footer .btn-primary"); + } + + set_primary_action(label, click) { + this.$wrapper.find('.modal-footer').removeClass('hidden'); + return super.set_primary_action(label, click) + .removeClass('hidden'); + } + + make() { + super.make(); + if (this.fields) { + this.$wrapper.find('.section-body').addClass('w-100'); + } + } +}; diff --git a/frappe/public/js/chat.bundle.js b/frappe/public/js/chat.bundle.js new file mode 100644 index 0000000000..5f9a91ebb7 --- /dev/null +++ b/frappe/public/js/chat.bundle.js @@ -0,0 +1 @@ +import "./frappe/chat"; diff --git a/frappe/public/js/checkout.bundle.js b/frappe/public/js/checkout.bundle.js new file mode 100644 index 0000000000..954e838fa8 --- /dev/null +++ b/frappe/public/js/checkout.bundle.js @@ -0,0 +1 @@ +import "./integrations/razorpay"; diff --git a/frappe/public/js/controls.bundle.js b/frappe/public/js/controls.bundle.js new file mode 100644 index 0000000000..30b5d43905 --- /dev/null +++ b/frappe/public/js/controls.bundle.js @@ -0,0 +1,18 @@ +import "air-datepicker/dist/js/datepicker.min.js"; +import "air-datepicker/dist/js/i18n/datepicker.cs.js"; +import "air-datepicker/dist/js/i18n/datepicker.da.js"; +import "air-datepicker/dist/js/i18n/datepicker.de.js"; +import "air-datepicker/dist/js/i18n/datepicker.en.js"; +import "air-datepicker/dist/js/i18n/datepicker.es.js"; +import "air-datepicker/dist/js/i18n/datepicker.fi.js"; +import "air-datepicker/dist/js/i18n/datepicker.fr.js"; +import "air-datepicker/dist/js/i18n/datepicker.hu.js"; +import "air-datepicker/dist/js/i18n/datepicker.nl.js"; +import "air-datepicker/dist/js/i18n/datepicker.pl.js"; +import "air-datepicker/dist/js/i18n/datepicker.pt-BR.js"; +import "air-datepicker/dist/js/i18n/datepicker.pt.js"; +import "air-datepicker/dist/js/i18n/datepicker.ro.js"; +import "air-datepicker/dist/js/i18n/datepicker.sk.js"; +import "air-datepicker/dist/js/i18n/datepicker.zh.js"; +import "./frappe/ui/capture.js"; +import "./frappe/form/controls/control.js"; diff --git a/frappe/public/js/data_import_tools.bundle.js b/frappe/public/js/data_import_tools.bundle.js new file mode 100644 index 0000000000..b6e4c11968 --- /dev/null +++ b/frappe/public/js/data_import_tools.bundle.js @@ -0,0 +1 @@ +import "./frappe/data_import"; diff --git a/frappe/public/js/desk.bundle.js b/frappe/public/js/desk.bundle.js new file mode 100644 index 0000000000..66eb72cda0 --- /dev/null +++ b/frappe/public/js/desk.bundle.js @@ -0,0 +1,105 @@ +import "./frappe/translate.js"; +import "./frappe/class.js"; +import "./frappe/polyfill.js"; +import "./frappe/provide.js"; +import "./frappe/assets.js"; +import "./frappe/format.js"; +import "./frappe/form/formatters.js"; +import "./frappe/dom.js"; +import "./frappe/ui/messages.js"; +import "./frappe/ui/keyboard.js"; +import "./frappe/ui/colors.js"; +import "./frappe/ui/sidebar.js"; +import "./frappe/ui/link_preview.js"; + +import "./frappe/request.js"; +import "./frappe/socketio_client.js"; +import "./frappe/utils/utils.js"; +import "./frappe/event_emitter.js"; +import "./frappe/router.js"; +import "./frappe/router_history.js"; +import "./frappe/defaults.js"; +import "./frappe/roles_editor.js"; +import "./frappe/module_editor.js"; +import "./frappe/microtemplate.js"; + +import "./frappe/ui/page.html"; +import "./frappe/ui/page.js"; +import "./frappe/ui/slides.js"; +// import "./frappe/ui/onboarding_dialog.js"; +import "./frappe/ui/find.js"; +import "./frappe/ui/iconbar.js"; +import "./frappe/form/layout.js"; +import "./frappe/ui/field_group.js"; +import "./frappe/form/link_selector.js"; +import "./frappe/form/multi_select_dialog.js"; +import "./frappe/ui/dialog.js"; +import "./frappe/ui/capture.js"; +import "./frappe/ui/app_icon.js"; +import "./frappe/ui/theme_switcher.js"; + +import "./frappe/model/model.js"; +import "./frappe/db.js"; +import "./frappe/model/meta.js"; +import "./frappe/model/sync.js"; +import "./frappe/model/create_new.js"; +import "./frappe/model/perm.js"; +import "./frappe/model/workflow.js"; +import "./frappe/model/user_settings.js"; + +import "./frappe/utils/user.js"; +import "./frappe/utils/common.js"; +import "./frappe/utils/urllib.js"; +import "./frappe/utils/pretty_date.js"; +import "./frappe/utils/tools.js"; +import "./frappe/utils/datetime.js"; +import "./frappe/utils/number_format.js"; +import "./frappe/utils/help.js"; +import "./frappe/utils/help_links.js"; +import "./frappe/utils/address_and_contact.js"; +import "./frappe/utils/preview_email.js"; +import "./frappe/utils/file_manager.js"; + +import "./frappe/upload.js"; +import "./frappe/ui/tree.js"; + +import "./frappe/views/container.js"; +import "./frappe/views/breadcrumbs.js"; +import "./frappe/views/factory.js"; +import "./frappe/views/pageview.js"; + +import "./frappe/ui/toolbar/awesome_bar.js"; +// import "./frappe/ui/toolbar/energy_points_notifications.js"; +import "./frappe/ui/notifications/notifications.js"; +import "./frappe/ui/toolbar/search.js"; +import "./frappe/ui/toolbar/tag_utils.js"; +import "./frappe/ui/toolbar/search.html"; +import "./frappe/ui/toolbar/search_utils.js"; +import "./frappe/ui/toolbar/about.js"; +import "./frappe/ui/toolbar/navbar.html"; +import "./frappe/ui/toolbar/toolbar.js"; +// import "./frappe/ui/toolbar/notifications.js"; +import "./frappe/views/communication.js"; +import "./frappe/views/translation_manager.js"; +import "./frappe/views/workspace/workspace.js"; + +import "./frappe/widgets/widget_group.js"; + +import "./frappe/ui/sort_selector.html"; +import "./frappe/ui/sort_selector.js"; + +import "./frappe/change_log.html"; +import "./frappe/ui/workspace_loading_skeleton.html"; +import "./frappe/desk.js"; +import "./frappe/query_string.js"; + +// import "./frappe/ui/comment.js"; + +import "./frappe/chat.js"; +import "./frappe/utils/energy_point_utils.js"; +import "./frappe/utils/dashboard_utils.js"; +import "./frappe/ui/chart.js"; +import "./frappe/ui/datatable.js"; +import "./frappe/ui/driver.js"; +import "./frappe/ui/plyr.js"; +import "./frappe/barcode_scanner/index.js"; diff --git a/frappe/public/js/dialog.bundle.js b/frappe/public/js/dialog.bundle.js new file mode 100644 index 0000000000..3100b42ca7 --- /dev/null +++ b/frappe/public/js/dialog.bundle.js @@ -0,0 +1,7 @@ +import "./frappe/dom.js"; +import "./frappe/form/formatters.js"; +import "./frappe/form/layout.js"; +import "./frappe/ui/field_group.js"; +import "./frappe/form/link_selector.js"; +import "./frappe/form/multi_select_dialog.js"; +import "./frappe/ui/dialog.js"; diff --git a/frappe/public/js/form.bundle.js b/frappe/public/js/form.bundle.js new file mode 100644 index 0000000000..5bed5c2cb8 --- /dev/null +++ b/frappe/public/js/form.bundle.js @@ -0,0 +1,17 @@ +import "./frappe/form/templates/address_list.html"; +import "./frappe/form/templates/contact_list.html"; +import "./frappe/form/templates/form_dashboard.html"; +import "./frappe/form/templates/form_footer.html"; +import "./frappe/form/templates/form_links.html"; +import "./frappe/form/templates/form_sidebar.html"; +import "./frappe/form/templates/print_layout.html"; +import "./frappe/form/templates/report_links.html"; +import "./frappe/form/templates/set_sharing.html"; +import "./frappe/form/templates/timeline_message_box.html"; +import "./frappe/form/templates/users_in_sidebar.html"; + +import "./frappe/form/controls/control.js"; +import "./frappe/views/formview.js"; +import "./frappe/form/form.js"; +import "./frappe/meta_tag.js"; + diff --git a/frappe/public/js/frappe-web.bundle.js b/frappe/public/js/frappe-web.bundle.js new file mode 100644 index 0000000000..9f7875f96b --- /dev/null +++ b/frappe/public/js/frappe-web.bundle.js @@ -0,0 +1,26 @@ +import "./jquery-bootstrap"; +import "./frappe/class.js"; +import "./frappe/polyfill.js"; +import "./lib/md5.min.js"; +import "./frappe/provide.js"; +import "./frappe/format.js"; +import "./frappe/utils/number_format.js"; +import "./frappe/utils/utils.js"; +import "./frappe/utils/common.js"; +import "./frappe/ui/messages.js"; +import "./frappe/translate.js"; +import "./frappe/utils/pretty_date.js"; +import "./frappe/microtemplate.js"; +import "./frappe/query_string.js"; + +import "./frappe/upload.js"; + +import "./frappe/model/meta.js"; +import "./frappe/model/model.js"; +import "./frappe/model/perm.js"; + +import "./bootstrap-4-web.bundle"; + + +import "../../website/js/website.js"; +import "./frappe/socketio_client.js"; diff --git a/frappe/public/js/frappe/assets.js b/frappe/public/js/frappe/assets.js index 76441af235..3fca8640f3 100644 --- a/frappe/public/js/frappe/assets.js +++ b/frappe/public/js/frappe/assets.js @@ -9,7 +9,14 @@ frappe.require = function(items, callback) { if(typeof items === "string") { items = [items]; } - frappe.assets.execute(items, callback); + items = items.map(item => frappe.assets.bundled_asset(item)); + + return new Promise(resolve => { + frappe.assets.execute(items, () => { + resolve(); + callback && callback(); + }); + }); }; frappe.assets = { @@ -160,4 +167,11 @@ frappe.assets = { frappe.dom.set_style(txt); } }, + + bundled_asset(path) { + if (!path.startsWith('/assets') && path.includes('.bundle.')) { + return frappe.boot.assets_json[path] || path; + } + return path; + } }; diff --git a/frappe/public/js/frappe/barcode_scanner/index.js b/frappe/public/js/frappe/barcode_scanner/index.js index c5e7a7600f..fa3975b578 100644 --- a/frappe/public/js/frappe/barcode_scanner/index.js +++ b/frappe/public/js/frappe/barcode_scanner/index.js @@ -13,7 +13,7 @@ frappe.barcode.scan_barcode = function() { } }, reject); } else { - frappe.require('/assets/js/barcode_scanner.min.js', () => { + frappe.require('barcode_scanner.bundle.js', () => { frappe.barcode.get_barcode().then(barcode => { resolve(barcode); }); diff --git a/frappe/public/js/frappe/build_events/BuildError.vue b/frappe/public/js/frappe/build_events/BuildError.vue new file mode 100644 index 0000000000..6e10852719 --- /dev/null +++ b/frappe/public/js/frappe/build_events/BuildError.vue @@ -0,0 +1,111 @@ + + + diff --git a/frappe/public/js/frappe/build_events/BuildSuccess.vue b/frappe/public/js/frappe/build_events/BuildSuccess.vue new file mode 100644 index 0000000000..75a365fdc2 --- /dev/null +++ b/frappe/public/js/frappe/build_events/BuildSuccess.vue @@ -0,0 +1,52 @@ + + + diff --git a/frappe/public/js/frappe/build_events/build_events.bundle.js b/frappe/public/js/frappe/build_events/build_events.bundle.js new file mode 100644 index 0000000000..6c8986af3f --- /dev/null +++ b/frappe/public/js/frappe/build_events/build_events.bundle.js @@ -0,0 +1,48 @@ +import BuildError from "./BuildError.vue"; +import BuildSuccess from "./BuildSuccess.vue"; + +let $container = $("#build-events-overlay"); +let success = null; +let error = null; + +frappe.realtime.on("build_event", data => { + if (data.success) { + show_build_success(data); + } else if (data.error) { + show_build_error(data); + } +}); + +function show_build_success() { + if (error) { + error.hide(); + } + if (!success) { + let target = $('
') + .appendTo($container) + .get(0); + let vm = new Vue({ + el: target, + render: h => h(BuildSuccess) + }); + success = vm.$children[0]; + } + success.show(); +} + +function show_build_error(data) { + if (success) { + success.hide(); + } + if (!error) { + let target = $('
') + .appendTo($container) + .get(0); + let vm = new Vue({ + el: target, + render: h => h(BuildError) + }); + error = vm.$children[0]; + } + error.show(data); +} diff --git a/frappe/public/js/frappe/class.js b/frappe/public/js/frappe/class.js index 4f6dd0dc97..79ef2792ae 100644 --- a/frappe/public/js/frappe/class.js +++ b/frappe/public/js/frappe/class.js @@ -80,4 +80,4 @@ To subclass, use: // export global.Class = Class; - })(this); \ No newline at end of file + })(window); diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index d6cb7f5507..46812f5fb6 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -24,12 +24,12 @@ $(document).ready(function() { frappe.start_app(); }); -frappe.Application = Class.extend({ - init: function() { +frappe.Application = class Application { + constructor() { this.startup(); - }, + } - startup: function() { + startup() { frappe.socketio.init(); frappe.model.init(); @@ -115,7 +115,7 @@ frappe.Application = Class.extend({ }); // listen to build errors - this.setup_build_error_listener(); + this.setup_build_events(); if (frappe.sys_defaults.email_user_password) { var email_list = frappe.sys_defaults.email_user_password.split(','); @@ -160,7 +160,7 @@ frappe.Application = Class.extend({ }, 600000); // check every 10 minutes } } - }, + } set_route() { frappe.flags.setting_original_route = true; @@ -175,14 +175,14 @@ frappe.Application = Class.extend({ frappe.router.on('change', () => { $(".tooltip").hide(); }); - }, + } setup_frappe_vue() { Vue.prototype.__ = window.__; Vue.prototype.frappe = window.frappe; - }, + } - set_password: function(user) { + set_password(user) { var me=this; frappe.call({ method: 'frappe.core.doctype.user.user.get_email_awaiting', @@ -199,9 +199,9 @@ frappe.Application = Class.extend({ } } }); - }, + } - email_password_prompt: function(email_account,user,i) { + email_password_prompt(email_account,user,i) { var me = this; let d = new frappe.ui.Dialog({ title: __('Password missing in Email Account'), @@ -255,8 +255,8 @@ frappe.Application = Class.extend({ }); }); d.show(); - }, - load_bootinfo: function() { + } + load_bootinfo() { if(frappe.boot) { this.setup_workspaces(); frappe.model.sync(frappe.boot.docs); @@ -278,7 +278,7 @@ frappe.Application = Class.extend({ } else { this.set_as_guest(); } - }, + } setup_workspaces() { frappe.modules = {}; @@ -289,26 +289,26 @@ frappe.Application = Class.extend({ } if (!frappe.workspaces['home']) { // default workspace is settings for Frappe - frappe.workspaces['home'] = frappe.workspaces['build']; + frappe.workspaces['home'] = frappe.workspaces[Object.keys(frappe.workspaces)[0]]; } - }, + } - load_user_permissions: function() { + load_user_permissions() { frappe.defaults.update_user_permissions(); frappe.realtime.on('update_user_permissions', frappe.utils.debounce(() => { frappe.defaults.update_user_permissions(); }, 500)); - }, + } - check_metadata_cache_status: function() { + check_metadata_cache_status() { if(frappe.boot.metadata_version != localStorage.metadata_version) { frappe.assets.clear_local_storage(); frappe.assets.init_local_storage(); } - }, + } - set_globals: function() { + set_globals() { frappe.session.user = frappe.boot.user.name; frappe.session.logged_in_user = frappe.boot.user.name; frappe.session.user_email = frappe.boot.user.email; @@ -360,8 +360,8 @@ frappe.Application = Class.extend({ } } }); - }, - sync_pages: function() { + } + sync_pages() { // clear cached pages if timestamp is not found if(localStorage["page_info"]) { frappe.boot.allowed_pages = []; @@ -376,8 +376,8 @@ frappe.Application = Class.extend({ frappe.boot.allowed_pages = Object.keys(frappe.boot.page_info); } localStorage["page_info"] = JSON.stringify(frappe.boot.page_info); - }, - set_as_guest: function() { + } + set_as_guest() { frappe.session.user = 'Guest'; frappe.session.user_email = ''; frappe.session.user_fullname = 'Guest'; @@ -385,23 +385,23 @@ frappe.Application = Class.extend({ frappe.user_defaults = {}; frappe.user_roles = ['Guest']; frappe.sys_defaults = {}; - }, - make_page_container: function() { + } + make_page_container() { if ($("#body").length) { $(".splash").remove(); frappe.temp_container = $("').appendTo(this.parent); } - }, - toggle_label: function(show) { + } + toggle_label(show) { this.$wrapper.find(".control-label").toggleClass("hide", !show); - }, - toggle_description: function(show) { + } + toggle_description(show) { this.$wrapper.find(".help-box").toggleClass("hide", !show); - }, - set_input_areas: function() { + } + set_input_areas() { if(this.only_input) { this.input_area = this.wrapper; } else { @@ -43,17 +43,17 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ // like links, currencies, HTMLs etc. this.disp_area = this.$wrapper.find(".control-value").get(0); } - }, - set_max_width: function() { - if(this.horizontal) { + } + set_max_width() { + if(this.constructor.horizontal) { this.$wrapper.addClass("input-max-width"); } - }, + } // update input value, label, description // display (show/hide/read-only), // mandatory style on refresh - refresh_input: function() { + refresh_input() { var me = this; var make_input = function() { if (!me.has_input) { @@ -106,13 +106,13 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ me.set_bold(); me.set_required(); } - }, + } can_write() { return this.disp_status == "Write"; - }, + } - set_disp_area: function(value) { + set_disp_area(value) { if(in_list(["Currency", "Int", "Float"], this.df.fieldtype) && (this.value === 0 || value === 0)) { // to set the 0 value in readonly for currency, int, float field @@ -126,8 +126,8 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ let doc = this.doc || (this.frm && this.frm.doc); let display_value = frappe.format(value, this.df, { no_icon: true, inline: true }, doc); this.disp_area && $(this.disp_area).html(display_value); - }, - set_label: function(label) { + } + set_label(label) { if(label) this.df.label = label; if(this.only_input || this.df.label==this._label) @@ -137,8 +137,8 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ this.label_span.innerHTML = (icon ? ' ' : "") + __(this.df.label) || " "; this._label = this.df.label; - }, - set_description: function(description) { + } + set_description(description) { if (description !== undefined) { this.df.description = description; } @@ -151,17 +151,17 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ this.set_empty_description(); } this._description = this.df.description; - }, - set_new_description: function(description) { + } + set_new_description(description) { this.$wrapper.find(".help-box").html(description); - }, - set_empty_description: function() { + } + set_empty_description() { this.$wrapper.find(".help-box").html(""); - }, - set_mandatory: function(value) { + } + set_mandatory(value) { this.$wrapper.toggleClass("has-error", Boolean(this.df.reqd && is_null(value))); - }, - set_invalid: function () { + } + set_invalid () { let invalid = !!this.df.invalid; if (this.grid) { this.$wrapper.parents('.grid-static-col').toggleClass('invalid', invalid); @@ -170,11 +170,11 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ } else { this.$wrapper.toggleClass('has-error', invalid); } - }, + } set_required() { this.label_area && $(this.label_area).toggleClass('reqd', Boolean(this.df.reqd)); - }, - set_bold: function() { + } + set_bold() { if(this.$input) { this.$input.toggleClass("bold", !!(this.df.bold || this.df.reqd)); } @@ -182,4 +182,4 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ $(this.disp_area).toggleClass("bold", !!(this.df.bold || this.df.reqd)); } } -}); +}; diff --git a/frappe/public/js/frappe/form/controls/button.js b/frappe/public/js/frappe/form/controls/button.js index d09e9c3a95..5e80ebfa0e 100644 --- a/frappe/public/js/frappe/form/controls/button.js +++ b/frappe/public/js/frappe/form/controls/button.js @@ -1,9 +1,9 @@ -frappe.ui.form.ControlButton = frappe.ui.form.ControlData.extend({ +frappe.ui.form.ControlButton = class ControlButton extends frappe.ui.form.ControlData { can_write() { // should be always true in case of button return true; - }, - make_input: function() { + } + make_input() { var me = this; const btn_type = this.df.primary ? 'btn-primary': 'btn-default'; const btn_size = this.df.btn_size @@ -18,8 +18,8 @@ frappe.ui.form.ControlButton = frappe.ui.form.ControlData.extend({ this.set_input_attributes(); this.has_input = true; this.toggle_label(false); - }, - onclick: function() { + } + onclick() { if (this.frm && this.frm.doc) { if (this.frm.script_manager.has_handlers(this.df.fieldname, this.doctype)) { this.frm.script_manager.trigger(this.df.fieldname, this.doctype, this.docname); @@ -31,8 +31,8 @@ frappe.ui.form.ControlButton = frappe.ui.form.ControlData.extend({ } else if (this.df.click) { this.df.click(); } - }, - run_server_script: function() { + } + run_server_script() { // DEPRECATE var me = this; if(this.frm && this.frm.docname) { @@ -47,18 +47,18 @@ frappe.ui.form.ControlButton = frappe.ui.form.ControlData.extend({ } }); } - }, + } hide() { this.$input.hide(); - }, - set_input_areas: function() { - this._super(); + } + set_input_areas() { + super.set_input_areas(); $(this.disp_area).removeClass().addClass("hide"); - }, - set_empty_description: function() { + } + set_empty_description() { this.$wrapper.find(".help-box").empty().toggle(false); - }, - set_label: function(label) { + } + set_label(label) { if (label) { this.df.label = label; } @@ -66,4 +66,4 @@ frappe.ui.form.ControlButton = frappe.ui.form.ControlData.extend({ $(this.label_span).html(" "); this.$input && this.$input.html(label); } -}); +}; diff --git a/frappe/public/js/frappe/form/controls/check.js b/frappe/public/js/frappe/form/controls/check.js index c8dc0df962..658ef640a9 100644 --- a/frappe/public/js/frappe/form/controls/check.js +++ b/frappe/public/js/frappe/form/controls/check.js @@ -1,6 +1,7 @@ -frappe.ui.form.ControlCheck = frappe.ui.form.ControlData.extend({ - input_type: "checkbox", - make_wrapper: function() { +frappe.ui.form.ControlCheck = class ControlCheck extends frappe.ui.form.ControlData { + static html_element = "input" + static input_type = "checkbox" + make_wrapper() { this.$wrapper = $(`
`).appendTo(this.parent); - }, - set_input_areas: function() { + } + set_input_areas() { this.label_area = this.label_span = this.$wrapper.find(".label-area").get(0); this.input_area = this.$wrapper.find(".input-area").get(0); this.disp_area = this.$wrapper.find(".disp-area").get(0); - }, - make_input: function() { - this._super(); + } + make_input() { + super.make_input(); this.$input.removeClass("form-control"); - }, - get_input_value: function() { + } + get_input_value() { return this.input && this.input.checked ? 1 : 0; - }, - validate: function(value) { + } + validate(value) { return cint(value); - }, - set_input: function(value) { + } + set_input(value) { value = cint(value); if(this.input) { this.input.checked = (value ? 1 : 0); @@ -36,4 +37,4 @@ frappe.ui.form.ControlCheck = frappe.ui.form.ControlData.extend({ this.set_mandatory(value); this.set_disp_area(value); } -}); +}; diff --git a/frappe/public/js/frappe/form/controls/code.js b/frappe/public/js/frappe/form/controls/code.js index 9600763588..9155333ee3 100644 --- a/frappe/public/js/frappe/form/controls/code.js +++ b/frappe/public/js/frappe/form/controls/code.js @@ -1,8 +1,8 @@ -frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({ +frappe.ui.form.ControlCode = class ControlCode extends frappe.ui.form.ControlText { make_input() { if (this.editor) return; this.load_lib().then(() => this.make_ace_editor()); - }, + } make_ace_editor() { if (this.editor) return; @@ -34,6 +34,7 @@ frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({ // setup autocompletion when it is set the first time Object.defineProperty(this.df, 'autocompletions', { + configurable: true, get() { return this._autocompletions || []; }, @@ -42,7 +43,7 @@ frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({ this.df._autocompletions = value; } }); - }, + } setup_autocompletion() { if (this._autocompletion_setup) return; @@ -82,20 +83,20 @@ frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({ }); }); this._autocompletion_setup = true; - }, + } refresh_height() { this.ace_editor_target.css('height', this.expanded ? 600 : 300); this.editor.resize(); - }, + } toggle_label() { this.$expand_button && this.$expand_button.text(this.get_button_label()); - }, + } get_button_label() { return this.expanded ? __('Collapse', null, 'Shrink code field.') : __('Expand', null, 'Enlarge code field.'); - }, + } set_language() { const language_map = { @@ -122,14 +123,14 @@ frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({ const ace_language_mode = language_map[language] || ''; this.editor.session.setMode(ace_language_mode); this.editor.setKeyboardHandler('ace/keyboard/vscode'); - }, + } parse(value) { if (value == null) { value = ""; } return value; - }, + } set_formatted_input(value) { return this.load_lib().then(() => { @@ -138,11 +139,11 @@ frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({ if (value === this.get_input_value()) return; this.editor.session.setValue(value); }); - }, + } get_input_value() { return this.editor ? this.editor.session.getValue() : ''; - }, + } load_lib() { if (this.library_loaded) return this.library_loaded; @@ -162,4 +163,4 @@ frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({ return this.library_loaded; } -}); +}; diff --git a/frappe/public/js/frappe/form/controls/color.js b/frappe/public/js/frappe/form/controls/color.js index 8a60f3e3da..7e8e25fac9 100644 --- a/frappe/public/js/frappe/form/controls/color.js +++ b/frappe/public/js/frappe/form/controls/color.js @@ -1,12 +1,13 @@ import Picker from '../../color_picker/color_picker'; -frappe.ui.form.ControlColor = frappe.ui.form.ControlData.extend({ - make_input: function () { +frappe.ui.form.ControlColor = class ControlColor extends frappe.ui.form.ControlData { + make_input() { this.df.placeholder = this.df.placeholder || __('Choose a color'); - this._super(); + super.make_input(); this.make_color_input(); - }, - make_color_input: function () { + } + + make_color_input() { let picker_wrapper = $('
'); this.picker = new Picker({ parent: picker_wrapper[0], @@ -73,27 +74,31 @@ frappe.ui.form.ControlColor = frappe.ui.form.ControlData.extend({ this.$wrapper.popover('hide'); }); }); - }, + } + refresh() { - this._super(); + super.refresh(); let color = this.get_color(); if (this.picker && this.picker.color !== color) { this.picker.color = color; this.picker.refresh(); } - }, - set_formatted_input: function(value) { - this._super(value); + } + + set_formatted_input(value) { + super.set_formatted_input(value); this.$input.val(value); this.selected_color.css({ "background-color": value || 'transparent', }); this.selected_color.toggleClass('no-value', !value); - }, + } + get_color() { return this.validate(this.get_value()); - }, - validate: function (value) { + } + + validate(value) { if (value === '') { return ''; } @@ -103,4 +108,4 @@ frappe.ui.form.ControlColor = frappe.ui.form.ControlData.extend({ } return null; } -}); +}; diff --git a/frappe/public/js/frappe/form/controls/comment.js b/frappe/public/js/frappe/form/controls/comment.js index 7efc60b61d..7c10b61366 100644 --- a/frappe/public/js/frappe/form/controls/comment.js +++ b/frappe/public/js/frappe/form/controls/comment.js @@ -3,7 +3,7 @@ import Mention from './quill-mention/quill.mention'; Quill.register('modules/mention', Mention, true); -frappe.ui.form.ControlComment = frappe.ui.form.ControlTextEditor.extend({ +frappe.ui.form.ControlComment = class ControlComment extends frappe.ui.form.ControlTextEditor { make_wrapper() { this.comment_wrapper = !this.no_wrapper ? $(`
@@ -32,10 +32,10 @@ frappe.ui.form.ControlComment = frappe.ui.form.ControlTextEditor.extend({ this.wrapper = this.$wrapper; this.button = this.comment_wrapper.find('.btn-comment'); - }, + } bind_events() { - this._super(); + super.bind_events(); this.button.click(() => { this.submit(); @@ -52,11 +52,11 @@ frappe.ui.form.ControlComment = frappe.ui.form.ControlTextEditor.extend({ this.quill.on('text-change', frappe.utils.debounce(() => { this.update_state(); }, 300)); - }, + } submit() { this.on_submit && this.on_submit(this.get_value()); - }, + } update_state() { const value = this.get_value(); @@ -65,17 +65,17 @@ frappe.ui.form.ControlComment = frappe.ui.form.ControlTextEditor.extend({ } else { this.button.addClass('btn-default').removeClass('btn-primary'); } - }, + } get_quill_options() { - const options = this._super(); + const options = super.get_quill_options(); return Object.assign(options, { theme: 'bubble', modules: Object.assign(options.modules, { mention: this.get_mention_options() }) }); - }, + } get_mention_options() { if (!this.enable_mentions) { @@ -98,7 +98,7 @@ frappe.ui.form.ControlComment = frappe.ui.form.ControlTextEditor.extend({ return `${value} ${item.is_group ? frappe.utils.icon('users') : ''}`; } }; - }, + } get_toolbar_options() { return [ @@ -108,19 +108,19 @@ frappe.ui.form.ControlComment = frappe.ui.form.ControlTextEditor.extend({ [{ 'list': 'ordered' }, { 'list': 'bullet' }], ['clean'] ]; - }, + } clear() { this.quill.setText(''); - }, + } disable() { this.quill.disable(); this.button.prop('disabled', true); - }, + } enable() { this.quill.enable(); this.button.prop('disabled', false); } -}); +}; diff --git a/frappe/public/js/frappe/form/controls/currency.js b/frappe/public/js/frappe/form/controls/currency.js index f6a7b566d6..0536a7403f 100644 --- a/frappe/public/js/frappe/form/controls/currency.js +++ b/frappe/public/js/frappe/form/controls/currency.js @@ -1,10 +1,10 @@ -frappe.ui.form.ControlCurrency = frappe.ui.form.ControlFloat.extend({ - format_for_input: function(value) { +frappe.ui.form.ControlCurrency = class ControlCurrency extends frappe.ui.form.ControlFloat { + format_for_input(value) { var formatted_value = format_number(value, this.get_number_format(), this.get_precision()); return isNaN(Number(value)) ? "" : formatted_value; - }, + } - get_precision: function() { + get_precision() { // always round based on field precision or currency's precision // this method is also called in this.parse() if (!this.df.precision) { @@ -17,4 +17,4 @@ frappe.ui.form.ControlCurrency = frappe.ui.form.ControlFloat.extend({ return this.df.precision; } -}); +}; diff --git a/frappe/public/js/frappe/form/controls/data.js b/frappe/public/js/frappe/form/controls/data.js index 991f93f30b..977789fc1b 100644 --- a/frappe/public/js/frappe/form/controls/data.js +++ b/frappe/public/js/frappe/form/controls/data.js @@ -1,14 +1,16 @@ frappe.provide('frappe.phone_call'); -frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ - html_element: "input", - input_type: "text", - trigger_change_on_input_event: true, - make_input: function() { +frappe.ui.form.ControlData = class ControlData extends frappe.ui.form.ControlInput { + static html_element = "input"; + static input_type = "text"; + static trigger_change_on_input_event = true; + make_input() { if(this.$input) return; - this.$input = $("<"+ this.html_element +">") - .attr("type", this.input_type) + let { html_element, input_type } = this.constructor; + + this.$input = $("<"+ html_element +">") + .attr("type", input_type) .attr("autocomplete", "off") .addClass("input-with-feedback form-control") .prependTo(this.input_area); @@ -32,7 +34,7 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ let doctype_edit_link = null; if (this.frm.meta.custom) { doctype_edit_link = frappe.utils.get_form_link( - 'DocType', + 'DocType', this.frm.doctype, true, __('this form') ); @@ -65,8 +67,9 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ if (this.df.options == 'URL') { this.setup_url_field(); } - }, - setup_url_field: function() { + } + + setup_url_field() { this.$wrapper.find('.control-input').append( ` @@ -74,14 +77,14 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ ` ); - + this.$link = this.$wrapper.find('.link-btn'); this.$link_open = this.$link.find('.btn-open'); - + this.$input.on("focus", () => { setTimeout(() => { let inputValue = this.get_input_value(); - + if (inputValue && validate_url(inputValue)) { this.$link.toggle(true); this.$link_open.attr('href', this.get_input_value()); @@ -100,7 +103,7 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ this.$link.toggle(false); } }); - + this.$input.on("blur", () => { // if this disappears immediately, the user's click // does not register, hence timeout @@ -108,8 +111,9 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ this.$link.toggle(false); }, 500); }); - }, - bind_change_event: function() { + } + + bind_change_event() { const change_handler = e => { if (this.change) this.change(e); else { @@ -118,12 +122,12 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ } }; this.$input.on("change", change_handler); - if (this.trigger_change_on_input_event) { + if (this.constructor.trigger_change_on_input_event) { // debounce to avoid repeated validations on value change this.$input.on("input", frappe.utils.debounce(change_handler, 500)); } - }, - setup_autoname_check: function() { + } + setup_autoname_check() { if (!this.df.parent) return; this.meta = frappe.get_meta(this.df.parent); if (this.meta && ((this.meta.autoname @@ -152,8 +156,8 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ } }); } - }, - set_input_attributes: function() { + } + set_input_attributes() { this.$input .attr("data-fieldtype", this.df.fieldtype) .attr("data-fieldname", this.df.fieldname) @@ -167,24 +171,24 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ if(this.df.input_class) { this.$input.addClass(this.df.input_class); } - }, - set_input: function(value) { + } + set_input(value) { this.last_value = this.value; this.value = value; this.set_formatted_input(value); this.set_disp_area(value); this.set_mandatory && this.set_mandatory(value); - }, - set_formatted_input: function(value) { + } + set_formatted_input(value) { this.$input && this.$input.val(this.format_for_input(value)); - }, - get_input_value: function() { + } + get_input_value() { return this.$input ? this.$input.val() : undefined; - }, - format_for_input: function(val) { + } + format_for_input(val) { return val==null ? "" : val; - }, - validate: function(v) { + } + validate(v) { if (!v) { return ''; } @@ -217,9 +221,9 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ } else { return v; } - }, - toggle_container_scroll: function(el_class, scroll_class, add=false) { + } + toggle_container_scroll(el_class, scroll_class, add=false) { let el = this.$input.parents(el_class)[0]; if (el) $(el).toggleClass(scroll_class, add); } -}); +}; diff --git a/frappe/public/js/frappe/form/controls/date.js b/frappe/public/js/frappe/form/controls/date.js index 0b3f7b6479..9ad81c7e46 100644 --- a/frappe/public/js/frappe/form/controls/date.js +++ b/frappe/public/js/frappe/form/controls/date.js @@ -1,16 +1,16 @@ -frappe.ui.form.ControlDate = frappe.ui.form.ControlData.extend({ - trigger_change_on_input_event: false, - make_input: function() { - this._super(); +frappe.ui.form.ControlDate = class ControlDate extends frappe.ui.form.ControlData { + static trigger_change_on_input_event = false + make_input() { + super.make_input(); this.make_picker(); - }, - make_picker: function() { + } + make_picker() { this.set_date_options(); this.set_datepicker(); this.set_t_for_today(); - }, - set_formatted_input: function(value) { - this._super(value); + } + set_formatted_input(value) { + super.set_formatted_input(value); if (this.timepicker_only) return; if (!this.datepicker) return; if (!value) { @@ -39,8 +39,8 @@ frappe.ui.form.ControlDate = frappe.ui.form.ControlData.extend({ if(should_refresh) { this.datepicker.selectDate(frappe.datetime.str_to_obj(value)); } - }, - set_date_options: function() { + } + set_date_options() { // webformTODO: let sysdefaults = frappe.boot.sysdefaults; @@ -75,8 +75,8 @@ frappe.ui.form.ControlDate = frappe.ui.form.ControlData.extend({ this.update_datepicker_position(); } }; - }, - set_datepicker: function() { + } + set_datepicker() { this.$input.datepicker(this.datepicker_options); this.datepicker = this.$input.data('datepicker'); @@ -87,8 +87,8 @@ frappe.ui.form.ControlDate = frappe.ui.form.ControlData.extend({ .click(() => { this.datepicker.selectDate(this.get_now_date()); }); - }, - update_datepicker_position: function() { + } + update_datepicker_position() { if(!this.frm) return; // show datepicker above or below the input // based on scroll position @@ -110,11 +110,11 @@ frappe.ui.form.ControlDate = frappe.ui.form.ControlData.extend({ } this.datepicker.update('position', position); - }, - get_now_date: function() { + } + get_now_date() { return frappe.datetime.now_date(true); - }, - set_t_for_today: function() { + } + set_t_for_today() { var me = this; this.$input.on("keydown", function(e) { if(e.which===84) { // 84 === t @@ -128,19 +128,19 @@ frappe.ui.form.ControlDate = frappe.ui.form.ControlData.extend({ return false; } }); - }, - parse: function(value) { + } + parse(value) { if(value) { return frappe.datetime.user_to_str(value); } - }, - format_for_input: function(value) { + } + format_for_input(value) { if(value) { return frappe.datetime.str_to_user(value); } return ""; - }, - validate: function(value) { + } + validate(value) { if(value && !frappe.datetime.validate(value)) { let sysdefaults = frappe.sys_defaults; let date_format = sysdefaults && sysdefaults.date_format @@ -150,4 +150,4 @@ frappe.ui.form.ControlDate = frappe.ui.form.ControlData.extend({ } return value; } -}); +}; diff --git a/frappe/public/js/frappe/form/controls/date_range.js b/frappe/public/js/frappe/form/controls/date_range.js index 6acc7b5748..727e9d55c2 100644 --- a/frappe/public/js/frappe/form/controls/date_range.js +++ b/frappe/public/js/frappe/form/controls/date_range.js @@ -1,11 +1,11 @@ -frappe.ui.form.ControlDateRange = frappe.ui.form.ControlData.extend({ - make_input: function() { - this._super(); +frappe.ui.form.ControlDateRange = class ControlDateRange extends frappe.ui.form.ControlData { + make_input() { + super.make_input(); this.set_date_options(); this.set_datepicker(); this.refresh(); - }, - set_date_options: function() { + } + set_date_options() { var me = this; this.datepicker_options = { language: "en", @@ -18,12 +18,12 @@ frappe.ui.form.ControlDateRange = frappe.ui.form.ControlData.extend({ this.datepicker_options.onSelect = function() { me.$input.trigger('change'); }; - }, - set_datepicker: function() { + } + set_datepicker() { this.$input.datepicker(this.datepicker_options); this.datepicker = this.$input.data('datepicker'); - }, - set_input: function(value, value2) { + } + set_input(value, value2) { this.last_value = this.value; if (value && value2) { this.value = [value, value2]; @@ -38,8 +38,8 @@ frappe.ui.form.ControlDateRange = frappe.ui.form.ControlData.extend({ } this.set_disp_area(value || ''); this.set_mandatory && this.set_mandatory(value); - }, - parse: function(value) { + } + parse(value) { // replace the separator (which can be in user language) with comma const to = __('{0} to {1}').replace('{0}', '').replace('{1}', ''); value = value.replace(to, ','); @@ -50,8 +50,8 @@ frappe.ui.form.ControlDateRange = frappe.ui.form.ControlData.extend({ var to_date = moment(frappe.datetime.user_to_obj(vals[vals.length-1])).format('YYYY-MM-DD'); return [from_date, to_date]; } - }, - format_for_input: function(value1, value2) { + } + format_for_input(value1, value2) { if(value1 && value2) { value1 = frappe.datetime.str_to_user(value1); value2 = frappe.datetime.str_to_user(value2); @@ -59,4 +59,4 @@ frappe.ui.form.ControlDateRange = frappe.ui.form.ControlData.extend({ } return ""; } -}); +}; diff --git a/frappe/public/js/frappe/form/controls/datetime.js b/frappe/public/js/frappe/form/controls/datetime.js index c99dfe899f..341a933066 100644 --- a/frappe/public/js/frappe/form/controls/datetime.js +++ b/frappe/public/js/frappe/form/controls/datetime.js @@ -1,6 +1,6 @@ -frappe.ui.form.ControlDatetime = frappe.ui.form.ControlDate.extend({ - set_date_options: function() { - this._super(); +frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.ControlDate { + set_date_options() { + super.set_date_options(); this.today_text = __("Now"); let sysdefaults = frappe.boot.sysdefaults; this.date_format = frappe.defaultDatetimeFormat; @@ -10,11 +10,11 @@ frappe.ui.form.ControlDatetime = frappe.ui.form.ControlDate.extend({ timepicker: true, timeFormat: time_format.toLowerCase().replace("mm", "ii") }); - }, - get_now_date: function() { + } + get_now_date() { return frappe.datetime.now_datetime(true); - }, - set_description: function() { + } + set_description() { const { description } = this.df; const { time_zone } = frappe.sys_defaults; if (!this.df.hide_timezone && !frappe.datetime.is_timezone_same()) { @@ -24,10 +24,10 @@ frappe.ui.form.ControlDatetime = frappe.ui.form.ControlDate.extend({ this.df.description += '
' + time_zone; } } - this._super(); - }, - set_datepicker: function() { - this._super(); + super.set_description(); + } + set_datepicker() { + super.set_datepicker(); if (this.datepicker.opts.timeFormat.indexOf('s') == -1) { // No seconds in time format const $tp = this.datepicker.timepicker; @@ -36,4 +36,4 @@ frappe.ui.form.ControlDatetime = frappe.ui.form.ControlDate.extend({ $tp.$secondsText.prev().css('display', 'none'); } } -}); +}; diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index e70afd6e65..361d10982e 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -1,10 +1,10 @@ -frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ - make_input: function() { - this._super(); +frappe.ui.form.ControlDuration = class ControlDuration extends frappe.ui.form.ControlData { + make_input() { + super.make_input(); this.make_picker(); - }, + } - make_picker: function() { + make_picker() { this.inputs = []; this.set_duration_options(); this.$picker = $( @@ -21,9 +21,9 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ this.$picker.hide(); this.bind_events(); this.refresh(); - }, + } - build_numeric_input: function(label, hidden, max) { + build_numeric_input(label, hidden, max) { let $duration_input = $(` `); @@ -47,13 +47,13 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ } $control.prepend($input); $control.appendTo(this.$picker.find(".picker-row")); - }, + } set_duration_options() { this.duration_options = frappe.utils.get_duration_options(this.df); - }, + } - set_duration_picker_value: function(value) { + set_duration_picker_value(value) { let total_duration = frappe.utils.seconds_to_duration(value, this.duration_options); if (this.$picker) { @@ -61,9 +61,9 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ this.inputs[duration].prop("value", total_duration[duration]); }); } - }, + } - bind_events: function() { + bind_events() { // flag to handle the display property of the picker let clicked = false; @@ -103,21 +103,21 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ this.$picker.hide(); } }); - }, + } get_value() { return cint(this.value); - }, + } - refresh_input: function() { - this._super(); + refresh_input() { + super.refresh_input(); this.set_duration_options(); this.set_duration_picker_value(this.value); - }, + } - format_for_input: function(value) { + format_for_input(value) { return frappe.utils.get_formatted_duration(value, this.duration_options); - }, + } get_duration() { // returns an object of days, hours, minutes and seconds from the inputs array @@ -138,7 +138,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ } } return total_duration; - }, + } is_duration_picker_set(inputs) { let is_set = false; @@ -149,4 +149,4 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }); return is_set; } -}); \ No newline at end of file +}; diff --git a/frappe/public/js/frappe/form/controls/dynamic_link.js b/frappe/public/js/frappe/form/controls/dynamic_link.js index 00bb02a5fc..2c5661ca87 100644 --- a/frappe/public/js/frappe/form/controls/dynamic_link.js +++ b/frappe/public/js/frappe/form/controls/dynamic_link.js @@ -1,5 +1,5 @@ -frappe.ui.form.ControlDynamicLink = frappe.ui.form.ControlLink.extend({ - get_options: function() { +frappe.ui.form.ControlDynamicLink = class ControlDynamicLink extends frappe.ui.form.ControlLink { + get_options() { let options = ''; if (this.df.get_options) { options = this.df.get_options(); @@ -28,5 +28,5 @@ frappe.ui.form.ControlDynamicLink = frappe.ui.form.ControlLink.extend({ } return options; - }, -}); + } +}; diff --git a/frappe/public/js/frappe/form/controls/float.js b/frappe/public/js/frappe/form/controls/float.js index 306c05dc1b..89f8f23cc5 100644 --- a/frappe/public/js/frappe/form/controls/float.js +++ b/frappe/public/js/frappe/form/controls/float.js @@ -1,27 +1,27 @@ -frappe.ui.form.ControlFloat = frappe.ui.form.ControlInt.extend({ - parse: function(value) { +frappe.ui.form.ControlFloat = class ControlFloat extends frappe.ui.form.ControlInt { + parse(value) { value = this.eval_expression(value); return isNaN(parseFloat(value)) ? null : flt(value, this.get_precision()); - }, + } - format_for_input: function(value) { + format_for_input(value) { var number_format; if (this.df.fieldtype==="Float" && this.df.options && this.df.options.trim()) { number_format = this.get_number_format(); } var formatted_value = format_number(value, number_format, this.get_precision()); return isNaN(Number(value)) ? "" : formatted_value; - }, + } - get_number_format: function() { + get_number_format() { var currency = frappe.meta.get_field_currency(this.df, this.get_doc()); return get_number_format(currency); - }, + } - get_precision: function() { + get_precision() { // round based on field precision or float precision, else don't round return this.df.precision || cint(frappe.boot.sysdefaults.float_precision, null); } -}); +}; frappe.ui.form.ControlPercent = frappe.ui.form.ControlFloat; diff --git a/frappe/public/js/frappe/form/controls/geolocation.js b/frappe/public/js/frappe/form/controls/geolocation.js index dfd0f4d174..080a1cbb48 100644 --- a/frappe/public/js/frappe/form/controls/geolocation.js +++ b/frappe/public/js/frappe/form/controls/geolocation.js @@ -1,11 +1,11 @@ frappe.provide('frappe.utils.utils'); -frappe.ui.form.ControlGeolocation = frappe.ui.form.ControlData.extend({ - horizontal: false, +frappe.ui.form.ControlGeolocation = class ControlGeolocation extends frappe.ui.form.ControlData { + static horizontal = false make_wrapper() { // Create the elements for map area - this._super(); + super.make_wrapper(); let $input_wrapper = this.$wrapper.find('.control-input-wrapper'); this.map_id = frappe.dom.get_unique_id(); @@ -24,14 +24,14 @@ frappe.ui.form.ControlGeolocation = frappe.ui.form.ControlData.extend({ this.make_map(); }); } - }, + } make_map() { this.bind_leaflet_map(); this.bind_leaflet_draw_control(); this.bind_leaflet_locate_control(); this.bind_leaflet_refresh_button(); - }, + } format_for_input(value) { if (!this.map) return; @@ -65,7 +65,7 @@ frappe.ui.form.ControlGeolocation = frappe.ui.form.ControlData.extend({ } else if ((value===undefined) || (value == JSON.stringify(new L.FeatureGroup().toGeoJSON()))) { this.locate_control.start(); } - }, + } bind_leaflet_map() { var circleToGeoJSON = L.Circle.prototype.toGeoJSON; @@ -97,13 +97,13 @@ frappe.ui.form.ControlGeolocation = frappe.ui.form.ControlData.extend({ L.tileLayer(frappe.utils.map_defaults.tiles, frappe.utils.map_defaults.options).addTo(this.map); - }, + } bind_leaflet_locate_control() { // To request location update and set location, sets current geolocation on load this.locate_control = L.control.locate({position:'topright'}); this.locate_control.addTo(this.map); - }, + } bind_leaflet_draw_control() { this.editableLayers = new L.FeatureGroup(); @@ -160,7 +160,7 @@ frappe.ui.form.ControlGeolocation = frappe.ui.form.ControlData.extend({ this.editableLayers.removeLayer(layer); this.set_value(JSON.stringify(this.editableLayers.toGeoJSON())); }); - }, + } bind_leaflet_refresh_button() { L.easyButton({ @@ -177,7 +177,7 @@ frappe.ui.form.ControlGeolocation = frappe.ui.form.ControlData.extend({ icon: 'fa fa-refresh' }] }).addTo(this.map); - }, + } add_non_group_layers(source_layer, target_group) { // https://gis.stackexchange.com/a/203773 @@ -189,11 +189,11 @@ frappe.ui.form.ControlGeolocation = frappe.ui.form.ControlData.extend({ } else { target_group.addLayer(source_layer); } - }, + } clear_editable_layers() { this.editableLayers.eachLayer((l)=>{ this.editableLayers.removeLayer(l); }); } -}); +}; diff --git a/frappe/public/js/frappe/form/controls/heading.js b/frappe/public/js/frappe/form/controls/heading.js index 7b9dd043f2..ccce412eaf 100644 --- a/frappe/public/js/frappe/form/controls/heading.js +++ b/frappe/public/js/frappe/form/controls/heading.js @@ -1,5 +1,5 @@ -frappe.ui.form.ControlHeading = frappe.ui.form.ControlHTML.extend({ - get_content: function() { +frappe.ui.form.ControlHeading = class ControlHeading extends frappe.ui.form.ControlHTML { + get_content() { return "

" + __(this.df.label) + "

"; } -}); +}; diff --git a/frappe/public/js/frappe/form/controls/html.js b/frappe/public/js/frappe/form/controls/html.js index f8a3645705..b2f18d4ccc 100644 --- a/frappe/public/js/frappe/form/controls/html.js +++ b/frappe/public/js/frappe/form/controls/html.js @@ -1,13 +1,13 @@ -frappe.ui.form.ControlHTML = frappe.ui.form.Control.extend({ - make: function() { - this._super(); +frappe.ui.form.ControlHTML = class ControlHTML extends frappe.ui.form.Control { + make() { + super.make(); this.disp_area = this.wrapper; - }, - refresh_input: function() { + } + refresh_input() { var content = this.get_content(); if(content) this.$wrapper.html(content); - }, - get_content: function() { + } + get_content() { var content = this.df.options || ""; content = __(content); try { @@ -15,11 +15,11 @@ frappe.ui.form.ControlHTML = frappe.ui.form.Control.extend({ } catch (e) { return content; } - }, - html: function(html) { + } + html(html) { this.$wrapper.html(html || this.get_content()); - }, - set_value: function(html) { + } + set_value(html) { if(html.appendTo) { // jquery object html.appendTo(this.$wrapper.empty()); @@ -29,4 +29,4 @@ frappe.ui.form.ControlHTML = frappe.ui.form.Control.extend({ this.html(html); } } -}); +}; diff --git a/frappe/public/js/frappe/form/controls/html_editor.js b/frappe/public/js/frappe/form/controls/html_editor.js index f708bdbd98..7d43112e6a 100644 --- a/frappe/public/js/frappe/form/controls/html_editor.js +++ b/frappe/public/js/frappe/form/controls/html_editor.js @@ -1,13 +1,13 @@ -frappe.ui.form.ControlHTMLEditor = frappe.ui.form.ControlMarkdownEditor.extend({ - editor_class: 'html', +frappe.ui.form.ControlHTMLEditor = class ControlHTMLEditor extends frappe.ui.form.ControlMarkdownEditor { + static editor_class = 'html'; set_language() { this.df.options = 'HTML'; - this._super(); - }, + super.set_language(); + } update_preview() { if (!this.markdown_preview) return; let value = this.get_value() || ''; value = frappe.dom.remove_script_and_style(value); this.markdown_preview.html(value); } -}); +}; diff --git a/frappe/public/js/frappe/form/controls/image.js b/frappe/public/js/frappe/form/controls/image.js index 90de22138a..d175330947 100644 --- a/frappe/public/js/frappe/form/controls/image.js +++ b/frappe/public/js/frappe/form/controls/image.js @@ -1,12 +1,12 @@ -frappe.ui.form.ControlImage = frappe.ui.form.Control.extend({ - make: function() { - this._super(); +frappe.ui.form.ControlImage = class ControlImage extends frappe.ui.form.Control { + make() { + super.make(); this.$wrapper.css({"margin": "0px"}); this.$body = $("
").appendTo(this.$wrapper) .css({"margin-bottom": "10px"}); $('
').appendTo(this.$wrapper); - }, - refresh_input: function() { + } + refresh_input() { this.$body.empty(); var doc = this.get_doc(); @@ -19,4 +19,4 @@ frappe.ui.form.ControlImage = frappe.ui.form.Control.extend({ } return false; } -}); +}; diff --git a/frappe/public/js/frappe/form/controls/int.js b/frappe/public/js/frappe/form/controls/int.js index aca3a85603..12652bf86e 100644 --- a/frappe/public/js/frappe/form/controls/int.js +++ b/frappe/public/js/frappe/form/controls/int.js @@ -1,13 +1,13 @@ -frappe.ui.form.ControlInt = frappe.ui.form.ControlData.extend({ - trigger_change_on_input_event: false, - make: function () { - this._super(); +frappe.ui.form.ControlInt = class ControlInt extends frappe.ui.form.ControlData { + static trigger_change_on_input_event = false + make () { + super.make(); // $(this.label_area).addClass('pull-right'); // $(this.disp_area).addClass('text-right'); - }, - make_input: function () { + } + make_input () { var me = this; - this._super(); + super.make_input(); this.$input // .addClass("text-right") .on("focus", function () { @@ -19,11 +19,11 @@ frappe.ui.form.ControlInt = frappe.ui.form.ControlData.extend({ }, 100); return false; }); - }, - validate: function (value) { + } + validate (value) { return this.parse(value); - }, - eval_expression: function (value) { + } + eval_expression (value) { if (typeof value === 'string') { if (value.match(/^[0-9+\-/* ]+$/)) { // If it is a string containing operators @@ -36,8 +36,8 @@ frappe.ui.form.ControlInt = frappe.ui.form.ControlData.extend({ } } return value; - }, - parse: function (value) { + } + parse (value) { return cint(this.eval_expression(value), null); } -}); +}; diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index c32c99f0ed..43bd7443ab 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -8,9 +8,9 @@ import Awesomplete from 'awesomplete'; frappe.ui.form.recent_link_validations = {}; -frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ - trigger_change_on_input_event: false, - make_input: function() { +frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlData { + static trigger_change_on_input_event = false + make_input() { var me = this; $(`