Initial commit: Core packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:45 +02:00
commit 12c29a983b
9512 changed files with 8379910 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View file

@ -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);

View 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()

View file

@ -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);

View file

@ -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>

View file

@ -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);

View file

@ -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>

View file

@ -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);

View file

@ -0,0 +1,8 @@
.o_ActivityListView {
width: #{"min(95vw, 300px)"};
max-height: #{"min(95vh, 350px)"};
}
.o_ActivityListView_activityList {
overflow-y: auto;
}

View file

@ -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>

View file

@ -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);

View file

@ -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;
}

View file

@ -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>

View file

@ -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);

View file

@ -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>

View file

@ -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);

View file

@ -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>

View file

@ -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);

View file

@ -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>

View file

@ -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);

View file

@ -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>

View file

@ -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 });
});
}

View file

@ -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();
});
}

View file

@ -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 });
});
}

View file

@ -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 || {};
};
}

View file

@ -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;
}

View file

@ -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);
});
}

View file

@ -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]();
} });
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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>

View file

@ -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);

View file

@ -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;
}

View file

@ -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>

View file

@ -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);

View file

@ -0,0 +1,7 @@
// ------------------------------------------------------------------
// Layout
// ------------------------------------------------------------------
.o_ActivityMarkDonePopoverContent_feedback {
min-height: 70px;
}

View file

@ -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 &amp; 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>

View file

@ -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',
});

View file

@ -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>

View file

@ -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);

View file

@ -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;
}

View file

@ -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>

View file

@ -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);

View file

@ -0,0 +1,7 @@
// ------------------------------------------------------------------
// Style
// ------------------------------------------------------------------
.o_AttachmentBox_dashedLine {
border-top: $border-width dashed $border-color;
}

View file

@ -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>

View file

@ -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);

View file

@ -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;
}

View file

@ -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>

View file

@ -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);

View file

@ -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>

View file

@ -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);

View file

@ -0,0 +1,19 @@
// ------------------------------------------------------------------
// Layout
// ------------------------------------------------------------------
.o_AttachmentImage {
min-width: 20px;
min-height: 20px;
img {
object-fit: contain;
}
}
// ------------------------------------------------------------------
// Style
// ------------------------------------------------------------------
.o_AttachmentImage {
cursor: zoom-in;
}

View file

@ -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>

View file

@ -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);

View file

@ -0,0 +1,3 @@
.o_AttachmentList {
max-width: var(--Chatter-max-width, none);
}

View file

@ -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>

View file

@ -0,0 +1,7 @@
// = Attachment Viewer View
// ============================================================================
// No CSS hacks, variables overrides only
.o_AttachmentViewer_toolbarButton {
--AttachmentViewer_toolbarButton-background-color: #{$o-gray-200};
}

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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>

View file

@ -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);

View file

@ -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>

View file

@ -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};
}

View file

@ -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);

View file

@ -0,0 +1,4 @@
.o_CallActionList_button:not(.btn-danger) {
background-color: var(--CallActionList_button-background-color, #{$o-gray-800});
color: #FFFFFF;
}

View file

@ -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>

View file

@ -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);

View file

@ -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;
}

View file

@ -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>

View file

@ -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