Merge pull request #4141 from huffman/entity-list-updates

Entity list updates - sorting, search, style
This commit is contained in:
Brad Hefta-Gaub 2015-01-20 18:18:15 -08:00
commit 5a621ba015
7 changed files with 243 additions and 135 deletions

View file

@ -44,10 +44,16 @@ var entityListTool = EntityListTool();
var hasShownPropertiesTool = false; var hasShownPropertiesTool = false;
var entityListVisible = false;
selectionManager.addEventListener(function() { selectionManager.addEventListener(function() {
selectionDisplay.updateHandles(); selectionDisplay.updateHandles();
if (selectionManager.hasSelection() && !hasShownPropertiesTool) { if (selectionManager.hasSelection() && !hasShownPropertiesTool) {
// Open properties and model list, but force selection of model list tab
propertiesTool.setVisible(false);
entityListTool.setVisible(false);
propertiesTool.setVisible(true); propertiesTool.setVisible(true);
entityListTool.setVisible(true);
hasShownPropertiesTool = true; hasShownPropertiesTool = true;
} }
}); });
@ -690,8 +696,8 @@ function setupModelMenus() {
print("delete exists... don't add ours"); print("delete exists... don't add ours");
} }
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Model List...", afterItem: "Models" }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Entity List...", shortcutKey: "CTRL+META+L", afterItem: "Models" });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Model List..." }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Entity List..." });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Large Models", shortcutKey: "CTRL+META+L", Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Large Models", shortcutKey: "CTRL+META+L",
afterItem: "Paste Models", isCheckable: true, isChecked: true }); afterItem: "Paste Models", isCheckable: true, isChecked: true });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Small Models", shortcutKey: "CTRL+META+S", Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Small Models", shortcutKey: "CTRL+META+S",
@ -703,6 +709,7 @@ function setupModelMenus() {
Menu.addMenuItem({ menuName: "File", menuItemName: "Export Models", shortcutKey: "CTRL+META+E", afterItem: "Models" }); Menu.addMenuItem({ menuName: "File", menuItemName: "Export Models", shortcutKey: "CTRL+META+E", afterItem: "Models" });
Menu.addMenuItem({ menuName: "File", menuItemName: "Import Models", shortcutKey: "CTRL+META+I", afterItem: "Export Models" }); Menu.addMenuItem({ menuName: "File", menuItemName: "Import Models", shortcutKey: "CTRL+META+I", afterItem: "Export Models" });
Menu.addMenuItem({ menuName: "View", menuItemName: MENU_EASE_ON_FOCUS, afterItem: MENU_INSPECT_TOOL_ENABLED, Menu.addMenuItem({ menuName: "View", menuItemName: MENU_EASE_ON_FOCUS, afterItem: MENU_INSPECT_TOOL_ENABLED,
isCheckable: true, isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) == "true" }); isCheckable: true, isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) == "true" });
@ -718,7 +725,7 @@ function cleanupModelMenus() {
Menu.removeMenuItem("Edit", "Delete"); Menu.removeMenuItem("Edit", "Delete");
} }
Menu.removeMenuItem("Edit", "Model List..."); Menu.removeMenuItem("Edit", "Entity List...");
Menu.removeMenuItem("Edit", "Paste Models"); Menu.removeMenuItem("Edit", "Paste Models");
Menu.removeMenuItem("Edit", "Allow Selecting of Large Models"); Menu.removeMenuItem("Edit", "Allow Selecting of Large Models");
Menu.removeMenuItem("Edit", "Allow Selecting of Small Models"); Menu.removeMenuItem("Edit", "Allow Selecting of Small Models");
@ -755,6 +762,28 @@ Script.update.connect(function (deltaTime) {
selectionDisplay.checkMove(); selectionDisplay.checkMove();
}); });
function deleteSelectedEntities() {
if (SelectionManager.hasSelection()) {
print(" Delete Entities");
SelectionManager.saveProperties();
var savedProperties = [];
for (var i = 0; i < selectionManager.selections.length; i++) {
var entityID = SelectionManager.selections[i];
var initialProperties = SelectionManager.savedProperties[entityID.id];
SelectionManager.savedProperties[entityID.id];
savedProperties.push({
entityID: entityID,
properties: initialProperties
});
Entities.deleteEntity(entityID);
}
SelectionManager.clearSelections();
pushCommandForSelections([], savedProperties);
} else {
print(" Delete Entity.... not holding...");
}
}
function handeMenuEvent(menuItem) { function handeMenuEvent(menuItem) {
if (menuItem == "Allow Selecting of Small Models") { if (menuItem == "Allow Selecting of Small Models") {
allowSmallModels = Menu.isOptionChecked("Allow Selecting of Small Models"); allowSmallModels = Menu.isOptionChecked("Allow Selecting of Small Models");
@ -763,57 +792,7 @@ function handeMenuEvent(menuItem) {
} else if (menuItem == "Allow Selecting of Lights") { } else if (menuItem == "Allow Selecting of Lights") {
Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights")); Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights"));
} else if (menuItem == "Delete") { } else if (menuItem == "Delete") {
if (SelectionManager.hasSelection()) { deleteSelectedEntities();
print(" Delete Entities");
SelectionManager.saveProperties();
var savedProperties = [];
for (var i = 0; i < selectionManager.selections.length; i++) {
var entityID = SelectionManager.selections[i];
var initialProperties = SelectionManager.savedProperties[entityID.id];
SelectionManager.savedProperties[entityID.id];
savedProperties.push({
entityID: entityID,
properties: initialProperties
});
Entities.deleteEntity(entityID);
}
SelectionManager.clearSelections();
pushCommandForSelections([], savedProperties);
} else {
print(" Delete Entity.... not holding...");
}
} else if (menuItem == "Model List...") {
var models = new Array();
models = Entities.findEntities(MyAvatar.position, Number.MAX_VALUE);
for (var i = 0; i < models.length; i++) {
models[i].properties = Entities.getEntityProperties(models[i]);
models[i].toString = function() {
var modelname;
if (this.properties.type == "Model") {
modelname = decodeURIComponent(
this.properties.modelURL.indexOf("/") != -1 ?
this.properties.modelURL.substring(this.properties.modelURL.lastIndexOf("/") + 1) :
this.properties.modelURL);
} else {
modelname = this.properties.id;
}
return "[" + this.properties.type + "] " + modelname;
};
}
var form = [{label: "Model: ", options: models}];
form.push({label: "Action: ", options: ["Properties", "Delete", "Teleport"]});
form.push({ button: "Cancel" });
if (Window.form("Model List", form)) {
var selectedModel = form[0].value;
if (form[1].value == "Properties") {
editModelID = selectedModel;
entityPropertyDialogBox.openDialog(editModelID);
} else if (form[1].value == "Delete") {
Entities.deleteEntity(selectedModel);
} else if (form[1].value == "Teleport") {
MyAvatar.position = selectedModel.properties.position;
}
}
} else if (menuItem == "Paste Models") { } else if (menuItem == "Paste Models") {
modelImporter.paste(); modelImporter.paste();
} else if (menuItem == "Export Models") { } else if (menuItem == "Export Models") {
@ -826,6 +805,10 @@ function handeMenuEvent(menuItem) {
} }
} else if (menuItem == "Import Models") { } else if (menuItem == "Import Models") {
modelImporter.doImport(); modelImporter.doImport();
} else if (menuItem == "Entity List...") {
if (isActive) {
entityListTool.toggleVisible();
}
} }
tooltip.show(false); tooltip.show(false);
} }
@ -842,7 +825,7 @@ Controller.keyPressEvent.connect(function(event) {
Controller.keyReleaseEvent.connect(function (event) { Controller.keyReleaseEvent.connect(function (event) {
// since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items // since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items
if (event.text == "BACKSPACE" || event.text == "DELETE") { if (event.text == "BACKSPACE" || event.text == "DELETE") {
handeMenuEvent("Delete"); deleteSelectedEntities();
} else if (event.text == "TAB") { } else if (event.text == "TAB") {
selectionDisplay.toggleSpaceMode(); selectionDisplay.toggleSpaceMode();
} else if (event.text == "f") { } else if (event.text == "f") {

View file

@ -1,14 +1,32 @@
<html> <html>
<head> <head>
<link rel="stylesheet" type="text/css" href="style.css"> <link rel="stylesheet" type="text/css" href="style.css">
<script src="list.min.js"></script>
<script> <script>
var entities = {}; var entities = {};
var selectedEntities = []; var selectedEntities = [];
var currentSortColumn = 'type';
var currentSortOrder = 'asc';
var entityList = null;
var refreshEntityListTimer = null;
var ASC_STRING = '&nbsp;&#x25BE;';
var DESC_STRING = '&nbsp;&#x25B4;';
function loaded() { function loaded() {
entityList = new List('entity-list', { valueNames: ['type', 'url']});
entityList.clear();
elEntityTable = document.getElementById("entity-table"); elEntityTable = document.getElementById("entity-table");
elEntityTableBody = document.getElementById("entity-table-body");
elRefresh = document.getElementById("refresh"); elRefresh = document.getElementById("refresh");
elDelete = document.getElementById("delete");
elTeleport = document.getElementById("teleport");
document.getElementById("entity-type").onclick = function() {
setSortColumn('type');
};
document.getElementById("entity-url").onclick = function() {
setSortColumn('url');
};
function onRowClicked(e) { function onRowClicked(e) {
var id = this.dataset.entityId; var id = this.dataset.entityId;
@ -20,7 +38,8 @@
selectedEntities = selection; selectedEntities = selection;
entities[id].el.className = 'selected'; this.className = 'selected';
EventBridge.emitWebEvent(JSON.stringify({ EventBridge.emitWebEvent(JSON.stringify({
type: "selectionUpdate", type: "selectionUpdate",
focus: false, focus: false,
@ -29,8 +48,6 @@
} }
function onRowDoubleClicked() { function onRowDoubleClicked() {
var id = this.dataset.entityId;
EventBridge.emitWebEvent(JSON.stringify({ EventBridge.emitWebEvent(JSON.stringify({
type: "selectionUpdate", type: "selectionUpdate",
focus: true, focus: true,
@ -40,41 +57,91 @@
function addEntity(id, type, url) { function addEntity(id, type, url) {
if (entities[id] === undefined) { if (entities[id] === undefined) {
var el = document.createElement('tr'); var urlParts = url.split('/');
el.setAttribute('id', 'entity_' + id); var filename = urlParts[urlParts.length - 1];
el.innerHTML += "<td>" + type + "</td>";
el.innerHTML += "<td>" + url + "</td>";
el.dataset.entityId = id;
el.onclick = onRowClicked;
el.ondblclick = onRowDoubleClicked;
elEntityTable.appendChild(el);
// Add element to local dict entityList.add([{ id: id, type: type, url: filename }], function(items) {
entities[id] = { var el = items[0].elm;
id: id, var id = items[0]._values.id;
name: id, entities[id] = {
el: el, id: id,
}; name: id,
} el: el,
} };
el.setAttribute('id', 'entity_' + id);
el.setAttribute('title', url);
el.dataset.entityId = id;
el.onclick = onRowClicked;
el.ondblclick = onRowDoubleClicked;
el.innerHTML
});
function removeEntity(id) { if (refreshEntityListTimer) {
if (entities[id] !== undefined) { clearTimeout(refreshEntityListTimer);
elEntityTable.removeChild(entities[id].el); }
delete entities[id]; refreshEntityListTimer = setTimeout(refreshEntityListObject, 50);
} }
} }
function clearEntities() { function clearEntities() {
for (id in entities) {
elEntityTable.removeChild(entities[id].el);
}
entities = {}; entities = {};
entityList.clear();
}
var elSortOrder = {
type: document.querySelector('#entity-type .sort-order'),
url: document.querySelector('#entity-url .sort-order'),
}
function setSortColumn(column) {
if (currentSortColumn == column) {
currentSortOrder = currentSortOrder == "asc" ? "desc" : "asc";
} else {
elSortOrder[currentSortColumn].style.display = 'none';
elSortOrder[column].style.display = 'inline';
currentSortColumn = column;
currentSortOrder = "asc";
}
elSortOrder[column].innerHTML = currentSortOrder == "asc" ? ASC_STRING : DESC_STRING;
entityList.sort(currentSortColumn, { order: currentSortOrder });
}
function refreshEntities() {
clearEntities();
EventBridge.emitWebEvent(JSON.stringify({ type: 'refresh' }));
}
function refreshEntityListObject() {
refreshEntityListTimer = null;
entityList.sort(currentSortColumn, { order: currentSortOrder });
entityList.search(document.getElementById("filter").value);
}
function updateSelectedEntities(selectedEntities) {
var notFound = false;
for (var id in entities) {
entities[id].el.className = '';
}
for (var i = 0; i < selectedEntities.length; i++) {
var id = selectedEntities[i];
if (id in entities) {
var entity = entities[id];
entity.el.className = 'selected';
} else {
notFound = true;
}
}
return notFound;
} }
elRefresh.onclick = function() { elRefresh.onclick = function() {
clearEntities(); refreshEntities();
EventBridge.emitWebEvent(JSON.stringify({ type: 'refresh' })); }
elTeleport.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'teleport' }));
}
elDelete.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
refreshEntities();
} }
if (window.EventBridge !== undefined) { if (window.EventBridge !== undefined) {
@ -82,16 +149,9 @@
data = JSON.parse(data); data = JSON.parse(data);
if (data.type == "selectionUpdate") { if (data.type == "selectionUpdate") {
selectedEntities = data.selectedIDs; var notFound = updateSelectedEntities(data.selectedIDs);
for (var id in entities) { if (notFound) {
entities[id].el.className = ''; refreshEntities();
}
for (var i = 0; i < data.selectedIDs.length; i++) {
var id = data.selectedIDs[i];
if (id in entities) {
var entity = entities[id];
entity.el.className = 'selected';
}
} }
} else if (data.type == "update") { } else if (data.type == "update") {
var newEntities = data.entities; var newEntities = data.entities;
@ -99,27 +159,39 @@
var id = newEntities[i].id; var id = newEntities[i].id;
addEntity(id, newEntities[i].type, newEntities[i].url); addEntity(id, newEntities[i].type, newEntities[i].url);
} }
updateSelectedEntities(data.selectedIDs);
} }
}); });
setTimeout(refreshEntities, 1000);
} }
} }
</script> </script>
</head> </head>
<body onload='loaded();'> <body onload='loaded();'>
<div> <div>
<button id="refresh">Refresh</button> <input type="button" id="refresh" value="Refresh"></button>
<input type="button" id="teleport" value="Teleport"></button>
<input type="button" id="delete" style="background-color: rgb(244, 64, 64); float: right" value="Delete"></button>
</div> </div>
<table id="entity-table"> <div id="entity-list">
<thead> <input type="text" class="search" id="filter" placeholder="Filter" />
<tr> <table id="entity-table">
<th id="entity-type">Type</th> <thead>
<th id="entity-url">URL</th> <tr>
</tr> <th id="entity-type" data-sort="type">Type <span class="sort-order" style="display: inline">&nbsp;&#x25BE;</span></th>
</thead> <th id="entity-url" data-sort="url">URL <span class="sort-order" style="display: none">&nbsp;&#x25BE;</span></th>
<tbody id="entity-table-body"> </tr>
</tbody> </thead>
</table> <tbody class="list" id="entity-table-body">
<tr>
<td class="id" style="display: none">Type</td>
<td class="type">Type</td>
<td class="url"><div class='outer'><div class='inner'>URL</div></div></td>
</tr>
</tbody>
</table>
</div>
</body> </body>
</html> </html>

1
examples/html/list.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -5,9 +5,10 @@ body {
margin: 0; margin: 0;
padding: 0; padding: 0;
background-color: #efefef; background-color: rgb(76, 76, 76);
color: rgb(204, 204, 204);
font-family: Arial; font-family: Arial;
font-size: 11.5px; font-size: 11px;
-webkit-touch-callout: none; -webkit-touch-callout: none;
-webkit-user-select: none; -webkit-user-select: none;
@ -17,12 +18,6 @@ body {
user-select: none; user-select: none;
} }
body.properties {
background-color: rgb(76, 76, 76);
color: rgb(204, 204, 204);
font-size: 11px;
}
.selectable { .selectable {
-webkit-touch-callout: text; -webkit-touch-callout: text;
-webkit-user-select: text; -webkit-user-select: text;
@ -117,12 +112,13 @@ input.coord {
table#entity-table { table#entity-table {
border-collapse: collapse; border-collapse: collapse;
font-family: Sans-Serif; font-family: Sans-Serif;
/* font-size: 12px; */ font-size: 10px;
width: 100%; width: 100%;
} }
#entity-table tr { #entity-table tr {
cursor: pointer; cursor: pointer;
border-bottom: 1px solid rgb(63, 63, 63)
} }
#entity-table tr.selected { #entity-table tr.selected {
@ -139,17 +135,22 @@ table#entity-table {
} }
#entity-table td { #entity-table td {
font-size: 11px;
border: 0px black solid; border: 0px black solid;
word-wrap: nowrap; word-wrap: nowrap;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis;
}
#entity-table td.url {
white-space: nowrap;
overflow: hidden;
} }
th#entity-type { th#entity-type {
width: 60px; width: 60px;
} }
th#entity-url {
}
div.input-area { div.input-area {
@ -226,3 +227,20 @@ table#properties-list {
col#col-label { col#col-label {
width: 130px; width: 130px;
} }
div.outer {
position: relative;
}
div.inner {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
position: absolute;
width: 100%;
}
td {
vertical-align: top;
}

