Entity List Context Menu

This commit is contained in:
Thijs Wenker 2018-10-11 18:43:56 +02:00
parent 7b04cbd092
commit 4c3f5b3bbc
5 changed files with 285 additions and 6 deletions

View file

@ -1801,3 +1801,42 @@ input[type=button]#export {
body#entity-list-body {
padding-bottom: 0;
}
.context-menu {
display: none;
position: fixed;
color: #000000;
background-color: #afafaf;
padding: 5px 0 5px 0;
cursor: default;
}
.context-menu li {
list-style-type: none;
padding: 0 5px 0 5px;
margin: 0;
white-space: nowrap;
}
.context-menu li:hover {
background-color: #e3e3e3;
}
.context-menu li.separator {
border-top: 1px solid #000000;
margin: 5px 5px;
}
.context-menu li.disabled {
color: #333333;
}
.context-menu li.separator:hover, .context-menu li.disabled:hover {
background-color: #afafaf;
}
input.rename-entity {
height: 100%;
width: 100%;
border: none;
font-family: FiraSans-SemiBold;
font-size: 15px;
/* need this to show the text cursor when the input field is empty */
padding-left: 2px;
}

View file

@ -16,6 +16,7 @@
<script type="text/javascript" src="js/eventBridgeLoader.js"></script>
<script type="text/javascript" src="js/spinButtons.js"></script>
<script type="text/javascript" src="js/listView.js"></script>
<script type="text/javascript" src="js/entityListContextMenu.js"></script>
<script type="text/javascript" src="js/entityList.js"></script>
</head>
<body onload='loaded();' id="entity-list-body">

View file

@ -70,6 +70,8 @@ var selectedEntities = [];
var entityList = null; // The ListView
var entityListContextMenu = new EntityListContextMenu();
var currentSortColumn = 'type';
var currentSortOrder = ASCENDING_SORT;
var isFilterInView = false;
@ -184,7 +186,83 @@ function loaded() {
entityList = new ListView(elEntityTableBody, elEntityTableScroll, elEntityTableHeaderRow,
createRow, updateRow, clearRow, WINDOW_NONVARIABLE_HEIGHT);
entityListContextMenu.initialize();
function startRenamingEntity(entityID) {
if (!entitiesByID[entityID] || !entitiesByID[entityID].elRow) {
return;
}
let elCell = entitiesByID[entityID].elRow.childNodes[COLUMN_INDEX.NAME];
let elRenameInput = document.createElement("input");
elRenameInput.setAttribute('class', 'rename-entity');
elRenameInput.value = entitiesByID[entityID].name;
elRenameInput.onclick = function(event) {
event.stopPropagation();
};
elRenameInput.onkeyup = function(keyEvent) {
if (keyEvent.key === "Enter") {
elRenameInput.blur();
}
};
elRenameInput.onblur = function(event) {
let value = elRenameInput.value;
EventBridge.emitWebEvent(JSON.stringify({
type: 'rename',
entityID: entityID,
name: value
}));
entitiesByID[entityID].name = value;
elCell.innerText = value;
};
elCell.innerHTML = "";
elCell.appendChild(elRenameInput);
elRenameInput.select();
}
entityListContextMenu.setCallback(function(optionName, selectedEntityID) {
switch (optionName) {
case "Copy":
EventBridge.emitWebEvent(JSON.stringify({ type: 'copy' }));
break;
case "Paste":
EventBridge.emitWebEvent(JSON.stringify({ type: 'paste' }));
break;
case "Rename":
startRenamingEntity(selectedEntityID);
break;
case "Duplicate":
EventBridge.emitWebEvent(JSON.stringify({ type: 'duplicate' }));
break;
case "Delete":
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
break;
}
});
function onRowContextMenu(clickEvent) {
let entityID = this.dataset.entityID;
if (!selectedEntities.includes(entityID)) {
let selection = [entityID];
updateSelectedEntities(selection);
EventBridge.emitWebEvent(JSON.stringify({
type: "selectionUpdate",
focus: false,
entityIds: selection,
}));
refreshFooter();
}
entityListContextMenu.open(clickEvent, entityID);
}
function onRowClicked(clickEvent) {
let entityID = this.dataset.entityID;
let selection = [entityID];
@ -221,9 +299,9 @@ function loaded() {
}
}
} else if (!clickEvent.ctrlKey && !clickEvent.shiftKey && selectedEntities.length === 1) {
// if reselecting the same entity then deselect it
// if reselecting the same entity then start renaming it
if (selectedEntities[0] === entityID) {
selection = [];
startRenamingEntity(entityID);
}
}
@ -502,6 +580,7 @@ function loaded() {
}
row.appendChild(column);
}
row.oncontextmenu = onRowContextMenu;
row.onclick = onRowClicked;
row.ondblclick = onRowDoubleClicked;
return row;
@ -672,8 +751,15 @@ function loaded() {
augmentSpinButtons();
// Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked
document.addEventListener("contextmenu", function (event) {
entityListContextMenu.close.call(entityListContextMenu);
// Disable default right-click context menu which is not visible in the HMD and makes it seem like the app has locked
event.preventDefault();
}, false);
// close context menu when switching focus to another window
$(window).blur(function(){
entityListContextMenu.close.call(entityListContextMenu);
});
}

