diff --git a/frappe/core/page/desktop/desktop.js b/frappe/core/page/desktop/desktop.js
index eae5b7a35d..b5dc7f7aac 100644
--- a/frappe/core/page/desktop/desktop.js
+++ b/frappe/core/page/desktop/desktop.js
@@ -112,13 +112,17 @@ $.extend(frappe.desktop, {
},
setup_module_click: function() {
+ var wiggling = false; // wiggle, wiggle, wiggle.
+
if(frappe.list_desktop) {
frappe.desktop.wrapper.on("click", ".desktop-list-item", function() {
frappe.desktop.open_module($(this));
});
} else {
frappe.desktop.wrapper.on("click", ".app-icon", function() {
- frappe.desktop.open_module($(this).parent());
+ if ( !wiggling ) {
+ frappe.desktop.open_module($(this).parent());
+ }
});
}
frappe.desktop.wrapper.on("click", ".circle", function() {
@@ -127,6 +131,109 @@ $.extend(frappe.desktop, {
frappe.ui.notifications.show_open_count_list(doctype);
}
});
+
+ // Wiggle, Wiggle, Wiggle.
+ const DURATION_LONG_PRESS = 1000;
+ // lesser the antidode, more the wiggle (like your drunk uncle)
+ // 75 seems good to replicate the iOS feels.
+ const WIGGLE_ANTIDODE = 75;
+
+ var timer_id = 0;
+ const $cases = frappe.desktop.wrapper.find('.case-wrapper');
+ const $icons = frappe.desktop.wrapper.find('.app-icon');
+ const $notis = $(frappe.desktop.wrapper.find('.circle').toArray().filter((object) => {
+ // This hack is so bad, I should punch myself.
+ const doctype = $(object).data('doctype');
+
+ return doctype;
+ }));
+
+ const clearWiggle = ($close) => {
+ const $closes = $cases.find('.module-remove');
+ $closes.hide();
+ $notis.show();
+
+ $icons.trigger('stopRumble');
+ };
+
+ // initiate wiggling.
+ $icons.jrumble({
+ speed: WIGGLE_ANTIDODE // seems neat enough to match the iOS way
+ });
+
+ frappe.desktop.wrapper.on('mousedown', '.app-icon', () => {
+ timer_id = setTimeout(() => {
+ wiggling = true;
+ // hide all notifications.
+ $notis.hide();
+
+ $cases.each((i) => {
+ const $case = $($cases[i]);
+ const template =
+ `
+
+ `
+
+ $case.append(template);
+ const $close = $case.find('.module-remove');
+ const name = $case.data('name');
+ $close.click((event) => {
+ // good enough to create dynamic dialogs?
+ const dialog = new frappe.ui.Dialog({
+ title: __(`Hide ${name}`)
+ });
+ dialog.set_primary_action(__('Hide'), () => {
+ frappe.call({
+ method: 'frappe.desk.doctype.desktop_icon.desktop_icon.hide',
+ args: { name: name },
+ freeze: true,
+ callback: (response) =>
+ {
+ if ( response.message ) {
+ location.reload();
+ }
+ }
+ })
+
+ dialog.hide();
+
+ clearWiggle();
+ });
+ // Hacks, Hacks and Hacks.
+ var $cancel = dialog.get_close_btn();
+ $cancel.click(() => {
+ clearWiggle();
+ });
+ $cancel.html(__(`Cancel`));
+
+ dialog.show();
+ });
+ });
+
+ $icons.trigger('startRumble');
+ }, DURATION_LONG_PRESS);
+ });
+ frappe.desktop.wrapper.on('mouseup mouseleave', '.app-icon', () => {
+ clearTimeout(timer_id);
+ });
+
+ // also stop wiggling if clicked elsewhere.
+ $('body').click((event) => {
+ if ( wiggling ) {
+ const $target = $(event.target);
+ // our target shouldn't be .app-icons or .close
+ const $parent = $target.parents('.case-wrapper');
+ if ( $parent.length == 0 )
+ clearWiggle();
+ }
+ });
+ // end wiggle
},
open_module: function(parent) {
diff --git a/frappe/desk/doctype/desktop_icon/desktop_icon.py b/frappe/desk/doctype/desktop_icon/desktop_icon.py
index 1319ffba49..59118b09b8 100644
--- a/frappe/desk/doctype/desktop_icon/desktop_icon.py
+++ b/frappe/desk/doctype/desktop_icon/desktop_icon.py
@@ -404,3 +404,16 @@ palette = (
('#4F8EA8', 1),
('#428B46', 1)
)
+
+@frappe.whitelist()
+def hide(name, user = None):
+ if not user:
+ user = frappe.session.user
+
+ try:
+ set_hidden(name, user, hidden = 1)
+ clear_desktop_icons_cache()
+ except:
+ return False
+
+ return True
\ No newline at end of file
diff --git a/frappe/public/build.json b/frappe/public/build.json
index 913adfe735..cf6e784ba5 100755
--- a/frappe/public/build.json
+++ b/frappe/public/build.json
@@ -130,7 +130,8 @@
"public/js/lib/jSignature.min.js",
"public/js/frappe/translate.js",
"public/js/lib/datepicker/datepicker.min.js",
- "public/js/lib/datepicker/locale-all.js"
+ "public/js/lib/datepicker/locale-all.js",
+ "public/js/lib/jquery.jrumble.min.js"
],
"js/desk.min.js": [
"public/js/frappe/class.js",
diff --git a/frappe/public/js/lib/jquery.jrumble.min.js b/frappe/public/js/lib/jquery.jrumble.min.js
new file mode 100644
index 0000000000..71de6ffb7c
--- /dev/null
+++ b/frappe/public/js/lib/jquery.jrumble.min.js
@@ -0,0 +1,2 @@
+/* jRumble v1.3 - http://jackrugile.com/jrumble - MIT License */
+(function(f){f.fn.jrumble=function(g){var a=f.extend({x:2,y:2,rotation:1,speed:15,opacity:false,opacityMin:0.5},g);return this.each(function(){var b=f(this),h=a.x*2,i=a.y*2,k=a.rotation*2,g=a.speed===0?1:a.speed,m=a.opacity,n=a.opacityMin,l,j,o=function(){var e=Math.floor(Math.random()*(h+1))-h/2,a=Math.floor(Math.random()*(i+1))-i/2,c=Math.floor(Math.random()*(k+1))-k/2,d=m?Math.random()+n:1,e=e===0&&h!==0?Math.random()<0.5?1:-1:e,a=a===0&&i!==0?Math.random()<0.5?1:-1:a;b.css("display")==="inline"&&(l=true,b.css("display","inline-block"));b.css({position:"relative",left:e+"px",top:a+"px","-ms-filter":"progid:DXImageTransform.Microsoft.Alpha(Opacity="+d*100+")",filter:"alpha(opacity="+d*100+")","-moz-opacity":d,"-khtml-opacity":d,opacity:d,"-webkit-transform":"rotate("+c+"deg)","-moz-transform":"rotate("+c+"deg)","-ms-transform":"rotate("+c+"deg)","-o-transform":"rotate("+c+"deg)",transform:"rotate("+c+"deg)"})},p={left:0,top:0,"-ms-filter":"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)",filter:"alpha(opacity=100)","-moz-opacity":1,"-khtml-opacity":1,opacity:1,"-webkit-transform":"rotate(0deg)","-moz-transform":"rotate(0deg)","-ms-transform":"rotate(0deg)","-o-transform":"rotate(0deg)",transform:"rotate(0deg)"};b.bind({startRumble:function(a){a.stopPropagation();clearInterval(j);j=setInterval(o,g)},stopRumble:function(a){a.stopPropagation();clearInterval(j);l&&b.css("display","inline");b.css(p)}})})}})(jQuery);
\ No newline at end of file