Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/jgraph/drawio.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Benson [draw.io] <david@jgraph.com>2019-09-27 13:51:03 +0300
committerDavid Benson [draw.io] <david@jgraph.com>2019-09-27 13:51:03 +0300
commit55312c18958b690e778a859b7c5f7d9ee9fb7db8 (patch)
treeed4de615a0b4231a81b250d04700ebbc33c59371
parent86d0f98d4b795c13ddd46446345d257d21f800ce (diff)
11.3.2 releasev11.3.2
-rw-r--r--ChangeLog2
-rw-r--r--src/main/java/com/mxgraph/online/AbsAuthServlet.java105
-rw-r--r--src/main/webapp/js/diagramly/App.js270
-rw-r--r--src/main/webapp/js/diagramly/Dialogs.js79
-rw-r--r--src/main/webapp/js/diagramly/DriveClient.js1233
-rw-r--r--src/main/webapp/js/diagramly/DriveComment.js48
-rw-r--r--src/main/webapp/js/diagramly/DriveFile.js39
-rw-r--r--src/main/webapp/js/diagramly/EditorUi.js114
-rw-r--r--src/main/webapp/js/diagramly/GitHubClient.js9
-rw-r--r--src/main/webapp/js/diagramly/GitLabClient.js7
-rw-r--r--src/main/webapp/js/diagramly/Init.js1
-rw-r--r--src/main/webapp/js/diagramly/Menus.js3
-rw-r--r--src/main/webapp/js/diagramly/Pages.js2
-rw-r--r--src/main/webapp/js/diagramly/Settings.js25
-rw-r--r--src/main/webapp/resources/dia.txt1
-rw-r--r--src/main/webapp/resources/dia_am.txt1
-rw-r--r--src/main/webapp/resources/dia_ar.txt1
-rw-r--r--src/main/webapp/resources/dia_bg.txt1
-rw-r--r--src/main/webapp/resources/dia_bn.txt1
-rw-r--r--src/main/webapp/resources/dia_bs.txt1
-rw-r--r--src/main/webapp/resources/dia_ca.txt1
-rw-r--r--src/main/webapp/resources/dia_cs.txt1
-rw-r--r--src/main/webapp/resources/dia_da.txt1
-rw-r--r--src/main/webapp/resources/dia_de.txt1
-rw-r--r--src/main/webapp/resources/dia_el.txt1
-rw-r--r--src/main/webapp/resources/dia_eo.txt1
-rw-r--r--src/main/webapp/resources/dia_es.txt1
-rw-r--r--src/main/webapp/resources/dia_et.txt1
-rw-r--r--src/main/webapp/resources/dia_fa.txt1
-rw-r--r--src/main/webapp/resources/dia_fi.txt1
-rw-r--r--src/main/webapp/resources/dia_fil.txt1
-rw-r--r--src/main/webapp/resources/dia_fr.txt1
-rw-r--r--src/main/webapp/resources/dia_gu.txt1
-rw-r--r--src/main/webapp/resources/dia_he.txt1
-rw-r--r--src/main/webapp/resources/dia_hi.txt1
-rw-r--r--src/main/webapp/resources/dia_hr.txt1
-rw-r--r--src/main/webapp/resources/dia_hu.txt1
-rw-r--r--src/main/webapp/resources/dia_i18n.txt1
-rw-r--r--src/main/webapp/resources/dia_id.txt1
-rw-r--r--src/main/webapp/resources/dia_it.txt1
-rw-r--r--src/main/webapp/resources/dia_ja.txt1
-rw-r--r--src/main/webapp/resources/dia_kn.txt1
-rw-r--r--src/main/webapp/resources/dia_ko.txt1
-rw-r--r--src/main/webapp/resources/dia_lt.txt1
-rw-r--r--src/main/webapp/resources/dia_lv.txt1
-rw-r--r--src/main/webapp/resources/dia_ml.txt1
-rw-r--r--src/main/webapp/resources/dia_mr.txt1
-rw-r--r--src/main/webapp/resources/dia_ms.txt1
-rw-r--r--src/main/webapp/resources/dia_nl.txt1
-rw-r--r--src/main/webapp/resources/dia_no.txt1
-rw-r--r--src/main/webapp/resources/dia_pl.txt1
-rw-r--r--src/main/webapp/resources/dia_pt-br.txt1
-rw-r--r--src/main/webapp/resources/dia_pt.txt1
-rw-r--r--src/main/webapp/resources/dia_ro.txt1
-rw-r--r--src/main/webapp/resources/dia_ru.txt1
-rw-r--r--src/main/webapp/resources/dia_sk.txt1
-rw-r--r--src/main/webapp/resources/dia_sl.txt1
-rw-r--r--src/main/webapp/resources/dia_sr.txt1
-rw-r--r--src/main/webapp/resources/dia_sv.txt1
-rw-r--r--src/main/webapp/resources/dia_sw.txt1
-rw-r--r--src/main/webapp/resources/dia_ta.txt1
-rw-r--r--src/main/webapp/resources/dia_te.txt1
-rw-r--r--src/main/webapp/resources/dia_th.txt1
-rw-r--r--src/main/webapp/resources/dia_tr.txt1
-rw-r--r--src/main/webapp/resources/dia_uk.txt1
-rw-r--r--src/main/webapp/resources/dia_vi.txt1
-rw-r--r--src/main/webapp/resources/dia_zh-tw.txt1
-rw-r--r--src/main/webapp/resources/dia_zh.txt1
68 files changed, 886 insertions, 1105 deletions
diff --git a/ChangeLog b/ChangeLog
index bcecedcc..477a07ca 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,5 @@
+- Fixes default grid color in dark mode
+
26-SEP-2019: 11.3.2
- Updates Dell rack server stencils
diff --git a/src/main/java/com/mxgraph/online/AbsAuthServlet.java b/src/main/java/com/mxgraph/online/AbsAuthServlet.java
index cb6ec7b7..168f0237 100644
--- a/src/main/java/com/mxgraph/online/AbsAuthServlet.java
+++ b/src/main/java/com/mxgraph/online/AbsAuthServlet.java
@@ -11,6 +11,7 @@ import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
+import java.util.HashMap;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
@@ -20,7 +21,9 @@ import javax.servlet.http.HttpServletResponse;
@SuppressWarnings("serial")
abstract public class AbsAuthServlet extends HttpServlet
{
-
+ private static final boolean DEBUG = false;
+ private static final String SEPARATOR = "/:::/";
+
static public class Config
{
public String DEV_CLIENT_SECRET = null, CLIENT_SECRET = null, DEV_CLIENT_ID = null, CLIENT_ID = null,
@@ -32,6 +35,13 @@ abstract public class AbsAuthServlet extends HttpServlet
return null;
}
+ protected String processAuthError(String errorCode)
+ {
+ //Usually sending null is enough as it is used as a value for auth info
+ //If more processing is needed, override this method
+ return processAuthResponse("null", false);
+ }
+
protected String processAuthResponse(String authRes, boolean jsonResponse)
{
return "";
@@ -45,23 +55,88 @@ abstract public class AbsAuthServlet extends HttpServlet
{
String code = request.getParameter("code");
String refreshToken = request.getParameter("refresh_token");
+ String error = request.getParameter("error");
+ HashMap<String, String> stateVars = new HashMap<>();
+
+ try
+ {
+ String state = request.getParameter("state");
+
+ if (state != null)
+ {
+ String[] parts = state.split("&");
+
+ for (String part : parts)
+ {
+ String[] keyVal = part.split("=");
+ stateVars.put(keyVal[0], keyVal[1]);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+
+ int configIndex = 0;
+
+ try
+ {
+ String appIndex = stateVars.get("appIndex");
+
+ if (appIndex != null)
+ {
+ configIndex = Integer.parseInt(appIndex);
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+
Config CONFIG = getConfig();
String secret, client, redirectUri;
+ String[] secrets, clients;
if ("127.0.0.1".equals(request.getServerName()))
{
- secret = CONFIG.DEV_CLIENT_SECRET;
- client = CONFIG.DEV_CLIENT_ID;
+ secrets = CONFIG.DEV_CLIENT_SECRET.split(SEPARATOR);
+ clients = CONFIG.DEV_CLIENT_ID.split(SEPARATOR);
redirectUri = CONFIG.DEV_REDIRECT_URI;
}
else
{
- secret = CONFIG.CLIENT_SECRET;
- client = CONFIG.CLIENT_ID;
+ secrets = CONFIG.CLIENT_SECRET.split(SEPARATOR);
+ clients = CONFIG.CLIENT_ID.split(SEPARATOR);
redirectUri = CONFIG.REDIRECT_URI;
}
- if (code == null && refreshToken == null)
+ secret = secrets.length > configIndex ? secrets[configIndex] : secrets[0];
+ client = clients.length > configIndex ? clients[configIndex] : clients[0];
+
+ if (error != null)
+ {
+ try
+ {
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+
+ OutputStream out = response.getOutputStream();
+
+ PrintWriter writer = new PrintWriter(out);
+
+ // Writes JavaScript code
+ writer.println(processAuthError(error));
+
+ writer.flush();
+ writer.close();
+ }
+ catch(Exception e)
+ {
+ e.printStackTrace();
+ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ }
+ }
+ else if (code == null && refreshToken == null)
{
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
@@ -135,7 +210,8 @@ abstract public class AbsAuthServlet extends HttpServlet
catch(IOException e)
{
e.printStackTrace();
-
+ StringBuilder details = new StringBuilder("");
+
if (con != null)
{
try
@@ -148,6 +224,8 @@ abstract public class AbsAuthServlet extends HttpServlet
while ((inputLine = in.readLine()) != null)
{
System.err.println(inputLine);
+ details.append(inputLine);
+ details.append("\n");
}
in.close();
}
@@ -165,6 +243,19 @@ abstract public class AbsAuthServlet extends HttpServlet
{
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
+
+ if (DEBUG)
+ {
+ OutputStream out = response.getOutputStream();
+
+ PrintWriter writer = new PrintWriter(out);
+
+ e.printStackTrace(writer);
+ writer.println(details.toString());
+
+ writer.flush();
+ writer.close();
+ }
}
catch (Exception e)
{
diff --git a/src/main/webapp/js/diagramly/App.js b/src/main/webapp/js/diagramly/App.js
index f466ba00..0f397176 100644
--- a/src/main/webapp/js/diagramly/App.js
+++ b/src/main/webapp/js/diagramly/App.js
@@ -279,12 +279,7 @@ App.PUSHER_URL = 'https://js.pusher.com/4.3/pusher.min.js';
* Google APIs to load. The realtime API is needed to notify collaborators of conversion
* of the realtime files, but after Dec 11 it's read-only and hence no longer needed.
*/
-App.GOOGLE_APIS = 'client,drive-share';
-
-/**
- * Google Realtime API export endpoint end of life on 09/27/2019.
- */
-App.GOOGLE_REALTIME_EOL = 1569535200000;
+App.GOOGLE_APIS = 'drive-share';
/**
* Function: authorize
@@ -910,7 +905,7 @@ mxUtils.extend(App, EditorUi);
/**
* Executes the first step for connecting to Google Drive.
*/
-App.prototype.defaultUserPicture = 'https://lh3.googleusercontent.com/-HIzvXUy6QUY/AAAAAAAAAAI/AAAAAAAAAAA/giuR7PQyjEk/photo.jpg?sz=30';
+App.prototype.defaultUserPicture = 'https://lh3.googleusercontent.com/-HIzvXUy6QUY/AAAAAAAAAAI/AAAAAAAAAAA/giuR7PQyjEk/photo.jpg?sz=64';
/**
*
@@ -1262,42 +1257,6 @@ App.prototype.init = function()
this.updateUserElement();
this.restoreLibraries();
this.checkLicense();
-
- // Stop notification one day before API is disabled
- if (App.GOOGLE_REALTIME_EOL - Date.now() > 86400000)
- {
- if (this.drive.user != null && (!isLocalStorage || mxSettings.settings == null ||
- mxSettings.settings.closeRealtimeWarning == null || mxSettings.settings.closeRealtimeWarning <
- new Date().getTime() - (2 * 24 * 60 * 60 * 1000)) &&
- (!this.editor.chromeless || this.editor.editable))
- {
- this.drive.checkRealtimeFiles(mxUtils.bind(this, function()
- {
- var footer = createFooter('You need to take action to convert legacy files. Click here.',
- 'https://desk.draw.io/support/solutions/articles/16000092210',
- 'geStatusAlert',
- mxUtils.bind(this, function()
- {
- footer.parentNode.removeChild(footer);
- this.hideFooter();
-
- // Close permanently
- if (isLocalStorage && mxSettings.settings != null)
- {
- mxSettings.settings.closeRealtimeWarning = Date.now();
- mxSettings.save();
- }
- }));
-
- document.body.appendChild(footer);
-
- window.setTimeout(mxUtils.bind(this, function()
- {
- mxUtils.setPrefixedStyle(footer.style, 'transform', 'translate(-50%,0%)');
- }), 1500);
- }));
- }
- }
}))
// Notifies listeners of new client
@@ -1306,24 +1265,7 @@ App.prototype.init = function()
if (window.DrawGapiClientCallback != null)
{
- gapi.load(((urlParams['picker'] != '0') ? 'picker,': '') + 'auth:' + App.GOOGLE_APIS, mxUtils.bind(this, function(resp)
- {
- // Starts the app without the Google Option if the API fails to load
- if (gapi.client != null)
- {
- gapi.client.load('drive', 'v2', mxUtils.bind(this, function()
- {
- // Needed to avoid popup blocking for non-immediate authentication
- gapi.auth.init(mxUtils.bind(this, function()
- {
- if (gapi.client.drive != null)
- {
- doInit();
- }
- }));
- }));
- }
- }));
+ gapi.load(((urlParams['picker'] != '0') ? 'picker,': '') + App.GOOGLE_APIS, doInit);
/**
* Clears any callbacks.
@@ -2436,33 +2378,7 @@ App.prototype.loadGapi = function(then)
{
if (typeof gapi !== 'undefined')
{
- gapi.load(((urlParams['picker'] != '0') ? 'picker,': '') + 'auth:' + App.GOOGLE_APIS, mxUtils.bind(this, function(resp)
- {
- // Starts the app without the Google Option if the API fails to load
- if (gapi.client == null)
- {
- this.drive = null;
- this.mode = null;
- then();
- }
- else
- {
- gapi.client.load('drive', 'v2', mxUtils.bind(this, function()
- {
- // Needed to avoid popup blocking for non-immediate authentication
- gapi.auth.init(mxUtils.bind(this, function()
- {
- if (gapi.client.drive == null)
- {
- this.drive = null;
- this.mode = null;
- }
-
- then();
- }));
- }));
- }
- }));
+ gapi.load(((urlParams['picker'] != '0') ? 'picker,': '') + App.GOOGLE_APIS, then);
}
};
@@ -5823,75 +5739,140 @@ App.prototype.updateUserElement = function()
if (this.drive != null)
{
- var driveUser = this.drive.getUser();
+ var driveUsers = this.drive.getUsersList();
- if (driveUser != null)
+ if (driveUsers.length > 0)
{
- connected = true;
- this.userPanel.innerHTML += '<table title="User ID: ' + driveUser.id +
- '" style="font-size:10pt;padding:20px 20px 10px 10px;">' +
- '<tr><td valign="middle">' +
- ((driveUser.pictureUrl != null) ?
- '<img width="50" height="50" style="margin-right:8px;border-radius:50%;" src="' + driveUser.pictureUrl + '"/>' :
- '<img width="46" height="46" style="margin-right:4px;margin-top:0px;" src="' + this.defaultUserPicture + '"/>') +
- '</td><td valign="top" style="white-space:nowrap;' +
- ((driveUser.pictureUrl != null) ? 'padding-top:4px;' : '') +
- '">' + mxUtils.htmlEntities(driveUser.displayName) + '<br>' +
- '<small style="color:gray;">' + mxUtils.htmlEntities(driveUser.email) +
- '</small><div style="margin-top:4px;"><i>' +
- mxResources.get('googleDrive') + '</i></div></tr></table>';
- var div = document.createElement('div');
- div.style.textAlign = 'center';
- div.style.paddingBottom = '12px';
- div.style.whiteSpace = 'nowrap';
-
// LATER: Cannot change user while file is open since close will not work with new
// credentials and closing the file using fileLoaded(null) will show splash dialog.
- var btn = mxUtils.button(mxResources.get('signOut'), mxUtils.bind(this, function()
+ var closeFile = mxUtils.bind(this, function(callback, spinnerMsg)
{
var file = this.getCurrentFile();
if (file != null && file.constructor == DriveFile)
{
- this.confirm(mxResources.get('areYouSure'), mxUtils.bind(this, function()
- {
- this.spinner.spin(document.body, mxResources.get('signOut'));
-
- this.diagramContainer.style.display = 'none';
- this.formatContainer.style.display = 'none';
- this.hsplit.style.display = 'none';
- this.sidebarContainer.style.display = 'none';
- this.sidebarFooterContainer.style.display = 'none';
+ this.spinner.spin(document.body, spinnerMsg);
- if (this.tabContainer != null)
- {
- this.tabContainer.style.display = 'none';
- }
-
- file.close();
-
- // LATER: Use callback to wait for thumbnail update
- window.setTimeout(mxUtils.bind(this, function()
+// file.close();
+ this.fileLoaded(null);
+
+ // LATER: Use callback to wait for thumbnail update
+ window.setTimeout(mxUtils.bind(this, function()
+ {
+ this.spinner.stop();
+ callback();
+ }), 2000);
+ }
+ else
+ {
+ callback();
+ }
+ });
+
+ var createUserRow = mxUtils.bind(this, function (user)
+ {
+ var tr = document.createElement('tr');
+ tr.style.cssText = user.isCurrent? '' : 'background-color: whitesmoke; cursor: pointer';
+ tr.setAttribute('title', 'User ID: ' + user.id);
+ tr.innerHTML = '<td valign="middle" style="height: 59px;width: 66px;' +
+ (user.isCurrent? '' : 'border-top: 1px solid rgb(224, 224, 224);') + '">' +
+ '<img width="50" height="50" style="margin: 4px 8px 0 8px;border-radius:50%;" src="' +
+ ((user.pictureUrl != null) ? user.pictureUrl : this.defaultUserPicture) + '"/>' +
+ '</td><td valign="middle" style="white-space:nowrap;' +
+ ((user.pictureUrl != null) ? 'padding-top:4px;' : '') +
+ (user.isCurrent? '' : 'border-top: 1px solid rgb(224, 224, 224);') +
+ '">' + mxUtils.htmlEntities(user.displayName) + '<br>' +
+ '<small style="color:gray;">' + mxUtils.htmlEntities(user.email) +
+ '</small><div style="margin-top:4px;"><i>' +
+ mxResources.get('googleDrive') + '</i></div>';
+
+ if (!user.isCurrent)
+ {
+ mxEvent.addListener(tr, 'click', mxUtils.bind(this, function(evt)
+ {
+ closeFile(mxUtils.bind(this, function()
{
- // Workaround to disable the splash screen before reload
- this.showDialog = function() {};
- window.location.hash = '';
- this.drive.clearUserId();
- gapi.auth.signOut();
+ this.stateArg = null;
+ this.drive.setUser(user);
- // Reload page to reset client auth
- window.location.reload();
- }), (file != null && file.constructor == DriveFile) ? 2000 : 0);
+ this.drive.authorize(true, mxUtils.bind(this, function()
+ {
+ this.setMode(App.MODE_GOOGLE);
+ this.hideDialog();
+ this.showSplash();
+ }), mxUtils.bind(this, function(resp)
+ {
+ this.handleError(resp);
+ }), true); //Remember is true since add account imply keeping that account
+ }), mxResources.get('closingFile', null, 'Closing file...'));
+
+ mxEvent.consume(evt);
}));
}
- else
+
+ return tr;
+ });
+
+ connected = true;
+
+ var driveUserTable = document.createElement('table');
+ driveUserTable.style.cssText ='font-size:10pt;padding: 20px 0 0 0;min-width: 300px;border-spacing: 0;';
+
+ for (var i = 0; i < driveUsers.length; i++)
+ {
+ driveUserTable.appendChild(createUserRow(driveUsers[i]));
+ }
+
+ this.userPanel.appendChild(driveUserTable);
+
+ var div = document.createElement('div');
+ div.style.textAlign = 'left';
+ div.style.padding = '8px';
+ div.style.whiteSpace = 'nowrap';
+ div.style.borderTop = '1px solid rgb(224, 224, 224)';
+
+ var btn = mxUtils.button(mxResources.get('signOut'), mxUtils.bind(this, function()
+ {
+ this.confirm(mxResources.get('areYouSure'), mxUtils.bind(this, function()
{
- this.drive.clearUserId();
- this.drive.setUser(null);
- gapi.auth.signOut();
- }
+ closeFile(mxUtils.bind(this, function()
+ {
+ this.stateArg = null;
+ this.drive.logout();
+ this.setMode(App.MODE_GOOGLE);
+ this.hideDialog();
+ this.showSplash();
+ }), mxResources.get('signOut'));
+ }));
}));
btn.className = 'geBtn';
+ btn.style.float = 'right';
+ div.appendChild(btn);
+
+ var btn = mxUtils.button(mxResources.get('addAccount', null, 'Add Account'), mxUtils.bind(this, function()
+ {
+ var authWin = this.drive.createAuthWin();
+ //FIXME This doean't work to set focus back to main window until closing the file is done
+ authWin.blur();
+ window.focus();
+
+ closeFile(mxUtils.bind(this, function()
+ {
+ this.stateArg = null;
+
+ this.drive.authorize(false, mxUtils.bind(this, function()
+ {
+ this.setMode(App.MODE_GOOGLE);
+ this.hideDialog();
+ this.showSplash();
+ }), mxUtils.bind(this, function(resp)
+ {
+ this.handleError(resp);
+ }), true, authWin); //Remember is true since add account imply keeping that account
+ }), mxResources.get('closingFile', null, 'Closing file...'));
+ }));
+ btn.className = 'geBtn';
+ btn.style.margin = '0px';
div.appendChild(btn);
this.userPanel.appendChild(div);
}
@@ -5907,12 +5888,17 @@ App.prototype.updateUserElement = function()
}
connected = true;
- this.userPanel.innerHTML += '<table style="font-size:10pt;padding:20px 20px 10px 10px;"><tr><td valign="top">' +
+ var userTable = document.createElement('table');
+ userTable.style.cssText = 'font-size:10pt;padding:' + (connected? '10' : '20') + 'px 20px 10px 10px;';
+
+ userTable.innerHTML += '<tr><td valign="top">' +
((logo != null) ? '<img style="margin-right:6px;" src="' + logo + '" width="40" height="40"/></td>' : '') +
'<td valign="middle" style="white-space:nowrap;">' + mxUtils.htmlEntities(user.displayName) +
((user.email != null) ? '<br><small style="color:gray;">' + mxUtils.htmlEntities(user.email) + '</small>' : '') +
((label != null) ? '<div style="margin-top:4px;"><i>' + mxUtils.htmlEntities(label) + '</i></div>' : '') +
- '</td></tr></table>';
+ '</td></tr>';
+
+ this.userPanel.appendChild(userTable);
var div = document.createElement('div');
div.style.textAlign = 'center';
div.style.paddingBottom = '12px';
diff --git a/src/main/webapp/js/diagramly/Dialogs.js b/src/main/webapp/js/diagramly/Dialogs.js
index 3298ba2f..48a327e1 100644
--- a/src/main/webapp/js/diagramly/Dialogs.js
+++ b/src/main/webapp/js/diagramly/Dialogs.js
@@ -736,7 +736,7 @@ var SplashDialog = function(editorUi)
if (!mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp)
{
- var driveUser = (editorUi.drive != null) ? editorUi.drive.getUser() : null;
+ var driveUsers = (editorUi.drive != null) ? editorUi.drive.getUsersList() : [];
function addLogout(logout)
{
@@ -763,48 +763,68 @@ var SplashDialog = function(editorUi)
buttons.appendChild(link);
};
- if (editorUi.mode == App.MODE_GOOGLE && driveUser != null)
+ if (editorUi.mode == App.MODE_GOOGLE && driveUsers.length > 0)
{
- btn.style.marginBottom = '24px';
-
- var link = document.createElement('a');
- link.setAttribute('href', 'javascript:void(0)');
- link.style.display = 'inline-block';
- link.style.marginTop = '6px';
- mxUtils.write(link, mxResources.get('changeUser') + ' (' + driveUser.displayName + ')');
+ var title = document.createElement('span');
+ title.style.marginTop = '6px';
+ mxUtils.write(title, mxResources.get('changeUser') + ':');
// Makes room after last big buttons
btn.style.marginBottom = '16px';
buttons.style.paddingBottom = '18px';
- mxEvent.addListener(link, 'click', function()
+ buttons.appendChild(title);
+
+ var usersSelect = document.createElement('select');
+ usersSelect.style.marginLeft = '4px';
+ usersSelect.style.width = '200px';
+
+ for (var i = 0; i < driveUsers.length; i++)
{
- editorUi.hideDialog();
- editorUi.drive.clearUserId();
- editorUi.drive.setUser(null);
- gapi.auth.signOut();
-
- // Restores current dialog after clearing user
- editorUi.setMode(App.MODE_GOOGLE);
- editorUi.hideDialog();
- editorUi.showSplash();
+ var option = document.createElement('option');
+ mxUtils.write(option, driveUsers[i].displayName);
+ option.value = i;
+ usersSelect.appendChild(option);
+ //More info (email) about the user in a disabled option
+ option = document.createElement('option');
+ option.innerHTML = '&nbsp;&nbsp;&nbsp;';
+ mxUtils.write(option, '<' + driveUsers[i].email + '>');
+ option.setAttribute('disabled', 'disabled');
+ usersSelect.appendChild(option);
+ }
+
+ //Add account option
+ var option = document.createElement('option');
+ mxUtils.write(option, mxResources.get('addAccount'));
+ option.value = driveUsers.length;
+ usersSelect.appendChild(option);
+
+ mxEvent.addListener(usersSelect, 'change', function()
+ {
+ var userIndex = usersSelect.value;
+ var existingAccount = driveUsers.length != userIndex;
- // FIXME: Does not force showing the auth dialog if only one user is logged in
- editorUi.drive.authorize(false, mxUtils.bind(this, mxUtils.bind(this, function()
+ if (existingAccount)
{
+ editorUi.drive.setUser(driveUsers[userIndex]);
+ }
+
+ editorUi.drive.authorize(existingAccount, function()
+ {
+ editorUi.setMode(App.MODE_GOOGLE);
editorUi.hideDialog();
editorUi.showSplash();
- })), mxUtils.bind(this, function(resp)
+ }, function(resp)
{
editorUi.handleError(resp, null, function()
{
editorUi.hideDialog();
editorUi.showSplash();
});
- }));
+ }, true);
});
-
- buttons.appendChild(link);
+
+ buttons.appendChild(usersSelect);
}
else if (editorUi.mode == App.MODE_ONEDRIVE && editorUi.oneDrive != null)
{
@@ -1980,11 +2000,10 @@ var BackgroundImageDialog = function(editorUi, applyFn)
// Creates one picker and reuses it to avoid polluting the DOM
if (editorUi.photoPicker == null)
{
- var token = gapi.auth.getToken().access_token;
var picker = new google.picker.PickerBuilder()
.setAppId(editorUi.drive.appId)
.setLocale(mxLanguage)
- .setOAuthToken(token)
+ .setOAuthToken(editorUi.drive.token)
.addView(google.picker.ViewId.PHOTO_UPLOAD);
editorUi.photoPicker = picker.setCallback(function(data)
@@ -4541,11 +4560,10 @@ var ImageDialog = function(editorUi, title, initialValue, fn, ignoreExisting, co
// Creates one picker and reuses it to avoid polluting the DOM
if (editorUi.photoPicker == null)
{
- var token = gapi.auth.getToken().access_token;
var picker = new google.picker.PickerBuilder()
.setAppId(editorUi.drive.appId)
.setLocale(mxLanguage)
- .setOAuthToken(token)
+ .setOAuthToken(editorUi.drive.token)
.addView(google.picker.ViewId.PHOTO_UPLOAD);
editorUi.photoPicker = picker.setCallback(function(data)
@@ -4931,7 +4949,6 @@ var LinkDialog = function(editorUi, initialValue, btnLabel, fn, showPages)
// Creates one picker and reuses it to avoid polluting the DOM
if (editorUi.linkPicker == null)
{
- var token = gapi.auth.getToken().access_token;
var view = new google.picker.DocsView(google.picker.ViewId.FOLDERS)
.setParent('root')
.setIncludeFolders(true)
@@ -4946,7 +4963,7 @@ var LinkDialog = function(editorUi, initialValue, btnLabel, fn, showPages)
var picker = new google.picker.PickerBuilder()
.setAppId(editorUi.drive.appId)
.setLocale(mxLanguage)
- .setOAuthToken(token)
+ .setOAuthToken(editorUi.drive.token)
.enableFeature(google.picker.Feature.SUPPORT_TEAM_DRIVES)
.addView(view)
.addView(view2)
diff --git a/src/main/webapp/js/diagramly/DriveClient.js b/src/main/webapp/js/diagramly/DriveClient.js
index fa299f4f..e753a555 100644
--- a/src/main/webapp/js/diagramly/DriveClient.js
+++ b/src/main/webapp/js/diagramly/DriveClient.js
@@ -6,6 +6,25 @@ DriveClient = function(editorUi)
{
mxEventSource.call(this);
+ DrawioClient.call(this, editorUi, 'gDriveAuthInfo');
+
+ var authInfo = JSON.parse(this.token);
+
+ if (authInfo != null && authInfo.current != null)
+ {
+ authInfo = authInfo.current;
+
+ this.userId = authInfo.userId;
+ this.token = authInfo.access_token;
+
+ var remainingTime = (authInfo.expires - Date.now()) / 1000;
+
+ authInfo.expires_in = remainingTime < 600? 1 : remainingTime; //10 min tolerance window in case of any rounding errors
+ this.resetTokenRefresh(authInfo);
+
+ this.authCalled = false;
+ }
+
/**
* Holds a reference to the UI. Needed for the sharing client.
*/
@@ -22,11 +41,13 @@ DriveClient = function(editorUi)
this.clientId = window.DRAWIO_GOOGLE_VIEWER_CLIENT_ID || '850530949725.apps.googleusercontent.com';
this.scopes = ['https://www.googleapis.com/auth/drive.readonly',
'https://www.googleapis.com/auth/userinfo.profile'];
+ this.appIndex = 0;
}
else
{
this.appId = window.DRAWIO_GOOGLE_APP_ID || '671128082532';
this.clientId = window.DRAWIO_GOOGLE_CLIENT_ID || '671128082532-jhphbq6d0e1gnsus9mn7vf8a6fjn10mp.apps.googleusercontent.com';
+ this.appIndex = 1;
}
this.mimeTypes = this.xmlMimeType + 'application/mxe,application/mxr,' +
@@ -41,6 +62,12 @@ DriveClient = function(editorUi)
// Extends mxEventSource
mxUtils.extend(DriveClient, mxEventSource);
+// Extends DrawioClient
+mxUtils.extend(DriveClient, DrawioClient);
+
+DriveClient.prototype.redirectUri = 'https://' + window.location.hostname + '/google';
+DriveClient.prototype.GDriveBaseUrl = 'https://www.googleapis.com/drive/v2';
+
/**
* OAuth 2.0 scopes for installing Drive Apps.
*/
@@ -142,111 +169,72 @@ DriveClient.prototype.setUser = function(user)
{
this.user = user;
- if (this.user == null && this.tokenRefreshThread != null)
+ if (this.user == null)
{
- window.clearTimeout(this.tokenRefreshThread);
- this.tokenRefreshThread = null;
+ this.userId = null;
+
+ if (this.tokenRefreshThread != null)
+ {
+ window.clearTimeout(this.tokenRefreshThread);
+ this.tokenRefreshThread = null;
+ }
+ }
+ else
+ {
+ this.userId = user.id;
}
this.fireEvent(new mxEventObject('userChanged'));
};
-/**
- * Authorizes the client, gets the userId and calls <open>.
- */
-DriveClient.prototype.getUser = function()
-{
- return this.user;
-};
-
-/**
- * Authorizes the client, gets the userId and calls <open>.
- */
-DriveClient.prototype.setUserId = function(userId, remember)
+DriveClient.prototype.setUserId = function(userId)
{
- if (remember)
+ this.userId = userId;
+
+ if (this.user != null && this.user.id != this.userId)
{
- if (isLocalStorage)
- {
- localStorage.setItem('.guid', userId);
- }
- else if (typeof(Storage) != 'undefined')
- {
- try
- {
- var expiry = new Date();
- expiry.setYear(expiry.getFullYear() + 1);
- document.cookie = 'GUID=' + userId + '; expires=' + expiry.toUTCString();
- }
- catch (e)
- {
- // any errors for storing the user ID can be safely ignored
- }
- }
+ this.user = null;
}
};
-
/**
* Authorizes the client, gets the userId and calls <open>.
*/
-DriveClient.prototype.clearUserId = function()
+DriveClient.prototype.getUser = function()
{
- if (isLocalStorage)
- {
- localStorage.removeItem('.guid');
- }
- else if (typeof(Storage) != 'undefined')
- {
- var expiry = new Date();
- expiry.setYear(expiry.getFullYear() - 1);
- document.cookie = 'GUID=; expires=' + expiry.toUTCString();
- }
+ return this.user;
};
-/**
- * Authorizes the client, gets the userId and calls <open>.
- */
-DriveClient.prototype.getUserId = function()
+DriveClient.prototype.getUsersList = function()
{
- var uid = null;
-
- if (this.user != null)
- {
- uid = this.user.id;
- }
+ var users = [];
+ var authInfo = JSON.parse(this.getPersistentToken(true));
+ var curUserId = null;
- if (uid == null && isLocalStorage)
+ if (authInfo != null)
{
- uid = localStorage.getItem('.guid');
- }
-
- if (uid == null && typeof(Storage) != 'undefined')
- {
- var cookies = document.cookie.split(";");
-
- for (var i = 0; i < cookies.length; i++)
+ if (authInfo.current != null)
{
- // Removes spaces around cookie
- var cookie = mxUtils.trim(cookies[i]);
+ curUserId = authInfo.current.userId;
+ users.push(authInfo[curUserId].user);
+ users[0].isCurrent = true;
- if (cookie.substring(0, 5) == 'GUID=')
- {
- uid = cookie.substring(5);
- break;
- }
}
- if (uid != null && isLocalStorage)
+ for (var id in authInfo)
{
- // Moves to local storage
- var expiry = new Date();
- expiry.setYear(expiry.getFullYear() - 1);
- document.cookie = 'GUID=; expires=' + expiry.toUTCString();
- localStorage.setItem('.guid', uid);
+ if (id == 'current' || id == curUserId) continue;
+
+ users.push(authInfo[id].user);
}
}
-
- return uid;
+ return users;
+};
+
+DriveClient.prototype.logout = function()
+{
+ this.clearPersistentToken();
+ this.setUser(null);
+ this.token = null;
};
/**
@@ -286,9 +274,7 @@ DriveClient.prototype.execute = function(fn)
}
}
- this.ui.drive.clearUserId();
- this.ui.drive.setUser(null);
- gapi.auth.signOut();
+ this.logout();
this.ui.showError(mxResources.get('error'), msg, mxResources.get('help'), mxUtils.bind(this, function()
{
@@ -305,7 +291,7 @@ DriveClient.prototype.execute = function(fn)
/**
* Executes the given request.
*/
-DriveClient.prototype.executeRequest = function(req, success, error)
+DriveClient.prototype.executeRequest = function(reqObj, success, error)
{
try
{
@@ -324,7 +310,7 @@ DriveClient.prototype.executeRequest = function(req, success, error)
try
{
this.requestThread = null;
- this.currentRequest = req;
+ this.currentRequest = reqObj;
if (timeoutThread != null)
{
@@ -341,7 +327,50 @@ DriveClient.prototype.executeRequest = function(req, success, error)
}
}), this.ui.timeout);
- req.execute(mxUtils.bind(this, function(resp)
+ var params = null;
+ var isJSON = false;
+
+ if (typeof reqObj.params === 'string')
+ {
+ params = reqObj.params;
+ }
+ else if (reqObj.params != null)
+ {
+ params = JSON.stringify(reqObj.params);
+ isJSON = true;
+ }
+
+ var url = reqObj.fullUrl || (this.GDriveBaseUrl + reqObj.url);
+
+ if (isJSON)
+ {
+ url += (url.indexOf('?') > 0 ? '&' : '?') + 'alt=json';
+ }
+
+ var req = new mxXmlRequest(url, params, reqObj.method || 'GET');
+
+ req.setRequestHeaders = mxUtils.bind(this, function(request, params)
+ {
+ if (reqObj.headers != null)
+ {
+ for (var key in reqObj.headers)
+ {
+ request.setRequestHeader(key, reqObj.headers[key]);
+ }
+ }
+ else if (reqObj.contentType != null)
+ {
+ request.setRequestHeader('Content-Type', reqObj.contentType);
+ }
+ else if (isJSON)
+ {
+ request.setRequestHeader('Content-Type', 'application/json');
+ }
+
+ request.setRequestHeader('Authorization', 'Bearer ' + this.token);
+ });
+
+ req.send(mxUtils.bind(this, function(req)
{
try
{
@@ -349,7 +378,18 @@ DriveClient.prototype.executeRequest = function(req, success, error)
if (acceptResponse)
{
- if (resp != null && resp.error == null)
+ var resp;
+
+ try
+ {
+ resp = JSON.parse(req.getText());
+ }
+ catch(e)
+ {
+ resp = null;
+ }
+
+ if (req.getStatus() >= 200 && req.getStatus() <= 299)
{
if (success != null)
{
@@ -376,23 +416,26 @@ DriveClient.prototype.executeRequest = function(req, success, error)
else if (resp != null && resp.error != null && (resp.error.code == 401 ||
(resp.error.code == 403 && reason != 'rateLimitExceeded')))
{
- // Shows an error if we're authenticated but the server still doesn't allow it
- if ((resp.error.code == 403 && this.user != null) ||
- (resp.error.code == 401 && this.user != null && reason == 'authError'))
+ // Shows an error if re-authenticated but the server still doesn't allow it
+ if ((resp.error.code == 403 && this.retryAuth) ||
+ (resp.error.code == 401 && this.retryAuth && reason == 'authError'))
{
if (error != null)
{
error(resp);
}
+
+ this.retryAuth = false;
}
else
{
+ this.retryAuth = true;
this.execute(fn);
}
}
// Schedules a retry if no new request was executed
else if (resp != null && resp.error != null && resp.error.code != 412 && resp.error.code != 404 &&
- resp.error.code != 400 && this.currentRequest == req && retryCount < this.maxRetries)
+ resp.error.code != 400 && this.currentRequest == reqObj && retryCount < this.maxRetries)
{
retryCount++;
var jitter = 1 + 0.1 * (Math.random() - 0.5);
@@ -434,7 +477,7 @@ DriveClient.prototype.executeRequest = function(req, success, error)
});
// Must get token before first request in this case
- if (gapi.auth.getToken() == null)
+ if (this.token == null || !this.authCalled)
{
this.execute(fn);
}
@@ -454,25 +497,106 @@ DriveClient.prototype.executeRequest = function(req, success, error)
throw e;
}
}
-},
+};
+
+DriveClient.prototype.createAuthWin = function(url)
+{
+ var width = 525,
+ height = 525,
+ screenX = window.screenX,
+ screenY = window.screenY,
+ outerWidth = window.outerWidth,
+ outerHeight = window.outerHeight;
+
+ var left = screenX + Math.max(outerWidth - width, 0) / 2;
+ var top = screenY + Math.max(outerHeight - height, 0) / 2;
+
+ var features = ['width=' + width, 'height=' + height,
+ 'top=' + top, 'left=' + left,
+ 'status=no', 'resizable=yes',
+ 'toolbar=no', 'menubar=no',
+ 'scrollbars=yes'];
+ return window.open(url? url : 'about:blank', 'gdauth', features.join(','));
+};
/**
* Authorizes the client, gets the userId and calls <open>.
*/
-DriveClient.prototype.authorize = function(immediate, success, error, remember)
+DriveClient.prototype.authorize = function(immediate, success, error, remember, popup)
{
- try
+ var updateAuthInfo = mxUtils.bind(this, function (newAuthInfo, remember, forceUserUpdate)
{
- var userId = this.getUserId();
+ this.token = newAuthInfo.access_token;
+ newAuthInfo.expires = Date.now() + parseInt(newAuthInfo.expires_in) * 1000;
+ newAuthInfo.remember = remember;
+
+ this.resetTokenRefresh(newAuthInfo);
+ this.authCalled = true;
+ if (forceUserUpdate || this.user == null)
+ {
+ //IE/Edge security doesn't allow access to newAuthInfo in a callback function (outside this function scope)
+ //So, stringify the object and restore it (parse) in the callback
+ var strAuthInfo = JSON.stringify(newAuthInfo);
+
+ this.updateUser(mxUtils.bind(this, function()
+ {
+ //Restore the auth info object to bypass IE/Edge security
+ var resAuthInfo = JSON.parse(strAuthInfo);
+ //Save user and new token
+ this.setPersistentToken(resAuthInfo, !remember);
+
+ if (success != null)
+ {
+ success();
+ }
+ }), error);
+ }
+ else if (success != null)
+ {
+ this.setPersistentToken(newAuthInfo, !remember);
+ success();
+ }
+ });
+
+ try
+ {
// Takes userId from state URL parameter
if (this.ui.stateArg != null && this.ui.stateArg.userId != null)
{
- userId = this.ui.stateArg.userId;
+ this.userId = this.ui.stateArg.userId;
+
+ if (this.user != null && this.user.id != this.userId)
+ {
+ this.user = null;
+ }
}
- // Immediate only possible with userId
- if (immediate && userId == null)
+ //Retry request with refreshed token
+ var authInfo = JSON.parse(this.getPersistentToken(true));
+
+ if (authInfo != null)
+ {
+ if (this.userId == null)
+ {
+ if (authInfo.current != null)
+ {
+ this.userId = authInfo.current.userId;
+ authInfo = authInfo[this.userId];
+ }
+ else
+ {
+ authInfo = null;
+ }
+ }
+ else
+ {
+ authInfo = authInfo[this.userId]; //If user id is new, authInfo will be null
+ }
+ }
+
+ // Immediate only possible with a refresh token
+ if (immediate && (authInfo == null || authInfo.refresh_token == null))
{
if (error != null)
{
@@ -481,58 +605,88 @@ DriveClient.prototype.authorize = function(immediate, success, error, remember)
}
else
{
- var params =
+ if (immediate) //Note, we checked refresh token is not null above
{
- scope: this.scopes,
- client_id: this.clientId
- };
-
- if (immediate && userId != null)
- {
- params.immediate = true;
- params.user_id = userId;
+ //state is used to identify which app is used
+ var req = new mxXmlRequest(this.redirectUri + '?state=appIndex%3D' + this.appIndex + '&refresh_token=' + authInfo.refresh_token, null, 'GET');
+
+ req.send(mxUtils.bind(this, function(req)
+ {
+ if (req.getStatus() >= 200 && req.getStatus() <= 299)
+ {
+ var newAuthInfo = JSON.parse(req.getText());
+ newAuthInfo.refresh_token = authInfo.refresh_token; //Refresh token is not returned in the new auth info
+
+ updateAuthInfo(newAuthInfo, true); //We set remember to true since we can only have a refresh token if user initially selected remember
+ }
+ else
+ {
+ this.logout();
+
+ if (error != null)
+ {
+ error(req); //TODO review this code path and how error is handled
+ }
+ }
+ }), error);
}
else
{
- params.immediate = false;
- params.authuser = -1;
- }
-
- gapi.auth.authorize(params, mxUtils.bind(this, function(resp)
- {
- try
+ var url = 'https://accounts.google.com/o/oauth2/v2/auth?client_id=' + this.clientId +
+ '&redirect_uri=' + encodeURIComponent(this.redirectUri) +
+ '&response_type=code&include_granted_scopes=true' +
+ (remember? '&access_type=offline&prompt=consent%20select_account' : '') + //Ask for consent again to get a new refresh token
+ '&scope=' + encodeURIComponent(this.scopes.join(' ')) +
+ '&state=appIndex%3D' + this.appIndex; //To identify which app is used
+
+ if (popup == null)
+ {
+ popup = this.createAuthWin(url);
+ }
+ else
{
- // Updates the current user info
- if (resp != null && resp.error == null)
+ popup.location = url;
+ }
+
+ if (popup != null)
+ {
+ window.onGoogleDriveCallback = mxUtils.bind(this, function(newAuthInfo, authWindow)
{
- if (this.user == null || !immediate || this.user.id != userId)
+ window.onGoogleDriveCallback = null;
+
+ try
{
- this.updateUser(success, error, remember);
+ if (newAuthInfo == null)
+ {
+ if (error != null)
+ {
+ error({message: mxResources.get('accessDenied')}); //TODO Check this error handling is correct
+ }
+ }
+ else
+ {
+ updateAuthInfo(newAuthInfo, remember, true);
+ }
}
- else if (success != null)
+ catch (e)
{
- success();
+ if (error != null)
+ {
+ error(e);
+ }
}
- }
- else if (error != null)
- {
- error(resp);
- }
-
- this.resetTokenRefresh(resp);
- }
- catch (e)
- {
- if (error != null)
- {
- error(e);
- }
- else
- {
- throw e;
- }
+ finally
+ {
+ if (authWindow != null)
+ {
+ authWindow.close();
+ }
+ }
+ });
+
+ popup.focus();
}
- }));
+ }
}
}
catch (e)
@@ -569,10 +723,10 @@ DriveClient.prototype.resetTokenRefresh = function(resp)
{
this.authorize(true, mxUtils.bind(this, function()
{
- //console.log('tokenRefresh: refreshed', gapi.auth.getToken());
+ //console.log('tokenRefresh: refreshed', this.token);
}), mxUtils.bind(this, function()
{
- //console.log('tokenRefresh: error refreshing', gapi.auth.getToken());
+ //console.log('tokenRefresh: error refreshing', this.token);
}));
}), resp.expires_in * 900);
}
@@ -608,19 +762,18 @@ DriveClient.prototype.checkToken = function(fn)
/**
* Checks if the client is authorized and calls the next step.
*/
-DriveClient.prototype.updateUser = function(success, error, remember)
+DriveClient.prototype.updateUser = function(success, error)
{
try
{
- var token = gapi.auth.getToken().access_token;
- var url = 'https://www.googleapis.com/oauth2/v2/userinfo?alt=json&access_token=' + token;
+ var url = 'https://www.googleapis.com/oauth2/v2/userinfo?alt=json&access_token=' + this.token;
this.ui.loadUrl(url, mxUtils.bind(this, function(data)
{
var info = JSON.parse(data);
// Requests more information about the user (email address is sometimes not in info)
- this.executeRequest(gapi.client.drive.about.get(), mxUtils.bind(this, function(resp)
+ this.executeRequest({url: '/about'}, mxUtils.bind(this, function(resp)
{
var email = mxResources.get('notAvailable');
var name = email;
@@ -634,7 +787,7 @@ DriveClient.prototype.updateUser = function(success, error, remember)
}
this.setUser(new DrawioUser(info.id, email, name, pic, info.locale));
- this.setUserId(info.id, remember);
+ this.userId = info.id;
if (success != null)
{
@@ -666,11 +819,12 @@ DriveClient.prototype.copyFile = function(id, title, success, error)
{
if (id != null && title != null)
{
- this.executeRequest(gapi.client.drive.files.copy({'fileId': id,
- 'fields': this.allFields, 'supportsTeamDrives': true,
- 'resource': {'title': title, 'properties':
- [{'key': 'channel', 'value': Editor.guid()}]}}),
- success, error);
+ this.executeRequest({url: '/files/' + id + '/copy?fields=' + encodeURIComponent(this.allFields)
+ + '&supportsTeamDrives=true', //&alt=json
+ method: 'POST',
+ params: {'title': title, 'properties':
+ [{'key': 'channel', 'value': Editor.guid()}]}
+ }, success, error);
}
};
@@ -712,13 +866,12 @@ DriveClient.prototype.moveFile = function(id, folderId, success, error)
*/
DriveClient.prototype.createDriveRequest = function(id, body)
{
- return gapi.client.request({
- 'path': '/drive/v2/files/' + id,
+ return {
+ 'url': '/files/' + id + '?uploadType=multipart&supportsTeamDrives=true',
'method': 'PUT',
- 'params': {'uploadType' : 'multipart', 'supportsTeamDrives': true},
- 'headers': {'Content-Type': 'application/json; charset=UTF-8'},
- 'body': JSON.stringify(body)
- });
+ 'contentType': 'application/json; charset=UTF-8',
+ 'params': body
+ };
};
/**
@@ -734,9 +887,9 @@ DriveClient.prototype.getLibrary = function(id, success, error)
*/
DriveClient.prototype.loadDescriptor = function(id, success, error, fields)
{
- this.executeRequest(gapi.client.drive.files.get({'fileId': id,
- 'fields': (fields != null) ? fields : this.allFields,
- 'supportsTeamDrives': true}), success, error);
+ this.executeRequest({
+ url: '/files/' + id + '?supportsTeamDrives=true&fields=' + (fields != null ? fields : this.allFields)
+ }, success, error);
};
/**
@@ -775,8 +928,9 @@ DriveClient.prototype.getFile = function(id, success, error, readXml, readLibrar
if (urlParams['rev'] != null)
{
- this.executeRequest(gapi.client.drive.revisions.get({'fileId': id,
- 'revisionId': urlParams['rev'], 'supportsTeamDrives': true}),
+ this.executeRequest({
+ url: '/files/' + id + '/revisions/' + urlParams['rev'] + '?supportsTeamDrives=true'
+ },
mxUtils.bind(this, function(resp)
{
// Redirects title to originalFilename to
@@ -805,7 +959,7 @@ DriveClient.prototype.getFile = function(id, success, error, readXml, readLibrar
if (/\.v(dx|sdx?)$/i.test(resp.title) || /\.gliffy$/i.test(resp.title) ||
(!this.ui.useCanvasForExport && binary))
{
- var url = resp.downloadUrl + '&access_token=' + gapi.auth.getToken().access_token;
+ var url = resp.downloadUrl + '&access_token=' + this.token;
this.ui.convertFile(url, resp.title, resp.mimeType, this.extension, success, error);
}
else
@@ -818,14 +972,7 @@ DriveClient.prototype.getFile = function(id, success, error, readXml, readLibrar
}
else
{
- if (this.isGoogleRealtimeMimeType(resp.mimeType))
- {
- this.convertRealtimeFile(resp, success, error);
- }
- else
- {
- this.getXmlFile(resp, success, error);
- }
+ this.getXmlFile(resp, success, error);
}
}
}
@@ -858,52 +1005,6 @@ DriveClient.prototype.isGoogleRealtimeMimeType = function(mimeType)
};
/**
- * Checks if the client is authorized and calls the next step.
- */
-DriveClient.prototype.getRealtimeData = function(id, success, error, retryCount)
-{
- if (App.GOOGLE_REALTIME_EOL - Date.now() < 0)
- {
- error({message: 'Google Realtime API export endpoint no longer available'});
- }
- else
- {
- this.executeRequest(gapi.client.drive.realtime.get({'fileId': id,
- 'supportsTeamDrives': true}), mxUtils.bind(this, function(resp)
- {
- var json = (resp.result != null) ? resp.result.data : null;
-
- if (json != null && json.value != null && json.value.diagrams != null)
- {
- success(json);
- }
- else if (error != null)
- {
- error({message: 'realtime.get returned invalid data for ' + id});
- }
- }), mxUtils.bind(this, function(resp)
- {
- if (retryCount == null)
- {
- retryCount = 0;
- }
-
- if (retryCount < 3)
- {
- window.setTimeout(mxUtils.bind(this, function()
- {
- this.getRealtimeData(id, success, error, retryCount + 1);
- }), (retryCount + 1) * 100);
- }
- else if (error != null)
- {
- error({message: 'realtime.get failed for ' + id});
- }
- }));
- }
-};
-
-/**
* Checks if the client is authorized and calls the next step. The ignoreMime argument is
* used for import via getFile. Default is false. The optional
* readLibrary argument is used for reading libraries. Default is false.
@@ -912,8 +1013,7 @@ DriveClient.prototype.getXmlFile = function(resp, success, error, ignoreMime, re
{
try
{
- var token = gapi.auth.getToken().access_token;
- var url = resp.downloadUrl + '&access_token=' + token;
+ var url = resp.downloadUrl + '&access_token=' + this.token;
// Loads XML to initialize realtime document if realtime is empty
this.ui.loadUrl(url, mxUtils.bind(this, function(data)
@@ -1290,21 +1390,17 @@ DriveClient.prototype.saveFile = function(file, revision, success, errFn, noChec
if (prevDesc != null)
{
// Pins previous revision
- this.executeRequest(gapi.client.drive.revisions.get(
- {
- 'fileId': prevDesc.id,
- 'revisionId': prevDesc.headRevisionId,
- 'supportsTeamDrives': true
- }), mxUtils.bind(this, mxUtils.bind(this, function(resp)
+ this.executeRequest({
+ url: '/files/' + prevDesc.id + '/revisions/' + prevDesc.headRevisionId + '?supportsTeamDrives=true'
+ }, mxUtils.bind(this, mxUtils.bind(this, function(resp)
{
resp.pinned = true;
- this.executeRequest(gapi.client.drive.revisions.update(
- {
- 'fileId': prevDesc.id,
- 'revisionId': prevDesc.headRevisionId,
- 'resource': resp
- }));
+ this.executeRequest({
+ url: '/files/' + prevDesc.id + '/revisions/' + prevDesc.headRevisionId,
+ method: 'PUT',
+ params: resp
+ });
})));
// Logs conversion
@@ -1412,9 +1508,10 @@ DriveClient.prototype.saveFile = function(file, revision, success, errFn, noChec
{
// Check for stale etag which can happen if a file is being saved or if
// the etag simply isn't change but system still returns a 412 error (stale)
- this.executeRequest(gapi.client.drive.files.get({'fileId': file.getId(),
- 'fields': this.catchupFields, 'supportsTeamDrives': true}),
- mxUtils.bind(this, function(resp)
+ this.executeRequest({
+ url: '/files/' + file.getId() + '?supportsTeamDrives=true&fields=' + this.catchupFields
+ },
+ mxUtils.bind(this, function(resp)
{
file.saveLevel = 7;
@@ -1655,8 +1752,9 @@ DriveClient.prototype.verifyMimeType = function(fileId, fn, force, error)
{
this.checkingMimeType = true;
- this.executeRequest(gapi.client.drive.files.get({'fileId': fileId, 'fields': 'mimeType',
- 'supportsTeamDrives': true}), mxUtils.bind(this, function(resp)
+ this.executeRequest({
+ url: '/files/' + fileId + '?supportsTeamDrives=true&fields=mimeType'
+ }, mxUtils.bind(this, function(resp)
{
this.checkingMimeType = false;
@@ -1785,29 +1883,25 @@ DriveClient.prototype.createUploadRequest = function(id, metadata, data, revisio
var reqObj =
{
- 'path': '/upload/drive/v2/files' + (id != null ? '/' + id : ''),
+ 'fullUrl': 'https://content.googleapis.com/upload/drive/v2/files' + (id != null ? '/' + id : '') + '?uploadType=multipart&supportsTeamDrives=true&fields=' + this.allFields,
'method': (id != null) ? 'PUT' : 'POST',
- 'params': {'uploadType': 'multipart'},
'headers': headers,
- 'body': delim + 'Content-Type: application/json\r\n\r\n' + JSON.stringify(metadata) + delim +
+ 'params': delim + 'Content-Type: application/json\r\n\r\n' + JSON.stringify(metadata) + delim +
'Content-Type: ' + ctype + '\r\n' + 'Content-Transfer-Encoding: base64\r\n' + '\r\n' +
((data != null) ? (binary) ? data : Base64.encode(data) : '') + close
}
if (!revision)
{
- reqObj.params['newRevision'] = false;
+ reqObj.url += '&newRevision=false';
}
if (pinned)
{
- reqObj.params['pinned'] = true;
+ reqObj.url += '&pinned=true';
}
- reqObj.params['supportsTeamDrives'] = true;
- reqObj.params['fields'] = this.allFields;
-
- return gapi.client.request(reqObj);
+ return reqObj;
};
/**
@@ -1840,7 +1934,6 @@ DriveClient.prototype.pickFile = function(fn, acceptAllFiles)
this.ui.spinner.stop();
// Reuses picker as long as token doesn't change.
- var token = gapi.auth.getToken().access_token;
var name = (acceptAllFiles) ? 'genericPicker' : 'filePicker';
// Click on background closes dialog as workaround for blocking dialog
@@ -1855,7 +1948,7 @@ DriveClient.prototype.pickFile = function(fn, acceptAllFiles)
}
});
- if (this[name] == null || this[name + 'Token'] != token)
+ if (this[name] == null || this[name + 'Token'] != this.token)
{
// FIXME: Dispose not working
// if (this[name] != null)
@@ -1864,7 +1957,7 @@ DriveClient.prototype.pickFile = function(fn, acceptAllFiles)
// this[name].dispose();
// }
- this[name + 'Token'] = token;
+ this[name + 'Token'] = this.token;
// Pseudo-hierarchical directory view, see
// https://groups.google.com/forum/#!topic/google-picker-api/FSFcuJe7icQ
@@ -1905,6 +1998,7 @@ DriveClient.prototype.pickFile = function(fn, acceptAllFiles)
.addView(view3)
.addView(google.picker.ViewId.RECENTLY_PICKED)
.addView(view4)
+// .setOrigin(window.location.protocol + '//' + window.location.host) //TODO Still there is an error in console about incorrect origin!, it also causes the picker to hang (has a blocking empty iframe on top!)
.setCallback(mxUtils.bind(this, function(data)
{
if (data.action == google.picker.Action.PICKED ||
@@ -1957,7 +2051,6 @@ DriveClient.prototype.pickFolder = function(fn, force)
this.ui.spinner.stop();
// Reuses picker as long as token doesn't change.
- var token = gapi.auth.getToken().access_token;
var name = 'folderPicker';
// Click on background closes dialog as workaround for blocking dialog
@@ -1972,7 +2065,7 @@ DriveClient.prototype.pickFolder = function(fn, force)
}
});
- if (this[name] == null || this[name + 'Token'] != token)
+ if (this[name] == null || this[name + 'Token'] != this.token)
{
// FIXME: Dispose not working
// if (this[name] != null)
@@ -1981,7 +2074,7 @@ DriveClient.prototype.pickFolder = function(fn, force)
// this[name].dispose();
// }
- this[name + 'Token'] = token;
+ this[name + 'Token'] = this.token;
// Pseudo-hierarchical directory view, see
// https://groups.google.com/forum/#!topic/google-picker-api/FSFcuJe7icQ
@@ -2013,6 +2106,7 @@ DriveClient.prototype.pickFolder = function(fn, force)
.addView(view3)
.addView(google.picker.ViewId.RECENTLY_PICKED)
.setTitle(mxResources.get('pickFolder'))
+// .setOrigin(window.location.protocol + '//' + window.location.host) //TODO Still there is an error in console about incorrect origin!, it also causes the picker to hang (has a blocking empty iframe on top!)
.setCallback(mxUtils.bind(this, function(data)
{
if (data.action == google.picker.Action.PICKED ||
@@ -2102,9 +2196,8 @@ DriveClient.prototype.pickLibrary = function(fn)
});
// Reuses picker as long as token doesn't change
- var token = gapi.auth.getToken().access_token;
- if (this.libraryPicker == null || this.libraryPickerToken != token)
+ if (this.libraryPicker == null || this.libraryPickerToken != this.token)
{
// FIXME: Dispose not working
// if (this[name] != null)
@@ -2113,7 +2206,7 @@ DriveClient.prototype.pickLibrary = function(fn)
// this[name].dispose();
// }
- this.libraryPickerToken = token;
+ this.libraryPickerToken = this.token;
// Pseudo-hierarchical directory view, see
// https://groups.google.com/forum/#!topic/google-picker-api/FSFcuJe7icQ
@@ -2144,6 +2237,7 @@ DriveClient.prototype.pickLibrary = function(fn)
.addView(view3)
.addView(google.picker.ViewId.RECENTLY_PICKED)
.addView(view4)
+// .setOrigin(window.location.protocol + '//' + window.location.host) //TODO Still there is an error in console about incorrect origin!, it also causes the picker to hang (has a blocking empty iframe on top!)
.setCallback(mxUtils.bind(this, function(data)
{
if (data.action == google.picker.Action.PICKED ||
@@ -2200,7 +2294,7 @@ DriveClient.prototype.showPermissions = function(id)
try
{
var shareClient = new gapi.drive.share.ShareClient(this.appId);
- shareClient.setOAuthToken(gapi.auth.getToken().access_token);
+ shareClient.setOAuthToken(this.token);
shareClient.setItemIds([id]);
shareClient.showSettingsDialog();
@@ -2262,624 +2356,35 @@ DriveClient.prototype.showPermissions = function(id)
}
};
-/**
- * Converts the given file from realtime to XML.
- */
-DriveClient.prototype.getRealtimeAge = function(desc, json)
+DriveClient.prototype.clearPersistentToken = function()
{
- var mod = (json != null && json.value != null && json.value.modifiedDate != null) ?
- json.value.modifiedDate.json : null;
- var result = 0;
-
- if (mod != null && mod > 0)
- {
- var ts = new Date(desc.modifiedDate);
- var rt = new Date(mod);
- result = ts.getTime() - rt.getTime();
- }
+ //Since we have multiple accounts now, full deletion is not possible
+ var authInfo = JSON.parse(this.getPersistentToken(true)) || {};
- return result;
-};
-
-/**
- * Converts the given file from realtime to XML.
- */
-DriveClient.prototype.convertRealtimeFile = function(desc, success, error)
-{
- var xmlSuccess = mxUtils.bind(this, function(file)
- {
- file.convertedFrom = 'xml';
- success(file);
- });
+ //Delete current user info
+ delete authInfo.current;
+ delete authInfo[this.userId];
- var jsonSuccess = mxUtils.bind(this, function(file)
- {
- file.convertedFrom = 'json';
- success(file);
- });
-
- this.getRealtimeData(desc.id, mxUtils.bind(this, function(json)
+ //Set the next user as current
+ for (var id in authInfo)
{
- try
- {
- var age = this.getRealtimeAge(desc, json);
-
- // Uses realtime if newer or less than 5 minutes old
- if (age < 300000)
- {
- jsonSuccess(new DriveFile(this.ui, mxUtils.getXml(
- this.convertJsonToXml(json)), desc));
- }
- else
- {
- this.getXmlFile(desc, xmlSuccess, mxUtils.bind(this, function()
- {
- try
- {
- jsonSuccess(new DriveFile(this.ui, mxUtils.getXml(
- this.convertJsonToXml(json)), desc));
-
- }
- catch (e)
- {
- this.getXmlFile(desc, xmlSuccess, error);
- }
- }));
- }
- }
- catch (e)
- {
- this.getXmlFile(desc, xmlSuccess, error);
- }
- }), mxUtils.bind(this, function()
- {
- this.getXmlFile(desc, xmlSuccess, error);
- }));
-};
-
-/**
- * Returns the location as a new object.
- */
-DriveClient.prototype.convertJsonToXml = function(json, uncompressed)
-{
- if (json.value == null || json.value.diagrams == null)
- {
- throw Error('Invalid JSON: no diagrams in root map');
- }
- else
- {
- var node = mxUtils.createXmlDocument().createElement('mxfile');
- var diagrams = json.value.diagrams.value;
-
- for (var i = 0; i < diagrams.length; i++)
- {
- try
- {
- var diagramNode = this.decodeJsonPage(diagrams[i].value,
- node.ownerDocument.createElement('diagram'),
- uncompressed);
-
- //if (diagramNode.getAttribute('name') == null)
- //{
- // TODO: Should only use when converting but not when comparing
- //diagramNode.setAttribute('name', mxResources.get('pageWithNumber', [i + 1]));
- //}
-
- node.appendChild(diagramNode);
- }
- catch (e)
- {
- throw Error('Error on page ' + i + ': ' + e.stack);
- }
- }
-
- //console.log('leaving convertJson', mxUtils.getPrettyXml(node));
-
- return node;
- }
-};
-
-/**
- * Returns true if copy, export and print are not allowed for this file.
- */
-DriveClient.prototype.decodeJsonPage = function(json, node, uncompressed)
-{
- if (json == null)
- {
- throw Error('Invalid JSON: json for page is null');
- }
- else
- {
- var codec = new mxCodec();
- var root = this.createJsonCell(json.root, codec);
-
- if (root == null)
- {
- throw Error('Invalid JSON: no root cell for page');
- }
- else
- {
- // Dummy model for encoding
- var modelNode = codec.encode(new mxGraphModel(root));
- this.decodeJsonViewState(json, modelNode);
-
- if (uncompressed)
- {
- node.appendChild(modelNode);
- }
- else
- {
- mxUtils.setTextContent(node, Graph.compressNode(modelNode));
- }
-
- // Adds attributes to diagram node
- if (json.id != null)
- {
- node.setAttribute('id', json.id.json);
- }
- else
- {
- // Workaround for missing page ID in JSON
- node.setAttribute('id', Editor.guid());
- }
-
- if (json.name != null)
- {
- node.setAttribute('name', json.name.json);
- }
- }
- }
-
- //console.log('decoded json page', json, node);
-
- return node;
-};
-
-/**
- * Writes the view state to the given node.
- */
-DriveClient.prototype.decodeJsonViewState = function(json, node)
-{
- // Page format is stored as "width,height"
- var pf = (json.pageFormat != null) ? json.pageFormat.json : null;
-
- if (pf != null && pf.length > 0)
- {
- var values = pf.split(',');
-
- if (values.length > 1)
- {
- node.setAttribute('pageWidth', values[0]);
- node.setAttribute('pageHeight', values[1]);
- }
+ authInfo.current = {userId: id, expires: 0}; //An expired token
+ break;
}
-
- var bg = (json.backgroundColor != null) ? json.backgroundColor.json : null;
- if (bg != null && bg.length > 0)
- {
- node.setAttribute('background', bg);
- }
-
- var img = (json.backgroundImage != null) ? json.backgroundImage.json : null;
-
- if (img != null && img.length > 0)
- {
- node.setAttribute('backgroundImage', img);
- }
-
- node.setAttribute('fold', (json.foldingEnabled != null) ? json.foldingEnabled.json : '0');
- node.setAttribute('pageScale', (json.pageScale != null) ? json.pageScale.json : mxGraph.prototype.pageScale);
- node.setAttribute('math', (json.mathEnabled != null) ? json.mathEnabled.json : '0');
- node.setAttribute('shadow', (json.shadowVisible != null) ? json.shadowVisible.json : '0');
-
- return node;
+ DrawioClient.prototype.setPersistentToken.call(this, JSON.stringify(authInfo));
};
-/**
- * Syncs initial state from collab model to graph model.
- */
-DriveClient.prototype.createJsonCell = function(json, codec)
+DriveClient.prototype.setPersistentToken = function(userAuthInfo, sessionOnly)
{
- if (json != null && json.id != null)
- {
- var val = json.value;
- var cell = this.jsonToCell(val, codec);
- codec.putObject(json.id, cell);
-
- cell.source = (val.source != null) ? this.createJsonCell(val.source, codec) : null;
- cell.target = (val.target != null) ? this.createJsonCell(val.target, codec) : null;
-
- // Cells can be serialized as parents of terminals
- this.createJsonCell(val.parent, codec)
-
- for (var i = 0; i < val.children.value.length; i++)
- {
- var child = this.createJsonCell(val.children.value[i], codec);
-
- if (child != null)
- {
- cell.insert(child);
- }
- else
- {
- throw Error('Invalid JSON: no child ' + i + ' for cell ' + json.id);
- }
- }
-
- return cell;
- }
- else if (json != null && json.ref != null)
- {
- return codec.objects[json.ref];
- }
- else
- {
- return null;
- }
-};
-
-/**
- * Adds the listener for automatically saving the diagram for local changes.
- */
-DriveClient.prototype.jsonToCell = function(val, codec)
-{
- var cell = new mxCell();
-
- cell.id = val.cellId.json;
- cell.vertex = val.type.json == 'vertex';
- cell.edge = val.type.json == 'edge';
- cell.connectable = val.connectable.json != '0';
- cell.collapsed = val.collapsed.json == '1';
- cell.visible = val.visible.json != '0';
- cell.style = (val.style != null) ? val.style.json : null;
- cell.value = (val.xmlValue != null) ?
- mxUtils.parseXml(val.xmlValue.json).documentElement :
- ((val.value != null) ? val.value.json : null);
- cell.geometry = (val.geometry != null) ?
- codec.decode(mxUtils.parseXml(val.geometry.json).documentElement) : null;
-
- return cell;
-};
-
-/**
- * Invokes the given function if the user has writeable realtime files
- * that must be converted.
- */
-DriveClient.prototype.checkRealtimeFiles = function(fn)
-{
- var email = (this.user != null && this.user.email != null) ? this.user.email : null;
-
- this.executeRequest(gapi.client.drive.files.list({'maxResults': 1, 'q':
- 'mimeType=\'application/vnd.jgraph.mxfile.realtime\'' +
- ((email != null) ? ' and \'' + email + '\' in writers' : ''),
- 'includeTeamDriveItems': true, 'supportsTeamDrives': true}), mxUtils.bind(this, function(res)
- {
- if (res != null && (res.nextPageToken != null || (res.items != null && res.items.length > 0)))
- {
- fn();
- }
- }));
-};
-
-/**
- * Converts all old realtime files. Invoke this using
- * https://www.draw.io/?mode=google&convert-realtime=1
- */
-DriveClient.prototype.convertRealtimeFiles = function()
-{
- var output = document.createElement('div');
- output.style.cssText = 'position:absolute;top:0px;left:0px;right:0px;bottom:0px;padding:8px;' +
- 'background:#ffffff;z-index:2;overflow:auto;white-space:nowrap;line-height:1.5em;';
- document.body.appendChild(output);
- var t0 = Date.now();
-
- var print = mxUtils.bind(this, function(msg, noBr)
- {
- output.innerHTML += msg + ((noBr) ? '' : '<br>');
- output.scrollTop = output.scrollHeight;
- });
-
- if (App.GOOGLE_REALTIME_EOL - Date.now() < 0)
- {
- print('draw.io (' + EditorUi.VERSION + '): Google Realtime API export endpoint no longer available');
- }
- else
- {
- print('draw.io (' + EditorUi.VERSION + ') is searching files to be converted...');
- print('<a href="https://desk.draw.io/support/solutions/articles/16000092210" target="_blank">Click here for help</a>');
-
- if (this.ui.spinner.spin(document.body, 'Searching files...'))
- {
- this.checkToken(mxUtils.bind(this, function()
- {
- var convertDelay = 2000;
- var convertedIds = {};
- var converted = 0;
- var fromJson = 0;
- var fromXml = 0;
- var loadFail = 0;
- var invalid = 0;
- var saveFail = 0;
- var failed = 0;
- var total = 0;
- var queryFail = 0;
+ var authInfo = JSON.parse(this.getPersistentToken(true)) || {};
- var email = (this.user != null && this.user.email != null) ? this.user.email : null;
- var q = 'mimeType=\'application/vnd.jgraph.mxfile.realtime\'' +
- ((email != null) ? ' and \'' + email + '\' in writers' : '');
-
- var done = mxUtils.bind(this, function()
- {
- this.ui.spinner.stop();
- print('<br>Conversion complete. Successfully converted ' + converted + ' file(s).', true);
-
- if (failed > 0)
- {
- print(' Failed to convert ' + failed + ' file(s).<br><br><b>ACTION REQUIRED:</b><br><ul><li>Click ' +
- '<a target="_blank" href="https://drive.google.com/drive/u/0/search?q=type:application/vnd.jgraph.mxfile.realtime">here</a> ' +
- 'to list all affected files</li><li>Open each file in turn by right-clicking the file and selecting open with draw.io</li>' +
- '<li>Open each file in turn. When loaded, select File->Save</li></ul>');
- }
- else
- {
- print('<br><br>This window can now be closed.')
- }
-
- try
- {
- var dt = Date.now() - t0;
-
- // Logs conversion
- EditorUi.logEvent({category: 'AUTO-CONVERT',
- action: 'total_' + total + '-done_' + converted +
- '-fail_' + failed + '-xml_' + fromXml + '-json_' + fromJson +
- '-load_' + loadFail + '-save_' + saveFail +
- '-invalid_' + invalid + '-dt-' + Math.round(dt / 1000),
- label: (this.user != null) ? ('user_' + this.user.id) : '-nouser'});
- }
- catch (e)
- {
- // ignore
- }
- });
-
- var getMessage = function(err)
- {
- return (err == null) ? '' : ((err.message != null) ? err.message : ((err.error != null &&
- err.error.message != null) ? err.error.message : ''));
- };
-
- var doConvert = mxUtils.bind(this, function()
- {
- if (this.ui.spinner.spin(document.body, 'Converting ' + total + ' file(s)'))
- {
- print('Found ' + total + ' file(s). This will take up to ' + Math.ceil((total * (convertDelay + 3000)) / 60000) +
- ' minute(s). <b>Please do not close this window!</b><br>');
- var counter = 0;
+ userAuthInfo.userId = this.userId;
+ authInfo.current = userAuthInfo;
+ authInfo[this.userId] = {
+ refresh_token: userAuthInfo.refresh_token,
+ user: this.user
+ };
- // Does not show picker if there are no folders in the root
- var nextPage = mxUtils.bind(this, function(token, delay)
- {
- var query = {'maxResults': 1, 'q': q, 'includeTeamDriveItems': true, 'supportsTeamDrives': true};
-
- if (token != null)
- {
- query.pageToken = token;
- }
-
- var acceptResponse = true;
-
- var timeoutThread = window.setTimeout(mxUtils.bind(this, function()
- {
- acceptResponse = false;
- nextPage(token, delay);
- }), this.ui.timeout);
-
- this.executeRequest(gapi.client.drive.files.list(query), mxUtils.bind(this, function(res)
- {
- window.clearTimeout(timeoutThread);
-
- if (acceptResponse)
- {
- var doNextPage = mxUtils.bind(this, function()
- {
- if (res.nextPageToken != null)
- {
- nextPage(res.nextPageToken);
- }
- else
- {
- done();
- }
- });
-
- if (res != null && res.error != null)
- {
- queryFail++;
-
- if (queryFail < 4)
- {
- nextPage(token, delay);
- }
- else
- {
- this.ui.spinner.stop();
- print('Query for next file failed multiple times. Exiting.<br><br>This window can now be closed.');
- }
- }
- else if (res != null && (res.items == null || res.items.length == 0) &&
- res.nextPageToken != null)
- {
- // Next page can still contain results, see
- // https://stackoverflow.com/questions/23741845
- nextPage(res.nextPageToken, 10000);
- }
- else if (res != null && res.items != null && res.items.length > 0)
- {
- var fileId = res.items[0].id;
- this.ui.spinner.stop();
- counter++;
-
- if (this.ui.spinner.spin(document.body, 'Converting file ' + counter + ' of ' + total))
- {
- print('Converting ' + counter + ' of ' + total + ': "' + mxUtils.htmlEntities(res.items[0].title) +
- '" (<a href="https://drive.google.com/open?id=' + fileId + '" target="_blank">' + fileId + '</a>)... ', true);
-
- window.setTimeout(mxUtils.bind(this, function()
- {
- // Exits if same file is returned twice
- if (convertedIds[fileId] == null)
- {
- convertedIds[fileId] = true;
-
- acceptResponse = true;
-
- timeoutThread = window.setTimeout(mxUtils.bind(this, function()
- {
- acceptResponse = false;
-
- failed++;
- loadFail++;
- print('<img src="' + this.ui.editor.graph.warningImage.src + '" border="0" valign="absmiddle"/> Timeout');
- doNextPage();
- }), this.ui.timeout);
-
- this.getFile(fileId, mxUtils.bind(this, function(file)
- {
- window.clearTimeout(timeoutThread);
-
- if (acceptResponse)
- {
- if (file.constructor == DriveFile)
- {
- if (file.convertedFrom == 'json')
- {
- fromJson++;
- }
- else
- {
- fromXml++;
- }
-
- acceptResponse = true;
-
- timeoutThread = window.setTimeout(mxUtils.bind(this, function()
- {
- acceptResponse = false;
-
- failed++;
- saveFail++;
- print('<img src="' + this.ui.editor.graph.warningImage.src + '" border="0" valign="absmiddle"/> Timeout');
- doNextPage();
- }), this.ui.timeout);
-
- this.saveFile(file, null, mxUtils.bind(this, function()
- {
- window.clearTimeout(timeoutThread);
-
- if (acceptResponse)
- {
- converted++;
- print('OK <img src="' + Editor.checkmarkImage + '" border="0" valign="middle"/>');
- doNextPage();
- }
- }), mxUtils.bind(this, function(err)
- {
- window.clearTimeout(timeoutThread);
-
- if (acceptResponse)
- {
- var msg = getMessage(err);
- failed++;
- saveFail++;
- print('<img src="' + this.ui.editor.graph.warningImage.src + '" border="0" valign="absmiddle"/> ' + msg);
- doNextPage();
- }
- }));
- }
- else
- {
- failed++;
- invalid++;
- print('<img src="' + this.ui.editor.graph.warningImage.src + '" border="0" valign="absmiddle"/> Invalid file');
- doNextPage();
- }
- }
- }), mxUtils.bind(this, function(err)
- {
- window.clearTimeout(timeoutThread);
-
- if (acceptResponse)
- {
- var msg = getMessage(err);
- failed++;
- loadFail++;
- print('<img src="' + this.ui.editor.graph.warningImage.src + '" border="0" valign="absmiddle"/> ' + msg);
- doNextPage();
- }
- }));
- }
- else
- {
- this.ui.spinner.stop();
- print('Search returned duplicate file ' + fileId + '. Exiting.<br><br>This window can now be closed.');
- }
- }), (delay != null) ? delay : convertDelay)
- }
- }
- else
- {
- done();
- }
- }
- }));
- });
-
- nextPage();
- }
- });
-
- var totals = {'maxResults': 10000, 'q': q, 'includeTeamDriveItems': true, 'supportsTeamDrives': true};
-
- var count = mxUtils.bind(this, function(token)
- {
- if (token != null)
- {
- totals.pageToken = token;
- }
-
- this.executeRequest(gapi.client.drive.files.list(totals), mxUtils.bind(this, function(res)
- {
- total += (res != null && res.items != null) ? res.items.length : 0;
-
- if (res.nextPageToken != null)
- {
- count(res.nextPageToken);
- }
- else
- {
- this.ui.spinner.stop();
-
- this.ui.showError('Confirm', 'You are about to convert ' + total + ' file(s)',
- 'Cancel', mxUtils.bind(this, function()
- {
- print('Cancelled by user.<br><br>This window can now be closed.');
- }), null, 'OK', doConvert, 'Help', function()
- {
- window.open('https://desk.draw.io/support/solutions/articles/16000092210');
- }, 340, 120);
- }
- }));
- });
-
- count();
- }));
- }
- else
- {
- this.ui.spinner.stop();
- print('Busy. <br><br>This window can now be closed.');
- }
- }
+ DrawioClient.prototype.setPersistentToken.call(this, JSON.stringify(authInfo), sessionOnly);
};
diff --git a/src/main/webapp/js/diagramly/DriveComment.js b/src/main/webapp/js/diagramly/DriveComment.js
index 829d98f6..c716eef1 100644
--- a/src/main/webapp/js/diagramly/DriveComment.js
+++ b/src/main/webapp/js/diagramly/DriveComment.js
@@ -20,12 +20,12 @@ DriveComment.prototype.addReply = function(reply, success, error, doResolve, doR
body.verb = 'reopen';
}
- this.file.ui.drive.executeRequest(gapi.client.drive.replies.insert(
+ this.file.ui.drive.executeRequest(
{
- 'fileId': this.file.getId(),
- 'commentId': this.id,
- 'resource': body
- }),
+ url: '/files/' + this.file.getId() + '/comments/' + this.id + '/replies',
+ params: body,
+ method: 'POST'
+ },
mxUtils.bind(this, function(resp)
{
success(resp.replyId); //pass comment id
@@ -39,17 +39,16 @@ DriveComment.prototype.editComment = function(newContent, success, error)
this.file.ui.drive.executeRequest(
this.pCommentId?
- gapi.client.drive.replies.patch({
- 'fileId': this.file.getId(),
- 'commentId': this.pCommentId,
- 'replyId': this.id,
- 'resource': body
- }) :
- gapi.client.drive.comments.patch({
- 'fileId': this.file.getId(),
- 'commentId': this.id,
- 'resource': body
- }),
+ {
+ url: '/files/' + this.file.getId() + '/comments/' + this.pCommentId + '/replies/' + this.id,
+ params: body,
+ method: 'PATCH'
+ } :
+ {
+ url: '/files/' + this.file.getId() + '/comments/' + this.id,
+ params: body,
+ method: 'PATCH'
+ },
success, error);
};
@@ -57,14 +56,13 @@ DriveComment.prototype.deleteComment = function(success, error)
{
this.file.ui.drive.executeRequest(
this.pCommentId?
- gapi.client.drive.replies.delete({
- 'fileId': this.file.getId(),
- 'commentId': this.pCommentId,
- 'replyId': this.id
- }):
- gapi.client.drive.comments.delete({
- 'fileId': this.file.getId(),
- 'commentId': this.id
- }),
+ {
+ url: '/files/' + this.file.getId() + '/comments/' + this.pCommentId + '/replies/' + this.id,
+ method: 'DELETE'
+ }:
+ {
+ url: '/files/' + this.file.getId() + '/comments/' + this.id,
+ method: 'DELETE'
+ },
success, error);
};
diff --git a/src/main/webapp/js/diagramly/DriveFile.js b/src/main/webapp/js/diagramly/DriveFile.js
index 1392681f..cf89e1f3 100644
--- a/src/main/webapp/js/diagramly/DriveFile.js
+++ b/src/main/webapp/js/diagramly/DriveFile.js
@@ -71,10 +71,10 @@ DriveFile.prototype.getMode = function()
*/
DriveFile.prototype.getPublicUrl = function(fn)
{
- gapi.client.drive.permissions.list(
- {
- 'fileId': this.desc.id
- }).execute(mxUtils.bind(this, function(resp)
+ this.ui.drive.executeRequest({
+ url: '/files/' + this.desc.id + '/permissions'
+ },
+ mxUtils.bind(this, function(resp)
{
if (resp != null && resp.items != null)
{
@@ -536,8 +536,11 @@ DriveFile.prototype.isRevisionHistorySupported = function()
*/
DriveFile.prototype.getRevisions = function(success, error)
{
- this.ui.drive.executeRequest(gapi.client.drive.revisions.list({'fileId': this.getId()}),
- mxUtils.bind(this, function(resp)
+ this.ui.drive.executeRequest(
+ {
+ url: '/files/' + this.getId() + '/revisions'
+ },
+ mxUtils.bind(this, function(resp)
{
for (var i = 0; i < resp.items.length; i++)
{
@@ -652,9 +655,11 @@ DriveFile.prototype.setDescriptorEtag = function(desc, etag)
*/
DriveFile.prototype.loadPatchDescriptor = function(success, error)
{
- this.ui.drive.executeRequest(gapi.client.drive.files.get({'fileId': this.getId(),
- 'fields': this.ui.drive.catchupFields, 'supportsTeamDrives': true}),
- mxUtils.bind(this, function(desc)
+ this.ui.drive.executeRequest(
+ {
+ url: '/files/' + this.getId() + '?supportsTeamDrives=true&fields=' + this.ui.drive.catchupFields
+ },
+ mxUtils.bind(this, function(desc)
{
success(desc);
}), error);
@@ -712,8 +717,11 @@ DriveFile.prototype.getComments = function(success, error)
return comment;
};
- this.ui.drive.executeRequest(gapi.client.drive.comments.list({'fileId': this.getId()}),
- mxUtils.bind(this, function(resp)
+ this.ui.drive.executeRequest(
+ {
+ url: '/files/' + this.getId() + '/comments'
+ },
+ mxUtils.bind(this, function(resp)
{
var comments = [];
@@ -735,8 +743,13 @@ DriveFile.prototype.addComment = function(comment, success, error)
{
var body = {'content': comment.content};
- this.ui.drive.executeRequest(gapi.client.drive.comments.insert({'fileId': this.getId(), 'resource': body}),
- mxUtils.bind(this, function(resp)
+ this.ui.drive.executeRequest(
+ {
+ url: '/files/' + this.getId() + '/comments',
+ method: 'POST',
+ params: body
+ },
+ mxUtils.bind(this, function(resp)
{
success(resp.commentId); //pass comment id
}), error);
diff --git a/src/main/webapp/js/diagramly/EditorUi.js b/src/main/webapp/js/diagramly/EditorUi.js
index 5d92b17e..648d5d36 100644
--- a/src/main/webapp/js/diagramly/EditorUi.js
+++ b/src/main/webapp/js/diagramly/EditorUi.js
@@ -14,6 +14,17 @@
*/
EditorUi.compactUi = uiTheme != 'atlas';
+
+ /**
+ * Overrides default grid color for dark mode
+ */
+ mxGraphView.prototype.defaultDarkGridColor = '#6e6e6e';
+
+ if (uiTheme == 'dark')
+ {
+ mxGraphView.prototype.gridColor = mxGraphView.prototype.defaultDarkGridColor;
+ }
+
/**
* Switch to disable logging for mode and search terms.
*/
@@ -3660,14 +3671,77 @@
this.handleError(resp, title, fn, invokeFnOnClose, notFoundMessage)
}), retry, mxResources.get('changeUser'), mxUtils.bind(this, function()
{
- if (this.spinner.spin(document.body, mxResources.get('loading')))
+ var driveUsers = this.drive.getUsersList();
+
+ var div = document.createElement('div');
+
+ var title = document.createElement('span');
+ title.style.marginTop = '6px';
+ mxUtils.write(title, mxResources.get('changeUser') + ': ');
+
+ div.appendChild(title);
+
+ var usersSelect = document.createElement('select');
+ usersSelect.style.width = '200px';
+
+ //TODO This code is similar to Dialogs.js change user part in SplashDialog
+ function fillUsersSelect()
{
- this.drive.clearUserId();
- gapi.auth.signOut();
+ usersSelect.innerHTML = '';
+
+ for (var i = 0; i < driveUsers.length; i++)
+ {
+ var option = document.createElement('option');
+ mxUtils.write(option, driveUsers[i].displayName);
+ option.value = i;
+ usersSelect.appendChild(option);
+ //More info (email) about the user in a disabled option
+ option = document.createElement('option');
+ option.innerHTML = '&nbsp;&nbsp;&nbsp;';
+ mxUtils.write(option, '<' + driveUsers[i].email + '>');
+ option.setAttribute('disabled', 'disabled');
+ usersSelect.appendChild(option);
+ }
- // Reload page to reset client auth
- window.location.reload();
+ //Add account option
+ var option = document.createElement('option');
+ mxUtils.write(option, mxResources.get('addAccount'));
+ option.value = driveUsers.length;
+ usersSelect.appendChild(option);
}
+
+ fillUsersSelect();
+
+ mxEvent.addListener(usersSelect, 'change', mxUtils.bind(this, function()
+ {
+ var userIndex = usersSelect.value;
+ var existingAccount = driveUsers.length != userIndex;
+
+ if (existingAccount)
+ {
+ this.drive.setUser(driveUsers[userIndex]);
+ }
+
+ this.drive.authorize(existingAccount, mxUtils.bind(this, function()
+ {
+ if (!existingAccount)
+ {
+ driveUsers = this.drive.getUsersList();
+ fillUsersSelect();
+ }
+ }), mxUtils.bind(this, function(resp)
+ {
+ this.handleError(resp);
+ }), true);
+ }));
+
+ div.appendChild(usersSelect);
+
+ var dlg = new CustomDialog(this, div, mxUtils.bind(this, function()
+ {
+ this.loadFile(window.location.hash.substr(1), true);
+ }));
+ this.showDialog(dlg.container, 300, 75, true, true);
}), mxResources.get('cancel'), mxUtils.bind(this, function()
{
window.location.hash = '';
@@ -9497,6 +9571,30 @@
// Gets recent colors from settings
ColorDialog.recentColors = mxSettings.getRecentColors();
+ // Avoids overridden values for changes in
+ // multiple windows and updates shared values
+ if (isLocalStorage)
+ {
+ try
+ {
+ window.addEventListener('storage', mxUtils.bind(this, function(evt)
+ {
+ if (evt.key == mxSettings.key)
+ {
+ mxSettings.load();
+
+ // Updates values
+ ColorDialog.recentColors = mxSettings.getRecentColors();
+ this.menus.customFonts = mxSettings.getCustomFonts();
+ }
+ }), false);
+ }
+ catch (e)
+ {
+ // ignore
+ }
+ }
+
// Updates UI to reflect current edge style
this.fireEvent(new mxEventObject('styleChanged', 'keys', [], 'values', [], 'cells', []));
@@ -9537,11 +9635,13 @@
/**
* Persists default grid color.
*/
- this.editor.graph.view.gridColor = mxSettings.getGridColor();
+ this.editor.graph.view.gridColor = mxSettings.getGridColor(uiTheme == 'dark');
this.addListener('gridColorChanged', mxUtils.bind(this, function(sender, evt)
{
- mxSettings.setGridColor(this.editor.graph.view.gridColor);
+ console.log('gridColorChanged', this.editor.graph.view.gridColor);
+
+ mxSettings.setGridColor(this.editor.graph.view.gridColor, uiTheme == 'dark');
mxSettings.save();
}));
diff --git a/src/main/webapp/js/diagramly/GitHubClient.js b/src/main/webapp/js/diagramly/GitHubClient.js
index 55230735..65fbaade 100644
--- a/src/main/webapp/js/diagramly/GitHubClient.js
+++ b/src/main/webapp/js/diagramly/GitHubClient.js
@@ -769,7 +769,7 @@ GitHubClient.prototype.showGitHubDialog = function(showFiles, fn)
var content = document.createElement('div');
content.style.whiteSpace = 'nowrap';
content.style.overflow = 'hidden';
- content.style.height = '224px';
+ content.style.height = '304px';
var hd = document.createElement('h3');
mxUtils.write(hd, mxResources.get((showFiles) ? 'selectFile' : 'selectFolder'));
@@ -783,7 +783,7 @@ GitHubClient.prototype.showGitHubDialog = function(showFiles, fn)
div.style.padding = '4px';
div.style.overflow = 'auto';
div.style.lineHeight = '1.2em';
- div.style.height = '194px';
+ div.style.height = '274px';
content.appendChild(div);
var listItem = document.createElement('div');
@@ -797,7 +797,7 @@ GitHubClient.prototype.showGitHubDialog = function(showFiles, fn)
{
fn(org + '/' + repo + '/' + encodeURIComponent(ref) + '/' + path);
}));
- this.ui.showDialog(dlg.container, 340, 270, true, true);
+ this.ui.showDialog(dlg.container, 420, 360, true, true);
if (showFiles)
{
@@ -808,9 +808,10 @@ GitHubClient.prototype.showGitHubDialog = function(showFiles, fn)
{
var link = document.createElement('a');
link.setAttribute('href', 'javascript:void(0);');
+ link.setAttribute('title', label);
mxUtils.write(link, label);
mxEvent.addListener(link, 'click', fn);
-
+
if (padding != null)
{
var temp = listItem.cloneNode();
diff --git a/src/main/webapp/js/diagramly/GitLabClient.js b/src/main/webapp/js/diagramly/GitLabClient.js
index 3462b14f..b25fd180 100644
--- a/src/main/webapp/js/diagramly/GitLabClient.js
+++ b/src/main/webapp/js/diagramly/GitLabClient.js
@@ -649,7 +649,7 @@ GitLabClient.prototype.showGitLabDialog = function(showFiles, fn)
var content = document.createElement('div');
content.style.whiteSpace = 'nowrap';
content.style.overflow = 'hidden';
- content.style.height = '224px';
+ content.style.height = '304px';
var hd = document.createElement('h3');
mxUtils.write(hd, mxResources.get((showFiles) ? 'selectFile' : 'selectFolder'));
@@ -663,7 +663,7 @@ GitLabClient.prototype.showGitLabDialog = function(showFiles, fn)
div.style.padding = '4px';
div.style.overflow = 'auto';
div.style.lineHeight = '1.2em';
- div.style.height = '194px';
+ div.style.height = '274px';
content.appendChild(div);
var listItem = document.createElement('div');
@@ -677,7 +677,7 @@ GitLabClient.prototype.showGitLabDialog = function(showFiles, fn)
{
fn(org + '/' + repo + '/' + encodeURIComponent(ref) + '/' + path);
}));
- this.ui.showDialog(dlg.container, 340, 270, true, true);
+ this.ui.showDialog(dlg.container, 420, 360, true, true);
if (showFiles)
{
@@ -688,6 +688,7 @@ GitLabClient.prototype.showGitLabDialog = function(showFiles, fn)
{
var link = document.createElement('a');
link.setAttribute('href', 'javascript:void(0);');
+ link.setAttribute('title', label);
mxUtils.write(link, label);
mxEvent.addListener(link, 'click', fn);
diff --git a/src/main/webapp/js/diagramly/Init.js b/src/main/webapp/js/diagramly/Init.js
index cb600afe..990e2ff1 100644
--- a/src/main/webapp/js/diagramly/Init.js
+++ b/src/main/webapp/js/diagramly/Init.js
@@ -309,6 +309,7 @@ if (urlParams['offline'] == '1' || urlParams['demo'] == '1' || urlParams['stealt
urlParams['db'] = '0';
urlParams['od'] = '0';
urlParams['gh'] = '0';
+ urlParams['gl'] = '0';
urlParams['tr'] = '0';
}
diff --git a/src/main/webapp/js/diagramly/Menus.js b/src/main/webapp/js/diagramly/Menus.js
index bdc8cad5..61cfdb30 100644
--- a/src/main/webapp/js/diagramly/Menus.js
+++ b/src/main/webapp/js/diagramly/Menus.js
@@ -1347,8 +1347,7 @@
req.setRequestHeaders = function(request)
{
mxXmlRequest.prototype.setRequestHeaders.apply(this, arguments);
- var token = gapi.auth.getToken().access_token;
- request.setRequestHeader('authorization', 'Bearer ' + token);
+ request.setRequestHeader('authorization', 'Bearer ' + editorUi.drive.token);
};
req.send(function(req)
diff --git a/src/main/webapp/js/diagramly/Pages.js b/src/main/webapp/js/diagramly/Pages.js
index 42f05137..4e24b7c2 100644
--- a/src/main/webapp/js/diagramly/Pages.js
+++ b/src/main/webapp/js/diagramly/Pages.js
@@ -500,7 +500,7 @@ Graph.prototype.createViewState = function(node)
return {
gridEnabled: node.getAttribute('grid') != '0',
- //gridColor: node.getAttribute('gridColor') || mxSettings.getGridColor(),
+ //gridColor: node.getAttribute('gridColor') || mxSettings.getGridColor(uiTheme == 'dark'),
gridSize: parseFloat(node.getAttribute('gridSize')) || mxGraph.prototype.gridSize,
guidesEnabled: node.getAttribute('guides') != '0',
foldingEnabled: node.getAttribute('fold') != '0',
diff --git a/src/main/webapp/js/diagramly/Settings.js b/src/main/webapp/js/diagramly/Settings.js
index d2cd2e50..e834b888 100644
--- a/src/main/webapp/js/diagramly/Settings.js
+++ b/src/main/webapp/js/diagramly/Settings.js
@@ -41,13 +41,20 @@ var mxSettings =
{
mxSettings.settings.showStartScreen = showStartScreen;
},
- getGridColor: function()
+ getGridColor: function(darkMode)
{
- return mxSettings.settings.gridColor;
+ return (darkMode) ? mxSettings.settings.darkGridColor : mxSettings.settings.gridColor;
},
- setGridColor: function(gridColor)
+ setGridColor: function(gridColor, darkMode)
{
- mxSettings.settings.gridColor = gridColor;
+ if (darkMode)
+ {
+ mxSettings.settings.darkGridColor = gridColor;
+ }
+ else
+ {
+ mxSettings.settings.gridColor = gridColor;
+ }
},
getAutosave: function()
{
@@ -192,7 +199,8 @@ var mxSettings =
pageFormat: mxGraph.prototype.pageFormat,
search: true,
showStartScreen: true,
- gridColor: mxGraphView.prototype.gridColor,
+ gridColor: mxGraphView.prototype.defaultGridColor,
+ darkGridColor: mxGraphView.prototype.defaultDarkGridColor,
autosave: true,
resizeImages: null,
openCounter: 0,
@@ -308,7 +316,12 @@ var mxSettings =
if (mxSettings.settings.gridColor == null)
{
- mxSettings.settings.gridColor = mxGraphView.prototype.gridColor;
+ mxSettings.settings.gridColor = mxGraphView.prototype.defaultGridColor;
+ }
+
+ if (mxSettings.settings.darkGridColor == null)
+ {
+ mxSettings.settings.darkGridColor = mxGraphView.prototype.defaultDarkGridColor;
}
if (mxSettings.settings.autosave == null)
diff --git a/src/main/webapp/resources/dia.txt b/src/main/webapp/resources/dia.txt
index 72824bf8..a0cf7a7a 100644
--- a/src/main/webapp/resources/dia.txt
+++ b/src/main/webapp/resources/dia.txt
@@ -5,6 +5,7 @@ accessDenied=Access Denied
action=Action
actualSize=Actual Size
add=Add
+addAccount=Add account
addedFile=Added {1}
addImages=Add Images
addImageUrl=Add Image URL
diff --git a/src/main/webapp/resources/dia_am.txt b/src/main/webapp/resources/dia_am.txt
index ccd93eae..962470a9 100644
--- a/src/main/webapp/resources/dia_am.txt
+++ b/src/main/webapp/resources/dia_am.txt
@@ -5,6 +5,7 @@ accessDenied=Access Denied
action=Action
actualSize=Actual Size
add=Add
+addAccount=Add account
addedFile=Added {1}
addImages=Add Images
addImageUrl=Add Image URL
diff --git a/src/main/webapp/resources/dia_ar.txt b/src/main/webapp/resources/dia_ar.txt
index 1257410f..189037f5 100644
--- a/src/main/webapp/resources/dia_ar.txt
+++ b/src/main/webapp/resources/dia_ar.txt
@@ -5,6 +5,7 @@ accessDenied=Access Denied
action=Action
actualSize=‫الحجم الحقيقي‬
add=‫إضافة‬
+addAccount=Add account
addedFile=Added {1}
addImages=‫إضافة صور‬
addImageUrl=‫إضافة رابط صورة‬
diff --git a/src/main/webapp/resources/dia_bg.txt b/src/main/webapp/resources/dia_bg.txt
index 5ea389e3..d8d6ee29 100644
--- a/src/main/webapp/resources/dia_bg.txt
+++ b/src/main/webapp/resources/dia_bg.txt
@@ -5,6 +5,7 @@ accessDenied=Достъпът отказан
action=Action
actualSize=Действителен размер
add=Добавяне
+addAccount=Add account
addedFile=Добавен {1}
addImages=Добавяне на изображения
addImageUrl=Добавяне на URL на изображението
diff --git a/src/main/webapp/resources/dia_bn.txt b/src/main/webapp/resources/dia_bn.txt
index 6f08a26c..91e3e050 100644
--- a/src/main/webapp/resources/dia_bn.txt
+++ b/src/main/webapp/resources/dia_bn.txt
@@ -5,6 +5,7 @@ accessDenied=Access Denied
action=Action
actualSize=Actual Size
add=Add
+addAccount=Add account
addedFile=Added {1}
addImages=Add Images
addImageUrl=Add Image URL
diff --git a/src/main/webapp/resources/dia_bs.txt b/src/main/webapp/resources/dia_bs.txt
index d7460cd9..67726a20 100644
--- a/src/main/webapp/resources/dia_bs.txt
+++ b/src/main/webapp/resources/dia_bs.txt
@@ -5,6 +5,7 @@ accessDenied=Pristup odbijen
action=Action
actualSize=Prirodna veličina
add=Dodaj
+addAccount=Add account
addedFile=Dodato {1}
addImages=Dodaj slike
addImageUrl=Dodaj URL slike
diff --git a/src/main/webapp/resources/dia_ca.txt b/src/main/webapp/resources/dia_ca.txt
index 3707396c..d7c91294 100644
--- a/src/main/webapp/resources/dia_ca.txt
+++ b/src/main/webapp/resources/dia_ca.txt
@@ -5,6 +5,7 @@ accessDenied=Accés denegat
action=Action
actualSize=Mida real
add=Afegeix
+addAccount=Add account
addedFile=Afegit {1}
addImages=Afegeix imatges
addImageUrl=Afegeix l'URL de la imatge
diff --git a/src/main/webapp/resources/dia_cs.txt b/src/main/webapp/resources/dia_cs.txt
index 29f7f699..1dc7ce95 100644
--- a/src/main/webapp/resources/dia_cs.txt
+++ b/src/main/webapp/resources/dia_cs.txt
@@ -5,6 +5,7 @@ accessDenied=Přístup odepřen
action=Action
actualSize=Skutečná velikost
add=Přidat
+addAccount=Add account
addedFile=Přidáno: {1}
addImages=Přidat obrázky
addImageUrl=Přidat URL obrázku
diff --git a/src/main/webapp/resources/dia_da.txt b/src/main/webapp/resources/dia_da.txt
index 61ba4dc5..402f524a 100644
--- a/src/main/webapp/resources/dia_da.txt
+++ b/src/main/webapp/resources/dia_da.txt
@@ -5,6 +5,7 @@ accessDenied=Adgang nægtet
action=Action
actualSize=Faktisk størrelse
add=Tilføj
+addAccount=Add account
addedFile=Tilføjet {1}
addImages=Tilføj billeder
addImageUrl=Tilføj billed-URL
diff --git a/src/main/webapp/resources/dia_de.txt b/src/main/webapp/resources/dia_de.txt
index f5691e9c..d9b0040d 100644
--- a/src/main/webapp/resources/dia_de.txt
+++ b/src/main/webapp/resources/dia_de.txt
@@ -5,6 +5,7 @@ accessDenied=Zugriff verweigert
action=Aktion
actualSize=Tatsächliche Größe
add=Einfügen
+addAccount=Konto hinzufügen
addedFile={1} eingefügt
addImages=Bilder hinzufügen
addImageUrl=Bild URL hinzufügen
diff --git a/src/main/webapp/resources/dia_el.txt b/src/main/webapp/resources/dia_el.txt
index 23d9b6e7..758b91c0 100644
--- a/src/main/webapp/resources/dia_el.txt
+++ b/src/main/webapp/resources/dia_el.txt
@@ -5,6 +5,7 @@ accessDenied=Απαγόρευση εισόδου
action=Action
actualSize=Πραγματικό Μέγεθος
add=Προσθήκη
+addAccount=Add account
addedFile=Προστέθηκε {1}
addImages=Προσθήκη εικόνων
addImageUrl=Προσθήκη εικόνας URL
diff --git a/src/main/webapp/resources/dia_eo.txt b/src/main/webapp/resources/dia_eo.txt
index 18726519..e35fff6e 100644
--- a/src/main/webapp/resources/dia_eo.txt
+++ b/src/main/webapp/resources/dia_eo.txt
@@ -5,6 +5,7 @@ accessDenied=Aliro rifuzita
action=Ago
actualSize=Vera grandeco
add=Aldoni
+addAccount=Add account
addedFile={1} aldonita
addImages=Aldoni bildojn
addImageUrl=Add Image URL
diff --git a/src/main/webapp/resources/dia_es.txt b/src/main/webapp/resources/dia_es.txt
index fc873c11..cff12e4e 100644
--- a/src/main/webapp/resources/dia_es.txt
+++ b/src/main/webapp/resources/dia_es.txt
@@ -5,6 +5,7 @@ accessDenied=Acceso denegado
action=Action
actualSize=Tamaño real
add=Agregar
+addAccount=Add account
addedFile=Agregado {1}
addImages=Agregar imágenes
addImageUrl=Añadir URL de la imagen
diff --git a/src/main/webapp/resources/dia_et.txt b/src/main/webapp/resources/dia_et.txt
index 3a447840..bc618988 100644
--- a/src/main/webapp/resources/dia_et.txt
+++ b/src/main/webapp/resources/dia_et.txt
@@ -5,6 +5,7 @@ accessDenied=Ligipääs keelatud
action=Tegevus
actualSize=Tegelik suurus
add=Lisa
+addAccount=Add account
addedFile=Lisatud {1}
addImages=Lisa pilte
addImageUrl=Lisa pildi URL
diff --git a/src/main/webapp/resources/dia_fa.txt b/src/main/webapp/resources/dia_fa.txt
index 2aa0f17d..b7d16378 100644
--- a/src/main/webapp/resources/dia_fa.txt
+++ b/src/main/webapp/resources/dia_fa.txt
@@ -5,6 +5,7 @@ accessDenied=‫دسترسی مجاز نیست.‬
action=Action
actualSize=‫سایز واقعی‬
add=‫افزودن‬
+addAccount=Add account
addedFile=‫اضافه شد {1}‬
addImages=‫افزودن تصاویر‬
addImageUrl=‫افزودن URL تصویر‬
diff --git a/src/main/webapp/resources/dia_fi.txt b/src/main/webapp/resources/dia_fi.txt
index b697b81d..b45c8657 100644
--- a/src/main/webapp/resources/dia_fi.txt
+++ b/src/main/webapp/resources/dia_fi.txt
@@ -5,6 +5,7 @@ accessDenied=Pääsy kielletty
action=Action
actualSize=Oikea koko
add=Lisää
+addAccount=Add account
addedFile=Lisätty tiedosto
addImages=Lisää kuvia
addImageUrl=Lisää kuvan URL
diff --git a/src/main/webapp/resources/dia_fil.txt b/src/main/webapp/resources/dia_fil.txt
index 00dec396..3edfb3fe 100644
--- a/src/main/webapp/resources/dia_fil.txt
+++ b/src/main/webapp/resources/dia_fil.txt
@@ -5,6 +5,7 @@ accessDenied=Tinanggihan ang pagpasok
action=Action
actualSize=Aktwal na sukat
add=Magdagdag
+addAccount=Add account
addedFile=Idinagdag
addImages=Magdagdag ng mga imahe
addImageUrl=Magdagdag ng URL ng imahe
diff --git a/src/main/webapp/resources/dia_fr.txt b/src/main/webapp/resources/dia_fr.txt
index 89a04e83..dfa2a357 100644
--- a/src/main/webapp/resources/dia_fr.txt
+++ b/src/main/webapp/resources/dia_fr.txt
@@ -5,6 +5,7 @@ accessDenied=Accès refusé
action=Action
actualSize=Taille réelle
add=Ajouter
+addAccount=Add account
addedFile=Ajouté(e) {1}
addImages=Ajouter des images
addImageUrl=Ajouter l'URL d'une image
diff --git a/src/main/webapp/resources/dia_gu.txt b/src/main/webapp/resources/dia_gu.txt
index 904a1f01..15a9a3cd 100644
--- a/src/main/webapp/resources/dia_gu.txt
+++ b/src/main/webapp/resources/dia_gu.txt
@@ -5,6 +5,7 @@ accessDenied=Access Denied
action=Action
actualSize=Actual Size
add=Add
+addAccount=Add account
addedFile=Added {1}
addImages=Add Images
addImageUrl=Add Image URL
diff --git a/src/main/webapp/resources/dia_he.txt b/src/main/webapp/resources/dia_he.txt
index fc60e4c2..3536e802 100644
--- a/src/main/webapp/resources/dia_he.txt
+++ b/src/main/webapp/resources/dia_he.txt
@@ -5,6 +5,7 @@ accessDenied=‫הגישה דחתה‬
action=Action
actualSize=‫גודל אמיתי‬
add=‫הוסף‬
+addAccount=Add account
addedFile=‫נוסף {1}‬
addImages=‫הוסף תמונות‬
addImageUrl=‫הוסף קישור לתמונה‬
diff --git a/src/main/webapp/resources/dia_hi.txt b/src/main/webapp/resources/dia_hi.txt
index 934596de..b01d8b1d 100644
--- a/src/main/webapp/resources/dia_hi.txt
+++ b/src/main/webapp/resources/dia_hi.txt
@@ -5,6 +5,7 @@ accessDenied=Access Denied
action=Action
actualSize=Actual Size
add=Add
+addAccount=Add account
addedFile=Added {1}
addImages=Add Images
addImageUrl=Add Image URL
diff --git a/src/main/webapp/resources/dia_hr.txt b/src/main/webapp/resources/dia_hr.txt
index 91d3315d..16cdffcb 100644
--- a/src/main/webapp/resources/dia_hr.txt
+++ b/src/main/webapp/resources/dia_hr.txt
@@ -5,6 +5,7 @@ accessDenied=Access Denied
action=Action
actualSize=Actual Size
add=Add
+addAccount=Add account
addedFile=Added {1}
addImages=Add Images
addImageUrl=Add Image URL
diff --git a/src/main/webapp/resources/dia_hu.txt b/src/main/webapp/resources/dia_hu.txt
index 421a028d..d062c2d4 100644
--- a/src/main/webapp/resources/dia_hu.txt
+++ b/src/main/webapp/resources/dia_hu.txt
@@ -5,6 +5,7 @@ accessDenied=Hozzáférés elutasítva
action=Action
actualSize=Aktuális méret
add=Hozzáad
+addAccount=Add account
addedFile={1} hozzáadva
addImages=Képet hozzáad
addImageUrl=Kép URL hozzáadás
diff --git a/src/main/webapp/resources/dia_i18n.txt b/src/main/webapp/resources/dia_i18n.txt
index 5888c46e..31f557e4 100644
--- a/src/main/webapp/resources/dia_i18n.txt
+++ b/src/main/webapp/resources/dia_i18n.txt
@@ -5,6 +5,7 @@ accessDenied=accessDenied
action=action
actualSize=actualSize
add=add
+addAccount=addAccount
addedFile=addedFile
addImages=addImages
addImageUrl=addImageUrl
diff --git a/src/main/webapp/resources/dia_id.txt b/src/main/webapp/resources/dia_id.txt
index 3d097afc..747d771c 100644
--- a/src/main/webapp/resources/dia_id.txt
+++ b/src/main/webapp/resources/dia_id.txt
@@ -5,6 +5,7 @@ accessDenied=Akses Ditolak
action=Action
actualSize=Ukuran Aktual
add=Tambahkan
+addAccount=Add account
addedFile=Ditambahkan
addImages=Tambahkan Gambar
addImageUrl=Tambahkan URL Gambar
diff --git a/src/main/webapp/resources/dia_it.txt b/src/main/webapp/resources/dia_it.txt
index a4b97092..f8cda286 100644
--- a/src/main/webapp/resources/dia_it.txt
+++ b/src/main/webapp/resources/dia_it.txt
@@ -5,6 +5,7 @@ accessDenied=Accesso negato
action=Action
actualSize=Dimensioni attuali
add=Aggiungi
+addAccount=Add account
addedFile=Aggiunto
addImages=Aggiungi immagini
addImageUrl=Aggiungi immagine da URL
diff --git a/src/main/webapp/resources/dia_ja.txt b/src/main/webapp/resources/dia_ja.txt
index d7bcc0d4..2110d098 100644
--- a/src/main/webapp/resources/dia_ja.txt
+++ b/src/main/webapp/resources/dia_ja.txt
@@ -5,6 +5,7 @@ accessDenied=アクセス拒否されました
action=Action
actualSize=実寸
add=追加する
+addAccount=Add account
addedFile=追加された {1}
addImages=画像を追加する
addImageUrl=画像のURLを追加する
diff --git a/src/main/webapp/resources/dia_kn.txt b/src/main/webapp/resources/dia_kn.txt
index aad20106..2fb85d9f 100644
--- a/src/main/webapp/resources/dia_kn.txt
+++ b/src/main/webapp/resources/dia_kn.txt
@@ -5,6 +5,7 @@ accessDenied=Access Denied
action=Action
actualSize=Actual Size
add=Add
+addAccount=Add account
addedFile=Added {1}
addImages=Add Images
addImageUrl=Add Image URL
diff --git a/src/main/webapp/resources/dia_ko.txt b/src/main/webapp/resources/dia_ko.txt
index 9c41c467..be19e2ef 100644
--- a/src/main/webapp/resources/dia_ko.txt
+++ b/src/main/webapp/resources/dia_ko.txt
@@ -5,6 +5,7 @@ accessDenied=접근거부
action=Action
actualSize=실제 크기
add=추가
+addAccount=Add account
addedFile=추가된 {1}
addImages=이미지 추가
addImageUrl=이미지 URL 추가
diff --git a/src/main/webapp/resources/dia_lt.txt b/src/main/webapp/resources/dia_lt.txt
index 6ea78045..88d53af4 100644
--- a/src/main/webapp/resources/dia_lt.txt
+++ b/src/main/webapp/resources/dia_lt.txt
@@ -5,6 +5,7 @@ accessDenied=Access Denied
action=Action
actualSize=Actual Size
add=Add
+addAccount=Add account
addedFile=Added {1}
addImages=Add Images
addImageUrl=Add Image URL
diff --git a/src/main/webapp/resources/dia_lv.txt b/src/main/webapp/resources/dia_lv.txt
index f6f49b8a..a6fde381 100644
--- a/src/main/webapp/resources/dia_lv.txt
+++ b/src/main/webapp/resources/dia_lv.txt
@@ -5,6 +5,7 @@ accessDenied=Access Denied
action=Action
actualSize=Actual Size
add=Add
+addAccount=Add account
addedFile=Added {1}
addImages=Add Images
addImageUrl=Add Image URL
diff --git a/src/main/webapp/resources/dia_ml.txt b/src/main/webapp/resources/dia_ml.txt
index 99f2ece5..f151e3c6 100644
--- a/src/main/webapp/resources/dia_ml.txt
+++ b/src/main/webapp/resources/dia_ml.txt
@@ -5,6 +5,7 @@ accessDenied=Access Denied
action=Action
actualSize=Actual Size
add=Add
+addAccount=Add account
addedFile=Added {1}
addImages=Add Images
addImageUrl=Add Image URL
diff --git a/src/main/webapp/resources/dia_mr.txt b/src/main/webapp/resources/dia_mr.txt
index 63715e18..d07f30cc 100644
--- a/src/main/webapp/resources/dia_mr.txt
+++ b/src/main/webapp/resources/dia_mr.txt
@@ -5,6 +5,7 @@ accessDenied=Access Denied
action=Action
actualSize=Actual Size
add=Add
+addAccount=Add account
addedFile=Added {1}
addImages=Add Images
addImageUrl=Add Image URL
diff --git a/src/main/webapp/resources/dia_ms.txt b/src/main/webapp/resources/dia_ms.txt
index a2c888a4..dd7809d5 100644
--- a/src/main/webapp/resources/dia_ms.txt
+++ b/src/main/webapp/resources/dia_ms.txt
@@ -5,6 +5,7 @@ accessDenied=Akses Ditolak
action=Action
actualSize=Saiz Sebenar
add=Tambah
+addAccount=Add account
addedFile=Tambah {1}
addImages=Tambah Imej
addImageUrl=Tambah URL Imej
diff --git a/src/main/webapp/resources/dia_nl.txt b/src/main/webapp/resources/dia_nl.txt
index 4620386b..f580d090 100644
--- a/src/main/webapp/resources/dia_nl.txt
+++ b/src/main/webapp/resources/dia_nl.txt
@@ -5,6 +5,7 @@ accessDenied=Toegang geweigerd
action=Actie
actualSize=Ware grootte
add=Toevoegen
+addAccount=Add account
addedFile={1} toegevoegd
addImages=Afbeeldingen toevoegen
addImageUrl=Afbeelding-URL toevoegen
diff --git a/src/main/webapp/resources/dia_no.txt b/src/main/webapp/resources/dia_no.txt
index e2989bb9..bfbd402d 100644
--- a/src/main/webapp/resources/dia_no.txt
+++ b/src/main/webapp/resources/dia_no.txt
@@ -5,6 +5,7 @@ accessDenied=Tilgang nektet
action=Action
actualSize=Faktisk størrelse
add=Legg til
+addAccount=Add account
addedFile=Fil lagt til
addImages=Legg til bilder
addImageUrl=Legg til bilde-URL
diff --git a/src/main/webapp/resources/dia_pl.txt b/src/main/webapp/resources/dia_pl.txt
index d0cf1a37..758f9d1b 100644
--- a/src/main/webapp/resources/dia_pl.txt
+++ b/src/main/webapp/resources/dia_pl.txt
@@ -5,6 +5,7 @@ accessDenied=Brak dostępu
action=Action
actualSize=Rozmiar rzeczywisty
add=Dodaj
+addAccount=Add account
addedFile=Dodano {1}
addImages=Dodaj obrazy
addImageUrl=Dodaj adres URL obrazu
diff --git a/src/main/webapp/resources/dia_pt-br.txt b/src/main/webapp/resources/dia_pt-br.txt
index 76da99c6..d492f36e 100644
--- a/src/main/webapp/resources/dia_pt-br.txt
+++ b/src/main/webapp/resources/dia_pt-br.txt
@@ -5,6 +5,7 @@ accessDenied=Acesso Negado
action=Action
actualSize=Tamanho real 
add=Adicionar
+addAccount=Add account
addedFile=Adicionado {1}
addImages=Adicionar imagens
addImageUrl=Adicionar URL da imagem
diff --git a/src/main/webapp/resources/dia_pt.txt b/src/main/webapp/resources/dia_pt.txt
index cad34c76..c1b217d4 100644
--- a/src/main/webapp/resources/dia_pt.txt
+++ b/src/main/webapp/resources/dia_pt.txt
@@ -5,6 +5,7 @@ accessDenied=Acesso Negado
action=Action
actualSize=Tamanho real 
add=Adicionar
+addAccount=Add account
addedFile=Adicionado {1}
addImages=Adicionar imagens
addImageUrl=Adicionar URL da imagem
diff --git a/src/main/webapp/resources/dia_ro.txt b/src/main/webapp/resources/dia_ro.txt
index 38e02fd3..5e97fc21 100644
--- a/src/main/webapp/resources/dia_ro.txt
+++ b/src/main/webapp/resources/dia_ro.txt
@@ -5,6 +5,7 @@ accessDenied=Acces interzis
action=Action
actualSize=Dimensiunea reală
add=Adaugă
+addAccount=Add account
addedFile=Adăugat {1}
addImages=Adaugă imagini
addImageUrl=Adaugă imagine URL
diff --git a/src/main/webapp/resources/dia_ru.txt b/src/main/webapp/resources/dia_ru.txt
index d072017c..821736f3 100644
--- a/src/main/webapp/resources/dia_ru.txt
+++ b/src/main/webapp/resources/dia_ru.txt
@@ -5,6 +5,7 @@ accessDenied=Доступ запрещён
action=Действие
actualSize=Фактический размер
add=Добавить
+addAccount=Add account
addedFile=Добавлено {1}
addImages=Добавить изображения
addImageUrl=Добавить ссылку на изображение
diff --git a/src/main/webapp/resources/dia_sk.txt b/src/main/webapp/resources/dia_sk.txt
index f957e670..dcb34886 100644
--- a/src/main/webapp/resources/dia_sk.txt
+++ b/src/main/webapp/resources/dia_sk.txt
@@ -5,6 +5,7 @@ accessDenied=Access Denied
action=Action
actualSize=Actual Size
add=Add
+addAccount=Add account
addedFile=Added {1}
addImages=Add Images
addImageUrl=Add Image URL
diff --git a/src/main/webapp/resources/dia_sl.txt b/src/main/webapp/resources/dia_sl.txt
index f8130682..a11ce0bb 100644
--- a/src/main/webapp/resources/dia_sl.txt
+++ b/src/main/webapp/resources/dia_sl.txt
@@ -5,6 +5,7 @@ accessDenied=Access Denied
action=Action
actualSize=Actual Size
add=Add
+addAccount=Add account
addedFile=Added {1}
addImages=Add Images
addImageUrl=Add Image URL
diff --git a/src/main/webapp/resources/dia_sr.txt b/src/main/webapp/resources/dia_sr.txt
index fcb60c6a..3f389c8f 100644
--- a/src/main/webapp/resources/dia_sr.txt
+++ b/src/main/webapp/resources/dia_sr.txt
@@ -5,6 +5,7 @@ accessDenied=Pristup je odbijen
action=Action
actualSize=Prirodna veličina
add=Dodaj
+addAccount=Add account
addedFile=Dodat {1}
addImages=Dodaj slike
addImageUrl=Dodaj URL slike
diff --git a/src/main/webapp/resources/dia_sv.txt b/src/main/webapp/resources/dia_sv.txt
index d9647c67..be002aeb 100644
--- a/src/main/webapp/resources/dia_sv.txt
+++ b/src/main/webapp/resources/dia_sv.txt
@@ -5,6 +5,7 @@ accessDenied=Åtkomst nekad
action=Action
actualSize=Verklig storlek
add=Lägg till
+addAccount=Add account
addedFile=Tillagd
addImages=Lägg till bilder
addImageUrl=Lägg till URL för bild
diff --git a/src/main/webapp/resources/dia_sw.txt b/src/main/webapp/resources/dia_sw.txt
index 19eb04d5..dbdb0458 100644
--- a/src/main/webapp/resources/dia_sw.txt
+++ b/src/main/webapp/resources/dia_sw.txt
@@ -5,6 +5,7 @@ accessDenied=Access Denied
action=Action
actualSize=Actual Size
add=Add
+addAccount=Add account
addedFile=Added {1}
addImages=Add Images
addImageUrl=Add Image URL
diff --git a/src/main/webapp/resources/dia_ta.txt b/src/main/webapp/resources/dia_ta.txt
index 762365b5..43a1e06b 100644
--- a/src/main/webapp/resources/dia_ta.txt
+++ b/src/main/webapp/resources/dia_ta.txt
@@ -5,6 +5,7 @@ accessDenied=Access Denied
action=Action
actualSize=Actual Size
add=Add
+addAccount=Add account
addedFile=Added {1}
addImages=Add Images
addImageUrl=Add Image URL
diff --git a/src/main/webapp/resources/dia_te.txt b/src/main/webapp/resources/dia_te.txt
index 9bcd028a..3d7a1cd0 100644
--- a/src/main/webapp/resources/dia_te.txt
+++ b/src/main/webapp/resources/dia_te.txt
@@ -5,6 +5,7 @@ accessDenied=Access Denied
action=Action
actualSize=Actual Size
add=Add
+addAccount=Add account
addedFile=Added {1}
addImages=Add Images
addImageUrl=Add Image URL
diff --git a/src/main/webapp/resources/dia_th.txt b/src/main/webapp/resources/dia_th.txt
index b68de385..b4fc66dd 100644
--- a/src/main/webapp/resources/dia_th.txt
+++ b/src/main/webapp/resources/dia_th.txt
@@ -5,6 +5,7 @@ accessDenied=ปฏิเสธการเข้าถึง
action=Action
actualSize=ขนาดปกติ
add=เพิ่ม
+addAccount=Add account
addedFile=เพิ่ม {1} แล้ว
addImages=เพิ่มรูปภาพ
addImageUrl=เพิ่ม URL รูปภาพ
diff --git a/src/main/webapp/resources/dia_tr.txt b/src/main/webapp/resources/dia_tr.txt
index cf772756..21530447 100644
--- a/src/main/webapp/resources/dia_tr.txt
+++ b/src/main/webapp/resources/dia_tr.txt
@@ -5,6 +5,7 @@ accessDenied=erişim engellendi
action=Action
actualSize=Gerçek boyut
add=Ekle
+addAccount=Add account
addedFile=Eklendi
addImages=Resim ekle
addImageUrl=Resim URL'si ekle
diff --git a/src/main/webapp/resources/dia_uk.txt b/src/main/webapp/resources/dia_uk.txt
index a84ff2b7..9ec7672f 100644
--- a/src/main/webapp/resources/dia_uk.txt
+++ b/src/main/webapp/resources/dia_uk.txt
@@ -5,6 +5,7 @@ accessDenied=Доступ заборонено
action=Action
actualSize=Фактичний розмір
add=Додати
+addAccount=Add account
addedFile=Додано {1}
addImages=Додати малюнки
addImageUrl=Додати URL-адресу малюнка
diff --git a/src/main/webapp/resources/dia_vi.txt b/src/main/webapp/resources/dia_vi.txt
index 8738ba74..a1074688 100644
--- a/src/main/webapp/resources/dia_vi.txt
+++ b/src/main/webapp/resources/dia_vi.txt
@@ -5,6 +5,7 @@ accessDenied=Truy cập bị từ chối
action=Action
actualSize=Kích thước thực tế
add=Thêm
+addAccount=Add account
addedFile=Đã thêm {1}
addImages=Thêm hình ảnh
addImageUrl=Thêm đường dẫn hình ảnh
diff --git a/src/main/webapp/resources/dia_zh-tw.txt b/src/main/webapp/resources/dia_zh-tw.txt
index 5c370576..87afb1d8 100644
--- a/src/main/webapp/resources/dia_zh-tw.txt
+++ b/src/main/webapp/resources/dia_zh-tw.txt
@@ -5,6 +5,7 @@ accessDenied=拒絕存取
action=Action
actualSize=實際尺寸
add=新增
+addAccount=Add account
addedFile=已新增{1}
addImages=新增圖片
addImageUrl=新增圖片URL
diff --git a/src/main/webapp/resources/dia_zh.txt b/src/main/webapp/resources/dia_zh.txt
index 8a960521..e2a004f8 100644
--- a/src/main/webapp/resources/dia_zh.txt
+++ b/src/main/webapp/resources/dia_zh.txt
@@ -5,6 +5,7 @@ accessDenied=访问被拒
action=Action
actualSize=实际尺寸
add=添加
+addAccount=Add account
addedFile=已添加 {1}
addImages=添加图片
addImageUrl=添加图片链接