mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 13:49:12 +02:00
Entity List Context Menu
This commit is contained in:
parent
7b04cbd092
commit
4c3f5b3bbc
5 changed files with 285 additions and 6 deletions
|
@ -1801,3 +1801,42 @@ input[type=button]#export {
|
||||||
body#entity-list-body {
|
body#entity-list-body {
|
||||||
padding-bottom: 0;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
<script type="text/javascript" src="js/eventBridgeLoader.js"></script>
|
<script type="text/javascript" src="js/eventBridgeLoader.js"></script>
|
||||||
<script type="text/javascript" src="js/spinButtons.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/listView.js"></script>
|
||||||
|
<script type="text/javascript" src="js/entityListContextMenu.js"></script>
|
||||||
<script type="text/javascript" src="js/entityList.js"></script>
|
<script type="text/javascript" src="js/entityList.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body onload='loaded();' id="entity-list-body">
|
<body onload='loaded();' id="entity-list-body">
|
||||||
|
|
|
@ -70,6 +70,8 @@ var selectedEntities = [];
|
||||||
|
|
||||||
var entityList = null; // The ListView
|
var entityList = null; // The ListView
|
||||||
|
|
||||||
|
var entityListContextMenu = new EntityListContextMenu();
|
||||||
|
|
||||||
var currentSortColumn = 'type';
|
var currentSortColumn = 'type';
|
||||||
var currentSortOrder = ASCENDING_SORT;
|
var currentSortOrder = ASCENDING_SORT;
|
||||||
var isFilterInView = false;
|
var isFilterInView = false;
|
||||||
|
@ -184,7 +186,83 @@ function loaded() {
|
||||||
|
|
||||||
entityList = new ListView(elEntityTableBody, elEntityTableScroll, elEntityTableHeaderRow,
|
entityList = new ListView(elEntityTableBody, elEntityTableScroll, elEntityTableHeaderRow,
|
||||||
createRow, updateRow, clearRow, WINDOW_NONVARIABLE_HEIGHT);
|
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) {
|
function onRowClicked(clickEvent) {
|
||||||
let entityID = this.dataset.entityID;
|
let entityID = this.dataset.entityID;
|
||||||
let selection = [entityID];
|
let selection = [entityID];
|
||||||
|
@ -221,9 +299,9 @@ function loaded() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (!clickEvent.ctrlKey && !clickEvent.shiftKey && selectedEntities.length === 1) {
|
} 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) {
|
if (selectedEntities[0] === entityID) {
|
||||||
selection = [];
|
startRenamingEntity(entityID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -502,6 +580,7 @@ function loaded() {
|
||||||
}
|
}
|
||||||
row.appendChild(column);
|
row.appendChild(column);
|
||||||
}
|
}
|
||||||
|
row.oncontextmenu = onRowContextMenu;
|
||||||
row.onclick = onRowClicked;
|
row.onclick = onRowClicked;
|
||||||
row.ondblclick = onRowDoubleClicked;
|
row.ondblclick = onRowDoubleClicked;
|
||||||
return row;
|
return row;
|
||||||
|
@ -672,8 +751,15 @@ function loaded() {
|
||||||
|
|
||||||
augmentSpinButtons();
|
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) {
|
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();
|
event.preventDefault();
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
|
// close context menu when switching focus to another window
|
||||||
|
$(window).blur(function(){
|
||||||
|
entityListContextMenu.close.call(entityListContextMenu);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
143
scripts/system/html/js/entityListContextMenu.js
Normal file
143
scripts/system/html/js/entityListContextMenu.js
Normal 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));
|
||||||
|
}
|
||||||
|
};
|
|
@ -15,7 +15,7 @@ var PROFILING_ENABLED = false;
|
||||||
var profileIndent = '';
|
var profileIndent = '';
|
||||||
const PROFILE_NOOP = function(_name, fn, args) {
|
const PROFILE_NOOP = function(_name, fn, args) {
|
||||||
fn.apply(this, args);
|
fn.apply(this, args);
|
||||||
} ;
|
};
|
||||||
PROFILE = !PROFILING_ENABLED ? PROFILE_NOOP : function(name, fn, args) {
|
PROFILE = !PROFILING_ENABLED ? PROFILE_NOOP : function(name, fn, args) {
|
||||||
console.log("PROFILE-Script " + profileIndent + "(" + name + ") Begin");
|
console.log("PROFILE-Script " + profileIndent + "(" + name + ") Begin");
|
||||||
var previousIndent = profileIndent;
|
var previousIndent = profileIndent;
|
||||||
|
@ -245,7 +245,7 @@ EntityListTool = function(shouldUseEditTabletApp) {
|
||||||
Window.saveAsync("Select Where to Save", "", "*.json");
|
Window.saveAsync("Select Where to Save", "", "*.json");
|
||||||
}
|
}
|
||||||
} else if (data.type === "pal") {
|
} 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) {
|
selectionManager.selections.forEach(function (id) {
|
||||||
var lastEditedBy = Entities.getEntityProperties(id, 'lastEditedBy').lastEditedBy;
|
var lastEditedBy = Entities.getEntityProperties(id, 'lastEditedBy').lastEditedBy;
|
||||||
if (lastEditedBy) {
|
if (lastEditedBy) {
|
||||||
|
@ -271,6 +271,16 @@ EntityListTool = function(shouldUseEditTabletApp) {
|
||||||
filterInView = data.filterInView === true;
|
filterInView = data.filterInView === true;
|
||||||
} else if (data.type === "radius") {
|
} else if (data.type === "radius") {
|
||||||
searchRadius = data.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();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue