Support quick undo of the last operation (Ctrl-Z).
git-svn-id: http://google-refine.googlecode.com/svn/trunk@338 7d457c2a-affb-35e4-300a-418c747d4874
This commit is contained in:
parent
6d8776953d
commit
3dc4db020f
@ -18,7 +18,9 @@ import org.json.JSONWriter;
|
||||
import com.metaweb.gridworks.Jsonizable;
|
||||
import com.metaweb.gridworks.ProjectManager;
|
||||
import com.metaweb.gridworks.browsing.Engine;
|
||||
import com.metaweb.gridworks.history.HistoryEntry;
|
||||
import com.metaweb.gridworks.model.Project;
|
||||
import com.metaweb.gridworks.process.Process;
|
||||
import com.metaweb.gridworks.util.ParsingUtilities;
|
||||
|
||||
/**
|
||||
@ -115,6 +117,26 @@ public abstract class Command {
|
||||
return null;
|
||||
}
|
||||
|
||||
static protected void performProcessAndRespond(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
Project project,
|
||||
Process process
|
||||
) throws Exception {
|
||||
HistoryEntry historyEntry = project.processManager.queueProcess(process);
|
||||
if (historyEntry != null) {
|
||||
JSONWriter writer = new JSONWriter(response.getWriter());
|
||||
Properties options = new Properties();
|
||||
|
||||
writer.object();
|
||||
writer.key("code"); writer.value("ok");
|
||||
writer.key("historyEntry"); historyEntry.write(writer, options);
|
||||
writer.endObject();
|
||||
} else {
|
||||
respond(response, "{ \"code\" : \"pending\" }");
|
||||
}
|
||||
}
|
||||
|
||||
static protected void respond(HttpServletResponse response, String content)
|
||||
throws IOException {
|
||||
|
||||
|
@ -45,10 +45,7 @@ abstract public class EngineDependentCommand extends Command {
|
||||
AbstractOperation op = createOperation(project, request, getEngineConfig(request));
|
||||
Process process = op.createProcess(project, new Properties());
|
||||
|
||||
boolean done = project.processManager.queueProcess(process);
|
||||
|
||||
respond(response, "{ \"code\" : " + (done ? "\"ok\"" : "\"pending\"") + " }");
|
||||
|
||||
performProcessAndRespond(request, response, project, process);
|
||||
} catch (Exception e) {
|
||||
respondException(response, e);
|
||||
}
|
||||
|
@ -6,8 +6,6 @@ import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.json.JSONWriter;
|
||||
|
||||
import com.metaweb.gridworks.commands.Command;
|
||||
import com.metaweb.gridworks.history.HistoryEntry;
|
||||
import com.metaweb.gridworks.model.Project;
|
||||
@ -39,16 +37,7 @@ public class AnnotateOneRowCommand extends Command {
|
||||
starred
|
||||
);
|
||||
|
||||
boolean done = project.processManager.queueProcess(process);
|
||||
if (done) {
|
||||
JSONWriter writer = new JSONWriter(response.getWriter());
|
||||
|
||||
writer.object();
|
||||
writer.key("code"); writer.value("ok");
|
||||
writer.endObject();
|
||||
} else {
|
||||
respond(response, "{ \"code\" : \"pending\" }");
|
||||
}
|
||||
performProcessAndRespond(request, response, project, process);
|
||||
} else {
|
||||
respond(response, "{ \"code\" : \"error\", \"message\" : \"invalid command parameters\" }");
|
||||
}
|
||||
|
@ -53,16 +53,19 @@ public class EditOneCellCommand extends Command {
|
||||
value
|
||||
);
|
||||
|
||||
boolean done = project.processManager.queueProcess(process);
|
||||
if (done) {
|
||||
HistoryEntry historyEntry = project.processManager.queueProcess(process);
|
||||
if (historyEntry != null) {
|
||||
/*
|
||||
* If the operation has been done, return the new cell's data
|
||||
* so the client side can update the cell's rendering right away.
|
||||
*/
|
||||
JSONWriter writer = new JSONWriter(response.getWriter());
|
||||
Properties options = new Properties();
|
||||
|
||||
writer.object();
|
||||
writer.key("code"); writer.value("ok");
|
||||
writer.key("cell"); process.newCell.write(writer, new Properties());
|
||||
writer.key("historyEntry"); historyEntry.write(writer, options);
|
||||
writer.key("cell"); process.newCell.write(writer, options);
|
||||
writer.endObject();
|
||||
} else {
|
||||
respond(response, "{ \"code\" : \"pending\" }");
|
||||
|
@ -1,6 +1,6 @@
|
||||
package com.metaweb.gridworks.commands.edit;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
@ -28,10 +28,7 @@ public class JoinMultiValueCellsCommand extends Command {
|
||||
AbstractOperation op = new MultiValuedCellJoinOperation(columnName, keyColumnName, separator);
|
||||
Process process = op.createProcess(project, new Properties());
|
||||
|
||||
boolean done = project.processManager.queueProcess(process);
|
||||
|
||||
respond(response, "{ \"code\" : " + (done ? "\"ok\"" : "\"pending\"") + " }");
|
||||
|
||||
performProcessAndRespond(request, response, project, process);
|
||||
} catch (Exception e) {
|
||||
respondException(response, e);
|
||||
}
|
||||
|
@ -26,10 +26,7 @@ public class RemoveColumnCommand extends Command {
|
||||
AbstractOperation op = new ColumnRemovalOperation(columnName);
|
||||
Process process = op.createProcess(project, new Properties());
|
||||
|
||||
boolean done = project.processManager.queueProcess(process);
|
||||
|
||||
respond(response, "{ \"code\" : " + (done ? "\"ok\"" : "\"pending\"") + " }");
|
||||
|
||||
performProcessAndRespond(request, response, project, process);
|
||||
} catch (Exception e) {
|
||||
respondException(response, e);
|
||||
}
|
||||
|
@ -32,10 +32,7 @@ public class SaveProtographCommand extends Command {
|
||||
AbstractOperation op = new SaveProtographOperation(protograph);
|
||||
Process process = op.createProcess(project, new Properties());
|
||||
|
||||
boolean done = project.processManager.queueProcess(process);
|
||||
|
||||
respond(response, "{ \"code\" : " + (done ? "\"ok\"" : "\"pending\"") + " }");
|
||||
|
||||
performProcessAndRespond(request, response, project, process);
|
||||
} catch (Exception e) {
|
||||
respondException(response, e);
|
||||
}
|
||||
|
@ -29,10 +29,7 @@ public class SplitMultiValueCellsCommand extends Command {
|
||||
AbstractOperation op = new MultiValuedCellSplitOperation(columnName, keyColumnName, separator, mode);
|
||||
Process process = op.createProcess(project, new Properties());
|
||||
|
||||
boolean done = project.processManager.queueProcess(process);
|
||||
|
||||
respond(response, "{ \"code\" : " + (done ? "\"ok\"" : "\"pending\"") + " }");
|
||||
|
||||
performProcessAndRespond(request, response, project, process);
|
||||
} catch (Exception e) {
|
||||
respondException(response, e);
|
||||
}
|
||||
|
@ -17,10 +17,23 @@ public class UndoRedoCommand extends Command {
|
||||
throws ServletException, IOException {
|
||||
|
||||
Project project = getProject(request);
|
||||
long lastDoneID = Long.parseLong(request.getParameter("lastDoneID"));
|
||||
|
||||
boolean done = project.processManager.queueProcess(
|
||||
new HistoryProcess(project, lastDoneID));
|
||||
long lastDoneID = -1;
|
||||
String lastDoneIDString = request.getParameter("lastDoneID");
|
||||
if (lastDoneIDString != null) {
|
||||
lastDoneID = Long.parseLong(lastDoneIDString);
|
||||
} else {
|
||||
String undoIDString = request.getParameter("undoID");
|
||||
if (undoIDString != null) {
|
||||
long undoID = Long.parseLong(undoIDString);
|
||||
|
||||
lastDoneID = project.history.getPrecedingEntryID(undoID);
|
||||
}
|
||||
}
|
||||
|
||||
boolean done = lastDoneID == -1 ||
|
||||
project.processManager.queueProcess(
|
||||
new HistoryProcess(project, lastDoneID));
|
||||
|
||||
respond(response, "{ \"code\" : " + (done ? "\"ok\"" : "\"pending\"") + " }");
|
||||
}
|
||||
|
@ -59,16 +59,19 @@ public class ReconJudgeOneCellCommand extends Command {
|
||||
match
|
||||
);
|
||||
|
||||
boolean done = project.processManager.queueProcess(process);
|
||||
if (done) {
|
||||
HistoryEntry historyEntry = project.processManager.queueProcess(process);
|
||||
if (historyEntry != null) {
|
||||
/*
|
||||
* If the process is done, write back the cell's data so that the
|
||||
* client side can update its UI right away.
|
||||
*/
|
||||
JSONWriter writer = new JSONWriter(response.getWriter());
|
||||
Properties options = new Properties();
|
||||
|
||||
writer.object();
|
||||
writer.key("code"); writer.value("ok");
|
||||
writer.key("cell"); process.newCell.write(writer, new Properties());
|
||||
writer.key("historyEntry"); historyEntry.write(writer, options);
|
||||
writer.key("cell"); process.newCell.write(writer, options);
|
||||
writer.endObject();
|
||||
} else {
|
||||
respond(response, "{ \"code\" : \"pending\" }");
|
||||
|
@ -114,6 +114,31 @@ public class History implements Jsonizable {
|
||||
}
|
||||
}
|
||||
|
||||
public long getPrecedingEntryID(long entryID) {
|
||||
if (entryID == 0) {
|
||||
return -1;
|
||||
} else {
|
||||
for (int i = 0; i < _pastEntries.size(); i++) {
|
||||
if (_pastEntries.get(i).id == entryID) {
|
||||
return i == 0 ? 0 : _pastEntries.get(i - 1).id;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _futureEntries.size(); i++) {
|
||||
if (_futureEntries.get(i).id == entryID) {
|
||||
if (i > 0) {
|
||||
return _futureEntries.get(i - 1).id;
|
||||
} else if (_pastEntries.size() > 0) {
|
||||
return _pastEntries.get(_pastEntries.size() - 1).id;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected HistoryEntry getEntry(long entryID) {
|
||||
for (int i = 0; i < _pastEntries.size(); i++) {
|
||||
if (_pastEntries.get(i).id == entryID) {
|
||||
|
@ -40,9 +40,11 @@ public class HistoryProcess extends Process {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void performImmediate() {
|
||||
public HistoryEntry performImmediate() {
|
||||
_project.history.undoRedo(_lastDoneID);
|
||||
_done = true;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void startPerforming(ProcessManager manager) {
|
||||
|
@ -5,6 +5,8 @@ import java.util.Properties;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONWriter;
|
||||
|
||||
import com.metaweb.gridworks.history.HistoryEntry;
|
||||
|
||||
abstract public class LongRunningProcess extends Process {
|
||||
final protected String _description;
|
||||
protected ProcessManager _manager;
|
||||
@ -51,7 +53,7 @@ abstract public class LongRunningProcess extends Process {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performImmediate() {
|
||||
public HistoryEntry performImmediate() {
|
||||
throw new RuntimeException("Not an immediate process");
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.metaweb.gridworks.process;
|
||||
|
||||
import com.metaweb.gridworks.Jsonizable;
|
||||
import com.metaweb.gridworks.history.HistoryEntry;
|
||||
|
||||
public abstract class Process implements Jsonizable {
|
||||
abstract public boolean isImmediate();
|
||||
@ -8,7 +9,7 @@ public abstract class Process implements Jsonizable {
|
||||
abstract public boolean isRunning();
|
||||
abstract public boolean isDone();
|
||||
|
||||
abstract public void performImmediate() throws Exception;
|
||||
abstract public HistoryEntry performImmediate() throws Exception;
|
||||
|
||||
abstract public void startPerforming(ProcessManager manager);
|
||||
abstract public void cancel();
|
||||
|
@ -8,6 +8,8 @@ import org.json.JSONException;
|
||||
import org.json.JSONWriter;
|
||||
|
||||
import com.metaweb.gridworks.Jsonizable;
|
||||
import com.metaweb.gridworks.history.HistoryEntry;
|
||||
import com.metaweb.gridworks.history.HistoryProcess;
|
||||
|
||||
public class ProcessManager implements Jsonizable {
|
||||
protected List<Process> _processes = new LinkedList<Process>();
|
||||
@ -29,22 +31,36 @@ public class ProcessManager implements Jsonizable {
|
||||
writer.endObject();
|
||||
}
|
||||
|
||||
public boolean queueProcess(Process process) {
|
||||
public HistoryEntry queueProcess(Process process) {
|
||||
if (process.isImmediate() && _processes.size() == 0) {
|
||||
try {
|
||||
process.performImmediate();
|
||||
return process.performImmediate();
|
||||
} catch (Exception e) {
|
||||
// TODO: Not sure what to do yet
|
||||
e.printStackTrace();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
_processes.add(process);
|
||||
|
||||
update();
|
||||
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean queueProcess(HistoryProcess process) {
|
||||
if (process.isImmediate() && _processes.size() == 0) {
|
||||
try {
|
||||
return process.performImmediate() != null;
|
||||
} catch (Exception e) {
|
||||
// TODO: Not sure what to do yet
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
_processes.add(process);
|
||||
|
||||
update();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean hasPending() {
|
||||
|
@ -31,12 +31,14 @@ abstract public class QuickHistoryEntryProcess extends Process {
|
||||
throw new RuntimeException("Not a long-running process");
|
||||
}
|
||||
|
||||
public void performImmediate() throws Exception {
|
||||
public HistoryEntry performImmediate() throws Exception {
|
||||
if (_historyEntry == null) {
|
||||
_historyEntry = createHistoryEntry();
|
||||
}
|
||||
_project.history.addEntry(_historyEntry);
|
||||
_done = true;
|
||||
|
||||
return _historyEntry;
|
||||
}
|
||||
|
||||
public void startPerforming(ProcessManager manager) {
|
||||
|
@ -215,6 +215,10 @@ Gridworks.postProcess = function(command, params, body, updateOptions, callbacks
|
||||
|
||||
if (o.code == "ok") {
|
||||
Gridworks.update(updateOptions, callbacks["onFinallyDone"]);
|
||||
|
||||
if ("historyEntry" in o) {
|
||||
ui.processWidget.showUndo(o.historyEntry);
|
||||
}
|
||||
} else if (o.code == "pending") {
|
||||
if ("onPending" in callbacks) {
|
||||
try {
|
||||
|
@ -5,6 +5,21 @@ function ProcessWidget(div) {
|
||||
|
||||
this._updateOptions = {};
|
||||
this._onDones = [];
|
||||
this._latestHistoryEntry = null;
|
||||
|
||||
var self = this;
|
||||
$(window).keypress(function(evt) {
|
||||
if (evt.ctrlKey || evt.metaKey) {
|
||||
var t = evt.target;
|
||||
if (t) {
|
||||
var tagName = t.tagName.toLowerCase();
|
||||
if (tagName == "textarea" || tagName == "input") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.undo();
|
||||
}
|
||||
});
|
||||
|
||||
this.update({});
|
||||
}
|
||||
@ -13,6 +28,8 @@ ProcessWidget.prototype.resize = function() {
|
||||
};
|
||||
|
||||
ProcessWidget.prototype.update = function(updateOptions, onDone) {
|
||||
this._latestHistoryEntry = null;
|
||||
|
||||
for (var n in updateOptions) {
|
||||
if (updateOptions.hasOwnProperty(n)) {
|
||||
this._updateOptions[n] = updateOptions[n];
|
||||
@ -30,11 +47,39 @@ ProcessWidget.prototype.update = function(updateOptions, onDone) {
|
||||
Ajax.chainGetJSON(
|
||||
"/command/get-processes?" + $.param({ project: theProject.id }), null,
|
||||
function(data) {
|
||||
self._latestHistoryEntry = null;
|
||||
self._render(data);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
ProcessWidget.prototype.showUndo = function(historyEntry) {
|
||||
var self = this;
|
||||
|
||||
this._latestHistoryEntry = historyEntry;
|
||||
|
||||
this._div.empty().show().html(
|
||||
'<div class="process-panel-inner"><div class="process-panel-undo">' +
|
||||
'<a href="javascript:{}" bind="undo">Undo</a> <span bind="description"></span>' +
|
||||
'</div></div>'
|
||||
);
|
||||
var elmts = DOM.bind(this._div);
|
||||
|
||||
elmts.description.text(historyEntry.description);
|
||||
elmts.undo.click(function() { self.undo() });
|
||||
};
|
||||
|
||||
ProcessWidget.prototype.undo = function() {
|
||||
if (this._latestHistoryEntry != null) {
|
||||
Gridworks.postProcess(
|
||||
"undo-redo",
|
||||
{ undoID: this._latestHistoryEntry.id },
|
||||
null,
|
||||
{ everythingChanged: true }
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
ProcessWidget.prototype._cancelAll = function() {
|
||||
var self = this;
|
||||
$.post(
|
||||
|
@ -7,11 +7,15 @@
|
||||
}
|
||||
|
||||
.process-panel-inner {
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid #aaa;
|
||||
width: 500px;
|
||||
margin: 0 auto;
|
||||
text-align: left;
|
||||
background: #fffee0;
|
||||
-moz-border-radius-bottomleft: 10px;
|
||||
-webkit-border-bottom-left-radius: 10px;
|
||||
-moz-border-radius-bottomright: 10px;
|
||||
-webkit-border-bottom-right-radius: 10px;
|
||||
}
|
||||
|
||||
.process-panel-head {
|
||||
@ -23,4 +27,10 @@
|
||||
max-height: 70px;
|
||||
overflow: auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.process-panel-undo {
|
||||
max-height: 50px;
|
||||
overflow: auto;
|
||||
padding: 10px;
|
||||
}
|
Loading…
Reference in New Issue
Block a user