X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/a6633642232efd164d4708967ab59e498fbff896..refs/pull/5052/head:/resources/js/services/drawio.js diff --git a/resources/js/services/drawio.js b/resources/js/services/drawio.js index 17e57cd6b..46e10327a 100644 --- a/resources/js/services/drawio.js +++ b/resources/js/services/drawio.js @@ -1,33 +1,59 @@ +// Docs: https://www.diagrams.net/doc/faq/embed-mode +import * as store from './store'; + let iFrame = null; +let lastApprovedOrigin; +let onInit; +let onSave; +const saveBackupKey = 'last-drawing-save'; -let onInit, onSave; +function drawPostMessage(data) { + iFrame.contentWindow.postMessage(JSON.stringify(data), lastApprovedOrigin); +} -/** - * Show the draw.io editor. - * @param {String} drawioUrl - * @param {Function} onInitCallback - Must return a promise with the xml to load for the editor. - * @param {Function} onSaveCallback - Is called with the drawing data on save. - */ -function show(drawioUrl, onInitCallback, onSaveCallback) { - onInit = onInitCallback; - onSave = onSaveCallback; +function drawEventExport(message) { + store.set(saveBackupKey, message.data); + if (onSave) { + onSave(message.data).then(() => { + store.del(saveBackupKey); + }); + } +} - iFrame = document.createElement('iframe'); - iFrame.setAttribute('frameborder', '0'); - window.addEventListener('message', drawReceive); - iFrame.setAttribute('src', drawioUrl); - iFrame.setAttribute('class', 'fullscreen'); - iFrame.style.backgroundColor = '#FFFFFF'; - document.body.appendChild(iFrame); +function drawEventSave(message) { + drawPostMessage({ + action: 'export', format: 'xmlpng', xml: message.xml, spin: 'Updating drawing', + }); } -function close() { - drawEventClose(); +function drawEventInit() { + if (!onInit) return; + onInit().then(xml => { + drawPostMessage({action: 'load', autosave: 1, xml}); + }); +} + +function drawEventConfigure() { + const config = {}; + window.$events.emitPublic(iFrame, 'editor-drawio::configure', {config}); + drawPostMessage({action: 'configure', config}); } +function drawEventClose() { + // eslint-disable-next-line no-use-before-define + window.removeEventListener('message', drawReceive); + if (iFrame) document.body.removeChild(iFrame); +} + +/** + * Receive and handle a message event from the draw.io window. + * @param {MessageEvent} event + */ function drawReceive(event) { if (!event.data || event.data.length < 1) return; - let message = JSON.parse(event.data); + if (event.origin !== lastApprovedOrigin) return; + + const message = JSON.parse(event.data); if (message.event === 'init') { drawEventInit(); } else if (message.event === 'exit') { @@ -36,52 +62,85 @@ function drawReceive(event) { drawEventSave(message); } else if (message.event === 'export') { drawEventExport(message); + } else if (message.event === 'configure') { + drawEventConfigure(); } } -function drawEventExport(message) { - if (onSave) { - onSave(message.data); +/** + * Attempt to prompt and restore unsaved drawing content if existing. + * @returns {Promise} + */ +async function attemptRestoreIfExists() { + const backupVal = await store.get(saveBackupKey); + const dialogEl = document.getElementById('unsaved-drawing-dialog'); + + if (!dialogEl) { + console.error('Missing expected unsaved-drawing dialog'); } -} -function drawEventSave(message) { - drawPostMessage({action: 'export', format: 'xmlpng', xml: message.xml, spin: 'Updating drawing'}); + if (backupVal) { + /** @var {ConfirmDialog} */ + const dialog = window.$components.firstOnElement(dialogEl, 'confirm-dialog'); + const restore = await dialog.show(); + if (restore) { + onInit = async () => backupVal; + } + } } -function drawEventInit() { - if (!onInit) return; - onInit().then(xml => { - drawPostMessage({action: 'load', autosave: 1, xml: xml}); - }); -} +/** + * Show the draw.io editor. + * onSaveCallback must return a promise that resolves on successful save and errors on failure. + * onInitCallback must return a promise with the xml to load for the editor. + * Will attempt to provide an option to restore unsaved changes if found to exist. + * @param {String} drawioUrl + * @param {Function>} onInitCallback + * @param {Function} onSaveCallback - Is called with the drawing data on save. + */ +export async function show(drawioUrl, onInitCallback, onSaveCallback) { + onInit = onInitCallback; + onSave = onSaveCallback; -function drawEventClose() { - window.removeEventListener('message', drawReceive); - if (iFrame) document.body.removeChild(iFrame); -} + await attemptRestoreIfExists(); -function drawPostMessage(data) { - iFrame.contentWindow.postMessage(JSON.stringify(data), '*'); + iFrame = document.createElement('iframe'); + iFrame.setAttribute('frameborder', '0'); + window.addEventListener('message', drawReceive); + iFrame.setAttribute('src', drawioUrl); + iFrame.setAttribute('class', 'fullscreen'); + iFrame.style.backgroundColor = '#FFFFFF'; + document.body.appendChild(iFrame); + lastApprovedOrigin = (new URL(drawioUrl)).origin; } -async function upload(imageData, pageUploadedToId) { - let data = { +export async function upload(imageData, pageUploadedToId) { + const data = { image: imageData, uploaded_to: pageUploadedToId, }; - const resp = await window.$http.post(window.baseUrl(`/images/drawio`), data); + const resp = await window.$http.post(window.baseUrl('/images/drawio'), data); return resp.data; } +export function close() { + drawEventClose(); +} + /** * Load an existing image, by fetching it as Base64 from the system. * @param drawingId * @returns {Promise} */ -async function load(drawingId) { - const resp = await window.$http.get(window.baseUrl(`/images/drawio/base64/${drawingId}`)); - return `data:image/png;base64,${resp.data.content}`; +export async function load(drawingId) { + try { + const resp = await window.$http.get(window.baseUrl(`/images/drawio/base64/${drawingId}`)); + return `data:image/png;base64,${resp.data.content}`; + } catch (error) { + if (error instanceof window.$http.HttpError) { + window.$events.showResponseError(error); + } + close(); + throw error; + } } - -export default {show, close, upload, load}; \ No newline at end of file