From f50101db8a717ae97e736d8e0a2df99fd861fdd0 Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Sat, 16 Mar 2024 14:04:24 -0400 Subject: [PATCH 1/2] Add "Import" Tab Add "Import" Tab --- scripts/system/create/qml/EditTabView.qml | 19 +++++++++++++++++ .../system/create/qml/EditToolsTabView.qml | 21 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/scripts/system/create/qml/EditTabView.qml b/scripts/system/create/qml/EditTabView.qml index 96e66c109ed..2db23ec659d 100644 --- a/scripts/system/create/qml/EditTabView.qml +++ b/scripts/system/create/qml/EditTabView.qml @@ -301,6 +301,22 @@ TabBar { } } + EditTabButton { + title: "IMPORT" + active: true + enabled: true + property string originalUrl: "" + + property Component visualItem: Component { + WebView { + id: advancedImportWebView + url: Qt.resolvedUrl("../importEntities/html/importEntities.html") + enabled: true + blurOnCtrlShift: false + } + } + } + function fromScript(message) { switch (message.method) { case 'selectTab': @@ -333,6 +349,9 @@ TabBar { case 'grid': editTabView.currentIndex = 3; break; + case 'import': + editTabView.currentIndex = 4; + break; default: console.warn('Attempt to switch to invalid tab:', id); } diff --git a/scripts/system/create/qml/EditToolsTabView.qml b/scripts/system/create/qml/EditToolsTabView.qml index 998c3a3aac3..1000724458a 100644 --- a/scripts/system/create/qml/EditToolsTabView.qml +++ b/scripts/system/create/qml/EditToolsTabView.qml @@ -291,6 +291,22 @@ TabBar { } } + EditTabButton { + title: "IMPORT" + active: true + enabled: true + property string originalUrl: "" + + property Component visualItem: Component { + WebView { + id: advancedImportWebView + url: Qt.resolvedUrl("../importEntities/html/importEntities.html") + enabled: true + blurOnCtrlShift: false + } + } + } + function fromScript(message) { switch (message.method) { case 'selectTab': @@ -304,7 +320,7 @@ TabBar { // Changes the current tab based on tab index or title as input function selectTab(id) { if (typeof id === 'number') { - if (id >= tabIndex.create && id <= tabIndex.grid) { + if (id >= tabIndex.create && id <= tabIndex.import) { editTabView.currentIndex = id; } else { console.warn('Attempt to switch to invalid tab:', id); @@ -320,6 +336,9 @@ TabBar { case 'grid': editTabView.currentIndex = tabIndex.grid; break; + case 'import': + editTabView.currentIndex = tabIndex.import; + break; default: console.warn('Attempt to switch to invalid tab:', id); } From a509f84a42013e0135db3f14452085fea97e98fc Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Sat, 16 Mar 2024 14:05:14 -0400 Subject: [PATCH 2/2] Add "Import" Tab Add "Import" Tab --- scripts/system/create/edit.js | 81 ++++++- .../html/css/importEntities.css | 160 +++++++++++++ .../importEntities/html/importEntities.html | 77 +++++++ .../html/js/importEntitiesUi.js | 217 ++++++++++++++++++ 4 files changed, 533 insertions(+), 2 deletions(-) create mode 100644 scripts/system/create/importEntities/html/css/importEntities.css create mode 100644 scripts/system/create/importEntities/html/importEntities.html create mode 100644 scripts/system/create/importEntities/html/js/importEntitiesUi.js diff --git a/scripts/system/create/edit.js b/scripts/system/create/edit.js index 5d3e924ccfd..1d7f4fc05e5 100644 --- a/scripts/system/create/edit.js +++ b/scripts/system/create/edit.js @@ -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 + }); } }; diff --git a/scripts/system/create/importEntities/html/css/importEntities.css b/scripts/system/create/importEntities/html/css/importEntities.css new file mode 100644 index 00000000000..61c75dabb35 --- /dev/null +++ b/scripts/system/create/importEntities/html/css/importEntities.css @@ -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; +} diff --git a/scripts/system/create/importEntities/html/importEntities.html b/scripts/system/create/importEntities/html/importEntities.html new file mode 100644 index 00000000000..a1550a642ef --- /dev/null +++ b/scripts/system/create/importEntities/html/importEntities.html @@ -0,0 +1,77 @@ + + + + Import Entities + + + + + + +

Import Entities (.json)

+ * URL/File (.json):
+  
+
+ + + + + +
+ Position:
+    
+    
+
+
+ X     + Y     + Z
+
+
+ Note: If you import a "serverless" json file, such data include positions. + It this case, the "Position" will act as an offset. +
+
+
+
+ + + + + +
+ Entity Host Type:
+    
+    
+
+
+
+
+
+ + + + + +
+
+ For large import, it can be wise to test it in a serverless environment before doing it in your real domain. +
+
+
+ +     + +
+
+
+ + diff --git a/scripts/system/create/importEntities/html/js/importEntitiesUi.js b/scripts/system/create/importEntities/html/js/importEntitiesUi.js new file mode 100644 index 00000000000..6e80c7f1738 --- /dev/null +++ b/scripts/system/create/importEntities/html/js/importEntitiesUi.js @@ -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 = "
ERROR: 'URL/File (.json)' is required.
"; + return; + } + + let positioningMode = getRadioValue("importAtPosition"); + let entityHostType = getRadioValue("entityHostType"); + + if (positioningMode === "position" && (elPositionX.value === "" || elPositionY.value === "" || elPositionZ.value === "")) { + elMessageContainer.innerHTML = "
ERROR: 'Position' is required.
"; + 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 = "
IMPORT SUCCESSFUL.
"; + } else if (messageObj.type === "importUi_IMPORT_ERROR") { + elMessageContainer.innerHTML = "
IMPORT ERROR: " + messageObj.reason + "
"; + } 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); + } +});