From 8aa395d6d4fa2b1571ef454050232b5a55832848 Mon Sep 17 00:00:00 2001 From: Tharun Budidha Date: Tue, 16 Jun 2026 07:37:43 +0530 Subject: [PATCH] fix: load resumable upload SDK as native ESM (hide import() from Babel) --- amd/build/upload_widget.min.js | 4 ++-- amd/build/upload_widget.min.js.map | 2 +- amd/src/upload_widget.js | 10 +++++++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/amd/build/upload_widget.min.js b/amd/build/upload_widget.min.js index ead89d1..2664b8e 100644 --- a/amd/build/upload_widget.min.js +++ b/amd/build/upload_widget.min.js @@ -1,10 +1,10 @@ -define("mod_fastpix/upload_widget",["exports","core/ajax","core/notification","core/templates"],function(_exports,_ajax,_notification,_templates){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=_interopRequireDefault(_notification),_templates=_interopRequireDefault(_templates);var _systemImportTransformerGlobalIdentifier="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{}; +define("mod_fastpix/upload_widget",["exports","core/ajax","core/notification","core/templates"],function(_exports,_ajax,_notification,_templates){function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}} /** * Upload widget for the mod_fastpix activity edit form. * * @module mod_fastpix/upload_widget * @copyright 2026 FastPix * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}const UPLOAD_SDK_URL=M.cfg.wwwroot+"/mod/fastpix/thirdparty/resumable-uploads/uploads.js",SELECTORS_region='[data-region="fastpix-upload-widget"]',SELECTORS_dropzone='[data-region="fastpix-upload-dropzone"]',SELECTORS_input='[data-region="fastpix-upload-input"]',SELECTORS_browseLink='[data-action="fastpix-browse-trigger"]',SELECTORS_progressWrap='[data-region="fastpix-upload-progress"]',SELECTORS_progressBar='[data-region="fastpix-upload-bar"]',SELECTORS_progressBarFill='[data-region="fastpix-upload-bar-fill"]',SELECTORS_progressPct='[data-region="fastpix-upload-percent"]',SELECTORS_status='[data-region="fastpix-upload-status"]',SELECTORS_urlStatus='[data-region="fastpix-urlpull-status"]',SELECTORS_sourceType='[name="source_type"]',SELECTORS_sourceUrl='[name="source_url"]',SELECTORS_validateBtn='[name="validate_url"]';let widgetContextId=null,widgetStrings={};const gatherUploadArgs=file=>{const nameEl=document.querySelector('input[name="name"]');let title=nameEl?.value?nameEl.value.trim():"";title||(title=file?.name?file.name.replace(/\.[^.]+$/,""):widgetStrings.untitledvideo||"");const apEl=document.querySelector('input[name="access_policy"]:checked'),accesspolicy=apEl?apEl.value:"private";let captionsmode="none";if(document.querySelector('input[name="captionsenabled"]:checked')){const modeEl=document.querySelector('input[name="captionsmode"]:checked');captionsmode="vtt"===modeEl?.value?"vtt":"auto"}const langEl=document.querySelector('select[name="languagecode"]'),languagecode="auto"===captionsmode&&langEl?langEl.value:"";return{contextid:widgetContextId,title:title,accesspolicy:accesspolicy,captionsmode:captionsmode,languagecode:languagecode}},setSourceType=value=>{const el=document.querySelector(SELECTORS_sourceType);el&&(el.value=value,el.dispatchEvent(new Event("change",{bubbles:!0})))},setStatus=(region,message,kind)=>{const el=region.querySelector(SELECTORS_status);el&&(el.hidden=!1,el.textContent=message,el.className=`mt-3 small alert alert-${kind}`)},setUrlStatus=(message,kind)=>{const el=document.querySelector(SELECTORS_urlStatus);if(!el)return;if(!message)return el.textContent="",void(el.className="fastpix-vs-url-status");const state={success:"success",danger:"danger",warning:"warning"}[kind]||"muted";el.textContent=message,el.className="fastpix-vs-url-status fastpix-vs-url-status--"+state},putToSignedUrl=async(file,uploadUrl,onProgress)=>{let sdk;try{sdk=await(async()=>{if(window.__fastpixUploadSdk)return window.__fastpixUploadSdk;const mod=await("function"==typeof _systemImportTransformerGlobalIdentifier.define&&_systemImportTransformerGlobalIdentifier.define.amd?new Promise(function(resolve,reject){_systemImportTransformerGlobalIdentifier.require([UPLOAD_SDK_URL],resolve,reject)}):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&_systemImportTransformerGlobalIdentifier.require&&"component"===_systemImportTransformerGlobalIdentifier.require.loader?Promise.resolve(require(UPLOAD_SDK_URL)):Promise.resolve(_systemImportTransformerGlobalIdentifier[UPLOAD_SDK_URL]));if(!mod.Uploader||"function"!=typeof mod.Uploader.init)throw new Error("upload_sdk_no_init");const adapter={createUpload:opts=>mod.Uploader.init(opts)};return window.__fastpixUploadSdk=adapter,adapter})()}catch(e){throw new Error("upload_sdk_load_failed")}return new Promise((resolve,reject)=>{const upload=sdk.createUpload({endpoint:uploadUrl,file:file,chunkSize:16384,retryChunkAttempt:5});upload.on("progress",event=>{const raw="number"==typeof event?.detail?.progress?event.detail.progress:0,pct=Math.max(0,Math.min(100,Math.round(raw)));onProgress(pct)}),upload.on("success",()=>{onProgress(100),resolve()}),upload.on("error",event=>{const detail=event.detail||{},msg=detail.message||detail.toString?.()||"upload_failed";reject(new Error(msg))})})},showDropzoneUI=region=>{const dropzone=region.querySelector(SELECTORS_dropzone);dropzone&&(dropzone.hidden=!1);const progress=region.querySelector(SELECTORS_progressWrap);progress&&(progress.hidden=!0)},handleFileSelected=async(region,sessionField,file)=>{if(!file)return;let session;setSourceType("upload"),(region=>{const el=region.querySelector(SELECTORS_status);el&&(el.hidden=!0,el.textContent="",el.className="mt-3 small")})(region),(region=>{const progress=region.querySelector(SELECTORS_progressWrap);progress&&(progress.hidden=!1)})(region);try{[session]=await Promise.all((0,_ajax.call)([{methodname:"local_fastpix_create_upload_session",args:gatherUploadArgs(file)}]))}catch(e){return _notification.default.exception(e),showDropzoneUI(region),void setStatus(region,widgetStrings.sessionfailed,"danger")}const bar=region.querySelector(SELECTORS_progressBar),fill=region.querySelector(SELECTORS_progressBarFill),pct=region.querySelector(SELECTORS_progressPct);try{await putToSignedUrl(file,session.uploadurl,percent=>{bar&&(bar.value=percent),fill&&(fill.style.width=`${percent}%`),pct&&(pct.textContent=`${percent}%`)})}catch(e){return _notification.default.exception(e),showDropzoneUI(region),void setStatus(region,widgetStrings.failed,"danger")}sessionField.value=String(session.session_id||session.uploadid||""),(region=>{const progress=region.querySelector(SELECTORS_progressWrap);progress&&(progress.hidden=!0),setStatus(region,widgetStrings.complete,"success")})(region)},validateUrl=async(sessionField,button)=>{const urlInput=document.querySelector(SELECTORS_sourceUrl);if(!urlInput?.value)return void setUrlStatus(widgetStrings.urlenterfirst,"warning");let session;setSourceType("urlpull"),setUrlStatus(widgetStrings.uploading,"muted"),button&&(button.disabled=!0);try{[session]=await Promise.all((0,_ajax.call)([{methodname:"local_fastpix_create_url_pull_session",args:{source_url:urlInput.value,contextid:widgetContextId}}]))}catch(e){return _notification.default.exception(e),setUrlStatus(widgetStrings.urlrejected,"danger"),void(button&&(button.disabled=!1))}sessionField.value=String(session.session_id),setUrlStatus("✓ "+widgetStrings.urlaccepted,"success"),button&&(button.disabled=!1)};_exports.init=async config=>{widgetContextId=config.contextId,widgetStrings=config.strings||{};const region=document.querySelector(SELECTORS_region);if(!region)return;const sessionField=(fieldname=config.fieldnameSession,document.querySelector(`input[name="${fieldname}"]`));var fieldname;if(!sessionField)return;const validateBtn=document.querySelector(SELECTORS_validateBtn);validateBtn&&"1"!==validateBtn.dataset.fastpixWired&&(validateBtn.dataset.fastpixWired="1",validateBtn.addEventListener("click",e=>{e.preventDefault(),validateUrl(sessionField,validateBtn)}));const urlInput=document.querySelector(SELECTORS_sourceUrl);urlInput&&"1"!==urlInput.dataset.fastpixWired&&(urlInput.dataset.fastpixWired="1",urlInput.addEventListener("input",()=>{sessionField&&(sessionField.value=""),setUrlStatus("","muted")})),await(async region=>{const{html:html,js:js}=await _templates.default.renderForPromise("mod_fastpix/upload_widget",{});_templates.default.replaceNodeContents(region,html,js)})(region);const renderedUrlInput=document.querySelector(SELECTORS_sourceUrl),renderedValidateBtn=document.querySelector(SELECTORS_validateBtn);renderedValidateBtn&&"1"!==renderedValidateBtn.dataset.fastpixWired&&(renderedValidateBtn.dataset.fastpixWired="1",renderedValidateBtn.addEventListener("click",e=>{e.preventDefault(),validateUrl(sessionField,renderedValidateBtn)})),renderedUrlInput&&"1"!==renderedUrlInput.dataset.fastpixWired&&(renderedUrlInput.dataset.fastpixWired="1",renderedUrlInput.addEventListener("input",()=>{sessionField&&(sessionField.value=""),setUrlStatus("","muted")})),((region,sessionField)=>{const dropzone=region.querySelector(SELECTORS_dropzone),input=region.querySelector(SELECTORS_input),browse=region.querySelector(SELECTORS_browseLink);if(!dropzone||!input)return;browse&&browse.addEventListener("click",e=>{e.preventDefault(),input.click()}),dropzone.addEventListener("click",e=>{e.target!==input&&e.target!==browse&&input.click()}),dropzone.addEventListener("keydown",e=>{"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),input.click())});const setDragging=on=>{dropzone.classList.toggle("is-dragging",!!on)};["dragenter","dragover"].forEach(ev=>dropzone.addEventListener(ev,e=>{e.preventDefault(),e.stopPropagation(),setDragging(!0)})),["dragleave","dragend"].forEach(ev=>dropzone.addEventListener(ev,e=>{e.preventDefault(),e.stopPropagation(),setDragging(!1)})),dropzone.addEventListener("drop",e=>{e.preventDefault(),e.stopPropagation(),setDragging(!1);const file=e.dataTransfer?.files?.[0];file&&handleFileSelected(region,sessionField,file)}),input.addEventListener("change",()=>{const file=input.files?.[0];file&&handleFileSelected(region,sessionField,file)})})(region,sessionField)}}); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=_interopRequireDefault(_notification),_templates=_interopRequireDefault(_templates);const UPLOAD_SDK_URL=M.cfg.wwwroot+"/mod/fastpix/thirdparty/resumable-uploads/uploads.js",SELECTORS_region='[data-region="fastpix-upload-widget"]',SELECTORS_dropzone='[data-region="fastpix-upload-dropzone"]',SELECTORS_input='[data-region="fastpix-upload-input"]',SELECTORS_browseLink='[data-action="fastpix-browse-trigger"]',SELECTORS_progressWrap='[data-region="fastpix-upload-progress"]',SELECTORS_progressBar='[data-region="fastpix-upload-bar"]',SELECTORS_progressBarFill='[data-region="fastpix-upload-bar-fill"]',SELECTORS_progressPct='[data-region="fastpix-upload-percent"]',SELECTORS_status='[data-region="fastpix-upload-status"]',SELECTORS_urlStatus='[data-region="fastpix-urlpull-status"]',SELECTORS_sourceType='[name="source_type"]',SELECTORS_sourceUrl='[name="source_url"]',SELECTORS_validateBtn='[name="validate_url"]';let widgetContextId=null,widgetStrings={};const gatherUploadArgs=file=>{const nameEl=document.querySelector('input[name="name"]');let title=nameEl?.value?nameEl.value.trim():"";title||(title=file?.name?file.name.replace(/\.[^.]+$/,""):widgetStrings.untitledvideo||"");const apEl=document.querySelector('input[name="access_policy"]:checked'),accesspolicy=apEl?apEl.value:"private";let captionsmode="none";if(document.querySelector('input[name="captionsenabled"]:checked')){const modeEl=document.querySelector('input[name="captionsmode"]:checked');captionsmode="vtt"===modeEl?.value?"vtt":"auto"}const langEl=document.querySelector('select[name="languagecode"]'),languagecode="auto"===captionsmode&&langEl?langEl.value:"";return{contextid:widgetContextId,title:title,accesspolicy:accesspolicy,captionsmode:captionsmode,languagecode:languagecode}},setSourceType=value=>{const el=document.querySelector(SELECTORS_sourceType);el&&(el.value=value,el.dispatchEvent(new Event("change",{bubbles:!0})))},setStatus=(region,message,kind)=>{const el=region.querySelector(SELECTORS_status);el&&(el.hidden=!1,el.textContent=message,el.className=`mt-3 small alert alert-${kind}`)},setUrlStatus=(message,kind)=>{const el=document.querySelector(SELECTORS_urlStatus);if(!el)return;if(!message)return el.textContent="",void(el.className="fastpix-vs-url-status");const state={success:"success",danger:"danger",warning:"warning"}[kind]||"muted";el.textContent=message,el.className="fastpix-vs-url-status fastpix-vs-url-status--"+state},loadUploadSdk=async()=>{if(window.__fastpixUploadSdk)return window.__fastpixUploadSdk;const mod=await(url=UPLOAD_SDK_URL,Function("u","return import(u);")(url));var url;if(!mod.Uploader||"function"!=typeof mod.Uploader.init)throw new Error("upload_sdk_no_init");const adapter={createUpload:opts=>mod.Uploader.init(opts)};return window.__fastpixUploadSdk=adapter,adapter},showDropzoneUI=region=>{const dropzone=region.querySelector(SELECTORS_dropzone);dropzone&&(dropzone.hidden=!1);const progress=region.querySelector(SELECTORS_progressWrap);progress&&(progress.hidden=!0)},handleFileSelected=async(region,sessionField,file)=>{if(!file)return;let session;setSourceType("upload"),(region=>{const el=region.querySelector(SELECTORS_status);el&&(el.hidden=!0,el.textContent="",el.className="mt-3 small")})(region),(region=>{const progress=region.querySelector(SELECTORS_progressWrap);progress&&(progress.hidden=!1)})(region);try{[session]=await Promise.all((0,_ajax.call)([{methodname:"local_fastpix_create_upload_session",args:gatherUploadArgs(file)}]))}catch(e){return _notification.default.exception(e),showDropzoneUI(region),void setStatus(region,widgetStrings.sessionfailed,"danger")}const bar=region.querySelector(SELECTORS_progressBar),fill=region.querySelector(SELECTORS_progressBarFill),pct=region.querySelector(SELECTORS_progressPct);try{await(async(file,uploadUrl,onProgress)=>{let sdk;try{sdk=await loadUploadSdk()}catch(e){throw new Error("upload_sdk_load_failed")}return new Promise((resolve,reject)=>{const upload=sdk.createUpload({endpoint:uploadUrl,file:file,chunkSize:16384,retryChunkAttempt:5});upload.on("progress",event=>{const raw="number"==typeof event?.detail?.progress?event.detail.progress:0,pct=Math.max(0,Math.min(100,Math.round(raw)));onProgress(pct)}),upload.on("success",()=>{onProgress(100),resolve()}),upload.on("error",event=>{const detail=event.detail||{},msg=detail.message||detail.toString?.()||"upload_failed";reject(new Error(msg))})})})(file,session.uploadurl,percent=>{bar&&(bar.value=percent),fill&&(fill.style.width=`${percent}%`),pct&&(pct.textContent=`${percent}%`)})}catch(e){return _notification.default.exception(e),showDropzoneUI(region),void setStatus(region,widgetStrings.failed,"danger")}sessionField.value=String(session.session_id||session.uploadid||""),(region=>{const progress=region.querySelector(SELECTORS_progressWrap);progress&&(progress.hidden=!0),setStatus(region,widgetStrings.complete,"success")})(region)},validateUrl=async(sessionField,button)=>{const urlInput=document.querySelector(SELECTORS_sourceUrl);if(!urlInput?.value)return void setUrlStatus(widgetStrings.urlenterfirst,"warning");let session;setSourceType("urlpull"),setUrlStatus(widgetStrings.uploading,"muted"),button&&(button.disabled=!0);try{[session]=await Promise.all((0,_ajax.call)([{methodname:"local_fastpix_create_url_pull_session",args:{source_url:urlInput.value,contextid:widgetContextId}}]))}catch(e){return _notification.default.exception(e),setUrlStatus(widgetStrings.urlrejected,"danger"),void(button&&(button.disabled=!1))}sessionField.value=String(session.session_id),setUrlStatus("✓ "+widgetStrings.urlaccepted,"success"),button&&(button.disabled=!1)};_exports.init=async config=>{widgetContextId=config.contextId,widgetStrings=config.strings||{};const region=document.querySelector(SELECTORS_region);if(!region)return;const sessionField=(fieldname=config.fieldnameSession,document.querySelector(`input[name="${fieldname}"]`));var fieldname;if(!sessionField)return;const validateBtn=document.querySelector(SELECTORS_validateBtn);validateBtn&&"1"!==validateBtn.dataset.fastpixWired&&(validateBtn.dataset.fastpixWired="1",validateBtn.addEventListener("click",e=>{e.preventDefault(),validateUrl(sessionField,validateBtn)}));const urlInput=document.querySelector(SELECTORS_sourceUrl);urlInput&&"1"!==urlInput.dataset.fastpixWired&&(urlInput.dataset.fastpixWired="1",urlInput.addEventListener("input",()=>{sessionField&&(sessionField.value=""),setUrlStatus("","muted")})),await(async region=>{const{html:html,js:js}=await _templates.default.renderForPromise("mod_fastpix/upload_widget",{});_templates.default.replaceNodeContents(region,html,js)})(region);const renderedUrlInput=document.querySelector(SELECTORS_sourceUrl),renderedValidateBtn=document.querySelector(SELECTORS_validateBtn);renderedValidateBtn&&"1"!==renderedValidateBtn.dataset.fastpixWired&&(renderedValidateBtn.dataset.fastpixWired="1",renderedValidateBtn.addEventListener("click",e=>{e.preventDefault(),validateUrl(sessionField,renderedValidateBtn)})),renderedUrlInput&&"1"!==renderedUrlInput.dataset.fastpixWired&&(renderedUrlInput.dataset.fastpixWired="1",renderedUrlInput.addEventListener("input",()=>{sessionField&&(sessionField.value=""),setUrlStatus("","muted")})),((region,sessionField)=>{const dropzone=region.querySelector(SELECTORS_dropzone),input=region.querySelector(SELECTORS_input),browse=region.querySelector(SELECTORS_browseLink);if(!dropzone||!input)return;browse&&browse.addEventListener("click",e=>{e.preventDefault(),input.click()}),dropzone.addEventListener("click",e=>{e.target!==input&&e.target!==browse&&input.click()}),dropzone.addEventListener("keydown",e=>{"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),input.click())});const setDragging=on=>{dropzone.classList.toggle("is-dragging",!!on)};["dragenter","dragover"].forEach(ev=>dropzone.addEventListener(ev,e=>{e.preventDefault(),e.stopPropagation(),setDragging(!0)})),["dragleave","dragend"].forEach(ev=>dropzone.addEventListener(ev,e=>{e.preventDefault(),e.stopPropagation(),setDragging(!1)})),dropzone.addEventListener("drop",e=>{e.preventDefault(),e.stopPropagation(),setDragging(!1);const file=e.dataTransfer?.files?.[0];file&&handleFileSelected(region,sessionField,file)}),input.addEventListener("change",()=>{const file=input.files?.[0];file&&handleFileSelected(region,sessionField,file)})})(region,sessionField)}}); //# sourceMappingURL=upload_widget.min.js.map \ No newline at end of file diff --git a/amd/build/upload_widget.min.js.map b/amd/build/upload_widget.min.js.map index 3ace5f9..6151346 100644 --- a/amd/build/upload_widget.min.js.map +++ b/amd/build/upload_widget.min.js.map @@ -1 +1 @@ -{"version":3,"file":"upload_widget.min.js","sources":["../src/upload_widget.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Upload widget for the mod_fastpix activity edit form.\n *\n * @module mod_fastpix/upload_widget\n * @copyright 2026 FastPix\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n// AMD module for the mod_fastpix activity edit form upload widget.\n//\n// Phase C unified panel:\n// - Single panel showing drop zone + URL row at once (no pill toggle).\n// - File drop or click → set hidden source_type='upload', auto-start upload.\n// - URL + Upload button → set hidden source_type='urlpull', validate via\n// local_fastpix_create_url_pull_session.\n// - Hidden source_type field is mutated via .value= and a dispatched\n// change event so any consumer (server-side validation, mform hideIf)\n// sees the active mode.\n//\n// Calls local_fastpix_create_upload_session / local_fastpix_create_url_pull_session\n// via core/ajax (CC2). Per A2: zero direct calls to the video CDN — the\n// signed upload URL is PUT to using fetch/XHR but the URL is supplied\n// by local_fastpix, not constructed here.\n\nimport {call as ajaxCall} from 'core/ajax';\nimport Notification from 'core/notification';\nimport Templates from 'core/templates';\n\n/**\n * FastPix resumable upload SDK — npm: @fastpix/resumable-uploads.\n *\n * Loaded via native ESM import() so it sits outside Moodle's RequireJS\n * context (same strategy view.php uses for @fastpix/fp-player). Splits\n * the file into chunkSize-KB pieces, uses the GCS two-step resumable\n * protocol (POST x-goog-resumable: start → PUT chunks with Content-Range),\n * retries failed chunks, and resumes from the last completed chunk on\n * network failure — so multi-GB uploads survive transient disconnects.\n *\n * Requires local_fastpix v >= 2026051201 (which sends X-Client-Type:\n * web-browser on /v1/on-demand/upload so FastPix returns a POST-signed\n * resumable URL — earlier versions returned a PUT-signed URL the SDK\n * cannot use and the initial POST would return 405).\n *\n * https://www.npmjs.com/package/@fastpix/resumable-uploads\n *\n * Vendored locally (Moodle requires all JS served from the site, not a CDN).\n * This is the upstream self-contained ESM build (dist/uploads.mjs, v1.0.5)\n * saved as .js for static-MIME safety. The pinned version is tracked in\n * thirdpartylibs.xml. Built from wwwroot at runtime so it works under any\n * Moodle subdirectory install.\n */\nconst UPLOAD_SDK_URL = M.cfg.wwwroot + '/mod/fastpix/thirdparty/resumable-uploads/uploads.js';\n\n/** Chunk size in KB (16 MB — matches the SDK default). */\nconst UPLOAD_CHUNK_KB = 16384;\n\n/** Per-chunk retry attempts before the upload gives up. */\nconst UPLOAD_CHUNK_ATTEMPTS = 5;\n\nconst SELECTORS = {\n region: '[data-region=\"fastpix-upload-widget\"]',\n picker: '[data-region=\"fastpix-upload-picker\"]',\n dropzone: '[data-region=\"fastpix-upload-dropzone\"]',\n input: '[data-region=\"fastpix-upload-input\"]',\n browseLink: '[data-action=\"fastpix-browse-trigger\"]',\n progressWrap: '[data-region=\"fastpix-upload-progress\"]',\n progressBar: '[data-region=\"fastpix-upload-bar\"]',\n progressBarFill: '[data-region=\"fastpix-upload-bar-fill\"]',\n progressPct: '[data-region=\"fastpix-upload-percent\"]',\n status: '[data-region=\"fastpix-upload-status\"]',\n urlStatus: '[data-region=\"fastpix-urlpull-status\"]',\n sourceType: '[name=\"source_type\"]',\n sourceUrl: '[name=\"source_url\"]',\n validateBtn: '[name=\"validate_url\"]',\n};\n\n// Course context id received from init() — forwarded to local_fastpix's upload\n// web services so they check mod/fastpix:uploadmedia against the course context\n// (where editing teachers hold the capability), not the system context.\nlet widgetContextId = null;\n\n// Localised status strings supplied by init() (M7 — no English literals in JS).\nlet widgetStrings = {};\n\nconst getSessionField = (fieldname) => document.querySelector(`input[name=\"${fieldname}\"]`);\n\n/**\n * Read the form's Media-settings fields into the create_upload_session args.\n * local_fastpix_create_upload_session consumes: contextid, title (required),\n * accesspolicy (required), captionsmode, languagecode — NOT filename/size.\n *\n * @param {File} file The file being uploaded (filename → fallback title).\n * @returns {Object} The web-service args.\n */\nconst gatherUploadArgs = (file) => {\n const nameEl = document.querySelector('input[name=\"name\"]');\n let title = nameEl?.value ? nameEl.value.trim() : '';\n if (!title) {\n title = file?.name ? file.name.replace(/\\.[^.]+$/, '') : (widgetStrings.untitledvideo || '');\n }\n const apEl = document.querySelector('input[name=\"access_policy\"]:checked');\n const accesspolicy = apEl ? apEl.value : 'private';\n let captionsmode = 'none';\n if (document.querySelector('input[name=\"captionsenabled\"]:checked')) {\n const modeEl = document.querySelector('input[name=\"captionsmode\"]:checked');\n captionsmode = (modeEl?.value === 'vtt') ? 'vtt' : 'auto';\n }\n const langEl = document.querySelector('select[name=\"languagecode\"]');\n const languagecode = (captionsmode === 'auto' && langEl) ? langEl.value : '';\n return {\n contextid: widgetContextId,\n title: title,\n accesspolicy: accesspolicy,\n captionsmode: captionsmode,\n languagecode: languagecode,\n };\n};\n\nconst setSourceType = (value) => {\n const el = document.querySelector(SELECTORS.sourceType);\n if (!el) { return; }\n el.value = value;\n el.dispatchEvent(new Event('change', {bubbles: true}));\n};\n\nconst setStatus = (region, message, kind) => {\n const el = region.querySelector(SELECTORS.status);\n if (!el) { return; }\n el.hidden = false;\n el.textContent = message;\n el.className = `mt-3 small alert alert-${kind}`;\n};\n\nconst clearStatus = (region) => {\n const el = region.querySelector(SELECTORS.status);\n if (!el) { return; }\n el.hidden = true;\n el.textContent = '';\n el.className = 'mt-3 small';\n};\n\nconst setUrlStatus = (message, kind) => {\n const el = document.querySelector(SELECTORS.urlStatus);\n if (!el) { return; }\n if (!message) {\n el.textContent = '';\n el.className = 'fastpix-vs-url-status';\n return;\n }\n const states = {success: 'success', danger: 'danger', warning: 'warning'};\n const state = states[kind] || 'muted';\n el.textContent = message;\n el.className = 'fastpix-vs-url-status fastpix-vs-url-status--' + state;\n};\n\n/**\n * Lazy-load the FastPix resumable upload SDK. Cached on `window` so the\n * dynamic import only runs once per page lifecycle even if the user\n * uploads multiple files. @fastpix/resumable-uploads exposes\n * `Uploader.init(opts)` — a STATIC factory, not a constructor.\n *\n * @returns {Promise<{createUpload: (opts: Object) => Object}>}\n */\nconst loadUploadSdk = async () => {\n if (window.__fastpixUploadSdk) {\n return window.__fastpixUploadSdk;\n }\n const mod = await import(UPLOAD_SDK_URL);\n if (!mod.Uploader || typeof mod.Uploader.init !== 'function') {\n throw new Error('upload_sdk_no_init');\n }\n const adapter = {\n createUpload: (opts) => mod.Uploader.init(opts),\n };\n window.__fastpixUploadSdk = adapter;\n return adapter;\n};\n\n/**\n * Resumable + chunked upload to a FastPix POST-signed resumable URL.\n *\n * - File is split into UPLOAD_CHUNK_KB chunks.\n * - Each chunk PUTs to the session URI with Content-Range; failed\n * chunks retry up to UPLOAD_CHUNK_ATTEMPTS times.\n * - On a dropped connection, the SDK resumes from the last completed\n * chunk instead of restarting the entire upload.\n * - Progress is reported as a 0..100 percentage of total bytes.\n *\n * @param {File} file\n * @param {string} uploadUrl POST-signed resumable URL from local_fastpix.\n * @param {(percent: number) => void} onProgress\n * @returns {Promise} resolves on `success`.\n */\nconst putToSignedUrl = async (file, uploadUrl, onProgress) => {\n let sdk;\n try {\n sdk = await loadUploadSdk();\n } catch (e) {\n throw new Error('upload_sdk_load_failed');\n }\n\n return new Promise((resolve, reject) => {\n const upload = sdk.createUpload({\n endpoint: uploadUrl,\n file: file,\n chunkSize: UPLOAD_CHUNK_KB,\n retryChunkAttempt: UPLOAD_CHUNK_ATTEMPTS,\n });\n\n upload.on('progress', (event) => {\n // FastPix resumable SDK reports the percentage at\n // event.detail.progress (0..100) — NOT event.detail. Reading\n // event.detail rounded an object to NaN, leaving the bar at 0%.\n const raw = (typeof event?.detail?.progress === 'number')\n ? event.detail.progress\n : 0;\n const pct = Math.max(0, Math.min(100, Math.round(raw)));\n onProgress(pct);\n });\n\n upload.on('success', () => {\n onProgress(100);\n resolve();\n });\n\n upload.on('error', (event) => {\n const detail = event.detail || {};\n const msg = detail.message || detail.toString?.() || 'upload_failed';\n reject(new Error(msg));\n });\n });\n};\n\nconst showProgressUI = (region) => {\n // Keep the upload dropzone visible during upload (don't hide it) — just\n // reveal the progress region below it.\n const progress = region.querySelector(SELECTORS.progressWrap);\n if (progress) { progress.hidden = false; }\n};\n\nconst showSuccessUI = (region) => {\n const progress = region.querySelector(SELECTORS.progressWrap);\n if (progress) { progress.hidden = true; }\n // Show just the completion state — not the filename.\n setStatus(region, widgetStrings.complete, 'success');\n};\n\nconst showDropzoneUI = (region) => {\n const dropzone = region.querySelector(SELECTORS.dropzone);\n if (dropzone) { dropzone.hidden = false; }\n const progress = region.querySelector(SELECTORS.progressWrap);\n if (progress) { progress.hidden = true; }\n};\n\nconst handleFileSelected = async (region, sessionField, file) => {\n if (!file) { return; }\n setSourceType('upload');\n clearStatus(region);\n showProgressUI(region);\n\n let session;\n try {\n [session] = await Promise.all(ajaxCall([{\n methodname: 'local_fastpix_create_upload_session',\n args: gatherUploadArgs(file),\n }]));\n } catch (e) {\n Notification.exception(e);\n showDropzoneUI(region);\n setStatus(region, widgetStrings.sessionfailed, 'danger');\n return;\n }\n\n const bar = region.querySelector(SELECTORS.progressBar);\n const fill = region.querySelector(SELECTORS.progressBarFill);\n const pct = region.querySelector(SELECTORS.progressPct);\n\n try {\n // create_upload_session returns 'uploadurl' (signed PUT target) +\n // 'uploadid'. NOTE the no-underscore field names — they differ from\n // create_url_pull_session ('upload_url' / 'session_id').\n await putToSignedUrl(file, session.uploadurl, (percent) => {\n if (bar) { bar.value = percent; }\n if (fill) { fill.style.width = `${percent}%`; }\n if (pct) { pct.textContent = `${percent}%`; }\n });\n } catch (e) {\n Notification.exception(e);\n showDropzoneUI(region);\n setStatus(region, widgetStrings.failed, 'danger');\n return;\n }\n\n // Persist the upload-session reference for the activity row. create_upload_session\n // currently returns only 'uploadid' (a FastPix UUID), not the integer session id\n // that create_url_pull_session returns — prefer session_id if present.\n sessionField.value = String(session.session_id || session.uploadid || '');\n showSuccessUI(region);\n};\n\nconst wireDropzone = (region, sessionField) => {\n const dropzone = region.querySelector(SELECTORS.dropzone);\n const input = region.querySelector(SELECTORS.input);\n const browse = region.querySelector(SELECTORS.browseLink);\n if (!dropzone || !input) { return; }\n\n // Browse link (rendered above the transparent file input z-index-wise).\n if (browse) {\n browse.addEventListener('click', (e) => {\n e.preventDefault();\n input.click();\n });\n }\n\n dropzone.addEventListener('click', (e) => {\n // The browse link handles its own click. The native input also catches\n // clicks directly via z-index. Avoid double-trigger.\n if (e.target === input || e.target === browse) { return; }\n input.click();\n });\n\n dropzone.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n input.click();\n }\n });\n\n const setDragging = (on) => {\n dropzone.classList.toggle('is-dragging', !!on);\n };\n\n ['dragenter', 'dragover'].forEach((ev) => dropzone.addEventListener(ev, (e) => {\n e.preventDefault();\n e.stopPropagation();\n setDragging(true);\n }));\n ['dragleave', 'dragend'].forEach((ev) => dropzone.addEventListener(ev, (e) => {\n e.preventDefault();\n e.stopPropagation();\n setDragging(false);\n }));\n dropzone.addEventListener('drop', (e) => {\n e.preventDefault();\n e.stopPropagation();\n setDragging(false);\n const file = e.dataTransfer?.files?.[0];\n if (file) {\n handleFileSelected(region, sessionField, file);\n }\n });\n\n input.addEventListener('change', () => {\n const file = input.files?.[0];\n if (file) {\n handleFileSelected(region, sessionField, file);\n }\n });\n};\n\nconst validateUrl = async (sessionField, button) => {\n const urlInput = document.querySelector(SELECTORS.sourceUrl);\n if (!urlInput?.value) {\n setUrlStatus(widgetStrings.urlenterfirst, 'warning');\n return;\n }\n\n setSourceType('urlpull');\n setUrlStatus(widgetStrings.uploading, 'muted');\n if (button) { button.disabled = true; }\n\n let session;\n try {\n [session] = await Promise.all(ajaxCall([{\n methodname: 'local_fastpix_create_url_pull_session',\n args: { source_url: urlInput.value, contextid: widgetContextId },\n }]));\n } catch (e) {\n Notification.exception(e);\n setUrlStatus(widgetStrings.urlrejected, 'danger');\n if (button) { button.disabled = false; }\n return;\n }\n\n sessionField.value = String(session.session_id);\n setUrlStatus('✓ ' + widgetStrings.urlaccepted, 'success');\n if (button) { button.disabled = false; }\n};\n\nconst renderInto = async (region) => {\n const {html, js} = await Templates.renderForPromise('mod_fastpix/upload_widget', {});\n Templates.replaceNodeContents(region, html, js);\n};\n\nexport const init = async (config) => {\n widgetContextId = config.contextId;\n widgetStrings = config.strings || {};\n const region = document.querySelector(SELECTORS.region);\n if (!region) { return; }\n\n const sessionField = getSessionField(config.fieldnameSession);\n if (!sessionField) { return; }\n\n // Wire the URL Upload button at outer-document scope BEFORE the inner\n // template renders, so the listener is in place even if the user\n // somehow clicks before render completes.\n const validateBtn = document.querySelector(SELECTORS.validateBtn);\n if (validateBtn && validateBtn.dataset.fastpixWired !== '1') {\n validateBtn.dataset.fastpixWired = '1';\n validateBtn.addEventListener('click', (e) => {\n e.preventDefault();\n validateUrl(sessionField, validateBtn);\n });\n }\n\n // Editing the URL invalidates a prior validate (so the form doesn't\n // submit a stale upload_session_id against a new URL).\n const urlInput = document.querySelector(SELECTORS.sourceUrl);\n if (urlInput && urlInput.dataset.fastpixWired !== '1') {\n urlInput.dataset.fastpixWired = '1';\n urlInput.addEventListener('input', () => {\n if (sessionField) { sessionField.value = ''; }\n setUrlStatus('', 'muted');\n });\n }\n\n await renderInto(region);\n\n // After render, re-query for URL input + validate button (template may\n // replace them) and wire dropzone.\n const renderedUrlInput = document.querySelector(SELECTORS.sourceUrl);\n const renderedValidateBtn = document.querySelector(SELECTORS.validateBtn);\n if (renderedValidateBtn && renderedValidateBtn.dataset.fastpixWired !== '1') {\n renderedValidateBtn.dataset.fastpixWired = '1';\n renderedValidateBtn.addEventListener('click', (e) => {\n e.preventDefault();\n validateUrl(sessionField, renderedValidateBtn);\n });\n }\n if (renderedUrlInput && renderedUrlInput.dataset.fastpixWired !== '1') {\n renderedUrlInput.dataset.fastpixWired = '1';\n renderedUrlInput.addEventListener('input', () => {\n if (sessionField) { sessionField.value = ''; }\n setUrlStatus('', 'muted');\n });\n }\n\n wireDropzone(region, sessionField);\n};\n"],"names":["_notification","_interopRequireDefault","_templates","_systemImportTransformerGlobalIdentifier","window","self","global","e","__esModule","default","UPLOAD_SDK_URL","M","cfg","wwwroot","SELECTORS","widgetContextId","widgetStrings","gatherUploadArgs","file","nameEl","document","querySelector","title","value","trim","name","replace","untitledvideo","apEl","accesspolicy","captionsmode","modeEl","langEl","languagecode","contextid","setSourceType","el","dispatchEvent","Event","bubbles","setStatus","region","message","kind","hidden","textContent","className","setUrlStatus","state","success","danger","warning","putToSignedUrl","async","uploadUrl","onProgress","sdk","__fastpixUploadSdk","mod","define","amd","Promise","resolve","reject","require","module","exports","component","loader","Uploader","init","Error","adapter","createUpload","opts","loadUploadSdk","upload","endpoint","chunkSize","retryChunkAttempt","on","event","raw","detail","progress","pct","Math","max","min","round","msg","toString","showDropzoneUI","dropzone","handleFileSelected","sessionField","session","clearStatus","showProgressUI","all","ajaxCall","call","methodname","args","Notification","exception","sessionfailed","bar","fill","uploadurl","percent","style","width","failed","String","session_id","uploadid","complete","showSuccessUI","validateUrl","button","urlInput","urlenterfirst","uploading","disabled","source_url","urlrejected","urlaccepted","_exports","config","contextId","strings","fieldname","fieldnameSession","validateBtn","dataset","fastpixWired","addEventListener","preventDefault","html","js","Templates","renderForPromise","replaceNodeContents","renderInto","renderedUrlInput","renderedValidateBtn","wireDropzone","input","browse","click","target","key","setDragging","classList","toggle","forEach","ev","stopPropagation","dataTransfer","files"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"upload_widget.min.js","sources":["../src/upload_widget.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Upload widget for the mod_fastpix activity edit form.\n *\n * @module mod_fastpix/upload_widget\n * @copyright 2026 FastPix\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n// AMD module for the mod_fastpix activity edit form upload widget.\n//\n// Phase C unified panel:\n// - Single panel showing drop zone + URL row at once (no pill toggle).\n// - File drop or click → set hidden source_type='upload', auto-start upload.\n// - URL + Upload button → set hidden source_type='urlpull', validate via\n// local_fastpix_create_url_pull_session.\n// - Hidden source_type field is mutated via .value= and a dispatched\n// change event so any consumer (server-side validation, mform hideIf)\n// sees the active mode.\n//\n// Calls local_fastpix_create_upload_session / local_fastpix_create_url_pull_session\n// via core/ajax (CC2). Per A2: zero direct calls to the video CDN — the\n// signed upload URL is PUT to using fetch/XHR but the URL is supplied\n// by local_fastpix, not constructed here.\n\nimport {call as ajaxCall} from 'core/ajax';\nimport Notification from 'core/notification';\nimport Templates from 'core/templates';\n\n/**\n * FastPix resumable upload SDK — npm: @fastpix/resumable-uploads.\n *\n * Loaded via native ESM import() so it sits outside Moodle's RequireJS\n * context (same strategy view.php uses for @fastpix/fp-player). Splits\n * the file into chunkSize-KB pieces, uses the GCS two-step resumable\n * protocol (POST x-goog-resumable: start → PUT chunks with Content-Range),\n * retries failed chunks, and resumes from the last completed chunk on\n * network failure — so multi-GB uploads survive transient disconnects.\n *\n * Requires local_fastpix v >= 2026051201 (which sends X-Client-Type:\n * web-browser on /v1/on-demand/upload so FastPix returns a POST-signed\n * resumable URL — earlier versions returned a PUT-signed URL the SDK\n * cannot use and the initial POST would return 405).\n *\n * https://www.npmjs.com/package/@fastpix/resumable-uploads\n *\n * Vendored locally (Moodle requires all JS served from the site, not a CDN).\n * This is the upstream self-contained ESM build (dist/uploads.mjs, v1.0.5)\n * saved as .js for static-MIME safety. The pinned version is tracked in\n * thirdpartylibs.xml. Built from wwwroot at runtime so it works under any\n * Moodle subdirectory install.\n */\nconst UPLOAD_SDK_URL = M.cfg.wwwroot + '/mod/fastpix/thirdparty/resumable-uploads/uploads.js';\n\n// The SDK MUST load as a native ES module — NOT through RequireJS. Babel's AMD\n// transform rewrites a bare `import()` into `require([...])`, which then fails\n// to load the ESM (throws upload_sdk_load_failed). The Function-constructor\n// indirection hides the import() from Babel so it stays native (same approach\n// as player.js / view.php).\n// eslint-disable-next-line no-new-func -- Intentional: hides native import() from Babel's AMD transform.\nconst esmImport = (url) => Function('u', 'return import(u);')(url);\n\n/** Chunk size in KB (16 MB — matches the SDK default). */\nconst UPLOAD_CHUNK_KB = 16384;\n\n/** Per-chunk retry attempts before the upload gives up. */\nconst UPLOAD_CHUNK_ATTEMPTS = 5;\n\nconst SELECTORS = {\n region: '[data-region=\"fastpix-upload-widget\"]',\n picker: '[data-region=\"fastpix-upload-picker\"]',\n dropzone: '[data-region=\"fastpix-upload-dropzone\"]',\n input: '[data-region=\"fastpix-upload-input\"]',\n browseLink: '[data-action=\"fastpix-browse-trigger\"]',\n progressWrap: '[data-region=\"fastpix-upload-progress\"]',\n progressBar: '[data-region=\"fastpix-upload-bar\"]',\n progressBarFill: '[data-region=\"fastpix-upload-bar-fill\"]',\n progressPct: '[data-region=\"fastpix-upload-percent\"]',\n status: '[data-region=\"fastpix-upload-status\"]',\n urlStatus: '[data-region=\"fastpix-urlpull-status\"]',\n sourceType: '[name=\"source_type\"]',\n sourceUrl: '[name=\"source_url\"]',\n validateBtn: '[name=\"validate_url\"]',\n};\n\n// Course context id received from init() — forwarded to local_fastpix's upload\n// web services so they check mod/fastpix:uploadmedia against the course context\n// (where editing teachers hold the capability), not the system context.\nlet widgetContextId = null;\n\n// Localised status strings supplied by init() (M7 — no English literals in JS).\nlet widgetStrings = {};\n\nconst getSessionField = (fieldname) => document.querySelector(`input[name=\"${fieldname}\"]`);\n\n/**\n * Read the form's Media-settings fields into the create_upload_session args.\n * local_fastpix_create_upload_session consumes: contextid, title (required),\n * accesspolicy (required), captionsmode, languagecode — NOT filename/size.\n *\n * @param {File} file The file being uploaded (filename → fallback title).\n * @returns {Object} The web-service args.\n */\nconst gatherUploadArgs = (file) => {\n const nameEl = document.querySelector('input[name=\"name\"]');\n let title = nameEl?.value ? nameEl.value.trim() : '';\n if (!title) {\n title = file?.name ? file.name.replace(/\\.[^.]+$/, '') : (widgetStrings.untitledvideo || '');\n }\n const apEl = document.querySelector('input[name=\"access_policy\"]:checked');\n const accesspolicy = apEl ? apEl.value : 'private';\n let captionsmode = 'none';\n if (document.querySelector('input[name=\"captionsenabled\"]:checked')) {\n const modeEl = document.querySelector('input[name=\"captionsmode\"]:checked');\n captionsmode = (modeEl?.value === 'vtt') ? 'vtt' : 'auto';\n }\n const langEl = document.querySelector('select[name=\"languagecode\"]');\n const languagecode = (captionsmode === 'auto' && langEl) ? langEl.value : '';\n return {\n contextid: widgetContextId,\n title: title,\n accesspolicy: accesspolicy,\n captionsmode: captionsmode,\n languagecode: languagecode,\n };\n};\n\nconst setSourceType = (value) => {\n const el = document.querySelector(SELECTORS.sourceType);\n if (!el) { return; }\n el.value = value;\n el.dispatchEvent(new Event('change', {bubbles: true}));\n};\n\nconst setStatus = (region, message, kind) => {\n const el = region.querySelector(SELECTORS.status);\n if (!el) { return; }\n el.hidden = false;\n el.textContent = message;\n el.className = `mt-3 small alert alert-${kind}`;\n};\n\nconst clearStatus = (region) => {\n const el = region.querySelector(SELECTORS.status);\n if (!el) { return; }\n el.hidden = true;\n el.textContent = '';\n el.className = 'mt-3 small';\n};\n\nconst setUrlStatus = (message, kind) => {\n const el = document.querySelector(SELECTORS.urlStatus);\n if (!el) { return; }\n if (!message) {\n el.textContent = '';\n el.className = 'fastpix-vs-url-status';\n return;\n }\n const states = {success: 'success', danger: 'danger', warning: 'warning'};\n const state = states[kind] || 'muted';\n el.textContent = message;\n el.className = 'fastpix-vs-url-status fastpix-vs-url-status--' + state;\n};\n\n/**\n * Lazy-load the FastPix resumable upload SDK. Cached on `window` so the\n * dynamic import only runs once per page lifecycle even if the user\n * uploads multiple files. @fastpix/resumable-uploads exposes\n * `Uploader.init(opts)` — a STATIC factory, not a constructor.\n *\n * @returns {Promise<{createUpload: (opts: Object) => Object}>}\n */\nconst loadUploadSdk = async () => {\n if (window.__fastpixUploadSdk) {\n return window.__fastpixUploadSdk;\n }\n const mod = await esmImport(UPLOAD_SDK_URL);\n if (!mod.Uploader || typeof mod.Uploader.init !== 'function') {\n throw new Error('upload_sdk_no_init');\n }\n const adapter = {\n createUpload: (opts) => mod.Uploader.init(opts),\n };\n window.__fastpixUploadSdk = adapter;\n return adapter;\n};\n\n/**\n * Resumable + chunked upload to a FastPix POST-signed resumable URL.\n *\n * - File is split into UPLOAD_CHUNK_KB chunks.\n * - Each chunk PUTs to the session URI with Content-Range; failed\n * chunks retry up to UPLOAD_CHUNK_ATTEMPTS times.\n * - On a dropped connection, the SDK resumes from the last completed\n * chunk instead of restarting the entire upload.\n * - Progress is reported as a 0..100 percentage of total bytes.\n *\n * @param {File} file\n * @param {string} uploadUrl POST-signed resumable URL from local_fastpix.\n * @param {(percent: number) => void} onProgress\n * @returns {Promise} resolves on `success`.\n */\nconst putToSignedUrl = async (file, uploadUrl, onProgress) => {\n let sdk;\n try {\n sdk = await loadUploadSdk();\n } catch (e) {\n throw new Error('upload_sdk_load_failed');\n }\n\n return new Promise((resolve, reject) => {\n const upload = sdk.createUpload({\n endpoint: uploadUrl,\n file: file,\n chunkSize: UPLOAD_CHUNK_KB,\n retryChunkAttempt: UPLOAD_CHUNK_ATTEMPTS,\n });\n\n upload.on('progress', (event) => {\n // FastPix resumable SDK reports the percentage at\n // event.detail.progress (0..100) — NOT event.detail. Reading\n // event.detail rounded an object to NaN, leaving the bar at 0%.\n const raw = (typeof event?.detail?.progress === 'number')\n ? event.detail.progress\n : 0;\n const pct = Math.max(0, Math.min(100, Math.round(raw)));\n onProgress(pct);\n });\n\n upload.on('success', () => {\n onProgress(100);\n resolve();\n });\n\n upload.on('error', (event) => {\n const detail = event.detail || {};\n const msg = detail.message || detail.toString?.() || 'upload_failed';\n reject(new Error(msg));\n });\n });\n};\n\nconst showProgressUI = (region) => {\n // Keep the upload dropzone visible during upload (don't hide it) — just\n // reveal the progress region below it.\n const progress = region.querySelector(SELECTORS.progressWrap);\n if (progress) { progress.hidden = false; }\n};\n\nconst showSuccessUI = (region) => {\n const progress = region.querySelector(SELECTORS.progressWrap);\n if (progress) { progress.hidden = true; }\n // Show just the completion state — not the filename.\n setStatus(region, widgetStrings.complete, 'success');\n};\n\nconst showDropzoneUI = (region) => {\n const dropzone = region.querySelector(SELECTORS.dropzone);\n if (dropzone) { dropzone.hidden = false; }\n const progress = region.querySelector(SELECTORS.progressWrap);\n if (progress) { progress.hidden = true; }\n};\n\nconst handleFileSelected = async (region, sessionField, file) => {\n if (!file) { return; }\n setSourceType('upload');\n clearStatus(region);\n showProgressUI(region);\n\n let session;\n try {\n [session] = await Promise.all(ajaxCall([{\n methodname: 'local_fastpix_create_upload_session',\n args: gatherUploadArgs(file),\n }]));\n } catch (e) {\n Notification.exception(e);\n showDropzoneUI(region);\n setStatus(region, widgetStrings.sessionfailed, 'danger');\n return;\n }\n\n const bar = region.querySelector(SELECTORS.progressBar);\n const fill = region.querySelector(SELECTORS.progressBarFill);\n const pct = region.querySelector(SELECTORS.progressPct);\n\n try {\n // create_upload_session returns 'uploadurl' (signed PUT target) +\n // 'uploadid'. NOTE the no-underscore field names — they differ from\n // create_url_pull_session ('upload_url' / 'session_id').\n await putToSignedUrl(file, session.uploadurl, (percent) => {\n if (bar) { bar.value = percent; }\n if (fill) { fill.style.width = `${percent}%`; }\n if (pct) { pct.textContent = `${percent}%`; }\n });\n } catch (e) {\n Notification.exception(e);\n showDropzoneUI(region);\n setStatus(region, widgetStrings.failed, 'danger');\n return;\n }\n\n // Persist the upload-session reference for the activity row. create_upload_session\n // currently returns only 'uploadid' (a FastPix UUID), not the integer session id\n // that create_url_pull_session returns — prefer session_id if present.\n sessionField.value = String(session.session_id || session.uploadid || '');\n showSuccessUI(region);\n};\n\nconst wireDropzone = (region, sessionField) => {\n const dropzone = region.querySelector(SELECTORS.dropzone);\n const input = region.querySelector(SELECTORS.input);\n const browse = region.querySelector(SELECTORS.browseLink);\n if (!dropzone || !input) { return; }\n\n // Browse link (rendered above the transparent file input z-index-wise).\n if (browse) {\n browse.addEventListener('click', (e) => {\n e.preventDefault();\n input.click();\n });\n }\n\n dropzone.addEventListener('click', (e) => {\n // The browse link handles its own click. The native input also catches\n // clicks directly via z-index. Avoid double-trigger.\n if (e.target === input || e.target === browse) { return; }\n input.click();\n });\n\n dropzone.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n input.click();\n }\n });\n\n const setDragging = (on) => {\n dropzone.classList.toggle('is-dragging', !!on);\n };\n\n ['dragenter', 'dragover'].forEach((ev) => dropzone.addEventListener(ev, (e) => {\n e.preventDefault();\n e.stopPropagation();\n setDragging(true);\n }));\n ['dragleave', 'dragend'].forEach((ev) => dropzone.addEventListener(ev, (e) => {\n e.preventDefault();\n e.stopPropagation();\n setDragging(false);\n }));\n dropzone.addEventListener('drop', (e) => {\n e.preventDefault();\n e.stopPropagation();\n setDragging(false);\n const file = e.dataTransfer?.files?.[0];\n if (file) {\n handleFileSelected(region, sessionField, file);\n }\n });\n\n input.addEventListener('change', () => {\n const file = input.files?.[0];\n if (file) {\n handleFileSelected(region, sessionField, file);\n }\n });\n};\n\nconst validateUrl = async (sessionField, button) => {\n const urlInput = document.querySelector(SELECTORS.sourceUrl);\n if (!urlInput?.value) {\n setUrlStatus(widgetStrings.urlenterfirst, 'warning');\n return;\n }\n\n setSourceType('urlpull');\n setUrlStatus(widgetStrings.uploading, 'muted');\n if (button) { button.disabled = true; }\n\n let session;\n try {\n [session] = await Promise.all(ajaxCall([{\n methodname: 'local_fastpix_create_url_pull_session',\n args: { source_url: urlInput.value, contextid: widgetContextId },\n }]));\n } catch (e) {\n Notification.exception(e);\n setUrlStatus(widgetStrings.urlrejected, 'danger');\n if (button) { button.disabled = false; }\n return;\n }\n\n sessionField.value = String(session.session_id);\n setUrlStatus('✓ ' + widgetStrings.urlaccepted, 'success');\n if (button) { button.disabled = false; }\n};\n\nconst renderInto = async (region) => {\n const {html, js} = await Templates.renderForPromise('mod_fastpix/upload_widget', {});\n Templates.replaceNodeContents(region, html, js);\n};\n\nexport const init = async (config) => {\n widgetContextId = config.contextId;\n widgetStrings = config.strings || {};\n const region = document.querySelector(SELECTORS.region);\n if (!region) { return; }\n\n const sessionField = getSessionField(config.fieldnameSession);\n if (!sessionField) { return; }\n\n // Wire the URL Upload button at outer-document scope BEFORE the inner\n // template renders, so the listener is in place even if the user\n // somehow clicks before render completes.\n const validateBtn = document.querySelector(SELECTORS.validateBtn);\n if (validateBtn && validateBtn.dataset.fastpixWired !== '1') {\n validateBtn.dataset.fastpixWired = '1';\n validateBtn.addEventListener('click', (e) => {\n e.preventDefault();\n validateUrl(sessionField, validateBtn);\n });\n }\n\n // Editing the URL invalidates a prior validate (so the form doesn't\n // submit a stale upload_session_id against a new URL).\n const urlInput = document.querySelector(SELECTORS.sourceUrl);\n if (urlInput && urlInput.dataset.fastpixWired !== '1') {\n urlInput.dataset.fastpixWired = '1';\n urlInput.addEventListener('input', () => {\n if (sessionField) { sessionField.value = ''; }\n setUrlStatus('', 'muted');\n });\n }\n\n await renderInto(region);\n\n // After render, re-query for URL input + validate button (template may\n // replace them) and wire dropzone.\n const renderedUrlInput = document.querySelector(SELECTORS.sourceUrl);\n const renderedValidateBtn = document.querySelector(SELECTORS.validateBtn);\n if (renderedValidateBtn && renderedValidateBtn.dataset.fastpixWired !== '1') {\n renderedValidateBtn.dataset.fastpixWired = '1';\n renderedValidateBtn.addEventListener('click', (e) => {\n e.preventDefault();\n validateUrl(sessionField, renderedValidateBtn);\n });\n }\n if (renderedUrlInput && renderedUrlInput.dataset.fastpixWired !== '1') {\n renderedUrlInput.dataset.fastpixWired = '1';\n renderedUrlInput.addEventListener('input', () => {\n if (sessionField) { sessionField.value = ''; }\n setUrlStatus('', 'muted');\n });\n }\n\n wireDropzone(region, sessionField);\n};\n"],"names":["_interopRequireDefault","e","__esModule","default","_notification","_templates","UPLOAD_SDK_URL","M","cfg","wwwroot","SELECTORS","widgetContextId","widgetStrings","gatherUploadArgs","file","nameEl","document","querySelector","title","value","trim","name","replace","untitledvideo","apEl","accesspolicy","captionsmode","modeEl","langEl","languagecode","contextid","setSourceType","el","dispatchEvent","Event","bubbles","setStatus","region","message","kind","hidden","textContent","className","setUrlStatus","state","success","danger","warning","loadUploadSdk","async","window","__fastpixUploadSdk","mod","url","Function","Uploader","init","Error","adapter","createUpload","opts","showDropzoneUI","dropzone","progress","handleFileSelected","sessionField","session","clearStatus","showProgressUI","Promise","all","ajaxCall","call","methodname","args","Notification","exception","sessionfailed","bar","fill","pct","uploadUrl","onProgress","sdk","resolve","reject","upload","endpoint","chunkSize","retryChunkAttempt","on","event","raw","detail","Math","max","min","round","msg","toString","putToSignedUrl","uploadurl","percent","style","width","failed","String","session_id","uploadid","complete","showSuccessUI","validateUrl","button","urlInput","urlenterfirst","uploading","disabled","source_url","urlrejected","urlaccepted","_exports","config","contextId","strings","fieldname","fieldnameSession","validateBtn","dataset","fastpixWired","addEventListener","preventDefault","html","js","Templates","renderForPromise","replaceNodeContents","renderInto","renderedUrlInput","renderedValidateBtn","wireDropzone","input","browse","click","target","key","setDragging","classList","toggle","forEach","ev","stopPropagation","dataTransfer","files"],"mappings":"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"} \ No newline at end of file diff --git a/amd/src/upload_widget.js b/amd/src/upload_widget.js index da10cb8..418bccc 100644 --- a/amd/src/upload_widget.js +++ b/amd/src/upload_widget.js @@ -66,6 +66,14 @@ import Templates from 'core/templates'; */ const UPLOAD_SDK_URL = M.cfg.wwwroot + '/mod/fastpix/thirdparty/resumable-uploads/uploads.js'; +// The SDK MUST load as a native ES module — NOT through RequireJS. Babel's AMD +// transform rewrites a bare `import()` into `require([...])`, which then fails +// to load the ESM (throws upload_sdk_load_failed). The Function-constructor +// indirection hides the import() from Babel so it stays native (same approach +// as player.js / view.php). +// eslint-disable-next-line no-new-func -- Intentional: hides native import() from Babel's AMD transform. +const esmImport = (url) => Function('u', 'return import(u);')(url); + /** Chunk size in KB (16 MB — matches the SDK default). */ const UPLOAD_CHUNK_KB = 16384; @@ -180,7 +188,7 @@ const loadUploadSdk = async () => { if (window.__fastpixUploadSdk) { return window.__fastpixUploadSdk; } - const mod = await import(UPLOAD_SDK_URL); + const mod = await esmImport(UPLOAD_SDK_URL); if (!mod.Uploader || typeof mod.Uploader.init !== 'function') { throw new Error('upload_sdk_no_init'); }