From 98a16ca500a70429bffa62b81b8ffcb256fac6fc Mon Sep 17 00:00:00 2001 From: David Huynh Date: Fri, 5 Feb 2010 23:05:00 +0000 Subject: [PATCH] Render contextual rows when filtered. git-svn-id: http://google-refine.googlecode.com/svn/trunk@51 7d457c2a-affb-35e4-300a-418c747d4874 --- .../browsing/ConjunctiveFilteredRows.java | 50 +++++++- .../metaweb/gridworks/browsing/Engine.java | 10 +- .../gridworks/browsing/RowVisitor.java | 2 +- .../facets/ExpressionNominalRowGrouper.java | 2 +- .../facets/ExpressionNumericRowBinner.java | 2 +- .../commands/info/ExportRowsCommand.java | 6 +- .../commands/info/GetRowsCommand.java | 25 +++- .../metaweb/gridworks/model/ColumnModel.java | 12 +- .../com/metaweb/gridworks/model/Project.java | 117 +++++++++++++++++- .../java/com/metaweb/gridworks/model/Row.java | 14 ++- .../operations/ApproveNewReconOperation.java | 2 +- .../operations/ApproveReconOperation.java | 2 +- .../operations/ColumnAdditionOperation.java | 4 +- .../operations/DiscardReconOperation.java | 2 +- .../EngineDependentMassCellOperation.java | 2 +- .../model/operations/ReconOperation.java | 4 +- .../operations/TextTransformOperation.java | 2 +- .../webapp/scripts/project/data-table-view.js | 4 + src/main/webapp/styles/project.css | 4 + 19 files changed, 228 insertions(+), 38 deletions(-) diff --git a/src/main/java/com/metaweb/gridworks/browsing/ConjunctiveFilteredRows.java b/src/main/java/com/metaweb/gridworks/browsing/ConjunctiveFilteredRows.java index 3703a9dd6..75737dc9a 100644 --- a/src/main/java/com/metaweb/gridworks/browsing/ConjunctiveFilteredRows.java +++ b/src/main/java/com/metaweb/gridworks/browsing/ConjunctiveFilteredRows.java @@ -9,6 +9,11 @@ import com.metaweb.gridworks.model.Row; public class ConjunctiveFilteredRows implements FilteredRows { final protected List _rowFilters = new LinkedList(); + final protected boolean _contextual; + + public ConjunctiveFilteredRows(boolean contextual) { + _contextual = contextual; + } public void add(RowFilter rowFilter) { _rowFilters.add(rowFilter); @@ -16,6 +21,14 @@ public class ConjunctiveFilteredRows implements FilteredRows { @Override public void accept(Project project, RowVisitor visitor) { + if (_contextual) { + contextualAccept(project, visitor); + } else { + simpleAccept(project, visitor); + } + } + + protected void simpleAccept(Project project, RowVisitor visitor) { for (int i = 0; i < project.rows.size(); i++) { Row row = project.rows.get(i); @@ -28,7 +41,42 @@ public class ConjunctiveFilteredRows implements FilteredRows { } if (ok) { - visitor.visit(project, i, row); + visitor.visit(project, i, row, false); + } + } + } + + protected void contextualAccept(Project project, RowVisitor visitor) { + int lastVisitedRow = -1; + + for (int i = 0; i < project.rows.size(); i++) { + Row row = project.rows.get(i); + + boolean ok = true; + for (RowFilter rowFilter : _rowFilters) { + if (!rowFilter.filterRow(project, i, row)) { + ok = false; + break; + } + } + + if (ok) { + if (row.contextRows != null && lastVisitedRow < i - 1) { + for (int contextRowIndex : row.contextRows) { + if (contextRowIndex > lastVisitedRow) { + visitor.visit( + project, + contextRowIndex, + project.rows.get(contextRowIndex), + true + ); + lastVisitedRow = contextRowIndex; + } + } + } + + visitor.visit(project, i, row, false); + lastVisitedRow = i; } } } diff --git a/src/main/java/com/metaweb/gridworks/browsing/Engine.java b/src/main/java/com/metaweb/gridworks/browsing/Engine.java index eeb460c37..c083c9d1b 100644 --- a/src/main/java/com/metaweb/gridworks/browsing/Engine.java +++ b/src/main/java/com/metaweb/gridworks/browsing/Engine.java @@ -25,12 +25,12 @@ public class Engine implements Jsonizable { _project = project; } - public FilteredRows getAllFilteredRows() { - return getFilteredRows(null); + public FilteredRows getAllFilteredRows(boolean contextual) { + return getFilteredRows(null, contextual); } - public FilteredRows getFilteredRows(Facet except) { - ConjunctiveFilteredRows cfr = new ConjunctiveFilteredRows(); + public FilteredRows getFilteredRows(Facet except, boolean contextual) { + ConjunctiveFilteredRows cfr = new ConjunctiveFilteredRows(contextual); for (Facet facet : _facets) { if (facet != except) { RowFilter rowFilter = facet.getRowFilter(); @@ -68,7 +68,7 @@ public class Engine implements Jsonizable { public void computeFacets() throws JSONException { for (Facet facet : _facets) { - FilteredRows filteredRows = getFilteredRows(facet); + FilteredRows filteredRows = getFilteredRows(facet, false); facet.computeChoices(_project, filteredRows); } diff --git a/src/main/java/com/metaweb/gridworks/browsing/RowVisitor.java b/src/main/java/com/metaweb/gridworks/browsing/RowVisitor.java index 3ef40bb81..146bab9b7 100644 --- a/src/main/java/com/metaweb/gridworks/browsing/RowVisitor.java +++ b/src/main/java/com/metaweb/gridworks/browsing/RowVisitor.java @@ -4,6 +4,6 @@ import com.metaweb.gridworks.model.Project; import com.metaweb.gridworks.model.Row; public interface RowVisitor { - public boolean visit(Project project, int rowIndex, Row row); + public boolean visit(Project project, int rowIndex, Row row, boolean contextual); } diff --git a/src/main/java/com/metaweb/gridworks/browsing/facets/ExpressionNominalRowGrouper.java b/src/main/java/com/metaweb/gridworks/browsing/facets/ExpressionNominalRowGrouper.java index 593b22580..b630ac8c2 100644 --- a/src/main/java/com/metaweb/gridworks/browsing/facets/ExpressionNominalRowGrouper.java +++ b/src/main/java/com/metaweb/gridworks/browsing/facets/ExpressionNominalRowGrouper.java @@ -24,7 +24,7 @@ public class ExpressionNominalRowGrouper implements RowVisitor { } @Override - public boolean visit(Project project, int rowIndex, Row row) { + public boolean visit(Project project, int rowIndex, Row row, boolean contextual) { if (_cellIndex < row.cells.size()) { Cell cell = row.cells.get(_cellIndex); if (cell != null) { diff --git a/src/main/java/com/metaweb/gridworks/browsing/facets/ExpressionNumericRowBinner.java b/src/main/java/com/metaweb/gridworks/browsing/facets/ExpressionNumericRowBinner.java index b0baaaae4..8e63fa29a 100644 --- a/src/main/java/com/metaweb/gridworks/browsing/facets/ExpressionNumericRowBinner.java +++ b/src/main/java/com/metaweb/gridworks/browsing/facets/ExpressionNumericRowBinner.java @@ -24,7 +24,7 @@ public class ExpressionNumericRowBinner implements RowVisitor { } @Override - public boolean visit(Project project, int rowIndex, Row row) { + public boolean visit(Project project, int rowIndex, Row row, boolean contextual) { if (_cellIndex < row.cells.size()) { Cell cell = row.cells.get(_cellIndex); if (cell != null) { diff --git a/src/main/java/com/metaweb/gridworks/commands/info/ExportRowsCommand.java b/src/main/java/com/metaweb/gridworks/commands/info/ExportRowsCommand.java index f18b395e2..0114b8418 100644 --- a/src/main/java/com/metaweb/gridworks/commands/info/ExportRowsCommand.java +++ b/src/main/java/com/metaweb/gridworks/commands/info/ExportRowsCommand.java @@ -51,7 +51,7 @@ public class ExportRowsCommand extends Command { } @Override - public boolean visit(Project project, int rowIndex, Row row) { + public boolean visit(Project project, int rowIndex, Row row, boolean contextual) { boolean first = true; for (Column column : project.columnModel.columns) { if (first) { @@ -74,7 +74,7 @@ public class ExportRowsCommand extends Command { } }.init(writer); - FilteredRows filteredRows = engine.getAllFilteredRows(); + FilteredRows filteredRows = engine.getAllFilteredRows(true); filteredRows.accept(project, visitor); } } catch (Exception e) { @@ -94,7 +94,7 @@ public class ExportRowsCommand extends Command { } @Override - public boolean visit(Project project, int rowIndex, Row row) { + public boolean visit(Project project, int rowIndex, Row row, boolean contextual) { boolean r = false; if (total >= start && total < start + limit) { diff --git a/src/main/java/com/metaweb/gridworks/commands/info/GetRowsCommand.java b/src/main/java/com/metaweb/gridworks/commands/info/GetRowsCommand.java index f3f1033dc..2d07c6916 100644 --- a/src/main/java/com/metaweb/gridworks/commands/info/GetRowsCommand.java +++ b/src/main/java/com/metaweb/gridworks/commands/info/GetRowsCommand.java @@ -40,17 +40,28 @@ public class GetRowsCommand extends Command { RowAccumulator acc = new RowAccumulator(start, limit) { JSONWriter writer; Properties options; + Properties extra; public RowAccumulator init(JSONWriter writer, Properties options) { this.writer = writer; this.options = options; + + this.extra = new Properties(); + this.extra.put("contextual", true); + return this; } @Override - public boolean internalVisit(int rowIndex, Row row) { + public boolean internalVisit(int rowIndex, Row row, boolean contextual) { try { + if (contextual) { + options.put("extra", extra); + } else { + options.remove("extra"); + } options.put("rowIndex", rowIndex); + row.write(writer, options); } catch (JSONException e) { } @@ -58,7 +69,7 @@ public class GetRowsCommand extends Command { } }.init(writer, options); - FilteredRows filteredRows = engine.getAllFilteredRows(); + FilteredRows filteredRows = engine.getAllFilteredRows(true); writer.key("rows"); writer.array(); filteredRows.accept(project, acc); @@ -89,17 +100,19 @@ public class GetRowsCommand extends Command { } @Override - public boolean visit(Project project, int rowIndex, Row row) { + public boolean visit(Project project, int rowIndex, Row row, boolean contextual) { boolean r = false; if (total >= start && total < start + limit) { - r = internalVisit(rowIndex, row); + r = internalVisit(rowIndex, row, contextual); + } + if (!contextual) { + total++; } - total++; return r; } - protected boolean internalVisit(int rowIndex, Row row) { + protected boolean internalVisit(int rowIndex, Row row, boolean contextual) { return false; } } diff --git a/src/main/java/com/metaweb/gridworks/model/ColumnModel.java b/src/main/java/com/metaweb/gridworks/model/ColumnModel.java index 19fabe24c..2ffb95270 100644 --- a/src/main/java/com/metaweb/gridworks/model/ColumnModel.java +++ b/src/main/java/com/metaweb/gridworks/model/ColumnModel.java @@ -23,7 +23,7 @@ public class ColumnModel implements Serializable, Jsonizable { final public List columnGroups = new LinkedList(); private int _maxCellIndex; - private int _keyCellIndex; + private int _keyColumnIndex; transient protected Map _nameToColumn; transient protected Map _cellIndexToColumn; @@ -45,13 +45,13 @@ public class ColumnModel implements Serializable, Jsonizable { return ++_maxCellIndex; } - public void setKeyCellIndex(int keyCellIndex) { + public void setKeyColumnIndex(int keyColumnIndex) { // TODO: check validity of new cell index, e.g., it's not in any group - this._keyCellIndex = keyCellIndex; + this._keyColumnIndex = keyColumnIndex; } - public int getKeyCellIndex() { - return _keyCellIndex; + public int getKeyColumnIndex() { + return _keyColumnIndex; } public void update() { @@ -79,7 +79,7 @@ public class ColumnModel implements Serializable, Jsonizable { } writer.endArray(); - writer.key("keyCellIndex"); writer.value(getKeyCellIndex()); + writer.key("keyCellIndex"); writer.value(getKeyColumnIndex()); writer.key("columnGroups"); writer.array(); for (ColumnGroup g : _rootColumnGroups) { diff --git a/src/main/java/com/metaweb/gridworks/model/Project.java b/src/main/java/com/metaweb/gridworks/model/Project.java index 4ea78274a..77df6a398 100644 --- a/src/main/java/com/metaweb/gridworks/model/Project.java +++ b/src/main/java/com/metaweb/gridworks/model/Project.java @@ -4,8 +4,11 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.List; +import com.metaweb.gridworks.expr.ExpressionUtils; import com.metaweb.gridworks.history.History; import com.metaweb.gridworks.process.ProcessManager; @@ -35,10 +38,118 @@ public class Project implements Serializable { protected void internalInitialize() { processManager = new ProcessManager(); - computeContext(); + recomputeRowContextDependencies(); } - protected void computeContext() { - // TODO + static protected class Group { + int[] cellIndices; + int keyCellIndex; + } + + public void recomputeRowContextDependencies() { + List keyedGroups = new ArrayList(); + + keyedGroups.add(createRootKeyedGroup()); + for (ColumnGroup group : columnModel.columnGroups) { + if (group.keyColumnIndex >= 0) { + Group keyedGroup = new Group(); + keyedGroup.keyCellIndex = columnModel.columns.get(group.keyColumnIndex).getCellIndex(); + keyedGroup.cellIndices = new int[group.columnSpan - 1]; + + int c = 0; + for (int i = 0; i < group.columnSpan; i++) { + int columnIndex = group.startColumnIndex + i; + if (columnIndex != group.keyColumnIndex) { + int cellIndex = columnModel.columns.get(columnIndex).getCellIndex(); + keyedGroup.cellIndices[c++] = cellIndex; + } + } + + keyedGroups.add(keyedGroup); + } + } + + Collections.sort(keyedGroups, new Comparator() { + @Override + public int compare(Group o1, Group o2) { + return o2.cellIndices.length - o1.cellIndices.length; // larger groups first + } + }); + + int[] lastNonBlankRowsByGroup = new int[keyedGroups.size()]; + for (int i = 0; i < lastNonBlankRowsByGroup.length; i++) { + lastNonBlankRowsByGroup[i] = -1; + } + + for (int r = 0; r < rows.size(); r++) { + Row row = rows.get(r); + row.contextRowSlots = null; + row.contextCellSlots = null; + + for (int g = 0; g < keyedGroups.size(); g++) { + Group group = keyedGroups.get(g); + + if (ExpressionUtils.isBlank(row.getCellValue(group.keyCellIndex))) { + int contextRowIndex = lastNonBlankRowsByGroup[g]; + if (contextRowIndex >= 0) { + for (int dependentCellIndex : group.cellIndices) { + if (!ExpressionUtils.isBlank(row.getCellValue(dependentCellIndex))) { + setRowDependency( + row, + dependentCellIndex, + contextRowIndex, + group.keyCellIndex + ); + } + } + } + } else { + lastNonBlankRowsByGroup[g] = r; + } + } + + if (row.contextRowSlots != null) { + row.contextRows = new ArrayList(); + for (int index : row.contextRowSlots) { + if (index >= 0) { + row.contextRows.add(index); + } + } + Collections.sort(row.contextRows); + } + } + } + + protected Group createRootKeyedGroup() { + int count = columnModel.getMaxCellIndex(); + + Group rootKeyedGroup = new Group(); + + rootKeyedGroup.cellIndices = new int[count - 1]; + rootKeyedGroup.keyCellIndex = columnModel.columns.get(columnModel.getKeyColumnIndex()).getCellIndex(); + for (int i = 0; i < count; i++) { + if (i < rootKeyedGroup.keyCellIndex) { + rootKeyedGroup.cellIndices[i] = i; + } else if (i > rootKeyedGroup.keyCellIndex) { + rootKeyedGroup.cellIndices[i - 1] = i; + } + } + return rootKeyedGroup; + } + + protected void setRowDependency(Row row, int cellIndex, int contextRowIndex, int contextCellIndex) { + int count = columnModel.getMaxCellIndex() + 1; + if (row.contextRowSlots == null || row.contextCellSlots == null) { + row.contextRowSlots = new int[count]; + row.contextCellSlots = new int[count]; + + for (int i = 0; i < count; i++) { + row.contextRowSlots[i] = -1; + row.contextCellSlots[i] = -1; + } + } + + row.contextRowSlots[cellIndex] = contextRowIndex; + row.contextCellSlots[cellIndex] = contextCellIndex; } } diff --git a/src/main/java/com/metaweb/gridworks/model/Row.java b/src/main/java/com/metaweb/gridworks/model/Row.java index b40cd722c..75d33f4d1 100644 --- a/src/main/java/com/metaweb/gridworks/model/Row.java +++ b/src/main/java/com/metaweb/gridworks/model/Row.java @@ -18,8 +18,9 @@ public class Row implements Serializable, HasFields, Jsonizable { public boolean starred; final public List cells; - transient public List contextRows; - transient public List contextCells; + transient public List contextRows; + transient public int[] contextRowSlots; + transient public int[] contextCellSlots; public Row(int cellCount) { cells = new ArrayList(cellCount); @@ -128,6 +129,15 @@ public class Row implements Serializable, HasFields, Jsonizable { if (options.containsKey("rowIndex")) { writer.key("i"); writer.value(options.get("rowIndex")); } + if (options.containsKey("extra")) { + Properties extra = (Properties) options.get("extra"); + if (extra != null) { + for (Object key : extra.keySet()) { + writer.key((String) key); + writer.value(extra.get(key)); + } + } + } writer.endObject(); } diff --git a/src/main/java/com/metaweb/gridworks/model/operations/ApproveNewReconOperation.java b/src/main/java/com/metaweb/gridworks/model/operations/ApproveNewReconOperation.java index f8fa211ae..abb14e088 100644 --- a/src/main/java/com/metaweb/gridworks/model/operations/ApproveNewReconOperation.java +++ b/src/main/java/com/metaweb/gridworks/model/operations/ApproveNewReconOperation.java @@ -52,7 +52,7 @@ public class ApproveNewReconOperation extends EngineDependentMassCellOperation { } @Override - public boolean visit(Project project, int rowIndex, Row row) { + public boolean visit(Project project, int rowIndex, Row row, boolean contextual) { if (cellIndex < row.cells.size()) { Cell cell = row.cells.get(cellIndex); diff --git a/src/main/java/com/metaweb/gridworks/model/operations/ApproveReconOperation.java b/src/main/java/com/metaweb/gridworks/model/operations/ApproveReconOperation.java index c4ab636c7..8af1d33eb 100644 --- a/src/main/java/com/metaweb/gridworks/model/operations/ApproveReconOperation.java +++ b/src/main/java/com/metaweb/gridworks/model/operations/ApproveReconOperation.java @@ -51,7 +51,7 @@ public class ApproveReconOperation extends EngineDependentMassCellOperation { } @Override - public boolean visit(Project project, int rowIndex, Row row) { + public boolean visit(Project project, int rowIndex, Row row, boolean contextual) { if (cellIndex < row.cells.size()) { Cell cell = row.cells.get(cellIndex); if (cell.recon != null && cell.recon.candidates.size() > 0) { diff --git a/src/main/java/com/metaweb/gridworks/model/operations/ColumnAdditionOperation.java b/src/main/java/com/metaweb/gridworks/model/operations/ColumnAdditionOperation.java index cc8e4b3cb..66dcd5899 100644 --- a/src/main/java/com/metaweb/gridworks/model/operations/ColumnAdditionOperation.java +++ b/src/main/java/com/metaweb/gridworks/model/operations/ColumnAdditionOperation.java @@ -63,7 +63,7 @@ public class ColumnAdditionOperation extends EngineDependentOperation { List cellsAtRows = new ArrayList(project.rows.size()); - FilteredRows filteredRows = engine.getAllFilteredRows(); + FilteredRows filteredRows = engine.getAllFilteredRows(false); filteredRows.accept(project, createRowVisitor(project, cellsAtRows)); String description = createDescription(column, cellsAtRows); @@ -108,7 +108,7 @@ public class ColumnAdditionOperation extends EngineDependentOperation { } @Override - public boolean visit(Project project, int rowIndex, Row row) { + public boolean visit(Project project, int rowIndex, Row row, boolean contextual) { if (cellIndex < row.cells.size()) { Cell cell = row.cells.get(cellIndex); if (cell.value != null) { diff --git a/src/main/java/com/metaweb/gridworks/model/operations/DiscardReconOperation.java b/src/main/java/com/metaweb/gridworks/model/operations/DiscardReconOperation.java index 4c2999d23..362dfc570 100644 --- a/src/main/java/com/metaweb/gridworks/model/operations/DiscardReconOperation.java +++ b/src/main/java/com/metaweb/gridworks/model/operations/DiscardReconOperation.java @@ -50,7 +50,7 @@ public class DiscardReconOperation extends EngineDependentMassCellOperation { } @Override - public boolean visit(Project project, int rowIndex, Row row) { + public boolean visit(Project project, int rowIndex, Row row, boolean contextual) { if (cellIndex < row.cells.size()) { Cell cell = row.cells.get(cellIndex); diff --git a/src/main/java/com/metaweb/gridworks/model/operations/EngineDependentMassCellOperation.java b/src/main/java/com/metaweb/gridworks/model/operations/EngineDependentMassCellOperation.java index 02abbb707..a1f7f8ea1 100644 --- a/src/main/java/com/metaweb/gridworks/model/operations/EngineDependentMassCellOperation.java +++ b/src/main/java/com/metaweb/gridworks/model/operations/EngineDependentMassCellOperation.java @@ -38,7 +38,7 @@ abstract public class EngineDependentMassCellOperation extends EngineDependentOp List cellChanges = new ArrayList(project.rows.size()); - FilteredRows filteredRows = engine.getAllFilteredRows(); + FilteredRows filteredRows = engine.getAllFilteredRows(false); filteredRows.accept(project, createRowVisitor(project, cellChanges)); String description = createDescription(column, cellChanges); diff --git a/src/main/java/com/metaweb/gridworks/model/operations/ReconOperation.java b/src/main/java/com/metaweb/gridworks/model/operations/ReconOperation.java index 841d9cd06..d52ebd36b 100644 --- a/src/main/java/com/metaweb/gridworks/model/operations/ReconOperation.java +++ b/src/main/java/com/metaweb/gridworks/model/operations/ReconOperation.java @@ -60,7 +60,7 @@ public class ReconOperation extends EngineDependentOperation { List entries = new ArrayList(project.rows.size()); - FilteredRows filteredRows = engine.getAllFilteredRows(); + FilteredRows filteredRows = engine.getAllFilteredRows(false); filteredRows.accept(project, new RowVisitor() { int cellIndex; List entries; @@ -72,7 +72,7 @@ public class ReconOperation extends EngineDependentOperation { } @Override - public boolean visit(Project project, int rowIndex, Row row) { + public boolean visit(Project project, int rowIndex, Row row, boolean contextual) { if (cellIndex < row.cells.size()) { Cell cell = row.cells.get(cellIndex); if (cell.value != null) { diff --git a/src/main/java/com/metaweb/gridworks/model/operations/TextTransformOperation.java b/src/main/java/com/metaweb/gridworks/model/operations/TextTransformOperation.java index 2bb5ad549..21fed9cbf 100644 --- a/src/main/java/com/metaweb/gridworks/model/operations/TextTransformOperation.java +++ b/src/main/java/com/metaweb/gridworks/model/operations/TextTransformOperation.java @@ -62,7 +62,7 @@ public class TextTransformOperation extends EngineDependentMassCellOperation { } @Override - public boolean visit(Project project, int rowIndex, Row row) { + public boolean visit(Project project, int rowIndex, Row row, boolean contextual) { if (cellIndex < row.cells.size()) { Cell cell = row.cells.get(cellIndex); if (cell.value != null) { diff --git a/src/main/webapp/scripts/project/data-table-view.js b/src/main/webapp/scripts/project/data-table-view.js index 8c4372943..d48050100 100644 --- a/src/main/webapp/scripts/project/data-table-view.js +++ b/src/main/webapp/scripts/project/data-table-view.js @@ -250,6 +250,10 @@ DataTableView.prototype.render = function() { var tr = table.insertRow(table.rows.length); tr.className = (r % 2) == 1 ? "odd" : "even"; + if ("contextual" in row && row.contextual) { + $(tr).addClass("contextual"); + } + var td = tr.insertCell(tr.cells.length); $(td).html((row.i + 1) + "."); diff --git a/src/main/webapp/styles/project.css b/src/main/webapp/styles/project.css index 604071d77..6f10a953f 100644 --- a/src/main/webapp/styles/project.css +++ b/src/main/webapp/styles/project.css @@ -14,6 +14,10 @@ table.data-table tr.even { background: #eee; } +table.data-table tr.contextual { + opacity: 0.2; +} + table.data-table td.column-header { background: #ddd; cursor: pointer;