Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Saving imported draw.io file fails #267 #272

Merged
merged 2 commits into from
Jun 18, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
333 changes: 324 additions & 9 deletions application-diagram-ui/src/main/resources/Diagram/DiagramEditSheet.xml
Original file line number Diff line number Diff line change
Expand Up @@ -219,17 +219,13 @@ define('diagram-store', ['jquery', 'xwiki-meta', 'xwiki-utils', 'diagram-utils',
},
setData: function(data) {
this.input.val(data);
let pages = this.ui.getPagesForXml(data);
if (pages.length > 0) {
this.ui.pages = this.ui.getPagesForXml(data);
}
},
isCompressed: function() {
return false;
},
open: function() {
var graphXML = this.getData() || '<mxGraphModel/>';
var graphNode = mxUtils.parseXml(graphXML).documentElement;
graphNode.fromStorage = true;
this.ui.editor.setGraphXml(graphNode);
this.ui.currentPage = this.ui.pages[0];
this.ui.setFileData(graphXML);
this.changeListener = mxUtils.bind(this, function(sender, eventObject) {
this.setModified(true);
});
Expand Down Expand Up @@ -1152,6 +1148,325 @@ define('diagram-image-editor', ['xwiki-utils', 'diagram-link-handler', 'draw.io'
return converter;
};

// Copied from the drawio version 24.5.4 to include a fix for importing a file in an empty diagram. This method should
// be removed once we upgrade to a drawio version >= 24.4.0. Being a big method, the structure was altered just to
// make it shorter.
var importFilesNew = function(files, x, y, maxSize, fn, resultFn, filterFn, barrierFn,
resizeDialog, maxBytes, resampleThreshold, ignoreEmbeddedXml, evt) {
maxSize = (maxSize != null) ? maxSize : this.maxImageSize;
maxBytes = (maxBytes != null) ? maxBytes : this.maxImageBytes;
var crop = x != null && y != null;
var resizeImages = true;
x = (x != null) ? x : 0;
y = (y != null) ? y : 0;
// Checks if large images are imported
var largeImages = false;
if (!mxClient.IS_CHROMEAPP && files != null) {
var thresh = resampleThreshold || this.resampleThreshold;
for (var i = 0; i < files.length; i++) {
if (files[i].type.substring(0, 9) !== 'image/svg' &&
files[i].type.substring(0, 6) === 'image/' &&
files[i].size > thresh) {
largeImages = true;
break;
}
}
}

var doImportFiles = mxUtils.bind(this, function() {
var graph = this.editor.graph;
var gs = graph.gridSize;

fn = (fn != null) ? fn : mxUtils.bind(this, function(data, mimeType, x, y, w, h, filename, done, file) {
try {
if (data != null && data.substring(0, 10) == '<mxlibrary') {
this.spinner.stop();
this.loadLibrary(new LocalLibrary(this, data, filename));
this.showSidebar();

return null;
}
else {
// Drop on empty file ignores drop location
if (this.isCompatibleString(data) && files.length == 1 && evt != null &&
evt.type == 'drop' && this.isBlankFile() && !this.canUndo()) {
crop = false;
x = 0;
y = 0;
}
return this.importFile(data, mimeType, x, y, w, h, filename,
done, file, crop, ignoreEmbeddedXml, evt);
}
}
catch (e) {
this.handleError(e);
return null;
}
});

resultFn = (resultFn != null) ? resultFn : mxUtils.bind(this, function(cells) {
graph.setSelectionCells(cells);
});

if (this.spinner.spin(document.body, mxResources.get('loading'))) {
var count = files.length;
var remain = count;
var queue = [];

// Barrier waits for all files to be loaded asynchronously
var barrier = mxUtils.bind(this, function(index, fnc) {
queue[index] = fnc;
if (--remain == 0) {
this.spinner.stop();
if (barrierFn != null) {
barrierFn(queue);
}
else {
var cells = [];
graph.getModel().beginUpdate();
try {
for (var j = 0; j < queue.length; j++) {
var tmp = queue[j]();
if (tmp != null) {
cells = cells.concat(tmp);
}
}
}
finally {
graph.getModel().endUpdate();
}
}
resultFn(cells);
}
});

for (var i = 0; i < count; i++) {
(mxUtils.bind(this, function(index) {
var file = files[index];
if (file != null) {
var reader = new FileReader();
reader.onload = mxUtils.bind(this, function(e) {
if (filterFn == null || filterFn(file)) {
try {
if (file.type.substring(0, 6) == 'image/') {
if (file.type.substring(0, 9) == 'image/svg') {
// Checks if SVG contains content attribute
var data = Graph.clipSvgDataUri(e.target.result);
var comma = data.indexOf(',');
var svgText = decodeURIComponent(escape(atob(data.substring(comma + 1))));
var root = mxUtils.parseXml(svgText);
var svgs = root.getElementsByTagName('svg');
if (svgs.length > 0) {
var svgRoot = svgs[0];
var cont = (ignoreEmbeddedXml) ? null : svgRoot.getAttribute('content');
if (cont != null && cont.charAt(0) != '<' && cont.charAt(0) != '%') {
cont = unescape((window.atob) ? atob(cont) : Base64.decode(cont, true));
}
if (cont != null && cont.charAt(0) == '%') {
cont = decodeURIComponent(cont);
}
if (cont != null && (cont.substring(0, 8) === '<mxfile ' ||
cont.substring(0, 14) === '<mxGraphModel ')) {
barrier(index, mxUtils.bind(this, function() {
return fn(cont, 'text/xml', x + index * gs, y + index * gs, 0, 0, file.name);
}));
}
else {
// SVG needs special handling to add viewbox if missing and
// find initial size from SVG attributes (only for IE11)
barrier(index, mxUtils.bind(this, function() {
try {
// Parses SVG and find width and height
if (root != null) {
var svgs = root.getElementsByTagName('svg');
if (svgs.length > 0) {
var svgRoot = svgs[0];
var w = svgRoot.getAttribute('width');
var h = svgRoot.getAttribute('height');
if (w != null && w.charAt(w.length - 1) != '%') {
w = parseFloat(w);
}
else {
w = NaN;
}
if (h != null && h.charAt(h.length - 1) != '%') {
h = parseFloat(h);
}
else {
h = NaN;
}
// Check if viewBox attribute already exists
var vb = svgRoot.getAttribute('viewBox');
if (vb == null || vb.length == 0) {
svgRoot.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
}
// Uses width and height from viewbox for
// missing width and height attributes
else if (isNaN(w) || isNaN(h)) {
var tokens = vb.split(' ');
if (tokens.length > 3) {
w = parseFloat(tokens[2]);
h = parseFloat(tokens[3]);
}
}
data = Editor.createSvgDataUri(mxUtils.getXml(svgRoot));
var s = Math.min(1, Math.min(maxSize / Math.max(1, w)), maxSize / Math.max(1, h));
var cells = fn(data, file.type, x + index * gs, y + index * gs, Math.max(
1, Math.round(w * s)), Math.max(1, Math.round(h * s)), file.name);
// Hack to fix width and height asynchronously
if (cells != null && (isNaN(w) || isNaN(h))) {
var img = new Image();
img.onload = mxUtils.bind(this, function() {
w = Math.max(1, img.width);
h = Math.max(1, img.height);
cells[0].geometry.width = w;
cells[0].geometry.height = h;
svgRoot.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
data = Editor.createSvgDataUri(mxUtils.getXml(svgRoot));
var semi = data.indexOf(';');
if (semi > 0) {
data = data.substring(0, semi) + data.substring(data.indexOf(',', semi + 1));
}
graph.setCellStyles('image', data, [cells[0]]);
});
img.src = Editor.createSvgDataUri(mxUtils.getXml(svgRoot));
}
return cells;
}
}
}
catch (e) {
// ignores any SVG parsing errors
}
return null;
}));
}
}
else {
barrier(index, mxUtils.bind(this, function() {
return null;
}));
}
}
else {
// Checks if PNG+XML is available to bypass code below
var containsModel = false;
if (file.type == 'image/png') {
var xml = (ignoreEmbeddedXml) ? null : this.extractGraphModelFromPng(e.target.result);
if (xml != null && xml.length > 0) {
var img = new Image();
img.src = e.target.result;
barrier(index, mxUtils.bind(this, function() {
return fn(xml, 'text/xml', x + index * gs, y + index * gs,
img.width, img.height, file.name);
}));
containsModel = true;
}
}
// Additional asynchronous step for finding image size
if (!containsModel) {
// Cannot load local files in Chrome App
if (mxClient.IS_CHROMEAPP) {
this.spinner.stop();
this.showError(mxResources.get('error'), mxResources.get('dragAndDropNotSupported'),
mxResources.get('cancel'), mxUtils.bind(this, function()
{
// Hides the dialog
}), null, mxResources.get('ok'), mxUtils.bind(this, function()
{
// Redirects to import function
this.actions.get('import').funct();
})
);
}
else {
this.loadImage(e.target.result, mxUtils.bind(this, function(img) {
this.resizeImage(img, e.target.result, mxUtils.bind(this, function(data2, w2, h2) {
barrier(index, mxUtils.bind(this, function() {
// Refuses to insert images above a certain size as they kill the app
if (data2 != null && data2.length < maxBytes) {
var s = (!resizeImages || !this.isResampleImageSize(
file.size, resampleThreshold)) ? 1 :
Math.min(1, Math.min(maxSize / w2, maxSize / h2));
return fn(data2, file.type, x + index * gs, y + index * gs,
Math.round(w2 * s), Math.round(h2 * s), file.name);
}
else {
this.handleError({message: mxResources.get('imageTooBig')});
return null;
}
}));
}), resizeImages, maxSize, resampleThreshold, file.size);
}), mxUtils.bind(this, function() {
this.handleError({message: mxResources.get('invalidOrMissingFile')});
}));
}
}
}
}
else {
var data = e.target.result;
fn(data, file.type, x + index * gs, y + index * gs, 240, 160, file.name, function(cells) {
barrier(index, function() {
return cells;
});
}, file);
}
}
catch (e) {
// Ignores file parsing error
barrier(index, mxUtils.bind(this, function() {
return null;
}));
if (window.console != null) {
console.error(e, file);
}
}
}
else {
// Ignores file and decrements counter
barrier(index, mxUtils.bind(this, function()
{
return null;
}));
}
});
// Handles special cases
if (/(\.v(dx|sdx?))($|\?)/i.test(file.name) || /(\.vs(x|sx?))($|\?)/i.test(file.name)) {
fn(null, file.type, x + index * gs, y + index * gs, 240, 160, file.name, function(cells) {
barrier(index, function() {
return cells;
});
}, file);
}
else if (file.type.substring(0, 5) == 'image' || file.type == 'application/pdf') {
reader.readAsDataURL(file);
}
else {
reader.readAsText(file);
}
}
}))(i);
}
}
});
if (largeImages) {
// Workaround for lost files array in async code
var tmp = [];
for (var i = 0; i < files.length; i++) {
tmp.push(files[i]);
}
files = tmp;
this.confirmImageResize(function(doResize) {
resizeImages = doResize;
doImportFiles();
}, resizeDialog);
}
else {
doImportFiles();
}
};

// Override for uploading the image as attachment instead of encode it to Base64.
var originalImportFiles = EditorUi.prototype.importFiles;
EditorUi.prototype.importFiles = function(files, x, y, maxSize, fn, resultFn, filterFn, barrierFn, resizeDialog,
Expand Down Expand Up @@ -1181,7 +1496,7 @@ define('diagram-image-editor', ['xwiki-utils', 'diagram-link-handler', 'draw.io'
}
};
}
originalImportFiles.apply(this, importFilesArgs);
importFilesNew.apply(this, importFilesArgs);
};

// Add support for inserting images by specifying the XWiki attachment reference.
Expand Down