diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py
index 362f4c79b3a..1acd7ee670d 100644
--- a/frappe/model/__init__.py
+++ b/frappe/model/__init__.py
@@ -72,7 +72,8 @@
'Email',
'Name',
'Phone',
- 'URL'
+ 'URL',
+ 'Barcode'
)
default_fields = (
diff --git a/frappe/public/icons/timeless/symbol-defs.svg b/frappe/public/icons/timeless/symbol-defs.svg
index 6108daa938a..0e8e24b7683 100644
--- a/frappe/public/icons/timeless/symbol-defs.svg
+++ b/frappe/public/icons/timeless/symbol-defs.svg
@@ -563,6 +563,10 @@
+
+
+
diff --git a/frappe/public/js/desk.bundle.js b/frappe/public/js/desk.bundle.js
index 66eb72cda07..c4ecf67c4f0 100644
--- a/frappe/public/js/desk.bundle.js
+++ b/frappe/public/js/desk.bundle.js
@@ -103,3 +103,4 @@ import "./frappe/ui/datatable.js";
import "./frappe/ui/driver.js";
import "./frappe/ui/plyr.js";
import "./frappe/barcode_scanner/index.js";
+import "./frappe/scanner";
diff --git a/frappe/public/js/frappe/form/controls/data.js b/frappe/public/js/frappe/form/controls/data.js
index 977789fc1b8..e4a7dd6d593 100644
--- a/frappe/public/js/frappe/form/controls/data.js
+++ b/frappe/public/js/frappe/form/controls/data.js
@@ -67,6 +67,10 @@ frappe.ui.form.ControlData = class ControlData extends frappe.ui.form.ControlInp
if (this.df.options == 'URL') {
this.setup_url_field();
}
+
+ if (this.df.options == 'Barcode') {
+ this.setup_barcode_field();
+ }
}
setup_url_field() {
@@ -113,6 +117,43 @@ frappe.ui.form.ControlData = class ControlData extends frappe.ui.form.ControlInp
});
}
+ setup_barcode_field() {
+ this.$wrapper.find('.control-input').append(
+ `
+
+ ${frappe.utils.icon('scan', 'sm')}
+
+ `
+ );
+
+ this.$scan_btn = this.$wrapper.find('.link-btn');
+
+ this.$input.on("focus", () => {
+ setTimeout(() => {
+ this.$scan_btn.toggle(true);
+ }, 500);
+ });
+
+ const me = this;
+ this.$scan_btn.on('click', 'a', () => {
+ new frappe.ui.Scanner({
+ dialog: true,
+ multiple: false,
+ on_scan(data) {
+ if (data && data.result && data.result.text) {
+ me.set_value(data.result.text);
+ }
+ }
+ });
+ });
+
+ this.$input.on("blur", () => {
+ setTimeout(() => {
+ this.$scan_btn.toggle(false);
+ }, 500);
+ });
+ }
+
bind_change_event() {
const change_handler = e => {
if (this.change) this.change(e);
diff --git a/frappe/public/js/frappe/scanner/index.js b/frappe/public/js/frappe/scanner/index.js
new file mode 100644
index 00000000000..0b9acf7f9e6
--- /dev/null
+++ b/frappe/public/js/frappe/scanner/index.js
@@ -0,0 +1,101 @@
+frappe.provide("frappe.ui");
+
+frappe.ui.Scanner = class Scanner {
+ constructor(options) {
+ this.dialog = null;
+ this.handler = null;
+ this.options = options;
+ this.is_alive = false;
+
+ if (!("multiple" in this.options)) {
+ this.options.multiple = false;
+ }
+ if (options.container) {
+ this.$scan_area = $(options.container);
+ this.scan_area_id = frappe.dom.set_unique_id(this.$scan_area);
+ }
+ if (options.dialog) {
+ this.dialog = this.make_dialog();
+ this.dialog.show();
+ }
+ }
+
+ scan() {
+ this.load_lib().then(() => this.start_scan());
+ }
+
+ start_scan() {
+ if (!this.handler) {
+ this.handler = new Html5Qrcode(this.scan_area_id); // eslint-disable-line
+ }
+ this.handler
+ .start(
+ { facingMode: "environment" },
+ { fps: 10, qrbox: 250 },
+ (decodedText, decodedResult) => {
+ if (this.options.on_scan) {
+ try {
+ this.options.on_scan(decodedResult);
+ } catch (error) {
+ console.error(error); // eslint-disable-line
+ }
+ }
+ if (!this.options.multiple) {
+ this.stop_scan();
+ this.hide_dialog();
+ }
+ },
+ errorMessage => { // eslint-disable-line
+ // parse error, ignore it.
+ }
+ )
+ .catch(err => {
+ this.is_alive = false;
+ this.hide_dialog();
+ console.error(err); // eslint-disable-line
+ });
+ this.is_alive = true;
+ }
+
+ stop_scan() {
+ if (this.handler && this.is_alive) {
+ this.handler.stop().then(() => {
+ this.is_alive = false;
+ this.$scan_area.empty();
+ this.hide_dialog();
+ });
+ }
+ }
+
+ make_dialog() {
+ let dialog = new frappe.ui.Dialog({
+ title: __("Scan QRCode"),
+ fields: [
+ {
+ fieldtype: "HTML",
+ fieldname: "scan_area"
+ }
+ ],
+ on_page_show: () => {
+ this.$scan_area = dialog.get_field("scan_area").$wrapper;
+ this.$scan_area.addClass("barcode-scanner");
+ this.scan_area_id = frappe.dom.set_unique_id(this.$scan_area);
+ this.scan();
+ },
+ on_hide: () => {
+ this.stop_scan();
+ }
+ });
+ return dialog;
+ }
+
+ hide_dialog() {
+ this.dialog && this.dialog.hide();
+ }
+
+ load_lib() {
+ return frappe.require(
+ "/assets/frappe/node_modules/html5-qrcode/dist/html5-qrcode.min.js"
+ );
+ }
+};
diff --git a/package.json b/package.json
index c099b14adeb..5b9504e142f 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
"frappe-gantt": "^0.5.0",
"fuse.js": "^3.4.6",
"highlight.js": "^10.4.1",
+ "html5-qrcode": "^2.0.11",
"jquery": "3.5.0",
"js-sha256": "^0.9.0",
"jsbarcode": "^3.9.0",
diff --git a/yarn.lock b/yarn.lock
index 899e0955498..11a774780e1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3183,6 +3183,11 @@ hsla-regex@^1.0.0:
resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38"
integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg=
+html5-qrcode@^2.0.11:
+ version "2.0.11"
+ resolved "https://registry.yarnpkg.com/html5-qrcode/-/html5-qrcode-2.0.11.tgz#2cc5f63e767be53dd6c6d56b6c4f180a12aa8075"
+ integrity sha512-cCrVOK2yJGPGSTjchqRhkBJIrxvojEwF/pDKLNxmTH1wiAsVc61ZnIqyApIVyNfn5dKRFax70Qpr7pZwbUNiUw==
+
http-cache-semantics@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"