View file

@ -0,0 +1,143 @@
//
// entityListContextMenu.js
//
// exampleContextMenus.js was originally created by David Rowe on 22 Aug 2018.
// Modified to entityListContextMenu.js by Thijs Wenker on 10 Oct 2018
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* eslint-env browser */
const CONTEXT_MENU_CLASS = "context-menu";
/**
* ContextMenu class for EntityList
* @constructor
*/
function EntityListContextMenu() {
this._elContextMenu = null;
this._callback = null;
}
EntityListContextMenu.prototype = {
/**
* @private
*/
_elContextMenu: null,
/**
* @private
*/
_callback: null,
/**
* @private
*/
_selectedEntityID: null,
/**
* Close the context menu
*/
close: function() {
if (this.isContextMenuOpen()) {
this._elContextMenu.style.display = "none";
}
},
isContextMenuOpen: function() {
return this._elContextMenu.style.display === "block";
},
/**
* Open the context menu
* @param clickEvent
* @param selectedEntityID
*/
open: function(clickEvent, selectedEntityID) {
this._selectedEntityID = selectedEntityID;
// If the right-clicked item has a context menu open it.
this._elContextMenu.style.display = "block";
this._elContextMenu.style.left
= Math.min(clickEvent.pageX, document.body.offsetWidth - this._elContextMenu.offsetWidth).toString() + "px";
this._elContextMenu.style.top
= Math.min(clickEvent.pageY, document.body.clientHeight - this._elContextMenu.offsetHeight).toString() + "px";
clickEvent.stopPropagation();
},
/**
* Set the event callback
* @param callback
*/
setCallback: function(callback) {
this._callback = callback;
},
/**
* Add a labeled item to the context menu
* @param itemLabel
* @param isEnabled
* @private
*/
_addListItem: function(itemLabel, isEnabled) {
let elListItem = document.createElement("li");
elListItem.innerText = itemLabel;
if (isEnabled === undefined || isEnabled) {
elListItem.addEventListener("click", function () {
if (this._callback) {
this._callback.call(this, itemLabel, this._selectedEntityID);
}
}.bind(this), false);
} else {
elListItem.setAttribute('class', 'disabled');
}
this._elContextMenu.appendChild(elListItem);
},
/**
* Add a separator item to the context menu
* @private
*/
_addListSeparator: function() {
let elListItem = document.createElement("li");
elListItem.setAttribute('class', 'separator');
this._elContextMenu.appendChild(elListItem);
},
/**
* Initialize the context menu.
*/
initialize: function() {
this._elContextMenu = document.createElement("ul");
this._elContextMenu.setAttribute("class", CONTEXT_MENU_CLASS);
document.body.appendChild(this._elContextMenu);
// TODO: enable Copy, Paste and Duplicate items once implemented
this._addListItem("Copy", false);
this._addListItem("Paste", false);
this._addListSeparator();
this._addListItem("Rename");
this._addListItem("Duplicate", false);
this._addListItem("Delete");
// Ignore clicks on context menu background or separator.
this._elContextMenu.addEventListener("click", function(event) {
// Sink clicks on context menu background or separator but let context menu item clicks through.
if (event.target.classList.contains(CONTEXT_MENU_CLASS)) {
event.stopPropagation();
}
});
// Provide means to close context menu without clicking menu item.
document.body.addEventListener("click", this.close.bind(this));
document.body.addEventListener("keydown", function(event) {
// Close context menu with Esc key.
if (this.isContextMenuOpen() && event.key === "Escape") {
this.close();
}
}.bind(this));
}
};

View file

@ -15,7 +15,7 @@ var PROFILING_ENABLED = false;
var profileIndent = '';
const PROFILE_NOOP = function(_name, fn, args) {
fn.apply(this, args);
} ;
};
PROFILE = !PROFILING_ENABLED ? PROFILE_NOOP : function(name, fn, args) {
console.log("PROFILE-Script " + profileIndent + "(" + name + ") Begin");
var previousIndent = profileIndent;
@ -245,7 +245,7 @@ EntityListTool = function(shouldUseEditTabletApp) {
Window.saveAsync("Select Where to Save", "", "*.json");
}
} else if (data.type === "pal") {
var sessionIds = {}; // Collect the sessionsIds of all selected entitities, w/o duplicates.
var sessionIds = {}; // Collect the sessionsIds of all selected entities, w/o duplicates.
selectionManager.selections.forEach(function (id) {
var lastEditedBy = Entities.getEntityProperties(id, 'lastEditedBy').lastEditedBy;
if (lastEditedBy) {
@ -271,6 +271,16 @@ EntityListTool = function(shouldUseEditTabletApp) {
filterInView = data.filterInView === true;
} else if (data.type === "radius") {
searchRadius = data.radius;
} else if (data.type === "copy") {
Window.alert("Copy is not yet implemented.");
} else if (data.type === "paste") {
Window.alert("Paste is not yet implemented.");
} else if (data.type === "duplicate") {
Window.alert("Duplicate is not yet implemented.");
} else if (data.type === "rename") {
Entities.editEntity(data.entityID, {name: data.name});
// make sure that the name also gets updated in the properties window
SelectionManager._update();
}
};