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:
David Huynh 2010-01-27 01:48:42 +00:00
parent e240cb4daf
commit e5bba41062
39 changed files with 983 additions and 20 deletions

View File

@ -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

View File

@ -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();

View File

@ -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\"") + " }");
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);

View File

@ -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\"") + " }");
}
}

View File

@ -0,0 +1,7 @@
package com.metaweb.gridlock.expr;
import java.util.Properties;
public interface Evaluable {
public Object evaluate(Properties bindings);
}

View File

@ -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;
}
}

View File

@ -0,0 +1,7 @@
package com.metaweb.gridlock.expr;
import java.util.Properties;
public interface Function {
public Object call(Properties bindings, Object[] args);
}

View File

@ -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);
}
}

View File

@ -0,0 +1,5 @@
package com.metaweb.gridlock.expr;
public interface HasFields {
public Object getField(String name);
}

View 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;
}
}

View 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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View 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;
}
}

View 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);
}

View 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;
}
}

View 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");
}
}

View File

@ -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() {
}
}

View File

@ -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);
}
}
}
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);

View 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();
}

View File

@ -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;
}
}
}

View File

@ -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() {
}
}

View File

@ -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>

View File

@ -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);
}

View File

@ -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 &amp; 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);
};

View 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"
);
};

View File

@ -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;
}