Add "Import" Tab

Add "Import" Tab
This commit is contained in:
Alezia Kurdis 2024-03-16 14:05:14 -04:00 committed by GitHub
parent f50101db8a
commit a509f84a42
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 533 additions and 2 deletions

View file

@ -121,6 +121,16 @@
var copiedPosition;
var copiedRotation;
var importUiPersistedData = {
"elJsonUrl": "",
"elImportAtAvatar": true,
"elImportAtSpecificPosition": false,
"elPositionX": 0,
"elPositionY": 0,
"elPositionZ": 0,
"elEntityHostTypeDomain": true,
"elEntityHostTypeAvatar": false
};
var cameraManager = new CameraManager();
@ -2009,7 +2019,8 @@
return position;
}
function importSVO(importURL) {
function importSVO(importURL, importEntityHostType) {
importEntityHostType = importEntityHostType || "domain";
if (!Entities.canRez() && !Entities.canRezTmp()) {
Window.notifyEditError(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG);
return;
@ -2032,7 +2043,7 @@
position = createApp.getPositionToCreateEntity(Clipboard.getClipboardContentsLargestDimension() / 2);
}
if (position !== null && position !== undefined) {
var pastedEntityIDs = Clipboard.pasteEntities(position);
var pastedEntityIDs = Clipboard.pasteEntities(position, importEntityHostType);
if (!isLargeImport) {
// The first entity in Clipboard gets the specified position with the rest being relative to it. Therefore, move
// entities after they're imported so that they're all the correct distance in front of and with geometric mean
@ -2792,6 +2803,72 @@
type: 'zoneListRequest',
zones: getExistingZoneList()
});
} else if (data.type === "importUiBrowse") {
let fileToImport = Window.browse("Select .json to Import", "", "*.json");
if (fileToImport !== null) {
emitScriptEvent({
type: 'importUi_SELECTED_FILE',
file: fileToImport
});
} else {
audioFeedback.rejection();
}
} else if (data.type === "importUiImport") {
if ((data.entityHostType === "domain" && Entities.canAdjustLocks() && Entities.canRez()) ||
(data.entityHostType === "avatar" && Entities.canRezAvatarEntities())) {
if (data.positioningMode === "avatar") {
importSVO(data.jsonURL, data.entityHostType);
} else {
if (Clipboard.importEntities(data.jsonURL)) {
let importedPastedEntities = Clipboard.pasteEntities(data.position, data.entityHostType);
if (importedPastedEntities.length === 0) {
emitScriptEvent({
type: 'importUi_IMPORT_ERROR',
reason: "No Entity has been imported."
});
} else {
if (isActive) {
selectionManager.setSelections(importedPastedEntities, this);
}
emitScriptEvent({type: 'importUi_IMPORT_CONFIRMATION'});
}
} else {
emitScriptEvent({
type: 'importUi_IMPORT_ERROR',
reason: "Import Entities has failed."
});
}
}
} else {
emitScriptEvent({
type: 'importUi_IMPORT_ERROR',
reason: "You don't have permission to create in this domain."
});
}
} else if (data.type === "importUiGoBack") {
if (location.canGoBack()) {
location.goBack();
} else {
audioFeedback.rejection();
}
} else if (data.type === "importUiGoTutorial") {
Window.location = "file:///~/serverless/tutorial.json";
} else if (data.type === "importUiGetCopiedPosition") {
if (copiedPosition !== undefined) {
emitScriptEvent({
type: 'importUi_POSITION_TO_PASTE',
position: copiedPosition
});
} else {
audioFeedback.rejection();
}
} else if (data.type === "importUiPersistData") {
importUiPersistedData = data.importUiPersistedData;
} else if (data.type === "importUiGetPersistData") {
emitScriptEvent({
type: 'importUi_LOAD_DATA',
importUiPersistedData: importUiPersistedData
});
}
};

View file