View file

@ -13,6 +13,10 @@ EntityListTool = function(opts) {
webView.setVisible(visible); webView.setVisible(visible);
}; };
that.toggleVisible = function() {
that.setVisible(!visible);
}
selectionManager.addEventListener(function() { selectionManager.addEventListener(function() {
var selectedIDs = []; var selectedIDs = [];
@ -24,10 +28,38 @@ EntityListTool = function(opts) {
type: 'selectionUpdate', type: 'selectionUpdate',
selectedIDs: selectedIDs, selectedIDs: selectedIDs,
}; };
print("Sending: " + JSON.stringify(data));
webView.eventBridge.emitScriptEvent(JSON.stringify(data)); webView.eventBridge.emitScriptEvent(JSON.stringify(data));
}); });
function sendUpdate() {
var entities = [];
var ids = Entities.findEntities(MyAvatar.position, 100);
for (var i = 0; i < ids.length; i++) {
var id = ids[i];
var properties = Entities.getEntityProperties(id);
entities.push({
id: id.id,
type: properties.type,
url: properties.type == "Model" ? properties.modelURL : "",
});
}
var selectedIDs = [];
for (var i = 0; i < selectionManager.selections.length; i++) {
selectedIDs.push(selectionManager.selections[i].id);
}
var data = {
type: "update",
entities: entities,
selectedIDs: selectedIDs,
};
webView.eventBridge.emitScriptEvent(JSON.stringify(data));
}
webView.eventBridge.webEventReceived.connect(function(data) { webView.eventBridge.webEventReceived.connect(function(data) {
print("Got: " + data);
data = JSON.parse(data); data = JSON.parse(data);
if (data.type == "selectionUpdate") { if (data.type == "selectionUpdate") {
var ids = data.entityIds; var ids = data.entityIds;
@ -46,22 +78,13 @@ EntityListTool = function(opts) {
Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); Menu.isOptionChecked(MENU_EASE_ON_FOCUS));
} }
} else if (data.type == "refresh") { } else if (data.type == "refresh") {
var entities = []; sendUpdate();
var ids = Entities.findEntities(MyAvatar.position, 100); } else if (data.type == "teleport") {
for (var i = 0; i < ids.length; i++) { if (selectionManager.hasSelection()) {
var id = ids[i]; MyAvatar.position = selectionManager.worldPosition;
var properties = Entities.getEntityProperties(id);
entities.push({
id: id.id,
type: properties.type,
url: properties.type == "Model" ? properties.modelURL : "",
});
} }
var data = { } else if (data.type == "delete") {
type: "update", deleteSelectedEntities();
entities: entities,
};
webView.eventBridge.emitScriptEvent(JSON.stringify(data));
} }
}); });

