@openmrs/esm-form-engine-lib 3.1.5-pre.2085 → 3.1.5-pre.2086

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- var _openmrs_esm_form_engine_lib;(()=>{"use strict";var e,r,t,n,o,i,a,l,s,u,f,d,p,c,h,m,v,g,b,y,w,_={78008:(e,r,t)=>{var n={"./start":()=>Promise.all([t.e(177),t.e(401),t.e(124),t.e(72),t.e(456),t.e(677)]).then((()=>()=>t(5677)))},o=(e,r)=>(t.R=r,r=t.o(n,e)?n[e]():Promise.resolve().then((()=>{throw new Error('Module "'+e+'" does not exist in container.')})),t.R=void 0,r),i=(e,r)=>{if(t.S){var n="default",o=t.S[n];if(o&&o!==e)throw new Error("Container initialization failed as it has already been initialized with a different share scope");return t.S[n]=e,t.I(n,r)}};t.d(r,{get:()=>o,init:()=>i})}},P={};function j(e){var r=P[e];if(void 0!==r)return r.exports;var t=P[e]={id:e,loaded:!1,exports:{}};return _[e].call(t.exports,t,t.exports,j),t.loaded=!0,t.exports}j.m=_,j.c=P,j.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return j.d(r,{a:r}),r},r=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,j.t=function(t,n){if(1&n&&(t=this(t)),8&n)return t;if("object"==typeof t&&t){if(4&n&&t.__esModule)return t;if(16&n&&"function"==typeof t.then)return t}var o=Object.create(null);j.r(o);var i={};e=e||[null,r({}),r([]),r(r)];for(var a=2&n&&t;"object"==typeof a&&!~e.indexOf(a);a=r(a))Object.getOwnPropertyNames(a).forEach((e=>i[e]=()=>t[e]));return i.default=()=>t,j.d(o,i),o},j.d=(e,r)=>{for(var t in r)j.o(r,t)&&!j.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},j.f={},j.e=e=>Promise.all(Object.keys(j.f).reduce(((r,t)=>(j.f[t](e,r),r)),[])),j.u=e=>e+".js",j.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),j.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),t={},n="@openmrs/esm-form-engine-lib:",j.l=(e,r,o,i)=>{if(t[e])t[e].push(r);else{var a,l;if(void 0!==o)for(var s=document.getElementsByTagName("script"),u=0;u<s.length;u++){var f=s[u];if(f.getAttribute("src")==e||f.getAttribute("data-webpack")==n+o){a=f;break}}a||(l=!0,(a=document.createElement("script")).charset="utf-8",a.timeout=120,j.nc&&a.setAttribute("nonce",j.nc),a.setAttribute("data-webpack",n+o),a.src=e),t[e]=[r];var d=(r,n)=>{a.onerror=a.onload=null,clearTimeout(p);var o=t[e];if(delete t[e],a.parentNode&&a.parentNode.removeChild(a),o&&o.forEach((e=>e(n))),r)return r(n)},p=setTimeout(d.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=d.bind(null,a.onerror),a.onload=d.bind(null,a.onload),l&&document.head.appendChild(a)}},j.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},j.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),j.j=719,(()=>{j.S={};var e={},r={};j.I=(t,n)=>{n||(n=[]);var o=r[t];if(o||(o=r[t]={}),!(n.indexOf(o)>=0)){if(n.push(o),e[t])return e[t];j.o(j.S,t)||(j.S[t]={});var i=j.S[t],a="@openmrs/esm-form-engine-lib",l=(e,r,t,n)=>{var o=i[e]=i[e]||{},l=o[r];(!l||!l.loaded&&(!n!=!l.eager?n:a>l.from))&&(o[r]={get:t,from:a,eager:!!n})},s=[];return"default"===t&&(l("@openmrs/esm-framework","7.0.1-pre.3291",(()=>Promise.all([j.e(177),j.e(387),j.e(401),j.e(72),j.e(456),j.e(766),j.e(310)]).then((()=>()=>j(96387))))),l("dayjs","1.11.13",(()=>j.e(353).then((()=>()=>j(74353))))),l("i18next","23.16.0",(()=>j.e(635).then((()=>()=>j(72635))))),l("react-i18next","11.18.6",(()=>Promise.all([j.e(72),j.e(414)]).then((()=>()=>j(93414))))),l("react","18.3.1",(()=>j.e(540).then((()=>()=>j(96540))))),l("swr/immutable","2.3.3",(()=>Promise.all([j.e(177),j.e(72),j.e(606)]).then((()=>()=>j(54225))))),l("swr/infinite","2.3.3",(()=>Promise.all([j.e(177),j.e(72),j.e(422)]).then((()=>()=>j(23041)))))),e[t]=s.length?Promise.all(s).then((()=>e[t]=1)):1}}})(),(()=>{var e;j.g.importScripts&&(e=j.g.location+"");var r=j.g.document;if(!e&&r&&(r.currentScript&&"SCRIPT"===r.currentScript.tagName.toUpperCase()&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var n=t.length-1;n>-1&&(!e||!/^http(s?):/.test(e));)e=t[n--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),j.p=e})(),o=e=>{var r=e=>e.split(".").map((e=>+e==e?+e:e)),t=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(e),n=t[1]?r(t[1]):[];return t[2]&&(n.length++,n.push.apply(n,r(t[2]))),t[3]&&(n.push([]),n.push.apply(n,r(t[3]))),n},i=(e,r)=>{e=o(e),r=o(r);for(var t=0;;){if(t>=e.length)return t<r.length&&"u"!=(typeof r[t])[0];var n=e[t],i=(typeof n)[0];if(t>=r.length)return"u"==i;var a=r[t],l=(typeof a)[0];if(i!=l)return"o"==i&&"n"==l||"s"==l||"u"==i;if("o"!=i&&"u"!=i&&n!=a)return n<a;t++}},a=e=>{var r=e[0],t="";if(1===e.length)return"*";if(r+.5){t+=0==r?">=":-1==r?"<":1==r?"^":2==r?"~":r>0?"=":"!=";for(var n=1,o=1;o<e.length;o++)n--,t+="u"==(typeof(l=e[o]))[0]?"-":(n>0?".":"")+(n=2,l);return t}var i=[];for(o=1;o<e.length;o++){var l=e[o];i.push(0===l?"not("+s()+")":1===l?"("+s()+" || "+s()+")":2===l?i.pop()+" "+i.pop():a(l))}return s();function s(){return i.pop().replace(/^\((.+)\)$/,"$1")}},l=(e,r)=>{if(0 in e){r=o(r);var t=e[0],n=t<0;n&&(t=-t-1);for(var i=0,a=1,s=!0;;a++,i++){var u,f,d=a<e.length?(typeof e[a])[0]:"";if(i>=r.length||"o"==(f=(typeof(u=r[i]))[0]))return!s||("u"==d?a>t&&!n:""==d!=n);if("u"==f){if(!s||"u"!=d)return!1}else if(s)if(d==f)if(a<=t){if(u!=e[a])return!1}else{if(n?u>e[a]:u<e[a])return!1;u!=e[a]&&(s=!1)}else if("s"!=d&&"n"!=d){if(n||a<=t)return!1;s=!1,a--}else{if(a<=t||f<d!=n)return!1;s=!1}else"s"!=d&&"n"!=d&&(s=!1,a--)}}var p=[],c=p.pop.bind(p);for(i=1;i<e.length;i++){var h=e[i];p.push(1==h?c()|c():2==h?c()&c():h?l(h,r):!c())}return!!c()},s=(e,r)=>e&&j.o(e,r),u=e=>(e.loaded=1,e.get()),f=e=>Object.keys(e).reduce(((r,t)=>(e[t].eager&&(r[t]=e[t]),r)),{}),d=(e,r,t)=>{var n=t?f(e[r]):e[r];return Object.keys(n).reduce(((e,r)=>!e||!n[e].loaded&&i(e,r)?r:e),0)},p=(e,r,t,n)=>"Unsatisfied version "+t+" from "+(t&&e[r][t].from)+" of shared singleton module "+r+" (required "+a(n)+")",c=e=>{throw new Error(e)},h=e=>{"undefined"!=typeof console&&console.warn&&console.warn(e)},m=(e,r,t)=>t?t():((e,r)=>c("Shared module "+r+" doesn't exist in shared scope "+e))(e,r),v=(e=>function(r,t,n,o,i){var a=j.I(r);return a&&a.then&&!n?a.then(e.bind(e,r,j.S[r],t,!1,o,i)):e(r,j.S[r],t,n,o,i)})(((e,r,t,n,o,i)=>{if(!s(r,t))return m(e,t,i);var a=d(r,t,n);return l(o,a)||h(p(r,t,a,o)),u(r[t][a])})),g={},b={16072:()=>v("default","react",!1,[1,18],(()=>j.e(540).then((()=>()=>j(96540))))),53941:()=>v("default","react-i18next",!1,[1,11],(()=>j.e(33).then((()=>()=>j(93414))))),66838:()=>v("default","@openmrs/esm-framework",!1,[1,7],(()=>Promise.all([j.e(177),j.e(387),j.e(766)]).then((()=>()=>j(96387))))),76766:()=>v("default","i18next",!1,[1,23],(()=>j.e(635).then((()=>()=>j(72635))))),44209:()=>v("default","swr/immutable",!1,[1,2],(()=>Promise.all([j.e(177),j.e(225)]).then((()=>()=>j(54225))))),56339:()=>v("default","swr/infinite",!1,[1,2],(()=>Promise.all([j.e(177),j.e(41)]).then((()=>()=>j(23041))))),70231:()=>v("default","dayjs",!1,[1,1],(()=>j.e(353).then((()=>()=>j(74353)))))},y={72:[16072],310:[44209,56339,70231],456:[53941,66838],677:[44209,56339,70231],766:[76766]},w={},j.f.consumes=(e,r)=>{j.o(y,e)&&y[e].forEach((e=>{if(j.o(g,e))return r.push(g[e]);if(!w[e]){var t=r=>{g[e]=0,j.m[e]=t=>{delete j.c[e],t.exports=r()}};w[e]=!0;var n=r=>{delete g[e],j.m[e]=t=>{throw delete j.c[e],r}};try{var o=b[e]();o.then?r.push(g[e]=o.then(t).catch(n)):t(o)}catch(e){n(e)}}}))},(()=>{var e={719:0};j.f.j=(r,t)=>{var n=j.o(e,r)?e[r]:void 0;if(0!==n)if(n)t.push(n[2]);else if(/^(456|72|766)$/.test(r))e[r]=0;else{var o=new Promise(((t,o)=>n=e[r]=[t,o]));t.push(n[2]=o);var i=j.p+j.u(r),a=new Error;j.l(i,(t=>{if(j.o(e,r)&&(0!==(n=e[r])&&(e[r]=void 0),n)){var o=t&&("load"===t.type?"missing":t.type),i=t&&t.target&&t.target.src;a.message="Loading chunk "+r+" failed.\n("+o+": "+i+")",a.name="ChunkLoadError",a.type=o,a.request=i,n[1](a)}}),"chunk-"+r,r)}};var r=(r,t)=>{var n,o,[i,a,l]=t,s=0;if(i.some((r=>0!==e[r]))){for(n in a)j.o(a,n)&&(j.m[n]=a[n]);l&&l(j)}for(r&&r(t);s<i.length;s++)o=i[s],j.o(e,o)&&e[o]&&e[o][0](),e[o]=0},t=globalThis.webpackChunk_openmrs_esm_form_engine_lib=globalThis.webpackChunk_openmrs_esm_form_engine_lib||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),j.nc=void 0;var S=j(78008);_openmrs_esm_form_engine_lib=S})();
1
+ var _openmrs_esm_form_engine_lib;(()=>{"use strict";var e,r,t,n,o,i,a,l,s,u,f,d,p,c,h,m,v,g,b,y,w,_={78008:(e,r,t)=>{var n={"./start":()=>Promise.all([t.e(177),t.e(899),t.e(759),t.e(72),t.e(456),t.e(306)]).then((()=>()=>t(4306)))},o=(e,r)=>(t.R=r,r=t.o(n,e)?n[e]():Promise.resolve().then((()=>{throw new Error('Module "'+e+'" does not exist in container.')})),t.R=void 0,r),i=(e,r)=>{if(t.S){var n="default",o=t.S[n];if(o&&o!==e)throw new Error("Container initialization failed as it has already been initialized with a different share scope");return t.S[n]=e,t.I(n,r)}};t.d(r,{get:()=>o,init:()=>i})}},P={};function j(e){var r=P[e];if(void 0!==r)return r.exports;var t=P[e]={id:e,loaded:!1,exports:{}};return _[e].call(t.exports,t,t.exports,j),t.loaded=!0,t.exports}j.m=_,j.c=P,j.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return j.d(r,{a:r}),r},r=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,j.t=function(t,n){if(1&n&&(t=this(t)),8&n)return t;if("object"==typeof t&&t){if(4&n&&t.__esModule)return t;if(16&n&&"function"==typeof t.then)return t}var o=Object.create(null);j.r(o);var i={};e=e||[null,r({}),r([]),r(r)];for(var a=2&n&&t;"object"==typeof a&&!~e.indexOf(a);a=r(a))Object.getOwnPropertyNames(a).forEach((e=>i[e]=()=>t[e]));return i.default=()=>t,j.d(o,i),o},j.d=(e,r)=>{for(var t in r)j.o(r,t)&&!j.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},j.f={},j.e=e=>Promise.all(Object.keys(j.f).reduce(((r,t)=>(j.f[t](e,r),r)),[])),j.u=e=>e+".js",j.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),j.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),t={},n="@openmrs/esm-form-engine-lib:",j.l=(e,r,o,i)=>{if(t[e])t[e].push(r);else{var a,l;if(void 0!==o)for(var s=document.getElementsByTagName("script"),u=0;u<s.length;u++){var f=s[u];if(f.getAttribute("src")==e||f.getAttribute("data-webpack")==n+o){a=f;break}}a||(l=!0,(a=document.createElement("script")).charset="utf-8",a.timeout=120,j.nc&&a.setAttribute("nonce",j.nc),a.setAttribute("data-webpack",n+o),a.src=e),t[e]=[r];var d=(r,n)=>{a.onerror=a.onload=null,clearTimeout(p);var o=t[e];if(delete t[e],a.parentNode&&a.parentNode.removeChild(a),o&&o.forEach((e=>e(n))),r)return r(n)},p=setTimeout(d.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=d.bind(null,a.onerror),a.onload=d.bind(null,a.onload),l&&document.head.appendChild(a)}},j.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},j.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),j.j=719,(()=>{j.S={};var e={},r={};j.I=(t,n)=>{n||(n=[]);var o=r[t];if(o||(o=r[t]={}),!(n.indexOf(o)>=0)){if(n.push(o),e[t])return e[t];j.o(j.S,t)||(j.S[t]={});var i=j.S[t],a="@openmrs/esm-form-engine-lib",l=(e,r,t,n)=>{var o=i[e]=i[e]||{},l=o[r];(!l||!l.loaded&&(!n!=!l.eager?n:a>l.from))&&(o[r]={get:t,from:a,eager:!!n})},s=[];return"default"===t&&(l("@openmrs/esm-framework","7.0.1-pre.3291",(()=>Promise.all([j.e(177),j.e(387),j.e(899),j.e(72),j.e(456),j.e(766),j.e(310)]).then((()=>()=>j(96387))))),l("dayjs","1.11.13",(()=>j.e(353).then((()=>()=>j(74353))))),l("i18next","23.16.0",(()=>j.e(635).then((()=>()=>j(72635))))),l("react-i18next","11.18.6",(()=>Promise.all([j.e(72),j.e(414)]).then((()=>()=>j(93414))))),l("react","18.3.1",(()=>j.e(540).then((()=>()=>j(96540))))),l("swr/immutable","2.3.3",(()=>Promise.all([j.e(177),j.e(72),j.e(606)]).then((()=>()=>j(54225))))),l("swr/infinite","2.3.3",(()=>Promise.all([j.e(177),j.e(72),j.e(422)]).then((()=>()=>j(23041)))))),e[t]=s.length?Promise.all(s).then((()=>e[t]=1)):1}}})(),(()=>{var e;j.g.importScripts&&(e=j.g.location+"");var r=j.g.document;if(!e&&r&&(r.currentScript&&"SCRIPT"===r.currentScript.tagName.toUpperCase()&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var n=t.length-1;n>-1&&(!e||!/^http(s?):/.test(e));)e=t[n--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),j.p=e})(),o=e=>{var r=e=>e.split(".").map((e=>+e==e?+e:e)),t=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(e),n=t[1]?r(t[1]):[];return t[2]&&(n.length++,n.push.apply(n,r(t[2]))),t[3]&&(n.push([]),n.push.apply(n,r(t[3]))),n},i=(e,r)=>{e=o(e),r=o(r);for(var t=0;;){if(t>=e.length)return t<r.length&&"u"!=(typeof r[t])[0];var n=e[t],i=(typeof n)[0];if(t>=r.length)return"u"==i;var a=r[t],l=(typeof a)[0];if(i!=l)return"o"==i&&"n"==l||"s"==l||"u"==i;if("o"!=i&&"u"!=i&&n!=a)return n<a;t++}},a=e=>{var r=e[0],t="";if(1===e.length)return"*";if(r+.5){t+=0==r?">=":-1==r?"<":1==r?"^":2==r?"~":r>0?"=":"!=";for(var n=1,o=1;o<e.length;o++)n--,t+="u"==(typeof(l=e[o]))[0]?"-":(n>0?".":"")+(n=2,l);return t}var i=[];for(o=1;o<e.length;o++){var l=e[o];i.push(0===l?"not("+s()+")":1===l?"("+s()+" || "+s()+")":2===l?i.pop()+" "+i.pop():a(l))}return s();function s(){return i.pop().replace(/^\((.+)\)$/,"$1")}},l=(e,r)=>{if(0 in e){r=o(r);var t=e[0],n=t<0;n&&(t=-t-1);for(var i=0,a=1,s=!0;;a++,i++){var u,f,d=a<e.length?(typeof e[a])[0]:"";if(i>=r.length||"o"==(f=(typeof(u=r[i]))[0]))return!s||("u"==d?a>t&&!n:""==d!=n);if("u"==f){if(!s||"u"!=d)return!1}else if(s)if(d==f)if(a<=t){if(u!=e[a])return!1}else{if(n?u>e[a]:u<e[a])return!1;u!=e[a]&&(s=!1)}else if("s"!=d&&"n"!=d){if(n||a<=t)return!1;s=!1,a--}else{if(a<=t||f<d!=n)return!1;s=!1}else"s"!=d&&"n"!=d&&(s=!1,a--)}}var p=[],c=p.pop.bind(p);for(i=1;i<e.length;i++){var h=e[i];p.push(1==h?c()|c():2==h?c()&c():h?l(h,r):!c())}return!!c()},s=(e,r)=>e&&j.o(e,r),u=e=>(e.loaded=1,e.get()),f=e=>Object.keys(e).reduce(((r,t)=>(e[t].eager&&(r[t]=e[t]),r)),{}),d=(e,r,t)=>{var n=t?f(e[r]):e[r];return Object.keys(n).reduce(((e,r)=>!e||!n[e].loaded&&i(e,r)?r:e),0)},p=(e,r,t,n)=>"Unsatisfied version "+t+" from "+(t&&e[r][t].from)+" of shared singleton module "+r+" (required "+a(n)+")",c=e=>{throw new Error(e)},h=e=>{"undefined"!=typeof console&&console.warn&&console.warn(e)},m=(e,r,t)=>t?t():((e,r)=>c("Shared module "+r+" doesn't exist in shared scope "+e))(e,r),v=(e=>function(r,t,n,o,i){var a=j.I(r);return a&&a.then&&!n?a.then(e.bind(e,r,j.S[r],t,!1,o,i)):e(r,j.S[r],t,n,o,i)})(((e,r,t,n,o,i)=>{if(!s(r,t))return m(e,t,i);var a=d(r,t,n);return l(o,a)||h(p(r,t,a,o)),u(r[t][a])})),g={},b={16072:()=>v("default","react",!1,[1,18],(()=>j.e(540).then((()=>()=>j(96540))))),53941:()=>v("default","react-i18next",!1,[1,11],(()=>j.e(33).then((()=>()=>j(93414))))),66838:()=>v("default","@openmrs/esm-framework",!1,[1,7],(()=>Promise.all([j.e(177),j.e(387),j.e(766)]).then((()=>()=>j(96387))))),76766:()=>v("default","i18next",!1,[1,23],(()=>j.e(635).then((()=>()=>j(72635))))),44209:()=>v("default","swr/immutable",!1,[1,2],(()=>Promise.all([j.e(177),j.e(225)]).then((()=>()=>j(54225))))),56339:()=>v("default","swr/infinite",!1,[1,2],(()=>Promise.all([j.e(177),j.e(41)]).then((()=>()=>j(23041))))),70231:()=>v("default","dayjs",!1,[1,1],(()=>j.e(353).then((()=>()=>j(74353)))))},y={72:[16072],306:[44209,56339,70231],310:[44209,56339,70231],456:[53941,66838],766:[76766]},w={},j.f.consumes=(e,r)=>{j.o(y,e)&&y[e].forEach((e=>{if(j.o(g,e))return r.push(g[e]);if(!w[e]){var t=r=>{g[e]=0,j.m[e]=t=>{delete j.c[e],t.exports=r()}};w[e]=!0;var n=r=>{delete g[e],j.m[e]=t=>{throw delete j.c[e],r}};try{var o=b[e]();o.then?r.push(g[e]=o.then(t).catch(n)):t(o)}catch(e){n(e)}}}))},(()=>{var e={719:0};j.f.j=(r,t)=>{var n=j.o(e,r)?e[r]:void 0;if(0!==n)if(n)t.push(n[2]);else if(/^(456|72|766)$/.test(r))e[r]=0;else{var o=new Promise(((t,o)=>n=e[r]=[t,o]));t.push(n[2]=o);var i=j.p+j.u(r),a=new Error;j.l(i,(t=>{if(j.o(e,r)&&(0!==(n=e[r])&&(e[r]=void 0),n)){var o=t&&("load"===t.type?"missing":t.type),i=t&&t.target&&t.target.src;a.message="Loading chunk "+r+" failed.\n("+o+": "+i+")",a.name="ChunkLoadError",a.type=o,a.request=i,n[1](a)}}),"chunk-"+r,r)}};var r=(r,t)=>{var n,o,[i,a,l]=t,s=0;if(i.some((r=>0!==e[r]))){for(n in a)j.o(a,n)&&(j.m[n]=a[n]);l&&l(j)}for(r&&r(t);s<i.length;s++)o=i[s],j.o(e,o)&&e[o]&&e[o][0](),e[o]=0},t=globalThis.webpackChunk_openmrs_esm_form_engine_lib=globalThis.webpackChunk_openmrs_esm_form_engine_lib||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),j.nc=void 0;var S=j(78008);_openmrs_esm_form_engine_lib=S})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-form-engine-lib",
3
- "version": "3.1.5-pre.2085",
3
+ "version": "3.1.5-pre.2086",
4
4
  "description": "React Form Engine for O3",