@ -0,0 +1,160 @@
/*
// importEntities.css
//
// Created by Alezia Kurdis on March 13th, 2024
// Copyright 2024 Overte e.V.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
*/
@font-face {
font-family: FiraSans-SemiBold;
src: url(../../../../../../resources/fonts/FiraSans-SemiBold.ttf), /* Windows production */
url(../../../../../../fonts/FiraSans-SemiBold.ttf); /* OSX production */
}
@font-face {
font-family: FiraSans-Regular;
src: url(../../../../../../resources/fonts/FiraSans-Regular.ttf), /* Windows production */
url(../../../../../../fonts/FiraSans-Regular.ttf); /* OSX production */
}
@font-face {
font-family: Raleway-Bold;
src: url(../../../../../../resources/fonts/Raleway-Bold.ttf), /* Windows production */
url(../../../../../../fonts/Raleway-Bold.ttf); /* OSX production */
}
html {
width: 100%;
height: 100%;
}
input[type="text"] {
font-family: FiraSans-SemiBold;
color: #BBBBBB;
background-color: #222222;
border: 0;
padding: 4px;
margin: 1px;
}
input[type="number"] {
font-family: FiraSans-SemiBold;
color: #BBBBBB;
background-color: #222222;
border: 0;
padding: 4px;
margin: 1px;
width: 90px;
}
h2 {
font-size: 18px;
color: #FFFFFF;
}
body {
background: #404040;
font-family: FiraSans-Regular;
font-size: 14px;
color: #BBBBBB;
text-decoration: none;
font-style: normal;
font-variant: normal;
text-transform: none;
}
#importAtSpecificPositionContainer {
display: none;
width: 100%;
}
#jsonUrl {
width:90%;
}
#browseBtn {
font-family: FiraSans-SemiBold;
}
#browseBtn:hover {
}
label {
font-family: FiraSans-SemiBold;
color: #DDDDDD;
}
font.red {
font-family: FiraSans-SemiBold;
color: #e83333;
}
font.green {
font-family: FiraSans-SemiBold;
color: #0db518;
}
font.blue {
font-family: FiraSans-SemiBold;
color: #447ef2;
}
#importBtn {
color: #ffffff;
background-color: #1080b8;
background: linear-gradient(#00b4ef 20%, #1080b8 100%);
font-family: Raleway-Bold;
font-size: 13px;
text-transform: uppercase;
vertical-align: top;
height: 28px;
min-width: 70px;
padding: 0 18px;
margin: 3px 3px 12px 3px;
border-radius: 5px;
border: 0;
cursor: pointer;
}
#importBtn:hover {
background: linear-gradient(#00b4ef, #00b4ef);
border: none;
}
input:focus {
outline: none;
color: #FFFFFF;
}
button:focus {
outline: none;
}
div.explicative {
width: 96%;
padding: 7px;
font-family: FiraSans-SemiBold;
font-size: 12px;
text-decoration: none;
color: #BBBBBB;
}
button.black {
font-family: Raleway-Bold;
font-size: 10px;
text-transform: uppercase;
vertical-align: top;
height: 18px;
min-width: 60px;
padding: 0 14px;
margin: 5px;
border-radius: 4px;
border: none;
color: #fff;
background-color: #000;
background: linear-gradient(#343434 20%, #000 100%);
cursor: pointer;
}
button.black:hover {
background: linear-gradient(#000, #000);
border: none;
}
#messageContainer {
font-family: FiraSans-SemiBold;
width: 100%;
}
#testContainer {
border: 1px solid #AAAAAA;
padding: 0px;
}

View file

@ -0,0 +1,77 @@
<!--
// importEntities.html
//
// Created by Alezia Kurdis on March 13th, 2024.
// Copyright 2024 Overte e.V.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-->
<html>
<head>
<title>Import Entities</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link rel="stylesheet" type="text/css" href="css/importEntities.css">
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="js/importEntitiesUi.js"></script>
</head>
<body onload="loaded();" >
<h2>Import Entities (.json)</h2>
<font class="red">* </font>URL/File (.json):<br>
<input type="text" id = "jsonUrl">&nbsp;<button id="browseBtn">...</button><br>
<br>
<table style="width: 96%;">
<tr style="vertical-align: top;">
<td style="width: 40%;">
Position:<br>
&nbsp;&nbsp;&nbsp;<input type="radio" name="importAtPosition" id="importAtAvatar" value="avatar" checked><label for="importAtAvatar">&nbsp;In front of your avatar</label><br>
&nbsp;&nbsp;&nbsp;<input type="radio" name="importAtPosition" id="importAtSpecificPosition" value="position"><label for="importAtSpecificPosition">&nbsp;At a specified Position</label><br>
</td>
<td style="width: 60%;">
<div id="importAtSpecificPositionContainer">
<font class="red">X</font> <input type="number" size="6" id = "positionX" value = "0">&nbsp;&nbsp;&nbsp;
<font class="green">Y</font> <input type="number" size="6" id = "positionY" value = "0">&nbsp;&nbsp;&nbsp;
<font class="blue">Z</font> <input type="number" size="6" id = "positionZ" value = "0"><br>
<button id="pastePositionBtn" class="black">Paste Position</button><br>
<div class="explicative">
Note: If you import a "serverless" json file, such data include positions.
It this case, the "Position" will act as an offset.
</div>
</div>
</td>
</tr>
</table>
<br>
<table style="width: 96%;">
<tr style="vertical-align: top;">
<td style="width: 30%;">
Entity Host Type:<br>
&nbsp;&nbsp;&nbsp;<input type="radio" name="entityHostType" id="entityHostTypeDomain" value="domain" checked><label for="entityHostTypeDomain">&nbsp;Domain Entities</label><br>
&nbsp;&nbsp;&nbsp;<input type="radio" name="entityHostType" id="entityHostTypeAvatar" value="avatar"><label for="entityHostTypeAvatar">&nbsp;Avatar Entities</label><br>
</td>
<td style="width: 70%;">
<div id="messageContainer"></div>
</td>
</tr>
</table>
<div style="text-align: right; width:96%;"><button id="importBtn">IMPORT</button></div>
<div id="testContainer">
<table style="width: 96%;">
<tr style="vertical-align: top;">
<td style="width: 60%;">
<div class="explicative">
For large import, it can be wise to test it in a serverless environment before doing it in your real domain.
</div>
</td>
<td style="width: 40%;">
<div style="text-align: center; width:96%;">
<button id="backBtn" class="black">&#11164; Back</button>
&nbsp;&nbsp;&nbsp;
<button id="tpTutorialBtn" class="black">Go test &#11166;</button>
</div>
</td>
</tr>
</table>
</div>
</body>
</html>

