Google Drive Realtime API

This commit is contained in:
benweet 2013-07-19 00:30:28 +01:00
parent 194b018cd2
commit d1cb3db557
8 changed files with 182 additions and 43 deletions

View File

@ -1,5 +1,5 @@
CACHE MANIFEST CACHE MANIFEST
# Wed 17 Jul 2013 01:37:24 IST # Wed 17 Jul 2013 01:37:25 IST
CACHE: CACHE:
index.html index.html

View File

@ -762,6 +762,15 @@ input[type="file"] {
z-index: -1; z-index: -1;
} }
.lock-ui {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1050;
}
.gecko #md-section-helper { .gecko #md-section-helper {
/* Firefox doesn't show the scrollbar if height is less than 40px */ /* Firefox doesn't show the scrollbar if height is less than 40px */
height: 40px; height: 40px;

View File

@ -226,7 +226,8 @@
<h3>Delete</h3> <h3>Delete</h3>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Are you sure you want to delete "<span class="file-title"></span>"? <p>
Are you sure you want to delete "<span class="file-title"></span>"?
</p> </p>
<blockquote class="muted"> <blockquote class="muted">
<b>NOTE:</b> This will not delete the file on synchronized <b>NOTE:</b> This will not delete the file on synchronized
@ -247,8 +248,10 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Please select your Markdown files here:</p> <p>Please select your Markdown files here:</p>
<p><input type="file" id="input-file-import-harddrive-markdown" <p>
multiple /></p> <input type="file" id="input-file-import-harddrive-markdown"
multiple />
</p>
<p>Or drag and drop your Markdown files here:</p> <p>Or drag and drop your Markdown files here:</p>
<p id="dropzone-import-harddrive-markdown" class="drop-zone">Drop <p id="dropzone-import-harddrive-markdown" class="drop-zone">Drop
files here</p> files here</p>
@ -266,13 +269,14 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Please select your HTML files here:</p> <p>Please select your HTML files here:</p>
<p><input type="file" id="input-file-import-harddrive-html" <p>
multiple /></p> <input type="file" id="input-file-import-harddrive-html" multiple />
</p>
<p>Or drag and drop your HTML files here:</p> <p>Or drag and drop your HTML files here:</p>
<p id="dropzone-import-harddrive-html" class="drop-zone">Drop <p id="dropzone-import-harddrive-html" class="drop-zone">Drop
files here</p> files here</p>
<p>Or insert your HTML code here:</p> <textarea <p>Or insert your HTML code here:</p>
id="input-convert-html"></textarea> <textarea id="input-convert-html"></textarea>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<a href="#" class="btn" data-dismiss="modal">Close</a> <a href="#" <a href="#" class="btn" data-dismiss="modal">Close</a> <a href="#"
@ -289,7 +293,8 @@
<div class="modal-body"> <div class="modal-body">
<blockquote class="muted">This will upload the current <blockquote class="muted">This will upload the current
document to your Google Drive account and keep it synchronized.</blockquote> document to your Google Drive account and keep it synchronized.</blockquote>
<p>Here, you can specify a <b>folder ID</b> (optional): <p>
Here, you can specify a <b>folder ID</b> (optional):
</p> </p>
<div class="input-prepend"> <div class="input-prepend">
<span class="add-on"><i class="icon-gdrive"></i></span><input <span class="add-on"><i class="icon-gdrive"></i></span><input
@ -297,6 +302,11 @@
placeholder="FolderID"></input> placeholder="FolderID"></input>
</div> </div>
<br /> <br /> <br /> <br />
<p>
<label class="checkbox"> <input id="input-sync-export-gdrive-realtime" type="checkbox">
Create a real time collaborative document
</label>
</p>
<blockquote class="muted"> <blockquote class="muted">
<b>NOTE:</b> <b>NOTE:</b>
<ul> <ul>
@ -304,6 +314,8 @@
your root folder.</li> your root folder.</li>
<li>You can move or rename the file afterwards within Google <li>You can move or rename the file afterwards within Google
Drive.</li> Drive.</li>
<li>Real time collaborative document can not have multiple
synchronized locations.</li>
</ul> </ul>
</blockquote> </blockquote>
</div> </div>
@ -323,8 +335,8 @@
<div class="modal-body"> <div class="modal-body">
<blockquote class="muted">This will upload the current <blockquote class="muted">This will upload the current
document to your Dropbox account and keep it synchronized.</blockquote> document to your Dropbox account and keep it synchronized.</blockquote>
<p>Please specify a <b>file path</b> for "<span <p>
class="file-title"></span>": Please specify a <b>file path</b> for "<span class="file-title"></span>":
</p> </p>
<div class="input-prepend"> <div class="input-prepend">
<span class="add-on"><i class="icon-dropbox"></i></span><input <span class="add-on"><i class="icon-dropbox"></i></span><input
@ -356,8 +368,9 @@
<h3>Synchronization</h3> <h3>Synchronization</h3>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p class="msg-sync-list hide">"<span class="file-title"></span>" <p class="msg-sync-list hide">
is synchronized with the following location(s): "<span class="file-title"></span>" is synchronized with the
following location(s):
</p> </p>
<div id="manage-sync-list"></div> <div id="manage-sync-list"></div>
<blockquote class="msg-sync-list hide muted"> <blockquote class="msg-sync-list hide muted">
@ -600,8 +613,9 @@
<h3>Publication</h3> <h3>Publication</h3>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p class="msg-publish-list hide">"<span class="file-title"></span>" <p class="msg-publish-list hide">
is published on the following location(s): "<span class="file-title"></span>" is published on the following
location(s):
</p> </p>
<div id="manage-publish-list"></div> <div id="manage-publish-list"></div>
<blockquote class="muted"> <blockquote class="muted">
@ -794,6 +808,7 @@
</div> </div>
<textarea id="md-section-helper"></textarea> <textarea id="md-section-helper"></textarea>
<div class="lock-ui hide"></div>
<div id="dropboxjs" data-app-key="x0k2l8puemfvg0o"></div> <div id="dropboxjs" data-app-key="x0k2l8puemfvg0o"></div>
</body> </body>