5
5
  "browser": "dist/openmrs-esm-form-engine-lib.js",
6
6
  "main": "src/index.ts",
@@ -1,3 +1,4 @@
1
+ import { getAttachmentByUuid } from '@openmrs/esm-framework';
1
2
  import { type FormContextProps } from '../provider/form-provider';
2
3
  import { type FormField } from '../types';
3
4
  import { findObsByFormField, hasPreviousObsValueChanged, ObsAdapter } from './obs-adapter';
@@ -40,6 +41,9 @@ const formContext = {
40
41
  setDeletedFields: jest.fn(),
41
42
  } as FormContextProps;
42
43
 
44
+ window.openmrsBase = 'openmrs';
45
+ const mockGetAttachmentByUuid = jest.mocked(getAttachmentByUuid);
46
+
43
47
  describe('ObsAdapter - transformFieldValue', () => {
44
48
  // new submission (enter mode)
45
49
  it('should handle submission for text input', () => {
@@ -188,6 +192,42 @@ describe('ObsAdapter - transformFieldValue', () => {
188
192
  });
189
193
  });
190
194
 
195
+ it('should handle value transformation for file input', () => {
196
+ // setup
197
+ const field: FormField = {
198
+ label: "Upload an image or use this device's camera to capture an image",
199
+ type: 'obs',
200
+ questionOptions: {
201
+ rendering: 'file',
202
+ },
203
+ meta: {},
204
+ id: 'demoFile',
205
+ };
206
+ // value
207
+ const image = {
208
+ base64Content: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoA',
209
+ capturedFromWebcam: true,
210
+ fileDescription: '',
211
+ fileName: 'Image taken from camera.png',
212
+ fileType: 'image',
213
+ };
214
+
215
+ // replay
216
+ const obs = ObsAdapter.transformFieldValue(field, [image], formContext);
217
+ // verify
218
+ expect(obs).toEqual([
219
+ {
220
+ base64Content: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoA',
221
+ capturedFromWebcam: true,
222
+ fileDescription: '',
223
+ fileName: 'Image taken from camera.png',
224
+ fileType: 'image',
225
+ formFieldNamespace: 'rfe-forms',
226
+ formFieldPath: 'rfe-forms-demoFile',
227
+ },
228
+ ]);
229
+ });
230
+
191
231
  // editing existing values (edit mode)
192
232
  it('should edit obs text/number value in edit mode', () => {
193
233
  // setup
@@ -519,6 +559,39 @@ describe('ObsAdapter - transformFieldValue', () => {
519
559
  });
520
560
  expect(field.meta.submission.newValue).toBe(null);
521
561
  });
562
+
563
+ it('should void deleted files', () => {
564
+ // setup
565
+ const field: FormField = {
566
+ label: "Upload an image or use this device's camera to capture an image",
567
+ type: 'obs',
568
+ questionOptions: {
569
+ rendering: 'file',
570
+ },
571
+ meta: {
572
+ initialValue: {
573
+ omrsObject: {
574
+ uuid: '6ff51289-b334-4050-8a0e-28cb2034bfc3',
575
+ formFieldPath: 'rfe-forms-demoFile',
576
+ },
577
+ },
578
+ },
579
+ id: 'demoFile',
580
+ };
581
+ // replay
582
+ ObsAdapter.transformFieldValue(
583
+ field,
584
+ [{ uuid: '6ff51289-b334-4050-8a0e-28cb2034bfc3', voided: true }],
585
+ formContext,
586
+ );
587
+ // verify
588
+ expect(field.meta.submission.voidedValue).toEqual([
589
+ {
590
+ uuid: '6ff51289-b334-4050-8a0e-28cb2034bfc3',
591
+ voided: true,
592
+ },
593
+ ]);
594
+ });
522
595
  });