View file

@ -0,0 +1,217 @@
// importEntitiesUi.js
//
// Created by Alezia Kurdis on March 13th, 2024
// Copyright 2024 Overte e.V.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
let elJsonUrl;
let elBrowseBtn;
let elImportAtAvatar;
let elImportAtSpecificPosition;
let elImportAtSpecificPositionContainer;
let elPositionX;
let elPositionY;
let elPositionZ;
let elEntityHostTypeDomain;
let elEntityHostTypeAvatar;
let elMessageContainer;
let elImportBtn;
let elBackBtn;
let elTpTutorialBtn;
let elPastePositionBtn;
let lockUntil;
const LOCK_BTN_DELAY = 2000; //2 sec
function loaded() {
lockUntil = 0;
elJsonUrl = document.getElementById("jsonUrl");
elBrowseBtn = document.getElementById("browseBtn");
elImportAtAvatar = document.getElementById("importAtAvatar");
elImportAtSpecificPosition = document.getElementById("importAtSpecificPosition");
elImportAtSpecificPositionContainer = document.getElementById("importAtSpecificPositionContainer");
elPositionX = document.getElementById("positionX");
elPositionY = document.getElementById("positionY");
elPositionZ = document.getElementById("positionZ");
elEntityHostTypeDomain = document.getElementById("entityHostTypeDomain");
elEntityHostTypeAvatar = document.getElementById("entityHostTypeAvatar");
elMessageContainer = document.getElementById("messageContainer");
elImportBtn = document.getElementById("importBtn");
elBackBtn = document.getElementById("backBtn");
elTpTutorialBtn = document.getElementById("tpTutorialBtn");
elPastePositionBtn = document.getElementById("pastePositionBtn");
elJsonUrl.oninput = function() {
persistData();
}
elPositionX.oninput = function() {
persistData();
}
elPositionY.oninput = function() {
persistData();
}
elPositionZ.oninput = function() {
persistData();
}
elEntityHostTypeDomain.onclick = function() {
persistData();
}
elEntityHostTypeAvatar.onclick = function() {
persistData();
}
elBrowseBtn.onclick = function() {
const d = new Date();
let time = d.getTime();
if ((d.getTime() - lockUntil) > LOCK_BTN_DELAY) {
EventBridge.emitWebEvent(JSON.stringify({ "type": "importUiBrowse" }));
lockUntil = d.getTime() + LOCK_BTN_DELAY;
}
};
elImportAtAvatar.onclick = function() {
elImportAtSpecificPositionContainer.style.display = "None";
persistData();
};
elImportAtSpecificPosition.onclick = function() {
elImportAtSpecificPositionContainer.style.display = "Block";
persistData();
};
elImportBtn.onclick = function() {
const d = new Date();
let time = d.getTime();
if ((d.getTime() - lockUntil) > LOCK_BTN_DELAY) {
importJsonToWorld();
lockUntil = d.getTime() + LOCK_BTN_DELAY;
}
};
elBackBtn.onclick = function() {
const d = new Date();
let time = d.getTime();
if ((d.getTime() - lockUntil) > LOCK_BTN_DELAY) {
EventBridge.emitWebEvent(JSON.stringify({ "type": "importUiGoBack" }));
lockUntil = d.getTime() + LOCK_BTN_DELAY;
}
};
elTpTutorialBtn.onclick = function() {
const d = new Date();
let time = d.getTime();
if ((d.getTime() - lockUntil) > LOCK_BTN_DELAY) {
EventBridge.emitWebEvent(JSON.stringify({ "type": "importUiGoTutorial" }));
lockUntil = d.getTime() + LOCK_BTN_DELAY;
}
};
elPastePositionBtn.onclick = function() {
const d = new Date();
let time = d.getTime();
if ((d.getTime() - lockUntil) > LOCK_BTN_DELAY) {
EventBridge.emitWebEvent(JSON.stringify({ "type": "importUiGetCopiedPosition" }));
lockUntil = d.getTime() + LOCK_BTN_DELAY;
}
};
EventBridge.emitWebEvent(JSON.stringify({ "type": "importUiGetPersistData" }));
}
function persistData() {
let message = {
"type": "importUiPersistData",
"importUiPersistedData": {
"elJsonUrl": elJsonUrl.value,
"elImportAtAvatar": elImportAtAvatar.checked,
"elImportAtSpecificPosition": elImportAtSpecificPosition.checked,
"elPositionX": elPositionX.value,
"elPositionY": elPositionY.value,
"elPositionZ": elPositionZ.value,
"elEntityHostTypeDomain": elEntityHostTypeDomain.checked,
"elEntityHostTypeAvatar": elEntityHostTypeAvatar.checked
}
};
EventBridge.emitWebEvent(JSON.stringify(message));
}
function loadDataInUi(importUiPersistedData) {
elJsonUrl.value = importUiPersistedData.elJsonUrl;
elImportAtAvatar.checked = importUiPersistedData.elImportAtAvatar;
elImportAtSpecificPosition.checked = importUiPersistedData.elImportAtSpecificPosition;
elPositionX.value = importUiPersistedData.elPositionX;
elPositionY.value = importUiPersistedData.elPositionY;
elPositionZ.value = importUiPersistedData.elPositionZ;
elEntityHostTypeDomain.checked = importUiPersistedData.elEntityHostTypeDomain;
elEntityHostTypeAvatar.checked = importUiPersistedData.elEntityHostTypeAvatar;
if (elImportAtSpecificPosition.checked) {
elImportAtSpecificPositionContainer.style.display = "Block";
}
}
function importJsonToWorld() {
elMessageContainer.innerHTML = "";
if (elJsonUrl.value === "") {
elMessageContainer.innerHTML = "<div style = 'padding: 10px; color: #000000; background-color: #ff7700;'>ERROR: 'URL/File (.json)' is required.</div>";
return;
}
let positioningMode = getRadioValue("importAtPosition");
let entityHostType = getRadioValue("entityHostType");
if (positioningMode === "position" && (elPositionX.value === "" || elPositionY.value === "" || elPositionZ.value === "")) {
elMessageContainer.innerHTML = "<div style = 'padding: 10px; color: #000000; background-color: #ff7700;'>ERROR: 'Position' is required.</div>";
return;
}
let position = {"x": parseFloat(elPositionX.value), "y": parseFloat(elPositionY.value), "z": parseFloat(elPositionZ.value)};
let message = {
"type": "importUiImport",
"jsonURL": elJsonUrl.value,
"positioningMode": positioningMode,
"position": position,
"entityHostType": entityHostType
};
EventBridge.emitWebEvent(JSON.stringify(message));
}
function getRadioValue(objectName) {
let radios = document.getElementsByName(objectName);
let i;
let selectedValue = "";
for (i = 0; i < radios.length; i++) {
if (radios[i].checked) {
selectedValue = radios[i].value;
break;
}
}
return selectedValue;
}
EventBridge.scriptEventReceived.connect(function(message){
let messageObj = JSON.parse(message);
if (messageObj.type === "importUi_IMPORT_CONFIRMATION") {
elMessageContainer.innerHTML = "<div style = 'padding: 10px; color: #000000; background-color: #00ff00;'>IMPORT SUCCESSFUL.</div>";
} else if (messageObj.type === "importUi_IMPORT_ERROR") {
elMessageContainer.innerHTML = "<div style = 'padding: 10px; color: #FFFFFF; background-color: #ff0000;'>IMPORT ERROR: " + messageObj.reason + "</div>";
} else if (messageObj.type === "importUi_SELECTED_FILE") {
elJsonUrl.value = messageObj.file;
persistData();
} else if (messageObj.type === "importUi_POSITION_TO_PASTE") {
elPositionX.value = messageObj.position.x;
elPositionY.value = messageObj.position.y;
elPositionZ.value = messageObj.position.z;
persistData();
} else if (messageObj.type === "importUi_LOAD_DATA") {
loadDataInUi(messageObj.importUiPersistedData);
}
});