View file

@ -41,19 +41,27 @@ WebWindowClass::WebWindowClass(const QString& title, const QString& url, int wid
_dockWidget = new QDockWidget(title, toolWindow); _dockWidget = new QDockWidget(title, toolWindow);
_dockWidget->setFeatures(QDockWidget::DockWidgetMovable); _dockWidget->setFeatures(QDockWidget::DockWidgetMovable);
QWebView* webView = new QWebView(_dockWidget);
webView->page()->mainFrame()->addToJavaScriptWindowObject("EventBridge", _eventBridge); _webView = new QWebView(_dockWidget);
webView->setUrl(url); _webView->setUrl(url);
_dockWidget->setWidget(webView); addEventBridgeToWindowObject();
_dockWidget->setWidget(_webView);
toolWindow->addDockWidget(Qt::RightDockWidgetArea, _dockWidget); toolWindow->addDockWidget(Qt::RightDockWidgetArea, _dockWidget);
connect(_webView->page()->mainFrame(), &QWebFrame::javaScriptWindowObjectCleared,
this, &WebWindowClass::addEventBridgeToWindowObject);
connect(this, &WebWindowClass::destroyed, _dockWidget, &QWidget::deleteLater); connect(this, &WebWindowClass::destroyed, _dockWidget, &QWidget::deleteLater);
} }
WebWindowClass::~WebWindowClass() { WebWindowClass::~WebWindowClass() {
} }
void WebWindowClass::addEventBridgeToWindowObject() {
_webView->page()->mainFrame()->addToJavaScriptWindowObject("EventBridge", _eventBridge);
}
void WebWindowClass::setVisible(bool visible) { void WebWindowClass::setVisible(bool visible) {
if (visible) { if (visible) {
QMetaObject::invokeMethod( QMetaObject::invokeMethod(

View file

@ -14,6 +14,7 @@
#include <QScriptContext> #include <QScriptContext>
#include <QScriptEngine> #include <QScriptEngine>
#include <QWebView>
class ScriptEventBridge : public QObject { class ScriptEventBridge : public QObject {
Q_OBJECT Q_OBJECT
@ -42,9 +43,11 @@ public:
public slots: public slots:
void setVisible(bool visible); void setVisible(bool visible);
ScriptEventBridge* getEventBridge() const { return _eventBridge; } ScriptEventBridge* getEventBridge() const { return _eventBridge; }
void addEventBridgeToWindowObject();
private: private:
QDockWidget* _dockWidget; QDockWidget* _dockWidget;
QWebView* _webView;
ScriptEventBridge* _eventBridge; ScriptEventBridge* _eventBridge;
}; };