mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-21 10:32:09 +02:00
Initial commit: Core packages
This commit is contained in:
commit
12c29a983b
9512 changed files with 8379910 additions and 0 deletions
BIN
odoo-bringout-oca-ocb-mail/mail/static/description/icon.png
Normal file
BIN
odoo-bringout-oca-ocb-mail/mail/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
|
|
@ -0,0 +1 @@
|
|||
<svg width="70" height="70" viewBox="0 0 70 70" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>mail/static/description/icon</title><defs><path d="M4 0h61c4 0 5 1 5 5v60c0 4-1 5-5 5H4c-3 0-4-1-4-5V5c0-4 1-5 4-5z" id="a"/><linearGradient x1="100%" y1="0%" x2="0%" y2="100%" id="c"><stop stop-color="#CD7690" offset="0%"/><stop stop-color="#CA5377" offset="100%"/></linearGradient><path d="M57.706 35.71c0 9.276-10.012 16.777-22.351 16.777-3.749 0-7.287-.694-10.392-1.92-3.127 2.517-6.969 4.057-11.051 4.493a.835.835 0 0 1-.893-.621c-.1-.404.21-.654.513-.952 1.497-1.484 3.313-2.646 4.027-7.63-2.855-2.815-4.555-6.331-4.555-10.146 0-9.267 10.011-16.776 22.35-16.776 12.34 0 22.352 7.509 22.352 16.776zm-33.647-6.534a1 1 0 0 0-1 1v1.353a1 1 0 0 0 1 1h22.588a1 1 0 0 0 1-1v-1.353a1 1 0 0 0-1-1H24.06zm0 8.056a1 1 0 0 0-1 1v1.353a1 1 0 0 0 1 1h22.588a1 1 0 0 0 1-1v-1.353a1 1 0 0 0-1-1H24.06z" id="d"/><path d="M57.706 33.71c0 9.276-10.012 16.777-22.351 16.777-3.749 0-7.287-.694-10.392-1.92-3.127 2.517-6.969 4.057-11.051 4.493a.835.835 0 0 1-.893-.621c-.1-.404.21-.654.513-.952 1.497-1.484 3.313-2.646 4.027-7.63-2.855-2.815-4.555-6.331-4.555-10.146 0-9.267 10.011-16.776 22.35-16.776 12.34 0 22.352 7.509 22.352 16.776zm-33.647-6.534a1 1 0 0 0-1 1v1.353a1 1 0 0 0 1 1h22.588a1 1 0 0 0 1-1v-1.353a1 1 0 0 0-1-1H24.06zm0 8.056a1 1 0 0 0-1 1v1.353a1 1 0 0 0 1 1h22.588a1 1 0 0 0 1-1v-1.353a1 1 0 0 0-1-1H24.06z" id="e"/></defs><g fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><g mask="url(#b)"><path fill="url(#c)" d="M0 0H70V70H0z"/><path d="M4 1h61c2.667 0 4.333.667 5 2V0H0v3c.667-1.333 2-2 4-2z" fill="#FFF" fill-opacity=".383"/><path d="M36.847 68.65L4 69c-2 0-4-.146-4-4.098V46.5L17.288 24 54 24.95v17.92L36.847 68.65z" fill="#393939" opacity=".324"/><path d="M4 69h61c2.667 0 4.333-1 5-3v4H0v-4c.667 2 2 3 4 3z" fill="#000" fill-opacity=".383"/><use fill="#000" fill-rule="nonzero" opacity=".3" xlink:href="#d"/><use fill="#FFF" fill-rule="nonzero" xlink:href="#e"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 2 KiB |
BIN
odoo-bringout-oca-ocb-mail/mail/static/img/Capture.PNG
Normal file
BIN
odoo-bringout-oca-ocb-mail/mail/static/img/Capture.PNG
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
BIN
odoo-bringout-oca-ocb-mail/mail/static/img/red_envelope.png
Normal file
BIN
odoo-bringout-oca-ocb-mail/mail/static/img/red_envelope.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.9 KiB |
|
|
@ -0,0 +1,75 @@
|
|||
(function(){/*
|
||||
|
||||
Copyright The Closure Library Authors.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
'use strict';var D;function aa(a){var b=0;return function(){return b<a.length?{done:!1,value:a[b++]}:{done:!0}}}var ba="function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){if(a==Array.prototype||a==Object.prototype)return a;a[b]=c.value;return a};
|
||||
function ca(a){a=["object"==typeof globalThis&&globalThis,a,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global];for(var b=0;b<a.length;++b){var c=a[b];if(c&&c.Math==Math)return c}throw Error("Cannot find global object");}var H=ca(this);function J(a,b){if(b)a:{var c=H;a=a.split(".");for(var d=0;d<a.length-1;d++){var f=a[d];if(!(f in c))break a;c=c[f]}a=a[a.length-1];d=c[a];b=b(d);b!=d&&null!=b&&ba(c,a,{configurable:!0,writable:!0,value:b})}}
|
||||
J("Symbol",function(a){function b(h){if(this instanceof b)throw new TypeError("Symbol is not a constructor");return new c(d+(h||"")+"_"+f++,h)}function c(h,e){this.g=h;ba(this,"description",{configurable:!0,writable:!0,value:e})}if(a)return a;c.prototype.toString=function(){return this.g};var d="jscomp_symbol_"+(1E9*Math.random()>>>0)+"_",f=0;return b});
|
||||
J("Symbol.iterator",function(a){if(a)return a;a=Symbol("Symbol.iterator");for(var b="Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array".split(" "),c=0;c<b.length;c++){var d=H[b[c]];"function"===typeof d&&"function"!=typeof d.prototype[a]&&ba(d.prototype,a,{configurable:!0,writable:!0,value:function(){return da(aa(this))}})}return a});function da(a){a={next:a};a[Symbol.iterator]=function(){return this};return a}
|
||||
function M(a){var b="undefined"!=typeof Symbol&&Symbol.iterator&&a[Symbol.iterator];return b?b.call(a):{next:aa(a)}}function ea(a){if(!(a instanceof Array)){a=M(a);for(var b,c=[];!(b=a.next()).done;)c.push(b.value);a=c}return a}var fa="function"==typeof Object.create?Object.create:function(a){function b(){}b.prototype=a;return new b},ha;
|
||||
if("function"==typeof Object.setPrototypeOf)ha=Object.setPrototypeOf;else{var ia;a:{var ja={a:!0},ka={};try{ka.__proto__=ja;ia=ka.a;break a}catch(a){}ia=!1}ha=ia?function(a,b){a.__proto__=b;if(a.__proto__!==b)throw new TypeError(a+" is not extensible");return a}:null}var la=ha;
|
||||
function ma(a,b){a.prototype=fa(b.prototype);a.prototype.constructor=a;if(la)la(a,b);else for(var c in b)if("prototype"!=c)if(Object.defineProperties){var d=Object.getOwnPropertyDescriptor(b,c);d&&Object.defineProperty(a,c,d)}else a[c]=b[c];a.ea=b.prototype}function na(){this.l=!1;this.i=null;this.h=void 0;this.g=1;this.s=this.m=0;this.j=null}function oa(a){if(a.l)throw new TypeError("Generator is already running");a.l=!0}na.prototype.o=function(a){this.h=a};
|
||||
function pa(a,b){a.j={U:b,V:!0};a.g=a.m||a.s}na.prototype.return=function(a){this.j={return:a};this.g=this.s};function N(a,b,c){a.g=c;return{value:b}}function qa(a){this.g=new na;this.h=a}function ra(a,b){oa(a.g);var c=a.g.i;if(c)return sa(a,"return"in c?c["return"]:function(d){return{value:d,done:!0}},b,a.g.return);a.g.return(b);return ta(a)}
|
||||
function sa(a,b,c,d){try{var f=b.call(a.g.i,c);if(!(f instanceof Object))throw new TypeError("Iterator result "+f+" is not an object");if(!f.done)return a.g.l=!1,f;var h=f.value}catch(e){return a.g.i=null,pa(a.g,e),ta(a)}a.g.i=null;d.call(a.g,h);return ta(a)}function ta(a){for(;a.g.g;)try{var b=a.h(a.g);if(b)return a.g.l=!1,{value:b.value,done:!1}}catch(c){a.g.h=void 0,pa(a.g,c)}a.g.l=!1;if(a.g.j){b=a.g.j;a.g.j=null;if(b.V)throw b.U;return{value:b.return,done:!0}}return{value:void 0,done:!0}}
|
||||
function ua(a){this.next=function(b){oa(a.g);a.g.i?b=sa(a,a.g.i.next,b,a.g.o):(a.g.o(b),b=ta(a));return b};this.throw=function(b){oa(a.g);a.g.i?b=sa(a,a.g.i["throw"],b,a.g.o):(pa(a.g,b),b=ta(a));return b};this.return=function(b){return ra(a,b)};this[Symbol.iterator]=function(){return this}}function O(a,b){b=new ua(new qa(b));la&&a.prototype&&la(b,a.prototype);return b}
|
||||
function va(a,b){a instanceof String&&(a+="");var c=0,d=!1,f={next:function(){if(!d&&c<a.length){var h=c++;return{value:b(h,a[h]),done:!1}}d=!0;return{done:!0,value:void 0}}};f[Symbol.iterator]=function(){return f};return f}var wa="function"==typeof Object.assign?Object.assign:function(a,b){for(var c=1;c<arguments.length;c++){var d=arguments[c];if(d)for(var f in d)Object.prototype.hasOwnProperty.call(d,f)&&(a[f]=d[f])}return a};J("Object.assign",function(a){return a||wa});
|
||||
J("Promise",function(a){function b(e){this.h=0;this.i=void 0;this.g=[];this.o=!1;var g=this.j();try{e(g.resolve,g.reject)}catch(k){g.reject(k)}}function c(){this.g=null}function d(e){return e instanceof b?e:new b(function(g){g(e)})}if(a)return a;c.prototype.h=function(e){if(null==this.g){this.g=[];var g=this;this.i(function(){g.l()})}this.g.push(e)};var f=H.setTimeout;c.prototype.i=function(e){f(e,0)};c.prototype.l=function(){for(;this.g&&this.g.length;){var e=this.g;this.g=[];for(var g=0;g<e.length;++g){var k=
|
||||
e[g];e[g]=null;try{k()}catch(l){this.j(l)}}}this.g=null};c.prototype.j=function(e){this.i(function(){throw e;})};b.prototype.j=function(){function e(l){return function(q){k||(k=!0,l.call(g,q))}}var g=this,k=!1;return{resolve:e(this.C),reject:e(this.l)}};b.prototype.C=function(e){if(e===this)this.l(new TypeError("A Promise cannot resolve to itself"));else if(e instanceof b)this.F(e);else{a:switch(typeof e){case "object":var g=null!=e;break a;case "function":g=!0;break a;default:g=!1}g?this.u(e):this.m(e)}};
|
||||
b.prototype.u=function(e){var g=void 0;try{g=e.then}catch(k){this.l(k);return}"function"==typeof g?this.G(g,e):this.m(e)};b.prototype.l=function(e){this.s(2,e)};b.prototype.m=function(e){this.s(1,e)};b.prototype.s=function(e,g){if(0!=this.h)throw Error("Cannot settle("+e+", "+g+"): Promise already settled in state"+this.h);this.h=e;this.i=g;2===this.h&&this.D();this.A()};b.prototype.D=function(){var e=this;f(function(){if(e.B()){var g=H.console;"undefined"!==typeof g&&g.error(e.i)}},1)};b.prototype.B=
|
||||
function(){if(this.o)return!1;var e=H.CustomEvent,g=H.Event,k=H.dispatchEvent;if("undefined"===typeof k)return!0;"function"===typeof e?e=new e("unhandledrejection",{cancelable:!0}):"function"===typeof g?e=new g("unhandledrejection",{cancelable:!0}):(e=H.document.createEvent("CustomEvent"),e.initCustomEvent("unhandledrejection",!1,!0,e));e.promise=this;e.reason=this.i;return k(e)};b.prototype.A=function(){if(null!=this.g){for(var e=0;e<this.g.length;++e)h.h(this.g[e]);this.g=null}};var h=new c;b.prototype.F=
|
||||
function(e){var g=this.j();e.J(g.resolve,g.reject)};b.prototype.G=function(e,g){var k=this.j();try{e.call(g,k.resolve,k.reject)}catch(l){k.reject(l)}};b.prototype.then=function(e,g){function k(w,t){return"function"==typeof w?function(y){try{l(w(y))}catch(m){q(m)}}:t}var l,q,v=new b(function(w,t){l=w;q=t});this.J(k(e,l),k(g,q));return v};b.prototype.catch=function(e){return this.then(void 0,e)};b.prototype.J=function(e,g){function k(){switch(l.h){case 1:e(l.i);break;case 2:g(l.i);break;default:throw Error("Unexpected state: "+
|
||||
l.h);}}var l=this;null==this.g?h.h(k):this.g.push(k);this.o=!0};b.resolve=d;b.reject=function(e){return new b(function(g,k){k(e)})};b.race=function(e){return new b(function(g,k){for(var l=M(e),q=l.next();!q.done;q=l.next())d(q.value).J(g,k)})};b.all=function(e){var g=M(e),k=g.next();return k.done?d([]):new b(function(l,q){function v(y){return function(m){w[y]=m;t--;0==t&&l(w)}}var w=[],t=0;do w.push(void 0),t++,d(k.value).J(v(w.length-1),q),k=g.next();while(!k.done)})};return b});
|
||||
J("Object.is",function(a){return a?a:function(b,c){return b===c?0!==b||1/b===1/c:b!==b&&c!==c}});J("Array.prototype.includes",function(a){return a?a:function(b,c){var d=this;d instanceof String&&(d=String(d));var f=d.length;c=c||0;for(0>c&&(c=Math.max(c+f,0));c<f;c++){var h=d[c];if(h===b||Object.is(h,b))return!0}return!1}});
|
||||
J("String.prototype.includes",function(a){return a?a:function(b,c){if(null==this)throw new TypeError("The 'this' value for String.prototype.includes must not be null or undefined");if(b instanceof RegExp)throw new TypeError("First argument to String.prototype.includes must not be a regular expression");return-1!==this.indexOf(b,c||0)}});J("Array.prototype.keys",function(a){return a?a:function(){return va(this,function(b){return b})}});var xa=this||self;
|
||||
function ya(a,b){a=a.split(".");var c=xa;a[0]in c||"undefined"==typeof c.execScript||c.execScript("var "+a[0]);for(var d;a.length&&(d=a.shift());)a.length||void 0===b?c[d]&&c[d]!==Object.prototype[d]?c=c[d]:c=c[d]={}:c[d]=b};function za(a,b){b=String.fromCharCode.apply(null,b);return null==a?b:a+b}var Aa,Ba="undefined"!==typeof TextDecoder,Ca,Da="undefined"!==typeof TextEncoder;
|
||||
function Ea(a){if(Da)a=(Ca||(Ca=new TextEncoder)).encode(a);else{var b=void 0;b=void 0===b?!1:b;for(var c=0,d=new Uint8Array(3*a.length),f=0;f<a.length;f++){var h=a.charCodeAt(f);if(128>h)d[c++]=h;else{if(2048>h)d[c++]=h>>6|192;else{if(55296<=h&&57343>=h){if(56319>=h&&f<a.length){var e=a.charCodeAt(++f);if(56320<=e&&57343>=e){h=1024*(h-55296)+e-56320+65536;d[c++]=h>>18|240;d[c++]=h>>12&63|128;d[c++]=h>>6&63|128;d[c++]=h&63|128;continue}else f--}if(b)throw Error("Found an unpaired surrogate");h=65533}d[c++]=
|
||||
h>>12|224;d[c++]=h>>6&63|128}d[c++]=h&63|128}}a=d.subarray(0,c)}return a};var Fa={},Ga=null;function Ha(a,b){void 0===b&&(b=0);Ja();b=Fa[b];for(var c=Array(Math.floor(a.length/3)),d=b[64]||"",f=0,h=0;f<a.length-2;f+=3){var e=a[f],g=a[f+1],k=a[f+2],l=b[e>>2];e=b[(e&3)<<4|g>>4];g=b[(g&15)<<2|k>>6];k=b[k&63];c[h++]=l+e+g+k}l=0;k=d;switch(a.length-f){case 2:l=a[f+1],k=b[(l&15)<<2]||d;case 1:a=a[f],c[h]=b[a>>2]+b[(a&3)<<4|l>>4]+k+d}return c.join("")}
|
||||
function Ka(a){var b=a.length,c=3*b/4;c%3?c=Math.floor(c):-1!="=.".indexOf(a[b-1])&&(c=-1!="=.".indexOf(a[b-2])?c-2:c-1);var d=new Uint8Array(c),f=0;La(a,function(h){d[f++]=h});return d.subarray(0,f)}
|
||||
function La(a,b){function c(k){for(;d<a.length;){var l=a.charAt(d++),q=Ga[l];if(null!=q)return q;if(!/^[\s\xa0]*$/.test(l))throw Error("Unknown base64 encoding at char: "+l);}return k}Ja();for(var d=0;;){var f=c(-1),h=c(0),e=c(64),g=c(64);if(64===g&&-1===f)break;b(f<<2|h>>4);64!=e&&(b(h<<4&240|e>>2),64!=g&&b(e<<6&192|g))}}
|
||||
function Ja(){if(!Ga){Ga={};for(var a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split(""),b=["+/=","+/","-_=","-_.","-_"],c=0;5>c;c++){var d=a.concat(b[c].split(""));Fa[c]=d;for(var f=0;f<d.length;f++){var h=d[f];void 0===Ga[h]&&(Ga[h]=f)}}}};var Ma="function"===typeof Uint8Array.prototype.slice,Na;function Oa(a,b,c){return b===c?Na||(Na=new Uint8Array(0)):Ma?a.slice(b,c):new Uint8Array(a.subarray(b,c))}var P=0,Q=0;function Pa(a,b){b=void 0===b?{}:b;b=void 0===b.v?!1:b.v;this.h=null;this.g=this.i=this.j=0;this.l=!1;this.v=b;a&&Qa(this,a)}function Qa(a,b){b=b.constructor===Uint8Array?b:b.constructor===ArrayBuffer?new Uint8Array(b):b.constructor===Array?new Uint8Array(b):b.constructor===String?Ka(b):b instanceof Uint8Array?new Uint8Array(b.buffer,b.byteOffset,b.byteLength):new Uint8Array(0);a.h=b;a.j=0;a.i=a.h.length;a.g=a.j}Pa.prototype.reset=function(){this.g=this.j};
|
||||
function Ra(a){var b=a.h,c=b[a.g],d=c&127;if(128>c)return a.g+=1,d;c=b[a.g+1];d|=(c&127)<<7;if(128>c)return a.g+=2,d;c=b[a.g+2];d|=(c&127)<<14;if(128>c)return a.g+=3,d;c=b[a.g+3];d|=(c&127)<<21;if(128>c)return a.g+=4,d;c=b[a.g+4];d|=(c&15)<<28;if(128>c)return a.g+=5,d>>>0;a.g+=5;128<=b[a.g++]&&128<=b[a.g++]&&128<=b[a.g++]&&128<=b[a.g++]&&a.g++;return d}
|
||||
function R(a){var b=a.h[a.g];var c=a.h[a.g+1];var d=a.h[a.g+2],f=a.h[a.g+3];a.g+=4;c=(b<<0|c<<8|d<<16|f<<24)>>>0;a=2*(c>>31)+1;b=c>>>23&255;c&=8388607;return 255==b?c?NaN:Infinity*a:0==b?a*Math.pow(2,-149)*c:a*Math.pow(2,b-150)*(c+Math.pow(2,23))}var Sa=[];function Ta(){this.g=new Uint8Array(64);this.h=0}Ta.prototype.push=function(a){if(!(this.h+1<this.g.length)){var b=this.g;this.g=new Uint8Array(Math.ceil(1+2*this.g.length));this.g.set(b)}this.g[this.h++]=a};Ta.prototype.length=function(){return this.h};Ta.prototype.end=function(){var a=this.g,b=this.h;this.h=0;return Oa(a,0,b)};function S(a,b){for(;127<b;)a.push(b&127|128),b>>>=7;a.push(b)};function Ua(a){var b={},c=void 0===b.N?!1:b.N;this.o={v:void 0===b.v?!1:b.v};this.N=c;b=this.o;Sa.length?(c=Sa.pop(),b&&(c.v=b.v),a&&Qa(c,a),a=c):a=new Pa(a,b);this.g=a;this.m=this.g.g;this.h=this.i=this.l=-1;this.j=!1}Ua.prototype.reset=function(){this.g.reset();this.h=this.l=-1};function Va(a){var b=a.g;(b=b.g==b.i)||(b=a.j)||(b=a.g,b=b.l||0>b.g||b.g>b.i);if(b)return!1;a.m=a.g.g;b=Ra(a.g);var c=b&7;if(0!=c&&5!=c&&1!=c&&2!=c&&3!=c&&4!=c)return a.j=!0,!1;a.i=b;a.l=b>>>3;a.h=c;return!0}
|
||||
function Wa(a){switch(a.h){case 0:if(0!=a.h)Wa(a);else{for(a=a.g;a.h[a.g]&128;)a.g++;a.g++}break;case 1:1!=a.h?Wa(a):(a=a.g,a.g+=8);break;case 2:if(2!=a.h)Wa(a);else{var b=Ra(a.g);a=a.g;a.g+=b}break;case 5:5!=a.h?Wa(a):(a=a.g,a.g+=4);break;case 3:b=a.l;do{if(!Va(a)){a.j=!0;break}if(4==a.h){a.l!=b&&(a.j=!0);break}Wa(a)}while(1);break;default:a.j=!0}}function Xa(a,b,c){var d=a.g.i,f=Ra(a.g);f=a.g.g+f;a.g.i=f;c(b,a);a.g.g=f;a.g.i=d;return b}
|
||||
function Ya(a){var b=Ra(a.g);a=a.g;var c=a.g;a.g+=b;a=a.h;var d;if(Ba)(d=Aa)||(d=Aa=new TextDecoder("utf-8",{fatal:!1})),d=d.decode(a.subarray(c,c+b));else{b=c+b;for(var f=[],h=null,e,g,k;c<b;)e=a[c++],128>e?f.push(e):224>e?c>=b?f.push(65533):(g=a[c++],194>e||128!==(g&192)?(c--,f.push(65533)):f.push((e&31)<<6|g&63)):240>e?c>=b-1?f.push(65533):(g=a[c++],128!==(g&192)||224===e&&160>g||237===e&&160<=g||128!==((d=a[c++])&192)?(c--,f.push(65533)):f.push((e&15)<<12|(g&63)<<6|d&63)):244>=e?c>=b-2?f.push(65533):
|
||||
(g=a[c++],128!==(g&192)||0!==(e<<28)+(g-144)>>30||128!==((d=a[c++])&192)||128!==((k=a[c++])&192)?(c--,f.push(65533)):(e=(e&7)<<18|(g&63)<<12|(d&63)<<6|k&63,e-=65536,f.push((e>>10&1023)+55296,(e&1023)+56320))):f.push(65533),8192<=f.length&&(h=za(h,f),f.length=0);d=za(h,f)}return d};function Za(){this.h=[];this.i=0;this.g=new Ta}function $a(a,b){0!==b.length&&(a.h.push(b),a.i+=b.length)}function ab(a){var b=a.i+a.g.length();if(0===b)return new Uint8Array(0);b=new Uint8Array(b);for(var c=a.h,d=c.length,f=0,h=0;h<d;h++){var e=c[h];0!==e.length&&(b.set(e,f),f+=e.length)}c=a.g;d=c.h;0!==d&&(b.set(c.g.subarray(0,d),f),c.h=0);a.h=[b];return b}
|
||||
function T(a,b,c){if(null!=c){S(a.g,8*b+5);a=a.g;var d=c;d=(c=0>d?1:0)?-d:d;0===d?0<1/d?P=Q=0:(Q=0,P=2147483648):isNaN(d)?(Q=0,P=2147483647):3.4028234663852886E38<d?(Q=0,P=(c<<31|2139095040)>>>0):1.1754943508222875E-38>d?(d=Math.round(d/Math.pow(2,-149)),Q=0,P=(c<<31|d)>>>0):(b=Math.floor(Math.log(d)/Math.LN2),d*=Math.pow(2,-b),d=Math.round(8388608*d)&8388607,Q=0,P=(c<<31|b+127<<23|d)>>>0);c=P;a.push(c>>>0&255);a.push(c>>>8&255);a.push(c>>>16&255);a.push(c>>>24&255)}};var bb="function"===typeof Uint8Array;function cb(a,b,c){if(null!=a)return"object"===typeof a?bb&&a instanceof Uint8Array?c(a):db(a,b,c):b(a)}function db(a,b,c){if(Array.isArray(a)){for(var d=Array(a.length),f=0;f<a.length;f++)d[f]=cb(a[f],b,c);Array.isArray(a)&&a.W&&eb(d);return d}d={};for(f in a)d[f]=cb(a[f],b,c);return d}function fb(a){return"number"===typeof a?isFinite(a)?a:String(a):a}var gb={W:{value:!0,configurable:!0}};
|
||||
function eb(a){Array.isArray(a)&&!Object.isFrozen(a)&&Object.defineProperties(a,gb);return a};var hb;function U(a,b,c){var d=hb;hb=null;a||(a=d);d=this.constructor.ca;a||(a=d?[d]:[]);this.j=d?0:-1;this.i=null;this.g=a;a:{d=this.g.length;a=d-1;if(d&&(d=this.g[a],null!==d&&"object"===typeof d&&d.constructor===Object)){this.l=a-this.j;this.h=d;break a}void 0!==b&&-1<b?(this.l=Math.max(b,a+1-this.j),this.h=null):this.l=Number.MAX_VALUE}if(c)for(b=0;b<c.length;b++)a=c[b],a<this.l?(a+=this.j,(d=this.g[a])?eb(d):this.g[a]=ib):(jb(this),(d=this.h[a])?eb(d):this.h[a]=ib)}var ib=Object.freeze(eb([]));
|
||||
function jb(a){var b=a.l+a.j;a.g[b]||(a.h=a.g[b]={})}function V(a,b,c){return-1===b?null:(void 0===c?0:c)||b>=a.l?a.h?a.h[b]:void 0:a.g[b+a.j]}function kb(a){var b=void 0===b?!1:b;var c=V(a,1,b);null==c&&(c=ib);c===ib&&(c=eb([]),W(a,1,c,b));return c}function X(a,b,c){a=V(a,b);a=null==a?a:+a;return null==a?void 0===c?0:c:a}function W(a,b,c,d){(void 0===d?0:d)||b>=a.l?(jb(a),a.h[b]=c):a.g[b+a.j]=c}
|
||||
function lb(a,b){a.i||(a.i={});var c=a.i[1];if(!c){var d=kb(a);c=[];for(var f=0;f<d.length;f++)c[f]=new b(d[f]);a.i[1]=c}return c}function mb(a,b,c,d){var f=lb(a,c);b=b?b:new c;a=kb(a);void 0!=d?(f.splice(d,0,b),a.splice(d,0,nb(b,!1))):(f.push(b),a.push(nb(b,!1)))}U.prototype.toJSON=function(){var a=nb(this,!1);return db(a,fb,Ha)};function nb(a,b){if(a.i)for(var c in a.i){var d=a.i[c];if(Array.isArray(d))for(var f=0;f<d.length;f++)d[f]&&nb(d[f],b);else d&&nb(d,b)}return a.g}
|
||||
U.prototype.toString=function(){return nb(this,!1).toString()};function ob(a,b){a=V(a,b);return null==a?0:a}function pb(a,b){a=V(a,b);return null==a?"":a};function qb(a,b){if(a=a.m){$a(b,b.g.end());for(var c=0;c<a.length;c++)$a(b,a[c])}}function rb(a,b){if(4==b.h)return!1;var c=b.m;Wa(b);b.N||(b=Oa(b.g.h,c,b.g.g),(c=a.m)?c.push(b):a.m=[b]);return!0};function Y(a,b){var c=void 0;return new (c||(c=Promise))(function(d,f){function h(k){try{g(b.next(k))}catch(l){f(l)}}function e(k){try{g(b["throw"](k))}catch(l){f(l)}}function g(k){k.done?d(k.value):(new c(function(l){l(k.value)})).then(h,e)}g((b=b.apply(a,void 0)).next())})};function sb(a){U.call(this,a)}ma(sb,U);function tb(a,b){for(;Va(b);)switch(b.i){case 8:var c=Ra(b.g);W(a,1,c);break;case 21:c=R(b.g);W(a,2,c);break;case 26:c=Ya(b);W(a,3,c);break;case 34:c=Ya(b);W(a,4,c);break;default:if(!rb(a,b))return a}return a};function ub(a){U.call(this,a,-1,vb)}ma(ub,U);ub.prototype.addClassification=function(a,b){mb(this,a,sb,b)};var vb=[1];function wb(a){U.call(this,a)}ma(wb,U);function xb(a,b){for(;Va(b);)switch(b.i){case 13:var c=R(b.g);W(a,1,c);break;case 21:c=R(b.g);W(a,2,c);break;case 29:c=R(b.g);W(a,3,c);break;case 37:c=R(b.g);W(a,4,c);break;case 45:c=R(b.g);W(a,5,c);break;default:if(!rb(a,b))return a}return a};function yb(a){U.call(this,a,-1,zb)}ma(yb,U);var zb=[1];function Ab(a){U.call(this,a)}ma(Ab,U);function Bb(a,b,c){c=a.createShader(0===c?a.VERTEX_SHADER:a.FRAGMENT_SHADER);a.shaderSource(c,b);a.compileShader(c);if(!a.getShaderParameter(c,a.COMPILE_STATUS))throw Error("Could not compile WebGL shader.\n\n"+a.getShaderInfoLog(c));return c};function Cb(a){return lb(a,sb).map(function(b){return{index:ob(b,1),Y:X(b,2),label:null!=V(b,3)?pb(b,3):void 0,displayName:null!=V(b,4)?pb(b,4):void 0}})};function Db(a){return{x:X(a,1),y:X(a,2),z:X(a,3),visibility:null!=V(a,4)?X(a,4):void 0}};function Eb(a,b){this.h=a;this.g=b;this.l=0}
|
||||
function Fb(a,b,c){Gb(a,b);if("function"===typeof a.g.canvas.transferToImageBitmap)return Promise.resolve(a.g.canvas.transferToImageBitmap());if(c)return Promise.resolve(a.g.canvas);if("function"===typeof createImageBitmap)return createImageBitmap(a.g.canvas);void 0===a.i&&(a.i=document.createElement("canvas"));return new Promise(function(d){a.i.height=a.g.canvas.height;a.i.width=a.g.canvas.width;a.i.getContext("2d",{}).drawImage(a.g.canvas,0,0,a.g.canvas.width,a.g.canvas.height);d(a.i)})}
|
||||
function Gb(a,b){var c=a.g;if(void 0===a.m){var d=Bb(c,"\n attribute vec2 aVertex;\n attribute vec2 aTex;\n varying vec2 vTex;\n void main(void) {\n gl_Position = vec4(aVertex, 0.0, 1.0);\n vTex = aTex;\n }",0),f=Bb(c,"\n precision mediump float;\n varying vec2 vTex;\n uniform sampler2D sampler0;\n void main(){\n gl_FragColor = texture2D(sampler0, vTex);\n }",1),h=c.createProgram();c.attachShader(h,d);c.attachShader(h,f);c.linkProgram(h);if(!c.getProgramParameter(h,c.LINK_STATUS))throw Error("Could not compile WebGL program.\n\n"+
|
||||
c.getProgramInfoLog(h));d=a.m=h;c.useProgram(d);f=c.getUniformLocation(d,"sampler0");a.j={I:c.getAttribLocation(d,"aVertex"),H:c.getAttribLocation(d,"aTex"),da:f};a.s=c.createBuffer();c.bindBuffer(c.ARRAY_BUFFER,a.s);c.enableVertexAttribArray(a.j.I);c.vertexAttribPointer(a.j.I,2,c.FLOAT,!1,0,0);c.bufferData(c.ARRAY_BUFFER,new Float32Array([-1,-1,-1,1,1,1,1,-1]),c.STATIC_DRAW);c.bindBuffer(c.ARRAY_BUFFER,null);a.o=c.createBuffer();c.bindBuffer(c.ARRAY_BUFFER,a.o);c.enableVertexAttribArray(a.j.H);c.vertexAttribPointer(a.j.H,
|
||||
2,c.FLOAT,!1,0,0);c.bufferData(c.ARRAY_BUFFER,new Float32Array([0,1,0,0,1,0,1,1]),c.STATIC_DRAW);c.bindBuffer(c.ARRAY_BUFFER,null);c.uniform1i(f,0)}d=a.j;c.useProgram(a.m);c.canvas.width=b.width;c.canvas.height=b.height;c.viewport(0,0,b.width,b.height);c.activeTexture(c.TEXTURE0);a.h.bindTexture2d(b.glName);c.enableVertexAttribArray(d.I);c.bindBuffer(c.ARRAY_BUFFER,a.s);c.vertexAttribPointer(d.I,2,c.FLOAT,!1,0,0);c.enableVertexAttribArray(d.H);c.bindBuffer(c.ARRAY_BUFFER,a.o);c.vertexAttribPointer(d.H,
|
||||
2,c.FLOAT,!1,0,0);c.bindFramebuffer(c.DRAW_FRAMEBUFFER?c.DRAW_FRAMEBUFFER:c.FRAMEBUFFER,null);c.clearColor(0,0,0,0);c.clear(c.COLOR_BUFFER_BIT);c.colorMask(!0,!0,!0,!0);c.drawArrays(c.TRIANGLE_FAN,0,4);c.disableVertexAttribArray(d.I);c.disableVertexAttribArray(d.H);c.bindBuffer(c.ARRAY_BUFFER,null);a.h.bindTexture2d(0)}function Hb(a){this.g=a};var Ib=new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,10,9,1,7,0,65,0,253,15,26,11]);function Jb(a,b){return b+a}function Kb(a,b){window[a]=b}function Lb(a){var b=document.createElement("script");b.setAttribute("src",a);b.setAttribute("crossorigin","anonymous");return new Promise(function(c){b.addEventListener("load",function(){c()},!1);b.addEventListener("error",function(){c()},!1);document.body.appendChild(b)})}
|
||||
function Mb(){return Y(this,function b(){return O(b,function(c){switch(c.g){case 1:return c.m=2,N(c,WebAssembly.instantiate(Ib),4);case 4:c.g=3;c.m=0;break;case 2:return c.m=0,c.j=null,c.return(!1);case 3:return c.return(!0)}})})}
|
||||
function Nb(a){this.g=a;this.listeners={};this.j={};this.F={};this.m={};this.s={};this.G=this.o=this.R=!0;this.C=Promise.resolve();this.P="";this.B={};this.locateFile=a&&a.locateFile||Jb;if("object"===typeof window)var b=window.location.pathname.toString().substring(0,window.location.pathname.toString().lastIndexOf("/"))+"/";else if("undefined"!==typeof location)b=location.pathname.toString().substring(0,location.pathname.toString().lastIndexOf("/"))+"/";else throw Error("solutions can only be loaded on a web page or in a web worker");
|
||||
this.S=b;if(a.options){b=M(Object.keys(a.options));for(var c=b.next();!c.done;c=b.next()){c=c.value;var d=a.options[c].default;void 0!==d&&(this.j[c]="function"===typeof d?d():d)}}}D=Nb.prototype;D.close=function(){this.i&&this.i.delete();return Promise.resolve()};function Ob(a,b){return void 0===a.g.files?[]:"function"===typeof a.g.files?a.g.files(b):a.g.files}
|
||||
function Pb(a){return Y(a,function c(){var d=this,f,h,e,g,k,l,q,v,w,t,y;return O(c,function(m){switch(m.g){case 1:f=d;if(!d.R)return m.return();h=Ob(d,d.j);return N(m,Mb(),2);case 2:e=m.h;if("object"===typeof window)return Kb("createMediapipeSolutionsWasm",{locateFile:d.locateFile}),Kb("createMediapipeSolutionsPackedAssets",{locateFile:d.locateFile}),l=h.filter(function(u){return void 0!==u.data}),q=h.filter(function(u){return void 0===u.data}),v=Promise.all(l.map(function(u){var x=Qb(f,u.url);if(void 0!==
|
||||
u.path){var z=u.path;x=x.then(function(E){f.overrideFile(z,E);return Promise.resolve(E)})}return x})),w=Promise.all(q.map(function(u){return void 0===u.simd||u.simd&&e||!u.simd&&!e?Lb(f.locateFile(u.url,f.S)):Promise.resolve()})).then(function(){return Y(f,function x(){var z,E,F=this;return O(x,function(K){if(1==K.g)return z=window.createMediapipeSolutionsWasm,E=window.createMediapipeSolutionsPackedAssets,N(K,z(E),2);F.h=K.h;K.g=0})})}),t=function(){return Y(f,function x(){var z=this;return O(x,function(E){z.g.graph&&
|
||||
z.g.graph.url?E=N(E,Qb(z,z.g.graph.url),0):(E.g=0,E=void 0);return E})})}(),N(m,Promise.all([w,v,t]),7);if("function"!==typeof importScripts)throw Error("solutions can only be loaded on a web page or in a web worker");g=h.filter(function(u){return void 0===u.simd||u.simd&&e||!u.simd&&!e}).map(function(u){return f.locateFile(u.url,f.S)});importScripts.apply(null,ea(g));return N(m,createMediapipeSolutionsWasm(Module),6);case 6:d.h=m.h;d.l=new OffscreenCanvas(1,1);d.h.canvas=d.l;k=d.h.GL.createContext(d.l,
|
||||
{antialias:!1,alpha:!1,ba:"undefined"!==typeof WebGL2RenderingContext?2:1});d.h.GL.makeContextCurrent(k);m.g=4;break;case 7:d.l=document.createElement("canvas");y=d.l.getContext("webgl2",{});if(!y&&(y=d.l.getContext("webgl",{}),!y))return alert("Failed to create WebGL canvas context when passing video frame."),m.return();d.D=y;d.h.canvas=d.l;d.h.createContext(d.l,!0,!0,{});case 4:d.i=new d.h.SolutionWasm,d.R=!1,m.g=0}})})}
|
||||
function Rb(a){return Y(a,function c(){var d=this,f,h,e,g,k,l,q,v;return O(c,function(w){if(1==w.g){if(d.g.graph&&d.g.graph.url&&d.P===d.g.graph.url)return w.return();d.o=!0;if(!d.g.graph||!d.g.graph.url){w.g=2;return}d.P=d.g.graph.url;return N(w,Qb(d,d.g.graph.url),3)}2!=w.g&&(f=w.h,d.i.loadGraph(f));h=M(Object.keys(d.B));for(e=h.next();!e.done;e=h.next())g=e.value,d.i.overrideFile(g,d.B[g]);d.B={};if(d.g.listeners)for(k=M(d.g.listeners),l=k.next();!l.done;l=k.next())q=l.value,Sb(d,q);v=d.j;d.j=
|
||||
{};d.setOptions(v);w.g=0})})}D.reset=function(){return Y(this,function b(){var c=this;return O(b,function(d){c.i&&(c.i.reset(),c.m={},c.s={});d.g=0})})};
|
||||
D.setOptions=function(a,b){var c=this;if(b=b||this.g.options){for(var d=[],f=[],h={},e=M(Object.keys(a)),g=e.next();!g.done;h={K:h.K,L:h.L},g=e.next()){var k=g.value;k in this.j&&this.j[k]===a[k]||(this.j[k]=a[k],g=b[k],void 0!==g&&(g.onChange&&(h.K=g.onChange,h.L=a[k],d.push(function(l){return function(){return Y(c,function v(){var w,t=this;return O(v,function(y){if(1==y.g)return N(y,l.K(l.L),2);w=y.h;!0===w&&(t.o=!0);y.g=0})})}}(h))),g.graphOptionXref&&(k={valueNumber:1===g.type?a[k]:0,valueBoolean:0===
|
||||
g.type?a[k]:!1,valueString:2===g.type?a[k]:""},g=Object.assign(Object.assign(Object.assign({},{calculatorName:"",calculatorIndex:0}),g.graphOptionXref),k),f.push(g))))}if(0!==d.length||0!==f.length)this.o=!0,this.A=(void 0===this.A?[]:this.A).concat(f),this.u=(void 0===this.u?[]:this.u).concat(d)}};
|
||||
function Tb(a){return Y(a,function c(){var d=this,f,h,e,g,k,l,q;return O(c,function(v){switch(v.g){case 1:if(!d.o)return v.return();if(!d.u){v.g=2;break}f=M(d.u);h=f.next();case 3:if(h.done){v.g=5;break}e=h.value;return N(v,e(),4);case 4:h=f.next();v.g=3;break;case 5:d.u=void 0;case 2:if(d.A){g=new d.h.GraphOptionChangeRequestList;k=M(d.A);for(l=k.next();!l.done;l=k.next())q=l.value,g.push_back(q);d.i.changeOptions(g);g.delete();d.A=void 0}d.o=!1;v.g=0}})})}
|
||||
D.initialize=function(){return Y(this,function b(){var c=this;return O(b,function(d){return 1==d.g?N(d,Pb(c),2):3!=d.g?N(d,Rb(c),3):N(d,Tb(c),0)})})};function Qb(a,b){return Y(a,function d(){var f=this,h,e;return O(d,function(g){if(b in f.F)return g.return(f.F[b]);h=f.locateFile(b,"");e=fetch(h).then(function(k){return k.arrayBuffer()});f.F[b]=e;return g.return(e)})})}D.overrideFile=function(a,b){this.i?this.i.overrideFile(a,b):this.B[a]=b};D.clearOverriddenFiles=function(){this.B={};this.i&&this.i.clearOverriddenFiles()};
|
||||
D.send=function(a,b){return Y(this,function d(){var f=this,h,e,g,k,l,q,v,w,t;return O(d,function(y){switch(y.g){case 1:if(!f.g.inputs)return y.return();h=1E3*(void 0===b||null===b?performance.now():b);return N(y,f.C,2);case 2:return N(y,f.initialize(),3);case 3:e=new f.h.PacketDataList;g=M(Object.keys(a));for(k=g.next();!k.done;k=g.next())if(l=k.value,q=f.g.inputs[l]){a:{var m=f;var u=a[l];switch(q.type){case "video":var x=m.m[q.stream];x||(x=new Eb(m.h,m.D),m.m[q.stream]=x);m=x;0===m.l&&(m.l=m.h.createTexture());
|
||||
if("undefined"!==typeof HTMLVideoElement&&u instanceof HTMLVideoElement){var z=u.videoWidth;x=u.videoHeight}else"undefined"!==typeof HTMLImageElement&&u instanceof HTMLImageElement?(z=u.naturalWidth,x=u.naturalHeight):(z=u.width,x=u.height);x={glName:m.l,width:z,height:x};z=m.g;z.canvas.width=x.width;z.canvas.height=x.height;z.activeTexture(z.TEXTURE0);m.h.bindTexture2d(m.l);z.texImage2D(z.TEXTURE_2D,0,z.RGBA,z.RGBA,z.UNSIGNED_BYTE,u);m.h.bindTexture2d(0);m=x;break a;case "detections":x=m.m[q.stream];
|
||||
x||(x=new Hb(m.h),m.m[q.stream]=x);m=x;m.data||(m.data=new m.g.DetectionListData);m.data.reset(u.length);for(x=0;x<u.length;++x){z=u[x];var E=m.data,F=E.setBoundingBox,K=x;var I=z.T;var r=new Ab;W(r,1,I.Z);W(r,2,I.$);W(r,3,I.height);W(r,4,I.width);W(r,5,I.rotation);W(r,6,I.X);var A=I=new Za;T(A,1,V(r,1));T(A,2,V(r,2));T(A,3,V(r,3));T(A,4,V(r,4));T(A,5,V(r,5));var C=V(r,6);if(null!=C&&null!=C){S(A.g,48);var p=A.g,B=C;C=0>B;B=Math.abs(B);var n=B>>>0;B=Math.floor((B-n)/4294967296);B>>>=0;C&&(B=~B>>>
|
||||
0,n=(~n>>>0)+1,4294967295<n&&(n=0,B++,4294967295<B&&(B=0)));P=n;Q=B;C=P;for(n=Q;0<n||127<C;)p.push(C&127|128),C=(C>>>7|n<<25)>>>0,n>>>=7;p.push(C)}qb(r,A);I=ab(I);F.call(E,K,I);if(z.O)for(E=0;E<z.O.length;++E)r=z.O[E],A=r.visibility?!0:!1,F=m.data,K=F.addNormalizedLandmark,I=x,r=Object.assign(Object.assign({},r),{visibility:A?r.visibility:0}),A=new wb,W(A,1,r.x),W(A,2,r.y),W(A,3,r.z),r.visibility&&W(A,4,r.visibility),p=r=new Za,T(p,1,V(A,1)),T(p,2,V(A,2)),T(p,3,V(A,3)),T(p,4,V(A,4)),T(p,5,V(A,5)),
|
||||
qb(A,p),r=ab(r),K.call(F,I,r);if(z.M)for(E=0;E<z.M.length;++E){F=m.data;K=F.addClassification;I=x;r=z.M[E];A=new sb;W(A,2,r.Y);r.index&&W(A,1,r.index);r.label&&W(A,3,r.label);r.displayName&&W(A,4,r.displayName);p=r=new Za;n=V(A,1);if(null!=n&&null!=n)if(S(p.g,8),C=p.g,0<=n)S(C,n);else{for(B=0;9>B;B++)C.push(n&127|128),n>>=7;C.push(1)}T(p,2,V(A,2));C=V(A,3);null!=C&&(C=Ea(C),S(p.g,26),S(p.g,C.length),$a(p,p.g.end()),$a(p,C));C=V(A,4);null!=C&&(C=Ea(C),S(p.g,34),S(p.g,C.length),$a(p,p.g.end()),$a(p,
|
||||
C));qb(A,p);r=ab(r);K.call(F,I,r)}}m=m.data;break a;default:m={}}}v=m;w=q.stream;switch(q.type){case "video":e.pushTexture2d(Object.assign(Object.assign({},v),{stream:w,timestamp:h}));break;case "detections":t=v;t.stream=w;t.timestamp=h;e.pushDetectionList(t);break;default:throw Error("Unknown input config type: '"+q.type+"'");}}f.i.send(e);return N(y,f.C,4);case 4:e.delete(),y.g=0}})})};
|
||||
function Ub(a,b,c){return Y(a,function f(){var h,e,g,k,l,q,v=this,w,t,y,m,u,x,z,E;return O(f,function(F){switch(F.g){case 1:if(!c)return F.return(b);h={};e=0;g=M(Object.keys(c));for(k=g.next();!k.done;k=g.next())l=k.value,q=c[l],"string"!==typeof q&&"texture"===q.type&&void 0!==b[q.stream]&&++e;1<e&&(v.G=!1);w=M(Object.keys(c));k=w.next();case 2:if(k.done){F.g=4;break}t=k.value;y=c[t];if("string"===typeof y)return z=h,E=t,N(F,Vb(v,t,b[y]),14);m=b[y.stream];if("detection_list"===y.type){if(m){var K=
|
||||
m.getRectList();for(var I=m.getLandmarksList(),r=m.getClassificationsList(),A=[],C=0;C<K.size();++C){var p=K.get(C);a:{var B=new Ab;for(p=new Ua(p);Va(p);)switch(p.i){case 13:var n=R(p.g);W(B,1,n);break;case 21:n=R(p.g);W(B,2,n);break;case 29:n=R(p.g);W(B,3,n);break;case 37:n=R(p.g);W(B,4,n);break;case 45:n=R(p.g);W(B,5,n);break;case 48:for(var G=p.g,L=128,Ia=0,Z=n=0;4>Z&&128<=L;Z++)L=G.h[G.g++],Ia|=(L&127)<<7*Z;128<=L&&(L=G.h[G.g++],Ia|=(L&127)<<28,n|=(L&127)>>4);if(128<=L)for(Z=0;5>Z&&128<=L;Z++)L=
|
||||
G.h[G.g++],n|=(L&127)<<7*Z+3;if(128>L){G=Ia>>>0;L=n>>>0;if(n=L&2147483648)G=~G+1>>>0,L=~L>>>0,0==G&&(L=L+1>>>0);G=4294967296*L+(G>>>0);n=n?-G:G}else G.l=!0,n=void 0;W(B,6,n);break;default:if(!rb(B,p))break a}}B={Z:X(B,1),$:X(B,2),height:X(B,3),width:X(B,4),rotation:X(B,5,0),X:ob(B,6)};n=I.get(C);a:for(p=new yb,n=new Ua(n);Va(n);)switch(n.i){case 10:G=Xa(n,new wb,xb);mb(p,G,wb,void 0);break;default:if(!rb(p,n))break a}p=lb(p,wb).map(Db);G=r.get(C);a:for(n=new ub,G=new Ua(G);Va(G);)switch(G.i){case 10:n.addClassification(Xa(G,
|
||||
new sb,tb));break;default:if(!rb(n,G))break a}B={T:B,O:p,M:Cb(n)};A.push(B)}K=A}else K=[];h[t]=K;F.g=7;break}if("proto_list"===y.type){if(m){K=Array(m.size());for(I=0;I<m.size();I++)K[I]=m.get(I);m.delete()}else K=[];h[t]=K;F.g=7;break}if(void 0===m){F.g=3;break}if("float_list"===y.type){h[t]=m;F.g=7;break}if("proto"===y.type){h[t]=m;F.g=7;break}if("texture"!==y.type)throw Error("Unknown output config type: '"+y.type+"'");u=v.s[t];u||(u=new Eb(v.h,v.D),v.s[t]=u);return N(F,Fb(u,m,v.G),13);case 13:x=
|
||||
F.h,h[t]=x;case 7:y.transform&&h[t]&&(h[t]=y.transform(h[t]));F.g=3;break;case 14:z[E]=F.h;case 3:k=w.next();F.g=2;break;case 4:return F.return(h)}})})}function Vb(a,b,c){return Y(a,function f(){var h=this,e;return O(f,function(g){return"number"===typeof c||c instanceof Uint8Array||c instanceof h.h.Uint8BlobList?g.return(c):c instanceof h.h.Texture2dDataOut?(e=h.s[b],e||(e=new Eb(h.h,h.D),h.s[b]=e),g.return(Fb(e,c,h.G))):g.return(void 0)})})}
|
||||
function Sb(a,b){for(var c=b.name||"$",d=[].concat(ea(b.wants)),f=new a.h.StringList,h=M(b.wants),e=h.next();!e.done;e=h.next())f.push_back(e.value);h=a.h.PacketListener.implement({onResults:function(g){for(var k={},l=0;l<b.wants.length;++l)k[d[l]]=g.get(l);var q=a.listeners[c];q&&(a.C=Ub(a,k,b.outs).then(function(v){v=q(v);for(var w=0;w<b.wants.length;++w){var t=k[d[w]];"object"===typeof t&&t.hasOwnProperty&&t.hasOwnProperty("delete")&&t.delete()}v&&(a.C=v)}))}});a.i.attachMultiListener(f,h);f.delete()}
|
||||
D.onResults=function(a,b){this.listeners[b||"$"]=a};ya("Solution",Nb);ya("OptionType",{BOOL:0,NUMBER:1,aa:2,0:"BOOL",1:"NUMBER",2:"STRING"});function Wb(a){void 0===a&&(a=0);switch(a){case 1:return"selfie_segmentation_landscape.tflite";default:return"selfie_segmentation.tflite"}}
|
||||
function Xb(a){var b=this;a=a||{};this.g=new Nb({locateFile:a.locateFile,files:function(c){return[{simd:!0,url:"selfie_segmentation_solution_simd_wasm_bin.js"},{simd:!1,url:"selfie_segmentation_solution_wasm_bin.js"},{data:!0,url:Wb(c.modelSelection)}]},graph:{url:"selfie_segmentation.binarypb"},listeners:[{wants:["segmentation_mask","image_transformed"],outs:{image:{type:"texture",stream:"image_transformed"},segmentationMask:{type:"texture",stream:"segmentation_mask"}}}],inputs:{image:{type:"video",
|
||||
stream:"input_frames_gpu"}},options:{useCpuInference:{type:0,graphOptionXref:{calculatorType:"InferenceCalculator",fieldName:"use_cpu_inference"},default:"iPad Simulator;iPhone Simulator;iPod Simulator;iPad;iPhone;iPod".split(";").includes(navigator.platform)||navigator.userAgent.includes("Mac")&&"ontouchend"in document},selfieMode:{type:0,graphOptionXref:{calculatorType:"GlScalerCalculator",calculatorIndex:1,fieldName:"flip_horizontal"}},modelSelection:{type:1,graphOptionXref:{calculatorType:"ConstantSidePacketCalculator",
|
||||
calculatorName:"ConstantSidePacketCalculatorModelSelection",fieldName:"int_value"},onChange:function(c){return Y(b,function f(){var h,e,g=this,k;return O(f,function(l){if(1==l.g)return h=Wb(c),e="third_party/mediapipe/modules/selfie_segmentation/"+h,N(l,Qb(g.g,h),2);k=l.h;g.g.overrideFile(e,k);return l.return(!0)})})}}}})}D=Xb.prototype;D.close=function(){this.g.close();return Promise.resolve()};D.onResults=function(a){this.g.onResults(a)};
|
||||
D.initialize=function(){return Y(this,function b(){var c=this;return O(b,function(d){return N(d,c.g.initialize(),0)})})};D.reset=function(){this.g.reset()};D.send=function(a){return Y(this,function c(){var d=this;return O(c,function(f){return N(f,d.g.send(a),0)})})};D.setOptions=function(a){this.g.setOptions(a)};ya("SelfieSegmentation",Xb);ya("VERSION","0.1.1632777926");}).call(this);
|
||||
46
odoo-bringout-oca-ocb-mail/mail/static/scripts/odoo-mailgate.py
Executable file
46
odoo-bringout-oca-ocb-mail/mail/static/scripts/odoo-mailgate.py
Executable file
|
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/env python2
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
#
|
||||
# odoo-mailgate
|
||||
#
|
||||
# This program will read an email from stdin and forward it to odoo. Configure
|
||||
# a pipe alias in your mail server to use it, postfix uses a syntax that looks
|
||||
# like:
|
||||
#
|
||||
# email@address: "|/home/odoo/src/odoo-mail.py"
|
||||
#
|
||||
# while exim uses a syntax that looks like:
|
||||
#
|
||||
# *: |/home/odoo/src/odoo-mail.py
|
||||
#
|
||||
# Note python2 was chosen on purpose for backward compatibility with old mail
|
||||
# servers.
|
||||
#
|
||||
import optparse
|
||||
import sys
|
||||
import traceback
|
||||
import xmlrpclib
|
||||
|
||||
def main():
|
||||
op = optparse.OptionParser(usage='usage: %prog [options]', version='%prog v1.2')
|
||||
op.add_option("-d", "--database", dest="database", help="Odoo database name (default: %default)", default='odoo')
|
||||
op.add_option("-u", "--userid", dest="userid", help="Odoo user id to connect with (default: %default)", default=1, type=int)
|
||||
op.add_option("-p", "--password", dest="password", help="Odoo user password (default: %default)", default='admin')
|
||||
op.add_option("--host", dest="host", help="Odoo host (default: %default)", default='localhost')
|
||||
op.add_option("--port", dest="port", help="Odoo port (default: %default)", default=8069, type=int)
|
||||
(o, args) = op.parse_args()
|
||||
|
||||
try:
|
||||
msg = sys.stdin.read()
|
||||
models = xmlrpclib.ServerProxy('http://%s:%s/xmlrpc/2/object' % (o.host, o.port), allow_none=True)
|
||||
models.execute_kw(o.database, o.userid, o.password, 'mail.thread', 'message_process', [False, xmlrpclib.Binary(msg)], {})
|
||||
except xmlrpclib.Fault as e:
|
||||
# reformat xmlrpc faults to print a readable traceback
|
||||
err = "xmlrpclib.Fault: %s\n%s" % (e.faultCode, e.faultString)
|
||||
sys.exit(err)
|
||||
except Exception as e:
|
||||
traceback.print_exc(None, sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/call_02_in_.mp3
Normal file
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/call_02_in_.mp3
Normal file
Binary file not shown.
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/call_02_in_.ogg
Normal file
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/call_02_in_.ogg
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/dm_02.mp3
Normal file
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/dm_02.mp3
Normal file
Binary file not shown.
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/dm_02.ogg
Normal file
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/dm_02.ogg
Normal file
Binary file not shown.
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/mute_1.mp3
Normal file
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/mute_1.mp3
Normal file
Binary file not shown.
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/mute_1.ogg
Normal file
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/mute_1.ogg
Normal file
Binary file not shown.
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/ptt_push_1.mp3
Normal file
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/ptt_push_1.mp3
Normal file
Binary file not shown.
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/ptt_push_1.ogg
Normal file
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/ptt_push_1.ogg
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/share_02.mp3
Normal file
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/share_02.mp3
Normal file
Binary file not shown.
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/share_02.ogg
Normal file
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/share_02.ogg
Normal file
Binary file not shown.
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/ting.mp3
Normal file
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/ting.mp3
Normal file
Binary file not shown.
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/ting.ogg
Normal file
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/ting.ogg
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/unmute_1.mp3
Normal file
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/unmute_1.mp3
Normal file
Binary file not shown.
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/unmute_1.ogg
Normal file
BIN
odoo-bringout-oca-ocb-mail/mail/static/src/audio/unmute_1.ogg
Normal file
Binary file not shown.
|
|
@ -0,0 +1,26 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { useRefToModel } from '@mail/component_hooks/use_ref_to_model';
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class ActivityButtonView extends Component {
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
useRefToModel({ fieldName: 'buttonRef', refName: 'button' });
|
||||
}
|
||||
|
||||
get activityButtonView() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(ActivityButtonView, {
|
||||
props: { record: Object },
|
||||
template: 'mail.ActivityButtonView',
|
||||
});
|
||||
|
||||
registerMessagingComponent(ActivityButtonView);
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.ActivityButtonView" owl="1">
|
||||
<a class="o_ActivityButtonView" role="button" t-on-click.prevent="activityButtonView.onClick" t-ref="button">
|
||||
<i class="o_ActivityButtonView_icon fa fa-fw fa-lg" t-att-class="activityButtonView.buttonClass" role="img"/>
|
||||
</a>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { standardFieldProps } from "@web/views/fields/standard_field_props";
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
class ActivityException extends Component {
|
||||
|
||||
get textClass() {
|
||||
if (this.props.value) {
|
||||
return 'text-' + this.props.value + ' fa ' + this.props.record.data.activity_exception_icon;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(ActivityException, {
|
||||
props: standardFieldProps,
|
||||
template: 'mail.ActivityException',
|
||||
fieldDependencies: {
|
||||
activity_exception_icon: { type: 'char' },
|
||||
},
|
||||
noLabel: true,
|
||||
});
|
||||
|
||||
registry.category('fields').add('activity_exception', ActivityException);
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="mail.ActivityException" owl="1">
|
||||
<div
|
||||
t-if="props.value"
|
||||
class="o_ActivityException float-end mt-1"
|
||||
t-att-class="textClass"
|
||||
title="This record has an exception activity."
|
||||
></div>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class ActivityListView extends Component {
|
||||
|
||||
get activityListView() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(ActivityListView, {
|
||||
props: { record: Object },
|
||||
template: 'mail.ActivityListView',
|
||||
});
|
||||
|
||||
registerMessagingComponent(ActivityListView);
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
.o_ActivityListView {
|
||||
width: #{"min(95vw, 300px)"};
|
||||
max-height: #{"min(95vh, 350px)"};
|
||||
}
|
||||
|
||||
.o_ActivityListView_activityList {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="mail.ActivityListView" owl="1">
|
||||
<div class="o_ActivityListView d-flex flex-column" t-ref="root">
|
||||
<div class="o_ActivityListView_activityList d-flex flex-column flex-grow-1">
|
||||
<t t-if="activityListView.activityListViewItems.length === 0">
|
||||
<span class="p-3 text-center fst-italic text-500 border-bottom">Schedule activities to help you get things done.</span>
|
||||
</t>
|
||||
<t t-if="activityListView.overdueActivityListViewItems.length > 0">
|
||||
<div class="d-flex bg-100 py-2 border-bottom">
|
||||
<span class="text-danger fw-bold mx-3">Overdue</span>
|
||||
<span class="flex-grow-1"/>
|
||||
<span class="badge rounded-pill text-bg-danger mx-3 align-self-center" t-esc="activityListView.overdueActivityListViewItems.length"/>
|
||||
</div>
|
||||
<t t-foreach="activityListView.overdueActivityListViewItems" t-as="activityListViewItem" t-key="activityListViewItem">
|
||||
<ActivityListViewItem record="activityListViewItem"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="activityListView.todayActivityListViewItems.length > 0">
|
||||
<div class="d-flex bg-100 py-2 border-bottom">
|
||||
<span class="text-warning fw-bold mx-3">Today</span>
|
||||
<span class="flex-grow-1"/>
|
||||
<span class="badge rounded-pill text-bg-warning mx-3 align-self-center" t-esc="activityListView.todayActivityListViewItems.length"/>
|
||||
</div>
|
||||
<t t-foreach="activityListView.todayActivityListViewItems" t-as="activityListViewItem" t-key="activityListViewItem">
|
||||
<ActivityListViewItem record="activityListViewItem"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="activityListView.plannedActivityListViewItems.length > 0">
|
||||
<div class="d-flex bg-100 py-2 border-bottom">
|
||||
<span class="text-success fw-bold mx-3">Planned</span>
|
||||
<span class="flex-grow-1"/>
|
||||
<span class="badge rounded-pill text-bg-success mx-3 align-self-center" t-esc="activityListView.plannedActivityListViewItems.length"/>
|
||||
</div>
|
||||
<t t-foreach="activityListView.plannedActivityListViewItems" t-as="activityListViewItem" t-key="activityListViewItem">
|
||||
<ActivityListViewItem record="activityListViewItem"/>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
<button class="o_ActivityListView_addActivityButton btn btn-secondary p-3 text-center" t-on-click="activityListView.onClickAddActivityButton">
|
||||
<i class="fa fa-plus fa-fw"></i><strong>Schedule an activity</strong>
|
||||
</button>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class ActivityListViewItem extends Component {
|
||||
|
||||
get activityListViewItem() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(ActivityListViewItem, {
|
||||
props: { record: Object },
|
||||
template: 'mail.ActivityListViewItem',
|
||||
});
|
||||
|
||||
registerMessagingComponent(ActivityListViewItem);
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
.o_ActivityListViewItem_actionLink {
|
||||
@include o-hover-text-color($text-muted, map-get($theme-colors, 'success'));
|
||||
@include o-hover-opacity(0.5, 1);
|
||||
}
|
||||
|
||||
.o_ActivityListViewItem_editButton {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.o_ActivityListViewItem:hover .o_ActivityListViewItem_editButton {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.o_ActivityListViewItem_container {
|
||||
min-width: 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="mail.ActivityListViewItem" owl="1">
|
||||
<div class="o_ActivityListViewItem d-flex flex-column border-bottom py-2">
|
||||
<div class="o_ActivityListViewItem_container d-flex align-items-baseline ms-3 me-1">
|
||||
<i t-if="activityListViewItem.activity.icon" class="fa small me-2" t-attf-class="{{ activityListViewItem.activity.icon }}" role="img"/>
|
||||
<t t-if="activityListViewItem.activity.summary">
|
||||
<b class="text-900 me-2 text-truncate flex-grow-1 flex-basis-0" t-esc="activityListViewItem.activity.summary"/>
|
||||
</t>
|
||||
<t t-if="!activityListViewItem.activity.summary and activityListViewItem.activity.type">
|
||||
<b class="text-900 me-2 text-truncate flex-grow-1" t-esc="activityListViewItem.activity.type.displayName"/>
|
||||
</t>
|
||||
<button t-if="activityListViewItem.hasEditButton" class="o_ActivityListViewItem_editButton btn btn-sm btn-link" t-on-click="activityListViewItem.onClickEditActivityButton">
|
||||
<i class="fa fa-pencil"/>
|
||||
</button>
|
||||
<t t-if="activityListViewItem.activity.canWrite">
|
||||
<button t-if="activityListViewItem.fileUploader" class="o_ActivityListViewItem_actionLink btn btn-link shadow-none fs-4 fa fa-upload" title="Upload file" aria-label="Upload File" t-on-click="activityListViewItem.onClickUploadDocument"/>
|
||||
<button t-if="activityListViewItem.hasMarkDoneButton" class="o_ActivityListViewItem_actionLink o_ActivityListViewItem_markAsDone btn btn-link shadow-none fs-4 fa fa-check-circle" title="Mark as done" aria-label="Mark as done" t-on-click="activityListViewItem.onClickMarkAsDone" t-ref="markDoneButton"/>
|
||||
</t>
|
||||
</div>
|
||||
<div t-if="activityListViewItem.activity.state !== 'today'" class="d-flex align-items-baseline flex-wrap mx-3">
|
||||
<i class="fa fa-clock-o me-2 text-muted" role="img" aria-label="Deadline" title="Deadline"/>
|
||||
<t t-if="!activityListViewItem.activity.isCurrentPartnerAssignee and activityListViewItem.activity.assignee">
|
||||
<small class="text-truncate" t-esc="activityListViewItem.activity.assignee.displayName"/>
|
||||
<small class="mx-1">-</small>
|
||||
</t>
|
||||
<small t-att-title="activityListViewItem.activity.dateDeadline" t-esc="activityListViewItem.delayLabel"/>
|
||||
</div>
|
||||
<ActivityMarkDonePopoverContent t-if="activityListViewItem.markDoneView" record="activityListViewItem.markDoneView"/>
|
||||
<div t-if="activityListViewItem.mailTemplateViews.length > 0" class="mx-3 mt-2">
|
||||
<MailTemplate
|
||||
t-foreach="activityListViewItem.mailTemplateViews" t-as="mailTemplateView" t-key="mailTemplateView"
|
||||
record="mailTemplateView"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class KanbanFieldActivityView extends Component {
|
||||
|
||||
get kanbanFieldActivityView() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(KanbanFieldActivityView, {
|
||||
props: { record: Object },
|
||||
template: 'mail.KanbanFieldActivityView',
|
||||
});
|
||||
|
||||
registerMessagingComponent(KanbanFieldActivityView);
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.KanbanFieldActivityView" owl="1">
|
||||
<ActivityButtonView record="kanbanFieldActivityView.activityButtonView"/>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
// ensure components are registered beforehand.
|
||||
import '@mail/backend_components/kanban_field_activity_view/kanban_field_activity_view';
|
||||
import { getMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { standardFieldProps } from "@web/views/fields/standard_field_props";
|
||||
|
||||
const { Component, onWillDestroy, onWillUpdateProps } = owl;
|
||||
|
||||
const getNextId = (function () {
|
||||
let tmpId = 0;
|
||||
return () => {
|
||||
tmpId += 1;
|
||||
return tmpId;
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* Container for messaging component KanbanFieldActivityView ensuring messaging
|
||||
* records are ready before rendering KanbanFieldActivityView component.
|
||||
*/
|
||||
export class KanbanFieldActivityViewContainer extends Component {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
this.kanbanFieldActivityView = undefined;
|
||||
this.kanbanFieldActivityViewId = getNextId();
|
||||
this._insertFromProps(this.props);
|
||||
onWillUpdateProps(nextProps => this._insertFromProps(nextProps));
|
||||
onWillDestroy(() => this._deleteRecord());
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_deleteRecord() {
|
||||
if (this.kanbanFieldActivityView) {
|
||||
if (this.kanbanFieldActivityView.exists()) {
|
||||
this.kanbanFieldActivityView.delete();
|
||||
}
|
||||
this.kanbanFieldActivityView = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async _insertFromProps(props) {
|
||||
const messaging = await this.env.services.messaging.get();
|
||||
if (owl.status(this) === "destroyed") {
|
||||
this._deleteRecord();
|
||||
return;
|
||||
}
|
||||
const kanbanFieldActivityView = messaging.models['KanbanFieldActivityView'].insert({
|
||||
id: this.kanbanFieldActivityViewId,
|
||||
thread: {
|
||||
activities: props.value.records.map(activityData => {
|
||||
return {
|
||||
id: activityData.resId,
|
||||
};
|
||||
}),
|
||||
hasActivities: true,
|
||||
id: props.record.resId,
|
||||
model: props.record.resModel,
|
||||
},
|
||||
webRecord: props.record,
|
||||
});
|
||||
if (kanbanFieldActivityView !== this.kanbanFieldActivityView) {
|
||||
this._deleteRecord();
|
||||
this.kanbanFieldActivityView = kanbanFieldActivityView;
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(KanbanFieldActivityViewContainer, {
|
||||
components: { KanbanFieldActivityView: getMessagingComponent('KanbanFieldActivityView') },
|
||||
fieldDependencies: {
|
||||
activity_exception_decoration: { type: 'selection' },
|
||||
activity_exception_icon: { type: 'char' },
|
||||
activity_state: { type: 'selection' },
|
||||
activity_summary: { type: 'char' },
|
||||
activity_type_icon: { type: 'char' },
|
||||
activity_type_id: { type: 'many2one', relation: 'mail.activity.type' },
|
||||
},
|
||||
props: {
|
||||
...standardFieldProps,
|
||||
},
|
||||
template: 'mail.KanbanFieldActivityViewContainer',
|
||||
});
|
||||
|
||||
registry.category('fields').add('kanban_activity', KanbanFieldActivityViewContainer);
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.KanbanFieldActivityViewContainer" owl="1">
|
||||
<KanbanFieldActivityView t-if="kanbanFieldActivityView" record="kanbanFieldActivityView"/>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class ListFieldActivityView extends Component {
|
||||
|
||||
get listFieldActivityView() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(ListFieldActivityView, {
|
||||
props: { record: Object },
|
||||
template: 'mail.ListFieldActivityView',
|
||||
});
|
||||
|
||||
registerMessagingComponent(ListFieldActivityView);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.ListFieldActivityView" owl="1">
|
||||
<ActivityButtonView record="listFieldActivityView.activityButtonView"/>
|
||||
<span class="o_ListFieldActivityView_summary" t-out="listFieldActivityView.summaryText"/>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
// ensure components are registered beforehand.
|
||||
import '@mail/backend_components/list_field_activity_view/list_field_activity_view';
|
||||
import { getMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { standardFieldProps } from "@web/views/fields/standard_field_props";
|
||||
|
||||
const { Component, onWillDestroy, onWillUpdateProps } = owl;
|
||||
|
||||
const getNextId = (function () {
|
||||
let tmpId = 0;
|
||||
return () => {
|
||||
tmpId += 1;
|
||||
return tmpId;
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* Container for messaging component ListFieldActivityView ensuring messaging
|
||||
* records are ready before rendering ListFieldActivityView component.
|
||||
*/
|
||||
export class ListFieldActivityViewContainer extends Component {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
this.listFieldActivityView = undefined;
|
||||
this.listFieldActivityViewId = getNextId();
|
||||
this._insertFromProps(this.props);
|
||||
onWillUpdateProps(nextProps => this._insertFromProps(nextProps));
|
||||
onWillDestroy(() => this._deleteRecord());
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_deleteRecord() {
|
||||
if (this.listFieldActivityView) {
|
||||
if (this.listFieldActivityView.exists()) {
|
||||
this.listFieldActivityView.delete();
|
||||
}
|
||||
this.listFieldActivityView = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async _insertFromProps(props) {
|
||||
const messaging = await this.env.services.messaging.get();
|
||||
if (owl.status(this) === "destroyed") {
|
||||
this._deleteRecord();
|
||||
return;
|
||||
}
|
||||
const listFieldActivityView = messaging.models['ListFieldActivityView'].insert({
|
||||
id: this.listFieldActivityViewId,
|
||||
thread: {
|
||||
activities: props.value.records.map(activityData => {
|
||||
return {
|
||||
id: activityData.resId,
|
||||
};
|
||||
}),
|
||||
hasActivities: true,
|
||||
id: props.record.resId,
|
||||
model: props.record.resModel,
|
||||
},
|
||||
webRecord: props.record,
|
||||
});
|
||||
if (listFieldActivityView !== this.listFieldActivityView) {
|
||||
this._deleteRecord();
|
||||
this.listFieldActivityView = listFieldActivityView;
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(ListFieldActivityViewContainer, {
|
||||
components: { ListFieldActivityView: getMessagingComponent('ListFieldActivityView') },
|
||||
fieldDependencies: {
|
||||
activity_exception_decoration: { type: 'selection' },
|
||||
activity_exception_icon: { type: 'char' },
|
||||
activity_state: { type: 'selection' },
|
||||
activity_summary: { type: 'char' },
|
||||
activity_type_icon: { type: 'char' },
|
||||
activity_type_id: { type: 'many2one', relation: 'mail.activity.type' },
|
||||
},
|
||||
props: {
|
||||
...standardFieldProps,
|
||||
},
|
||||
template: 'mail.ListFieldActivityViewContainer',
|
||||
});
|
||||
|
||||
registry.category('fields').add('list_activity', ListFieldActivityViewContainer);
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.ListFieldActivityViewContainer" owl="1">
|
||||
<ListFieldActivityView t-if="listFieldActivityView" record="listFieldActivityView"/>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { clear } from '@mail/model/model_field_command';
|
||||
|
||||
const { onWillUpdateProps, useComponent } = owl;
|
||||
|
||||
/**
|
||||
* This hook provides support for saving the reference of the component directly
|
||||
* into the field of a record, and appropriately updates it when necessary
|
||||
* (props change or destroy).
|
||||
*
|
||||
* @param {Object} param0
|
||||
* @param {string} param0.fieldName Name of the field on the target record.
|
||||
*/
|
||||
export function useComponentToModel({ fieldName }) {
|
||||
const component = useComponent();
|
||||
component.props.record.update({ [fieldName]: component });
|
||||
onWillUpdateProps(nextProps => {
|
||||
const currentRecord = component.props.record;
|
||||
const nextRecord = nextProps.record;
|
||||
if (currentRecord.exists() && currentRecord !== nextRecord) {
|
||||
currentRecord.update({ [fieldName]: clear() });
|
||||
}
|
||||
nextRecord.update({ [fieldName]: component });
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { Listener } from '@mail/model/model_listener';
|
||||
|
||||
const { onRendered, onWillDestroy, onWillRender, useComponent } = owl;
|
||||
|
||||
/**
|
||||
* This hook provides support for automatically re-rendering when used records
|
||||
* or fields changed.
|
||||
*
|
||||
* Components that use this hook must be instantiated after messaging service is
|
||||
* started. However there is no restriction on the messaging record (coming from
|
||||
* the modelManager of the messaging service) being already initialized or even
|
||||
* created.
|
||||
*/
|
||||
export function useModels() {
|
||||
const component = useComponent();
|
||||
const listener = new Listener({
|
||||
isLocking: false, // unfortunately __render has side effects such as children components updating their reference to their corresponding model
|
||||
name: `useModels() of ${component}`,
|
||||
onChange: () => component.render(),
|
||||
});
|
||||
onWillRender(() => {
|
||||
component.env.services.messaging.modelManager.startListening(listener);
|
||||
});
|
||||
onRendered(() => {
|
||||
component.env.services.messaging.modelManager.stopListening(listener);
|
||||
});
|
||||
onWillDestroy(() => {
|
||||
component.env.services.messaging.modelManager.removeListener(listener);
|
||||
});
|
||||
component.env.services.messaging.modelManager.messagingCreatedPromise.then(() => {
|
||||
component.render();
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { clear } from '@mail/model/model_field_command';
|
||||
|
||||
const { onWillUpdateProps, useComponent, useRef } = owl;
|
||||
|
||||
/**
|
||||
* This hook provides support for saving the result of useRef directly into the
|
||||
* field of a record, and appropriately updates it when necessary (props change
|
||||
* or destroy).
|
||||
*
|
||||
* @param {Object} param0
|
||||
* @param {string} param0.fieldName Name of the field on the target record.
|
||||
* @param {string} param0.refName Name of the t-ref on this component.
|
||||
*/
|
||||
export function useRefToModel({ fieldName, refName }) {
|
||||
const component = useComponent();
|
||||
const ref = useRef(refName);
|
||||
component.props.record.update({ [fieldName]: ref });
|
||||
onWillUpdateProps(nextProps => {
|
||||
const currentRecord = component.props.record;
|
||||
const nextRecord = nextProps.record;
|
||||
if (currentRecord.exists() && currentRecord !== nextRecord) {
|
||||
currentRecord.update({ [fieldName]: clear() });
|
||||
}
|
||||
nextRecord.update({ [fieldName]: ref });
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
const { useComponent } = owl;
|
||||
|
||||
/**
|
||||
* This hook provides support for dynamic-refs.
|
||||
*
|
||||
* @returns {function} returns object whose keys are t-ref values of active refs.
|
||||
* and values are refs.
|
||||
*/
|
||||
export function useRefs() {
|
||||
const component = useComponent();
|
||||
return function () {
|
||||
return component.__owl__.refs || {};
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { Listener } from '@mail/model/model_listener';
|
||||
|
||||
const { onMounted, onPatched, onWillDestroy, onWillRender, useComponent } = owl;
|
||||
|
||||
/**
|
||||
* This hooks provides support for accessing the values returned by the given
|
||||
* selector at the time of the last render. The values will be updated after
|
||||
* every mount/patch.
|
||||
*
|
||||
* @param {function} selector function that will be executed at the time of the
|
||||
* render and of which the result will be stored for future reference.
|
||||
* @returns {function} function to call to retrieve the last rendered values.
|
||||
*/
|
||||
export function useRenderedValues(selector) {
|
||||
const component = useComponent();
|
||||
let renderedValues;
|
||||
let patchedValues;
|
||||
const listener = new Listener({
|
||||
name: `useRenderedValues() of ${component}`,
|
||||
onChange: () => component.render(),
|
||||
});
|
||||
onWillRender(() => {
|
||||
component.env.services.messaging.modelManager.startListening(listener);
|
||||
renderedValues = selector();
|
||||
component.env.services.messaging.modelManager.stopListening(listener);
|
||||
});
|
||||
onMounted(onUpdate);
|
||||
onPatched(onUpdate);
|
||||
function onUpdate() {
|
||||
patchedValues = renderedValues;
|
||||
}
|
||||
onWillDestroy(() => component.env.services.messaging.modelManager.removeListener(listener));
|
||||
return () => patchedValues;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { Listener } from '@mail/model/model_listener';
|
||||
|
||||
const { onMounted, onPatched, onWillDestroy, useComponent } = owl;
|
||||
|
||||
/**
|
||||
* This hook provides support for executing code after update (render or patch).
|
||||
*
|
||||
* @param {Object} param0
|
||||
* @param {function} param0.func the function to execute after the update.
|
||||
*/
|
||||
export function useUpdate({ func }) {
|
||||
const component = useComponent();
|
||||
const listener = new Listener({
|
||||
isLocking: false, // unfortunately onUpdate methods often have side effect
|
||||
name: `useUpdate() of ${component}`,
|
||||
onChange: () => component.render(),
|
||||
});
|
||||
function onUpdate() {
|
||||
component.env.services.messaging.modelManager.startListening(listener);
|
||||
func();
|
||||
component.env.services.messaging.modelManager.stopListening(listener);
|
||||
}
|
||||
onMounted(onUpdate);
|
||||
onPatched(onUpdate);
|
||||
onWillDestroy(() => {
|
||||
component.env.services.messaging.modelManager.removeListener(listener);
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { useUpdate } from '@mail/component_hooks/use_update';
|
||||
|
||||
const { useComponent } = owl;
|
||||
|
||||
/**
|
||||
* This hook provides support for binding the onMounted/onPatched hooks to the
|
||||
* method of a target record.
|
||||
*
|
||||
* @param {Object} param0
|
||||
* @param {string} param0.methodName Name of the method on the target record.
|
||||
*/
|
||||
export function useUpdateToModel({ methodName }) {
|
||||
const component = useComponent();
|
||||
useUpdate({ func: () => {
|
||||
component.props.record[methodName]();
|
||||
} });
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { useComponentToModel } from '@mail/component_hooks/use_component_to_model';
|
||||
import { useRefToModel } from '@mail/component_hooks/use_ref_to_model';
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
import Popover from "web.Popover";
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
|
||||
export class Activity extends LegacyComponent {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
useComponentToModel({ fieldName: 'component' });
|
||||
useRefToModel({ fieldName: 'markDoneButtonRef', refName: 'markDoneButton', });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {ActivityView}
|
||||
*/
|
||||
get activityView() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(Activity, {
|
||||
props: { record: Object },
|
||||
template: 'mail.Activity',
|
||||
components: { Popover },
|
||||
});
|
||||
|
||||
registerMessagingComponent(Activity);
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
// ------------------------------------------------------------------
|
||||
// Layout
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_Activity_core {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.o_Activity_detailsUserAvatar {
|
||||
object-fit: cover;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
.o_Activity_iconContainer {
|
||||
@include o-position-absolute($top: auto, $left: auto, $bottom: -16%, $right: -5%);
|
||||
}
|
||||
|
||||
.o_Activity_note p {
|
||||
margin-bottom: map-get($spacers, 0);
|
||||
}
|
||||
|
||||
.o_Activity_sidebar {
|
||||
width: $o-mail-thread-avatar-size;
|
||||
min-width: $o-mail-thread-avatar-size;
|
||||
height: $o-mail-thread-avatar-size;
|
||||
}
|
||||
|
||||
// From python template
|
||||
.o_mail_note_title {
|
||||
margin-top: map-get($spacers, 2);
|
||||
}
|
||||
|
||||
.o_mail_note_title + div p {
|
||||
margin-bottom: map-get($spacers, 0);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Style
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_Activity_detailsButton {
|
||||
@include o-hover-text-color($default-color: $o-main-color-muted);
|
||||
}
|
||||
|
||||
.o_Activity_iconContainer {
|
||||
box-shadow: 0 0 0 2px $o-view-background-color;
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.Activity" owl="1">
|
||||
<t t-if="activityView">
|
||||
<div class="o_Activity d-flex py-2 px-3" t-attf-class="{{ className }}" t-on-click="activityView.onClickActivity" t-ref="root">
|
||||
<div class="o_Activity_sidebar me-3">
|
||||
<div class="o_Activity_user position-relative h-100 w-100">
|
||||
<t t-if="activityView.activity.assignee">
|
||||
<img class="o_Activity_userAvatar rounded-circle h-100 w-100 o_object_fit_cover" t-attf-src="/web/image/res.users/{{ activityView.activity.assignee.id }}/avatar_128" t-att-alt="activityView.activity.assignee.nameOrDisplayName"/>
|
||||
</t>
|
||||
<div class="o_Activity_iconContainer d-flex align-items-center justify-content-center rounded-circle w-50 h-50"
|
||||
t-att-class="{
|
||||
'text-bg-success': activityView.activity.state === 'planned',
|
||||
'text-bg-warning': activityView.activity.state === 'today',
|
||||
'text-bg-danger': activityView.activity.state === 'overdue',
|
||||
}"
|
||||
>
|
||||
<i class="o_Activity_icon fa small" t-attf-class="{{ activityView.activity.icon }}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_Activity_core">
|
||||
<div class="o_Activity_info d-flex align-items-baseline">
|
||||
<div class="o_Activity_dueDateText me-2"
|
||||
t-att-class="{
|
||||
'text-danger': activityView.activity.state === 'overdue',
|
||||
'text-success': activityView.activity.state === 'planned',
|
||||
'text-warning': activityView.activity.state === 'today',
|
||||
}"
|
||||
>
|
||||
<b t-esc="activityView.delayLabel"/>
|
||||
</div>
|
||||
<t t-if="activityView.activity.summary">
|
||||
<b class="o_Activity_summary text-900 me-2">
|
||||
<t t-esc="activityView.summary"/>
|
||||
</b>
|
||||
</t>
|
||||
<t t-elif="activityView.activity.type">
|
||||
<b class="o_Activity_summary o_Activity_type text-900 me-2">
|
||||
<t t-esc="activityView.activity.type.displayName"/>
|
||||
</b>
|
||||
</t>
|
||||
<t t-if="activityView.activity.assignee">
|
||||
<div class="o_Activity_userName">
|
||||
<t t-esc="activityView.assignedUserText"/>
|
||||
</div>
|
||||
</t>
|
||||
<a
|
||||
href="#"
|
||||
class="o_Activity_detailsButton btn py-0"
|
||||
t-att-class="activityView.areDetailsVisible ? 'text-primary' : 'btn-link btn-primary'"
|
||||
t-att-aria-expanded="activityView.areDetailsVisible ? 'true' : 'false'"
|
||||
t-on-click="activityView.onClickDetailsButton"
|
||||
role="button"
|
||||
>
|
||||
<i class="fa fa-info-circle" role="img" title="Info" aria-label="Info"/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<t t-if="activityView.areDetailsVisible">
|
||||
<div class="o_Activity_details">
|
||||
<div class="d-md-table table table-sm mt-2 mb-3">
|
||||
<div t-if="activityView.activity.type" class="d-md-table-row mb-3">
|
||||
<div class="d-md-table-cell fw-bold text-md-end m-0 py-md-1 px-md-4">Activity type</div>
|
||||
<div class="o_Activity_type d-md-table-cell py-md-1 pe-4">
|
||||
<t t-esc="activityView.activity.type.displayName"/>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="activityView.activity.creator" class="d-md-table-row mb-3">
|
||||
<div class="d-md-table-cell fw-bold text-md-end m-0 py-md-1 px-md-4">Created</div>
|
||||
<div class="o_Activity_detailsCreation d-md-table-cell py-md-1 pe-4">
|
||||
<t t-esc="activityView.formattedCreateDatetime"/>, <br t-if="messaging.device.isSmall"/>by
|
||||
<img class="o_Activity_detailsUserAvatar o_Activity_detailsCreatorAvatar ms-1 me-1 rounded-circle align-text-bottom p-0" t-attf-src="/web/image/res.users/{{ activityView.activity.creator.id }}/avatar_128" t-att-title="activityView.activity.creator.nameOrDisplayName" t-att-alt="activityView.activity.creator.nameOrDisplayName"/>
|
||||
<b class="o_Activity_detailsCreator">
|
||||
<t t-esc="activityView.activity.creator.nameOrDisplayName"/>
|
||||
</b>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="activityView.activity.assignee" class="d-md-table-row mb-3">
|
||||
<div class="d-md-table-cell fw-bold text-md-end m-0 py-md-1 px-md-4">Assigned to</div>
|
||||
<div class="o_Activity_detailsAssignation d-md-table-cell py-md-1 pe-4">
|
||||
<img class="o_Activity_detailsUserAvatar o_Activity_detailsAssignationUserAvatar me-1 rounded-circle align-text-bottom p-0" t-attf-src="/web/image/res.users/{{ activityView.activity.assignee.id }}/avatar_128" t-att-title="activityView.activity.assignee.nameOrDisplayName" t-att-alt="activityView.activity.assignee.nameOrDisplayName"/>
|
||||
<b t-esc="activityView.activity.assignee.nameOrDisplayName"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-md-table-row">
|
||||
<div class="d-md-table-cell fw-bold text-md-end m-0 py-md-1 px-md-4">Due on</div>
|
||||
<div class="o_Activity_detailsDueDate d-md-table-cell py-md-1 pe-4">
|
||||
<span class="o_Activity_deadlineDateText"
|
||||
t-att-class="{
|
||||
'text-danger': activityView.activity.state === 'overdue',
|
||||
'text-success': activityView.activity.state === 'planned',
|
||||
'text-warning': activityView.activity.state === 'today',
|
||||
}"
|
||||
>
|
||||
<t t-esc="activityView.formattedDeadlineDate"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-if="activityView.activity.note">
|
||||
<div class="o_Activity_note">
|
||||
<t t-out="activityView.activity.noteAsMarkup"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-if="activityView.mailTemplateViews.length > 0">
|
||||
<div class="o_Activity_mailTemplates">
|
||||
<t t-foreach="activityView.mailTemplateViews" t-as="mailTemplateView" t-key="mailTemplateView.localId">
|
||||
<MailTemplate
|
||||
className="'o_Activity_mailTemplate'"
|
||||
record="mailTemplateView"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-if="activityView.activity.canWrite">
|
||||
<div name="tools" class="o_Activity_tools d-flex">
|
||||
<button class="o_Activity_toolButton o_Activity_markDoneButton btn btn-link btn-primary pt-0 ps-0" t-att-title="activityView.markDoneText" t-ref="markDoneButton" t-on-click="activityView.onClickMarkDoneButton">
|
||||
<i class="fa fa-check"/> Mark Done
|
||||
</button>
|
||||
<t t-if="activityView.fileUploader">
|
||||
<button class="o_Activity_toolButton o_Activity_uploadButton btn btn-link btn-primary pt-0 ps-0" t-on-click="activityView.onClickUploadDocument">
|
||||
<i class="fa fa-upload"/> Upload Document
|
||||
</button>
|
||||
</t>
|
||||
<button class="o_Activity_toolButton o_Activity_editButton btn btn-link btn-primary pt-0" t-on-click="activityView.onClickEdit">
|
||||
<i class="fa fa-pencil"/> Edit
|
||||
</button>
|
||||
<button class="o_Activity_toolButton o_Activity_cancelButton btn btn-link btn-primary pt-0" t-on-click="activityView.onClickCancel" >
|
||||
<i class="fa fa-times"/> Cancel
|
||||
</button>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class ActivityBox extends Component {
|
||||
|
||||
/**
|
||||
* @returns {ActivityBoxView}
|
||||
*/
|
||||
get activityBoxView() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(ActivityBox, {
|
||||
props: { record: Object },
|
||||
template: 'mail.ActivityBox',
|
||||
});
|
||||
|
||||
registerMessagingComponent(ActivityBox);
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// ------------------------------------------------------------------
|
||||
// Layout
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_ActivityBox_activityList {
|
||||
max-width: var(--Chatter-max-width, none);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Style
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_ActivityBox_title {
|
||||
margin-top: var(--ActivityBox_title-margin, #{map-get($spacers, 4)});
|
||||
}
|
||||
|
||||
.o_ActivityBox_titleLine {
|
||||
border-top: $border-width dashed $border-color;
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.ActivityBox" owl="1">
|
||||
<t t-if="activityBoxView">
|
||||
<div class="o_ActivityBox" t-attf-class="{{ className }}" t-ref="root">
|
||||
<a href="#" role="button" class="o_ActivityBox_title btn d-flex align-items-center p-0 w-100 fw-bold" t-att-aria-expanded="activityBoxView.isActivityListVisible ? 'true' : 'false'" t-on-click="activityBoxView.onClickActivityBoxTitle">
|
||||
<hr class="o_ActivityBox_titleLine w-auto flex-grow-1 me-3" />
|
||||
<span class="o_ActivityBox_titleText">
|
||||
<i class="fa fa-fw" t-att-class="activityBoxView.isActivityListVisible ? 'fa-caret-down' : 'fa-caret-right'"/>
|
||||
Planned activities
|
||||
</span>
|
||||
<t t-if="!activityBoxView.isActivityListVisible">
|
||||
<span class="o_ActivityBox_titleBadges ms-2">
|
||||
<t t-if="activityBoxView.chatter.thread.overdueActivities.length > 0">
|
||||
<span class="o_ActivityBox_titleBadge me-1 badge text-bg-danger">
|
||||
<t t-esc="activityBoxView.chatter.thread.overdueActivities.length"/>
|
||||
</span>
|
||||
</t>
|
||||
<t t-if="activityBoxView.chatter.thread.todayActivities.length > 0">
|
||||
<span class="o_ActivityBox_titleBadge me-1 badge text-bg-warning">
|
||||
<t t-esc="activityBoxView.chatter.thread.todayActivities.length"/>
|
||||
</span>
|
||||
</t>
|
||||
<t t-if="activityBoxView.chatter.thread.futureActivities.length > 0">
|
||||
<span class="o_ActivityBox_titleBadge me-1 badge text-bg-success">
|
||||
<t t-esc="activityBoxView.chatter.thread.futureActivities.length"/>
|
||||
</span>
|
||||
</t>
|
||||
</span>
|
||||
</t>
|
||||
<hr class="o_ActivityBox_titleLine w-auto flex-grow-1 ms-3"/>
|
||||
</a>
|
||||
<t t-if="activityBoxView.isActivityListVisible">
|
||||
<div class="o_ActivityBox_activityList">
|
||||
<t t-foreach="activityBoxView.activityViews" t-as="activityView" t-key="activityView.localId">
|
||||
<Activity className="'o_ActivityBox_activity'" record="activityView"/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { useComponentToModel } from '@mail/component_hooks/use_component_to_model';
|
||||
import { useRefToModel } from '@mail/component_hooks/use_ref_to_model';
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
|
||||
const { onMounted, useRef } = owl;
|
||||
|
||||
export class ActivityMarkDonePopoverContent extends LegacyComponent {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
useComponentToModel({ fieldName: 'component' });
|
||||
useRefToModel({ fieldName: 'feedbackTextareaRef', refName: 'feedbackTextarea' });
|
||||
this._feedbackTextareaRef = useRef('feedbackTextarea');
|
||||
onMounted(() => this._mounted());
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
_mounted() {
|
||||
this._feedbackTextareaRef.el.focus();
|
||||
if (this.activityMarkDonePopoverContentView.activity.feedbackBackup) {
|
||||
this._feedbackTextareaRef.el.value = this.activityMarkDonePopoverContentView.activity.feedbackBackup;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {ActivityMarkDonePopoverContentView}
|
||||
*/
|
||||
get activityMarkDonePopoverContentView() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(ActivityMarkDonePopoverContent, {
|
||||
props: { record: Object },
|
||||
template: 'mail.ActivityMarkDonePopoverContent',
|
||||
});
|
||||
|
||||
registerMessagingComponent(ActivityMarkDonePopoverContent);
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
// ------------------------------------------------------------------
|
||||
// Layout
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_ActivityMarkDonePopoverContent_feedback {
|
||||
min-height: 70px;
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.ActivityMarkDonePopoverContent" owl="1">
|
||||
<t t-if="activityMarkDonePopoverContentView">
|
||||
<div class="o_ActivityMarkDonePopoverContent" t-attf-class="{{ className }}" t-on-keydown="activityMarkDonePopoverContentView.onKeydown" t-ref="root">
|
||||
<h6 t-if="activityMarkDonePopoverContentView.hasHeader" class="o_ActivityMarkDonePopoverContent_header p-2 fw-bolder bg-200 border-bottom" t-esc="activityMarkDonePopoverContentView.headerText"/>
|
||||
<div class="o_ActivityMarkDonePopoverContent_content py-2 px-3">
|
||||
<textarea class="form-control o_ActivityMarkDonePopoverContent_feedback" rows="3" placeholder="Write Feedback" t-on-blur="activityMarkDonePopoverContentView.onBlur" t-ref="feedbackTextarea"/>
|
||||
<div class="o_ActivityMarkDonePopoverContent_buttons mt-2">
|
||||
<button type="button" class="o_ActivityMarkDonePopoverContent_doneScheduleNextButton btn btn-sm btn-primary" t-on-click="activityMarkDonePopoverContentView.onClickDoneAndScheduleNext">
|
||||
Done & Schedule Next
|
||||
</button>
|
||||
<t t-if="activityMarkDonePopoverContentView.activity.chaining_type === 'suggest'">
|
||||
<button type="button" class="o_ActivityMarkDonePopoverContent_doneButton btn btn-sm btn-primary mx-2" t-on-click="activityMarkDonePopoverContentView.onClickDone">
|
||||
Done
|
||||
</button>
|
||||
</t>
|
||||
<button type="button" class="o_ActivityMarkDonePopoverContent_discardButton btn btn-sm btn-link" t-on-click="activityMarkDonePopoverContentView.onClickDiscard">
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
// ensure components are registered beforehand.
|
||||
import '@mail/components/activity_menu_view/activity_menu_view';
|
||||
import { getMessagingComponent } from "@mail/utils/messaging_component";
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class ActivityMenuContainer extends Component {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
this.env.services.messaging.modelManager.messagingCreatedPromise.then(() => {
|
||||
this.activityMenuView = this.env.services.messaging.modelManager.messaging.models['ActivityMenuView'].insert();
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(ActivityMenuContainer, {
|
||||
components: { ActivityMenuView: getMessagingComponent('ActivityMenuView') },
|
||||
template: 'mail.ActivityMenuContainer',
|
||||
});
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="mail.ActivityMenuContainer" owl="1">
|
||||
<t t-if="activityMenuView">
|
||||
<ActivityMenuView record="activityMenuView"/>
|
||||
</t>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { useComponentToModel } from '@mail/component_hooks/use_component_to_model';
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class ActivityMenuView extends Component {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
useComponentToModel({ fieldName: 'component' });
|
||||
}
|
||||
/**
|
||||
* @returns {ActivityMenuView}
|
||||
*/
|
||||
get activityMenuView() {
|
||||
return this.props.record;
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(ActivityMenuView, {
|
||||
props: { record: Object },
|
||||
template: 'mail.ActivityMenuView',
|
||||
});
|
||||
|
||||
registerMessagingComponent(ActivityMenuView);
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
.o_ActivityMenuView_activityGroups {
|
||||
flex: 0 1 auto;
|
||||
max-height: 400px;
|
||||
min-height: 50px;
|
||||
overflow-y: auto;
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
max-height: none;
|
||||
padding-bottom: 52px; // leave space for tabs
|
||||
}
|
||||
}
|
||||
|
||||
.o_ActivityMenuView_activityGroup {
|
||||
display: flex;
|
||||
background-color: transparent;
|
||||
color: $o-main-text-color;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
&:hover {
|
||||
background-color: map-get($theme-colors, 'light');
|
||||
.o_ActivityMenuView_activityGroupName {
|
||||
color: $headings-color;
|
||||
}
|
||||
}
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid map-get($grays, '400');
|
||||
}
|
||||
@include media-breakpoint-down(md) {
|
||||
padding: $o-mail-chatter-mobile-gap;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
min-height: 50px;
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_ActivityMenuView_activityGroupActionButtons {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-flow: row-reverse wrap;
|
||||
}
|
||||
|
||||
.o_ActivityMenuView_activityGroupActionButton {
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.o_ActivityMenuView_activityGroupFilterButton {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.o_ActivityMenuView_activityGroupIconContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
position: relative;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
object-fit: cover;
|
||||
> img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
.fa-circle-o {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_ActivityMenuView_activityGroupInfo {
|
||||
flex: 1 1 100%;
|
||||
overflow: hidden;
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
margin-left: $o-mail-chatter-mobile-gap;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_ActivityMenuView_activityGroupName {
|
||||
flex: 0 1 auto;
|
||||
@include o-text-overflow;
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
}
|
||||
|
||||
.o_ActivityMenuView_activityGroupNoCount {
|
||||
cursor: initial;
|
||||
align-items: center;
|
||||
opacity: 0.5;
|
||||
padding: 3px;
|
||||
min-height: inherit;
|
||||
}
|
||||
|
||||
.o_ActivityMenuView_activityGroupTitle {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.o_ActivityMenuView_dropdownMenu {
|
||||
direction: ltr;
|
||||
width: 350px;
|
||||
padding: 0;
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
position: fixed;
|
||||
top: $o-mail-chat-window-header-height-mobile;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
max-height: none;
|
||||
}
|
||||
}
|
||||
|
||||
.o_ActivityMenuView_noActivity {
|
||||
cursor: initial;
|
||||
align-items: center;
|
||||
color: grey;
|
||||
opacity: 0.5;
|
||||
padding: 3px;
|
||||
min-height: inherit;
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.ActivityMenuView" owl="1">
|
||||
<div class="o_ActivityMenuView dropdown" t-ref="root">
|
||||
<a class="o_ActivityMenuView_dropdownToggle dropdown-toggle o-no-caret o-dropdown--narrow" t-att-aria-expanded="activityMenuView.isOpen ? 'true' : 'false'" title="Activities" href="#" role="button" t-on-click="activityMenuView.onClickDropdownToggle">
|
||||
<i class="fa fa-lg fa-clock-o" role="img" aria-label="Activities"/> <span t-if="activityMenuView.counter > 0" class="o_ActivityMenuView_counter badge" t-esc="activityMenuView.counter"/>
|
||||
</a>
|
||||
<div t-if="activityMenuView.isOpen" class="o_ActivityMenuView_dropdownMenu o-dropdown-menu dropdown-menu-end show bg-view" role="menu">
|
||||
<div class="o_ActivityMenuView_activityGroups">
|
||||
<t t-if="activityMenuView.activityGroupViews.length === 0">
|
||||
<div class="o_ActivityMenuView_noActivity dropdown-item-text text-center d-flex justify-content-center">
|
||||
<span>Congratulations, you're done with your activities.</span>
|
||||
</div>
|
||||
</t>
|
||||
<t t-foreach="activityMenuView.activityGroupViews" t-as="activityGroupView" t-key="activityGroupView.localId" name="activityGroupLoop">
|
||||
<div class="o_ActivityMenuView_activityGroup" t-att-data-res_model="activityGroupView.activityGroup.irModel.model" t-att-data-model_name="activityGroupView.activityGroup.irModel.name" t-att-data-domain="activityGroupView.activityGroup.domain" data-filter='my' t-att-data-activity-group-view-local-id="activityGroupView.localId" t-on-click="activityGroupView.onClickFilterButton">
|
||||
<div t-if="activityGroupView.activityGroup.irModel.iconUrl" class="o_ActivityMenuView_activityGroupIconContainer">
|
||||
<img t-att-src="activityGroupView.activityGroup.irModel.iconUrl" alt="Activity"/>
|
||||
</div>
|
||||
<div class="o_ActivityMenuView_activityGroupInfo">
|
||||
<div class="o_ActivityMenuView_activityGroupTitle">
|
||||
<span class="o_ActivityMenuView_activityGroupName">
|
||||
<t t-esc="activityGroupView.activityGroup.irModel.name"/>
|
||||
</span>
|
||||
<div t-if="activityGroupView.activityGroup.actions" class="o_ActivityMenuView_activityGroupActionButtons">
|
||||
<t t-foreach="activityGroupView.activityGroup.actions" t-as="action" t-key="action.name">
|
||||
<button type="button"
|
||||
t-att-title="action.name"
|
||||
t-att-class="'o_ActivityMenuView_activityGroupActionButton btn btn-link fa ' + action.icon"
|
||||
t-att-data-action_xmlid="action.action_xmlid"
|
||||
t-att-data-res_model="activityGroupView.activityGroup.irModel.model"
|
||||
t-att-data-model_name="activityGroupView.activityGroup.irModel.name"
|
||||
t-att-data-domain="activityGroupView.activityGroup.domain"
|
||||
t-on-click="activityGroupView.onClick"
|
||||
>
|
||||
</button>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="activityGroupView.activityGroup.type == 'activity'">
|
||||
<button t-if="activityGroupView.activityGroup.overdue_count" type="button" class="o_ActivityMenuView_activityGroupFilterButton btn btn-link mr16" t-att-data-res_model="activityGroupView.activityGroup.irModel.model" t-att-data-model_name="activityGroupView.activityGroup.irModel.name" data-filter='overdue'><t t-esc="activityGroupView.activityGroup.overdue_count"/> Late </button>
|
||||
<span t-if="!activityGroupView.activityGroup.overdue_count" class="o_ActivityMenuView_activityGroupNoCount mr16 text-muted">0 Late </span>
|
||||
<button t-if="activityGroupView.activityGroup.today_count" type="button" class="o_ActivityMenuView_activityGroupFilterButton btn btn-link mr16" t-att-data-res_model="activityGroupView.activityGroup.irModel.model" t-att-data-model_name="activityGroupView.activityGroup.irModel.name" data-filter='today'> <t t-esc="activityGroupView.activityGroup.today_count"/> Today </button>
|
||||
<span t-if="!activityGroupView.activityGroup.today_count" class="o_ActivityMenuView_activityGroupNoCount mr16 text-muted">0 Today </span>
|
||||
<button t-if="activityGroupView.activityGroup.planned_count" type="button" class="o_ActivityMenuView_activityGroupFilterButton btn btn-link float-end" t-att-data-res_model="activityGroupView.activityGroup.irModel.model" t-att-data-model_name="activityGroupView.activityGroup.irModel.name" data-filter='upcoming_all'> <t t-esc="activityGroupView.activityGroup.planned_count"/> Future </button>
|
||||
<span t-if="!activityGroupView.activityGroup.planned_count" class="o_ActivityMenuView_activityGroupNoCount float-end text-muted">0 Future</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { useComponentToModel } from '@mail/component_hooks/use_component_to_model';
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class AttachmentBox extends Component {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
useComponentToModel({ fieldName: 'component' });
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @returns {AttachmentBoxView|undefined}
|
||||
*/
|
||||
get attachmentBoxView() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(AttachmentBox, {
|
||||
props: { record: Object },
|
||||
template: 'mail.AttachmentBox',
|
||||
});
|
||||
|
||||
registerMessagingComponent(AttachmentBox);
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
// ------------------------------------------------------------------
|
||||
// Style
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_AttachmentBox_dashedLine {
|
||||
border-top: $border-width dashed $border-color;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.AttachmentBox" owl="1">
|
||||
<t t-if="attachmentBoxView">
|
||||
<div class="o_AttachmentBox position-relative" t-attf-class="{{ className }}" t-ref="root">
|
||||
<div class="o_AttachmentBox_title d-flex align-items-center">
|
||||
<hr class="o_AttachmentBox_dashedLine flex-grow-1"/>
|
||||
<span class="o_AttachmentBox_titleText p-3 fw-bold">
|
||||
Files
|
||||
</span>
|
||||
<hr class="o_AttachmentBox_dashedLine flex-grow-1"/>
|
||||
</div>
|
||||
<div class="o_AttachmentBox_content d-flex flex-column">
|
||||
<t t-if="attachmentBoxView.attachmentList">
|
||||
<AttachmentList
|
||||
className="'o_attachmentBox_attachmentList'"
|
||||
record="attachmentBoxView.attachmentList"
|
||||
/>
|
||||
</t>
|
||||
<button class="o_AttachmentBox_buttonAdd btn btn-link" type="button" t-on-click="attachmentBoxView.onClickAddAttachment" t-att-disabled="!attachmentBoxView.chatter.isTemporary and !attachmentBoxView.chatter.hasWriteAccess">
|
||||
<i class="fa fa-plus-square"/>
|
||||
Attach files
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class AttachmentCard extends Component {
|
||||
|
||||
/**
|
||||
* @returns {AttachmentCard}
|
||||
*/
|
||||
get attachmentCard() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(AttachmentCard, {
|
||||
props: { record: Object },
|
||||
template: 'mail.AttachmentCard',
|
||||
});
|
||||
|
||||
registerMessagingComponent(AttachmentCard);
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// ------------------------------------------------------------------
|
||||
// Layout
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_AttachmentCard:hover .o_AttachmentCard_asideItemUnlink.o-pretty {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.o_AttachmentCard_action {
|
||||
min-width: 20px;
|
||||
}
|
||||
|
||||
.o_AttachmentCard_aside {
|
||||
&:not(.o-hasMultipleActions) {
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
&.o-hasMultipleActions {
|
||||
min-width: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_AttachmentCard_asideItemUnlink.o-pretty {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
.o_AttachmentCard_details {
|
||||
min-width: 0; /* This allows the text ellipsis in the flex element */
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Style
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_AttachmentCard_image.o-attachment-viewable {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="mail.AttachmentCard" owl="1">
|
||||
<t t-if="attachmentCard">
|
||||
<div t-attf-class="{{ className }}" t-ref="root">
|
||||
<div class="o_AttachmentCard o-has-card-details d-flex rounded bg-300"
|
||||
t-att-class="{
|
||||
'o-isUploading': attachmentCard.attachment.isUploading,
|
||||
'o-viewable': attachmentCard.attachment.isViewable,
|
||||
}" t-att-title="attachmentCard.attachment.displayName ? attachmentCard.attachment.displayName : undefined" role="menu" t-att-aria-label="attachmentCard.attachment.displayName" t-att-data-id="attachmentCard.attachment.localId"
|
||||
>
|
||||
<!-- Image style-->
|
||||
<!-- o_image from mimetype.scss -->
|
||||
<div class="o_AttachmentCard_image o_image flex-shrink-0 m-1" t-on-click="attachmentCard.onClickImage" t-att-class="{'o-attachment-viewable opacity-75-hover': attachmentCard.attachment.isViewable,}" role="menuitem" aria-label="Preview" t-att-tabindex="attachmentCard.attachment.isViewable ? 0 : -1" t-att-aria-disabled="!attachmentCard.attachment.isViewable" t-att-data-mimetype="attachmentCard.attachment.mimetype">
|
||||
</div>
|
||||
<!-- Attachment details -->
|
||||
<div class="o_AttachmentCard_details d-flex justify-content-center flex-column px-1">
|
||||
<t t-if="attachmentCard.attachment.displayName">
|
||||
<div class="o_AttachmentCard_filename text-truncate">
|
||||
<t t-esc="attachmentCard.attachment.displayName"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="attachmentCard.attachment.extension">
|
||||
<small class="o_AttachmentCard_extension text-uppercase">
|
||||
<t t-esc="attachmentCard.attachment.extension"/>
|
||||
</small>
|
||||
</t>
|
||||
</div>
|
||||
<!-- Attachment aside -->
|
||||
<div class="o_AttachmentCard_aside position-relative rounded-end overflow-hidden" t-att-class="{ 'o-hasMultipleActions d-flex flex-column': attachmentCard.hasMultipleActions }">
|
||||
<!-- Uploading icon -->
|
||||
<t t-if="attachmentCard.attachment.isUploading and attachmentCard.attachmentList.composerViewOwner">
|
||||
<div class="o_AttachmentCard_asideItem o_AttachmentCard_asideItemUploading d-flex justify-content-center align-items-center w-100 h-100" title="Uploading">
|
||||
<i class="fa fa-spin fa-spinner"/>
|
||||
</div>
|
||||
</t>
|
||||
<!-- Uploaded icon -->
|
||||
<t t-if="!attachmentCard.attachment.isUploading and attachmentCard.attachmentList.composerViewOwner">
|
||||
<div class="o_AttachmentCard_asideItem o_AttachmentCard_asideItemUploaded d-flex justify-content-center align-items-center w-100 h-100 text-primary" title="Uploaded">
|
||||
<i class="fa fa-check"/>
|
||||
</div>
|
||||
</t>
|
||||
<!-- Remove button -->
|
||||
<t t-if="attachmentCard.attachment.isDeletable">
|
||||
<button class="o_AttachmentCard_asideItem o_AttachmentCard_asideItemUnlink btn top-0 justify-content-center align-items-center d-flex w-100 h-100 rounded-0" t-attf-class="{{ attachmentCard.attachmentList.composerViewOwner ? 'o-pretty position-absolute btn-primary transition-base' : 'bg-300' }}" t-on-click="attachmentCard.onClickUnlink" title="Remove">
|
||||
<i class="fa fa-trash" role="img" aria-label="Remove"/>
|
||||
</button>
|
||||
</t>
|
||||
<!-- Open link button -->
|
||||
<t t-if="attachmentCard.attachment.type === 'url'">
|
||||
<a class="o_AttachmentCard_asideItem o_AttachmentCard_asideItemOpenLink btn d-flex justify-content-center align-items-center w-100 h-100 rounded-0 bg-300" t-att-href="attachmentCard.attachment.url" target='_blank' title="Open Link">
|
||||
<i class="fa fa-external-link" role="img" aria-label="Open Link"/>
|
||||
</a>
|
||||
</t>
|
||||
<!-- Download button -->
|
||||
<t t-elif="!attachmentCard.attachmentList.composerViewOwner and !attachmentCard.attachment.isUploading">
|
||||
<button class="o_AttachmentCard_asideItem o_AttachmentCard_asideItemDownload btn d-flex justify-content-center align-items-center w-100 h-100 rounded-0 bg-300" t-on-click="attachmentCard.attachment.onClickDownload" title="Download">
|
||||
<i class="fa fa-download" role="img" aria-label="Download"/>
|
||||
</button>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { useComponentToModel } from '@mail/component_hooks/use_component_to_model';
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class AttachmentDeleteConfirm extends Component {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
useComponentToModel({ fieldName: 'component' });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {AttachmentDeleteConfirmView}
|
||||
*/
|
||||
get attachmentDeleteConfirmView() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(AttachmentDeleteConfirm, {
|
||||
props: { record: Object },
|
||||
template: 'mail.AttachmentDeleteConfirm',
|
||||
});
|
||||
|
||||
registerMessagingComponent(AttachmentDeleteConfirm);
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="mail.AttachmentDeleteConfirm" owl="1">
|
||||
<t t-if="attachmentDeleteConfirmView">
|
||||
<div class="o_AttachmentDeleteConfirm card bg-view" t-attf-class="{{ className }}" t-ref="root">
|
||||
<h4 class="m-3">Confirmation</h4>
|
||||
<hr class="mt-0 mb-3"/>
|
||||
<p class="o_AttachmentDeleteConfirm_mainText mx-3 mb-3" t-esc="attachmentDeleteConfirmView.body"/>
|
||||
<hr class="mt-0 mb-3"/>
|
||||
<div class="o_AttachmentDeleteConfirm_buttons mx-3 mb-3">
|
||||
<button class="o_AttachmentDeleteConfirm_confirmButton btn btn-primary me-2" t-on-click="attachmentDeleteConfirmView.onClickOk">Ok</button>
|
||||
<button class="o_AttachmentDeleteConfirm_cancelButton btn btn-secondary me-2" t-on-click="attachmentDeleteConfirmView.onClickCancel">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class AttachmentImage extends Component {
|
||||
|
||||
/**
|
||||
* @returns {AttachmentImage}
|
||||
*/
|
||||
get attachmentImage() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(AttachmentImage, {
|
||||
props: { record: Object },
|
||||
template: 'mail.AttachmentImage',
|
||||
});
|
||||
|
||||
registerMessagingComponent(AttachmentImage);
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// ------------------------------------------------------------------
|
||||
// Layout
|
||||
// ------------------------------------------------------------------
|
||||
.o_AttachmentImage {
|
||||
min-width: 20px;
|
||||
min-height: 20px;
|
||||
|
||||
img {
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Style
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_AttachmentImage {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="mail.AttachmentImage" owl="1">
|
||||
<t t-if="attachmentImage">
|
||||
<div t-attf-class="{{ className }}" role="menu" t-att-aria-label="attachmentImage.attachment.displayName" t-ref="root">
|
||||
<div class="o_AttachmentImage d-flex position-relative flex-shrink-0"
|
||||
t-att-class="{
|
||||
'o-isUploading': attachmentImage.attachment.isUploading,
|
||||
}"
|
||||
t-att-title="attachmentImage.attachment.displayName ? attachmentImage.attachment.displayName : undefined"
|
||||
t-att-data-id="attachmentImage.attachment.localId"
|
||||
tabindex="0"
|
||||
aria-label="View image"
|
||||
role="menuitem"
|
||||
t-on-click="attachmentImage.onClickImage"
|
||||
t-att-data-mimetype="attachmentImage.attachment.mimetype"
|
||||
>
|
||||
<t t-if="!attachmentImage.attachment.isUploading">
|
||||
<img class="img img-fluid my-0 mx-auto" t-att-src="attachmentImage.imageUrl" t-att-alt="attachmentImage.attachment.name" t-attf-style="max-width: min(100%, {{ attachmentImage.width }}px); max-height: {{ attachmentImage.height }}px;"/>
|
||||
</t>
|
||||
<t t-if="attachmentImage.attachment.isUploading">
|
||||
<div class="o_AttachmentImageUploading position-absolute top-0 bottom-0 start-0 end-0 d-flex align-items-center justify-content-center" title="Uploading">
|
||||
<i class="fa fa-spin fa-spinner"/>
|
||||
</div>
|
||||
</t>
|
||||
<div class="o_AttachmentImage_imageOverlay position-absolute top-0 bottom-0 start-0 end-0 p-2 text-white opacity-0 opacity-100-hover d-flex align-items-end flax-wrap flex-column">
|
||||
<div t-if="attachmentImage.attachment.isDeletable" class="o_AttachmentImage_action o_AttachmentImage_actionUnlink btn btn-sm btn-dark rounded opacity-75 opacity-100-hover" t-att-class="{'o-pretty': attachmentImage.attachmentList.composerViewOwner}" tabindex="0" aria-label="Remove" role="menuitem" t-on-click="attachmentImage.onClickUnlink" title="Remove">
|
||||
<i class="fa fa-trash"/>
|
||||
</div>
|
||||
<div t-if="attachmentImage.hasDownloadButton" class="o_AttachmentImage_action o_AttachmentImage_actionDownload btn btn-sm btn-dark rounded opacity-75 opacity-100-hover mt-auto" t-on-click="attachmentImage.onClickDownload" title="Download">
|
||||
<i class="fa fa-download"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class AttachmentList extends Component {
|
||||
|
||||
/**
|
||||
* @returns {AttachmentList}
|
||||
*/
|
||||
get attachmentList() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(AttachmentList, {
|
||||
props: { record: Object },
|
||||
template: 'mail.AttachmentList',
|
||||
});
|
||||
|
||||
registerMessagingComponent(AttachmentList);
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.o_AttachmentList {
|
||||
max-width: var(--Chatter-max-width, none);
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.AttachmentList" owl="1">
|
||||
<t t-if="attachmentList">
|
||||
<div class="o_AttachmentList d-flex flex-column mt-1" t-att-class="{ 'me-2 pe-4': attachmentList.isInChatWindowAndIsAlignedLeft and !attachmentList.composerViewOwner, 'ms-2 ps-4': attachmentList.isInChatWindowAndIsAlignedRight and !attachmentList.composerViewOwner }" t-attf-class="{{ className }}" t-ref="root">
|
||||
<div t-if="attachmentList.attachmentImages.length > 0" class="o_AttachmentList_partialList o_AttachmentList_partialListImages d-flex flex-grow-1 flex-wrap" t-att-class="{ 'justify-content-end': attachmentList.isInChatWindowAndIsAlignedRight and !attachmentList.composerViewOwner }">
|
||||
<t t-foreach="attachmentList.attachmentImages" t-as="attachmentImage" t-key="attachmentImage.localId">
|
||||
<AttachmentImage className="'o_AttachmentList_attachment mw-100 mb-1'" classNameObj="{ 'ms-1': attachmentList.isInChatWindowAndIsAlignedRight, 'me-1': !attachmentList.isInChatWindowAndIsAlignedRight }" record="attachmentImage"/>
|
||||
</t>
|
||||
</div>
|
||||
<div t-if="attachmentList.attachmentCards.length > 0" class="o_AttachmentList_partialList o_AttachmentList_partialListNonImages d-flex flex-grow-1 flex-wrap mt-1" t-att-class="{ 'justify-content-end': attachmentList.isInChatWindowAndIsAlignedRight and !attachmentList.composerViewOwner }">
|
||||
<t t-foreach="attachmentList.attachmentCards" t-as="attachmentCard" t-key="attachmentCard.localId">
|
||||
<AttachmentCard className="'o_AttachmentList_attachment mw-100 mb-1'" classNameObj="{ 'ms-1': attachmentList.isInChatWindowAndIsAlignedRight, 'me-1': !attachmentList.isInChatWindowAndIsAlignedRight }" record="attachmentCard"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
// = Attachment Viewer View
|
||||
// ============================================================================
|
||||
// No CSS hacks, variables overrides only
|
||||
|
||||
.o_AttachmentViewer_toolbarButton {
|
||||
--AttachmentViewer_toolbarButton-background-color: #{$o-gray-200};
|
||||
}
|
||||
|
|
@ -0,0 +1,359 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { useComponentToModel } from '@mail/component_hooks/use_component_to_model';
|
||||
import { useRefs } from '@mail/component_hooks/use_refs';
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
import { hidePDFJSButtons } from '@web/legacy/js/libs/pdfjs';
|
||||
|
||||
const { Component, onMounted, onPatched, onWillUnmount, useRef } = owl;
|
||||
|
||||
const MIN_SCALE = 0.5;
|
||||
const SCROLL_ZOOM_STEP = 0.1;
|
||||
const ZOOM_STEP = 0.5;
|
||||
|
||||
export class AttachmentViewer extends Component {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
useComponentToModel({ fieldName: 'component' });
|
||||
this.MIN_SCALE = MIN_SCALE;
|
||||
/**
|
||||
* Used to ensure that the ref is always up to date, which seems to be needed if the element
|
||||
* has a t-key, which was added to force the rendering of a new element when the src of the image changes.
|
||||
* This was made to remove the display of the previous image as soon as the src changes.
|
||||
*/
|
||||
this._getRefs = useRefs();
|
||||
/**
|
||||
* Reference of the zoomer node. Useful to apply translate
|
||||
* transformation on image visualisation.
|
||||
*/
|
||||
this._zoomerRef = useRef('zoomer');
|
||||
/**
|
||||
* Reference of the IFRAME node when the attachment is a PDF.
|
||||
*/
|
||||
this._iframeViewerPdfRef = useRef('iframeViewerPdf');
|
||||
/**
|
||||
* Tracked translate transformations on image visualisation. This is
|
||||
* not observed for re-rendering because they are used to compute zoomer
|
||||
* style, and this is changed directly on zoomer for performance
|
||||
* reasons (overhead of making vdom is too significant for each mouse
|
||||
* position changes while dragging)
|
||||
*/
|
||||
this._translate = { x: 0, y: 0, dx: 0, dy: 0 };
|
||||
this._onClickGlobal = this._onClickGlobal.bind(this);
|
||||
onMounted(() => this._mounted());
|
||||
onPatched(() => this._patched());
|
||||
onWillUnmount(() => this._willUnmount());
|
||||
}
|
||||
|
||||
_mounted() {
|
||||
if (!this.root.el) {
|
||||
return;
|
||||
}
|
||||
this.root.el.focus();
|
||||
this._handleImageLoad();
|
||||
this._hideUnwantedPdfJsButtons();
|
||||
document.addEventListener('click', this._onClickGlobal);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a new image is displayed, show a spinner until it is loaded.
|
||||
*/
|
||||
_patched() {
|
||||
this._handleImageLoad();
|
||||
this._hideUnwantedPdfJsButtons();
|
||||
}
|
||||
|
||||
_willUnmount() {
|
||||
document.removeEventListener('click', this._onClickGlobal);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @returns {AttachmentViewer}
|
||||
*/
|
||||
get attachmentViewer() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Determine whether the current image is rendered for the 1st time, and if
|
||||
* that's the case, display a spinner until loaded.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_handleImageLoad() {
|
||||
if (!this.attachmentViewer.exists() || !this.attachmentViewer.attachmentViewerViewable) {
|
||||
return;
|
||||
}
|
||||
const refs = this._getRefs();
|
||||
const image = refs[`image_${this.attachmentViewer.attachmentViewerViewable.localId}`];
|
||||
if (
|
||||
this.attachmentViewer.attachmentViewerViewable.isImage &&
|
||||
(!image || !image.complete)
|
||||
) {
|
||||
this.attachmentViewer.update({ isImageLoading: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see 'hidePDFJSButtons'
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_hideUnwantedPdfJsButtons() {
|
||||
if (this._iframeViewerPdfRef.el) {
|
||||
hidePDFJSButtons(this._iframeViewerPdfRef.el);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop dragging interaction of the user.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_stopDragging() {
|
||||
this.attachmentViewer.update({ isDragging: false });
|
||||
this._translate.x += this._translate.dx;
|
||||
this._translate.y += this._translate.dy;
|
||||
this._translate.dx = 0;
|
||||
this._translate.dy = 0;
|
||||
this._updateZoomerStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the style of the zoomer based on translate transformation. Changes
|
||||
* are directly applied on zoomer, instead of triggering re-render and
|
||||
* defining them in the template, for performance reasons.
|
||||
*
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_updateZoomerStyle() {
|
||||
const attachmentViewer = this.attachmentViewer;
|
||||
const refs = this._getRefs();
|
||||
const image = refs[`image_${this.attachmentViewer.attachmentViewerViewable.localId}`];
|
||||
// some actions are too fast that sometimes this function is called
|
||||
// before setting the refs, so we just do nothing when image is null
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
const tx = image.offsetWidth * attachmentViewer.scale > this._zoomerRef.el.offsetWidth
|
||||
? this._translate.x + this._translate.dx
|
||||
: 0;
|
||||
const ty = image.offsetHeight * attachmentViewer.scale > this._zoomerRef.el.offsetHeight
|
||||
? this._translate.y + this._translate.dy
|
||||
: 0;
|
||||
if (tx === 0) {
|
||||
this._translate.x = 0;
|
||||
}
|
||||
if (ty === 0) {
|
||||
this._translate.y = 0;
|
||||
}
|
||||
this._zoomerRef.el.style = `transform: ` +
|
||||
`translate(${tx}px, ${ty}px)`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zoom in the image.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} [param0={}]
|
||||
* @param {boolean} [param0.scroll=false]
|
||||
*/
|
||||
_zoomIn({ scroll = false } = {}) {
|
||||
this.attachmentViewer.update({
|
||||
scale: this.attachmentViewer.scale + (scroll ? SCROLL_ZOOM_STEP : ZOOM_STEP),
|
||||
});
|
||||
this._updateZoomerStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Zoom out the image.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} [param0={}]
|
||||
* @param {boolean} [param0.scroll=false]
|
||||
*/
|
||||
_zoomOut({ scroll = false } = {}) {
|
||||
if (this.attachmentViewer.scale === MIN_SCALE) {
|
||||
return;
|
||||
}
|
||||
const unflooredAdaptedScale = (
|
||||
this.attachmentViewer.scale -
|
||||
(scroll ? SCROLL_ZOOM_STEP : ZOOM_STEP)
|
||||
);
|
||||
this.attachmentViewer.update({
|
||||
scale: Math.max(MIN_SCALE, unflooredAdaptedScale),
|
||||
});
|
||||
this._updateZoomerStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the zoom scale of the image.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_zoomReset() {
|
||||
this.attachmentViewer.update({ scale: 1 });
|
||||
this._updateZoomerStyle();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickGlobal(ev) {
|
||||
if (!this.attachmentViewer.exists()) {
|
||||
return;
|
||||
}
|
||||
if (!this.attachmentViewer.isDragging) {
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation();
|
||||
this._stopDragging();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when clicking on zoom in icon.
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickZoomIn(ev) {
|
||||
ev.stopPropagation();
|
||||
this._zoomIn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when clicking on zoom out icon.
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickZoomOut(ev) {
|
||||
ev.stopPropagation();
|
||||
this._zoomOut();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when clicking on reset zoom icon.
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickZoomReset(ev) {
|
||||
ev.stopPropagation();
|
||||
this._zoomReset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {KeyboardEvent} ev
|
||||
*/
|
||||
_onKeydown(ev) {
|
||||
switch (ev.key) {
|
||||
case 'ArrowRight':
|
||||
this.attachmentViewer.next();
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
this.attachmentViewer.previous();
|
||||
break;
|
||||
case 'Escape':
|
||||
this.attachmentViewer.close();
|
||||
break;
|
||||
case 'q':
|
||||
this.attachmentViewer.close();
|
||||
break;
|
||||
case 'r':
|
||||
this.attachmentViewer.rotate();
|
||||
break;
|
||||
case '+':
|
||||
this._zoomIn();
|
||||
break;
|
||||
case '-':
|
||||
this._zoomOut();
|
||||
break;
|
||||
case '0':
|
||||
this._zoomReset();
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {DragEvent} ev
|
||||
*/
|
||||
_onMousedownImage(ev) {
|
||||
if (!this.attachmentViewer.exists()) {
|
||||
return;
|
||||
}
|
||||
if (this.attachmentViewer.isDragging) {
|
||||
return;
|
||||
}
|
||||
if (ev.button !== 0) {
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation();
|
||||
this.attachmentViewer.update({ isDragging: true });
|
||||
this._dragstartX = ev.clientX;
|
||||
this._dragstartY = ev.clientY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {DragEvent}
|
||||
*/
|
||||
_onMousemoveView(ev) {
|
||||
if (!this.attachmentViewer.exists()) {
|
||||
return;
|
||||
}
|
||||
if (!this.attachmentViewer.isDragging) {
|
||||
return;
|
||||
}
|
||||
this._translate.dx = ev.clientX - this._dragstartX;
|
||||
this._translate.dy = ev.clientY - this._dragstartY;
|
||||
this._updateZoomerStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onWheelImage(ev) {
|
||||
ev.stopPropagation();
|
||||
if (!this.root.el) {
|
||||
return;
|
||||
}
|
||||
if (ev.deltaY > 0) {
|
||||
this._zoomOut({ scroll: true });
|
||||
} else {
|
||||
this._zoomIn({ scroll: true });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(AttachmentViewer, {
|
||||
props: { record: Object },
|
||||
template: 'mail.AttachmentViewer',
|
||||
});
|
||||
|
||||
registerMessagingComponent(AttachmentViewer);
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
// ------------------------------------------------------------------
|
||||
// Layout
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_AttachmentViewer {
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.o_AttachmentViewer_buttonNavigation {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.o_AttachmentViewer_buttonNavigationNextIcon {
|
||||
margin: 1px 0 0 1px; // not correctly centered for some reasons
|
||||
}
|
||||
|
||||
.o_AttachmentViewer_buttonNavigationPreviousIcon {
|
||||
margin: 1px 1px 0 0; // not correctly centered for some reasons
|
||||
}
|
||||
|
||||
.o_AttachmentViewer_header {
|
||||
height: $o-navbar-height;
|
||||
}
|
||||
|
||||
.o_AttachmentViewer_main {
|
||||
z-index: -1;
|
||||
padding: ($o-navbar-height * 1.125) 0;
|
||||
}
|
||||
|
||||
.o_AttachmentViewer_name {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.o_AttachmentViewer_zoomer {
|
||||
padding: ($o-navbar-height * 1.125) 0;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Style
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_AttachmentViewer {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.o_AttachmentViewer_headerItemButton:hover {
|
||||
background-color: rgba($white, 0.1);
|
||||
color: lighten($gray-400, 15%);
|
||||
}
|
||||
|
||||
.o_AttachmentViewer_toolbarButton {
|
||||
background-color: var(--AttachmentViewer_toolbarButton-background-color, #{$o-gray-800});
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(1.3);
|
||||
}
|
||||
}
|
||||
|
||||
.o_AttachmentViewer_view {
|
||||
background-color: #000000;
|
||||
box-shadow: 0 0 40px #000000;
|
||||
outline: none;
|
||||
|
||||
&.o_AttachmentViewer_isText {
|
||||
background: $white;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.AttachmentViewer" owl="1">
|
||||
<t t-if="attachmentViewer">
|
||||
<div class="o_AttachmentViewer flex-column align-items-center d-flex w-100 h-100" t-attf-class="{{ className }}" t-on-click="attachmentViewer.onClick" t-on-keydown="_onKeydown" tabindex="0" t-ref="root">
|
||||
<div class="o_AttachmentViewer_header d-flex w-100 bg-black-75 text-400" t-on-click="attachmentViewer.onClickHeader">
|
||||
<t t-if="attachmentViewer.attachmentViewerViewable.isViewable">
|
||||
<div class="o_AttachmentViewer_headerItem o_AttachmentViewer_icon d-flex align-items-center ms-4 me-2">
|
||||
<t t-if="attachmentViewer.attachmentViewerViewable.isImage">
|
||||
<i class="fa fa-picture-o" role="img" title="Image"/>
|
||||
</t>
|
||||
<t t-if="attachmentViewer.attachmentViewerViewable.isPdf">
|
||||
<i class="fa fa-file-text" role="img" title="PDF file"/>
|
||||
</t>
|
||||
<t t-if="attachmentViewer.attachmentViewerViewable.isText">
|
||||
<i class="fa fa-file-text" role="img" title="Text file"/>
|
||||
</t>
|
||||
<t t-if="attachmentViewer.attachmentViewerViewable.isVideo">
|
||||
<i class="fa fa-video-camera" role="img" title="Video"/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<div class="o_AttachmentViewer_headerItem o_AttachmentViewer_name d-flex align-items-center mx-2">
|
||||
<span class="o_AttachmentViewer_nameText text-truncate" t-esc="attachmentViewer.attachmentViewerViewable.displayName"/>
|
||||
</div>
|
||||
<div class="flex-grow-1"/>
|
||||
<div class="o_AttachmentViewer_buttonDownload o_AttachmentViewer_headerItem o_AttachmentViewer_headerItemButton d-flex align-items-center px-3 cursor-pointer" t-on-click="attachmentViewer.onClickDownload" role="button" title="Download">
|
||||
<i class="o_AttachmentViewer_headerItemButtonIcon fa fa-download fa-fw" t-att-class="{ 'o-hasLabel me-2': messaging.device.sizeClass > messaging.device.sizeClasses.MD }" role="img"/>
|
||||
<t t-if="messaging.device.sizeClass > messaging.device.sizeClasses.MD">
|
||||
<span>Download</span>
|
||||
</t>
|
||||
</div>
|
||||
<div class="o_AttachmentViewer_headerItem o_AttachmentViewer_headerItemButton o_AttachmentViewer_headerItemButtonClose d-flex align-items-center mb-0 px-3 h4 text-reset cursor-pointer" t-on-click="attachmentViewer.onClickClose" role="button" title="Close (Esc)" aria-label="Close">
|
||||
<i class="fa fa-fw fa-times" role="img"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_AttachmentViewer_main position-absolute top-0 bottom-0 start-0 end-0 align-items-center justify-content-center d-flex" t-att-class="{ 'o_with_img overflow-hidden': attachmentViewer.attachmentViewerViewable.isImage }" t-on-mousemove="_onMousemoveView">
|
||||
<t t-if="attachmentViewer.attachmentViewerViewable.isImage">
|
||||
<div class="o_AttachmentViewer_zoomer position-absolute align-items-center justify-content-center d-flex w-100 h-100" t-ref="zoomer">
|
||||
<t t-if="attachmentViewer.isImageLoading">
|
||||
<div class="o_AttachmentViewer_loading position-absolute">
|
||||
<i class="fa fa-3x fa-circle-o-notch fa-fw fa-spin text-white" role="img" title="Loading"/>
|
||||
</div>
|
||||
</t>
|
||||
<img class="o_AttachmentViewer_view o_AttachmentViewer_viewImage mw-100 mh-100 transition-base" t-on-click="attachmentViewer.onClickImage" t-on-mousedown="_onMousedownImage" t-on-wheel="_onWheelImage" t-on-load="attachmentViewer.onLoadImage" t-att-src="attachmentViewer.attachmentViewerViewable.imageUrl" t-att-style="attachmentViewer.imageStyle" draggable="false" alt="Viewer" t-key="'image_' + attachmentViewer.attachmentViewerViewable.localId" t-ref="image_{{ attachmentViewer.attachmentViewerViewable.localId }}"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="attachmentViewer.attachmentViewerViewable.isPdf">
|
||||
<iframe class="o_AttachmentViewer_view o_AttachmentViewer_viewIframe o_AttachmentViewer_viewPdf w-75 h-100 border-0" t-ref="iframeViewerPdf" t-att-class="{ 'w-100': messaging.device.isSmall }" t-att-src="attachmentViewer.attachmentViewerViewable.defaultSource"/>
|
||||
</t>
|
||||
<t t-if="attachmentViewer.attachmentViewerViewable.isText">
|
||||
<iframe class="o_AttachmentViewer_view o_AttachmentViewer_viewIframe o_AttachmentViewer_isText o_text w-75 h-100 border-0" t-att-src="attachmentViewer.attachmentViewerViewable.defaultSource"/>
|
||||
</t>
|
||||
<t t-if="attachmentViewer.attachmentViewerViewable.isUrlYoutube">
|
||||
<iframe allow="autoplay; encrypted-media" class="o_AttachmentViewer_view o_AttachmentViewer_viewIframe o_AttachmentViewer_youtube w-75 h-100 border-0" t-att-src="attachmentViewer.attachmentViewerViewable.defaultSource" height="315" width="560"/>
|
||||
</t>
|
||||
<t t-if="attachmentViewer.attachmentViewerViewable.isVideo">
|
||||
<video class="o_AttachmentViewer_view o_AttachmentViewer_viewVideo w-75 h-75" t-att-class="{ 'w-100 h-100': messaging.device.isSmall }" t-on-click="attachmentViewer.onClickVideo" controls="controls">
|
||||
<source t-att-data-type="attachmentViewer.attachmentViewerViewable.mimetype" t-att-src="attachmentViewer.attachmentViewerViewable.defaultSource"/>
|
||||
</video>
|
||||
</t>
|
||||
</div>
|
||||
<t t-if="attachmentViewer.attachmentViewerViewable.isImage">
|
||||
<div class="o_AttachmentViewer_toolbar position-absolute bottom-0 d-flex" role="toolbar">
|
||||
<div class="o_AttachmentViewer_toolbarButton p-3 rounded-0" t-on-click="_onClickZoomIn" title="Zoom In (+)" role="button">
|
||||
<i class="fa fa-fw fa-plus" role="img"/>
|
||||
</div>
|
||||
<div class="o_AttachmentViewer_toolbarButton p-3 rounded-0" t-att-class="{ 'o_disabled opacity-50': attachmentViewer.scale === 1 }" t-on-click="_onClickZoomReset" role="button" title="Reset Zoom (0)">
|
||||
<i class="fa fa-fw fa-search" role="img"/>
|
||||
</div>
|
||||
<div class="o_AttachmentViewer_toolbarButton p-3 rounded-0" t-att-class="{ 'o_disabled opacity-50': attachmentViewer.scale === MIN_SCALE }" t-on-click="_onClickZoomOut" title="Zoom Out (-)" role="button">
|
||||
<i class="fa fa-fw fa-minus" role="img"/>
|
||||
</div>
|
||||
<div class="o_AttachmentViewer_toolbarButton p-3 rounded-0" t-on-click="attachmentViewer.onClickRotate" title="Rotate (r)" role="button">
|
||||
<i class="fa fa-fw fa-repeat" role="img"/>
|
||||
</div>
|
||||
<div class="o_AttachmentViewer_toolbarButton p-3 rounded-0" t-on-click="attachmentViewer.onClickPrint" title="Print" role="button">
|
||||
<i class="fa fa-fw fa-print" role="img"/>
|
||||
</div>
|
||||
<div class="o_AttachmentViewer_buttonDownload o_AttachmentViewer_toolbarButton p-3 rounded-0 cursor-pointer" t-on-click="attachmentViewer.onClickDownload" title="Download" role="button">
|
||||
<i class="fa fa-download fa-fw" role="img"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="attachmentViewer.attachmentViewerViewables.length > 1">
|
||||
<div class="o_AttachmentViewer_buttonNavigation o_AttachmentViewer_buttonNavigationPrevious o_AttachmentViewer_buttonNavigationPreviousIcon position-absolute top-0 bottom-0 start-0 align-items-center justify-content-center d-flex my-auto ms-3 rounded-circle bg-dark text-white" t-on-click="attachmentViewer.onClickPrevious" title="Previous (Left-Arrow)" role="button">
|
||||
<span class="fa fa-chevron-left" role="img"/>
|
||||
</div>
|
||||
<div class="o_AttachmentViewer_buttonNavigation o_AttachmentViewer_buttonNavigationNext o_AttachmentViewer_buttonNavigationNextIcon position-absolute top-0 bottom-0 end-0 align-items-center justify-content-center d-flex my-auto me-3 rounded-circle bg-dark text-white" t-on-click="attachmentViewer.onClickNext" title="Next (Right-Arrow)" role="button">
|
||||
<span class="fa fa-chevron-right" role="img"/>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { useComponentToModel } from '@mail/component_hooks/use_component_to_model';
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component, onMounted, onWillUnmount } = owl;
|
||||
|
||||
export class AutocompleteInputView extends Component {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
useComponentToModel({ fieldName: 'component' });
|
||||
onMounted(() => this._mounted());
|
||||
onWillUnmount(() => this._willUnmount());
|
||||
}
|
||||
|
||||
_mounted() {
|
||||
if (!this.root.el) {
|
||||
return;
|
||||
}
|
||||
if (this.autocompleteInputView.isFocusOnMount) {
|
||||
this.root.el.focus();
|
||||
}
|
||||
|
||||
const args = {
|
||||
autoFocus: true,
|
||||
select: (ev, ui) => {
|
||||
if (this.autocompleteInputView) {
|
||||
this.autocompleteInputView.onSelect(ev, ui);
|
||||
}
|
||||
},
|
||||
source: (req, res) => {
|
||||
if (this.autocompleteInputView) {
|
||||
this.autocompleteInputView.onSource(req, res);
|
||||
}
|
||||
},
|
||||
html: this.autocompleteInputView.isHtml,
|
||||
};
|
||||
|
||||
if (this.autocompleteInputView.customClass) {
|
||||
args.classes = { 'ui-autocomplete': this.autocompleteInputView.customClass };
|
||||
}
|
||||
|
||||
const autoCompleteElem = $(this.root.el).autocomplete(args);
|
||||
// Resize the autocomplete dropdown options to handle the long strings
|
||||
// By setting the width of dropdown based on the width of the input element.
|
||||
autoCompleteElem.data('ui-autocomplete')._resizeMenu = function () {
|
||||
const ul = this.menu.element;
|
||||
ul.outerWidth(this.element.outerWidth());
|
||||
};
|
||||
}
|
||||
|
||||
_willUnmount() {
|
||||
if (!this.root.el) {
|
||||
return;
|
||||
}
|
||||
$(this.root.el).autocomplete('destroy');
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {AutocompleteInputView}
|
||||
*/
|
||||
get autocompleteInputView() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(AutocompleteInputView, {
|
||||
props: { record: Object },
|
||||
template: 'mail.AutocompleteInputView',
|
||||
});
|
||||
|
||||
registerMessagingComponent(AutocompleteInputView);
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.AutocompleteInputView" owl="1">
|
||||
<input class="o_AutocompleteInputView form-control" t-attf-class="{{ className }}" t-on-blur="autocompleteInputView.onBlur" t-on-focusin="autocompleteInputView.onFocusin" t-on-keydown="autocompleteInputView.onKeydown" t-att-placeholder="autocompleteInputView.placeholder" t-ref="root"/>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
// = Call Action List View
|
||||
// ============================================================================
|
||||
// No CSS hacks, variables overrides only
|
||||
|
||||
.o_CallActionList_button:not(.btn-danger) {
|
||||
--CallActionList_button-background-color: #{$o-gray-200};
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { useRefToModel } from '@mail/component_hooks/use_ref_to_model';
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class CallActionList extends Component {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
useRefToModel({ fieldName: 'moreButtonRef', refName: 'moreButton' });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {CallActionListView}
|
||||
*/
|
||||
get callActionListView() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(CallActionList, {
|
||||
props: { record: Object },
|
||||
template: 'mail.CallActionList',
|
||||
});
|
||||
|
||||
registerMessagingComponent(CallActionList);
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
.o_CallActionList_button:not(.btn-danger) {
|
||||
background-color: var(--CallActionList_button-background-color, #{$o-gray-800});
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.CallActionList" owl="1">
|
||||
<t t-if="callActionListView">
|
||||
<div class="o_CallActionList d-flex justify-content-between" t-attf-class="{{ className }}" t-ref="root">
|
||||
<div class="o_CallActionList_buttons d-flex align-items-center flex-wrap">
|
||||
<t t-if="callActionListView.thread.rtc and messaging.rtc.currentRtcSession">
|
||||
<button class="o_CallActionList_button btn d-flex m-1 border-0 rounded-circle shadow-none opacity-100 opacity-75-hover"
|
||||
t-att-class="{ 'o-isActive': !messaging.rtc.currentRtcSession.isMute, 'o-isSmall p-2': callActionListView.isSmall, 'p-3': !callActionListView.isSmall }"
|
||||
t-att-aria-label="callActionListView.microphoneButtonTitle"
|
||||
t-att-title="callActionListView.microphoneButtonTitle"
|
||||
t-on-click="callActionListView.onClickMicrophone">
|
||||
<div class="o_CallActionList_buttonIconWrapper fa-stack" t-att-class="{ 'o-isSmall': callActionListView.isSmall }">
|
||||
<i class="fa fa-stack-1x" t-att-class="{
|
||||
'fa-lg': !callActionListView.isSmall,
|
||||
'fa-microphone': !messaging.rtc.currentRtcSession.isMute,
|
||||
'fa-microphone-slash': messaging.rtc.currentRtcSession.isMute,
|
||||
'text-danger': messaging.rtc.currentRtcSession.isMute,
|
||||
}"/>
|
||||
</div>
|
||||
</button>
|
||||
<button class="o_CallActionList_button btn d-flex m-1 border-0 rounded-circle shadow-none opacity-100 opacity-75-hover"
|
||||
t-att-class="{ 'o-isActive': !messaging.rtc.currentRtcSession.isDeaf, 'o-isSmall p-2': callActionListView.isSmall, 'p-3': !callActionListView.isSmall }"
|
||||
t-att-aria-label="callActionListView.headphoneButtonTitle"
|
||||
t-att-title="callActionListView.headphoneButtonTitle"
|
||||
t-on-click="callActionListView.onClickDeafen">
|
||||
<div class="o_CallActionList_buttonIconWrapper fa-stack" t-att-class="{ 'o-isSmall': callActionListView.isSmall }">
|
||||
<i class="fa fa-stack-1x" t-att-class="{
|
||||
'fa-lg': !callActionListView.isSmall,
|
||||
'fa-headphones': !messaging.rtc.currentRtcSession.isDeaf,
|
||||
'fa-deaf': messaging.rtc.currentRtcSession.isDeaf,
|
||||
'text-danger': messaging.rtc.currentRtcSession.isDeaf,
|
||||
}"/>
|
||||
</div>
|
||||
</button>
|
||||
<button class="o_CallActionList_button o_CallActionList_videoButton btn d-flex m-1 border-0 rounded-circle shadow-none opacity-100 opacity-75-hover"
|
||||
t-att-class="{
|
||||
'o-isActive': messaging.rtc.sendUserVideo,
|
||||
'o-isSmall p-2': callActionListView.isSmall,
|
||||
'p-3': !callActionListView.isSmall,
|
||||
}"
|
||||
t-att-aria-label="callActionListView.cameraButtonTitle"
|
||||
t-att-title="callActionListView.cameraButtonTitle"
|
||||
t-on-click="callActionListView.onClickCamera">
|
||||
<div class="o_CallActionList_buttonIconWrapper fa-stack" t-att-class="{ 'o-isSmall': callActionListView.isSmall }">
|
||||
<i class="fa fa-video-camera fa-stack-1x" t-att-class="{ 'fa-lg': !callActionListView.isSmall, 'text-success': messaging.rtc.sendUserVideo }"/>
|
||||
</div>
|
||||
</button>
|
||||
<t t-if="!messaging.device.isMobileDevice">
|
||||
<button class="o_CallActionList_button o_CallActionList_videoButton btn d-flex m-1 border-0 rounded-circle shadow-none opacity-100 opacity-75-hover"
|
||||
t-att-class="{
|
||||
'o-isActive': messaging.rtc.sendDisplay,
|
||||
'o-isSmall p-2': callActionListView.isSmall,
|
||||
'p-3': !callActionListView.isSmall,
|
||||
}"
|
||||
t-att-aria-label="callActionListView.screenSharingButtonTitle"
|
||||
t-att-title="callActionListView.screenSharingButtonTitle"
|
||||
t-on-click="callActionListView.onClickScreen">
|
||||
<div class="o_CallActionList_buttonIconWrapper fa-stack" t-att-class="{ 'o-isSmall': callActionListView.isSmall }">
|
||||
<i class="fa fa-desktop fa-stack-1x" t-att-class="{ 'fa-lg': !callActionListView.isSmall, 'text-success': messaging.rtc.sendDisplay }"/>
|
||||
</div>
|
||||
</button>
|
||||
</t>
|
||||
<t t-if="!callActionListView.callView.isFullScreen">
|
||||
<button class="o_CallActionList_button btn d-flex m-1 border-0 rounded-circle shadow-none opacity-100 opacity-75-hover"
|
||||
aria-label="Activate Full Screen"
|
||||
title="Activate Full Screen"
|
||||
t-att-class="{ 'o-isSmall p-2': callActionListView.isSmall, 'p-3': !callActionListView.isSmall }"
|
||||
t-on-click="callActionListView.callView.activateFullScreen"
|
||||
>
|
||||
<div class="o_CallActionList_buttonIconWrapper fa-stack" t-att-class="{ 'o-isSmall': callActionListView.isSmall }">
|
||||
<i class="fa fa-arrows-alt fa-stack-1x" t-att-class="{ 'fa-lg': !callActionListView.isSmall }"/>
|
||||
</div>
|
||||
</button>
|
||||
</t>
|
||||
<t t-if="callActionListView.callView.isFullScreen">
|
||||
<button class="o_CallActionList_button btn d-flex m-1 border-0 rounded-circle shadow-none opacity-100 opacity-75-hover"
|
||||
aria-label="Deactivate Full Screen"
|
||||
title="Deactivate Full Screen"
|
||||
t-att-class="{ 'o-isSmall p-2': callActionListView.isSmall, 'p-3': !callActionListView.isSmall }"
|
||||
t-on-click="callActionListView.callView.deactivateFullScreen"
|
||||
>
|
||||
<div class="o_CallActionList_buttonIconWrapper fa-stack" t-att-class="{ 'o-isSmall': callActionListView.isSmall }">
|
||||
<i class="fa fa-compress fa-stack-1x" t-att-class="{ 'fa-lg': !callActionListView.isSmall }"/>
|
||||
</div>
|
||||
</button>
|
||||
</t>
|
||||
<t t-if="messaging.modelManager.isDebug">
|
||||
<button class="o_CallActionList_button btn d-flex m-1 border-0 rounded-circle shadow-none opacity-100 opacity-75-hover"
|
||||
aria-label="More"
|
||||
title="More"
|
||||
t-att-class="{ 'o-isSmall p-2': callActionListView.isSmall, 'p-3': !callActionListView.isSmall }"
|
||||
t-on-click="callActionListView.onClickMore"
|
||||
t-ref="moreButton"
|
||||
>
|
||||
<div class="o_CallActionList_buttonIconWrapper fa-stack" t-att-class="{ 'o-isSmall': callActionListView.isSmall }">
|
||||
<i class="fa fa-ellipsis-h fa-stack-1x" t-att-class="{ 'fa-lg': !callActionListView.isSmall }"/>
|
||||
</div>
|
||||
</button>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="!callActionListView.thread">
|
||||
<button class="o_CallActionList_button o_CallActionList_callToggle btn btn-success d-flex m-1 border-0 rounded-circle shadow-none"
|
||||
t-att-class="{ 'o-isSmall p-2': callActionListView.isSmall, 'p-3': !callActionListView.isSmall }"
|
||||
aria-label="Join Video Call"
|
||||
title="Join Video Call"
|
||||
t-att-disabled="callActionListView.thread.hasPendingRtcRequest"
|
||||
t-on-click="callActionListView.onClickToggleVideoCall">
|
||||
<div class="o_CallActionList_buttonIconWrapper fa-stack" t-att-class="{ 'o-isSmall': callActionListView.isSmall }">
|
||||
<i class="fa fa-video-camera fa-stack-1x" t-att-class="{ 'fa-lg': !callActionListView.isSmall }"/>
|
||||
</div>
|
||||
</button>
|
||||
</t>
|
||||
<t t-if="callActionListView.thread">
|
||||
<t t-if="callActionListView.thread.rtcInvitingSession and !callActionListView.thread.rtc">
|
||||
<button class="o_CallActionList_button o_CallActionList_callToggle o-isActive btn btn-danger d-flex m-1 border-0 rounded-circle shadow-none"
|
||||
t-att-class="{ 'o-isSmall p-2': callActionListView.isSmall, 'p-3': !callActionListView.isSmall }"
|
||||
aria-label="Reject"
|
||||
title="Reject"
|
||||
t-att-disabled="callActionListView.thread.hasPendingRtcRequest"
|
||||
t-on-click="callActionListView.onClickRejectCall">
|
||||
<div class="o_CallActionList_buttonIconWrapper fa-stack" t-att-class="{ 'o-isSmall': callActionListView.isSmall }">
|
||||
<i class="fa fa-phone fa-stack-1x" t-att-class="{ 'fa-lg': !callActionListView.isSmall }"/>
|
||||
</div>
|
||||
</button>
|
||||
</t>
|
||||
<button class="o_CallActionList_button o_CallActionList_callToggle btn d-flex m-1 border-0 rounded-circle shadow-none"
|
||||
t-att-aria-label="callActionListView.callButtonTitle"
|
||||
t-att-class="{ 'o-isActive btn-danger': !!callActionListView.thread.rtc, 'o-isSmall p-2': callActionListView.isSmall, 'p-3': !callActionListView.isSmall, 'btn-success': !callActionListView.thread.rtc }"
|
||||
t-att-disabled="callActionListView.thread.hasPendingRtcRequest"
|
||||
t-att-title="callActionListView.callButtonTitle"
|
||||
t-on-click="callActionListView.onClickToggleAudioCall">
|
||||
<div class="o_CallActionList_buttonIconWrapper fa-stack" t-att-class="{ 'o-isSmall': callActionListView.isSmall }">
|
||||
<i class="fa fa-phone fa-stack-1x" t-att-class="{ 'fa-lg': !callActionListView.isSmall }"/>
|
||||
</div>
|
||||
</button>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { useRefToModel } from '@mail/component_hooks/use_ref_to_model';
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class CallDemoView extends Component {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
useRefToModel({ fieldName: 'audioRef', refName: 'audio' });
|
||||
useRefToModel({ fieldName: 'videoRef', refName: 'video' });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {CallDemoView}
|
||||
*/
|
||||
get callDemoView() {
|
||||
return this.props.record;
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(CallDemoView, {
|
||||
props: { record: Object },
|
||||
template: 'mail.CallDemoView',
|
||||
});
|
||||
|
||||
registerMessagingComponent(CallDemoView);
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
.o_CallDemoView {
|
||||
max-width: max-content;
|
||||
}
|
||||
|
||||
.o_CallDemoView_mediaDevicesStatus {
|
||||
@include o-position-absolute($bottom: 50%);
|
||||
}
|
||||
|
||||
.o_CallDemoView_buttonsContainer {
|
||||
@include o-position-absolute($bottom: 0);
|
||||
}
|
||||
|
||||
.o_CallDemoView_button {
|
||||
height: 4rem;
|
||||
width: 4rem;
|
||||
font-size: 1.5em;
|
||||
line-height: 4rem;
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.CallDemoView" owl="1">
|
||||
<t t-if="callDemoView">
|
||||
<div class="o_CallDemoView position-relative d-flex justify-content-center" t-attf-class="{{ className }}" t-ref="root">
|
||||
<video class="o_CallDemoView_videoDisplay shadow rounded bg-dark" height="480" width="640" autoplay="" t-ref="video"/>
|
||||
<p t-if="callDemoView.doesBrowserSupportMediaDevices and !callDemoView.isVideoEnabled" class="o_CallDemoView_mediaDevicesStatus position-absolute text-light">
|
||||
Camera is off
|
||||
</p>
|
||||
<p t-if="!callDemoView.doesBrowserSupportMediaDevices" class="o_CallDemoView_mediaDevicesStatus text-light">
|
||||
Your browser does not support videoconference
|
||||
</p>
|
||||
<div class="o_CallDemoView_buttonsContainer">
|
||||
<button t-if="!callDemoView.isMicrophoneEnabled" class="o_CallDemoView_enableMicrophoneButton o_CallDemoView_button btn btn-danger btn-lg rounded-circle p-0 m-3 fa fa-microphone-slash" t-on-click="callDemoView.onClickEnableMicrophoneButton"/>
|
||||
<button t-if="callDemoView.isMicrophoneEnabled" class="o_CallDemoView_disableMicrophoneButton o_CallDemoView_button btn btn-dark btn-lg p-0 m-3 rounded-circle border-light fa fa-microphone" t-on-click="callDemoView.onClickDisableMicrophoneButton"/>
|
||||
<button t-if="!callDemoView.isVideoEnabled" class="o_CallDemoView_enableVideoButton o_CallDemoView_button btn btn-danger btn-lg p-0 m-3 rounded-circle fa fa-eye-slash" t-on-click="callDemoView.onClickEnableVideoButton"/>
|
||||
<button t-if="callDemoView.isVideoEnabled" class="o_CallDemoView_disableVideoButton o_CallDemoView_button btn btn-dark btn-lg p-0 m-3 rounded-circle border-light fa fa-video-camera" t-on-click="callDemoView.onClickDisableVideoButton"/>
|
||||
</div>
|
||||
<audio class="o_CallDemoView_audioPlayer" autoplay="" t-ref="audio"/>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class CallInviteRequestPopup extends Component {
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Getters / Setters
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @returns {CallInviteRequestPopup}
|
||||
*/
|
||||
get callInviteRequestPopup() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(CallInviteRequestPopup, {
|
||||
props: { record: Object },
|
||||
template: 'mail.CallInviteRequestPopup',
|
||||
});
|
||||
|
||||
registerMessagingComponent(CallInviteRequestPopup);
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue