debian-mirror-gitlab/app/assets/javascripts/drawio/drawio_editor.js
2023-05-27 22:25:52 +05:30

277 lines
7.8 KiB
JavaScript

import _ from 'lodash';
import { createAlert, VARIANT_SUCCESS } from '~/alert';
import { darkModeEnabled } from '~/lib/utils/color_utils';
import { __ } from '~/locale';
import { setAttributes } from '~/lib/utils/dom_utils';
import {
DARK_BACKGROUND_COLOR,
DRAWIO_EDITOR_URL,
DRAWIO_FRAME_ID,
DIAGRAM_BACKGROUND_COLOR,
DRAWIO_IFRAME_TIMEOUT,
DIAGRAM_MAX_SIZE,
} from './constants';
function updateDrawioEditorState(drawIOEditorState, data) {
Object.assign(drawIOEditorState, data);
}
function postMessageToDrawioEditor(drawIOEditorState, message) {
const { origin } = new URL(DRAWIO_EDITOR_URL);
drawIOEditorState.iframe.contentWindow.postMessage(JSON.stringify(message), origin);
}
function disposeDrawioEditor(drawIOEditorState) {
drawIOEditorState.disposeEventListener();
drawIOEditorState.iframe.remove();
}
function getSvg(data) {
const svgPath = atob(data.substring(data.indexOf(',') + 1));
return `<?xml version="1.0" encoding="UTF-8"?>\n\
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n\
${svgPath}`;
}
async function saveDiagram(drawIOEditorState, editorFacade) {
const { newDiagram, diagramMarkdown, filename, diagramSvg } = drawIOEditorState;
const filenameWithExt = filename.endsWith('.drawio.svg') ? filename : `${filename}.drawio.svg`;
postMessageToDrawioEditor(drawIOEditorState, {
action: 'spinner',
show: true,
messageKey: 'saving',
});
try {
const uploadResults = await editorFacade.uploadDiagram({
filename: filenameWithExt,
diagramSvg,
});
if (newDiagram) {
editorFacade.insertDiagram({ uploadResults });
} else {
editorFacade.updateDiagram({ diagramMarkdown, uploadResults });
}
createAlert({
message: __('Diagram saved successfully.'),
variant: VARIANT_SUCCESS,
fadeTransition: true,
});
setTimeout(() => disposeDrawioEditor(drawIOEditorState), 10);
} catch {
postMessageToDrawioEditor(drawIOEditorState, { action: 'spinner', show: false });
postMessageToDrawioEditor(drawIOEditorState, {
action: 'dialog',
titleKey: 'error',
modified: true,
buttonKey: 'close',
messageKey: 'errorSavingFile',
});
}
}
function promptName(drawIOEditorState, name, errKey) {
postMessageToDrawioEditor(drawIOEditorState, {
action: 'prompt',
titleKey: 'filename',
okKey: 'save',
defaultValue: name || '',
});
if (errKey !== null) {
postMessageToDrawioEditor(drawIOEditorState, {
action: 'dialog',
titleKey: 'error',
messageKey: errKey,
buttonKey: 'ok',
});
}
}
function sendLoadDiagramMessage(drawIOEditorState) {
postMessageToDrawioEditor(drawIOEditorState, {
action: 'load',
xml: drawIOEditorState.diagramSvg,
border: 8,
background: DIAGRAM_BACKGROUND_COLOR,
dark: drawIOEditorState.dark,
title: drawIOEditorState.filename,
});
}
async function loadExistingDiagram(drawIOEditorState, editorFacade) {
let diagram = null;
try {
diagram = await editorFacade.getDiagram();
} catch (e) {
throw new Error(__('Cannot load the diagram into the diagrams.net editor'));
}
if (diagram) {
const { diagramMarkdown, filename, diagramSvg, contentType, diagramURL } = diagram;
const resolvedURL = new URL(diagramURL, window.location.origin);
const diagramSvgSize = new Blob([diagramSvg]).size;
if (contentType !== 'image/svg+xml') {
throw new Error(__('The selected image is not a valid SVG diagram'));
}
if (resolvedURL.origin !== window.location.origin) {
throw new Error(__('The selected image is not an asset uploaded in the application'));
}
if (diagramSvgSize > DIAGRAM_MAX_SIZE) {
throw new Error(__('The selected image is too large.'));
}
updateDrawioEditorState(drawIOEditorState, {
newDiagram: false,
filename,
diagramMarkdown,
diagramSvg,
});
} else {
updateDrawioEditorState(drawIOEditorState, {
newDiagram: true,
});
}
sendLoadDiagramMessage(drawIOEditorState);
}
async function prepareEditor(drawIOEditorState, editorFacade) {
const { iframe } = drawIOEditorState;
iframe.style.cursor = 'wait';
try {
await loadExistingDiagram(drawIOEditorState, editorFacade);
iframe.style.visibility = 'visible';
iframe.style.cursor = '';
window.scrollTo(0, 0);
} catch (e) {
createAlert({
message: e.message,
error: e,
});
disposeDrawioEditor(drawIOEditorState);
}
}
function configureDrawIOEditor(drawIOEditorState) {
postMessageToDrawioEditor(drawIOEditorState, {
action: 'configure',
config: {
darkColor: DARK_BACKGROUND_COLOR,
settingsName: 'gitlab',
},
colorSchemeMeta: drawIOEditorState.dark, // For transparent iframe background in dark mode
});
updateDrawioEditorState(drawIOEditorState, {
initialized: true,
});
}
function onDrawIOEditorMessage(drawIOEditorState, editorFacade, evt) {
if (_.isNil(evt) || evt.source !== drawIOEditorState.iframe.contentWindow) {
return;
}
const msg = JSON.parse(evt.data);
if (msg.event === 'configure') {
configureDrawIOEditor(drawIOEditorState);
} else if (msg.event === 'init') {
prepareEditor(drawIOEditorState, editorFacade);
} else if (msg.event === 'exit') {
disposeDrawioEditor(drawIOEditorState);
} else if (msg.event === 'prompt') {
updateDrawioEditorState(drawIOEditorState, {
filename: msg.value,
});
if (!drawIOEditorState.filename) {
promptName(drawIOEditorState, 'diagram.drawio.svg', 'filenameShort');
} else {
saveDiagram(drawIOEditorState, editorFacade);
}
} else if (msg.event === 'export') {
updateDrawioEditorState(drawIOEditorState, {
diagramSvg: getSvg(msg.data),
});
// TODO Add this to draw.io editor configuration
sendLoadDiagramMessage(drawIOEditorState); // Save removes diagram from the editor, so we need to reload it.
postMessageToDrawioEditor(drawIOEditorState, { action: 'status', modified: true }); // And set editor modified flag to true.
if (!drawIOEditorState.filename) {
promptName(drawIOEditorState, 'diagram.drawio.svg', null);
} else {
saveDiagram(drawIOEditorState, editorFacade);
}
}
}
function createEditorIFrame(drawIOEditorState) {
const iframe = document.createElement('iframe');
setAttributes(iframe, {
id: DRAWIO_FRAME_ID,
src: DRAWIO_EDITOR_URL,
class: 'drawio-editor',
});
document.body.appendChild(iframe);
setTimeout(() => {
if (drawIOEditorState.initialized === false) {
disposeDrawioEditor(drawIOEditorState);
createAlert({ message: __('The diagrams.net editor could not be loaded.') });
}
}, DRAWIO_IFRAME_TIMEOUT);
updateDrawioEditorState(drawIOEditorState, {
iframe,
});
}
function attachDrawioIFrameMessageListener(drawIOEditorState, editorFacade) {
const evtHandler = (evt) => {
onDrawIOEditorMessage(drawIOEditorState, editorFacade, evt);
};
window.addEventListener('message', evtHandler);
// Stores a function in the editor state object that allows disposing
// the message event listener when the editor exits.
updateDrawioEditorState(drawIOEditorState, {
disposeEventListener: () => {
window.removeEventListener('message', evtHandler);
},
});
}
const createDrawioEditorState = ({ filename = null }) => ({
newDiagram: true,
filename,
diagramSvg: null,
diagramMarkdown: null,
iframe: null,
isBusy: false,
initialized: false,
dark: darkModeEnabled(),
disposeEventListener: null,
});
export function launchDrawioEditor({ editorFacade, filename }) {
const drawIOEditorState = createDrawioEditorState({ filename });
// The execution order of these two functions matter
attachDrawioIFrameMessageListener(drawIOEditorState, editorFacade);
createEditorIFrame(drawIOEditorState);
}