X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/9135a85de4eef32a91c7a3ee0aa405ed454e5a4c..refs/pull/4467/head:/resources/js/services/drawio.js diff --git a/resources/js/services/drawio.js b/resources/js/services/drawio.js index 67ac4cc0e..46e10327a 100644 --- a/resources/js/services/drawio.js +++ b/resources/js/services/drawio.js @@ -1,29 +1,48 @@ +// Docs: https://www.diagrams.net/doc/faq/embed-mode +import * as store from './store'; + let iFrame = null; let lastApprovedOrigin; -let onInit, onSave; +let onInit; +let onSave; +const saveBackupKey = 'last-drawing-save'; -/** - * 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 drawPostMessage(data) { + iFrame.contentWindow.postMessage(JSON.stringify(data), lastApprovedOrigin); +} - 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; +function drawEventExport(message) { + store.set(saveBackupKey, message.data); + if (onSave) { + onSave(message.data).then(() => { + store.del(saveBackupKey); + }); + } } -function close() { - drawEventClose(); +function drawEventSave(message) { + drawPostMessage({ + action: 'export', format: 'xmlpng', xml: message.xml, spin: 'Updating drawing', + }); +} + +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); } /** @@ -48,53 +67,72 @@ function drawReceive(event) { } } -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'); -function drawEventSave(message) { - drawPostMessage({action: 'export', format: 'xmlpng', xml: message.xml, spin: 'Updating drawing'}); -} + if (!dialogEl) { + console.error('Missing expected unsaved-drawing dialog'); + } -function drawEventInit() { - if (!onInit) return; - onInit().then(xml => { - drawPostMessage({action: 'load', autosave: 1, xml: xml}); - }); + if (backupVal) { + /** @var {ConfirmDialog} */ + const dialog = window.$components.firstOnElement(dialogEl, 'confirm-dialog'); + const restore = await dialog.show(); + if (restore) { + onInit = async () => backupVal; + } + } } -function drawEventConfigure() { - const config = {}; - window.$events.emitPublic(iFrame, 'editor-drawio::configure', {config}); - drawPostMessage({action: 'configure', config}); -} +/** + * 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), lastApprovedOrigin); + 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) { +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}`; @@ -106,5 +144,3 @@ async function load(drawingId) { throw error; } } - -export default {show, close, upload, load}; \ No newline at end of file