From 226ad1d91aacef25cf8c1736fa872b78940713a2 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Sat, 17 Apr 2021 15:12:38 +0530 Subject: [PATCH] feat: New Build System based on esbuild - Deprecate use of build.json - *.bundle.js files placed anywhere in the public folder are bundled - Built files are created in public/build folder which is gitignored WIP --- .gitignore | 1 + esbuild/esbuild-plugin-html.js | 44 + esbuild/esbuild-watch.js | 69 ++ esbuild/ignore-assets.js | 11 + esbuild/index.js | 79 ++ esbuild/utils.js | 110 ++ frappe/hooks.py | 10 +- frappe/public/js/controls.bundle.js | 20 + frappe/public/js/desk.bundle.js | 107 ++ frappe/public/js/form.bundle.js | 18 + frappe/public/js/frappe-web.bundle.js | 25 + frappe/public/js/frappe/class.js | 2 +- frappe/public/js/frappe/form/layout.js | 2 +- frappe/public/js/frappe/utils/common.js | 3 +- frappe/public/js/frappe/utils/utils.js | 8 + .../js/frappe/views/reports/query_report.js | 2 +- frappe/public/js/libs.bundle.js | 11 + frappe/public/js/list.bundle.js | 42 + frappe/public/js/report.bundle.js | 9 + frappe/public/js/web/bootstrap-4.js | 65 + frappe/public/scss/common/datepicker.scss | 2 +- frappe/public/scss/common/quill.scss | 4 +- frappe/templates/base.html | 3 +- frappe/utils/jinja.py | 9 +- frappe/website/js/website.js | 3 +- frappe/www/app.html | 13 +- package.json | 14 +- yarn.lock | 1045 ++++++++++++++++- 28 files changed, 1676 insertions(+), 55 deletions(-) create mode 100644 esbuild/esbuild-plugin-html.js create mode 100644 esbuild/esbuild-watch.js create mode 100644 esbuild/ignore-assets.js create mode 100644 esbuild/index.js create mode 100644 esbuild/utils.js create mode 100644 frappe/public/js/controls.bundle.js create mode 100644 frappe/public/js/desk.bundle.js create mode 100644 frappe/public/js/form.bundle.js create mode 100644 frappe/public/js/frappe-web.bundle.js create mode 100644 frappe/public/js/libs.bundle.js create mode 100644 frappe/public/js/list.bundle.js create mode 100644 frappe/public/js/report.bundle.js create mode 100644 frappe/public/js/web/bootstrap-4.js diff --git a/.gitignore b/.gitignore index 766288fe2e..08d9876242 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ locale dist/ # build/ frappe/docs/current +frappe/public/build .vscode node_modules .kdev4/ diff --git a/esbuild/esbuild-plugin-html.js b/esbuild/esbuild-plugin-html.js new file mode 100644 index 0000000000..9edd6b627a --- /dev/null +++ b/esbuild/esbuild-plugin-html.js @@ -0,0 +1,44 @@ +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(/\s/g, " "); + content = content.replace(/()/g, ""); + return content.replace("'", "'"); // eslint-disable-line +} diff --git a/esbuild/esbuild-watch.js b/esbuild/esbuild-watch.js new file mode 100644 index 0000000000..97d11024c2 --- /dev/null +++ b/esbuild/esbuild-watch.js @@ -0,0 +1,69 @@ +let esbuild = require("esbuild"); +let htmlPlugin = require("./esbuild-plugin-html"); +let vue = require("esbuild-vue"); +let http = require("http"); +let httpProxy = require("http-proxy"); +let path = require("path"); + +const { get_public_path, apps_list } = require("../rollup/rollup.utils"); + +let proxy = httpProxy.createProxyServer({}); + +proxy.on("error", function(e) { + console.error(e); +}); + +let server = http.createServer((req, res) => { + if (req.url.includes("/public/")) { + buildAsset(req.url).then(result => { + if (!result) { + proxy_request(); + return; + } + res.setHeader("Content-Header", "application/javascript"); + res.writeHead(200); + res.end(result); + }); + } else { + proxy_request(); + } + + function proxy_request() { + proxy.web(req, res, { target: "http://localhost:8000" }); + } +}); + +server.listen(8080); + +server.on("listening", () => { + console.log("dev server started at http:localhost:8080"); +}); + +function buildAsset(url) { + if (url.startsWith("/")) { + url = url.substr(1); + } + let app; + let parts = url.split(path.sep); + if (apps_list.includes(parts[0])) { + app = parts[0]; + } + if (!app) return; + + let filepath = path.resolve(get_public_path(app), url.split("public/")[1]); + console.log("building " + url); + + return esbuild + .build({ + entryPoints: [filepath], + write: false, + bundle: true, + plugins: [htmlPlugin, vue()], + define: { + "process.env.NODE_ENV": "'development'" + } + }) + .then(result => { + return result.outputFiles[0].text; + }); +} 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..419b6eb641 --- /dev/null +++ b/esbuild/index.js @@ -0,0 +1,79 @@ +let glob = require("fast-glob"); +let esbuild = require("esbuild"); +let html_plugin = require("./esbuild-plugin-html"); +let vue = require("esbuild-vue"); +let postCssPlugin = require("esbuild-plugin-postcss2").default; +let ignore_assets = require("./ignore-assets"); +let { get_options_for_scss } = require("../rollup/rollup.utils"); + +console.time("Build time"); + +glob(["frappe/public/js/**/*.bundle.js"]).then(entry_files => { + esbuild + .build({ + entryPoints: entry_files, + outdir: "frappe/public/build", + outbase: "frappe/public", + sourcemap: true, + bundle: true, + metafile: true, + minify: true, + define: { + "process.env.NODE_ENV": "'development'" + }, + plugins: [ + html_plugin, + ignore_assets, + vue(), + postCssPlugin({ + plugins: [require("autoprefixer")], + sassOptions: { + ...get_options_for_scss(), + 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 + }; + } + } + }) + ], + + // watch: { + // onRebuild(error, result) { + // if (error) console.error("watch build failed:", error); + // else { + // console.log("watch build succeeded:"); + // log_build_meta(result.metafile); + // } + // } + // } + }) + .then(result => { + log_build_meta(result.metafile); + + if (result.warnings.length) { + console.warn(result.warnings); + } + }) + .catch(e => console.error("error")) + .finally(() => { + console.timeEnd("Build time"); + }); +}); + +function log_build_meta(metafile) { + for (let outfile in metafile.outputs) { + if (outfile.endsWith('.map')) continue; + let data = metafile.outputs[outfile]; + console.log(outfile, data.bytes / 1000 + " Kb"); + } +} diff --git a/esbuild/utils.js b/esbuild/utils.js new file mode 100644 index 0000000000..6cecebcf59 --- /dev/null +++ b/esbuild/utils.js @@ -0,0 +1,110 @@ +const path = require("path"); +const fs = require("fs"); + +const frappe_path = path.resolve(__dirname, ".."); +const bench_path = path.resolve(frappe_path, "..", ".."); +const sites_path = path.resolve(bench_path, "sites"); +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(bench_path, "apps", 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]; + +const get_options_for_scss = () => { + const node_modules_path = path.resolve( + get_app_path("frappe"), + "..", + "node_modules" + ); + const app_paths = app_list + .map(get_app_path) + .map(app_path => path.resolve(app_path, "..")); + + return { + includePaths: [node_modules_path, ...app_paths] + }; +}; + +function get_apps_list() { + return fs + .readFileSync(path.resolve(sites_path, "apps.txt"), { + encoding: "utf-8" + }) + .split("\n") + .filter(Boolean); +} + +module.exports = { + app_list, + bench_path, + assets_path, + sites_path, + bundle_map, + get_public_path, + get_build_json_path, + get_build_json, + get_app_path, + delete_file, + run_serially, + get_options_for_scss +}; diff --git a/frappe/hooks.py b/frappe/hooks.py index 74c538c5df..967aed7f60 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -30,11 +30,11 @@ 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", + "frappe/public/js/desk.bundle.js", + "frappe/public/js/list.bundle.js", + "frappe/public/js/form.bundle.js", + "frappe/public/js/controls.bundle.js", + "frappe/public/js/report.bundle.js", ] app_include_css = [ "/assets/css/desk.min.css", diff --git a/frappe/public/js/controls.bundle.js b/frappe/public/js/controls.bundle.js new file mode 100644 index 0000000000..f9a983ecae --- /dev/null +++ b/frappe/public/js/controls.bundle.js @@ -0,0 +1,20 @@ +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"; + +console.log('controls') diff --git a/frappe/public/js/desk.bundle.js b/frappe/public/js/desk.bundle.js new file mode 100644 index 0000000000..469714158c --- /dev/null +++ b/frappe/public/js/desk.bundle.js @@ -0,0 +1,107 @@ +import '../scss/desk.scss'; +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/test_utils.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/form.bundle.js b/frappe/public/js/form.bundle.js new file mode 100644 index 0000000000..994a5437c3 --- /dev/null +++ b/frappe/public/js/form.bundle.js @@ -0,0 +1,18 @@ +import "./frappe/form/templates/address_list.html"; +import "./frappe/form/templates/attachment.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..eef68b54c6 --- /dev/null +++ b/frappe/public/js/frappe-web.bundle.js @@ -0,0 +1,25 @@ +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 "./web/bootstrap-4"; + + +import "../../website/js/website.js"; +import "./frappe/socketio_client.js"; 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/form/layout.js b/frappe/public/js/frappe/form/layout.js index 8b6c627882..d1437623a6 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -543,7 +543,7 @@ frappe.ui.form.Layout = Class.extend({ } else if (expression.substr(0, 5)=='eval:') { try { - out = eval(expression.substr(5)); + out = frappe.utils.eval(expression.substr(5), { doc }); if (parent && parent.istable && expression.includes('is_submittable')) { out = true; } diff --git a/frappe/public/js/frappe/utils/common.js b/frappe/public/js/frappe/utils/common.js index 8fec3b2611..cce356f6ca 100644 --- a/frappe/public/js/frappe/utils/common.js +++ b/frappe/public/js/frappe/utils/common.js @@ -1,4 +1,5 @@ // common file between desk and website +import md5 from 'md5' frappe.avatar = function (user, css_class, title, image_url=null, remove_color=false, filterable=false) { let user_info; @@ -375,4 +376,4 @@ frappe.utils.get_page_view_count = function (route) { return frappe.call("frappe.website.doctype.web_page_view.web_page_view.get_page_view_count", { path: route }); -}; \ No newline at end of file +}; diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 7ce30a525c..fc3865a322 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -954,6 +954,14 @@ Object.assign(frappe.utils, { return $el; }, + eval(code, context={}) { + let variable_names = Object.keys(context); + let variables = Object.values(context); + code = `return (${code})`; + let expression_function = new Function(...variable_names, code); + return expression_function(...variables); + }, + get_browser() { let ua = navigator.userAgent; let tem; diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 834946b437..27aace1c75 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -445,7 +445,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { out = expression; } else if (expression.substr(0, 5) == 'eval:') { try { - out = eval(expression.substr(5)); + out = frappe.utils.eval(expression.substr(5), { doc }); } catch (e) { frappe.throw(__('Invalid "depends_on" expression set in filter {0}', [filter_label])); } diff --git a/frappe/public/js/libs.bundle.js b/frappe/public/js/libs.bundle.js new file mode 100644 index 0000000000..3d635e1f51 --- /dev/null +++ b/frappe/public/js/libs.bundle.js @@ -0,0 +1,11 @@ +import "bootstrap/dist/js/bootstrap.bundle.js"; +import Vue from "vue/dist/vue.esm.js"; +import moment from "moment/min/moment-with-locales.js"; +import momentTimezone from "moment-timezone/builds/moment-timezone-with-data.js"; +import "socket.io-client/dist/socket.io.slim.js"; +import "./lib/Sortable.min.js"; +import "./lib/jquery/jquery.hotkeys.js"; +import "./lib/jSignature.min.js"; + +window.moment = momentTimezone; +window.Vue = Vue; diff --git a/frappe/public/js/list.bundle.js b/frappe/public/js/list.bundle.js new file mode 100644 index 0000000000..e40ef94a81 --- /dev/null +++ b/frappe/public/js/list.bundle.js @@ -0,0 +1,42 @@ +import "./frappe/ui/listing.html"; + +import "./frappe/model/indicator.js"; +import "./frappe/ui/filters/filter.js"; +import "./frappe/ui/filters/filter_list.js"; +import "./frappe/ui/filters/field_select.js"; +import "./frappe/ui/filters/edit_filter.html"; +import "./frappe/ui/tags.js"; +import "./frappe/ui/tag_editor.js"; +import "./frappe/ui/like.js"; +// import "./frappe/ui/liked_by.html"; +import "../html/print_template.html"; + +import "./frappe/list/base_list.js"; +import "./frappe/list/list_view.js"; +import "./frappe/list/list_factory.js"; + +import "./frappe/list/list_view_select.js"; +import "./frappe/list/list_sidebar.js"; +import "./frappe/list/list_sidebar.html"; +import "./frappe/list/list_sidebar_stat.html"; +import "./frappe/list/list_sidebar_group_by.js"; +import "./frappe/list/list_view_permission_restrictions.html"; + +import "./frappe/views/gantt/gantt_view.js"; +import "./frappe/views/calendar/calendar.js"; +import "./frappe/views/dashboard/dashboard_view.js"; +import "./frappe/views/image/image_view.js"; +import "./frappe/views/map/map_view.js"; +import "./frappe/views/kanban/kanban_view.js"; +import "./frappe/views/inbox/inbox_view.js"; +import "./frappe/views/file/file_view.js"; + +import "./frappe/views/treeview.js"; +import "./frappe/views/interaction.js"; + +import "./frappe/views/image/image_view_item_row.html"; +import "./frappe/views/image/photoswipe_dom.html"; + +import "./frappe/views/kanban/kanban_board.html"; +import "./frappe/views/kanban/kanban_column.html"; +import "./frappe/views/kanban/kanban_card.html"; diff --git a/frappe/public/js/report.bundle.js b/frappe/public/js/report.bundle.js new file mode 100644 index 0000000000..3f2aa3b4c1 --- /dev/null +++ b/frappe/public/js/report.bundle.js @@ -0,0 +1,9 @@ +import "./lib/clusterize.min.js"; +import "./frappe/views/reports/report_factory.js"; +import "./frappe/views/reports/report_view.js"; +import "./frappe/views/reports/query_report.js"; +import "./frappe/views/reports/print_grid.html"; +import "./frappe/views/reports/print_tree.html"; +import "./frappe/ui/group_by/group_by.html"; +import "./frappe/ui/group_by/group_by.js"; +import "./frappe/views/reports/report_utils.js"; diff --git a/frappe/public/js/web/bootstrap-4.js b/frappe/public/js/web/bootstrap-4.js new file mode 100644 index 0000000000..c12919c570 --- /dev/null +++ b/frappe/public/js/web/bootstrap-4.js @@ -0,0 +1,65 @@ +import 'bootstrap/dist/js/bootstrap.bundle'; + +// 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/scss/common/datepicker.scss b/frappe/public/scss/common/datepicker.scss index d159f27fc1..93bdfcc03d 100644 --- a/frappe/public/scss/common/datepicker.scss +++ b/frappe/public/scss/common/datepicker.scss @@ -1,4 +1,4 @@ -@import "~air-datepicker/dist/css/datepicker.min.css"; +@import "~air-datepicker/dist/css/datepicker.min"; .datepicker { diff --git a/frappe/public/scss/common/quill.scss b/frappe/public/scss/common/quill.scss index d15ca7e036..95942d7c61 100644 --- a/frappe/public/scss/common/quill.scss +++ b/frappe/public/scss/common/quill.scss @@ -1,5 +1,5 @@ -@import '~quill/dist/quill.snow.css'; -@import '~quill/dist/quill.bubble.css'; +@import '~quill/dist/quill.snow'; +@import '~quill/dist/quill.bubble'; .ql-toolbar.ql-snow, .ql-container.ql-snow { diff --git a/frappe/templates/base.html b/frappe/templates/base.html index 78aa573c99..7eb4e5e8d6 100644 --- a/frappe/templates/base.html +++ b/frappe/templates/base.html @@ -97,8 +97,7 @@ {% block base_scripts %} - - + {% endblock %} {%- for link in web_include_js %} diff --git a/frappe/utils/jinja.py b/frappe/utils/jinja.py index cd74b2a283..8a80013887 100644 --- a/frappe/utils/jinja.py +++ b/frappe/utils/jinja.py @@ -23,7 +23,8 @@ def get_jenv(): 'resolve_class': resolve_class, 'inspect': inspect, 'web_blocks': web_blocks, - 'web_block': web_block + 'web_block': web_block, + 'js_asset': js_asset }) frappe.local.jenv = jenv @@ -228,3 +229,9 @@ def web_blocks(blocks): html += ''.format(script) return html + +def js_asset(path): + import frappe + if not frappe.local.dev_server or True: + path = path.replace('frappe/public/', '/assets/frappe/build/') + return f'' diff --git a/frappe/website/js/website.js b/frappe/website/js/website.js index ea0b9aedfa..9e800c8f1a 100644 --- a/frappe/website/js/website.js +++ b/frappe/website/js/website.js @@ -605,7 +605,8 @@ $(document).ready(function() { $(document).on("page-change", function() { $(document).trigger("apply_permissions"); - $('.dropdown-toggle').dropdown(); + // TODO: esbuild + // $('.dropdown-toggle').dropdown(); //multilevel dropdown fix $('.dropdown-menu .dropdown-submenu .dropdown-toggle').on('click', function(e) { diff --git a/frappe/www/app.html b/frappe/www/app.html index 8da4d11c00..419d3ffe07 100644 --- a/frappe/www/app.html +++ b/frappe/www/app.html @@ -52,11 +52,14 @@ - {% for include in include_js %} - - {% endfor %} - {% include "templates/includes/app_analytics/google_analytics.html" %} - {% include "templates/includes/app_analytics/mixpanel_analytics.html" %} + {% for include in include_js %} + {{ js_asset(include) }} + {% endfor %} + + {{ js_asset('frappe/public/js/test.bundle.js') }} + + {% include "templates/includes/app_analytics/google_analytics.html" %} + {% include "templates/includes/app_analytics/mixpanel_analytics.html" %} {% for sound in (sounds or []) %}