We now have a few basic text transform features. The history widget is also starting to work. It depends on a process queue in the back-end.
git-svn-id: http://google-refine.googlecode.com/svn/trunk@7 7d457c2a-affb-35e4-300a-418c747d4874
This commit is contained in:
parent
e240cb4daf
commit
e5bba41062
@ -16,9 +16,12 @@ import org.json.JSONTokener;
|
||||
|
||||
import com.metaweb.gridlock.commands.Command;
|
||||
import com.metaweb.gridlock.commands.CreateProjectFromUploadCommand;
|
||||
import com.metaweb.gridlock.commands.DoTextTransformCommand;
|
||||
import com.metaweb.gridlock.commands.GetColumnModelCommand;
|
||||
import com.metaweb.gridlock.commands.GetHistoryCommand;
|
||||
import com.metaweb.gridlock.commands.GetProjectMetadataCommand;
|
||||
import com.metaweb.gridlock.commands.GetRowsCommand;
|
||||
import com.metaweb.gridlock.commands.UndoRedoCommand;
|
||||
|
||||
public class GridlockServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 2386057901503517403L;
|
||||
@ -30,6 +33,9 @@ public class GridlockServlet extends HttpServlet {
|
||||
_commands.put("get-project-metadata", new GetProjectMetadataCommand());
|
||||
_commands.put("get-column-model", new GetColumnModelCommand());
|
||||
_commands.put("get-rows", new GetRowsCommand());
|
||||
_commands.put("get-history", new GetHistoryCommand());
|
||||
_commands.put("undo-redo", new UndoRedoCommand());
|
||||
_commands.put("do-text-transform", new DoTextTransformCommand());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -82,6 +82,10 @@ public class ProjectManager implements Serializable {
|
||||
_projects = new HashMap<Long, Project>();
|
||||
}
|
||||
|
||||
public File getDataDir() {
|
||||
return _dir;
|
||||
}
|
||||
|
||||
public Project createProject(ProjectMetadata projectMetadata) {
|
||||
Project project = new Project();
|
||||
|
||||
|
@ -0,0 +1,111 @@
|
||||
package com.metaweb.gridlock.commands;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONTokener;
|
||||
|
||||
import com.metaweb.gridlock.expr.Evaluable;
|
||||
import com.metaweb.gridlock.expr.FieldAccessorExpr;
|
||||
import com.metaweb.gridlock.expr.Function;
|
||||
import com.metaweb.gridlock.expr.FunctionCallExpr;
|
||||
import com.metaweb.gridlock.expr.LiteralExpr;
|
||||
import com.metaweb.gridlock.expr.VariableExpr;
|
||||
import com.metaweb.gridlock.expr.functions.Replace;
|
||||
import com.metaweb.gridlock.expr.functions.ToLowercase;
|
||||
import com.metaweb.gridlock.expr.functions.ToTitlecase;
|
||||
import com.metaweb.gridlock.expr.functions.ToUppercase;
|
||||
import com.metaweb.gridlock.history.CellChange;
|
||||
import com.metaweb.gridlock.history.HistoryEntry;
|
||||
import com.metaweb.gridlock.history.MassCellChange;
|
||||
import com.metaweb.gridlock.model.Cell;
|
||||
import com.metaweb.gridlock.model.Project;
|
||||
import com.metaweb.gridlock.model.Row;
|
||||
import com.metaweb.gridlock.process.QuickHistoryEntryProcess;
|
||||
|
||||
public class DoTextTransformCommand extends Command {
|
||||
|
||||
@Override
|
||||
public void doPost(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
|
||||
Project project = getProject(request);
|
||||
int cellIndex = Integer.parseInt(request.getParameter("cell"));
|
||||
|
||||
String expression = request.getParameter("expression");
|
||||
|
||||
// HACK: quick hack before we implement a parser
|
||||
|
||||
Evaluable eval = null;
|
||||
if (expression.startsWith("replace(this.value,")) {
|
||||
// HACK: huge hack
|
||||
|
||||
String s = "[" + expression.substring(
|
||||
"replace(this.value,".length(), expression.length() - 1) + "]";
|
||||
|
||||
try {
|
||||
JSONTokener t = new JSONTokener(s);
|
||||
JSONArray a = (JSONArray) t.nextValue();
|
||||
|
||||
eval = new FunctionCallExpr(new Evaluable[] {
|
||||
new FieldAccessorExpr(new VariableExpr("this"), "value"),
|
||||
new LiteralExpr(a.get(0)),
|
||||
new LiteralExpr(a.get(1))
|
||||
}, new Replace());
|
||||
|
||||
} catch (JSONException e) {
|
||||
}
|
||||
} else {
|
||||
Function f = null;
|
||||
if (expression.equals("toUppercase(this.value)")) {
|
||||
f = new ToUppercase();
|
||||
} else if (expression.equals("toLowercase(this.value)")) {
|
||||
f = new ToLowercase();
|
||||
} else if (expression.equals("toTitlecase(this.value)")) {
|
||||
f = new ToTitlecase();
|
||||
}
|
||||
|
||||
eval = new FunctionCallExpr(new Evaluable[] {
|
||||
new FieldAccessorExpr(new VariableExpr("this"), "value")
|
||||
}, f);
|
||||
}
|
||||
|
||||
Properties bindings = new Properties();
|
||||
List<CellChange> cellChanges = new ArrayList<CellChange>(project.rows.size());
|
||||
|
||||
for (int r = 0; r < project.rows.size(); r++) {
|
||||
Row row = project.rows.get(r);
|
||||
if (cellIndex < row.cells.size()) {
|
||||
Cell cell = row.cells.get(cellIndex);
|
||||
if (cell.value == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bindings.put("this", cell);
|
||||
|
||||
Cell newCell = new Cell();
|
||||
newCell.value = eval.evaluate(bindings);
|
||||
newCell.recon = cell.recon;
|
||||
|
||||
CellChange cellChange = new CellChange(r, cellIndex, cell, newCell);
|
||||
cellChanges.add(cellChange);
|
||||
}
|
||||
}
|
||||
|
||||
MassCellChange massCellChange = new MassCellChange(cellChanges);
|
||||
HistoryEntry historyEntry = new HistoryEntry(project, "Text transform: " + expression, massCellChange);
|
||||
|
||||
boolean done = project.processManager.queueProcess(
|
||||
new QuickHistoryEntryProcess(project, historyEntry));
|
||||
|
||||
respond(response, "{ \"code\" : " + (done ? "\"ok\"" : "\"pending\"") + " }");
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package com.metaweb.gridlock.commands;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@ -16,11 +17,13 @@ public class GetColumnModelCommand extends Command {
|
||||
throws ServletException, IOException {
|
||||
|
||||
Project project = getProject(request);
|
||||
Properties options = new Properties();
|
||||
|
||||
try {
|
||||
respondJSON(response, project.columnModel.getJSON());
|
||||
respondJSON(response, project.columnModel.getJSON(options));
|
||||
} catch (JSONException e) {
|
||||
respondException(response, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
package com.metaweb.gridlock.commands;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import com.metaweb.gridlock.model.Project;
|
||||
|
||||
public class GetHistoryCommand extends Command {
|
||||
@Override
|
||||
public void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
|
||||
Project project = getProject(request);
|
||||
Properties options = new Properties();
|
||||
|
||||
try {
|
||||
respondJSON(response, project.history.getJSON(options));
|
||||
} catch (JSONException e) {
|
||||
respondException(response, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -3,6 +3,7 @@ package com.metaweb.gridlock.commands;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@ -11,7 +12,6 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import com.metaweb.gridlock.model.Column;
|
||||
import com.metaweb.gridlock.model.Project;
|
||||
import com.metaweb.gridlock.model.Row;
|
||||
|
||||
@ -23,6 +23,7 @@ public class GetRowsCommand extends Command {
|
||||
Project project = getProject(request);
|
||||
int start = Math.min(project.rows.size(), Math.max(0, getIntegerParameter(request, "start", 0)));
|
||||
int limit = Math.min(project.rows.size() - start, Math.max(0, getIntegerParameter(request, "limit", 20)));
|
||||
Properties options = new Properties();
|
||||
|
||||
try {
|
||||
JSONObject o = new JSONObject();
|
||||
@ -32,7 +33,7 @@ public class GetRowsCommand extends Command {
|
||||
for (int r = start; r < start + limit && r < project.rows.size(); r++) {
|
||||
Row row = project.rows.get(r);
|
||||
|
||||
a.add(row.getJSON());
|
||||
a.add(row.getJSON(options));
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
respondException(response, e);
|
||||
|
@ -0,0 +1,26 @@
|
||||
package com.metaweb.gridlock.commands;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.metaweb.gridlock.history.HistoryProcess;
|
||||
import com.metaweb.gridlock.model.Project;
|
||||
|
||||
public class UndoRedoCommand extends Command {
|
||||
|
||||
@Override
|
||||
public void doPost(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
|
||||
Project project = getProject(request);
|
||||
long lastDoneID = Long.parseLong(request.getParameter("lastDoneID"));
|
||||
|
||||
boolean done = project.processManager.queueProcess(
|
||||
new HistoryProcess(project, lastDoneID));
|
||||
|
||||
respond(response, "{ \"code\" : " + (done ? "\"ok\"" : "\"pending\"") + " }");
|
||||
}
|
||||
}
|
7
src/main/java/com/metaweb/gridlock/expr/Evaluable.java
Normal file
7
src/main/java/com/metaweb/gridlock/expr/Evaluable.java
Normal file
@ -0,0 +1,7 @@
|
||||
package com.metaweb.gridlock.expr;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
public interface Evaluable {
|
||||
public Object evaluate(Properties bindings);
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.metaweb.gridlock.expr;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
public class FieldAccessorExpr implements Evaluable {
|
||||
final protected Evaluable _inner;
|
||||
final protected String _fieldName;
|
||||
|
||||
public FieldAccessorExpr(Evaluable inner, String fieldName) {
|
||||
_inner = inner;
|
||||
_fieldName = fieldName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object evaluate(Properties bindings) {
|
||||
Object o = _inner.evaluate(bindings);
|
||||
if (o != null && o instanceof HasFields) {
|
||||
return ((HasFields) o).getField(_fieldName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
7
src/main/java/com/metaweb/gridlock/expr/Function.java
Normal file
7
src/main/java/com/metaweb/gridlock/expr/Function.java
Normal file
@ -0,0 +1,7 @@
|
||||
package com.metaweb.gridlock.expr;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
public interface Function {
|
||||
public Object call(Properties bindings, Object[] args);
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.metaweb.gridlock.expr;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
public class FunctionCallExpr implements Evaluable {
|
||||
final protected Evaluable[] _args;
|
||||
final protected Function _function;
|
||||
|
||||
public FunctionCallExpr(Evaluable[] args, Function f) {
|
||||
_args = args;
|
||||
_function = f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object evaluate(Properties bindings) {
|
||||
Object[] args = new Object[_args.length];
|
||||
for (int i = 0; i < _args.length; i++) {
|
||||
args[i] = _args[i].evaluate(bindings);
|
||||
}
|
||||
return _function.call(bindings, args);
|
||||
}
|
||||
|
||||
}
|
5
src/main/java/com/metaweb/gridlock/expr/HasFields.java
Normal file
5
src/main/java/com/metaweb/gridlock/expr/HasFields.java
Normal file
@ -0,0 +1,5 @@
|
||||
package com.metaweb.gridlock.expr;
|
||||
|
||||
public interface HasFields {
|
||||
public Object getField(String name);
|
||||
}
|
17
src/main/java/com/metaweb/gridlock/expr/LiteralExpr.java
Normal file
17
src/main/java/com/metaweb/gridlock/expr/LiteralExpr.java
Normal file
@ -0,0 +1,17 @@
|
||||
package com.metaweb.gridlock.expr;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
public class LiteralExpr implements Evaluable {
|
||||
final protected Object _value;
|
||||
|
||||
public LiteralExpr(Object value) {
|
||||
_value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object evaluate(Properties bindings) {
|
||||
return _value;
|
||||
}
|
||||
|
||||
}
|
17
src/main/java/com/metaweb/gridlock/expr/VariableExpr.java
Normal file
17
src/main/java/com/metaweb/gridlock/expr/VariableExpr.java
Normal file
@ -0,0 +1,17 @@
|
||||
package com.metaweb.gridlock.expr;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
public class VariableExpr implements Evaluable {
|
||||
final protected String _name;
|
||||
|
||||
public VariableExpr(String name) {
|
||||
_name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object evaluate(Properties bindings) {
|
||||
return bindings.get(_name);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.metaweb.gridlock.expr.functions;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import com.metaweb.gridlock.expr.Function;
|
||||
|
||||
public class Replace implements Function {
|
||||
|
||||
@Override
|
||||
public Object call(Properties bindings, Object[] args) {
|
||||
if (args.length == 3) {
|
||||
Object v = args[0];
|
||||
Object find = args[1];
|
||||
Object replace = args[2];
|
||||
if (v != null && find != null && replace != null
|
||||
&& find instanceof String && replace instanceof String) {
|
||||
return (v instanceof String ? (String) v : v.toString()).replace((String) find, (String) replace);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.metaweb.gridlock.expr.functions;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import com.metaweb.gridlock.expr.Function;
|
||||
|
||||
public class ToLowercase implements Function {
|
||||
|
||||
@Override
|
||||
public Object call(Properties bindings, Object[] args) {
|
||||
if (args.length == 1 && args[0] != null) {
|
||||
Object o = args[0];
|
||||
return (o instanceof String ? (String) o : o.toString()).toLowerCase();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.metaweb.gridlock.expr.functions;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import com.metaweb.gridlock.expr.Function;
|
||||
|
||||
public class ToTitlecase implements Function {
|
||||
|
||||
@Override
|
||||
public Object call(Properties bindings, Object[] args) {
|
||||
if (args.length == 1 && args[0] != null) {
|
||||
Object o = args[0];
|
||||
String s = o instanceof String ? (String) o : o.toString();
|
||||
String[] words = s.split("\\s+");
|
||||
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (int i = 0; i < words.length; i++) {
|
||||
String word = words[i];
|
||||
if (word.length() > 0) {
|
||||
if (sb.length() > 0) {
|
||||
sb.append(' ');
|
||||
}
|
||||
sb.append(word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.metaweb.gridlock.expr.functions;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import com.metaweb.gridlock.expr.Function;
|
||||
|
||||
public class ToUppercase implements Function {
|
||||
|
||||
@Override
|
||||
public Object call(Properties bindings, Object[] args) {
|
||||
if (args.length == 1 && args[0] != null) {
|
||||
Object o = args[0];
|
||||
return (o instanceof String ? (String) o : o.toString()).toUpperCase();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
21
src/main/java/com/metaweb/gridlock/history/CellChange.java
Normal file
21
src/main/java/com/metaweb/gridlock/history/CellChange.java
Normal file
@ -0,0 +1,21 @@
|
||||
package com.metaweb.gridlock.history;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.metaweb.gridlock.model.Cell;
|
||||
|
||||
public class CellChange implements Serializable {
|
||||
private static final long serialVersionUID = -2637405780084390883L;
|
||||
|
||||
final public int row;
|
||||
final public int column;
|
||||
final public Cell oldCell;
|
||||
final public Cell newCell;
|
||||
|
||||
public CellChange(int row, int column, Cell oldCell, Cell newCell) {
|
||||
this.row = row;
|
||||
this.column = column;
|
||||
this.oldCell = oldCell;
|
||||
this.newCell = newCell;
|
||||
}
|
||||
}
|
10
src/main/java/com/metaweb/gridlock/history/Change.java
Normal file
10
src/main/java/com/metaweb/gridlock/history/Change.java
Normal file
@ -0,0 +1,10 @@
|
||||
package com.metaweb.gridlock.history;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.metaweb.gridlock.model.Project;
|
||||
|
||||
public interface Change extends Serializable {
|
||||
public void apply(Project project);
|
||||
public void revert(Project project);
|
||||
}
|
100
src/main/java/com/metaweb/gridlock/history/History.java
Normal file
100
src/main/java/com/metaweb/gridlock/history/History.java
Normal file
@ -0,0 +1,100 @@
|
||||
package com.metaweb.gridlock.history;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import com.metaweb.gridlock.ProjectManager;
|
||||
import com.metaweb.gridlock.model.Project;
|
||||
|
||||
public class History implements Serializable {
|
||||
private static final long serialVersionUID = -1529783362243627391L;
|
||||
|
||||
protected long _projectID;
|
||||
protected List<HistoryEntry> _pastEntries;
|
||||
protected List<HistoryEntry> _futureEntries;
|
||||
|
||||
public History(Project project) {
|
||||
_projectID = project.id;
|
||||
_pastEntries = new ArrayList<HistoryEntry>();
|
||||
_futureEntries = new ArrayList<HistoryEntry>();
|
||||
}
|
||||
|
||||
public void addEntry(HistoryEntry entry) {
|
||||
_futureEntries.clear();
|
||||
_pastEntries.add(entry);
|
||||
entry.apply(ProjectManager.singleton.getProject(_projectID));
|
||||
}
|
||||
|
||||
public List<HistoryEntry> getLastPastEntries(int count) {
|
||||
return _pastEntries.subList(Math.max(_pastEntries.size() - count, 0), _pastEntries.size());
|
||||
}
|
||||
|
||||
public void undoRedo(long lastDoneEntryID) {
|
||||
if (lastDoneEntryID == 0) {
|
||||
undo(_pastEntries.size());
|
||||
} else {
|
||||
for (int i = 0; i < _pastEntries.size(); i++) {
|
||||
if (_pastEntries.get(i).id == lastDoneEntryID) {
|
||||
undo(_pastEntries.size() - i - 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _futureEntries.size(); i++) {
|
||||
if (_futureEntries.get(i).id == lastDoneEntryID) {
|
||||
redo(i + 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void undo(int times) {
|
||||
Project project = ProjectManager.singleton.getProject(_projectID);
|
||||
|
||||
while (times > 0 && _pastEntries.size() > 0) {
|
||||
HistoryEntry entry = _pastEntries.remove(_pastEntries.size() - 1);
|
||||
times--;
|
||||
|
||||
entry.revert(project);
|
||||
|
||||
_futureEntries.add(0, entry);
|
||||
}
|
||||
}
|
||||
|
||||
protected void redo(int times) {
|
||||
Project project = ProjectManager.singleton.getProject(_projectID);
|
||||
|
||||
while (times > 0 && _futureEntries.size() > 0) {
|
||||
HistoryEntry entry = _futureEntries.remove(0);
|
||||
times--;
|
||||
|
||||
entry.apply(project);
|
||||
|
||||
_pastEntries.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
public JSONObject getJSON(Properties options) throws JSONException {
|
||||
JSONObject o = new JSONObject();
|
||||
|
||||
List<JSONObject> a = new ArrayList<JSONObject>(_pastEntries.size());
|
||||
for (HistoryEntry entry : _pastEntries) {
|
||||
a.add(entry.getJSON(options));
|
||||
}
|
||||
o.put("past", a);
|
||||
|
||||
List<JSONObject> b = new ArrayList<JSONObject>(_futureEntries.size());
|
||||
for (HistoryEntry entry : _futureEntries) {
|
||||
b.add(entry.getJSON(options));
|
||||
}
|
||||
o.put("future", b);
|
||||
|
||||
return o;
|
||||
}
|
||||
}
|
132
src/main/java/com/metaweb/gridlock/history/HistoryEntry.java
Normal file
132
src/main/java/com/metaweb/gridlock/history/HistoryEntry.java
Normal file
@ -0,0 +1,132 @@
|
||||
package com.metaweb.gridlock.history;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import com.metaweb.gridlock.ProjectManager;
|
||||
import com.metaweb.gridlock.model.Project;
|
||||
|
||||
public class HistoryEntry implements Serializable {
|
||||
private static final long serialVersionUID = 532766467813930262L;
|
||||
|
||||
public long id;
|
||||
public long projectID;
|
||||
public String description;
|
||||
public Date time;
|
||||
|
||||
transient protected Change _change;
|
||||
|
||||
public HistoryEntry(Project project, String description, Change change) {
|
||||
this.id = Math.round(Math.random() * 1000000) + System.currentTimeMillis();
|
||||
this.projectID = project.id;
|
||||
this.description = description;
|
||||
this.time = new Date();
|
||||
|
||||
_change = change;
|
||||
|
||||
saveChange();
|
||||
}
|
||||
|
||||
public JSONObject getJSON(Properties options) throws JSONException {
|
||||
JSONObject o = new JSONObject();
|
||||
|
||||
SimpleDateFormat sdf = (SimpleDateFormat) SimpleDateFormat.getDateTimeInstance();
|
||||
|
||||
o.put("id", id);
|
||||
o.put("description", description);
|
||||
o.put("time", sdf.format(time));
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
public void apply(Project project) {
|
||||
if (_change == null) {
|
||||
loadChange();
|
||||
}
|
||||
_change.apply(project);
|
||||
}
|
||||
|
||||
public void revert(Project project) {
|
||||
if (_change == null) {
|
||||
loadChange();
|
||||
}
|
||||
_change.revert(project);
|
||||
}
|
||||
|
||||
protected void loadChange() {
|
||||
File file = getFile();
|
||||
|
||||
FileInputStream fis = null;
|
||||
ObjectInputStream in = null;
|
||||
try {
|
||||
fis = new FileInputStream(file);
|
||||
in = new ObjectInputStream(fis);
|
||||
|
||||
_change = (Change) in.readObject();
|
||||
} catch(IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch(ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (fis != null) {
|
||||
try {
|
||||
fis.close();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
if (in != null) {
|
||||
try {
|
||||
in.close();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void saveChange() {
|
||||
File file = getFile();
|
||||
|
||||
FileOutputStream fos = null;
|
||||
ObjectOutputStream out = null;
|
||||
try {
|
||||
fos = new FileOutputStream(file);
|
||||
out = new ObjectOutputStream(fos);
|
||||
|
||||
out.writeObject(_change);
|
||||
out.flush();
|
||||
} catch(IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (fos != null) {
|
||||
try {
|
||||
fos.close();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected File getFile() {
|
||||
File dir = new File(ProjectManager.singleton.getDataDir(), projectID + ".history");
|
||||
dir.mkdirs();
|
||||
|
||||
return new File(dir, id + ".entry");
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.metaweb.gridlock.history;
|
||||
|
||||
import com.metaweb.gridlock.model.Project;
|
||||
import com.metaweb.gridlock.process.Process;
|
||||
|
||||
public class HistoryProcess extends Process {
|
||||
final protected Project _project;
|
||||
final protected long _lastDoneID;
|
||||
|
||||
public HistoryProcess(Project project, long lastDoneID) {
|
||||
_project = project;
|
||||
_lastDoneID = lastDoneID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isImmediate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performImmediate() {
|
||||
_project.history.undoRedo(_lastDoneID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startPerforming() {
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.metaweb.gridlock.history;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.metaweb.gridlock.model.Project;
|
||||
import com.metaweb.gridlock.model.Row;
|
||||
|
||||
public class MassCellChange implements Change {
|
||||
private static final long serialVersionUID = -933571199802688027L;
|
||||
|
||||
protected CellChange[] _cellChanges;
|
||||
|
||||
public MassCellChange(List<CellChange> cellChanges) {
|
||||
_cellChanges = new CellChange[cellChanges.size()];
|
||||
cellChanges.toArray(_cellChanges);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
synchronized (project) {
|
||||
List<Row> rows = project.rows;
|
||||
|
||||
for (CellChange cellChange : _cellChanges) {
|
||||
rows.get(cellChange.row).cells.set(cellChange.column, cellChange.newCell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void revert(Project project) {
|
||||
synchronized (project) {
|
||||
List<Row> rows = project.rows;
|
||||
|
||||
for (CellChange cellChange : _cellChanges) {
|
||||
rows.get(cellChange.row).cells.set(cellChange.column, cellChange.oldCell);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +1,35 @@
|
||||
package com.metaweb.gridlock.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class Cell implements Serializable {
|
||||
import com.metaweb.gridlock.expr.HasFields;
|
||||
|
||||
public class Cell implements Serializable, HasFields {
|
||||
private static final long serialVersionUID = -5891067829205458102L;
|
||||
|
||||
public Object value;
|
||||
public Recon recon;
|
||||
|
||||
public JSONObject getJSON() throws JSONException {
|
||||
public JSONObject getJSON(Properties options) throws JSONException {
|
||||
JSONObject o = new JSONObject();
|
||||
|
||||
o.put("v", value);
|
||||
if (recon != null && options.containsKey("cell-recon")) {
|
||||
o.put("recon", recon.getJSON(options));
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getField(String name) {
|
||||
if ("value".equals(name)) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.metaweb.gridlock.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
@ -12,7 +13,7 @@ public class Column implements Serializable {
|
||||
public String headerLabel;
|
||||
public Class valueType;
|
||||
|
||||
public JSONObject getJSON() throws JSONException {
|
||||
public JSONObject getJSON(Properties options) throws JSONException {
|
||||
JSONObject o = new JSONObject();
|
||||
|
||||
o.put("cellIndex", cellIndex);
|
||||
|
@ -4,6 +4,7 @@ import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
@ -13,12 +14,12 @@ public class ColumnModel implements Serializable {
|
||||
|
||||
public List<Column> columns = new LinkedList<Column>();
|
||||
|
||||
public JSONObject getJSON() throws JSONException {
|
||||
public JSONObject getJSON(Properties options) throws JSONException {
|
||||
JSONObject o = new JSONObject();
|
||||
|
||||
List<JSONObject> a = new ArrayList<JSONObject>(columns.size());
|
||||
for (Column column : columns) {
|
||||
a.add(column.getJSON());
|
||||
a.add(column.getJSON(options));
|
||||
}
|
||||
o.put("columns", a);
|
||||
|
||||
|
@ -1,17 +1,38 @@
|
||||
package com.metaweb.gridlock.model;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.metaweb.gridlock.history.History;
|
||||
import com.metaweb.gridlock.process.ProcessManager;
|
||||
|
||||
public class Project implements Serializable {
|
||||
private static final long serialVersionUID = -5089046824819472570L;
|
||||
|
||||
public long id;
|
||||
public ColumnModel columnModel = new ColumnModel();
|
||||
public List<Row> rows = new ArrayList<Row>();
|
||||
|
||||
public ColumnModel columnModel = new ColumnModel();
|
||||
public List<Row> rows = new ArrayList<Row>();
|
||||
public History history;
|
||||
|
||||
transient public ProcessManager processManager;
|
||||
|
||||
public Project() {
|
||||
id = Math.round(Math.random() * 1000000) + System.currentTimeMillis();
|
||||
history = new History(this);
|
||||
|
||||
internalInitialize();
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
|
||||
in.defaultReadObject();
|
||||
internalInitialize();
|
||||
}
|
||||
|
||||
protected void internalInitialize() {
|
||||
processManager = new ProcessManager();
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,10 @@ import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class Recon implements Serializable {
|
||||
private static final long serialVersionUID = 8906257833709315762L;
|
||||
@ -17,4 +21,19 @@ public class Recon implements Serializable {
|
||||
|
||||
public Map<String, Object> features = new HashMap<String, Object>();
|
||||
public List<ReconCandidate> candidates = new LinkedList<ReconCandidate>();
|
||||
public Judgment judgment = Judgment.None;
|
||||
|
||||
public JSONObject getJSON(Properties options) throws JSONException {
|
||||
JSONObject o = new JSONObject();
|
||||
|
||||
if (judgment == Judgment.None) {
|
||||
o.put("j", "none");
|
||||
} else if (judgment == Judgment.Approve) {
|
||||
o.put("j", "approve");
|
||||
} else if (judgment == Judgment.New) {
|
||||
o.put("j", "new");
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,27 @@
|
||||
package com.metaweb.gridlock.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class ReconCandidate implements Serializable {
|
||||
private static final long serialVersionUID = -8013997214978715606L;
|
||||
|
||||
public String topicID;
|
||||
public String topicGUID;
|
||||
public String topicID;
|
||||
public String topicGUID;
|
||||
public String topicName;
|
||||
public String[] typeIDs;
|
||||
|
||||
public JSONObject getJSON(Properties options) throws JSONException {
|
||||
JSONObject o = new JSONObject();
|
||||
|
||||
o.put("id", topicID);
|
||||
o.put("guid", topicGUID);
|
||||
o.put("name", topicName);
|
||||
o.put("types", typeIDs);
|
||||
|
||||
return o;
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package com.metaweb.gridlock.model;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
@ -18,12 +19,12 @@ public class Row implements Serializable {
|
||||
cells = new ArrayList<Cell>(cellCount);
|
||||
}
|
||||
|
||||
public JSONObject getJSON() throws JSONException {
|
||||
public JSONObject getJSON(Properties options) throws JSONException {
|
||||
JSONObject o = new JSONObject();
|
||||
|
||||
List<JSONObject> a = new ArrayList<JSONObject>(cells.size());
|
||||
for (Cell cell : cells) {
|
||||
a.add(cell.getJSON());
|
||||
a.add(cell.getJSON(options));
|
||||
}
|
||||
o.put("cells", a);
|
||||
o.put("flagged", flagged);
|
||||
|
10
src/main/java/com/metaweb/gridlock/process/Process.java
Normal file
10
src/main/java/com/metaweb/gridlock/process/Process.java
Normal file
@ -0,0 +1,10 @@
|
||||
package com.metaweb.gridlock.process;
|
||||
|
||||
public abstract class Process {
|
||||
abstract public boolean isImmediate();
|
||||
|
||||
abstract public void performImmediate();
|
||||
|
||||
abstract public void startPerforming();
|
||||
abstract public void cancel();
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.metaweb.gridlock.process;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class ProcessManager {
|
||||
protected List<Process> _processes = new LinkedList<Process>();
|
||||
|
||||
public ProcessManager() {
|
||||
|
||||
}
|
||||
|
||||
public boolean queueProcess(Process process) {
|
||||
if (process.isImmediate() && _processes.size() == 0) {
|
||||
process.performImmediate();
|
||||
return true;
|
||||
} else {
|
||||
_processes.add(process);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.metaweb.gridlock.process;
|
||||
|
||||
import com.metaweb.gridlock.history.HistoryEntry;
|
||||
import com.metaweb.gridlock.model.Project;
|
||||
|
||||
public class QuickHistoryEntryProcess extends Process {
|
||||
final protected Project _project;
|
||||
final protected HistoryEntry _historyEntry;
|
||||
|
||||
public QuickHistoryEntryProcess(Project project, HistoryEntry historyEntry) {
|
||||
_project = project;
|
||||
_historyEntry = historyEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isImmediate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performImmediate() {
|
||||
_project.history.addEntry(_historyEntry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startPerforming() {
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
<html>
<head>
<title>Gridlock</title>
<link rel="stylesheet" href="/styles/common.css" />
<link rel="stylesheet" href="/styles/project.css" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
<script type="text/javascript" src="scripts/util/url.js"></script>
<script type="text/javascript" src="scripts/util/string.js"></script>
<script type="text/javascript" src="scripts/util/ajax.js"></script>
<script type="text/javascript" src="scripts/util/menu.js"></script>
<script type="text/javascript" src="scripts/project.js"></script>
<script type="text/javascript" src="scripts/project/data-table-view.js"></script>
</head>
<body>
<div id="header">
<h1 id="title">Gridlock</h1>
</div>
<div id="body">
Loading ...
</div>
</body>
</html>
|
||||
<html>
<head>
<title>Gridlock</title>
<link rel="stylesheet" href="/styles/common.css" />
<link rel="stylesheet" href="/styles/project.css" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
<script type="text/javascript" src="scripts/util/url.js"></script>
<script type="text/javascript" src="scripts/util/string.js"></script>
<script type="text/javascript" src="scripts/util/ajax.js"></script>
<script type="text/javascript" src="scripts/util/menu.js"></script>
<script type="text/javascript" src="scripts/project.js"></script>
<script type="text/javascript" src="scripts/project/data-table-view.js"></script>
<script type="text/javascript" src="scripts/project/history-widget.js"></script>
</head>
<body>
<div id="header">
<h1 id="title">Gridlock</h1>
</div>
<div id="body">
Loading ...
</div>
</body>
</html>
|
@ -54,6 +54,8 @@ function initializeUI() {
|
||||
|
||||
ui.viewPanel = $('<div></div>').appendTo(tdLeft).css("width", tdLeft.offsetWidth + "px").css("overflow-x", "auto");
|
||||
ui.facetPanel = $('<div></div>').appendTo(tdRight);
|
||||
ui.historyPanel = $('<div></div>').addClass("history-panel").appendTo(document.body);
|
||||
|
||||
ui.dataTableView = new DataTableView(ui.viewPanel);
|
||||
ui.historyWidget = new HistoryWidget(ui.historyPanel);
|
||||
}
|
||||
|
@ -218,20 +218,25 @@ DataTableView.prototype._createMenuForColumnHeader = function(column, index, elm
|
||||
submenu: [
|
||||
{
|
||||
label: "To Titlecase",
|
||||
click: function() {}
|
||||
click: function() { self._doTextTransform(column, "toTitlecase(this.value)"); }
|
||||
},
|
||||
{
|
||||
label: "To Uppercase",
|
||||
click: function() {}
|
||||
click: function() { self._doTextTransform(column, "toUppercase(this.value)"); }
|
||||
},
|
||||
{
|
||||
label: "To Lowercase",
|
||||
click: function() {}
|
||||
click: function() { self._doTextTransform(column, "toLowercase(this.value)"); }
|
||||
},
|
||||
{},
|
||||
{
|
||||
label: "Find & Replace ...",
|
||||
click: function() {}
|
||||
label: "Custom Expression ...",
|
||||
click: function() {
|
||||
var expression = window.prompt("Enter expression", 'replace(this.value,"","")');
|
||||
if (expression != null) {
|
||||
self._doTextTransform(column, expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -251,3 +256,24 @@ DataTableView.prototype._createMenuForColumnHeader = function(column, index, elm
|
||||
}
|
||||
], elmt);
|
||||
};
|
||||
|
||||
DataTableView.prototype._doTextTransform = function(column, expression) {
|
||||
var self = this;
|
||||
$.post(
|
||||
"/command/do-text-transform?" + $.param({ project: theProject.id, cell: column.cellIndex, expression: expression }),
|
||||
null,
|
||||
function(data) {
|
||||
if (data.code == "ok") {
|
||||
self.update();
|
||||
ui.historyWidget.update();
|
||||
} else {
|
||||
// update process UI
|
||||
}
|
||||
},
|
||||
"json"
|
||||
);
|
||||
};
|
||||
|
||||
DataTableView.prototype.update = function() {
|
||||
this._showRows(theProject.rowModel.start);
|
||||
};
|
||||
|
59
src/main/webapp/scripts/project/history-widget.js
Normal file
59
src/main/webapp/scripts/project/history-widget.js
Normal file
@ -0,0 +1,59 @@
|
||||
function HistoryWidget(div) {
|
||||
this._div = div;
|
||||
this.update();
|
||||
}
|
||||
|
||||
HistoryWidget.prototype.update = function(onDone) {
|
||||
var self = this;
|
||||
Ajax.chainGetJSON(
|
||||
"/command/get-history?" + $.param({ project: theProject.id }), null,
|
||||
function(data) {
|
||||
self._data = data;
|
||||
self._render();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
HistoryWidget.prototype._render = function() {
|
||||
var self = this;
|
||||
var container = this._div.empty();
|
||||
|
||||
$('<h3>History</h3>').appendTo(this._div);
|
||||
|
||||
var renderEntry = function(container, entry, lastDoneID, title) {
|
||||
var a = $('<a href="javascript:{}"></a>').appendTo(container);
|
||||
a.addClass("history-entry").html(entry.description).attr("title", title).click(function(evt) {
|
||||
return self._onClickHistoryEntry(evt, entry, lastDoneID);
|
||||
});
|
||||
};
|
||||
|
||||
var divPast = $('<div></div>').addClass("history-past").appendTo(this._div);
|
||||
for (var i = 0; i < this._data.past.length; i++) {
|
||||
var entry = this._data.past[i];
|
||||
renderEntry(divPast, entry, i == 0 ? 0 : this._data.past[i - 1].id, "Undo upto and including this change");
|
||||
}
|
||||
|
||||
var divFuture = $('<div></div>').addClass("history-future").appendTo(this._div);
|
||||
for (var i = 0; i < this._data.future.length; i++) {
|
||||
var entry = this._data.future[i];
|
||||
renderEntry(divFuture, entry, entry.id, "Redo upto and including this change");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
HistoryWidget.prototype._onClickHistoryEntry = function(evt, entry, lastDoneID) {
|
||||
var self = this;
|
||||
$.post(
|
||||
"/command/undo-redo?" + $.param({ project: theProject.id, lastDoneID: lastDoneID }),
|
||||
null,
|
||||
function(data) {
|
||||
if (data.code == "ok") {
|
||||
self.update();
|
||||
ui.dataTableView.update();
|
||||
} else {
|
||||
// update process UI
|
||||
}
|
||||
},
|
||||
"json"
|
||||
);
|
||||
};
|
@ -34,3 +34,37 @@ img.column-header-menu {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.history-panel {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
right: 20px;
|
||||
width: 200px;
|
||||
padding: 2px;
|
||||
background: white;
|
||||
border: 1px solid #eee;
|
||||
height: 200px;
|
||||
overflow: auto;
|
||||
}
|
||||
.history-panel h3 {
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
background: #fee;
|
||||
}
|
||||
.history-past {
|
||||
padding-bottom: 3px;
|
||||
border-bottom: 2px solid #aaa;
|
||||
}
|
||||
.history-future {
|
||||
padding-top: 3px;
|
||||
}
|
||||
a.history-entry {
|
||||
display: block;
|
||||
padding: 3px 5px;
|
||||
border-bottom: 1px solid #eee;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
a.history-entry:hover {
|
||||
background: #eee;
|
||||
color: #a88;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user