523
596
 
524
597
  describe('ObsAdapter - getInitialValue', () => {
@@ -663,6 +736,49 @@ describe('ObsAdapter - getInitialValue', () => {
663
736
  expect(initialValue).toEqual('12f7be3d-fb5d-47dc-b5e3-56c501be80a6');
664
737
  });
665
738
 
739
+ it('should get initial value for file rendering', async () => {
740
+ // setup
741
+ const field: FormField = {
742
+ label: "Upload an image or use this device's camera to capture an image",
743
+ type: 'obs',
744
+ questionOptions: {
745
+ rendering: 'file',
746
+ },
747
+ meta: {},
748
+ id: 'demoFile',
749
+ };
750
+
751
+ const obs = {
752
+ uuid: '6ff51289-b334-4050-8a0e-28cb2034bfc3',
753
+ formFieldPath: 'rfe-forms-demoFile',
754
+ };
755
+ formContext.domainObjectValue['obs'].push(obs);
756
+ mockGetAttachmentByUuid.mockReturnValue(
757
+ Promise.resolve({
758
+ data: {
759
+ uuid: '6ff51289-b334-4050-8a0e-28cb2034bfc3',
760
+ dateTime: '2025-08-21T19:31:39.000+0000',
761
+ filename: 'image.png',
762
+ comment: 'An image captured for test purposes',
763
+ bytesMimeType: 'image/png',
764
+ bytesContentFamily: 'IMAGE',
765
+ },
766
+ } as any),
767
+ );
768
+
769
+ // replay
770
+ const initialValue = await ObsAdapter.getInitialValue(field, formContext.domainObjectValue, formContext);
771
+ expect(initialValue).toEqual([
772
+ {
773
+ uuid: '6ff51289-b334-4050-8a0e-28cb2034bfc3',
774
+ base64Content: 'openmrs/ws/rest/v1/attachment/6ff51289-b334-4050-8a0e-28cb2034bfc3/bytes',
775
+ fileName: 'image.png',
776
+ fileDescription: 'An image captured for test purposes',
777
+ fileType: 'image',
778
+ },
779
+ ]);
780
+ });
781
+
666
782
  it('should get initial values for obs-group members', async () => {
667
783
  // setup
668
784
  const basePath = 'rfe-forms-';
@@ -952,6 +1068,7 @@ describe('findObsByFormField', () => {
952
1068
  it('Should find observation by field path', () => {
953
1069
  // do find
954
1070
  let matchedObs = findObsByFormField(obsList, [], fields[0]);
1071
+
955
1072
  // verify
956
1073
  expect(matchedObs.length).toBe(1);
957
1074
  expect(matchedObs[0]).toBe(obsList[0]);
@@ -1,11 +1,10 @@
1
1
  import dayjs from 'dayjs';
2
2
  import { ConceptTrue, codedTypes } from '../constants';
3
3
  import {
4
+ type Attachment,
4
5
  type OpenmrsObs,
5
6
  type FormField,
6
7
  type OpenmrsEncounter,
7
- type AttachmentResponse,
8
- type Attachment,
9
8
  type ValueAndDisplay,
10
9
  type FormFieldValueAdapter,
11
10
  } from '../types';
@@ -19,8 +18,7 @@ import {
19
18
  } from '../utils/common-utils';
20
19
  import { type FormContextProps } from '../provider/form-provider';
21
20
  import { isEmpty } from '../validators/form-validator';
22
- import { getAttachmentByUuid } from '../api';
23
- import { formatDate, type OpenmrsResource, restBaseUrl } from '@openmrs/esm-framework';
21
+ import { attachmentUrl, getAttachmentByUuid, type OpenmrsResource } from '@openmrs/esm-framework';
24
22
 
25
23
  // Temporarily holds observations that have already been bound with matching fields
26
24
  export let assignedObsIds: string[] = [];
@@ -28,15 +26,11 @@ export let assignedObsIds: string[] = [];
28
26
  export const ObsAdapter: FormFieldValueAdapter = {
29
27
  async getInitialValue(field: FormField, sourceObject: any, context: FormContextProps) {
30
28
  const encounter = sourceObject ?? (context.domainObjectValue as OpenmrsEncounter);
31
- if (hasRendering(field, 'file')) {
32
- const ac = new AbortController();
33
- const attachmentsResponse = await getAttachmentByUuid(context.patient.id, encounter.uuid, ac);
34
- // TODO: This seems like a violation of the data model.
35
- // I think we should instead use something like `formFieldPath` to do the mapping.
36
- const rawAttachment = attachmentsResponse.results?.find((attachment) => attachment.comment === field.id);
37
- return rawAttachment ? generateAttachment(rawAttachment) : null;
29
+ const matchingObs = findObsByFormField(flattenObsList(encounter.obs), assignedObsIds, field);
30
+ if (hasRendering(field, 'file') && matchingObs?.length) {
31
+ return resolveAttachmentsFromObs(field, matchingObs);
38
32
  }
39
- return extractFieldValue(field, findObsByFormField(flattenObsList(encounter.obs), assignedObsIds, field), true);
33
+ return extractFieldValue(field, matchingObs, true);
40
34
  },
41
35
  async getPreviousValue(field: FormField, sourceObject: any, context: FormContextProps): Promise<ValueAndDisplay> {
42
36
  const encounter = sourceObject ?? (context.previousDomainObjectValue as OpenmrsEncounter);
@@ -85,6 +79,9 @@ export const ObsAdapter: FormFieldValueAdapter = {
85
79
  if (hasRendering(field, 'checkbox')) {
86
80
  return handleMultiSelect(field, Array.isArray(value) ? value : [value]);
87
81
  }
82
+ if (hasRendering(field, 'file')) {
83
+ return handleAttachments(field, value);
84
+ }
88
85
  if (!isEmpty(value) && hasPreviousObsValueChanged(field, value)) {
89
86
  return gracefullySetSubmission(field, editObs(field, value), undefined);
90
87
  }
@@ -251,6 +248,20 @@ function handleMultiSelect(field: FormField, values: Array<string> = []) {
251
248
  );
252
249
  }
253
250
 
251
+ function handleAttachments(field: FormField, attachments: Attachment[] = []) {
252
+ const voided = attachments
253
+ .filter((attachment) => attachment.uuid && attachment.voided)
254
+ .map((voided) => ({ uuid: voided.uuid, voided: true }));
255
+ const newAttachments = (field.meta.submission.newValue = attachments
256
+ .filter((attachment) => !attachment.uuid)
257
+ .map((newAttachment) => ({
258
+ formFieldNamespace: 'rfe-forms',
259
+ formFieldPath: `rfe-forms-${field.id}`,
260
+ ...newAttachment,
261
+ })));
262
+ return gracefullySetSubmission(field, newAttachments, voided);
263
+ }
264
+
254
265
  /**
255
266
  * Retrieves a list of observations from a given `obsList` that correspond to the specified field.
256
267
  *
@@ -263,9 +274,15 @@ export function findObsByFormField(
263
274
  claimedObsIds: string[],
264
275
  field: FormField,
265
276
  ): OpenmrsObs[] {
266
- const obs = obsList.filter(
267
- (o) => o.formFieldPath == `rfe-forms-${field.id}` && o.concept.uuid == field.questionOptions.concept,
268
- );
277
+ const obs = obsList.filter((candidate) => {
278
+ // we ignore the concept for attachments because they're managed from the backend
279
+ if (hasRendering(field, 'file') && candidate.formFieldPath == `rfe-forms-${field.id}`) {
280
+ return true;
281
+ }
282
+ return (
283
+ candidate.formFieldPath == `rfe-forms-${field.id}` && candidate.concept.uuid == field.questionOptions.concept
284
+ );
285
+ });
269
286
 
270
287
  // We shall fall back to mapping by the associated concept
271
288
  // That being said, we shall find all matching obs and pick the one that wasn't previously claimed.
@@ -277,17 +294,27 @@ export function findObsByFormField(
277
294
  return obs;
278
295
  }
279
296
 
280
- function generateAttachment(rawAttachment: AttachmentResponse): Attachment {
281
- const attachmentUrl = `${restBaseUrl}/attachment`;
282
- return {
283
- id: rawAttachment.uuid,
284
- src: `${window.openmrsBase}${attachmentUrl}/${rawAttachment.uuid}/bytes`,
285
- title: rawAttachment.comment,
286
- description: '',
287
- dateTime: formatDate(new Date(rawAttachment.dateTime), {
288
- mode: 'wide',
289
- }),
290
- bytesMimeType: rawAttachment.bytesMimeType,
291
- bytesContentFamily: rawAttachment.bytesContentFamily,
297
+ async function resolveAttachmentsFromObs(field: FormField, obs: OpenmrsObs[]) {
298
+ const abortController = new AbortController();
299
+ const attachments = await Promise.all(
300
+ obs.map((obs) =>
301
+ getAttachmentByUuid(obs.uuid, abortController)
302
+ .then((response) => response.data)
303
+ .catch((error) => {
304
+ console.error(`Failed to fetch attachment ${obs.uuid}:`, error);
305
+ return null;
306
+ }),
307
+ ),
308
+ );
309
+
310
+ field.meta.initialValue = {
311
+ omrsObject: obs,
292
312
  };
313
+ return attachments.filter(Boolean).map((attachment) => ({
314
+ uuid: attachment.uuid,
315
+ base64Content: `${window.openmrsBase}${attachmentUrl}/${attachment.uuid}/bytes`,
316
+ fileName: attachment.filename,
317
+ fileDescription: attachment.comment,
318
+ fileType: attachment.bytesContentFamily?.toLowerCase(),
319
+ })) as Attachment[];
293
320
  }
package/src/api/index.ts CHANGED
@@ -1,6 +1,12 @@
1
- import { fhirBaseUrl, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
1
+ import { attachmentUrl, fhirBaseUrl, openmrsFetch, restBaseUrl, type UploadedFile } from '@openmrs/esm-framework';
2
2
  import { encounterRepresentation } from '../constants';
3
- import type { FHIRObsResource, OpenmrsForm, PatientIdentifier, PatientProgramPayload } from '../types';
3
+ import type {
4
+ AttachmentFieldValue,
5
+ FHIRObsResource,
6
+ OpenmrsForm,
7
+ PatientIdentifier,
8
+ PatientProgramPayload,
9
+ } from '../types';
4
10
  import { isUuid } from '../utils/boolean-utils';
5
11
 
6
12
  export function saveEncounter(abortController: AbortController, payload, encounterUuid?: string) {
@@ -18,41 +24,28 @@ export function saveEncounter(abortController: AbortController, payload, encount
18
24
  });
19
25
  }
20
26
 
21
- export function saveAttachment(patientUuid, field, conceptUuid, date, encounterUUID, abortController) {
22
- const url = `${restBaseUrl}/attachment`;
23
-
24
- const content = field.meta.submission?.newValue?.value;
25
- const cameraUploadType = typeof content === 'string' && content?.split(';')[0].split(':')[1].split('/')[1];
26
-
27
+ export async function createAttachment(patientUuid: string, encounterUUID: string, attachment: AttachmentFieldValue) {
27
28
  const formData = new FormData();
28
- const fileCaption = field.id;
29
29
 
30
- formData.append('fileCaption', fileCaption);
30
+ formData.append('fileCaption', attachment.fileDescription);
31
31
  formData.append('patient', patientUuid);
32
32
 
33
- if (typeof content === 'object') {
34
- formData.append('file', content);
33
+ if (attachment.file) {
34
+ formData.append('file', attachment.file, attachment.fileName);
35
35
  } else {
36
- formData.append('file', new File([''], `camera-upload.${cameraUploadType}`), `camera-upload.${cameraUploadType}`);
37
- formData.append('base64Content', content);
36
+ formData.append('file', new File([''], attachment.fileName), attachment.fileName);
37
+ formData.append('base64Content', attachment.base64Content);
38
38
  }
39
39
  formData.append('encounter', encounterUUID);
40
- formData.append('obsDatetime', date);
40
+ formData.append('formFieldNamespace', attachment.formFieldNamespace);
41
+ formData.append('formFieldPath', attachment.formFieldPath);
41
42
 
42
- return openmrsFetch(url, {
43
+ return openmrsFetch(`${attachmentUrl}`, {
43
44
  method: 'POST',
44
- signal: abortController.signal,
45
45
  body: formData,
46
46
  });
47
47
  }
48
48
 
49
- export function getAttachmentByUuid(patientUuid: string, encounterUuid: string, abortController: AbortController) {
50
- const attachmentUrl = `${restBaseUrl}/attachment`;
51
- return openmrsFetch(`${attachmentUrl}?patient=${patientUuid}&encounter=${encounterUuid}`, {
52
- signal: abortController.signal,
53
- }).then((response) => response.data);
54
- }
55
-
56
49
  export function getConcept(conceptUuid: string, v: string) {
57
50
  return openmrsFetch(`${restBaseUrl}/concept/${conceptUuid}?v=${v}`).then(({ data }) => data.results);
58
51
  }
@@ -0,0 +1,55 @@
1
+ import React, { useMemo } from 'react';
2
+ import styles from './file-thumbnail.scss';
3
+ import { CloseFilled, DocumentPdf, DocumentUnknown } from '@carbon/react/icons';
4
+ import { Button } from '@carbon/react';
5
+
6
+ interface FileThumbnailProps {
7
+ src: string;
8
+ title: string;
9
+ bytesContentFamily: string;
10
+ removeFileCb: () => void;
11
+ }
12
+
13
+ type ThumbnailProps = Omit<FileThumbnailProps, 'bytesContentFamily' | 'removeFileCb'>;
14
+
15
+ export function FileThumbnail({ bytesContentFamily, removeFileCb, ...thumbnailProps }: FileThumbnailProps) {
16
+ const Thumbnail = useMemo(() => {
17
+ switch (bytesContentFamily) {
18
+ case 'image':
19
+ return ImageThumbnail;
20
+ case 'pdf':
21
+ return PDFThumbnail;
22
+ default:
23
+ return OtherThumbnail;
24
+ }
25
+ }, []);
26
+
27
+ return (
28
+ <div className={styles.thumbnail}>
29
+ <Thumbnail {...thumbnailProps} />
30
+ <Button kind="ghost" className={styles.removeButton} onClick={removeFileCb}>
31
+ <CloseFilled size={16} className={styles.closeIcon} />
32
+ </Button>
33
+ </div>
34
+ );
35
+ }
36
+
37
+ function ImageThumbnail(props: ThumbnailProps) {
38
+ return <img className={styles.imageThumbnail} src={props.src} alt={props.title} />;
39
+ }
40
+
41
+ function PDFThumbnail(props: ThumbnailProps) {
42
+ return (
43
+ <div className={styles.pdfThumbnail} role="button" tabIndex={0}>
44
+ <DocumentPdf size={24} />
45
+ </div>
46
+ );
47
+ }
48
+
49
+ function OtherThumbnail(props: ThumbnailProps) {
50
+ return (
51
+ <div className={styles.pdfThumbnail} role="button" tabIndex={0}>
52
+ <DocumentUnknown size={24} />
53
+ </div>
54
+ );
55
+ }
@@ -0,0 +1,42 @@
1
+ @use '@carbon/layout';
2
+ @use '@openmrs/esm-styleguide/src/vars' as vars;
3
+
4
+ .thumbnail {
5
+ height: 7rem;
6
+ width: 7rem;
7
+ }
8
+
9
+ .imageThumbnail {
10
+ max-width: 100%;
11
+ max-height: 100%;
12
+ object-fit: cover;
13
+ }
14
+
15
+ .pdfThumbnail {
16
+ background-color: vars.$color-gray-30;
17
+ display: flex;
18
+ justify-content: center;
19
+ align-items: center;
20
+ width: 100%;
21
+ height: 100%;
22
+ }
23
+
24
+ .removeButton {
25
+ position: absolute;
26
+ top: layout.$spacing-02;
27
+ right: layout.$spacing-02;
28
+ background-color: transparent;
29
+ border: none;
30
+ padding: 0;
31
+ cursor: pointer;
32
+ padding-block-start: 0;
33
+ --cds-layout-size-height-local: 0;
34
+ }
35
+
36
+ .removeButton:hover {
37
+ background-color: transparent;
38
+ }
39
+
40
+ .closeIcon {
41
+ fill: white !important;
42
+ }
@@ -1,56 +1,57 @@
1
- import React, { useState, useMemo, useCallback } from 'react';
2
- import { FileUploader, Button } from '@carbon/react';
1
+ import React, { useCallback } from 'react';
2
+ import { Button } from '@carbon/react';
3
3
  import { useTranslation } from 'react-i18next';
4
- import { isTrue } from '../../../utils/boolean-utils';
5
- import Camera from './camera/camera.component';
6
- import { Close, DocumentPdf } from '@carbon/react/icons';
4
+ import { Add } from '@carbon/react/icons';
7
5
  import styles from './file.scss';
8
- import { type FormFieldInputProps } from '../../../types';
6
+ import { type Attachment, type FormFieldInputProps } from '../../../types';
9
7
  import { useFormProviderContext } from '../../../provider/form-provider';
10
8
  import { isViewMode } from '../../../utils/common-utils';
11
9
  import FieldValueView from '../../value/view/field-value-view.component';
12
10
  import FieldLabel from '../../field-label/field-label.component';
11
+ import { showModal, type UploadedFile, useLayoutType } from '@openmrs/esm-framework';
12
+ import { FileThumbnail } from './file-thumbnail.component';
13
+ import classNames from 'classnames';
13
14
 
14
- type DataSourceType = 'filePicker' | 'camera' | null;
15
-
16
- const File: React.FC<FormFieldInputProps> = ({ field, value, setFieldValue }) => {
15
+ const File: React.FC<FormFieldInputProps<Array<Attachment>>> = ({ field, value, setFieldValue }) => {
17
16
  const { t } = useTranslation();
18
- const [cameraWidgetVisible, setCameraWidgetVisible] = useState(false);
19
- const [imagePreview, setImagePreview] = useState(null);
20
- const [dataSource, setDataSource] = useState<DataSourceType>(null);
21
17
  const { sessionMode } = useFormProviderContext();
18
+ const isTablet = useLayoutType() === 'tablet';
22
19
 
23
- const labelDescription = useMemo(() => {
24
- return field.questionOptions.allowedFileTypes
25
- ? t(
26
- 'fileUploadDescription',
27
- 'Upload one of the following file types: {{fileTypes}}',
28
- {
29
- fileTypes: field.questionOptions.allowedFileTypes.map(
30
- (eachItem) => ` ${eachItem}`,
31
- )
32
- }
33
- )
34
- : t('fileUploadDescriptionAny', 'Upload any file type');
35
- }, [field.questionOptions.allowedFileTypes, t]);
20
+ const showImageCaptureModal = useCallback(() => {
21
+ const close = showModal('capture-photo-modal', {
22
+ saveFile: (file: UploadedFile) => {
23
+ if (file.capturedFromWebcam && !file.fileName.includes('.')) {
24
+ file.fileName = `${file.fileName}.png`;
25
+ }
26
+ const currentFiles = value ? value : [];
27
+ setFieldValue([...currentFiles, file]);
28
+ close();
29
+ return Promise.resolve();
30
+ },
31
+ closeModal: () => {
32
+ close();
33
+ },
34
+ allowedExtensions: field.questionOptions.allowedFileTypes,
35
+ multipleFiles: field.questionOptions.allowMultiple,
36
+ collectDescription: true,
37
+ });
38
+ }, [value, field]);
36
39
 
37
- const handleFilePickerChange = useCallback(
38
- (event) => {
39
- // TODO: Add multiple file upload support; see: https://openmrs.atlassian.net/browse/O3-3682
40
- const [selectedFile]: File[] = Array.from(event.target.files);
41
- setImagePreview(null);
42
- setFieldValue(selectedFile);
40
+ const handleRemoveFile = useCallback(
41
+ (index: number) => {
42
+ const buffer = [...value];
43
+ const attachment = buffer[index];
44
+ if (attachment.uuid) {
45
+ buffer[index] = {
46
+ ...attachment,
47
+ voided: true,
48
+ };
49
+ } else {
50
+ buffer.splice(index, 1);
51
+ }
52
+ setFieldValue(buffer);
43
53
  },
44
- [setFieldValue],
45
- );
46
-
47
- const handleCameraImageChange = useCallback(
48
- (newImage) => {
49
- setImagePreview(newImage);
50
- setCameraWidgetVisible(false);
51
- setFieldValue(newImage);
52
- },
53
- [setFieldValue],
54
+ [value],
54
55
  );
55
56
 
56
57
  if (isViewMode(sessionMode) && !value) {
@@ -59,102 +60,37 @@ const File: React.FC<FormFieldInputProps> = ({ field, value, setFieldValue }) =>
59
60
  );
60
61
  }
61
62
 
62
- return isViewMode(sessionMode) ? (
63
+ return (
63
64
  <div>
64
- <div className={styles.label}>{t(field.label)}</div>
65
- <div className={styles.editModeImage}>
66
- <div className={styles.imageContent}>
67
- {value.bytesContentFamily === 'PDF' ? (
68
- <div className={styles.pdfThumbnail} role="button" tabIndex={0}>
69
- <DocumentPdf size={24} />
70
- </div>
71
- ) : (
72
- <img src={value.src} alt={t('preview', 'Preview')} width="200px" />
73
- )}
74
- </div>
75
- </div>
76
- </div>
77
- ) : (
78
- <div>
79
- <div className={styles.label}>
65
+ <div className={classNames(styles.label, 'cds--label')}>
80
66
  <FieldLabel field={field} />
81
67
  </div>
82
- <div className={styles.uploadSelector}>
83
- <div className={styles.selectorButton}>
84
- <Button disabled={isTrue(field.readonly)} onClick={() => setDataSource('filePicker')}>
85
- {t('uploadImage', 'Upload image')}
86
- </Button>
87
- </div>
88
- <div className={styles.selectorButton}>
89
- <Button disabled={isTrue(field.readonly)} onClick={() => setDataSource('camera')}>
90
- {t('cameraCapture', 'Camera capture')}
68
+ {!isViewMode(sessionMode) && (
69
+ <div>
70
+ <Button
71
+ className={styles.uploadButton}
72
+ kind={isTablet ? 'ghost' : 'tertiary'}
73
+ onClick={showImageCaptureModal}
74
+ renderIcon={(props) => <Add size={16} {...props} />}>
75
+ {field.questionOptions.buttonLabel ? t(field.questionOptions.buttonLabel) : t('addFile', 'Add file')}
91
76
  </Button>
92
77
  </div>
93
- </div>
94
- {!dataSource && value && (
95
- <div className={styles.editModeImage}>
96
- <div className={styles.imageContent}>
97
- {value.bytesContentFamily === 'PDF' ? (
98
- <div className={styles.pdfThumbnail} role="button" tabIndex={0}>
99
- <DocumentPdf size={24} />
100
- </div>
101
- ) : (
102
- <img src={value.src} alt="Preview" width="200px" />
103
- )}
104
- </div>
105
- </div>
106
78
  )}
107
- {dataSource === 'filePicker' && (
108
- <div className={styles.fileUploader}>
109
- <FileUploader
110
- accept={field.questionOptions.allowedFileTypes ?? []}
111
- buttonKind="primary"
112
- buttonLabel={t('addFile', 'Add files')}
113
- filenameStatus="edit"
114
- iconDescription={t('clearFile', 'Clear file')}
115
- labelDescription={labelDescription}
116
- labelTitle={t('upload', 'Upload')}
117
- // TODO: Add multiple file upload support; see: https://openmrs.atlassian.net/browse/O3-3682
118
- // multiple={field.questionOptions.allowMultiple}
119
- onChange={handleFilePickerChange}
120
- />
121
- </div>
122
- )}
123
- {dataSource === 'camera' && (
124
- <div className={styles.cameraUploader}>
125
- <div className={styles.camButton}>
126
- <p className={styles.titleStyles}>Camera</p>
127
- <p className={styles.descriptionStyles}>Capture image via camera</p>
128
- <Button onClick={() => setCameraWidgetVisible((prevState) => !prevState)} size="md">
129
- {cameraWidgetVisible ? t('closeCamera', 'Close camera') : t('addCameraImage', 'Add camera image')}
130
- </Button>
131
- </div>
132
- {cameraWidgetVisible && (
133
- <div className={styles.cameraPreview}>
134
- <Camera handleImages={handleCameraImageChange} />
135
- </div>
136
- )}
137
- {imagePreview && (
138
- <div className={styles.capturedImage}>
139
- <div className={styles.imageContent}>
140
- <img src={imagePreview} alt={t('preview', 'Preview')} width="200px" />
141
- <div className={styles.caption}>
142
- <p>{t('uploadedPhoto', 'Uploaded photo')}</p>
143
- <div
144
- tabIndex={0}
145
- role="button"
146
- onClick={() => {
147
- setImagePreview(null);
148
- }}
149
- className={styles.closeIcon}>
150
- <Close />
151
- </div>
152
- </div>
79
+ <div className={styles.thumbnailGrid}>
80
+ {value &&
81
+ value
82
+ .filter((file) => !file.voided)
83
+ .map((file, index) => (
84
+ <div className={styles.thumbnailContainer}>
85
+ <FileThumbnail
86
+ title={file.fileName}
87
+ src={file.base64Content}
88
+ bytesContentFamily={file.fileType}
89
+ removeFileCb={() => handleRemoveFile(index)}
90
+ />
153
91
  </div>
154
- </div>
155
- )}
156
- </div>
157
- )}
92
+ ))}
93
+ </div>
158
94
  </div>
159
95
  );
160
96
  };
@@ -1,101 +1,21 @@
1
1
  @use '@carbon/layout';
2
2
 
3
3
  .label {
4
- font-family: IBM Plex Sans;
5
- font-size: 14px;
6
- font-style: normal;
7
4
  font-weight: 600;
8
- line-height: 30px; /* 128.571% */
9
- letter-spacing: 0.16px;
5
+ line-height: 30px;
10
6
  color: #000000;
11
7
  }
12
8
 
13
- .saveFile {
14
- margin: 0;
15
- }
16
-
17
- .capturedImage {
18
- width: 100%;
19
- }
20
-
21
- .caption {
22
- display: flex;
23
- }
24
-
25
- .closeIcon {
26
- margin: 0 1rem;
27
- }
28
-
29
- .uploadSelector {
30
- display: flex;
31
- align-items: center;
32
- margin-bottom: 1rem;
33
- flex-wrap: wrap;
9
+ .thumbnailGrid {
10
+ display: grid;
11
+ grid-template-columns: repeat(auto-fill, 7rem);
34
12
  gap: layout.$spacing-05;
13
+ margin-top: layout.$spacing-05;
35
14
  }
36
15
 
37
- .caption {
38
- display: flex;
39
- margin-top: 5px;
40
- }
41
-
42
- .imageDescription {
43
- margin-top: 5px;
44
- }
45
-
46
- .fileUploader {
47
- background-color: rgb(255, 255, 255);
48
- padding: 1rem;
49
- }
50
-
51
- .cameraUploader {
52
- margin: 1rem 0;
53
- background-color: white;
54
- padding: 1rem;
55
- }
56
-
57
- .camButton {
58
- margin-bottom: 1rem;
59
- }
60
-
61
- .cameraPreview {
62
- margin-top: 1rem;
63
- }
64
-
65
- .imageContent {
66
- padding: 0;
67
- margin: 0;
68
- }
69
-
70
- .titleStyles {
71
- color: #161616;
72
- font-size: 0.875rem;
73
- font-weight: 600;
74
- letter-spacing: 0.16px;
75
- line-height: 1.2857;
76
- margin-bottom: 0.5rem;
77
- }
78
-
79
- .descriptionStyles {
80
- color: #525252;
81
- font-size: 0.875rem;
82
- font-weight: 400;
83
- letter-spacing: 0.16px;
84
- line-height: 1.28572;
85
- margin-bottom: 1rem;
86
- }
87
-
88
- .editModeImage {
89
- background-color: white;
90
- padding: 1rem;
91
- }
92
-
93
- .pdfThumbnail {
94
- cursor: pointer;
95
- background-color: gray;
16
+ .thumbnailContainer {
96
17
  display: flex;
97
- justify-content: center;
18
+ flex-direction: column;
98
19
  align-items: center;
99
- width: 5rem;
100
- height: 5rem;
20
+ position: relative;
101
21
  }
@@ -15,7 +15,7 @@ const WorkspaceLauncher: React.FC<FormFieldInputProps> = ({ field }) => {
15
15
 
16
16
  const handleLaunchWorkspace = () => {
17
17
  const workspaceName = field.questionOptions?.workspaceName;
18
- // TODO: properly check if workspace name is valid
18
+ // TODO: properly check if workspace name is valid
19
19
  // https://openmrs.atlassian.net/browse/O3-4976
20
20
  const isWorkspaceNameValid = true;
21
21
  if (!isWorkspaceNameValid) {
@@ -38,7 +38,9 @@ const WorkspaceLauncher: React.FC<FormFieldInputProps> = ({ field }) => {
38
38
  <div className={styles.label}>{t(field.label)}</div>
39
39
  <div className={styles.workspaceButton}>
40
40
  <Button disabled={isTrue(field.readonly)} onClick={handleLaunchWorkspace}>
41
- {t(field.questionOptions?.buttonLabel) ?? t('launchWorkspace', 'Launch Workspace')}
41
+ {field.questionOptions.buttonLabel
42
+ ? t(field.questionOptions.buttonLabel)
43
+ : t('launchWorkspace', 'Launch Workspace')}
42
44
  </Button>
43
45
  </div>
44
46
  </div>
@@ -185,9 +185,8 @@ export class EncounterFormProcessor extends FormProcessor {
185
185
  }
186
186
  // handle attachments
187
187
  try {
188
- const attachmentsResponse = await Promise.all(
189
- saveAttachments(context.formFields, savedEncounter, abortController),
190
- );
188
+ const attachmentsResponse = await saveAttachments(context.formFields, savedEncounter, abortController);
189
+
191
190
  if (attachmentsResponse?.length) {
192
191
  showSnackbar({
193
192
  title: t('attachmentsSaved', 'Attachment(s) saved successfully'),
@@ -196,6 +195,7 @@ export class EncounterFormProcessor extends FormProcessor {
196
195
  });
197
196
  }
198
197
  } catch (error) {
198
+ console.error('Error saving attachments', error);
199
199
  const errorMessages = extractErrorMessagesFromResponse(error);
200
200
  return Promise.reject({
201
201
  title: t('errorSavingAttachments', 'Error saving attachment(s)'),
@@ -206,6 +206,7 @@ export class EncounterFormProcessor extends FormProcessor {
206
206
  }
207
207
  return savedEncounter;
208
208
  } catch (error) {
209
+ console.error('Error saving encounter', error);
209
210
  const errorMessages = extractErrorMessagesFromResponse(error);
210
211
  return Promise.reject({
211
212
  title: t('errorSavingEncounter', 'Error saving encounter'),
@@ -1,4 +1,5 @@
1
1
  import {
2
+ type AttachmentFieldValue,
2
3
  type FormField,
3
4
  type FormProcessorContextProps,
4
5
  type OpenmrsEncounter,
@@ -7,7 +8,7 @@ import {
7
8
  type PatientProgram,
8
9
  type PatientProgramPayload,
9
10
  } from '../../types';
10
- import { saveAttachment, savePatientIdentifier, saveProgramEnrollment } from '../../api';
11
+ import { createAttachment, savePatientIdentifier, saveProgramEnrollment } from '../../api';
11
12
  import { hasRendering, hasSubmission } from '../../utils/common-utils';
12
13
  import dayjs from 'dayjs';
13
14
  import { assignedObsIds, constructObs, voidObs } from '../../adapters/obs-adapter';
@@ -144,19 +145,17 @@ export function savePatientPrograms(patientPrograms: PatientProgramPayload[]) {
144
145
  export function saveAttachments(fields: FormField[], encounter: OpenmrsEncounter, abortController: AbortController) {
145
146
  const complexFields = fields?.filter((field) => field?.questionOptions.rendering === 'file' && hasSubmission(field));
146
147
 
147
- if (!complexFields?.length) return [];
148
+ if (!complexFields?.length) {
149
+ return [];
150
+ }
148
151
 
149
- return complexFields.map((field) => {
152
+ const allPromises = complexFields.flatMap((field) => {
150
153
  const patientUuid = typeof encounter?.patient === 'string' ? encounter?.patient : encounter?.patient?.uuid;
151
- return saveAttachment(
152
- patientUuid,
153
- field,
154
- field?.questionOptions.concept,
155
- new Date().toISOString(),
156
- encounter?.uuid,
157
- abortController,
158
- );
154
+ const attachments = (field.meta.submission.newValue as AttachmentFieldValue[]) ?? [];
155
+ return attachments.map((attachment) => createAttachment(patientUuid, encounter.uuid, attachment));
159
156
  });
157
+
158
+ return Promise.all(allPromises);
160
159
  }
161
160
 
162
161
  export function getMutableSessionProps(context: FormContextProps) {
@@ -199,11 +198,14 @@ function processObsField(obsForSubmission: OpenmrsObs[], field: FormField) {
199
198
 
200
199
  if (field.type === 'obsGroup') {
201
200
  processObsGroup(obsForSubmission, field);
202
- } else if (hasSubmission(field)) {
203
- // For non-group obs with a submission
201
+ return;
202
+ }
203
+
204
+ // new attachments will be processed later
205
+ if (!hasRendering(field, 'file')) {
204
206
  addObsToList(obsForSubmission, field.meta.submission.newValue);
205
- addObsToList(obsForSubmission, field.meta.submission.voidedValue);
206
207
  }
208
+ addObsToList(obsForSubmission, field.meta.submission.voidedValue);
207
209
  }
208
210
 
209
211
  function processObsGroup(obsForSubmission: OpenmrsObs[], groupField: FormField) {
@@ -257,7 +259,7 @@ function hasSubmittableObs(field: FormField) {
257
259
  type,
258
260
  } = field;
259
261
 
260
- if (isTransient || !['obs', 'obsGroup'].includes(type) || hasRendering(field, 'file') || field.meta.groupId) {
262
+ if (isTransient || !['obs', 'obsGroup'].includes(type) || field.meta.groupId) {
261
263
  return false;
262
264
  }
263
265
  if ((field.isHidden || field.isParentHidden) && field.meta.initialValue?.omrsObject) {
@@ -16,6 +16,7 @@ import { getControlTemplate } from './inbuilt-components/control-templates';
16
16
  import { inbuiltPostSubmissionActions } from './inbuilt-components/InbuiltPostSubmissionActions';
17
17
  import { inbuiltFormTransformers } from './inbuilt-components/inbuiltTransformers';
18
18
  import { inbuiltFieldValueAdapters } from './inbuilt-components/inbuiltFieldValueAdapters';
19
+ import { hasRendering } from '../utils/common-utils';
19
20
 
20
21
  /**
21
22
  * @internal
@@ -143,7 +144,7 @@ export async function getRegisteredControl(renderType: string) {
143
144
  */
144
145
  export function getFieldControlWithFallback(question: FormField) {
145
146
  // Check if the question has a missing concept
146
- if (hasMissingConcept(question)) {
147
+ if (hasMissingConcept(question) && !hasRendering(question, 'file')) {
147
148
  // If so, render a disabled text input
148
149
  question.disabled = true;
149
150
  question.isDisabled = true;
@@ -1,4 +1,4 @@
1
- import { type OpenmrsResource } from '@openmrs/esm-framework';
1
+ import { type UploadedFile, type OpenmrsResource } from '@openmrs/esm-framework';
2
2
 
3
3
  export interface OpenmrsEncounter {
4
4
  uuid?: string;
@@ -108,14 +108,14 @@ export interface OpenmrsFormResource extends OpenmrsResource {
108
108
  valueReference?: string;
109
109
  }
110
110
 
111
- export interface Attachment {
112
- id: string;
113
- src: string;
114
- title: string;
115
- description: string;
116
- dateTime: string;
117
- bytesMimeType: string;
118
- bytesContentFamily: string;
111
+ export interface Attachment extends UploadedFile {
112
+ uuid?: string;
113
+ voided?: boolean;
114
+ }
115
+
116
+ export interface AttachmentFieldValue extends Attachment {
117
+ formFieldNamespace: string;
118
+ formFieldPath: string;
119
119
  }
120
120
 
121
121
  export interface AttachmentResponse {
@@ -118,8 +118,8 @@ export interface PostSubmissionAction {
118
118
  ): void;
119
119
  }
120
120
 
121
- export interface FormFieldInputProps {
122
- value: any;
121
+ export interface FormFieldInputProps<TValue = any> {
122
+ value: TValue;
123
123
  field: FormField;
124
124
  errors: ValidationResult[];
125
125
  warnings: ValidationResult[];
@@ -128,7 +128,7 @@ export interface FormFieldInputProps {
128
128
  *
129
129
  * @param value - The new value of the field.
130
130
  */
131
- setFieldValue: (value: any) => void;
131
+ setFieldValue: (value: TValue) => void;
132
132
  }
133
133
 
134
134
  /**
@@ -1,34 +0,0 @@
1
- import React from 'react';
2
- import Webcam from 'react-webcam';
3
- import { Button } from '@carbon/react';
4
- import { Camera as CameraIcon } from '@carbon/react/icons';
5
-
6
- import styles from './camera.scss';
7
-
8
- interface CameraProps {
9
- handleImages: (state: any) => void;
10
- }
11
-
12
- const Camera: React.FC<CameraProps> = ({ handleImages }) => {
13
- const webcamRef = React.useRef(null);
14
-
15
- const capture = React.useCallback(() => {
16
- const imageSrc = webcamRef.current.getScreenshot();
17
- handleImages(imageSrc);
18
- }, [webcamRef]);
19
-
20
- const videoConstraints = {
21
- facingMode: 'user',
22
- };
23
-
24
- return (
25
- <div>
26
- <Webcam audio={false} ref={webcamRef} screenshotFormat="image/png" videoConstraints={videoConstraints} />
27
- <div className={styles.captureButton}>
28
- <Button onClick={capture} type="button" hasIconOnly renderIcon={() => <CameraIcon size={24} />}></Button>
29
- </div>
30
- </div>
31
- );
32
- };
33
-
34
- export default Camera;
@@ -1,3 +0,0 @@
1
- .captureButton {
2
- margin: 0.5rem 0;
3
- }