View File

@ -332,6 +332,28 @@ define([
$("#wmd-redo-button").append($("<i>").addClass("icon-share-alt")); $("#wmd-redo-button").append($("<i>").addClass("icon-share-alt"));
}; };
// Used to lock the editor from the user interaction during asynchronous tasks
var uiLocked = false;
core.lockUI = function(param) {
uiLocked = param;
$("#wmd-input").prop("disabled", uiLocked);
$(".btn").each(function() {
var classes = $(this).attr("class");
if(uiLocked) {
$(this).attr("class", classes + " disabled");
} else {
$(this).attr("class", classes.replace(" disabled", ""));
}
});
if(uiLocked) {
$(".lock-ui").removeClass("hide");
}
else {
$(".lock-ui").addClass("hide");
}
};
// onReady event callbacks // onReady event callbacks
var readyCallbacks = []; var readyCallbacks = [];
core.onReady = function(callback) { core.onReady = function(callback) {
@ -401,7 +423,7 @@ define([
// Configure Mousetrap // Configure Mousetrap
mousetrap.stopCallback = function(e, element, combo) { mousetrap.stopCallback = function(e, element, combo) {
return shownModalId || $(element).is("input, select, textarea:not(#wmd-input)"); return uiLocked || shownModalId || $(element).is("input, select, textarea:not(#wmd-input)");
}; };
// Click events on "insert link" and "insert image" dialog buttons // Click events on "insert link" and "insert image" dialog buttons

View File

@ -81,9 +81,11 @@ define([
updateButtonState(); updateButtonState();
}; };
// Check that a file has synchronized locations // Check that a file has synchronized locations and no real time synchronized location
var checkSynchronization = function(fileDesc) { var checkSynchronization = function(fileDesc) {
if(_.size(fileDesc.syncLocations) !== 0) { if(_.size(fileDesc.syncLocations) !== 0 && !_.some(fileDesc.syncLocations, function(syncAttributes) {
return syncAttributes.isRealtime;
})) {
uploadPending = true; uploadPending = true;
updateButtonState(); updateButtonState();
} }

View File

@ -181,6 +181,47 @@ define([
task.enqueue(); task.enqueue();
}; };
googleHelper.createRealtimeFile = function(parentId, title, callback) {
var result = undefined;
var task = new AsyncTask();
connect(task);
authenticate(task);
task.onRun(function() {
var metadata = {
title: title,
mimeType : 'application/vnd.google-apps.drive-sdk',
};
if(parentId !== undefined) {
// Specify the directory
metadata.parents = [
{
kind: 'drive#fileLink',
id: parentId
}
];
}
var request = gapi.client.drive.files.insert({
'resource' : metadata
});
request.execute(function(response) {
if(response && response.id) {
// Upload success
result = response;
task.chain();
return;
}
handleError(response.error, task);
});
});
task.onSuccess(function() {
callback(undefined, result);
});
task.onError(function(error) {
callback(error);
});
task.enqueue();
};
googleHelper.uploadImg = function(name, content, albumId, callback) { googleHelper.uploadImg = function(name, content, albumId, callback) {
var result = undefined; var result = undefined;
var task = new AsyncTask(); var task = new AsyncTask();

View File

@ -56,7 +56,6 @@ define([
}); });
}); });
} }
;
gdriveProvider.importFiles = function() { gdriveProvider.importFiles = function() {
googleHelper.picker(function(error, docs) { googleHelper.picker(function(error, docs) {
@ -89,6 +88,18 @@ define([
}); });
}; };
gdriveProvider.exportRealtimeFile = function(event, title, content, callback) {
var parentId = utils.getInputTextValue("#input-sync-export-gdrive-parentid");
googleHelper.createRealtimeFile(parentId, title, function(error, result) {
if(error) {
callback(error);
return;
}
var syncAttributes = createSyncAttributes(result.id, result.etag, content, title);
callback(undefined, syncAttributes);
});
};
gdriveProvider.exportManual = function(event, title, content, callback) { gdriveProvider.exportManual = function(event, title, content, callback) {
var id = utils.getInputTextValue("#input-sync-manual-gdrive-id", event); var id = utils.getInputTextValue("#input-sync-manual-gdrive-id", event);
if(!id) { if(!id) {
@ -243,30 +254,37 @@ define([
return publishAttributes; return publishAttributes;
}; };
// Keep a link to the pagedown editor
var editor = undefined; var editor = undefined;
extensionMgr.addHookCallback("onEditorConfigure", function(editorParam) { extensionMgr.addHookCallback("onEditorConfigure", function(editorParam) {
editor = editorParam; editor = editorParam;
}); });
// Start realtime synchronization
var binding = undefined; var binding = undefined;
gdriveProvider.onSyncStart = function(fileDesc, syncAttributes) { gdriveProvider.startSync = function(content, syncAttributes, callback) {
console.log("onSyncStart"); logger.log("Starting Google Drive realtime synchronization");
console.log(syncAttributes); googleHelper.loadRealtime(syncAttributes.id, content, function(err, doc) {
googleHelper.loadRealtime(syncAttributes.id, fileDesc.content, function(err, doc) {
if(err || !doc) { if(err || !doc) {
callback(err);
return; return;
} }
var string = doc.getModel().getRoot().get('content'); var string = doc.getModel().getRoot().get('content');
binding = gapi.drive.realtime.databinding.bindString(string, $("#wmd-input")[0]); binding = gapi.drive.realtime.databinding.bindString(string, $("#wmd-input")[0]);
// Listen to
var debouncedRefreshPreview = _.debounce(editor.refreshPreview, 100); var debouncedRefreshPreview = _.debounce(editor.refreshPreview, 100);
string.addEventListener(gapi.drive.realtime.EventType.TEXT_INSERTED, debouncedRefreshPreview); string.addEventListener(gapi.drive.realtime.EventType.TEXT_INSERTED, debouncedRefreshPreview);
string.addEventListener(gapi.drive.realtime.EventType.TEXT_DELETED, debouncedRefreshPreview); string.addEventListener(gapi.drive.realtime.EventType.TEXT_DELETED, debouncedRefreshPreview);
callback();
}); });
}; };
gdriveProvider.onSyncStop = function(syncAttributes) { // Stop realtime synchronization
console.log("onSyncStop"); gdriveProvider.stopSync = function(syncAttributes) {
logger.log("Stopping Google Drive realtime synchronization");
if(binding !== undefined) { if(binding !== undefined) {
binding.unbind(); binding.unbind();
binding = undefined;
} }
}; };

View File

@ -188,21 +188,29 @@ define([
return true; return true;
}; };
// Used for realtime synchronization
function onFileOpen(fileDesc) { function onFileOpen(fileDesc) {
_.each(fileDesc.syncLocations, function(syncAttributes) { _.each(fileDesc.syncLocations, function(syncAttributes) {
if(_.isFunction(syncAttributes.provider.onSyncStart)) { if(syncAttributes.isRealtime) {
syncAttributes.provider.onSyncStart(fileDesc, syncAttributes); core.lockUI(true);
syncAttributes.provider.startSync(fileDesc.content, syncAttributes, function() {
core.lockUI(false);
});
} }
}); });
} }
function onFileClosed(fileDesc) { function onFileClosed(fileDesc) {
_.each(fileDesc.syncLocations, function(syncAttributes) { _.each(fileDesc.syncLocations, function(syncAttributes) {
if(_.isFunction(syncAttributes.provider.onSyncStop)) { if(syncAttributes.isRealtime) {
syncAttributes.provider.onSyncStop(syncAttributes); syncAttributes.provider.stopSync(syncAttributes);
} }
}); });
} }
// Enable realtime synchronization
if(viewerMode === false) {
extensionMgr.addHookCallback("onFileOpen", onFileOpen);
extensionMgr.addHookCallback("onFileClosed", onFileClosed);
}
// Initialize the export dialog // Initialize the export dialog
@ -235,9 +243,30 @@ define([
initExportDialog(provider); initExportDialog(provider);
}); });
$(".action-sync-export-" + provider.providerId).click(function(event) { $(".action-sync-export-" + provider.providerId).click(function(event) {
var isRealtime = utils.getInputChecked("#input-sync-export-" + provider.providerId + "-realtime");
// Perform the provider's export
var fileDesc = fileMgr.currentFile; var fileDesc = fileMgr.currentFile;
if(isRealtime) {
if(_.size(fileDesc.syncLocations) > 0) {
extensionMgr.onError("Realtime collaboration document can't be synchronized with multiple locations");
return;
}
// Perform the provider's real time export
provider.exportRealtimeFile(event, fileDesc.title, fileDesc.content, function(error, syncAttributes) {
if(error) {
return;
}
syncAttributes.isRealtime = true;
fileDesc.addSyncLocation(syncAttributes);
extensionMgr.onSyncExportSuccess(fileDesc, syncAttributes);
});
}
else {
if(_.size(fileDesc.syncLocations) > 0 && _.first(_.values(obj)).isRealtime) {
extensionMgr.onError("Realtime collaboration document can't be synchronized with multiple locations");
return;
}
// Perform the provider's standard export
provider.exportFile(event, fileDesc.title, fileDesc.content, function(error, syncAttributes) { provider.exportFile(event, fileDesc.title, fileDesc.content, function(error, syncAttributes) {
if(error) { if(error) {
return; return;
@ -245,6 +274,7 @@ define([
fileDesc.addSyncLocation(syncAttributes); fileDesc.addSyncLocation(syncAttributes);
extensionMgr.onSyncExportSuccess(fileDesc, syncAttributes); extensionMgr.onSyncExportSuccess(fileDesc, syncAttributes);
}); });
}
// Store input values as preferences for next time we open the // Store input values as preferences for next time we open the
// export dialog // export dialog
@ -257,6 +287,10 @@ define([
// Provider's manual export button // Provider's manual export button
$(".action-sync-manual-" + provider.providerId).click(function(event) { $(".action-sync-manual-" + provider.providerId).click(function(event) {
var fileDesc = fileMgr.currentFile; var fileDesc = fileMgr.currentFile;
if(_.size(fileDesc.syncLocations) > 0 && _.first(_.values(obj)).isRealtime) {
extensionMgr.onError("Realtime collaboration document can't be synchronized with multiple locations");
return;
}
provider.exportManual(event, fileDesc.title, fileDesc.content, function(error, syncAttributes) { provider.exportManual(event, fileDesc.title, fileDesc.content, function(error, syncAttributes) {
if(error) { if(error) {
return; return;
@ -268,8 +302,6 @@ define([
}); });
}); });
extensionMgr.addHookCallback("onFileOpen", onFileOpen);
extensionMgr.addHookCallback("onFileClosed", onFileClosed);
extensionMgr.onSynchronizerCreated(synchronizer); extensionMgr.onSynchronizerCreated(synchronizer);
return synchronizer; return synchronizer;
}); });