blob: e72907b21e249d7a6f7cadb0a5bc9d83784bf7a7 [file] [log] [blame]
/*
* Copyright (C) 2013-2015 Apple Inc. All rights reserved.
* Copyright (C) 2013 Samsung Electronics. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
WI.DOMStorageContentView = class DOMStorageContentView extends WI.ContentView
{
constructor(representedObject)
{
super(representedObject);
this.element.classList.add("dom-storage");
representedObject.addEventListener(WI.DOMStorageObject.Event.ItemsCleared, this.itemsCleared, this);
representedObject.addEventListener(WI.DOMStorageObject.Event.ItemAdded, this.itemAdded, this);
representedObject.addEventListener(WI.DOMStorageObject.Event.ItemRemoved, this.itemRemoved, this);
representedObject.addEventListener(WI.DOMStorageObject.Event.ItemUpdated, this.itemUpdated, this);
let columns = {};
columns.key = {title: WI.UIString("Key"), sortable: true};
columns.value = {title: WI.UIString("Value"), sortable: true};
this._dataGrid = new WI.DataGrid(columns, {
editCallback: this._editingCallback.bind(this),
copyCallback: this._dataGridCopy.bind(this),
deleteCallback: this._deleteCallback.bind(this),
});
this._dataGrid.sortOrder = WI.DataGrid.SortOrder.Ascending;
this._dataGrid.sortColumnIdentifier = "key";
this._dataGrid.allowsMultipleSelection = true;
this._dataGrid.createSettings("dom-storage-content-view");
this._dataGrid.addEventListener(WI.DataGrid.Event.SortChanged, this._sortDataGrid, this);
this.addSubview(this._dataGrid);
this._filterBarNavigationItem = new WI.FilterBarNavigationItem;
this._filterBarNavigationItem.filterBar.addEventListener(WI.FilterBar.Event.FilterDidChange, this._handleFilterBarFilterDidChange, this);
let clearButtonLabel = representedObject.isLocalStorage() ? WI.UIString("Clear Local Storage") : WI.UIString("Clear Session Storage");
this._clearButtonNavigationItem = new WI.ButtonNavigationItem("dom-storage-clear", clearButtonLabel, "Images/NavigationItemTrash.svg", 15, 15);
this._clearButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
this._clearButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleClearNavigationItemClicked, this);
this._populate();
}
// Public
get navigationItems()
{
return [
this._filterBarNavigationItem,
new WI.DividerNavigationItem,
this._clearButtonNavigationItem,
];
}
saveToCookie(cookie)
{
cookie.type = WI.ContentViewCookieType.DOMStorage;
cookie.isLocalStorage = this.representedObject.isLocalStorage();
cookie.host = this.representedObject.host;
}
get scrollableElements()
{
return [this._dataGrid.scrollContainer];
}
get canFocusFilterBar()
{
return true;
}
focusFilterBar()
{
this._filterBarNavigationItem.filterBar.focus();
}
itemsCleared(event)
{
this._dataGrid.removeChildren();
this._dataGrid.addPlaceholderNode();
}
itemRemoved(event)
{
for (let node of this._dataGrid.children) {
if (node.data.key === event.data.key)
return this._dataGrid.removeChild(node);
}
return null;
}
itemAdded(event)
{
let {key, value} = event.data;
let originalValue = value;
value = this._truncateValue(value);
// Enforce key uniqueness.
for (let node of this._dataGrid.children) {
if (node.data.key === key)
return;
}
this._dataGrid.appendChild(new WI.DataGridNode({key, value, originalValue}));
this._sortDataGrid();
}
itemUpdated(event)
{
let {key, newValue: value} = event.data;
let originalValue = value;
value = this._truncateValue(value);
let keyFound = false;
for (let childNode of this._dataGrid.children) {
if (childNode.data.key === key) {
// Remove any rows that are now duplicates.
if (keyFound) {
this._dataGrid.removeChild(childNode);
continue;
}
keyFound = true;
childNode.data.value = value;
childNode.data.originalValue = originalValue;
childNode.refresh();
}
}
this._sortDataGrid();
}
// Private
_truncateValue(value)
{
return value.truncate(200);
}
_populate()
{
this.representedObject.getEntries(function(error, entries) {
if (error)
return;
for (let [key, value] of entries) {
if (!key || !value)
continue;
let originalValue = value;
value = this._truncateValue(value);
let node = new WI.DataGridNode({key, value, originalValue});
this._dataGrid.appendChild(node);
}
this._sortDataGrid();
this._dataGrid.addPlaceholderNode();
this._dataGrid.updateLayout();
}.bind(this));
}
_sortDataGrid()
{
let sortColumnIdentifier = this._dataGrid.sortColumnIdentifier || "key";
function comparator(a, b)
{
return a.data[sortColumnIdentifier].extendedLocaleCompare(b.data[sortColumnIdentifier]);
}
this._dataGrid.sortNodesImmediately(comparator);
}
_deleteCallback()
{
for (let dataGridNode of this._dataGrid.selectedDataGridNodes) {
if (dataGridNode.isPlaceholderNode)
continue;
this._dataGrid.removeChild(dataGridNode);
this.representedObject.removeItem(dataGridNode.data["key"]);
}
}
_editingCallback(editingNode, columnIdentifier, oldText, newText, moveDirection)
{
var key = editingNode.data["key"].trim().removeWordBreakCharacters();
var value = editingNode.data["value"].trim().removeWordBreakCharacters();
var previousValue = oldText.trim().removeWordBreakCharacters();
var enteredValue = newText.trim().removeWordBreakCharacters();
var hasUncommittedEdits = editingNode.__hasUncommittedEdits;
var hasChange = previousValue !== enteredValue;
var isEditingKey = columnIdentifier === "key";
var isEditingValue = !isEditingKey;
var domStorage = this.representedObject;
// Nothing changed, just bail.
if (!hasChange && !hasUncommittedEdits)
return;
// Something changed, save the original key/value and enter uncommitted state.
if (hasChange && !editingNode.__hasUncommittedEdits) {
editingNode.__hasUncommittedEdits = true;
editingNode.__originalKey = isEditingKey ? previousValue : key;
editingNode.__originalValue = isEditingValue ? previousValue : value;
}
function cleanup()
{
editingNode.element.classList.remove(WI.DOMStorageContentView.MissingKeyStyleClassName);
editingNode.element.classList.remove(WI.DOMStorageContentView.MissingValueStyleClassName);
editingNode.element.classList.remove(WI.DOMStorageContentView.DuplicateKeyStyleClassName);
editingNode.__hasUncommittedEdits = undefined;
editingNode.__originalKey = undefined;
editingNode.__originalValue = undefined;
}
// If the key/value field was cleared, add "missing" style.
if (isEditingKey) {
if (key.length)
editingNode.element.classList.remove(WI.DOMStorageContentView.MissingKeyStyleClassName);
else
editingNode.element.classList.add(WI.DOMStorageContentView.MissingKeyStyleClassName);
} else if (isEditingValue) {
if (value.length)
editingNode.element.classList.remove(WI.DOMStorageContentView.MissingValueStyleClassName);
else
editingNode.element.classList.add(WI.DOMStorageContentView.MissingValueStyleClassName);
}
// Check for key duplicates. If this is a new row, or an existing row that changed key.
var keyChanged = key !== editingNode.__originalKey;
if (keyChanged) {
if (domStorage.entries.has(key))
editingNode.element.classList.add(WI.DOMStorageContentView.DuplicateKeyStyleClassName);
else
editingNode.element.classList.remove(WI.DOMStorageContentView.DuplicateKeyStyleClassName);
}
// See if we are done editing this row or not.
var columnIndex = this._dataGrid.orderedColumns.indexOf(columnIdentifier);
var mayMoveToNextRow = moveDirection === "forward" && columnIndex === this._dataGrid.orderedColumns.length - 1;
var mayMoveToPreviousRow = moveDirection === "backward" && columnIndex === 0;
var doneEditing = mayMoveToNextRow || mayMoveToPreviousRow || !moveDirection;
// Expecting more edits on this row.
if (!doneEditing)
return;
// Key and value were cleared, remove the row.
if (!key.length && !value.length && !editingNode.isPlaceholderNode) {
this._dataGrid.removeChild(editingNode);
domStorage.removeItem(editingNode.__originalKey);
return;
}
// Done editing but leaving the row in an invalid state. Leave in uncommitted state.
var isDuplicate = editingNode.element.classList.contains(WI.DOMStorageContentView.DuplicateKeyStyleClassName);
if (!key.length || !value.length || isDuplicate)
return;
// Commit.
if (keyChanged && !editingNode.isPlaceholderNode)
domStorage.removeItem(editingNode.__originalKey);
if (editingNode.isPlaceholderNode)
this._dataGrid.addPlaceholderNode();
cleanup();
domStorage.setItem(key, value);
}
_dataGridCopy(node, columnIdentifier, text)
{
if (columnIdentifier === "value" && node.data.originalValue)
return node.data.originalValue;
return text;
}
_handleFilterBarFilterDidChange(event)
{
this._dataGrid.filterText = this._filterBarNavigationItem.filterBar.filters.text || "";
}
_handleClearNavigationItemClicked(event)
{
this.representedObject.clear();
}
};
WI.DOMStorageContentView.DuplicateKeyStyleClassName = "duplicate-key";
WI.DOMStorageContentView.MissingKeyStyleClassName = "missing-key";
WI.DOMStorageContentView.MissingValueStyleClassName = "missing-value";