diff --git a/launchers/win32/.vscode/ipch/109049fa4514b214/mmap_address.bin b/launchers/win32/.vscode/ipch/109049fa4514b214/mmap_address.bin new file mode 100644 index 0000000000..862b8428b9 Binary files /dev/null and b/launchers/win32/.vscode/ipch/109049fa4514b214/mmap_address.bin differ diff --git a/launchers/win32/.vscode/ipch/83ab9744ea50c018/LAUNCHERAPP.ipch b/launchers/win32/.vscode/ipch/83ab9744ea50c018/LAUNCHERAPP.ipch new file mode 100644 index 0000000000..4ebafd2288 Binary files /dev/null and b/launchers/win32/.vscode/ipch/83ab9744ea50c018/LAUNCHERAPP.ipch differ diff --git a/launchers/win32/.vscode/ipch/83ab9744ea50c018/mmap_address.bin b/launchers/win32/.vscode/ipch/83ab9744ea50c018/mmap_address.bin new file mode 100644 index 0000000000..862b8428b9 Binary files /dev/null and b/launchers/win32/.vscode/ipch/83ab9744ea50c018/mmap_address.bin differ diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 25c330e3fe..17f2aea9a5 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -114,7 +114,7 @@ QUrl expandScriptUrl(const QUrl& rawScriptURL) { url = QUrl::fromLocalFile(fileInfo.canonicalFilePath()); QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); - if (!defaultScriptsLoc.isParentOf(url)) { + if (!defaultScriptsLoc.isParentOf(url) && defaultScriptsLoc != url) { qCWarning(scriptengine) << "Script.include() ignoring file path" << "-- outside of standard libraries: " << url.path() diff --git a/scripts/simplifiedUI/system/+android_interface/actionbar.js b/scripts/simplifiedUI/system/+android_interface/actionbar.js new file mode 100644 index 0000000000..74b3896a62 --- /dev/null +++ b/scripts/simplifiedUI/system/+android_interface/actionbar.js @@ -0,0 +1,59 @@ +"use strict"; +// +// backbutton.js +// scripts/system/+android +// +// Created by Gabriel Calero & Cristian Duarte on Apr 06, 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 +// +(function() { // BEGIN LOCAL_SCOPE + +var actionbar; +var backButton; + +var logEnabled = true; + +function printd(str) { + if (logEnabled) + print("[actionbar.js] " + str); +} + +function init() { + actionbar = new QmlFragment({ + qml: "hifi/ActionBar.qml" + }); + backButton = actionbar.addButton({ + icon: "icons/+android_interface/backward.svg", + activeIcon: "icons/+android_interface/backward.svg", + text: "", + bgOpacity: 0.0, + hoverBgOpacity: 0.0, + activeBgOpacity: 0.0 + }); + + backButton.entered.connect(onBackPressed); + backButton.clicked.connect(onBackClicked); +} + +function onBackPressed() { + Controller.triggerHapticPulseOnDevice(Controller.findDevice("TouchscreenVirtualPad"), 0.1, 40.0, 0); +} + +function onBackClicked() { + Window.openAndroidActivity("Home", false); +} + + +Script.scriptEnding.connect(function() { + if(backButton) { + backButton.entered.disconnect(onBackPressed); + backButton.clicked.disconnect(onBackClicked); + } +}); + +init(); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/+android_interface/audio.js b/scripts/simplifiedUI/system/+android_interface/audio.js new file mode 100644 index 0000000000..34dd52604a --- /dev/null +++ b/scripts/simplifiedUI/system/+android_interface/audio.js @@ -0,0 +1,71 @@ +"use strict"; +// +// audio.js +// scripts/system/ +// +// Created by Gabriel Calero & Cristian Duarte on Jan 16, 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 +// + +(function() { // BEGIN LOCAL_SCOPE + +var audiobar; +var audioButton; + +var logEnabled = false; + +function printd(str) { + if (logEnabled) + print("[audio.js] " + str); +} + +function init() { + audiobar = new QmlFragment({ + qml: "hifi/AudioBar.qml" + }); + + audioButton = audiobar.addButton({ + icon: "icons/mic-unmute-a.svg", + activeIcon: "icons/mic-mute-a.svg", + text: "", + bgOpacity: 0.0, + hoverBgOpacity: 0.0, + activeHoverBgOpacity: 0.0, + activeBgOpacity: 0.0 + }); + + onMuteToggled(); + + audioButton.clicked.connect(onMuteClicked); + audioButton.entered.connect(onMutePressed); + Audio.mutedChanged.connect(onMuteToggled); +} + +function onMuteClicked() { + Audio.muted = !Audio.muted; +} + +function onMutePressed() { + Controller.triggerHapticPulseOnDevice(Controller.findDevice("TouchscreenVirtualPad"), 0.1, 40.0, 0); +} + +function onMuteToggled() { + printd("On Mute Toggled"); + audioButton.isActive = Audio.muted; // Menu.isOptionChecked("Mute Microphone") + printd("Audio button is active: " + audioButton.isActive); +} + +Script.scriptEnding.connect(function () { + if(audioButton) { + audioButton.clicked.disconnect(onMuteClicked); + audioButton.entered.disconnect(onMutePressed); + Audio.mutedChanged.connect(onMuteToggled); + } +}); + +init(); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/+android_interface/clickWeb.js b/scripts/simplifiedUI/system/+android_interface/clickWeb.js new file mode 100644 index 0000000000..229b2b8b82 --- /dev/null +++ b/scripts/simplifiedUI/system/+android_interface/clickWeb.js @@ -0,0 +1,108 @@ +"use strict"; +// +// clickWeb.js +// scripts/system/+android +// +// Created by Gabriel Calero & Cristian Duarte on Jun 22, 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 +// + +(function() { // BEGIN LOCAL_SCOPE + +var logEnabled = false; +var touchOverlayID; +var touchEntityID; + +function printd(str) { + if (logEnabled) + print("[clickWeb.js] " + str); +} + +function intersectsWebOverlay(intersection) { + return intersection && intersection.intersects && intersection.overlayID && + Overlays.getOverlayType(intersection.overlayID) == "web3d"; +} + +function intersectsWebEntity(intersection) { + if (intersection && intersection.intersects && intersection.entityID) { + var properties = Entities.getEntityProperties(intersection.entityID, ["type", "sourceUrl"]); + return properties.type && properties.type == "Web" && properties.sourceUrl; + } + return false; +} + +function findRayIntersection(pickRay) { + // Check 3D overlays and entities. Argument is an object with origin and direction. + var overlayRayIntersection = Overlays.findRayIntersection(pickRay); + var entityRayIntersection = Entities.findRayIntersection(pickRay, true); + var isOverlayInters = intersectsWebOverlay(overlayRayIntersection); + var isEntityInters = intersectsWebEntity(entityRayIntersection); + if (isOverlayInters && + (!isEntityInters || + overlayRayIntersection.distance < entityRayIntersection.distance)) { + return { type: 'overlay', obj: overlayRayIntersection }; + } else if (isEntityInters && + (!isOverlayInters || + entityRayIntersection.distance < overlayRayIntersection.distance)) { + return { type: 'entity', obj: entityRayIntersection }; + } + return false; +} + +function touchBegin(event) { + var intersection = findRayIntersection(Camera.computePickRay(event.x, event.y)); + if (intersection && intersection.type == 'overlay') { + touchOverlayID = intersection.obj.overlayID; + touchEntityID = null; + } else if (intersection && intersection.type == 'entity') { + touchEntityID = intersection.obj.entityID; + touchOverlayID = null; + } +} + +function touchEnd(event) { + var intersection = findRayIntersection(Camera.computePickRay(event.x, event.y)); + if (intersection && intersection.type == 'overlay' && touchOverlayID == intersection.obj.overlayID) { + var propertiesToGet = {}; + propertiesToGet[overlayID] = ['url']; + var properties = Overlays.getOverlaysProperties(propertiesToGet); + if (properties[overlayID].url) { + Window.openUrl(properties[overlayID].url); + } + } else if (intersection && intersection.type == 'entity' && touchEntityID == intersection.obj.entityID) { + var properties = Entities.getEntityProperties(touchEntityID, ["sourceUrl"]); + if (properties.sourceUrl) { + Window.openUrl(properties.sourceUrl); + } + } + + touchOverlayID = null; + touchEntityID = null; +} + +function ending() { + Controller.touchBeginEvent.disconnect(touchBegin); + Controller.touchEndEvent.disconnect(touchEnd); +} + +function init() { + Controller.touchBeginEvent.connect(touchBegin); + Controller.touchEndEvent.connect(touchEnd); + + Script.scriptEnding.connect(function () { + ending(); + }); + +} + +module.exports = { + init: init, + ending: ending +} + +init(); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/+android_interface/displayNames.js b/scripts/simplifiedUI/system/+android_interface/displayNames.js new file mode 100644 index 0000000000..509d85cd2b --- /dev/null +++ b/scripts/simplifiedUI/system/+android_interface/displayNames.js @@ -0,0 +1,167 @@ +"use strict"; +// +// displayNames.js +// scripts/system/ +// +// Created by Cristian Duarte & Gabriel Calero on May 3, 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 +// +(function() { // BEGIN LOCAL_SCOPE + +var MAX_DISTANCE_PX = 20; // Should we use dp instead? +var UNKNOWN_NAME = "Unknown"; +var METERS_ABOVE_HEAD = 0.4; + +var TEXT_LINE_HEIGHT = .1; +var TEXT_MARGIN = 0.025; + +var HIDE_MS = 10000; + +var currentTouchToAnalyze = null; + +var currentlyShownAvatar = { + avatarID: null, + avatar: null, + overlay: null +}; + +var logEnabled = false; + +var hideTimer = null; + +function printd(str) { + if (logEnabled) { + print("[displayNames.js] " + str); + } +} + +function clearOverlay() { + currentlyShownAvatar.avatar = null; + if (currentlyShownAvatar.overlay) { + Overlays.editOverlay(currentlyShownAvatar.overlay, {visible: false}); + } +} + +function touchedAvatar(avatarID, avatarData) { + printd("[AVATARNAME] touchEnd FOUND " + JSON.stringify(avatarData)); + + if (hideTimer) { + Script.clearTimeout(hideTimer); + } + + // Case: touching an already selected avatar + if (currentlyShownAvatar.avatar && currentlyShownAvatar.avatarID == avatarID) { + clearOverlay(); + return; + } + + // Save currently selected avatar + currentlyShownAvatar.avatarID = avatarID; + currentlyShownAvatar.avatar = avatarData; + + if (currentlyShownAvatar.overlay == null) { + var over = Overlays.addOverlay("text3d", { + lineHeight: TEXT_LINE_HEIGHT, + color: { red: 255, green: 255, blue: 255}, + backgroundColor: {red: 0, green: 0, blue: 0}, + leftMargin: TEXT_MARGIN, + topMargin: TEXT_MARGIN, + rightMargin: TEXT_MARGIN, + bottomMargin: TEXT_MARGIN, + alpha: 0.6, + solid: true, + isFacingAvatar: true, + visible: false + }); + currentlyShownAvatar.overlay = over; + } + + var nameToShow = avatarData.displayName ? avatarData.displayName : + (avatarData.sessionDisplayName ? avatarData.sessionDisplayName : UNKNOWN_NAME); + var textSize = Overlays.textSize(currentlyShownAvatar.overlay, nameToShow); + + Overlays.editOverlay(currentlyShownAvatar.overlay, { + dimensions: { + x: textSize.width + 2 * TEXT_MARGIN, + y: TEXT_LINE_HEIGHT + 2 * TEXT_MARGIN + }, + localPosition: { x: 0, y: METERS_ABOVE_HEAD, z: 0 }, + text: nameToShow, + parentID: avatarData.sessionUUID, + parentJointIndex: avatarData.getJointIndex("Head"), + visible: true + }); + + hideTimer = Script.setTimeout(function() { + clearOverlay(); + }, HIDE_MS); +} + +function touchBegin(event) { + currentTouchToAnalyze = event; +} + +function touchEnd(event) { + if (Vec3.distance({x: event.x, y: event.y }, {x: currentTouchToAnalyze.x, y: currentTouchToAnalyze.y}) > MAX_DISTANCE_PX) { + printd("[AVATARNAME] touchEnd moved too much"); + currentTouchToAnalyze = null; + return; + } + + var pickRay = Camera.computePickRay(event.x, event.y); + var avatarRay = AvatarManager.findRayIntersection(pickRay, [], [MyAvatar.sessionUUID]) + + if (avatarRay.intersects) { + touchedAvatar(avatarRay.avatarID, AvatarManager.getAvatar(avatarRay.avatarID)); + } else { + printd("[AVATARNAME] touchEnd released outside the avatar"); + } + + currentTouchToAnalyze = null; +} + +var runAtLeastOnce = false; + +function ending() { + if (!runAtLeastOnce) { + return; + } + + Controller.touchBeginEvent.disconnect(touchBegin); + Controller.touchEndEvent.disconnect(touchEnd); + Controller.mousePressEvent.disconnect(touchBegin); + Controller.mouseReleaseEvent.disconnect(touchEnd); + + if (currentlyShownAvatar.overlay) { + Overlays.deleteOverlay(currentlyShownAvatar.overlay); + currentlyShownAvatar.overlay = null; + } + if (currentlyShownAvatar.avatar) { + currentlyShownAvatar.avatar = null; + } +} + +function init() { + Controller.touchBeginEvent.connect(touchBegin); + Controller.touchEndEvent.connect(touchEnd); + Controller.mousePressEvent.connect(touchBegin); + Controller.mouseReleaseEvent.connect(touchEnd); + + Script.scriptEnding.connect(function () { + ending(); + }); + + runAtLeastOnce = true; +} + +module.exports = { + init: init, + ending: ending +} + +//init(); // Enable to use in desktop as a standalone + +}()); // END LOCAL_SCOPE \ No newline at end of file diff --git a/scripts/simplifiedUI/system/+android_interface/modes.js b/scripts/simplifiedUI/system/+android_interface/modes.js new file mode 100644 index 0000000000..f495af3bba --- /dev/null +++ b/scripts/simplifiedUI/system/+android_interface/modes.js @@ -0,0 +1,124 @@ +"use strict"; +// +// modes.js +// scripts/system/ +// +// Created by Gabriel Calero & Cristian Duarte on Jan 23, 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 +// +(function() { // BEGIN LOCAL_SCOPE + +var modeButton; +var currentMode; +var barQml; + +var SETTING_CURRENT_MODE_KEY = 'Android/Mode'; +var MODE_VR = "VR", MODE_RADAR = "RADAR", MODE_MY_VIEW = "MY VIEW"; +var DEFAULT_MODE = MODE_MY_VIEW; +var nextMode = {}; +nextMode[MODE_RADAR]=MODE_MY_VIEW; +nextMode[MODE_MY_VIEW]=MODE_RADAR; +var modeLabel = {}; +modeLabel[MODE_RADAR]="TOP VIEW"; +modeLabel[MODE_MY_VIEW]="MY VIEW"; + +var logEnabled = false; +var radar = Script.require('./radar.js'); +var uniqueColor = Script.require('./uniqueColor.js'); +var displayNames = Script.require('./displayNames.js'); +var clickWeb = Script.require('./clickWeb.js'); + +function printd(str) { + if (logEnabled) { + print("[modes.js] " + str); + } +} + +function init() { + radar.setUniqueColor(uniqueColor); + radar.init(); + + barQml = new QmlFragment({ + qml: "hifi/modesbar.qml" + }); + modeButton = barQml.addButton({ + icon: "icons/myview-a.svg", + activeBgOpacity: 0.0, + hoverBgOpacity: 0.0, + activeHoverBgOpacity: 0.0, + text: "MODE", + height:240, + bottomMargin: 16, + textSize: 38, + fontFamily: "Raleway", + fontBold: true + + }); + + switchToMode(getCurrentModeSetting()); + + modeButton.entered.connect(modeButtonPressed); + modeButton.clicked.connect(modeButtonClicked); +} + +function shutdown() { + modeButton.entered.disconnect(modeButtonPressed); + modeButton.clicked.disconnect(modeButtonClicked); +} + +function modeButtonPressed() { + Controller.triggerHapticPulseOnDevice(Controller.findDevice("TouchscreenVirtualPad"), 0.1, 40.0, 0); +} + +function modeButtonClicked() { + switchToMode(nextMode[currentMode]); +} + +function saveCurrentModeSetting(mode) { + Settings.setValue(SETTING_CURRENT_MODE_KEY, mode); +} + +function getCurrentModeSetting(mode) { + return Settings.getValue(SETTING_CURRENT_MODE_KEY, DEFAULT_MODE); +} + +function switchToMode(newMode) { + // before leaving radar mode + if (currentMode == MODE_RADAR) { + radar.endRadarMode(); + } + currentMode = newMode; + modeButton.text = modeLabel[currentMode]; + + saveCurrentModeSetting(currentMode); + + if (currentMode == MODE_RADAR) { + radar.startRadarMode(); + displayNames.ending(); + clickWeb.ending(); + } else if (currentMode == MODE_MY_VIEW) { + // nothing to do yet + displayNames.init(); + clickWeb.init(); + } else { + printd("Unknown view mode " + currentMode); + } + +} + +function sendToQml(o) { + if(barQml) { + barQml.sendToQml(o); + } +} + +Script.scriptEnding.connect(function () { + shutdown(); +}); + +init(); + +}()); // END LOCAL_SCOPE \ No newline at end of file diff --git a/scripts/simplifiedUI/system/+android_interface/radar.js b/scripts/simplifiedUI/system/+android_interface/radar.js new file mode 100644 index 0000000000..1cbe721ad0 --- /dev/null +++ b/scripts/simplifiedUI/system/+android_interface/radar.js @@ -0,0 +1,1217 @@ +"use strict"; +// +// radar.js +// scripts/system/+android/ +// +// Created by Cristian Duarte & Gabriel Calero on 31 Jan 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 +// + +var radarModeInterface = {}; + +var logEnabled = false; +function printd(str) { + if (logEnabled) { + print("[radar.js] " + str); + } +} + +var radar = false; +var RADAR_HEIGHT_INIT_DELTA = 10; +var radarHeight = MyAvatar.position.y + RADAR_HEIGHT_INIT_DELTA; // camera position (absolute y) +var tablet; + +var RADAR_CAMERA_OFFSET = -1; // 1 meter below the avatar +var ABOVE_GROUND_DROP = 2; +var MOVE_BY = 1; + +// Swipe/Drag vars +var PINCH_INCREMENT_FIRST = 0.4; // 0.1 meters zoom in - out +var PINCH_INCREMENT = 0.4; // 0.1 meters zoom in - out +var RADAR_HEIGHT_MAX_PLUS_AVATAR = 80; +var RADAR_HEIGHT_MIN_PLUS_AVATAR = 2; +var RADAR_CAMERA_DISTANCE_TO_ICONS = 1.5; // Icons are near the camera to prevent the LOD manager dismissing them +var RADAR_ICONS_APPARENT_DISTANCE_TO_AVATAR_BASE = 1; // How much above the avatar base should the icon appear +var AVATAR_DISPLAY_NAME_HEIGHT = 106; +var AVATAR_DISPLAY_NAME_CHAR_WIDTH = 48; +var AVATAR_DISPLAY_NAME_FONT_SIZE = 50; +var lastDragAt; +var lastDeltaDrag; + +var uniqueColor; + +function moveTo(position) { + if (radar) { + MyAvatar.goToLocation(position, false); + Camera.position = { + x : position.x, + y : radarHeight, + z : position.z + }; + } +} + +function keyPressEvent(event) { + if (radar) { + switch (event.text) { + case "UP": + moveTo(Vec3.sum(MyAvatar.position, { + x : 0.0, + y : 0, + z : -1 * MOVE_BY + })); + break; + case "DOWN": + moveTo(Vec3.sum(MyAvatar.position, { + x : 0, + y : 0, + z : MOVE_BY + })); + break; + case "LEFT": + moveTo(Vec3.sum(MyAvatar.position, { + x : -1 * MOVE_BY, + y : 0, + z : 0 + })); + break; + case "RIGHT": + moveTo(Vec3.sum(MyAvatar.position, { + x : MOVE_BY, + y : 0, + z : 0 + })); + break; + } + } +} + +function toggleRadarMode() { + if (radar) { + endRadar(); + } else { + startRadar(); + } +} + +function fakeDoubleTap(event) { + // CLD - temporarily disable toggling mode through double tap + // * As we have a new UI for toggling between modes, it may be discarded + // completely in the future. + // toggleRadarMode(); + teleporter.dragTeleportUpdate(event); + teleporter.dragTeleportRelease(event); +} + +var DOUBLE_TAP_TIME = 300; +var fakeDoubleTapStart = Date.now(); +var touchEndCount = 0; + +/* + * Counts touchEnds and if there were 2 in the DOUBLE_TAP_TIME lapse, it + * triggers a fakeDoubleTap and returns true. Otherwise, returns false (no + * double tap yet) + */ +function analyzeDoubleTap(event) { + var fakeDoubleTapEnd = Date.now(); + var elapsed = fakeDoubleTapEnd - fakeDoubleTapStart; + if (elapsed > DOUBLE_TAP_TIME) { + touchEndCount = 0; + } + + // if this is our first "up" then record time so we can + // later determine if second "up" is a double tap + if (touchEndCount == 0) { + fakeDoubleTapStart = Date.now(); + } + touchEndCount++; + + if (touchEndCount >= 2) { + var fakeDoubleTapEnd = Date.now(); + var elapsed = fakeDoubleTapEnd - fakeDoubleTapStart; + printd("-- fakeDoubleTapEnd:" + fakeDoubleTapEnd + "-- elapsed:" + + elapsed) + if (elapsed <= DOUBLE_TAP_TIME) { + touchEndCount = 0; + fakeDoubleTap(event); + return true; // don't do the normal touch end processing + } + + touchEndCount = 0; + } + return false; +} + +function touchEnd(event) { + printd("touchEnd received " + JSON.stringify(event)); + // Clean up touch variables + lastDragAt = null; + lastDeltaDrag = null; + touchStartingCoordinates = null; // maybe in special cases it should be + // setup later? + startedDraggingCamera = false; + prevTouchPinchRadius = null; + draggingCamera = false; + + if (movingCamera) { + // if camera was indeed moving, we should not further process, it was + // just dragging + movingCamera = false; + dragModeFunc = null; + return; + } + + // teleport release analysis + if (teleporter && teleporter.dragTeleportUpdate == dragModeFunc) { + teleporter.dragTeleportRelease(event); + dragModeFunc = null; + return; + } + dragModeFunc = null; + + // if pinching or moving is still detected, cancel + if (event.isPinching) { + printd("touchEnd fail because isPinching"); + return; + } + if (event.isPinchOpening) { + printd("touchEnd fail because isPinchingOpening"); + return; + } + if (event.isMoved) { + printd("touchEnd fail because isMoved"); + return; + } + + if (analyzeDoubleTap(event)) + return; // double tap detected, finish + +} + +/** + * Polyfill for sign(x) + */ +if (!Math.sign) { + Math.sign = function(x) { + // If x is NaN, the result is NaN. + // If x is -0, the result is -0. + // If x is +0, the result is +0. + // If x is negative and not -0, the result is -1. + // If x is positive and not +0, the result is +1. + x = +x; // convert to a number + if (x === 0 || isNaN(x)) { + return Number(x); + } + return x > 0 ? 1 : -1; + }; +} + +/******************************************************************************* + * Line and Plane intersection methods + ******************************************************************************/ + +/** + * findLinePlaneIntersection Given points p {x: y: z:} and q that define a line, + * and the plane of formula ax+by+cz+d = 0, returns the intersection point or + * null if none. + */ +function findLinePlaneIntersection(p, q, a, b, c, d) { + return findLinePlaneIntersectionCoords(p.x, p.y, p.z, q.x, q.y, q.z, a, b, + c, d); +} + +/** + * findLineToHeightIntersection Given points p {x: y: z:} and q that define a + * line, and a planeY value that defines a plane paralel to 'the floor' xz + * plane, returns the intersection to that plane or null if none. + */ +function findLineToHeightIntersection(p, q, planeY) { + return findLinePlaneIntersection(p, q, 0, 1, 0, -planeY); +} + +/** + * findLinePlaneIntersectionCoords (to avoid requiring unnecessary + * instantiation) Given points p with px py pz and q that define a line, and the + * plane of formula ax+by+cz+d = 0, returns the intersection point or null if + * none. + */ +function findLinePlaneIntersectionCoords(px, py, pz, qx, qy, qz, a, b, c, d) { + var tDenom = a * (qx - px) + b * (qy - py) + c * (qz - pz); + if (tDenom == 0) + return null; + + var t = -(a * px + b * py + c * pz + d) / tDenom; + + return { + x : (px + t * (qx - px)), + y : (py + t * (qy - py)), + z : (pz + t * (qz - pz)) + }; +} + +/** + * findLineToHeightIntersection Given points p with px py pz and q that define a + * line, and a planeY value that defines a plane paralel to 'the floor' xz + * plane, returns the intersection to that plane or null if none. + */ +function findLineToHeightIntersectionCoords(px, py, pz, qx, qy, qz, planeY) { + return findLinePlaneIntersectionCoords(px, py, pz, qx, qy, qz, 0, 1, 0, + -planeY); +} + +function findRayIntersection(pickRay) { + // Check 3D overlays and entities. Argument is an object with origin and + // direction. + var result = Overlays.findRayIntersection(pickRay); + if (!result.intersects) { + result = Entities.findRayIntersection(pickRay, true); + } + return result; +} + +/** + * Given a 2d point (x,y) this function returns the intersection (x, y, z) of + * the computedPickRay for that point with the plane y = py + */ +function computePointAtPlaneY(x, y, py) { + var ray = Camera.computePickRay(x, y); + var p1 = ray.origin; + var p2 = Vec3.sum(p1, Vec3.multiply(ray.direction, 1)); + return findLineToHeightIntersectionCoords(p1.x, p1.y, p1.z, p2.x, p2.y, + p2.z, py); +} + +/******************************************************************************* + * + ******************************************************************************/ + +var touchStartingCoordinates = null; + +var KEEP_PRESSED_FOR_TELEPORT_MODE_TIME = 750; +var touchBeginTime; + +function touchBegin(event) { + var coords = { + x : event.x, + y : event.y + }; + touchStartingCoordinates = coords; + touchBeginTime = Date.now(); +} + +var startedDraggingCamera = false; // first time +var draggingCamera = false; // is trying +var movingCamera = false; // definitive + +var MIN_DRAG_DISTANCE_TO_CONSIDER = 100; // distance by axis, not real + // distance + +var prevTouchPinchRadius = null; + +function pinchUpdate(event) { + if (!event.isMoved) + return; + if (event.radius <= 0) + return; + + // pinch management + var avatarY = MyAvatar.position.y; + var pinchIncrement; + if (!!prevTouchPinchRadius) { + // no prev value + pinchIncrement = PINCH_INCREMENT + * Math.abs(event.radius - prevTouchPinchRadius) * 0.1; + } else { + pinchIncrement = PINCH_INCREMENT_FIRST; + } + + if (event.isPinching) { + if (radarHeight + pinchIncrement > RADAR_HEIGHT_MAX_PLUS_AVATAR + + avatarY) { + radarHeight = RADAR_HEIGHT_MAX_PLUS_AVATAR + avatarY; + } else { + radarHeight += pinchIncrement; + } + } else if (event.isPinchOpening) { + if (radarHeight - pinchIncrement < RADAR_HEIGHT_MIN_PLUS_AVATAR + + avatarY) { + radarHeight = RADAR_HEIGHT_MIN_PLUS_AVATAR + avatarY; + } else { + radarHeight -= pinchIncrement; + } + } + + Camera.position = { + x : Camera.position.x, + y : radarHeight, + z : Camera.position.z + }; + + if (!draggingCamera) { + startedDraggingCamera = true; + draggingCamera = true; + } + + prevTouchPinchRadius = event.radius; +} + +function isInsideSquare(coords0, coords1, halfside) { + return coords0 != undefined && coords1 != undefined && + Math.abs(coords0.x - coords1.x) <= halfside + && Math.abs(coords0.y - coords1.y) <= halfside; +} + +function dragScrollUpdate(event) { + if (!event.isMoved) + return; + + // drag management + var pickRay = Camera.computePickRay(event.x, event.y); + var dragAt = Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, + radarHeight - MyAvatar.position.y)); + + if (lastDragAt === undefined || lastDragAt === null) { + lastDragAt = dragAt; + return; + } + + var deltaDrag = { + x : (lastDragAt.x - dragAt.x), + y : 0, + z : (lastDragAt.z - dragAt.z) + }; + + lastDragAt = dragAt; + if (lastDeltaDrag === undefined || lastDeltaDrag === null) { + lastDeltaDrag = deltaDrag; + return; + } + + if (!draggingCamera) { + startedDraggingCamera = true; + draggingCamera = true; + } else { + if (!movingCamera) { + if (!isInsideSquare(touchStartingCoordinates, event, + MIN_DRAG_DISTANCE_TO_CONSIDER)) { + movingCamera = true; + } + } + + if (movingCamera) { + if (Math.sign(deltaDrag.x) == Math.sign(lastDeltaDrag.x) + && Math.sign(deltaDrag.z) == Math.sign(lastDeltaDrag.z)) { + // Process movement if direction of the movement is the same + // than the previous frame + // process delta + var moveCameraTo = Vec3.sum(Camera.position, deltaDrag); + // move camera + Camera.position = moveCameraTo; + } else { + // Do not move camera if it's changing direction in this case, + // wait until the next direction confirmation.. + } + lastDeltaDrag = deltaDrag; // save last + } + } +} + +/******************************************************************************* + * Teleport feature + ******************************************************************************/ + +function Teleporter() { + + var SURFACE_DETECTION_FOR_TELEPORT = true; // true if uses teleport.js + // similar logic to detect + // surfaces. false if uses plain + // teleport to avatar same + // height. + + var TELEPORT_TARGET_MODEL_URL = Script + .resolvePath("../assets/models/teleport-destination.fbx"); + var TELEPORT_TOO_CLOSE_MODEL_URL = Script + .resolvePath("../assets/models/teleport-cancel.fbx"); + + var TELEPORT_MODEL_DEFAULT_DIMENSIONS = { + x : 0.10, + y : 0.00001, + z : 0.10 + }; + + var teleportOverlay = Overlays.addOverlay("model", { + url : TELEPORT_TARGET_MODEL_URL, + dimensions : TELEPORT_MODEL_DEFAULT_DIMENSIONS, + orientation : Quat.fromPitchYawRollDegrees(0, 180, 0), + visible : false + }); + + var teleportCancelOverlay = Overlays.addOverlay("model", { + url : TELEPORT_TOO_CLOSE_MODEL_URL, + dimensions : TELEPORT_MODEL_DEFAULT_DIMENSIONS, + orientation : Quat.fromPitchYawRollDegrees(0, 180, 0), + visible : false + }); + + var TELEPORT_COLOR = { + red : 0, + green : 255, + blue : 255 + }; + var TELEPORT_CANCEL_COLOR = { + red : 255, + green : 255, + blue : 0 + }; + + var teleportLine = Overlays.addOverlay("line3d", { + start : { + x : 0, + y : 0, + z : 0 + }, + end : { + x : 0, + y : 0, + z : 0 + }, + color : TELEPORT_COLOR, + alpha : 1, + lineWidth : 2, + dashed : false, + visible : false + }); + + // code from teleport.js + var TELEPORT_TARGET = { + NONE : 'none', // Not currently targetting anything + INVISIBLE : 'invisible', // The current target is an invvsible + // surface + INVALID : 'invalid', // The current target is invalid (wall, ceiling, + // etc.) + SURFACE : 'surface', // The current target is a valid surface + SEAT : 'seat', // The current target is a seat + } + + var TELEPORT_CANCEL_RANGE = 1; + var teleportTargetType = TELEPORT_TARGET.NONE; + + function parseJSON(json) { + try { + return JSON.parse(json); + } catch (e) { + return undefined; + } + } + + /* + * Enhanced with intersection with terrain instead of using current avatar y + * position if SURFACE_DETECTION_FOR_TELEPORT is true + */ + function computeDestination(touchEventPos, avatarPosition, cameraPosition, + radarH) { + if (SURFACE_DETECTION_FOR_TELEPORT) { + var pickRay = Camera.computePickRay(touchEventPos.x, + touchEventPos.y); + printd("newTeleportDetect - pickRay " + JSON.stringify(pickRay)); + var destination = Entities.findRayIntersection(pickRay, true, [], + [], false, true); + printd("newTeleportDetect - destination " + + JSON.stringify(destination)); + return destination; + } else { + var pickRay = Camera.computePickRay(touchEventPos.x, + touchEventPos.y); + var pointingAt = Vec3.sum(pickRay.origin, Vec3.multiply( + pickRay.direction, radarH)); + var destination = { + x : pointingAt.x, + y : avatarPosition.y, + z : pointingAt.z + }; + return destination; + } + } + + function renderTeleportOverlays(destination) { + var overlayPosition = findLineToHeightIntersection(destination, + Camera.position, Camera.position.y + - RADAR_CAMERA_DISTANCE_TO_ICONS); + printd("[newTeleport] TELEPORT ! render overlay at " + + JSON.stringify(overlayPosition)); + + // CLD note Oct 11, 2017 + // Version of teleport.js 3c109f294f88ba7573bd1221f907f2605893c509 + // doesn't allow invisible surfaces, let's allow it for now + if (teleportTargetType == TELEPORT_TARGET.SURFACE + || teleportTargetType == TELEPORT_TARGET.INVISIBLE) { + Overlays.editOverlay(teleportOverlay, { + visible : true, + position : overlayPosition + }); + Overlays.editOverlay(teleportCancelOverlay, { + visible : false + }); + Overlays.editOverlay(teleportLine, { + start : MyAvatar.position, + end : destination, + color : TELEPORT_COLOR, + visible : true + }); + } else if (teleportTargetType == TELEPORT_TARGET.INVALID) { + Overlays.editOverlay(teleportOverlay, { + visible : false + }); + Overlays.editOverlay(teleportCancelOverlay, { + visible : true, + position : overlayPosition + }); + Overlays.editOverlay(teleportLine, { + start : MyAvatar.position, + end : destination, + color : TELEPORT_CANCEL_COLOR, + visible : true + }); + } else { // TELEPORT_TARGET:NONE? + Overlays.editOverlay(teleportOverlay, { + visible : false + }); + Overlays.editOverlay(teleportCancelOverlay, { + visible : false + }); + Overlays.editOverlay(teleportLine, { + visible : false + }); + } + } + + var BORDER_DISTANCE_PX = 100; + var border_top = 0; + var border_left = 0; + var border_right = Window.innerWidth; + var border_bottom = Window.innerHeight; + + function moveOnBorders(event) { + var xDelta = 0; + var zDelta = 0; + + if (event.y <= border_top + BORDER_DISTANCE_PX) { + zDelta = -0.1; + } else if (event.y >= border_bottom - BORDER_DISTANCE_PX) { + zDelta = 0.1; + } + if (event.x <= border_left + BORDER_DISTANCE_PX) { + xDelta = -0.1; + } else if (event.x >= border_right - BORDER_DISTANCE_PX) { + xDelta = 0.1; + } + if (xDelta == 0 && zDelta == 0) { + draggingCamera = false; + return; + } + + + Camera.position = Vec3.sum(Camera.position, { + x : xDelta, + y : 0, + z : zDelta + }); + draggingCamera = true; + } + + // When determininig whether you can teleport to a location, the normal of + // the + // point that is being intersected with is looked at. If this normal is more + // than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from <0, 1, 0> (straight up), + // then + // you can't teleport there. + const MAX_ANGLE_FROM_UP_TO_TELEPORT = 70; + function getTeleportTargetType(intersection) { + if (SURFACE_DETECTION_FOR_TELEPORT) { + if (!intersection.intersects) { + return TELEPORT_TARGET.NONE; + } + var props = Entities.getEntityProperties(intersection.entityID, [ + 'userData', 'visible' ]); + var data = parseJSON(props.userData); + if (data !== undefined && data.seat !== undefined) { + return TELEPORT_TARGET.SEAT; + } + + if (!props.visible) { + return TELEPORT_TARGET.INVISIBLE; + } + + var surfaceNormal = intersection.surfaceNormal; + var adj = Math.sqrt(surfaceNormal.x * surfaceNormal.x + + surfaceNormal.z * surfaceNormal.z); + var angleUp = Math.atan2(surfaceNormal.y, adj) * (180 / Math.PI); + + if (angleUp < (90 - MAX_ANGLE_FROM_UP_TO_TELEPORT) + || angleUp > (90 + MAX_ANGLE_FROM_UP_TO_TELEPORT) + || Vec3.distance(MyAvatar.position, + intersection.intersection) <= TELEPORT_CANCEL_RANGE) { + return TELEPORT_TARGET.INVALID; + } else { + return TELEPORT_TARGET.SURFACE; + } + } else { + var destination = intersection; + if (Vec3.distance(MyAvatar.position, destination) <= TELEPORT_CANCEL_RANGE) { + return TELEPORT_TARGET.INVALID; + } else { + return TELEPORT_TARGET.SURFACE; + } + } + } + ; + + function moveToFromEvent(event) { + var destination = computeDestination(event, MyAvatar.position, + Camera.position, radarHeight); + moveTo(SURFACE_DETECTION_FOR_TELEPORT ? Vec3.sum( + destination.intersection, { + y : 1 + }) : destination); + return true; + } + + return { + dragTeleportBegin : function(event) { + printd("[newTeleport] TELEPORT began"); + var overlayDimensions = teleportIconModelDimensions(MyAvatar.position.y); + // var destination = computeDestination(event, MyAvatar.position, + // Camera.position, radarHeight); + // Dimension teleport and cancel overlays (not show them yet) + Overlays.editOverlay(teleportOverlay, { + dimensions : overlayDimensions + }); + Overlays.editOverlay(teleportCancelOverlay, { + dimensions : overlayDimensions + }); + // Position line + Overlays.editOverlay(teleportLine, { + visible : true, + start : 0, + end : 0 + }); + }, + + dragTeleportUpdate : function(event) { + // if in border, move camera + moveOnBorders(event); + + var destination = computeDestination(event, MyAvatar.position, + Camera.position, radarHeight); + + teleportTargetType = getTeleportTargetType(destination); + renderTeleportOverlays(SURFACE_DETECTION_FOR_TELEPORT ? destination.intersection + : destination); + }, + + dragTeleportRelease : function(event) { + printd("[newTeleport] TELEPORT released at " + + JSON.stringify(event)); + // CLD note Oct 11, 2017 + // Version of teleport.js 3c109f294f88ba7573bd1221f907f2605893c509 + // doesn't allow invisible surfaces, let's allow it for now + if (teleportTargetType == TELEPORT_TARGET.SURFACE + || teleportTargetType == TELEPORT_TARGET.INVISIBLE) { + moveToFromEvent(event); + } + teleportTargetType = TELEPORT_TARGET.NONE; + + Overlays.editOverlay(teleportOverlay, { + visible : false + }); + Overlays.editOverlay(teleportLine, { + visible : false + }); + Overlays.editOverlay(teleportCancelOverlay, { + visible : false + }); + } + }; + +} + +var teleporter = Teleporter(); + +/******************************************************************************* + * + ******************************************************************************/ + +var dragModeFunc = null; // by default is nothing + +function oneFingerTouchUpdate(event) { + if (dragModeFunc) { + dragModeFunc(event); + } else { + if (!isInsideSquare(touchStartingCoordinates, event, + MIN_DRAG_DISTANCE_TO_CONSIDER)) { + dragModeFunc = dragScrollUpdate; + dragModeFunc(event); + } else { + var now = Date.now(); // check time + if (now - touchBeginTime >= KEEP_PRESSED_FOR_TELEPORT_MODE_TIME) { + teleporter.dragTeleportBegin(event); + dragModeFunc = teleporter.dragTeleportUpdate; + dragModeFunc(event); + } else { + // not defined yet, let's wait for time or movement to happen + } + } + } +} + +function touchUpdate(event) { + if (event.isPinching || event.isPinchOpening) { + pinchUpdate(event); + } else { + oneFingerTouchUpdate(event); + } +} + +/******************************************************************************* + * Avatar cache structure for showing avatars markers + ******************************************************************************/ + +// by QUuid +var avatarsData = {}; +var avatarsIcons = []; // a parallel list of icons (overlays) to easily run + // through +var avatarsNames = []; // a parallel list of names (overlays) to easily run + // through + +function getAvatarIconForUser(uid) { + var color = uniqueColor.getColor(uid); + if (color.charAt(0) == '#') { + color = color.substring(1, color.length); + } + // FIXME: this is a temporary solution until we can use circle3d with + // lineWidth + return Script.resolvePath("assets/images/circle-" + color + ".svg"); +} + +var avatarIconDimensionsVal = { + x : 0, + y : 0, + z : 0.00001 +}; +function avatarIconPlaneDimensions() { + // given the current height, give a size + var xy = -0.003531 * (radarHeight - MyAvatar.position.y) + 0.1; + avatarIconDimensionsVal.x = Math.abs(xy); + avatarIconDimensionsVal.y = Math.abs(xy); + // reuse object + return avatarIconDimensionsVal; +} + +function currentOverlayIconForAvatar(QUuid) { + if (avatarsData[QUuid] != undefined) { + return avatarsData[QUuid].icon; + } else { + return null; + } +} + +function currentOverlayNameForAvatar(QUuid) { + if (avatarsData[QUuid] != undefined) { + return avatarsData[QUuid].name; + } else { + return null; + } +} + +function saveAvatarData(QUuid) { + if (QUuid == null) + return; + var avat = AvatarList.getAvatar(QUuid); + printd("avatar added save avatar " + QUuid); + + if (!avat) + return; + + if (avatarsData[QUuid] != undefined) { + avatarsData[QUuid].position = avat.position; + } else { + var avatarIcon = Overlays.addOverlay("circle3d", { + color: uniqueColor.convertHexToRGB(uniqueColor.getColor(QUuid)), + dimensions: ICON_ENTITY_DEFAULT_DIMENSIONS, + rotation: Quat.fromPitchYawRollDegrees(90, 0, 0), + innerRadius: 1.8, + outerRadius: 2, + isSolid: true, + visible: false + }); + + var needRefresh = !avat || !avat.displayName; + var displayName = avat && avat.displayName ? avat.displayName + : "Unknown"; + var textWidth = displayName.length * AVATAR_DISPLAY_NAME_CHAR_WIDTH; + var avatarName = Overlays.addOverlay("text", { + width: textWidth, + height: AVATAR_DISPLAY_NAME_HEIGHT, + color: { red: 255, green: 255, blue: 255}, + backgroundAlpha: 0.0, + textRaiseColor: { red: 0, green: 0, blue: 0}, + font: {size: AVATAR_DISPLAY_NAME_FONT_SIZE, bold: true}, + visible: false, + text: displayName, + textAlignCenter: true + }); + avatarsIcons.push(avatarIcon); + avatarsNames.push(avatarName); + avatarsData[QUuid] = { + position : avat.position, + icon : avatarIcon, + name : avatarName, + textWidth : textWidth, + needRefresh : needRefresh + }; + } +} + +function removeAvatarData(QUuid) { + if (QUuid == null) + return; + + var itsOverlay = currentOverlayIconForAvatar(QUuid); + if (itsOverlay != null) { + Overlays.deleteOverlay(itsOverlay); + } + var itsNameOverlay = currentOverlayNameForAvatar(QUuid); + if (itsNameOverlay != null) { + Overlays.deleteOverlay(itsNameOverlay); + } + + var idx = avatarsIcons.indexOf(itsOverlay); + avatarsIcons.splice(idx, 1); + idx = avatarsNames.indexOf(itsNameOverlay); + avatarsNames.splice(idx, 1); + + delete avatarsData[QUuid]; +} + +function saveAllOthersAvatarsData() { + var avatarIds = AvatarList.getAvatarIdentifiers(); + var len = avatarIds.length; + for (var i = 0; i < len; i++) { + if (avatarIds[i]) { + saveAvatarData(avatarIds[i]); + } + } +} + +function avatarAdded(QUuid) { + printd("avatar added " + QUuid);// + " at " + + // JSON.stringify(AvatarList.getAvatar(QUuid).position)); + saveAvatarData(QUuid); +} + +function avatarRemoved(QUuid) { + printd("avatar removed " + QUuid); + removeAvatarData(QUuid); +} + +/******************************************************************************* + * Avatar Icon/Markers rendering + ******************************************************************************/ +var myAvatarIcon; +var myAvatarName; +function distanceForCameraHeight(h) { + if (h < 30) return 1; + if (h < 40) return 2; + if (h < 50) return 2.5; + return 5; +} +function renderMyAvatarIcon() { + var commonY = Camera.position.y - distanceForCameraHeight(Camera.position.y); + var iconPos = findLineToHeightIntersectionCoords( MyAvatar.position.x, + MyAvatar.position.y + RADAR_ICONS_APPARENT_DISTANCE_TO_AVATAR_BASE, + MyAvatar.position.z, + Camera.position.x, Camera.position.y, Camera.position.z, + commonY); + if (!iconPos) { printd("avatarmy icon pos null"); return;} + var iconDimensions = avatarIconPlaneDimensions(); + + var avatarPos = MyAvatar.position; + var cameraPos = Camera.position; + var borderPoints = [ + computePointAtPlaneY(0, 0, commonY), + computePointAtPlaneY(Window.innerWidth, Window.innerHeight, commonY) ]; + + var p1 = findLineToHeightIntersectionCoords(avatarPos.x, avatarPos.y, + avatarPos.z, cameraPos.x, cameraPos.y, cameraPos.z, commonY); + var x = (p1.x - borderPoints[0].x) * (Window.innerWidth) + / (borderPoints[1].x - borderPoints[0].x); + var y = (p1.z - borderPoints[0].z) * (Window.innerHeight) + / (borderPoints[1].z - borderPoints[0].z); + + if (!myAvatarIcon && MyAvatar.SELF_ID) { + myAvatarIcon = Overlays.addOverlay("circle3d", { + color: uniqueColor.convertHexToRGB(uniqueColor.getColor(MyAvatar.SELF_ID)), + dimensions: ICON_ENTITY_DEFAULT_DIMENSIONS, + rotation: Quat.fromPitchYawRollDegrees(90, 0, 0), + innerRadius: 1.8, + outerRadius: 2, + isSolid: true, + visible: false + }); + } + + if (!myAvatarName) { + myAvatarName = Overlays.addOverlay("text", { + width: 100, + height: AVATAR_DISPLAY_NAME_HEIGHT, + textAlignCenter: true, + color: { red: 255, green: 255, blue: 255}, + backgroundAlpha: 0.0, + font: {size: AVATAR_DISPLAY_NAME_FONT_SIZE, bold: true}, + textRaiseColor: { red: 0, green: 0, blue: 0}, + visible: false, + text: "Me" + }); + } + + if (myAvatarIcon) { + Overlays.editOverlay(myAvatarIcon, { + visible : true, + dimensions : iconDimensions, + position : iconPos + }); + + } + var textSize = (14 + (iconDimensions.y - 0.03) * 15 / 0.06); + + Overlays.editOverlay(myAvatarName, { + visible : true, + x : x - 18 + (iconDimensions.y - 0.03) * 2 / 0.06, + y : y + iconDimensions.y * 550, + font : { + size : textSize, + bold : true + }, + }); + +} + +function hideAllAvatarIcons() { + var len = avatarsIcons.length; + for (var i = 0; i < len; i++) { + Overlays.editOverlay(avatarsIcons[i], { + visible : false + }); + } + len = avatarsNames.length; + for (var j = 0; j < len; j++) { + Overlays.editOverlay(avatarsNames[j], { + visible : false + }); + } + if (myAvatarIcon) { + Overlays.editOverlay(myAvatarIcon, { + visible : false + }); + } + Overlays.editOverlay(myAvatarName, { + visible : false + }) +} + +function renderAllOthersAvatarIcons() { + var avatarPos; + var iconDimensions = avatarIconPlaneDimensions(); + var commonY = Camera.position.y - distanceForCameraHeight(Camera.position.y); + var borderPoints = [ + computePointAtPlaneY(0, 0, commonY), + computePointAtPlaneY(Window.innerWidth, Window.innerHeight, commonY) ]; + + for ( var QUuid in avatarsData) { + if (avatarsData.hasOwnProperty(QUuid)) { + if (AvatarList.getAvatar(QUuid) != null) { + avatarPos = AvatarList.getAvatar(QUuid).position; + + var cameraPos = Camera.position; + var p1 = findLineToHeightIntersectionCoords(avatarPos.x, avatarPos.y, avatarPos.z, + cameraPos.x, cameraPos.y, cameraPos.z, + commonY); + + var x = (p1.x - borderPoints[0].x) * (Window.innerWidth) / (borderPoints[1].x - borderPoints[0].x); + var y = (p1.z - borderPoints[0].z) * (Window.innerHeight) / (borderPoints[1].z - borderPoints[0].z); + + if (avatarsData[QUuid].icon != undefined) { + var iconPos = findLineToHeightIntersectionCoords( avatarPos.x, avatarPos.y + RADAR_ICONS_APPARENT_DISTANCE_TO_AVATAR_BASE, avatarPos.z, + Camera.position.x, Camera.position.y, Camera.position.z, + commonY); + if (!iconPos) { print ("avatar icon pos bad for " + QUuid); continue; } + if (avatarsData[QUuid].needRefresh) { + var avat = AvatarList.getAvatar(QUuid); + if (avat && avat.displayName) { + Overlays.editOverlay(avatarsData[QUuid].name, { + width : avat.displayName.length + * AVATAR_DISPLAY_NAME_CHAR_WIDTH, + text : avat.displayName, + textAlignCenter : true + }); + avatarsData[QUuid].needRefresh = false; + } + } + var textSize = (14 + (iconDimensions.y - 0.03) * 15 / 0.06); + Overlays.editOverlay(avatarsData[QUuid].icon, { + visible : true, + dimensions : iconDimensions, + position : iconPos + }); + Overlays.editOverlay(avatarsData[QUuid].name, { + visible : true, + x : x - avatarsData[QUuid].textWidth * 0.5, + y : y + iconDimensions.y * 550, + font : { + size : textSize, + bold : true + } + }); + } + } + } + } +} + +var ICON_ENTITY_DEFAULT_DIMENSIONS = { + x : 0.10, + y : 0.00001, + z : 0.10 +}; + + +function teleportIconModelDimensions(y) { + var teleportModelDimensions = ICON_ENTITY_DEFAULT_DIMENSIONS; + var xz = -0.002831 * (radarHeight - y) + 0.1; + teleportModelDimensions.x = xz; + teleportModelDimensions.z = xz; + // reuse object + return teleportModelDimensions; +} + +/******************************************************************************* + * + ******************************************************************************/ + +function startRadar() { + printd("avatar added my avatar is " + MyAvatar.sessionUUID); + saveAllOthersAvatarsData(); + Camera.mode = "independent"; + + initCameraOverMyAvatar(); + + Camera.orientation = Quat.fromPitchYawRollDegrees(-90, 0, 0); + radar = true; + + Controller.setVPadEnabled(false); // this was done before in CompositeExtra in the DisplayPlugin (Checking for camera not independent, not radar mode) + + connectRadarModeEvents(); +} + +function endRadar() { + printd("-- endRadar"); + Camera.mode = "third person"; + radar = false; + + Controller.setVPadEnabled(true); + + disconnectRadarModeEvents(); + hideAllAvatarIcons(); +} + +function onRadarModeClicked() { + startRadar(); +} + +function onMyViewModeClicked() { + endRadar(); +} + +radarModeInterface.startRadarMode = function() { + startRadar(); +}; + +radarModeInterface.endRadarMode = function() { + endRadar(); +}; + +radarModeInterface.init = function() { + init(); +} + +radarModeInterface.setUniqueColor = function(c) { + uniqueColor = c; +}; + +module.exports = radarModeInterface; + +function updateRadar() { + // Update avatar icons + if (startedDraggingCamera) { + hideAllAvatarIcons(); + startedDraggingCamera = false; + } else if (!draggingCamera) { + renderMyAvatarIcon(); + renderAllOthersAvatarIcons(); + } +} + +function valueIfDefined(value) { + return value !== undefined ? value : ""; +} + +function connectRadarModeEvents() { + Script.update.connect(updateRadar); // 60Hz loop + Controller.keyPressEvent.connect(keyPressEvent); + Controller.touchUpdateEvent.connect(touchUpdate); + Window.domainChanged.connect(domainChanged); + MyAvatar.positionGoneTo.connect(positionGoneTo); +} + +function initCameraOverMyAvatar() { + radarHeight = MyAvatar.position.y + RADAR_HEIGHT_INIT_DELTA; + Camera.position = { + x : MyAvatar.position.x, + y : radarHeight, + z : MyAvatar.position.z + }; +} + +function domainChanged() { + initCameraOverMyAvatar(); +} + +function positionGoneTo() { + Camera.position = { + x : MyAvatar.position.x, + y : radarHeight, + z : MyAvatar.position.z + }; +} + +function disconnectRadarModeEvents() { + Script.update.disconnect(updateRadar); + Controller.keyPressEvent.disconnect(keyPressEvent); + Controller.touchUpdateEvent.disconnect(touchUpdate); + MyAvatar.positionGoneTo.disconnect(positionGoneTo); + Window.domainChanged.disconnect(domainChanged); +} + +function init() { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + + Controller.touchBeginEvent.connect(touchBegin); + Controller.touchEndEvent.connect(touchEnd); + + AvatarList.avatarAddedEvent.connect(avatarAdded); + AvatarList.avatarRemovedEvent.connect(avatarRemoved); +} diff --git a/scripts/simplifiedUI/system/+android_interface/stats.js b/scripts/simplifiedUI/system/+android_interface/stats.js new file mode 100644 index 0000000000..0731684291 --- /dev/null +++ b/scripts/simplifiedUI/system/+android_interface/stats.js @@ -0,0 +1,39 @@ +"use strict"; +// +// stats.js +// scripts/system/ +// +// Created by Sam Gondelman on 3/14/18 +// 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 +// + +(function() { // BEGIN LOCAL_SCOPE + +var statsbar; +var statsButton; + +function init() { + statsbar = new QmlFragment({ + qml: "hifi/StatsBar.qml" + }); + + statsButton = statsbar.addButton({ + icon: "icons/stats.svg", + activeIcon: "icons/stats.svg", + textSize: 45, + bgOpacity: 0.0, + activeBgOpacity: 0.0, + bgColor: "#FFFFFF", + text: "STATS" + }); + statsButton.clicked.connect(function() { + Menu.triggerOption("Show Statistics"); + }); +} + +init(); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/+android_interface/touchscreenvirtualpad.js b/scripts/simplifiedUI/system/+android_interface/touchscreenvirtualpad.js new file mode 100644 index 0000000000..d48b623e03 --- /dev/null +++ b/scripts/simplifiedUI/system/+android_interface/touchscreenvirtualpad.js @@ -0,0 +1,22 @@ +"use strict"; +// +// touchscreenvirtualpad.js +// scripts/system/ +// +// Created by Gabriel Calero & Cristian Duarte on Jan 16, 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 +// + +(function() { // BEGIN LOCAL_SCOPE + +function init() { + Controller.setVPadEnabled(true); + Controller.setVPadHidden(false); +} + +init(); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/+android_interface/uniqueColor.js b/scripts/simplifiedUI/system/+android_interface/uniqueColor.js new file mode 100644 index 0000000000..a2741642d2 --- /dev/null +++ b/scripts/simplifiedUI/system/+android_interface/uniqueColor.js @@ -0,0 +1,54 @@ +"use strict"; +// +// uniqueColor.js +// scripts/system/ +// +// Created by Gabriel Calero & Cristian Duarte on 17 Oct 2017 +// Copyright 2017 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 +// + +var colorsMap = {}; +var colorsCount = 0; + // 'red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'magenta' +var baseColors = [ '#EB3345', '#F0851F', '#FFCD29', '#94C338', '#11A6C5', '#294C9F', '#C01D84' ]; + +function getNextColor(n) { + var N = baseColors.length; + /*if (n < baseColors.length) { + return baseColors[n]; + } else { + var baseColor = baseColors[n % N]; + var d = (n / N) % 10; + var c2 = "" + Qt.lighter(baseColor, 1 + d / 10); + return c2; + }*/ + return baseColors[n%N]; +} + +function getColorForId(uuid) { + if (colorsMap == undefined) { + colorsMap = {}; + } + if (!colorsMap.hasOwnProperty(uuid)) { + colorsMap[uuid] = getNextColor(colorsCount); + colorsCount = colorsCount + 1; + } + return colorsMap[uuid]; +} + +module.exports = { + getColor: function(id) { + return getColorForId(id); + }, + convertHexToRGB: function(hex) { + var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result ? { + red: parseInt(result[1], 16), + green: parseInt(result[2], 16), + blue: parseInt(result[3], 16) + } : null; + } +}; diff --git a/scripts/simplifiedUI/system/assets/animations/Cheering.fbx b/scripts/simplifiedUI/system/assets/animations/Cheering.fbx new file mode 100644 index 0000000000..8787bf4bd8 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Cheering.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Clapping.fbx b/scripts/simplifiedUI/system/assets/animations/Clapping.fbx new file mode 100644 index 0000000000..d05b41866d Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Clapping.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Crying.fbx b/scripts/simplifiedUI/system/assets/animations/Crying.fbx new file mode 100644 index 0000000000..2e60ba2450 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Crying.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Dancing.fbx b/scripts/simplifiedUI/system/assets/animations/Dancing.fbx new file mode 100644 index 0000000000..7759d273b7 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Dancing.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Fall.fbx b/scripts/simplifiedUI/system/assets/animations/Fall.fbx new file mode 100644 index 0000000000..627e909bb4 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Fall.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Love.fbx b/scripts/simplifiedUI/system/assets/animations/Love.fbx new file mode 100644 index 0000000000..159ccafd04 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Love.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Pointing.fbx b/scripts/simplifiedUI/system/assets/animations/Pointing.fbx new file mode 100644 index 0000000000..da3c9bbeca Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Pointing.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Sit1.fbx b/scripts/simplifiedUI/system/assets/animations/Sit1.fbx new file mode 100644 index 0000000000..db75219980 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Sit1.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Sit2.fbx b/scripts/simplifiedUI/system/assets/animations/Sit2.fbx new file mode 100644 index 0000000000..400b599794 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Sit2.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Sit3.fbx b/scripts/simplifiedUI/system/assets/animations/Sit3.fbx new file mode 100644 index 0000000000..174fd75c4e Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Sit3.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Surprised.fbx b/scripts/simplifiedUI/system/assets/animations/Surprised.fbx new file mode 100644 index 0000000000..49362605b3 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Surprised.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Waving.fbx b/scripts/simplifiedUI/system/assets/animations/Waving.fbx new file mode 100644 index 0000000000..e2442f64f4 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Waving.fbx differ diff --git a/scripts/simplifiedUI/system/assets/data/createAppTooltips.json b/scripts/simplifiedUI/system/assets/data/createAppTooltips.json new file mode 100644 index 0000000000..b59797fca7 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/data/createAppTooltips.json @@ -0,0 +1,623 @@ +{ + "shape": { + "tooltip": "The shape of this entity's geometry." + }, + "color": { + "tooltip": "The RGB value of this entity." + }, + "text": { + "tooltip": "The text to display on the entity." + }, + "textColor": { + "tooltip": "The color of the text." + }, + "textAlpha": { + "tooltip": "The alpha of the text." + }, + "backgroundColor": { + "tooltip": "The color of the background." + }, + "backgroundAlpha": { + "tooltip": "The alpha of the background." + }, + "lineHeight": { + "tooltip": "The height of each line of text. This determines the size of the text." + }, + "textBillboardMode": { + "tooltip": "If enabled, determines how the entity will face the camera.", + "jsPropertyName": "billboardMode" + }, + "topMargin": { + "tooltip": "The top margin, in meters." + }, + "rightMargin": { + "tooltip": "The right margin, in meters." + }, + "bottomMargin": { + "tooltip": "The bottom margin, in meters." + }, + "leftMargin": { + "tooltip": "The left margin, in meters." + }, + "zoneShapeType": { + "tooltip": "The shape of the volume in which the zone's lighting effects and avatar permissions have effect.", + "jsPropertyName": "shapeType" + }, + "zoneCompoundShapeURL": { + "tooltip": "The model file to use for the compound shape if Shape Type is \"Use Compound Shape URL\".", + "jsPropertyName": "compoundShapeURL" + }, + "flyingAllowed": { + "tooltip": "If enabled, users can fly in the zone." + }, + "ghostingAllowed": { + "tooltip": "If enabled, users with avatar collisions turned off will not collide with content in the zone." + }, + "filterURL": { + "tooltip": "The URL of a JS file that checks for changes to entity properties within the zone. Runs periodically." + }, + "keyLightMode": { + "tooltip": "Configures the key light in the zone. This light is directional." + }, + "keyLight.color": { + "tooltip": "The color of the key light." + }, + "keyLight.intensity": { + "tooltip": "The intensity of the key light." + }, + "keyLight.direction.y": { + "tooltip": "The angle in deg at which light emits. Starts in the entity's -z direction, and rotates around its y axis." + }, + "keyLight.direction.x": { + "tooltip": "The angle in deg at which light emits. Starts in the entity's -z direction, and rotates around its x axis." + }, + "keyLight.castShadows": { + "tooltip": "If enabled, shadows are cast. The entity or avatar casting the shadow must also have Cast Shadows enabled." + }, + "skyboxMode": { + "tooltip": "Configures the skybox in the zone. The skybox is a cube map image." + }, + "skybox.color": { + "tooltip": "If the URL is blank, this changes the color of the sky, otherwise it modifies the color of the skybox." + }, + "skybox.url": { + "tooltip": "A cube map image that is used to render the sky." + }, + "ambientLightMode": { + "tooltip": "Configures the ambient light in the zone. Use this if you want your skybox to reflect light on the content." + }, + "ambientLight.ambientIntensity": { + "tooltip": "The intensity of the ambient light." + }, + "ambientLight.ambientURL": { + "tooltip": "A cube map image that defines the color of the light coming from each direction." + }, + "hazeMode": { + "tooltip": "Configures the haze in the scene." + }, + "haze.hazeRange": { + "tooltip": "How far the haze extends out. This is measured in meters." + }, + "haze.hazeAltitudeEffect": { + "tooltip": "If enabled, this adjusts the haze intensity as it gets higher." + }, + "haze.hazeBaseRef": { + "tooltip": "The base of the altitude range. Measured in entity space." + }, + "haze.hazeCeiling": { + "tooltip": "The ceiling of the altitude range. Measured in entity space." + }, + "haze.hazeColor": { + "tooltip": "The color of the haze." + }, + "haze.hazeBackgroundBlend": { + "tooltip": "How much the skybox shows through the haze. The higher the value, the more it shows through." + }, + "haze.hazeEnableGlare": { + "tooltip": "If enabled, a glare is enabled on the skybox, based on the key light." + }, + "haze.hazeGlareColor": { + "tooltip": "The color of the glare based on the key light." + }, + "haze.hazeGlareAngle": { + "tooltip": "The angular size of the glare and how much it encompasses the skybox, based on the key light." + }, + "bloomMode": { + "tooltip": "Configures how much bright areas of the scene glow." + }, + "bloom.bloomIntensity": { + "tooltip": "The intensity, or brightness, of the bloom effect." + }, + "bloom.bloomThreshold": { + "tooltip": "The cutoff of the bloom. The higher the value, the more only bright areas of the scene will glow." + }, + "bloom.bloomSize": { + "tooltip": "The radius of bloom. The higher the value, the larger the bloom." + }, + "avatarPriority": { + "tooltip": "Alter Avatars' update priorities." + }, + "modelURL": { + "tooltip": "A mesh model from an FBX or OBJ file." + }, + "shapeType": { + "tooltip": "The shape of the collision hull used if collisions are enabled. This affects how an entity collides." + }, + "compoundShapeURL": { + "tooltip": "The model file to use for the compound shape if Collision Shape is \"Compound\"." + }, + "animation.url": { + "tooltip": "An animation to play on the model." + }, + "animation.running": { + "tooltip": "If enabled, the animation on the model will play automatically." + }, + "animation.allowTranslation": { + "tooltip": "If enabled, this allows an entity to move in space during an animation." + }, + "animation.loop": { + "tooltip": "If enabled, then the animation will continuously repeat." + }, + "animation.hold": { + "tooltip": "If enabled, then rotations and translations of the last frame played are maintained when the animation stops." + }, + "animation.currentFrame": { + "tooltip": "The current frame being played in the animation." + }, + "animation.firstFrame": { + "tooltip": "The first frame to play in the animation." + }, + "animation.lastFrame": { + "tooltip": "The last frame to play in the animation." + }, + "animation.fps": { + "tooltip": "The speed of the animation." + }, + "textures": { + "tooltip": "A JSON string containing a texture. Use a name from the Original Texture property to override it." + }, + "originalTextures": { + "tooltip": "A JSON string containing the original texture used on the model." + }, + "imageURL": { + "tooltip": "The URL for the image source." + }, + "imageColor": { + "tooltip": "The tint to be applied to the image.", + "jsPropertyName": "color" + }, + "emissive": { + "tooltip": "If enabled, the image will display at full brightness." + }, + "subImage": { + "tooltip": "The area of the image that is displayed." + }, + "imageBillboardMode": { + "tooltip": "If enabled, determines how the entity will face the camera.", + "jsPropertyName": "billboardMode" + }, + "keepAspectRatio": { + "tooltip": "If enabled, the image will maintain its original aspect ratio." + }, + "sourceUrl": { + "tooltip": "The URL for the web page source." + }, + "dpi": { + "tooltip": "The resolution to display the page at, in pixels per inch. Use this to resize your web source in the frame." + }, + "isEmitting": { + "tooltip": "If enabled, then particles are emitted." + }, + "lifespan": { + "tooltip": "How long each particle lives, measured in seconds." + }, + "maxParticles": { + "tooltip": "The maximum number of particles to render at one time. Older particles are swapped out for new ones." + }, + "particleTextures": { + "tooltip": "The URL of a JPG or PNG image file to display for each particle.", + "jsPropertyName": "textures" + }, + "emitRate": { + "tooltip": "The number of particles per second to emit." + }, + "emitSpeed": { + "tooltip": "The speed that each particle is emitted at, measured in m/s." + }, + "speedSpread": { + "tooltip": "The spread in speeds at which particles are emitted at, resulting in a variety of speeds." + }, + "particleShapeType": { + "tooltip": "The shape of the surface from which to emit particles.", + "jsPropertyName": "shapeType" + }, + "particleCompoundShapeURL": { + "tooltip": "The model file to use for the particle emitter if Shape Type is \"Use Compound Shape URL\".", + "jsPropertyName": "compoundShapeURL" + }, + "emitDimensions": { + "tooltip": "The outer limit radius in dimensions that the particles can be emitted from." + }, + "emitOrientation": { + "tooltip": "The orientation of particle emission relative to the entity's axes." + }, + "emitRadiusStart": { + "tooltip": "The inner limit radius in dimensions that the particles start emitting from." + }, + "emitterShouldTrail": { + "tooltip": "If enabled, then particles are \"left behind\" as the emitter moves, otherwise they are not." + }, + "particleRadiusTriple": { + "tooltip": "The size of each particle.", + "jsPropertyName": "particleRadius" + }, + "particleRadius": { + "tooltip": "The size of each particle." + }, + "radiusStart": { + "tooltip": "The start size of each particle." + }, + "radiusFinish": { + "tooltip": "The finish size of each particle." + }, + "radiusSpread": { + "tooltip": "The spread in size that each particle is given, resulting in a variety of sizes." + }, + "particleColorTriple": { + "tooltip": "The color of each particle.", + "jsPropertyName": "color" + }, + "particleColor": { + "tooltip": "The color of each particle.", + "jsPropertyName": "color" + }, + "colorStart": { + "tooltip": "The start color of each particle." + }, + "colorFinish": { + "tooltip": "The finish color of each particle." + }, + "colorSpread": { + "tooltip": "The spread in color that each particle is given, resulting in a variety of colors." + }, + "particleAlphaTriple": { + "tooltip": "The alpha of each particle.", + "jsPropertyName": "alpha" + }, + "alpha": { + "tooltip": "The alpha of each particle." + }, + "alphaStart": { + "tooltip": "The start alpha of each particle." + }, + "alphaFinish": { + "tooltip": "The finish alpha of each particle." + }, + "alphaSpread": { + "tooltip": "The spread in alpha that each particle is given, resulting in a variety of alphas." + }, + "emitAcceleration": { + "tooltip": "The acceleration that is applied to each particle during its lifetime." + }, + "accelerationSpread": { + "tooltip": "The spread in accelerations that each particle is given, resulting in a variety of accelerations." + }, + "particleSpinTriple": { + "tooltip": "The spin of each particle.", + "jsPropertyName": "particleSpin" + }, + "particleSpin": { + "tooltip": "The spin of each particle." + }, + "spinStart": { + "tooltip": "The start spin of each particle." + }, + "spinFinish": { + "tooltip": "The finish spin of each particle." + }, + "spinSpread": { + "tooltip": "The spread in spin that each particle is given, resulting in a variety of spins." + }, + "rotateWithEntity": { + "tooltip": "If enabled, each particle will spin relative to the rotation of the entity as a whole." + }, + "particlePolarTriple": { + "tooltip": "The angle range in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis.", + "skipJSProperty": true + }, + "polarStart": { + "tooltip": "The start angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis." + }, + "polarFinish": { + "tooltip": "The finish angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis." + }, + "particleAzimuthTriple": { + "tooltip": "The angle range in deg at which particles are emitted. Starts in the entity's -x direction, and rotates around its z axis.", + "skipJSProperty": true + }, + "azimuthStart": { + "tooltip": "The start angle in deg at which particles are emitted. Starts in the entity's -x direction, and rotates around its z axis." + }, + "azimuthFinish": { + "tooltip": "The finish angle in deg at which particles are emitted. Starts in the entity's -x direction, and rotates around its z axis." + }, + "lightColor": { + "tooltip": "The color of the light emitted.", + "jsPropertyName": "color" + }, + "intensity": { + "tooltip": "The brightness of the light." + }, + "falloffRadius": { + "tooltip": "The distance from the light's center where the intensity is reduced." + }, + "isSpotlight": { + "tooltip": "If enabled, then the light is directional, otherwise the light is a point light which emits light in all directions." + }, + "exponent": { + "tooltip": "Affects the softness of the spotlight beam; the higher the value, the softer the beam." + }, + "cutoff": { + "tooltip": "Affects the size of the spotlight beam; the higher the value, the larger the beam." + }, + "materialURL": { + "tooltip": "The URL to an external JSON file or \"materialData\", \"materialData? to use Material Data." + }, + "materialData": { + "tooltip": "Can be used instead of a JSON file when material set to materialData." + }, + "parentMaterialName": { + "tooltip": "The target mesh indices or material names that this material entity should be assigned to on it's parent. This only supports parents that are Avatars as well as Shape or Model entity types." + }, + "priority": { + "tooltip": "The priority of the material, where a larger number means higher priority. Original materials = 0." + }, + "materialMappingMode": { + "tooltip": "How the material is mapped to the entity. If set to \"UV space\", then the material will be applied with the target entity's UV coordinates. If set to \"3D Projected\", then the 3D transform of the material entity will be used." + }, + "materialMappingPos": { + "tooltip": "The offset position of the bottom left of the material within the parent's UV space." + }, + "materialMappingScale": { + "tooltip": "How many times the material will repeat in each direction within the parent's UV space." + }, + "materialMappingRot": { + "tooltip": "How much to rotate the material within the parent's UV-space, in degrees." + }, + "materialRepeat": { + "tooltip": "If enabled, the material will repeat, otherwise it will clamp." + }, + "followCamera": { + "tooltip": "If enabled, the grid is always visible even as the camera moves to another position." + }, + "majorGridEvery": { + "tooltip": "The number of \"Minor Grid Every\" intervals at which to draw a thick grid line." + }, + "minorGridEvery": { + "tooltip": "The real number of meters at which to draw thin grid lines." + }, + "id": { + "tooltip": "The unique identifier of this entity." + }, + "name": { + "tooltip": "The name of this entity." + }, + "description": { + "tooltip": "Use this field to describe the entity." + }, + "position": { + "tooltip": "The global position of this entity." + }, + "localPosition": { + "tooltip": "The local position of this entity." + }, + "rotation": { + "tooltip": "The global rotation of this entity." + }, + "localRotation": { + "tooltip": "The local rotation of this entity." + }, + "dimensions": { + "tooltip": "The global dimensions of this entity." + }, + "localDimensions": { + "tooltip": "The local dimensions of this entity." + }, + "scale": { + "tooltip": "The global scaling of this entity.", + "skipJSProperty": true + }, + "registrationPoint": { + "tooltip": "The point in the entity at which the entity is rotated about." + }, + "visible": { + "tooltip": "If enabled, this entity will be visible." + }, + "locked": { + "tooltip": "If enabled, this entity will be locked." + }, + "collisionless": { + "tooltip": "If enabled, this entity will collide with other entities or avatars." + }, + "dynamic": { + "tooltip": "If enabled, this entity has collisions associated with it that can affect its movement." + }, + "collidesWithStatic": { + "tooltip": "If enabled, this entity will collide with other non-moving, static entities.", + "jsPropertyName": "collidesWith" + }, + "collidesWithDynamic": { + "tooltip": "If enabled, this entity will collide with other dynamic entities.", + "jsPropertyName": "collidesWith" + }, + "collidesWithKinematic": { + "tooltip": "If enabled, this entity will collide with other kinematic entities (they have velocity but are not dynamic).", + "jsPropertyName": "collidesWith" + }, + "collidesWithOtherAvatar": { + "tooltip": "If enabled, this entity will collide with other user's avatars.", + "jsPropertyName": "collidesWith" + }, + "collidesWithMyAvatar": { + "tooltip": "If enabled, this entity will collide with your own avatar.", + "jsPropertyName": "collidesWith" + }, + "collisionSoundURL": { + "tooltip": "The URL of a sound to play when the entity collides with something else." + }, + "grab.grabbable": { + "tooltip": "If enabled, this entity will allow grabbing input and will be movable." + }, + "grab.triggerable": { + "tooltip": "If enabled, the collider on this entity is used for triggering events." + }, + "cloneable": { + "tooltip": "If enabled, this entity can be duplicated." + }, + "cloneLifetime": { + "tooltip": "The lifetime for clones of this entity." + }, + "cloneLimit": { + "tooltip": "The total number of clones of this entity that can exist in the domain at any given time." + }, + "cloneDynamic": { + "tooltip": "If enabled, then clones created from this entity will be dynamic, allowing the clone to collide." + }, + "cloneAvatarEntity": { + "tooltip": "If enabled, then clones created from this entity will be created as avatar entities." + }, + "grab.grabFollowsController": { + "tooltip": "If enabled, grabbed entities will follow the movements of your hand controller instead of your avatar's hand." + }, + "canCastShadow": { + "tooltip": "If enabled, this geometry of this entity casts shadows when a shadow-casting light source shines on it." + }, + "ignorePickIntersection": { + "tooltip": "If enabled, this entity will not be considered for ray picks, and will also not occlude other entities when picking." + }, + "parentID": { + "tooltip": "The ID of the entity or avatar that this entity is parented to." + }, + "parentJointIndex": { + "tooltip": "If the entity is parented to an avatar, this joint defines where on the avatar the entity is parented." + }, + "href": { + "tooltip": "The URL that will be opened when a user clicks on this entity. Useful for web pages and portals." + }, + "script": { + "tooltip": "The URL to an external JS file to add behaviors to the client." + }, + "serverScripts": { + "tooltip": "The URL to an external JS file to add behaviors to the server." + }, + "serverScriptsStatus": { + "tooltip": "The status of the server script, if provided. This shows if it's running or has an error.", + "skipJSProperty": true + }, + "hasLifetime": { + "tooltip": "If enabled, the entity will disappear after a certain amount of time specified by Lifetime.", + "jsPropertyName": "lifetime" + }, + "lifetime": { + "tooltip": "The time this entity will exist in the environment for." + }, + "userData": { + "tooltip": "Used to store extra data about the entity in JSON format." + }, + "localVelocity": { + "tooltip": "The linear velocity vector of the entity. The velocity at which this entity moves forward in space." + }, + "damping": { + "tooltip": "The linear damping to slow down the linear velocity of an entity over time." + }, + "localAngularVelocity": { + "tooltip": "The angular velocity of the entity in rad/s with respect to its axes, about its pivot point." + }, + "angularDamping": { + "tooltip": "The angular damping to slow down the angular velocity of an entity over time." + }, + "restitution": { + "tooltip": "If enabled, the entity can bounce against other objects that also have Bounciness." + }, + "friction": { + "tooltip": "The friction applied to slow down an entity when it's moving against another entity." + }, + "density": { + "tooltip": "The density of the entity. The higher the density, the harder the entity is to move." + }, + "gravity": { + "tooltip": "The acceleration due to gravity that the entity should move with, in world space." + }, + "acceleration": { + "tooltip": "A acceleration that the entity should move with, in world space." + }, + "renderLayer": { + "tooltip": "The layer on which this entity is rendered." + }, + "primitiveMode": { + "tooltip": "The mode in which to draw an entity, either \"Solid\" or \"Wireframe\"." + }, + "groupCulled": { + "tooltip": "If false, individual pieces of the entity may be culled by the render engine. If true, either the entire entity will be culled, or it won't at all." + }, + "webColor": { + "tooltip": "The tint of the web entity." + }, + "webAlpha": { + "tooltip": "The alpha of the web entity." + }, + "maxFPS": { + "tooltip": "The FPS at which to render the web entity. Higher values will have a performance impact." + }, + "scriptURL": { + "tooltip": "The URL of a script to inject into the web page." + }, + "alignToGrid": { + "tooltip": "Used to align entities to the grid, or floor of the environment.", + "skipJSProperty": true + }, + "createModel": { + "tooltip": "An entity that is based on a custom mesh created from an .OBJ or .FBX.", + "skipJSProperty": true + }, + "createShape": { + "tooltip": "An entity that has many different primitive shapes.", + "skipJSProperty": true + }, + "createLight": { + "tooltip": "An entity that emits light.", + "skipJSProperty": true + }, + "createText": { + "tooltip": "An entity that displays text on a panel.", + "skipJSProperty": true + }, + "createImage": { + "tooltip": "An entity that displays an image on a panel.", + "skipJSProperty": true + }, + "createWeb": { + "tooltip": "An entity that displays a web page on a panel.", + "skipJSProperty": true + }, + "createZone": { + "tooltip": "An entity that can be used for skyboxes, lighting, and can constrain or change avatar behaviors.", + "skipJSProperty": true + }, + "createParticle": { + "tooltip": "An entity that emits particles.", + "skipJSProperty": true + }, + "createMaterial": { + "tooltip": "An entity that creates a material that can be attached to a Shape or Model.", + "skipJSProperty": true + }, + "useAssetServer": { + "tooltip": "A server that hosts content and assets. You can't take items that are hosted here into other domains.", + "skipJSProperty": true + }, + "importNewEntity": { + "tooltip": "Import a local or hosted file that can be used across domains.", + "skipJSProperty": true + } +} diff --git a/scripts/simplifiedUI/system/assets/images/Overlay-Viz-blank.png b/scripts/simplifiedUI/system/assets/images/Overlay-Viz-blank.png new file mode 100644 index 0000000000..bbbf44f7a9 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/images/Overlay-Viz-blank.png differ diff --git a/scripts/simplifiedUI/system/assets/images/Particle-Sprite-Smoke-1.png b/scripts/simplifiedUI/system/assets/images/Particle-Sprite-Smoke-1.png new file mode 100644 index 0000000000..78c9b3da4a Binary files /dev/null and b/scripts/simplifiedUI/system/assets/images/Particle-Sprite-Smoke-1.png differ diff --git a/scripts/simplifiedUI/system/assets/images/close-small-light.svg b/scripts/simplifiedUI/system/assets/images/close-small-light.svg new file mode 100644 index 0000000000..f9edf95fca --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/close-small-light.svg @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/grabsprite-3.png b/scripts/simplifiedUI/system/assets/images/grabsprite-3.png new file mode 100644 index 0000000000..4ecc772a41 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/images/grabsprite-3.png differ diff --git a/scripts/simplifiedUI/system/assets/images/hourglass.svg b/scripts/simplifiedUI/system/assets/images/hourglass.svg new file mode 100644 index 0000000000..5c5055b755 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/hourglass.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/icon-particles.svg b/scripts/simplifiedUI/system/assets/images/icon-particles.svg new file mode 100644 index 0000000000..5e0105d7cd --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/icon-particles.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/icon-point-light.svg b/scripts/simplifiedUI/system/assets/images/icon-point-light.svg new file mode 100644 index 0000000000..896c35b63b --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/icon-point-light.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/icon-spot-light.svg b/scripts/simplifiedUI/system/assets/images/icon-spot-light.svg new file mode 100644 index 0000000000..ac2f87bb27 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/icon-spot-light.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/icon-zone.svg b/scripts/simplifiedUI/system/assets/images/icon-zone.svg new file mode 100644 index 0000000000..41aeac4951 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/icon-zone.svg @@ -0,0 +1,73 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/ignore-target.svg b/scripts/simplifiedUI/system/assets/images/ignore-target.svg new file mode 100644 index 0000000000..3d685139ec --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/ignore-target.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/kick-target.svg b/scripts/simplifiedUI/system/assets/images/kick-target.svg new file mode 100644 index 0000000000..21cb3a5462 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/kick-target.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/lock.svg b/scripts/simplifiedUI/system/assets/images/lock.svg new file mode 100644 index 0000000000..bb9658de00 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/lock.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/lod-a.svg b/scripts/simplifiedUI/system/assets/images/lod-a.svg new file mode 100644 index 0000000000..6845e0ff78 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/lod-a.svg @@ -0,0 +1,46 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/lod-i.svg b/scripts/simplifiedUI/system/assets/images/lod-i.svg new file mode 100644 index 0000000000..f909f3b495 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/lod-i.svg @@ -0,0 +1,46 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/luci-a.svg b/scripts/simplifiedUI/system/assets/images/luci-a.svg new file mode 100644 index 0000000000..40e1481eba --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/luci-a.svg @@ -0,0 +1,10 @@ + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/luci-i.svg b/scripts/simplifiedUI/system/assets/images/luci-i.svg new file mode 100644 index 0000000000..2d73339908 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/luci-i.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/materials/GridPattern.json b/scripts/simplifiedUI/system/assets/images/materials/GridPattern.json new file mode 100644 index 0000000000..468b709ea4 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/materials/GridPattern.json @@ -0,0 +1,13 @@ +{ + "materialVersion": 1, + "materials": { + "albedo": [ + 0.0, + 0.0, + 7.0 + ], + "unlit": true, + "opacity": 0.4, + "albedoMap": "GridPattern.png" + } +} diff --git a/scripts/simplifiedUI/system/assets/images/materials/GridPattern.png b/scripts/simplifiedUI/system/assets/images/materials/GridPattern.png new file mode 100644 index 0000000000..2ecc7f8570 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/images/materials/GridPattern.png differ diff --git a/scripts/simplifiedUI/system/assets/images/min-max-toggle.svg b/scripts/simplifiedUI/system/assets/images/min-max-toggle.svg new file mode 100644 index 0000000000..1699cc705d --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/min-max-toggle.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/mute-target.svg b/scripts/simplifiedUI/system/assets/images/mute-target.svg new file mode 100644 index 0000000000..1ed642c79e --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/mute-target.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/progress-bar-2k.svg b/scripts/simplifiedUI/system/assets/images/progress-bar-2k.svg new file mode 100644 index 0000000000..45758c7c68 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/progress-bar-2k.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/progress-bar-4k.svg b/scripts/simplifiedUI/system/assets/images/progress-bar-4k.svg new file mode 100644 index 0000000000..609ab9610b --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/progress-bar-4k.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/progress-bar-text.svg b/scripts/simplifiedUI/system/assets/images/progress-bar-text.svg new file mode 100644 index 0000000000..05ebb3f637 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/progress-bar-text.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/run.svg b/scripts/simplifiedUI/system/assets/images/run.svg new file mode 100644 index 0000000000..0957166346 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/run.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/textures/dirt.jpeg b/scripts/simplifiedUI/system/assets/images/textures/dirt.jpeg new file mode 100644 index 0000000000..694e4f3c9a Binary files /dev/null and b/scripts/simplifiedUI/system/assets/images/textures/dirt.jpeg differ diff --git a/scripts/simplifiedUI/system/assets/images/textures/grass.png b/scripts/simplifiedUI/system/assets/images/textures/grass.png new file mode 100644 index 0000000000..d51fe0cf28 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/images/textures/grass.png differ diff --git a/scripts/simplifiedUI/system/assets/images/tools/add-remove-friends.svg b/scripts/simplifiedUI/system/assets/images/tools/add-remove-friends.svg new file mode 100644 index 0000000000..6ee9ce842d --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/add-remove-friends.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/assets-01.svg b/scripts/simplifiedUI/system/assets/images/tools/assets-01.svg new file mode 100644 index 0000000000..d7bdd1d7f0 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/assets-01.svg @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/bubble.svg b/scripts/simplifiedUI/system/assets/images/tools/bubble.svg new file mode 100644 index 0000000000..064b7734a9 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/bubble.svg @@ -0,0 +1,275 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/cube-01.svg b/scripts/simplifiedUI/system/assets/images/tools/cube-01.svg new file mode 100644 index 0000000000..f8cf96e4f2 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/cube-01.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/directory.svg b/scripts/simplifiedUI/system/assets/images/tools/directory.svg new file mode 100644 index 0000000000..09f3c14bf0 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/directory.svg @@ -0,0 +1,255 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/edit.svg b/scripts/simplifiedUI/system/assets/images/tools/edit.svg new file mode 100644 index 0000000000..f65e0cd84d --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/edit.svg @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/help.svg b/scripts/simplifiedUI/system/assets/images/tools/help.svg new file mode 100644 index 0000000000..b7fa8ca5cd --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/help.svg @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/ignore.svg b/scripts/simplifiedUI/system/assets/images/tools/ignore.svg new file mode 100644 index 0000000000..f315c5f249 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/ignore.svg @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/kick.svg b/scripts/simplifiedUI/system/assets/images/tools/kick.svg new file mode 100644 index 0000000000..1eed6e7f43 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/kick.svg @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/light-01.svg b/scripts/simplifiedUI/system/assets/images/tools/light-01.svg new file mode 100644 index 0000000000..4573c7d636 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/light-01.svg @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/market.svg b/scripts/simplifiedUI/system/assets/images/tools/market.svg new file mode 100644 index 0000000000..0cec030933 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/market.svg @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/mic.svg b/scripts/simplifiedUI/system/assets/images/tools/mic.svg new file mode 100644 index 0000000000..3a7c183bc4 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/mic.svg @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/min-max-toggle.svg b/scripts/simplifiedUI/system/assets/images/tools/min-max-toggle.svg new file mode 100644 index 0000000000..1699cc705d --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/min-max-toggle.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/model-01.svg b/scripts/simplifiedUI/system/assets/images/tools/model-01.svg new file mode 100644 index 0000000000..e760d74d5c --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/model-01.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/particle-01.svg b/scripts/simplifiedUI/system/assets/images/tools/particle-01.svg new file mode 100644 index 0000000000..cfcfb0ea7f --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/particle-01.svg @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/people.svg b/scripts/simplifiedUI/system/assets/images/tools/people.svg new file mode 100644 index 0000000000..5fedfefa75 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/people.svg @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/polyvox.svg b/scripts/simplifiedUI/system/assets/images/tools/polyvox.svg new file mode 100644 index 0000000000..69f1e978ff --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/polyvox.svg @@ -0,0 +1,39 @@ + + + + +polyvox + + + +voxels + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/snap.svg b/scripts/simplifiedUI/system/assets/images/tools/snap.svg new file mode 100644 index 0000000000..c540f307ae --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/snap.svg @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/sphere-01.svg b/scripts/simplifiedUI/system/assets/images/tools/sphere-01.svg new file mode 100644 index 0000000000..975199c8da --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/sphere-01.svg @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/sphere-add.svg b/scripts/simplifiedUI/system/assets/images/tools/sphere-add.svg new file mode 100644 index 0000000000..59ca489b5f --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/sphere-add.svg @@ -0,0 +1,77 @@ + + + +image/svg+xmladd + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/tools/sphere-delete.svg b/scripts/simplifiedUI/system/assets/images/tools/sphere-delete.svg new file mode 100644 index 0000000000..6997654500 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/sphere-delete.svg @@ -0,0 +1,76 @@ + + + +image/svg+xmldelete + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/tools/steam-invite.svg b/scripts/simplifiedUI/system/assets/images/tools/steam-invite.svg new file mode 100644 index 0000000000..ce225cca68 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/steam-invite.svg @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/switch.svg b/scripts/simplifiedUI/system/assets/images/tools/switch.svg new file mode 100644 index 0000000000..e67a9aac04 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/switch.svg @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/text-01.svg b/scripts/simplifiedUI/system/assets/images/tools/text-01.svg new file mode 100644 index 0000000000..d33d66d4a5 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/text-01.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/upload-01.svg b/scripts/simplifiedUI/system/assets/images/tools/upload-01.svg new file mode 100644 index 0000000000..149b10f3bc --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/upload-01.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/voxel-add.svg b/scripts/simplifiedUI/system/assets/images/tools/voxel-add.svg new file mode 100644 index 0000000000..8e6e2c5b35 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/voxel-add.svg @@ -0,0 +1,104 @@ + + + +image/svg+xmladd + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/tools/voxel-delete.svg b/scripts/simplifiedUI/system/assets/images/tools/voxel-delete.svg new file mode 100644 index 0000000000..0b0d0b9787 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/voxel-delete.svg @@ -0,0 +1,103 @@ + + + +image/svg+xmldelete + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/tools/voxel-terrain.svg b/scripts/simplifiedUI/system/assets/images/tools/voxel-terrain.svg new file mode 100644 index 0000000000..e5ed16dbcd --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/voxel-terrain.svg @@ -0,0 +1,101 @@ + + + +image/svg+xmlterrain + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/tools/voxels.svg b/scripts/simplifiedUI/system/assets/images/tools/voxels.svg new file mode 100644 index 0000000000..a0dbd63d45 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/voxels.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/web-01.svg b/scripts/simplifiedUI/system/assets/images/tools/web-01.svg new file mode 100644 index 0000000000..903b3ac819 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/web-01.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/zone-01.svg b/scripts/simplifiedUI/system/assets/images/tools/zone-01.svg new file mode 100644 index 0000000000..29d17e5187 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/zone-01.svg @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/unlock.svg b/scripts/simplifiedUI/system/assets/images/unlock.svg new file mode 100644 index 0000000000..789a8b0ed5 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/unlock.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/models/Avatar-Overlay-v1.fbx b/scripts/simplifiedUI/system/assets/models/Avatar-Overlay-v1.fbx new file mode 100644 index 0000000000..db710702f2 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/Avatar-Overlay-v1.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/Bubble-v14.fbx b/scripts/simplifiedUI/system/assets/models/Bubble-v14.fbx new file mode 100644 index 0000000000..c7d3122f00 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/Bubble-v14.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/black-sphere.fbx b/scripts/simplifiedUI/system/assets/models/black-sphere.fbx new file mode 100644 index 0000000000..2e6dea233f Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/black-sphere.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/miniTabletBlank.fbx b/scripts/simplifiedUI/system/assets/models/miniTabletBlank.fbx new file mode 100644 index 0000000000..a2faa2a80a Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/miniTabletBlank.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/oculusSensorv11.fbx b/scripts/simplifiedUI/system/assets/models/oculusSensorv11.fbx new file mode 100644 index 0000000000..52fadc77dc Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/oculusSensorv11.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/teleport-cancel.fbx b/scripts/simplifiedUI/system/assets/models/teleport-cancel.fbx new file mode 100644 index 0000000000..1c12e28159 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/teleport-cancel.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/teleport-destination.fbm/Teleportation-Destination-Texture2.png b/scripts/simplifiedUI/system/assets/models/teleport-destination.fbm/Teleportation-Destination-Texture2.png new file mode 100644 index 0000000000..eb9addcfca Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/teleport-destination.fbm/Teleportation-Destination-Texture2.png differ diff --git a/scripts/simplifiedUI/system/assets/models/teleport-destination.fbx b/scripts/simplifiedUI/system/assets/models/teleport-destination.fbx new file mode 100644 index 0000000000..5fdb0d56af Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/teleport-destination.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/teleport-seat.fbx b/scripts/simplifiedUI/system/assets/models/teleport-seat.fbx new file mode 100644 index 0000000000..cd7a9abc7e Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/teleport-seat.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/teleportationSpotBasev8.fbx b/scripts/simplifiedUI/system/assets/models/teleportationSpotBasev8.fbx new file mode 100644 index 0000000000..d651575dea Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/teleportationSpotBasev8.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/trackingSpacev18.fbx b/scripts/simplifiedUI/system/assets/models/trackingSpacev18.fbx new file mode 100644 index 0000000000..16597eb285 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/trackingSpacev18.fbx differ diff --git a/scripts/simplifiedUI/system/assets/sounds/bubble.wav b/scripts/simplifiedUI/system/assets/sounds/bubble.wav new file mode 100644 index 0000000000..fd23a235d4 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/bubble.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/button-click.wav b/scripts/simplifiedUI/system/assets/sounds/button-click.wav new file mode 100644 index 0000000000..30a097ce45 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/button-click.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/button-hover.wav b/scripts/simplifiedUI/system/assets/sounds/button-hover.wav new file mode 100644 index 0000000000..cd76d0174c Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/button-hover.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/countdown-tick.wav b/scripts/simplifiedUI/system/assets/sounds/countdown-tick.wav new file mode 100644 index 0000000000..015e1f642e Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/countdown-tick.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/entitySnap.wav b/scripts/simplifiedUI/system/assets/sounds/entitySnap.wav new file mode 100644 index 0000000000..4584f3dcaa Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/entitySnap.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/finish-recording.wav b/scripts/simplifiedUI/system/assets/sounds/finish-recording.wav new file mode 100644 index 0000000000..f224049f97 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/finish-recording.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/goodbye.wav b/scripts/simplifiedUI/system/assets/sounds/goodbye.wav new file mode 100644 index 0000000000..bd6c51a5c0 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/goodbye.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/hello.wav b/scripts/simplifiedUI/system/assets/sounds/hello.wav new file mode 100644 index 0000000000..6269dab5db Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/hello.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/notification-general1.raw b/scripts/simplifiedUI/system/assets/sounds/notification-general1.raw new file mode 100644 index 0000000000..be81fa15c5 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/notification-general1.raw differ diff --git a/scripts/simplifiedUI/system/assets/sounds/notification-general2.raw b/scripts/simplifiedUI/system/assets/sounds/notification-general2.raw new file mode 100644 index 0000000000..58f0bac19c Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/notification-general2.raw differ diff --git a/scripts/simplifiedUI/system/assets/sounds/rezzing.wav b/scripts/simplifiedUI/system/assets/sounds/rezzing.wav new file mode 100644 index 0000000000..3c059aecdf Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/rezzing.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/short1.wav b/scripts/simplifiedUI/system/assets/sounds/short1.wav new file mode 100644 index 0000000000..fb03f5dd49 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/short1.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/start-recording.wav b/scripts/simplifiedUI/system/assets/sounds/start-recording.wav new file mode 100644 index 0000000000..71c69f3372 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/start-recording.wav differ diff --git a/scripts/simplifiedUI/system/attachedEntitiesManager.js b/scripts/simplifiedUI/system/attachedEntitiesManager.js new file mode 100644 index 0000000000..061e27f595 --- /dev/null +++ b/scripts/simplifiedUI/system/attachedEntitiesManager.js @@ -0,0 +1,285 @@ +// +// attachedEntitiesManager.js +// +// Created by Seth Alves on 2016-1-20 +// Copyright 2016 High Fidelity, Inc. +// +// This script handles messages from the grab script related to wearables, and interacts with a doppelganger. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include("libraries/utils.js"); + +var DEFAULT_WEARABLE_DATA = { + joints: {} +}; + + +var MINIMUM_DROP_DISTANCE_FROM_JOINT = 0.8; +var ATTACHED_ENTITY_SEARCH_DISTANCE = 10.0; +var ATTACHED_ENTITIES_SETTINGS_KEY = "ATTACHED_ENTITIES"; +var DRESSING_ROOM_DISTANCE = 2.0; +var SHOW_TOOL_BAR = false; + +// tool bar + +if (SHOW_TOOL_BAR) { + var BUTTON_SIZE = 64; + var PADDING = 6; + Script.include(["libraries/toolBars.js"]); + + var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.attachedEntities.toolbar"); + var lockButton = toolBar.addTool({ + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: Script.resolvePath("assets/images/lock.svg"), + color: { + red: 255, + green: 255, + blue: 255 + }, + alpha: 1, + visible: true + }, false); +} + + +function mousePressEvent(event) { + var clickedOverlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + + if (lockButton === toolBar.clicked(clickedOverlay)) { + manager.toggleLocked(); + } +} + +function scriptEnding() { + if (SHOW_TOOL_BAR) { + toolBar.cleanup(); + } +} + +if (SHOW_TOOL_BAR) { + Controller.mousePressEvent.connect(mousePressEvent); +} +Script.scriptEnding.connect(scriptEnding); + + + +// attached entites + + +function AttachedEntitiesManager() { + var clothingLocked = false; + + this.subscribeToMessages = function() { + Messages.subscribe('Hifi-Object-Manipulation'); + Messages.messageReceived.connect(this.handleWearableMessages); + } + + this.handleWearableMessages = function(channel, message, sender) { + if (channel !== 'Hifi-Object-Manipulation') { + return; + } + + var parsedMessage = null; + + try { + parsedMessage = JSON.parse(message); + } catch (e) { + print('error parsing wearable message'); + return; + } + + if (parsedMessage.action === 'update' || + parsedMessage.action === 'loaded') { + // ignore + } else if (parsedMessage.action === 'release') { + manager.handleEntityRelease(parsedMessage.grabbedEntity, parsedMessage.joint) + // manager.saveAttachedEntities(); + } else if (parsedMessage.action === 'equip') { + // manager.saveAttachedEntities(); + } else { + print('attachedEntitiesManager -- unknown actions: ' + parsedMessage.action); + } + } + + this.handleEntityRelease = function(grabbedEntity, releasedFromJoint) { + // if this is still equipped, just rewrite the position information. + var grabData = getEntityCustomData('grabKey', grabbedEntity, {}); + + var allowedJoints = getEntityCustomData('wearable', grabbedEntity, DEFAULT_WEARABLE_DATA).joints; + + var props = Entities.getEntityProperties(grabbedEntity, ["position", "parentID", "parentJointIndex"]); + if (props.parentID === Uuid.NULL || props.parentID === MyAvatar.sessionUUID) { + var bestJointName = ""; + var bestJointIndex = -1; + var bestJointDistance = 0; + var bestJointOffset = null; + for (var jointName in allowedJoints) { + if ((releasedFromJoint == "LeftHand" || releasedFromJoint == "RightHand") && + (jointName == "LeftHand" || jointName == "RightHand")) { + // don't auto-attach to a hand if a hand just dropped something + continue; + } + var jointIndex = MyAvatar.getJointIndex(jointName); + if (jointIndex >= 0) { + var jointPosition = MyAvatar.getJointPosition(jointIndex); + var distanceFromJoint = Vec3.distance(jointPosition, props.position); + if (distanceFromJoint <= MINIMUM_DROP_DISTANCE_FROM_JOINT) { + if (bestJointIndex == -1 || distanceFromJoint < bestJointDistance) { + bestJointName = jointName; + bestJointIndex = jointIndex; + bestJointDistance = distanceFromJoint; + bestJointOffset = allowedJoints[jointName]; + } + } + } + } + + if (bestJointIndex != -1) { + var wearProps = Entities.getEntityProperties(grabbedEntity); + wearProps.parentID = MyAvatar.sessionUUID; + wearProps.parentJointIndex = bestJointIndex; + delete wearProps.localPosition; + delete wearProps.localRotation; + var updatePresets = false; + if (bestJointOffset && bestJointOffset.constructor === Array) { + if (!clothingLocked || bestJointOffset.length < 2) { + // we're unlocked or this thing didn't have a preset position, so update it + updatePresets = true; + } else { + // don't snap the entity to the preferred position if unlocked + wearProps.localPosition = bestJointOffset[0]; + wearProps.localRotation = bestJointOffset[1]; + } + } + + Entities.deleteEntity(grabbedEntity); + //the true boolean here after add entity adds it as an 'avatar entity', which can travel with you from server to server. + + var newEntity = Entities.addEntity(wearProps, true); + + if (updatePresets) { + this.updateRelativeOffsets(newEntity); + } + } else if (props.parentID != Uuid.NULL) { + // drop the entity and set it to have no parent (not on the avatar), unless it's being equipped in a hand. + if (props.parentID === MyAvatar.sessionUUID && + (props.parentJointIndex == MyAvatar.getJointIndex("RightHand") || + props.parentJointIndex == MyAvatar.getJointIndex("LeftHand"))) { + // this is equipped on a hand -- don't clear the parent. + } else { + var wearProps = Entities.getEntityProperties(grabbedEntity); + wearProps.parentID = Uuid.NULL; + wearProps.parentJointIndex = -1; + delete wearProps.id; + delete wearProps.created; + delete wearProps.age; + delete wearProps.ageAsText; + delete wearProps.naturalDimensions; + delete wearProps.naturalPosition; + delete wearProps.actionData; + delete wearProps.sittingPoints; + delete wearProps.boundingBox; + delete wearProps.avatarEntity; + delete wearProps.owningAvatarID; + delete wearProps.localPosition; + delete wearProps.localRotation; + Entities.deleteEntity(grabbedEntity); + Entities.addEntity(wearProps); + } + } + } + } + + this.updateRelativeOffsets = function(entityID) { + // save the preferred (current) relative position and rotation into the user-data of the entity + var props = Entities.getEntityProperties(entityID); + if (props.parentID == MyAvatar.sessionUUID) { + grabData = getEntityCustomData('grabKey', entityID, {}); + var wearableData = getEntityCustomData('wearable', entityID, DEFAULT_WEARABLE_DATA); + var currentJointName = MyAvatar.getJointNames()[props.parentJointIndex]; + wearableData.joints[currentJointName] = [props.localPosition, props.localRotation]; + setEntityCustomData('wearable', entityID, wearableData); + return true; + } + return false; + } + + // this.saveAttachedEntities = function() { + // print("--- saving attached entities ---"); + // saveData = []; + // var nearbyEntities = Entities.findEntities(MyAvatar.position, ATTACHED_ENTITY_SEARCH_DISTANCE); + // for (i = 0; i < nearbyEntities.length; i++) { + // var entityID = nearbyEntities[i]; + // if (this.updateRelativeOffsets(entityID)) { + // var props = Entities.getEntityProperties(entityID); // refresh, because updateRelativeOffsets changed them + // this.scrubProperties(props); + // saveData.push(props); + // } + // } + // Settings.setValue(ATTACHED_ENTITIES_SETTINGS_KEY, JSON.stringify(saveData)); + // } + + // this.scrubProperties = function(props) { + // var toScrub = ["queryAACube", "position", "rotation", + // "created", "ageAsText", "naturalDimensions", + // "naturalPosition", "velocity", "acceleration", + // "angularVelocity", "boundingBox"]; + // toScrub.forEach(function(propertyName) { + // delete props[propertyName]; + // }); + // // if the userData has a grabKey, clear old state + // if ("userData" in props) { + // try { + // parsedUserData = JSON.parse(props.userData); + // if ("grabKey" in parsedUserData) { + // parsedUserData.grabKey.refCount = 0; + // delete parsedUserData.grabKey["avatarId"]; + // props["userData"] = JSON.stringify(parsedUserData); + // } + // } catch (e) { + // } + // } + // } + + // this.loadAttachedEntities = function(grabbedEntity) { + // print("--- loading attached entities ---"); + // jsonAttachmentData = Settings.getValue(ATTACHED_ENTITIES_SETTINGS_KEY); + // var loadData = []; + // try { + // loadData = JSON.parse(jsonAttachmentData); + // } catch (e) { + // print('error parsing saved attachment data'); + // return; + // } + + // for (i = 0; i < loadData.length; i ++) { + // var savedProps = loadData[ i ]; + // var currentProps = Entities.getEntityProperties(savedProps.id); + // if (currentProps.id == savedProps.id && + // // TODO -- also check that parentJointIndex matches? + // currentProps.parentID == MyAvatar.sessionUUID) { + // // entity is already in-world. TODO -- patch it up? + // continue; + // } + // this.scrubProperties(savedProps); + // delete savedProps["id"]; + // savedProps.parentID = MyAvatar.sessionUUID; // this will change between sessions + // var loadedEntityID = Entities.addEntity(savedProps, true); + + // Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + // action: 'loaded', + // grabbedEntity: loadedEntityID + // })); + // } + // } +} + +var manager = new AttachedEntitiesManager(); +manager.subscribeToMessages(); \ No newline at end of file diff --git a/scripts/simplifiedUI/system/audio.js b/scripts/simplifiedUI/system/audio.js new file mode 100644 index 0000000000..a161b40ffd --- /dev/null +++ b/scripts/simplifiedUI/system/audio.js @@ -0,0 +1,92 @@ +"use strict"; + +// +// audio.js +// +// Created by Howard Stearns on 2 Jun 2016 +// Copyright 2016 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 indent: ["error", 4, { "outerIIFEBody": 0 }] */ + +(function() { // BEGIN LOCAL_SCOPE + +var TABLET_BUTTON_NAME = "AUDIO"; +var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; +var AUDIO_QML_SOURCE = "hifi/audio/Audio.qml"; + +var MUTE_ICONS = { + icon: "icons/tablet-icons/mic-mute-i.svg", + activeIcon: "icons/tablet-icons/mic-mute-a.svg" +}; + +var UNMUTE_ICONS = { + icon: "icons/tablet-icons/mic-unmute-i.svg", + activeIcon: "icons/tablet-icons/mic-unmute-a.svg" +}; +var PTT_ICONS = { + icon: "icons/tablet-icons/mic-ptt-i.svg", + activeIcon: "icons/tablet-icons/mic-ptt-a.svg" +}; + +function onMuteToggled() { + if (Audio.pushToTalk) { + button.editProperties(PTT_ICONS); + } else if (Audio.muted) { + button.editProperties(MUTE_ICONS); + } else { + button.editProperties(UNMUTE_ICONS); + } +} + +var onAudioScreen = false; + +function onClicked() { + if (onAudioScreen) { + // for toolbar-mode: go back to home screen, this will close the window. + tablet.gotoHomeScreen(); + } else { + if (HMD.tabletID) { + Entities.editEntity(HMD.tabletID, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) }); + } + tablet.loadQMLSource(AUDIO_QML_SOURCE); + } +} + +function onScreenChanged(type, url) { + onAudioScreen = (type === "QML" && url === AUDIO_QML_SOURCE); + // for toolbar mode: change button to active when window is first openend, false otherwise. + button.editProperties({isActive: onAudioScreen}); +} + +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); +var button = tablet.addButton({ + icon: Audio.pushToTalk ? PTT_ICONS.icon : Audio.muted ? MUTE_ICONS.icon : UNMUTE_ICONS.icon, + activeIcon: Audio.pushToTalk ? PTT_ICONS.activeIcon : Audio.muted ? MUTE_ICONS.activeIcon : UNMUTE_ICONS.activeIcon, + text: TABLET_BUTTON_NAME, + sortOrder: 1 +}); + +onMuteToggled(); + +button.clicked.connect(onClicked); +tablet.screenChanged.connect(onScreenChanged); +Audio.mutedChanged.connect(onMuteToggled); +Audio.pushToTalkChanged.connect(onMuteToggled); +HMD.displayModeChanged.connect(onMuteToggled); + +Script.scriptEnding.connect(function () { + if (onAudioScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onClicked); + tablet.screenChanged.disconnect(onScreenChanged); + Audio.mutedChanged.disconnect(onMuteToggled); + Audio.pushToTalkChanged.disconnect(onMuteToggled); + HMD.displayModeChanged.disconnect(onMuteToggled); + tablet.removeButton(button); +}); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/audioMuteOverlay.js b/scripts/simplifiedUI/system/audioMuteOverlay.js new file mode 100644 index 0000000000..9acc5ab123 --- /dev/null +++ b/scripts/simplifiedUI/system/audioMuteOverlay.js @@ -0,0 +1,130 @@ +// +// audioMuteOverlay.js +// +// client script that creates an overlay to provide mute feedback +// +// Created by Triplelexx on 17/03/09 +// Reworked by Seth Alves on 2019-2-17 +// Copyright 2017 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 +// + +"use strict"; + +/* global Audio, Script, Overlays, Quat, MyAvatar, HMD */ + +(function() { // BEGIN LOCAL_SCOPE + + var lastShortTermInputLoudness = 0.0; + var lastLongTermInputLoudness = 0.0; + var sampleRate = 8.0; // Hz + + var shortTermAttackTC = Math.exp(-1.0 / (sampleRate * 0.500)); // 500 milliseconds attack + var shortTermReleaseTC = Math.exp(-1.0 / (sampleRate * 1.000)); // 1000 milliseconds release + + var longTermAttackTC = Math.exp(-1.0 / (sampleRate * 5.0)); // 5 second attack + var longTermReleaseTC = Math.exp(-1.0 / (sampleRate * 10.0)); // 10 seconds release + + var activationThreshold = 0.05; // how much louder short-term needs to be than long-term to trigger warning + + var holdReset = 2.0 * sampleRate; // 2 seconds hold + var holdCount = 0; + var warningOverlayID = null; + var pollInterval = null; + var warningText = "Muted"; + + function showWarning() { + if (warningOverlayID) { + return; + } + + if (HMD.active) { + warningOverlayID = Overlays.addOverlay("text3d", { + name: "Muted-Warning", + localPosition: { x: 0.0, y: -0.45, z: -1.0 }, + localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }), + text: warningText, + textAlpha: 1, + textColor: { red: 226, green: 51, blue: 77 }, + backgroundAlpha: 0, + lineHeight: 0.042, + dimensions: { x: 0.11, y: 0.05 }, + visible: true, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + parentID: MyAvatar.SELF_ID, + parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX") + }); + } + } + + function hideWarning() { + if (!warningOverlayID) { + return; + } + Overlays.deleteOverlay(warningOverlayID); + warningOverlayID = null; + } + + function startPoll() { + if (pollInterval) { + return; + } + pollInterval = Script.setInterval(function() { + var shortTermInputLoudness = Audio.inputLevel; + var longTermInputLoudness = shortTermInputLoudness; + + var shortTc = (shortTermInputLoudness > lastShortTermInputLoudness) ? shortTermAttackTC : shortTermReleaseTC; + var longTc = (longTermInputLoudness > lastLongTermInputLoudness) ? longTermAttackTC : longTermReleaseTC; + + shortTermInputLoudness += shortTc * (lastShortTermInputLoudness - shortTermInputLoudness); + longTermInputLoudness += longTc * (lastLongTermInputLoudness - longTermInputLoudness); + + lastShortTermInputLoudness = shortTermInputLoudness; + lastLongTermInputLoudness = longTermInputLoudness; + + if (shortTermInputLoudness > lastLongTermInputLoudness + activationThreshold) { + holdCount = holdReset; + } else { + holdCount = Math.max(holdCount - 1, 0); + } + + if (holdCount > 0) { + showWarning(); + } else { + hideWarning(); + } + }, 1000.0 / sampleRate); + } + + function stopPoll() { + if (!pollInterval) { + return; + } + Script.clearInterval(pollInterval); + pollInterval = null; + hideWarning(); + } + + function startOrStopPoll() { + if (Audio.warnWhenMuted && Audio.muted) { + startPoll(); + } else { + stopPoll(); + } + } + + function cleanup() { + stopPoll(); + } + + Script.scriptEnding.connect(cleanup); + + startOrStopPoll(); + Audio.mutedChanged.connect(startOrStopPoll); + Audio.warnWhenMutedChanged.connect(startOrStopPoll); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/audioScope.js b/scripts/simplifiedUI/system/audioScope.js new file mode 100644 index 0000000000..81d8e8fbd4 --- /dev/null +++ b/scripts/simplifiedUI/system/audioScope.js @@ -0,0 +1,95 @@ +"use strict"; +// +// audioScope.js +// scripts/system/ +// +// Created by Brad Hefta-Gaub on 3/10/2016 +// Copyright 2016 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 +// +/* global Script, Tablet, AudioScope, Audio */ + +(function () { // BEGIN LOCAL_SCOPE + + var scopeVisibile = AudioScope.getVisible(); + var scopePaused = AudioScope.getPause(); + var autoPause = false; + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var showScopeButton = tablet.addButton({ + icon: "icons/tablet-icons/scope.svg", + text: "Audio Scope", + isActive: scopeVisibile + }); + + var scopePauseImage = "icons/tablet-icons/scope-pause.svg"; + var scopePlayImage = "icons/tablet-icons/scope-play.svg"; + + var pauseScopeButton = tablet.addButton({ + icon: scopePaused ? scopePlayImage : scopePauseImage, + text: scopePaused ? "Unpause" : "Pause", + isActive: scopePaused + }); + + var autoPauseScopeButton = tablet.addButton({ + icon: "icons/tablet-icons/scope-auto.svg", + text: "Auto Pause", + isActive: autoPause + }); + + function setScopePause(paused) { + scopePaused = paused; + pauseScopeButton.editProperties({ + isActive: scopePaused, + icon: scopePaused ? scopePlayImage : scopePauseImage, + text: scopePaused ? "Unpause" : "Pause" + }); + AudioScope.setPause(scopePaused); + } + + showScopeButton.clicked.connect(function () { + // toggle button active state + scopeVisibile = !scopeVisibile; + showScopeButton.editProperties({ + isActive: scopeVisibile + }); + + AudioScope.setVisible(scopeVisibile); + }); + + pauseScopeButton.clicked.connect(function () { + // toggle button active state + setScopePause(!scopePaused); + }); + + autoPauseScopeButton.clicked.connect(function () { + // toggle button active state + autoPause = !autoPause; + autoPauseScopeButton.editProperties({ + isActive: autoPause, + text: autoPause ? "Auto Pause" : "Manual" + }); + }); + + Script.scriptEnding.connect(function () { + tablet.removeButton(showScopeButton); + tablet.removeButton(pauseScopeButton); + tablet.removeButton(autoPauseScopeButton); + }); + + Audio.noiseGateOpened.connect(function(){ + if (autoPause) { + setScopePause(false); + } + }); + + Audio.noiseGateClosed.connect(function(){ + // noise gate closed + if (autoPause) { + setScopePause(true); + } + }); + +}()); // END LOCAL_SCOPE \ No newline at end of file diff --git a/scripts/simplifiedUI/system/avatarFinderBeacon.js b/scripts/simplifiedUI/system/avatarFinderBeacon.js new file mode 100644 index 0000000000..00f3d15fbb --- /dev/null +++ b/scripts/simplifiedUI/system/avatarFinderBeacon.js @@ -0,0 +1,93 @@ +// avatarFinderBeacon.js +// +// Created by Thijs Wenker on 12/7/16 +// Copyright 2016 High Fidelity, Inc. +// +// Shows 2km long red beams for each avatar outside of the 20 meter radius of your avatar, tries to ignore AC Agents. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +var MIN_DISPLAY_DISTANCE = 20.0; // meters +var BEAM_COLOR = {red: 255, green: 0, blue: 0}; +var SHOW_THROUGH_WALLS = false; +var BEACON_LENGTH = 2000.0; // meters +var TRY_TO_IGNORE_AC_AGENTS = true; + +var HALF_BEACON_LENGTH = BEACON_LENGTH / 2.0; + +var beacons = {}; + +// List of .fst files used by AC scripts, that should be ignored in the script in case TRY_TO_IGNORE_AC_AGENTS is enabled +var POSSIBLE_AC_AVATARS = [ + 'http://hifi-content.s3.amazonaws.com/ozan/dev/avatars/invisible_avatar/invisible_avatar.fst', + 'http://hifi-content.s3.amazonaws.com/ozan/dev/avatars/camera_man/pod/_latest/camera_man_pod.fst' +]; + +AvatarFinderBeacon = function(avatar) { + var visible = false; + var avatarSessionUUID = avatar.sessionUUID; + this.overlay = Overlays.addOverlay('line3d', { + color: BEAM_COLOR, + dashed: false, + start: Vec3.sum(avatar.position, {x: 0, y: -HALF_BEACON_LENGTH, z: 0}), + end: Vec3.sum(avatar.position, {x: 0, y: HALF_BEACON_LENGTH, z: 0}), + rotation: {x: 0, y: 0, z: 0, w: 1}, + visible: visible, + drawInFront: SHOW_THROUGH_WALLS, + ignoreRayIntersection: true, + parentID: avatarSessionUUID, + parentJointIndex: -2 + }); + this.cleanup = function() { + Overlays.deleteOverlay(this.overlay); + }; + this.shouldShow = function() { + return Vec3.distance(MyAvatar.position, avatar.position) >= MIN_DISPLAY_DISTANCE; + }; + this.update = function() { + avatar = AvatarList.getAvatar(avatarSessionUUID); + Overlays.editOverlay(this.overlay, { + visible: this.shouldShow() + }); + }; +}; + +function updateBeacon(avatarSessionUUID) { + if (!(avatarSessionUUID in beacons)) { + var avatar = AvatarList.getAvatar(avatarSessionUUID); + if (TRY_TO_IGNORE_AC_AGENTS + && (POSSIBLE_AC_AVATARS.indexOf(avatar.skeletonModelURL) !== -1 || Vec3.length(avatar.position) === 0.0)) { + return; + } + beacons[avatarSessionUUID] = new AvatarFinderBeacon(avatar); + return; + } + beacons[avatarSessionUUID].update(); +} + +Window.domainChanged.connect(function () { + beacons = {}; +}); + +Script.update.connect(function() { + AvatarList.getAvatarIdentifiers().forEach(function(avatarSessionUUID) { + updateBeacon(avatarSessionUUID); + }); +}); + +AvatarList.avatarRemovedEvent.connect(function(avatarSessionUUID) { + if (avatarSessionUUID in beacons) { + beacons[avatarSessionUUID].cleanup(); + delete beacons[avatarSessionUUID]; + } +}); + +Script.scriptEnding.connect(function() { + for (var sessionUUID in beacons) { + if (!beacons.hasOwnProperty(sessionUUID)) { + return; + } + beacons[sessionUUID].cleanup(); + } +}); diff --git a/scripts/simplifiedUI/system/avatarapp.js b/scripts/simplifiedUI/system/avatarapp.js new file mode 100644 index 0000000000..6439d30023 --- /dev/null +++ b/scripts/simplifiedUI/system/avatarapp.js @@ -0,0 +1,663 @@ +"use strict"; +/*jslint vars:true, plusplus:true, forin:true*/ +/*global Tablet, Script, Entities, MyAvatar, Camera, Quat, HMD, Account, UserActivityLogger, Messages, print, + AvatarBookmarks, ContextOverlay, AddressManager +*/ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +// +// avatarapp.js +// +// Created by Alexander Ivash on April 30, 2018 +// Copyright 2016 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 +// + +(function() { // BEGIN LOCAL_SCOPE + +var AVATARAPP_QML_SOURCE = "hifi/AvatarApp.qml"; +Script.include("/~/system/libraries/controllers.js"); + +// constants from AvatarBookmarks.h +var ENTRY_AVATAR_URL = "avatarUrl"; +var ENTRY_AVATAR_ENTITIES = "avatarEntites"; +var ENTRY_AVATAR_SCALE = "avatarScale"; + +function executeLater(callback) { + Script.setTimeout(callback, 300); +} + +function isWearable(avatarEntity) { + return avatarEntity.properties.visible === true && + (avatarEntity.properties.parentID === MyAvatar.sessionUUID || avatarEntity.properties.parentID === MyAvatar.SELF_ID); +} + +function getMyAvatarWearables() { + var entitiesArray = MyAvatar.getAvatarEntitiesVariant(); + var wearablesArray = []; + + for (var i = 0; i < entitiesArray.length; ++i) { + var entity = entitiesArray[i]; + if (!isWearable(entity)) { + continue; + } + + var localRotation = entity.properties.localRotation; + entity.properties.localRotationAngles = Quat.safeEulerAngles(localRotation); + wearablesArray.push(entity); + } + + return wearablesArray; +} + +function getMyAvatar() { + var avatar = {}; + avatar[ENTRY_AVATAR_URL] = MyAvatar.skeletonModelURL; + avatar[ENTRY_AVATAR_SCALE] = MyAvatar.getAvatarScale(); + avatar[ENTRY_AVATAR_ENTITIES] = getMyAvatarWearables(); + return avatar; +} + +function getMyAvatarSettings() { + return { + dominantHand: MyAvatar.getDominantHand(), + hmdAvatarAlignmentType: MyAvatar.getHmdAvatarAlignmentType(), + collisionsEnabled: MyAvatar.getCollisionsEnabled(), + otherAvatarsCollisionsEnabled: MyAvatar.getOtherAvatarsCollisionsEnabled(), + collisionSoundUrl : MyAvatar.collisionSoundURL, + animGraphUrl: MyAvatar.getAnimGraphUrl(), + animGraphOverrideUrl : MyAvatar.getAnimGraphOverrideUrl(), + }; +} + +function updateAvatarWearables(avatar, callback, wearablesOverride) { + executeLater(function() { + var wearables = wearablesOverride ? wearablesOverride : getMyAvatarWearables(); + avatar[ENTRY_AVATAR_ENTITIES] = wearables; + + sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables}); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); + + if(callback) + callback(); + }); +} + +var adjustWearables = { + opened : false, + cameraMode : '', + setOpened : function(value) { + if(this.opened !== value) { + if(value) { + this.cameraMode = Camera.mode; + + if(!HMD.active) { + Camera.mode = 'mirror'; + } + } else { + Camera.mode = this.cameraMode; + } + + this.opened = value; + } + } +}; + +var currentAvatarWearablesBackup = null; +var currentAvatar = null; +var currentAvatarSettings = getMyAvatarSettings(); + +var notifyScaleChanged = true; +function onTargetScaleChanged() { + if(currentAvatar.scale !== MyAvatar.getAvatarScale()) { + currentAvatar.scale = MyAvatar.getAvatarScale(); + if(notifyScaleChanged) { + sendToQml({'method' : 'scaleChanged', 'value' : currentAvatar.scale}); + } + } +} + +function onSkeletonModelURLChanged() { + if(currentAvatar || (currentAvatar.skeletonModelURL !== MyAvatar.skeletonModelURL)) { + fromQml({'method' : 'getAvatars'}); + } +} + +function onDominantHandChanged(dominantHand) { + if(currentAvatarSettings.dominantHand !== dominantHand) { + currentAvatarSettings.dominantHand = dominantHand; + sendToQml({'method' : 'settingChanged', 'name' : 'dominantHand', 'value' : dominantHand}); + } +} + +function onHmdAvatarAlignmentTypeChanged(type) { + if (currentAvatarSettings.hmdAvatarAlignmentType !== type) { + currentAvatarSettings.hmdAvatarAlignmentType = type; + sendToQml({'method' : 'settingChanged', 'name' : 'hmdAvatarAlignmentType', 'value' : type}); + } +} + +function onCollisionsEnabledChanged(enabled) { + if(currentAvatarSettings.collisionsEnabled !== enabled) { + currentAvatarSettings.collisionsEnabled = enabled; + sendToQml({'method' : 'settingChanged', 'name' : 'collisionsEnabled', 'value' : enabled}); + } +} + +function onOtherAvatarsCollisionsEnabledChanged(enabled) { + if (currentAvatarSettings.otherAvatarsCollisionsEnabled !== enabled) { + currentAvatarSettings.otherAvatarsCollisionsEnabled = enabled; + sendToQml({ 'method': 'settingChanged', 'name': 'otherAvatarsCollisionsEnabled', 'value': enabled }); + } +} + +function onNewCollisionSoundUrl(url) { + if(currentAvatarSettings.collisionSoundUrl !== url) { + currentAvatarSettings.collisionSoundUrl = url; + sendToQml({'method' : 'settingChanged', 'name' : 'collisionSoundUrl', 'value' : url}); + } +} + +function onAnimGraphUrlChanged(url) { + if (currentAvatarSettings.animGraphUrl !== url) { + currentAvatarSettings.animGraphUrl = url; + sendToQml({ 'method': 'settingChanged', 'name': 'animGraphUrl', 'value': currentAvatarSettings.animGraphUrl }); + + if (currentAvatarSettings.animGraphOverrideUrl !== MyAvatar.getAnimGraphOverrideUrl()) { + currentAvatarSettings.animGraphOverrideUrl = MyAvatar.getAnimGraphOverrideUrl(); + sendToQml({ 'method': 'settingChanged', 'name': 'animGraphOverrideUrl', + 'value': currentAvatarSettings.animGraphOverrideUrl }); + } + } +} + +var selectedAvatarEntityID = null; +var grabbedAvatarEntityChangeNotifier = null; + +var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; +var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; +var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("html/js/marketplacesInject.js"); + +function getWearablesFrozen() { + var wearablesFrozen = true; + var wearablesArray = getMyAvatarWearables(); + wearablesArray.forEach(function(wearable) { + if (isGrabbable(wearable.id)) { + wearablesFrozen = false; + } + }); + + return wearablesFrozen; +} + +function freezeWearables() { + var wearablesArray = getMyAvatarWearables(); + wearablesArray.forEach(function(wearable) { + setGrabbable(wearable.id, false); + }); +} + +function unfreezeWearables() { + var wearablesArray = getMyAvatarWearables(); + wearablesArray.forEach(function(wearable) { + setGrabbable(wearable.id, true); + }); +} + + +function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. + switch (message.method) { + case 'getAvatars': + currentAvatar = getMyAvatar(); + currentAvatarSettings = getMyAvatarSettings(); + + message.data = { + 'bookmarks' : AvatarBookmarks.getBookmarks(), + 'displayName' : MyAvatar.displayName, + 'currentAvatar' : currentAvatar, + 'currentAvatarSettings' : currentAvatarSettings + }; + + for(var bookmarkName in message.data.bookmarks) { + var bookmark = message.data.bookmarks[bookmarkName]; + + if (bookmark.avatarEntites) { + bookmark.avatarEntites.forEach(function(avatarEntity) { + avatarEntity.properties.localRotationAngles = Quat.safeEulerAngles(avatarEntity.properties.localRotation); + }); + } + } + + sendToQml(message); + break; + case 'selectAvatar': + Entities.addingWearable.disconnect(onAddingWearable); + Entities.deletingWearable.disconnect(onDeletingWearable); + AvatarBookmarks.loadBookmark(message.name); + Entities.addingWearable.connect(onAddingWearable); + Entities.deletingWearable.connect(onDeletingWearable); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); + break; + case 'deleteAvatar': + AvatarBookmarks.removeBookmark(message.name); + break; + case 'addAvatar': + AvatarBookmarks.addBookmark(message.name); + break; + case 'adjustWearable': + if(message.properties.localRotationAngles) { + message.properties.localRotation = Quat.fromVec3Degrees(message.properties.localRotationAngles); + } + + Entities.editEntity(message.entityID, message.properties); + message.properties = Entities.getEntityProperties(message.entityID, Object.keys(message.properties)); + + if(message.properties.localRotation) { + message.properties.localRotationAngles = Quat.safeEulerAngles(message.properties.localRotation); + } + + sendToQml({'method' : 'wearableUpdated', 'entityID' : message.entityID, wearableIndex : message.wearableIndex, properties : message.properties, updateUI : false}); + break; + case 'adjustWearablesOpened': + currentAvatarWearablesBackup = getMyAvatarWearables(); + adjustWearables.setOpened(true); + unfreezeWearables(); + + Entities.mousePressOnEntity.connect(onSelectedEntity); + Messages.subscribe('Hifi-Object-Manipulation'); + Messages.messageReceived.connect(handleWearableMessages); + break; + case 'adjustWearablesClosed': + if(!message.save) { + // revert changes using snapshot of wearables + if(currentAvatarWearablesBackup !== null) { + AvatarBookmarks.updateAvatarEntities(currentAvatarWearablesBackup); + updateAvatarWearables(currentAvatar, null, currentAvatarWearablesBackup); + } + } else { + sendToQml({'method' : 'updateAvatarInBookmarks'}); + } + + adjustWearables.setOpened(false); + ensureWearableSelected(null); + Entities.mousePressOnEntity.disconnect(onSelectedEntity); + Messages.messageReceived.disconnect(handleWearableMessages); + Messages.unsubscribe('Hifi-Object-Manipulation'); + break; + case 'addWearable': + + var joints = MyAvatar.getJointNames(); + var hipsIndex = -1; + + for(var i = 0; i < joints.length; ++i) { + if(joints[i] === 'Hips') { + hipsIndex = i; + break; + } + } + + var properties = { + name: "Custom wearable", + type: "Model", + modelURL: message.url, + parentID: MyAvatar.sessionUUID, + relayParentJoints: false, + parentJointIndex: hipsIndex + }; + + Entities.addingWearable.disconnect(onAddingWearable); + var entityID = Entities.addEntity(properties, true); + Entities.addingWearable.connect(onAddingWearable); + + updateAvatarWearables(currentAvatar, function() { + onSelectedEntity(entityID); + }); + break; + case 'selectWearable': + ensureWearableSelected(message.entityID); + break; + case 'deleteWearable': + + Entities.deletingWearable.disconnect(onDeletingWearable); + Entities.deleteEntity(message.entityID); + Entities.deletingWearable.connect(onDeletingWearable); + + updateAvatarWearables(currentAvatar); + break; + case 'changeDisplayName': + if (MyAvatar.displayName !== message.displayName) { + MyAvatar.displayName = message.displayName; + UserActivityLogger.palAction("display_name_change", message.displayName); + } + break; + case 'applyExternalAvatar': + var currentAvatarURL = MyAvatar.getFullAvatarURLFromPreferences(); + if(currentAvatarURL !== message.avatarURL) { + MyAvatar.useFullAvatarURL(message.avatarURL); + sendToQml({'method' : 'externalAvatarApplied', 'avatarURL' : message.avatarURL}); + } + break; + case 'navigate': + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + if(message.url.indexOf('app://') === 0) { + if (message.url === 'app://marketplace') { + tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); + } else if (message.url === 'app://purchases') { + tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); + } + + } else if(message.url.indexOf('hifi://') === 0) { + AddressManager.handleLookupString(message.url, false); + } else if(message.url.indexOf('https://') === 0 || message.url.indexOf('http://') === 0) { + tablet.gotoWebScreen(message.url, MARKETPLACES_INJECT_SCRIPT_URL); + } + + break; + case 'setScale': + notifyScaleChanged = false; + MyAvatar.setAvatarScale(message.avatarScale); + currentAvatar.avatarScale = message.avatarScale; + notifyScaleChanged = true; + break; + case 'revertScale': + MyAvatar.setAvatarScale(message.avatarScale); + currentAvatar.avatarScale = message.avatarScale; + break; + case 'saveSettings': + MyAvatar.setAvatarScale(message.avatarScale); + currentAvatar.avatarScale = message.avatarScale; + + MyAvatar.setDominantHand(message.settings.dominantHand); + MyAvatar.setHmdAvatarAlignmentType(message.settings.hmdAvatarAlignmentType); + MyAvatar.setOtherAvatarsCollisionsEnabled(message.settings.otherAvatarsCollisionsEnabled); + MyAvatar.setCollisionsEnabled(message.settings.collisionsEnabled); + MyAvatar.collisionSoundURL = message.settings.collisionSoundUrl; + MyAvatar.setAnimGraphOverrideUrl(message.settings.animGraphOverrideUrl); + + currentAvatarSettings = getMyAvatarSettings(); + break; + case 'toggleWearablesFrozen': + var wearablesFrozen = getWearablesFrozen(); + wearablesFrozen = !wearablesFrozen; + if (wearablesFrozen) { + freezeWearables(); + } else { + unfreezeWearables(); + } + sendToQml({'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : wearablesFrozen}); + break; + default: + print('Unrecognized message from AvatarApp.qml'); + } +} + +function isGrabbable(entityID) { + if(entityID === null) { + return false; + } + + var properties = Entities.getEntityProperties(entityID, ['avatarEntity', 'grab.grabbable']); + if (properties.avatarEntity) { + return properties.grab.grabbable; + } + + return false; +} + +function setGrabbable(entityID, grabbable) { + var properties = Entities.getEntityProperties(entityID, ['avatarEntity', 'grab.grabbable']); + if (properties.avatarEntity && properties.grab.grabbable != grabbable) { + var editProps = { grab: { grabbable: grabbable }}; + Entities.editEntity(entityID, editProps); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); + } +} + +function ensureWearableSelected(entityID) { + if(selectedAvatarEntityID !== entityID) { + if(grabbedAvatarEntityChangeNotifier !== null) { + Script.clearInterval(grabbedAvatarEntityChangeNotifier); + grabbedAvatarEntityChangeNotifier = null; + } + selectedAvatarEntityID = entityID; + return true; + } + + return false; +} + +function isEntityBeingWorn(entityID) { + return Entities.getEntityProperties(entityID, 'parentID').parentID === MyAvatar.sessionUUID; +} + +function onSelectedEntity(entityID, pointerEvent) { + if(selectedAvatarEntityID !== entityID && isEntityBeingWorn(entityID)) + { + if(ensureWearableSelected(entityID)) { + sendToQml({'method' : 'selectAvatarEntity', 'entityID' : selectedAvatarEntityID}); + } + } +} + +function onAddingWearable(entityID) { + updateAvatarWearables(currentAvatar, function() { + sendToQml({'method' : 'updateAvatarInBookmarks'}); + }); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); +} + +function onDeletingWearable(entityID) { + updateAvatarWearables(currentAvatar, function() { + sendToQml({'method' : 'updateAvatarInBookmarks'}); + }); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); +} + +function handleWearableMessages(channel, message, sender) { + if (channel !== 'Hifi-Object-Manipulation') { + return; + } + + var parsedMessage = null; + + try { + parsedMessage = JSON.parse(message); + } catch (e) { + return; + } + + var entityID = parsedMessage.grabbedEntity; + + var updateWearable = function() { + // for some reasons Entities.getEntityProperties returns more than was asked.. + var propertyNames = ['localPosition', 'localRotation', 'dimensions', 'naturalDimensions']; + var entityProperties = Entities.getEntityProperties(selectedAvatarEntityID, propertyNames); + var properties = {}; + + propertyNames.forEach(function(propertyName) { + properties[propertyName] = entityProperties[propertyName]; + }); + + properties.localRotationAngles = Quat.safeEulerAngles(properties.localRotation); + sendToQml({'method' : 'wearableUpdated', 'entityID' : selectedAvatarEntityID, + 'wearableIndex' : -1, 'properties' : properties, updateUI : true}); + + }; + + if(parsedMessage.action === 'grab') { + if(selectedAvatarEntityID !== entityID) { + ensureWearableSelected(entityID); + sendToQml({'method' : 'selectAvatarEntity', 'entityID' : selectedAvatarEntityID}); + } + + grabbedAvatarEntityChangeNotifier = Script.setInterval(updateWearable, 1000); + } else if(parsedMessage.action === 'release') { + if(grabbedAvatarEntityChangeNotifier !== null) { + Script.clearInterval(grabbedAvatarEntityChangeNotifier); + grabbedAvatarEntityChangeNotifier = null; + updateWearable(); + } + } +} + +function sendToQml(message) { + tablet.sendToQml(message); +} + +function onBookmarkLoaded(bookmarkName) { + executeLater(function() { + currentAvatar = getMyAvatar(); + sendToQml({'method' : 'bookmarkLoaded', 'data' : {'name' : bookmarkName, 'currentAvatar' : currentAvatar} }); + }); +} + +function onBookmarkDeleted(bookmarkName) { + sendToQml({'method' : 'bookmarkDeleted', 'name' : bookmarkName}); +} + +function onBookmarkAdded(bookmarkName) { + var bookmark = AvatarBookmarks.getBookmark(bookmarkName); + bookmark.avatarEntites.forEach(function(avatarEntity) { + avatarEntity.properties.localRotationAngles = Quat.safeEulerAngles(avatarEntity.properties.localRotation); + }); + + sendToQml({ 'method': 'bookmarkAdded', 'bookmarkName': bookmarkName, 'bookmark': bookmark }); +} + +// +// Manage the connection between the button and the window. +// +var button; +var buttonName = "AVATAR"; +var tablet = null; + +function startup() { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton({ + text: buttonName, + icon: "icons/tablet-icons/avatar-i.svg", + activeIcon: "icons/tablet-icons/avatar-a.svg", + sortOrder: 7 + }); + button.clicked.connect(onTabletButtonClicked); + tablet.screenChanged.connect(onTabletScreenChanged); +} + +startup(); + +var isWired = false; +function off() { + if(adjustWearables.opened) { + adjustWearables.setOpened(false); + ensureWearableSelected(null); + Entities.mousePressOnEntity.disconnect(onSelectedEntity); + + Messages.messageReceived.disconnect(handleWearableMessages); + Messages.unsubscribe('Hifi-Object-Manipulation'); + } + + if (isWired) { // It is not ok to disconnect these twice, hence guard. + isWired = false; + + AvatarBookmarks.bookmarkLoaded.disconnect(onBookmarkLoaded); + AvatarBookmarks.bookmarkDeleted.disconnect(onBookmarkDeleted); + AvatarBookmarks.bookmarkAdded.disconnect(onBookmarkAdded); + + Entities.addingWearable.disconnect(onAddingWearable); + Entities.deletingWearable.disconnect(onDeletingWearable); + MyAvatar.skeletonModelURLChanged.disconnect(onSkeletonModelURLChanged); + MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged); + MyAvatar.hmdAvatarAlignmentTypeChanged.disconnect(onHmdAvatarAlignmentTypeChanged); + MyAvatar.collisionsEnabledChanged.disconnect(onCollisionsEnabledChanged); + MyAvatar.otherAvatarsCollisionsEnabledChanged.disconnect(onOtherAvatarsCollisionsEnabledChanged); + MyAvatar.newCollisionSoundURL.disconnect(onNewCollisionSoundUrl); + MyAvatar.animGraphUrlChanged.disconnect(onAnimGraphUrlChanged); + MyAvatar.targetScaleChanged.disconnect(onTargetScaleChanged); + } +} + +function on() { + + if (!isWired) { // It is not ok to connect these twice, hence guard. + isWired = true; + + AvatarBookmarks.bookmarkLoaded.connect(onBookmarkLoaded); + AvatarBookmarks.bookmarkDeleted.connect(onBookmarkDeleted); + AvatarBookmarks.bookmarkAdded.connect(onBookmarkAdded); + + Entities.addingWearable.connect(onAddingWearable); + Entities.deletingWearable.connect(onDeletingWearable); + MyAvatar.skeletonModelURLChanged.connect(onSkeletonModelURLChanged); + MyAvatar.dominantHandChanged.connect(onDominantHandChanged); + MyAvatar.hmdAvatarAlignmentTypeChanged.connect(onHmdAvatarAlignmentTypeChanged); + MyAvatar.collisionsEnabledChanged.connect(onCollisionsEnabledChanged); + MyAvatar.otherAvatarsCollisionsEnabledChanged.connect(onOtherAvatarsCollisionsEnabledChanged); + MyAvatar.newCollisionSoundURL.connect(onNewCollisionSoundUrl); + MyAvatar.animGraphUrlChanged.connect(onAnimGraphUrlChanged); + MyAvatar.targetScaleChanged.connect(onTargetScaleChanged); + } +} + +function onTabletButtonClicked() { + if (onAvatarAppScreen) { + // for toolbar-mode: go back to home screen, this will close the window. + tablet.gotoHomeScreen(); + } else { + ContextOverlay.enabled = false; + tablet.loadQMLSource(AVATARAPP_QML_SOURCE); + } +} +var hasEventBridge = false; +function wireEventBridge(on) { + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQml); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQml); + hasEventBridge = false; + } + } +} + +var onAvatarAppScreen = false; +function onTabletScreenChanged(type, url) { + var onAvatarAppScreenNow = (type === "QML" && url === AVATARAPP_QML_SOURCE); + wireEventBridge(onAvatarAppScreenNow); + // for toolbar mode: change button to active when window is first openend, false otherwise. + button.editProperties({isActive: onAvatarAppScreenNow}); + + if (!onAvatarAppScreen && onAvatarAppScreenNow) { + on(); + } else if(onAvatarAppScreen && !onAvatarAppScreenNow) { + off(); + } + + onAvatarAppScreen = onAvatarAppScreenNow; + + if(onAvatarAppScreenNow) { + sendToQml({ 'method' : 'initialize', 'data' : { jointNames : MyAvatar.getJointNames() }}); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); + } +} + +function shutdown() { + if (onAvatarAppScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onTabletButtonClicked); + tablet.removeButton(button); + tablet.screenChanged.disconnect(onTabletScreenChanged); + + off(); +} + +// +// Cleanup. +// +Script.scriptEnding.connect(shutdown); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/bubble.js b/scripts/simplifiedUI/system/bubble.js new file mode 100644 index 0000000000..eca3b3dcd4 --- /dev/null +++ b/scripts/simplifiedUI/system/bubble.js @@ -0,0 +1,206 @@ +"use strict"; + +// +// bubble.js +// scripts/system/ +// +// Created by Brad Hefta-Gaub on 11/18/2016 +// Copyright 2016 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 +// +/* global Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation, UserActivityLogger */ + +(function () { // BEGIN LOCAL_SCOPE + var button; + // Used for animating and disappearing the bubble + var bubbleOverlayTimestamp; + // Used for rate limiting the bubble sound + var lastBubbleSoundTimestamp = 0; + // Affects bubble height + var BUBBLE_HEIGHT_SCALE = 0.15; + // The bubble model itself + var bubbleOverlay = Overlays.addOverlay("model", { + url: Script.resolvePath("assets/models/Bubble-v14.fbx"), // If you'd like to change the model, modify this line (and the dimensions below) + dimensions: { x: MyAvatar.sensorToWorldScale, y: 0.75 * MyAvatar.sensorToWorldScale, z: MyAvatar.sensorToWorldScale }, + position: { x: MyAvatar.position.x, y: -MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, z: MyAvatar.position.z }, + rotation: Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({x: 0.0, y: 180.0, z: 0.0})), + scale: { x: 2 , y: MyAvatar.scale * 0.5 + 0.5, z: 2 }, + visible: false, + ignoreRayIntersection: true + }); + // The bubble activation sound + var bubbleActivateSound = SoundCache.getSound(Script.resolvePath("assets/sounds/bubble.wav")); + // Is the update() function connected? + var updateConnected = false; + + var BUBBLE_VISIBLE_DURATION_MS = 3000; + var BUBBLE_RAISE_ANIMATION_DURATION_MS = 750; + var BUBBLE_SOUND_RATE_LIMIT_MS = 15000; + + // Hides the bubble model overlay + function hideOverlays() { + Overlays.editOverlay(bubbleOverlay, { + visible: false + }); + } + + // Make the bubble overlay visible, set its position, and play the sound + function createOverlays() { + var nowTimestamp = Date.now(); + if (nowTimestamp - lastBubbleSoundTimestamp >= BUBBLE_SOUND_RATE_LIMIT_MS) { + Audio.playSound(bubbleActivateSound, { + position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z }, + localOnly: true, + volume: 0.2 + }); + lastBubbleSoundTimestamp = nowTimestamp; + } + hideOverlays(); + if (updateConnected === true) { + updateConnected = false; + Script.update.disconnect(update); + } + + Overlays.editOverlay(bubbleOverlay, { + dimensions: { + x: MyAvatar.sensorToWorldScale, + y: 0.75 * MyAvatar.sensorToWorldScale, + z: MyAvatar.sensorToWorldScale + }, + position: { + x: MyAvatar.position.x, + y: -MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, + z: MyAvatar.position.z + }, + rotation: Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({x: 0.0, y: 180.0, z: 0.0})), + scale: { + x: 2 , + y: MyAvatar.scale * 0.5 + 0.5 , + z: 2 + }, + visible: true + }); + bubbleOverlayTimestamp = nowTimestamp; + Script.update.connect(update); + updateConnected = true; + } + + // Called from the C++ scripting interface to show the bubble overlay + function enteredIgnoreRadius() { + createOverlays(); + UserActivityLogger.privacyShieldActivated(); + } + + // Used to set the state of the bubble HUD button + function writeButtonProperties(parameter) { + button.editProperties({isActive: parameter}); + } + + // The bubble script's update function + function update() { + var timestamp = Date.now(); + var delay = (timestamp - bubbleOverlayTimestamp); + var overlayAlpha = 1.0 - (delay / BUBBLE_VISIBLE_DURATION_MS); + if (overlayAlpha > 0) { + if (delay < BUBBLE_RAISE_ANIMATION_DURATION_MS) { + Overlays.editOverlay(bubbleOverlay, { + dimensions: { + x: MyAvatar.sensorToWorldScale, + y: 0.75 * MyAvatar.sensorToWorldScale, + z: MyAvatar.sensorToWorldScale + }, + // Quickly raise the bubble from the ground up + position: { + x: MyAvatar.position.x, + y: (-((BUBBLE_RAISE_ANIMATION_DURATION_MS - delay) / BUBBLE_RAISE_ANIMATION_DURATION_MS)) * MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, + z: MyAvatar.position.z + }, + rotation: Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({x: 0.0, y: 180.0, z: 0.0})), + scale: { + x: 2 , + y: ((1 - ((BUBBLE_RAISE_ANIMATION_DURATION_MS - delay) / BUBBLE_RAISE_ANIMATION_DURATION_MS)) * MyAvatar.scale * 0.5 + 0.5), + z: 2 + } + }); + } else { + // Keep the bubble in place for a couple seconds + Overlays.editOverlay(bubbleOverlay, { + dimensions: { + x: MyAvatar.sensorToWorldScale, + y: 0.75 * MyAvatar.sensorToWorldScale, + z: MyAvatar.sensorToWorldScale + }, + position: { + x: MyAvatar.position.x, + y: MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, + z: MyAvatar.position.z + }, + rotation: Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({x: 0.0, y: 180.0, z: 0.0})), + scale: { + x: 2, + y: MyAvatar.scale * 0.5 + 0.5 , + z: 2 + } + }); + } + } else { + hideOverlays(); + if (updateConnected === true) { + Script.update.disconnect(update); + updateConnected = false; + } + } + } + + // When the space bubble is toggled... + // NOTE: the c++ calls this with just the first param -- we added a second + // just for not logging the initial state of the bubble when we startup. + function onBubbleToggled(enabled, doNotLog) { + writeButtonProperties(enabled); + if (doNotLog !== true) { + UserActivityLogger.privacyShieldToggled(enabled); + } + if (enabled) { + createOverlays(); + } else { + hideOverlays(); + if (updateConnected === true) { + Script.update.disconnect(update); + updateConnected = false; + } + } + } + + // Setup the bubble button + var buttonName = "SHIELD"; + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton({ + icon: "icons/tablet-icons/bubble-i.svg", + activeIcon: "icons/tablet-icons/bubble-a.svg", + text: buttonName, + sortOrder: 4 + }); + + onBubbleToggled(Users.getIgnoreRadiusEnabled(), true); // pass in true so we don't log this initial one in the UserActivity table + + button.clicked.connect(Users.toggleIgnoreRadius); + Users.ignoreRadiusEnabledChanged.connect(onBubbleToggled); + Users.enteredIgnoreRadius.connect(enteredIgnoreRadius); + + // Cleanup the tablet button and overlays when script is stopped + Script.scriptEnding.connect(function () { + button.clicked.disconnect(Users.toggleIgnoreRadius); + if (tablet) { + tablet.removeButton(button); + } + Users.ignoreRadiusEnabledChanged.disconnect(onBubbleToggled); + Users.enteredIgnoreRadius.disconnect(enteredIgnoreRadius); + Overlays.deleteOverlay(bubbleOverlay); + if (updateConnected === true) { + Script.update.disconnect(update); + } + }); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/chat.js b/scripts/simplifiedUI/system/chat.js new file mode 100644 index 0000000000..749665f3d8 --- /dev/null +++ b/scripts/simplifiedUI/system/chat.js @@ -0,0 +1,1010 @@ +"use strict"; + +// Chat.js +// By Don Hopkins (dhopkins@donhopkins.com) +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + + var webPageURL = Script.resolvePath("html/ChatPage.html"); // URL of tablet web page. + var randomizeWebPageURL = true; // Set to true for debugging. + var lastWebPageURL = ""; // Last random URL of tablet web page. + var onChatPage = false; // True when chat web page is opened. + var webHandlerConnected = false; // True when the web handler has been connected. + var channelName = "Chat"; // Unique name for channel that we listen to. + var tabletButtonName = "CHAT"; // Tablet button label. + var tabletButtonIcon = "icons/tablet-icons/menu-i.svg"; // Icon for chat button. + var tabletButtonActiveIcon = "icons/tablet-icons/menu-a.svg"; // Active icon for chat button. + var tabletButton = null; // The button we create in the tablet. + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); // The awesome tablet. + var chatLog = []; // Array of chat messages in the form of [avatarID, displayName, message, data]. + var avatarIdentifiers = {}; // Map of avatar ids to dict of identifierParams. + var speechBubbleShowing = false; // Is the speech bubble visible? + var speechBubbleMessage = null; // The message shown in the speech bubble. + var speechBubbleData = null; // The data of the speech bubble message. + var speechBubbleTextID = null; // The id of the speech bubble local text entity. + var speechBubbleTimer = null; // The timer to pop down the speech bubble. + var speechBubbleParams = null; // The params used to create or edit the speech bubble. + + // Persistent variables saved in the Settings. + var chatName = ''; // The user's name shown in chat. + var chatLogMaxSize = 100; // The maximum number of chat messages we remember. + var sendTyping = true; // Send typing begin and end notification. + var identifyAvatarDuration = 10; // How long to leave the avatar identity line up, in seconds. + var identifyAvatarLineColor = { red: 0, green: 255, blue: 0 }; // The color of the avatar identity line. + var identifyAvatarMyJointName = 'Head'; // My bone from which to draw the avatar identity line. + var identifyAvatarYourJointName = 'Head'; // Your bone to which to draw the avatar identity line. + var speechBubbleDuration = 10; // How long to leave the speech bubble up, in seconds. + var speechBubbleTextColor = {red: 255, green: 255, blue: 255}; // The text color of the speech bubble. + var speechBubbleBackgroundColor = {red: 0, green: 0, blue: 0}; // The background color of the speech bubble. + var speechBubbleOffset = {x: 0, y: 0.3, z: 0.0}; // The offset from the joint to whic the speech bubble is attached. + var speechBubbleJointName = 'Head'; // The name of the joint to which the speech bubble is attached. + var speechBubbleLineHeight = 0.05; // The height of a line of text in the speech bubble. + var SPEECH_BUBBLE_MAX_WIDTH = 1; // meters + + var textSizeOverlay = Overlays.addOverlay("text3d", { + position: MyAvatar.position, + lineHeight: speechBubbleLineHeight, + leftMargin: 0, + topMargin: 0, + rightMargin: 0, + bottomMargin: 0, + ignoreRayIntersection: true, + visible: false + }); + + // Load the persistent variables from the Settings, with defaults. + function loadSettings() { + chatName = Settings.getValue('Chat_chatName', MyAvatar.displayName); + if (!chatName) { + chatName = randomAvatarName(); + } + chatLogMaxSize = Settings.getValue('Chat_chatLogMaxSize', 100); + sendTyping = Settings.getValue('Chat_sendTyping', true); + identifyAvatarDuration = Settings.getValue('Chat_identifyAvatarDuration', 10); + identifyAvatarLineColor = Settings.getValue('Chat_identifyAvatarLineColor', { red: 0, green: 255, blue: 0 }); + identifyAvatarMyJointName = Settings.getValue('Chat_identifyAvatarMyJointName', 'Head'); + identifyAvatarYourJointName = Settings.getValue('Chat_identifyAvatarYourJointName', 'Head'); + speechBubbleDuration = Settings.getValue('Chat_speechBubbleDuration', 10); + speechBubbleTextColor = Settings.getValue('Chat_speechBubbleTextColor', {red: 255, green: 255, blue: 255}); + speechBubbleBackgroundColor = Settings.getValue('Chat_speechBubbleBackgroundColor', {red: 0, green: 0, blue: 0}); + speechBubbleOffset = Settings.getValue('Chat_speechBubbleOffset', {x: 0.0, y: 0.3, z:0.0}); + speechBubbleJointName = Settings.getValue('Chat_speechBubbleJointName', 'Head'); + speechBubbleLineHeight = Settings.getValue('Chat_speechBubbleLineHeight', 0.05); + Overlays.editOverlay(textSizeOverlay, { + lineHeight: speechBubbleLineHeight + }); + + saveSettings(); + } + + // Save the persistent variables to the Settings. + function saveSettings() { + Settings.setValue('Chat_chatName', chatName); + Settings.setValue('Chat_chatLogMaxSize', chatLogMaxSize); + Settings.setValue('Chat_sendTyping', sendTyping); + Settings.setValue('Chat_identifyAvatarDuration', identifyAvatarDuration); + Settings.setValue('Chat_identifyAvatarLineColor', identifyAvatarLineColor); + Settings.setValue('Chat_identifyAvatarMyJointName', identifyAvatarMyJointName); + Settings.setValue('Chat_identifyAvatarYourJointName', identifyAvatarYourJointName); + Settings.setValue('Chat_speechBubbleDuration', speechBubbleDuration); + Settings.setValue('Chat_speechBubbleTextColor', speechBubbleTextColor); + Settings.setValue('Chat_speechBubbleBackgroundColor', speechBubbleBackgroundColor); + Settings.setValue('Chat_speechBubbleOffset', speechBubbleOffset); + Settings.setValue('Chat_speechBubbleJointName', speechBubbleJointName); + Settings.setValue('Chat_speechBubbleLineHeight', speechBubbleLineHeight); + } + + // Reset the Settings and persistent variables to the defaults. + function resetSettings() { + Settings.setValue('Chat_chatName', null); + Settings.setValue('Chat_chatLogMaxSize', null); + Settings.setValue('Chat_sendTyping', null); + Settings.setValue('Chat_identifyAvatarDuration', null); + Settings.setValue('Chat_identifyAvatarLineColor', null); + Settings.setValue('Chat_identifyAvatarMyJointName', null); + Settings.setValue('Chat_identifyAvatarYourJointName', null); + Settings.setValue('Chat_speechBubbleDuration', null); + Settings.setValue('Chat_speechBubbleTextColor', null); + Settings.setValue('Chat_speechBubbleBackgroundColor', null); + Settings.setValue('Chat_speechBubbleOffset', null); + Settings.setValue('Chat_speechBubbleJointName', null); + Settings.setValue('Chat_speechBubbleLineHeight', null); + + loadSettings(); + } + + // Update anything that might depend on the settings. + function updateSettings() { + updateSpeechBubble(); + trimChatLog(); + updateChatPage(); + } + + // Trim the chat log so it is no longer than chatLogMaxSize lines. + function trimChatLog() { + if (chatLog.length > chatLogMaxSize) { + chatLog.splice(0, chatLogMaxSize - chatLog.length); + } + } + + // Clear the local chat log. + function clearChatLog() { + //print("clearChatLog"); + chatLog = []; + updateChatPage(); + } + + // We got a chat message from the channel. + // Trim the chat log, save the latest message in the chat log, + // and show the message on the tablet, if the chat page is showing. + function handleTransmitChatMessage(avatarID, displayName, message, data) { + //print("receiveChat", "avatarID", avatarID, "displayName", displayName, "message", message, "data", data); + + trimChatLog(); + chatLog.push([avatarID, displayName, message, data]); + + if (onChatPage) { + tablet.emitScriptEvent( + JSON.stringify({ + type: "ReceiveChatMessage", + avatarID: avatarID, + displayName: displayName, + message: message, + data: data + })); + } + } + + // Trim the chat log, save the latest log message in the chat log, + // and show the message on the tablet, if the chat page is showing. + function logMessage(message, data) { + //print("logMessage", message, data); + + trimChatLog(); + chatLog.push([null, null, message, data]); + + if (onChatPage) { + tablet.emitScriptEvent( + JSON.stringify({ + type: "LogMessage", + message: message, + data: data + })); + } + } + + // An empty chat message was entered. + // Hide our speech bubble. + function emptyChatMessage(data) { + popDownSpeechBubble(); + } + + // Notification that we typed a keystroke. + function type() { + //print("type"); + } + + // Notification that we began typing. + // Notify everyone that we started typing. + function beginTyping() { + //print("beginTyping"); + if (!sendTyping) { + return; + } + + Messages.sendMessage( + channelName, + JSON.stringify({ + type: 'AvatarBeginTyping', + avatarID: MyAvatar.sessionUUID, + displayName: chatName + })); + } + + // Notification that somebody started typing. + function handleAvatarBeginTyping(avatarID, displayName) { + //print("handleAvatarBeginTyping:", "avatarID", avatarID, displayName); + } + + // Notification that we stopped typing. + // Notify everyone that we stopped typing. + function endTyping() { + //print("endTyping"); + if (!sendTyping) { + return; + } + + Messages.sendMessage( + channelName, + JSON.stringify({ + type: 'AvatarEndTyping', + avatarID: MyAvatar.sessionUUID, + displayName: chatName + })); + } + + // Notification that somebody stopped typing. + function handleAvatarEndTyping(avatarID, displayName) { + //print("handleAvatarEndTyping:", "avatarID", avatarID, displayName); + } + + // Identify an avatar by drawing a line from our head to their head. + // If the avatar is our own, then just draw a line up into the sky. + function identifyAvatar(yourAvatarID) { + //print("identifyAvatar", yourAvatarID); + + unidentifyAvatars(); + + var myAvatarID = MyAvatar.sessionUUID; + var myJointIndex = MyAvatar.getJointIndex(identifyAvatarMyJointName); + var myJointRotation = + Quat.multiply( + MyAvatar.orientation, + MyAvatar.getAbsoluteJointRotationInObjectFrame(myJointIndex)); + var myJointPosition = + Vec3.sum( + MyAvatar.position, + Vec3.multiplyQbyV( + MyAvatar.orientation, + MyAvatar.getAbsoluteJointTranslationInObjectFrame(myJointIndex))); + + var yourJointIndex = -1; + var yourJointPosition; + + if (yourAvatarID == myAvatarID) { + + // You pointed at your own name, so draw a line up from your head. + + yourJointPosition = { + x: myJointPosition.x, + y: myJointPosition.y + 1000.0, + z: myJointPosition.z + }; + + } else { + + // You pointed at somebody else's name, so draw a line from your head to their head. + + var yourAvatar = AvatarList.getAvatar(yourAvatarID); + if (!yourAvatar) { + return; + } + + yourJointIndex = yourAvatar.getJointIndex(identifyAvatarMyJointName) + + var yourJointRotation = + Quat.multiply( + yourAvatar.orientation, + yourAvatar.getAbsoluteJointRotationInObjectFrame(yourJointIndex)); + yourJointPosition = + Vec3.sum( + yourAvatar.position, + Vec3.multiplyQbyV( + yourAvatar.orientation, + yourAvatar.getAbsoluteJointTranslationInObjectFrame(yourJointIndex))); + + } + + var identifierParams = { + parentID: myAvatarID, + parentJointIndex: myJointIndex, + lifetime: identifyAvatarDuration, + start: myJointPosition, + endParentID: yourAvatarID, + endParentJointIndex: yourJointIndex, + end: yourJointPosition, + color: identifyAvatarLineColor, + alpha: 1 + }; + + avatarIdentifiers[yourAvatarID] = identifierParams; + + identifierParams.lineID = Overlays.addOverlay("line3d", identifierParams); + + //print("ADDOVERLAY lineID", lineID, "myJointPosition", JSON.stringify(myJointPosition), "yourJointPosition", JSON.stringify(yourJointPosition), "lineData", JSON.stringify(lineData)); + + identifierParams.timer = + Script.setTimeout(function() { + //print("DELETEOVERLAY lineID"); + unidentifyAvatar(yourAvatarID); + }, identifyAvatarDuration * 1000); + + } + + // Stop identifying an avatar. + function unidentifyAvatar(yourAvatarID) { + //print("unidentifyAvatar", yourAvatarID); + + var identifierParams = avatarIdentifiers[yourAvatarID]; + if (!identifierParams) { + return; + } + + if (identifierParams.timer) { + Script.clearTimeout(identifierParams.timer); + } + + if (identifierParams.lineID) { + Overlays.deleteOverlay(identifierParams.lineID); + } + + delete avatarIdentifiers[yourAvatarID]; + } + + // Stop identifying all avatars. + function unidentifyAvatars() { + var ids = []; + + for (var avatarID in avatarIdentifiers) { + ids.push(avatarID); + } + + for (var i = 0, n = ids.length; i < n; i++) { + var avatarID = ids[i]; + unidentifyAvatar(avatarID); + } + + } + + // Turn to face another avatar. + function faceAvatar(yourAvatarID, displayName) { + //print("faceAvatar:", yourAvatarID, displayName); + + var myAvatarID = MyAvatar.sessionUUID; + if (yourAvatarID == myAvatarID) { + // You clicked on yourself. + return; + } + + var yourAvatar = AvatarList.getAvatar(yourAvatarID); + if (!yourAvatar) { + logMessage(displayName + ' is not here!', null); + return; + } + + // Project avatar positions to the floor and get the direction between those points, + // then face my avatar towards your avatar. + var yourPosition = yourAvatar.position; + yourPosition.y = 0; + var myPosition = MyAvatar.position; + myPosition.y = 0; + var myOrientation = Quat.lookAtSimple(myPosition, yourPosition); + MyAvatar.orientation = myOrientation; + } + + // Make a hopefully unique random anonymous avatar name. + function randomAvatarName() { + return 'Anon_' + Math.floor(Math.random() * 1000000); + } + + // Change the avatar size to bigger. + function biggerSize() { + //print("biggerSize"); + logMessage("Increasing avatar size", null); + MyAvatar.increaseSize(); + } + + // Change the avatar size to smaller. + function smallerSize() { + //print("smallerSize"); + logMessage("Decreasing avatar size", null); + MyAvatar.decreaseSize(); + } + + // Set the avatar size to normal. + function normalSize() { + //print("normalSize"); + logMessage("Resetting avatar size to normal!", null); + MyAvatar.resetSize(); + } + + // Send out a "Who" message, including our avatarID as myAvatarID, + // which will be sent in the response, so we can tell the reply + // is to our request. + function transmitWho() { + //print("transmitWho"); + logMessage("Who is here?", null); + Messages.sendMessage( + channelName, + JSON.stringify({ + type: 'Who', + myAvatarID: MyAvatar.sessionUUID + })); + } + + // Send a reply to a "Who" message, with a friendly message, + // our avatarID and our displayName. myAvatarID is the id + // of the avatar who send the Who message, to whom we're + // responding. + function handleWho(myAvatarID) { + var avatarID = MyAvatar.sessionUUID; + if (myAvatarID == avatarID) { + // Don't reply to myself. + return; + } + + var message = "I'm here!"; + var data = {}; + + Messages.sendMessage( + channelName, + JSON.stringify({ + type: 'ReplyWho', + myAvatarID: myAvatarID, + avatarID: avatarID, + displayName: chatName, + message: message, + data: data + })); + } + + // Receive the reply to a "Who" message. Ignore it unless we were the one + // who sent it out (if myAvatarIS is our avatar's id). + function handleReplyWho(myAvatarID, avatarID, displayName, message, data) { + if (myAvatarID != MyAvatar.sessionUUID) { + return; + } + + handleTransmitChatMessage(avatarID, displayName, message, data); + } + + // Handle input form the user, possibly multiple lines separated by newlines. + // Each line may be a chat command starting with "/", or a chat message. + function handleChatMessage(message, data) { + + var messageLines = message.trim().split('\n'); + + for (var i = 0, n = messageLines.length; i < n; i++) { + var messageLine = messageLines[i]; + + if (messageLine.substr(0, 1) == '/') { + handleChatCommand(messageLine, data); + } else { + transmitChatMessage(messageLine, data); + } + } + + } + + // Handle a chat command prefixed by "/". + function handleChatCommand(message, data) { + + var commandLine = message.substr(1); + var tokens = commandLine.trim().split(' '); + var command = tokens[0]; + var rest = commandLine.substr(command.length + 1).trim(); + + //print("commandLine", commandLine, "command", command, "tokens", tokens, "rest", rest); + + switch (command) { + + case '?': + case 'help': + logMessage('Type "/?" or "/help" for help', null); + logMessage('Type "/name " to set your chat name, or "/name" to use your display name. If your display name is not defined, a random name will be used.', null); + logMessage('Type "/close" to close your overhead chat message.', null); + logMessage('Type "/say " to display a new message.', null); + logMessage('Type "/clear" to clear your chat log.', null); + logMessage('Type "/who" to ask who is in the chat session.', null); + logMessage('Type "/bigger", "/smaller" or "/normal" to change your avatar size.', null); + break; + + case 'name': + if (rest == '') { + if (MyAvatar.displayName) { + chatName = MyAvatar.displayName; + saveSettings(); + logMessage('Your chat name has been set to your display name "' + chatName + '".', null); + } else { + chatName = randomAvatarName(); + saveSettings(); + logMessage('Your avatar\'s display name is not defined, so your chat name has been set to "' + chatName + '".', null); + } + } else { + chatName = rest; + saveSettings(); + logMessage('Your chat name has been set to "' + chatName + '".', null); + } + break; + + case 'close': + popDownSpeechBubble(); + logMessage('Overhead chat message closed.', null); + break; + + case 'say': + if (rest == '') { + emptyChatMessage(data); + } else { + transmitChatMessage(rest, data); + } + break; + + case 'who': + transmitWho(); + break; + + case 'clear': + clearChatLog(); + break; + + case 'bigger': + biggerSize(); + break; + + case 'smaller': + smallerSize(); + break; + + case 'normal': + normalSize(); + break; + + case 'resetsettings': + resetSettings(); + updateSettings(); + break; + + case 'speechbubbleheight': + var y = parseInt(rest); + if (!isNaN(y)) { + speechBubbleOffset.y = y; + } + saveSettings(); + updateSettings(); + break; + + case 'speechbubbleduration': + var duration = parseFloat(rest); + if (!isNaN(duration)) { + speechBubbleDuration = duration; + } + saveSettings(); + updateSettings(); + break; + + default: + logMessage('Unknown chat command. Type "/help" or "/?" for help.', null); + break; + + } + + } + + // Send out a chat message to everyone. + function transmitChatMessage(message, data) { + //print("transmitChatMessage", 'avatarID', avatarID, 'displayName', displayName, 'message', message, 'data', data); + + popUpSpeechBubble(message, data); + + Messages.sendMessage( + channelName, + JSON.stringify({ + type: 'TransmitChatMessage', + avatarID: MyAvatar.sessionUUID, + displayName: chatName, + message: message, + data: data + })); + + } + + // Show the speech bubble. + function popUpSpeechBubble(message, data) { + //print("popUpSpeechBubble", message, data); + + popDownSpeechBubble(); + + speechBubbleShowing = true; + speechBubbleMessage = message; + speechBubbleData = data; + + updateSpeechBubble(); + + if (speechBubbleDuration > 0) { + speechBubbleTimer = Script.setTimeout( + function () { + popDownSpeechBubble(); + }, + speechBubbleDuration * 1000); + } + } + + // Update the speech bubble. + // This is factored out so we can update an existing speech bubble if any settings change. + function updateSpeechBubble() { + if (!speechBubbleShowing) { + return; + } + + var jointIndex = MyAvatar.getJointIndex(speechBubbleJointName); + var dimensions = { + x: 100.0, + y: 100.0, + z: 0.1 + }; + + speechBubbleParams = { + type: "Text", + lifetime: speechBubbleDuration, + parentID: MyAvatar.sessionUUID, + jointIndex: jointIndex, + dimensions: dimensions, + lineHeight: speechBubbleLineHeight, + leftMargin: 0, + topMargin: 0, + rightMargin: 0, + bottomMargin: 0, + faceCamera: true, + drawInFront: true, + ignoreRayIntersection: true, + text: speechBubbleMessage, + textColor: speechBubbleTextColor, + color: speechBubbleTextColor, + backgroundColor: speechBubbleBackgroundColor + }; + + // Only overlay text3d has a way to measure the text, not entities. + // So we make a temporary one just for measuring text, then delete it. + var speechBubbleTextOverlayID = Overlays.addOverlay("text3d", speechBubbleParams); + var textSize = Overlays.textSize(textSizeOverlay, speechBubbleMessage); + try { + Overlays.deleteOverlay(speechBubbleTextOverlayID); + } catch (e) {} + + //print("updateSpeechBubble:", "speechBubbleMessage", speechBubbleMessage, "textSize", textSize.width, textSize.height); + + var fudge = 0.02; + + var width = textSize.width + fudge; + var height = speechBubbleLineHeight + fudge; + + if (textSize.width >= SPEECH_BUBBLE_MAX_WIDTH) { + var numLines = Math.ceil(width); + height = speechBubbleLineHeight * numLines + fudge; + width = SPEECH_BUBBLE_MAX_WIDTH; + } + + dimensions = { + x: width, + y: height, + z: 0.1 + }; + speechBubbleParams.dimensions = dimensions; + + var headRotation = + Quat.multiply( + MyAvatar.orientation, + MyAvatar.getAbsoluteJointRotationInObjectFrame(jointIndex)); + var headPosition = + Vec3.sum( + MyAvatar.position, + Vec3.multiplyQbyV( + MyAvatar.orientation, + MyAvatar.getAbsoluteJointTranslationInObjectFrame(jointIndex))); + var rotatedOffset = + Vec3.multiplyQbyV( + headRotation, + speechBubbleOffset); + var position = + Vec3.sum( + headPosition, + rotatedOffset); + position.y += height / 2; // offset based on half of bubble height + speechBubbleParams.position = position; + + if (!speechBubbleTextID) { + speechBubbleTextID = + Entities.addEntity(speechBubbleParams, true); + } else { + Entities.editEntity(speechBubbleTextID, speechBubbleParams); + } + + //print("speechBubbleTextID:", speechBubbleTextID, "speechBubbleParams", JSON.stringify(speechBubbleParams)); + } + + // Hide the speech bubble. + function popDownSpeechBubble() { + cancelSpeechBubbleTimer(); + + speechBubbleShowing = false; + + //print("popDownSpeechBubble speechBubbleTextID", speechBubbleTextID); + + if (speechBubbleTextID) { + try { + Entities.deleteEntity(speechBubbleTextID); + } catch (e) {} + speechBubbleTextID = null; + } + } + + // Cancel the speech bubble popup timer. + function cancelSpeechBubbleTimer() { + if (speechBubbleTimer) { + Script.clearTimeout(speechBubbleTimer); + speechBubbleTimer = null; + } + } + + // Show the tablet web page and connect the web handler. + function showTabletWebPage() { + var url = Script.resolvePath(webPageURL); + if (randomizeWebPageURL) { + url += '?rand=' + Math.random(); + } + lastWebPageURL = url; + onChatPage = true; + tablet.gotoWebScreen(lastWebPageURL); + // Connect immediately so we don't miss anything. + connectWebHandler(); + } + + // Update the tablet web page with the chat log. + function updateChatPage() { + if (!onChatPage) { + return; + } + + tablet.emitScriptEvent( + JSON.stringify({ + type: "Update", + chatLog: chatLog + })); + } + + function onChatMessageReceived(channel, message, senderID) { + + // Ignore messages to any other channel than mine. + if (channel != channelName) { + return; + } + + // Parse the message and pull out the message parameters. + var messageData = JSON.parse(message); + var messageType = messageData.type; + + //print("MESSAGE", message); + //print("MESSAGEDATA", messageData, JSON.stringify(messageData)); + + switch (messageType) { + + case 'TransmitChatMessage': + handleTransmitChatMessage(messageData.avatarID, messageData.displayName, messageData.message, messageData.data); + break; + + case 'AvatarBeginTyping': + handleAvatarBeginTyping(messageData.avatarID, messageData.displayName); + break; + + case 'AvatarEndTyping': + handleAvatarEndTyping(messageData.avatarID, messageData.displayName); + break; + + case 'Who': + handleWho(messageData.myAvatarID); + break; + + case 'ReplyWho': + handleReplyWho(messageData.myAvatarID, messageData.avatarID, messageData.displayName, messageData.message, messageData.data); + break; + + default: + print("onChatMessageReceived: unknown messageType", messageType, "message", message); + break; + + } + + } + + // Handle events from the tablet web page. + function onWebEventReceived(event) { + if (!onChatPage) { + return; + } + + //print("onWebEventReceived: event", event); + + var eventData = JSON.parse(event); + var eventType = eventData.type; + + switch (eventType) { + + case 'Ready': + updateChatPage(); + break; + + case 'Update': + updateChatPage(); + break; + + case 'HandleChatMessage': + var message = eventData.message; + var data = eventData.data; + //print("onWebEventReceived: HandleChatMessage:", 'message', message, 'data', data); + handleChatMessage(message, data); + break; + + case 'PopDownSpeechBubble': + popDownSpeechBubble(); + break; + + case 'EmptyChatMessage': + emptyChatMessage(); + break; + + case 'Type': + type(); + break; + + case 'BeginTyping': + beginTyping(); + break; + + case 'EndTyping': + endTyping(); + break; + + case 'IdentifyAvatar': + identifyAvatar(eventData.avatarID); + break; + + case 'UnidentifyAvatar': + unidentifyAvatar(eventData.avatarID); + break; + + case 'FaceAvatar': + faceAvatar(eventData.avatarID, eventData.displayName); + break; + + case 'ClearChatLog': + clearChatLog(); + break; + + case 'Who': + transmitWho(); + break; + + case 'Bigger': + biggerSize(); + break; + + case 'Smaller': + smallerSize(); + break; + + case 'Normal': + normalSize(); + break; + + default: + print("onWebEventReceived: unexpected eventType", eventType); + break; + + } + } + + function onScreenChanged(type, url) { + //print("onScreenChanged", "type", type, "url", url, "lastWebPageURL", lastWebPageURL); + + if ((type === "Web") && + (url === lastWebPageURL)) { + if (!onChatPage) { + onChatPage = true; + connectWebHandler(); + } + } else { + if (onChatPage) { + onChatPage = false; + disconnectWebHandler(); + } + } + + } + + function connectWebHandler() { + if (webHandlerConnected) { + return; + } + + try { + tablet.webEventReceived.connect(onWebEventReceived); + } catch (e) { + print("connectWebHandler: error connecting: " + e); + return; + } + + webHandlerConnected = true; + //print("connectWebHandler connected"); + + updateChatPage(); + } + + function disconnectWebHandler() { + if (!webHandlerConnected) { + return; + } + + try { + tablet.webEventReceived.disconnect(onWebEventReceived); + } catch (e) { + print("disconnectWebHandler: error disconnecting web handler: " + e); + return; + } + webHandlerConnected = false; + + //print("disconnectWebHandler: disconnected"); + } + + // Show the tablet web page when the chat button on the tablet is clicked. + function onTabletButtonClicked() { + showTabletWebPage(); + } + + // Shut down the chat application when the tablet button is destroyed. + function onTabletButtonDestroyed() { + shutDown(); + } + + // Start up the chat application. + function startUp() { + //print("startUp"); + + loadSettings(); + + tabletButton = tablet.addButton({ + icon: tabletButtonIcon, + activeIcon: tabletButtonActiveIcon, + text: tabletButtonName + }); + + Messages.subscribe(channelName); + + tablet.screenChanged.connect(onScreenChanged); + + Messages.messageReceived.connect(onChatMessageReceived); + + tabletButton.clicked.connect(onTabletButtonClicked); + + Script.scriptEnding.connect(onTabletButtonDestroyed); + + logMessage('Type "/?" or "/help" for help with chat.', null); + + //print("Added chat button to tablet."); + } + + // Shut down the chat application. + function shutDown() { + //print("shutDown"); + + popDownSpeechBubble(); + unidentifyAvatars(); + disconnectWebHandler(); + + Overlays.deleteOverlay(textSizeOverlay); + + if (onChatPage) { + tablet.gotoHomeScreen(); + onChatPage = false; + } + + tablet.screenChanged.disconnect(onScreenChanged); + + Messages.messageReceived.disconnect(onChatMessageReceived); + + // Clean up the tablet button we made. + tabletButton.clicked.disconnect(onTabletButtonClicked); + tablet.removeButton(tabletButton); + tabletButton = null; + + //print("Removed chat button from tablet."); + } + + // Kick off the chat application! + startUp(); + +}()); diff --git a/scripts/simplifiedUI/system/clickToAvatarApp.js b/scripts/simplifiedUI/system/clickToAvatarApp.js new file mode 100644 index 0000000000..8024f595b5 --- /dev/null +++ b/scripts/simplifiedUI/system/clickToAvatarApp.js @@ -0,0 +1,7 @@ +(function () { + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + this.clickDownOnEntity = function (entityID, mouseEvent) { + tablet.loadQMLSource("hifi/AvatarApp.qml"); + }; +} +); diff --git a/scripts/simplifiedUI/system/commerce/wallet.js b/scripts/simplifiedUI/system/commerce/wallet.js new file mode 100644 index 0000000000..86806fd8b4 --- /dev/null +++ b/scripts/simplifiedUI/system/commerce/wallet.js @@ -0,0 +1,737 @@ +"use strict"; +/* jslint vars:true, plusplus:true, forin:true */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +// +// wallet.js +// +// Created by Zach Fox on 2017-08-17 +// Copyright 2017 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 +// + +/* global getConnectionData getControllerWorldLocation openLoginWindow WalletScriptingInterface */ + +(function () { // BEGIN LOCAL_SCOPE +Script.include("/~/system/libraries/accountUtils.js"); +Script.include("/~/system/libraries/connectionUtils.js"); +var AppUi = Script.require('appUi'); + +var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; + +// BEGIN AVATAR SELECTOR LOGIC +var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 }; +var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 }; +var HOVER_COLOR = { red: 0xD0, green: 0xD0, blue: 0xD0 }; + +var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier. + +function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with. + overlays[key] = this; + this.key = key; + this.selected = false; + this.hovering = false; + this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected... +} +// Instance methods: +ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay + Overlays.deleteOverlay(this.activeOverlay); + delete overlays[this.key]; +}; + +ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay + Overlays.editOverlay(this.activeOverlay, properties); +}; + +function color(selected, hovering) { + var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR; + function scale(component) { + return component; + } + return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) }; +} +// so we don't have to traverse the overlays to get the last one +var lastHoveringId = 0; +ExtendedOverlay.prototype.hover = function (hovering) { + this.hovering = hovering; + if (this.key === lastHoveringId) { + if (hovering) { + return; + } + lastHoveringId = 0; + } + this.editOverlay({ color: color(this.selected, hovering) }); + if (hovering) { + // un-hover the last hovering overlay + if (lastHoveringId && lastHoveringId !== this.key) { + ExtendedOverlay.get(lastHoveringId).hover(false); + } + lastHoveringId = this.key; + } +}; +ExtendedOverlay.prototype.select = function (selected) { + if (this.selected === selected) { + return; + } + + this.editOverlay({ color: color(selected, this.hovering) }); + this.selected = selected; +}; +// Class methods: +var selectedId = false; +ExtendedOverlay.isSelected = function (id) { + return selectedId === id; +}; +ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier + return overlays[key]; +}; +ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy. + var key; + for (key in overlays) { + if (iterator(ExtendedOverlay.get(key))) { + return; + } + } +}; +ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any) + if (lastHoveringId) { + ExtendedOverlay.get(lastHoveringId).hover(false); + } +}; + +// hit(overlay) on the one overlay intersected by pickRay, if any. +// noHit() if no ExtendedOverlay was intersected (helps with hover) +ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) { + // Depends on nearer coverOverlays to extend closer to us than farther ones. + var pickedOverlay = Overlays.findRayIntersection(pickRay); + if (!pickedOverlay.intersects) { + if (noHit) { + return noHit(); + } + return; + } + ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours. + if ((overlay.activeOverlay) === pickedOverlay.overlayID) { + hit(overlay); + return true; + } + }); +}; + +function addAvatarNode(id) { + return new ExtendedOverlay(id, "sphere", { + drawInFront: true, + solid: true, + alpha: 0.8, + color: color(false, false), + ignoreRayIntersection: false + }); +} + +var pingPong = true; +var OVERLAY_SCALE = 0.032; +function updateOverlays() { + var eye = Camera.position; + AvatarList.getAvatarIdentifiers().forEach(function (id) { + if (!id) { + return; // don't update ourself, or avatars we're not interested in + } + var avatar = AvatarList.getAvatar(id); + if (!avatar) { + return; // will be deleted below if there had been an overlay. + } + var overlay = ExtendedOverlay.get(id); + if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back. + overlay = addAvatarNode(id); + } + var target = avatar.position; + var distance = Vec3.distance(target, eye); + var offset = 0.2; + // get diff between target and eye (a vector pointing to the eye from avatar position) + var diff = Vec3.subtract(target, eye); + var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can + if (headIndex > 0) { + offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2; + } + + // move a bit in front, towards the camera + target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset)); + + // now bump it up a bit + target.y = target.y + offset; + + overlay.ping = pingPong; + overlay.editOverlay({ + color: color(ExtendedOverlay.isSelected(id), overlay.hovering), + position: target, + dimensions: OVERLAY_SCALE * distance + }); + }); + pingPong = !pingPong; + ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.) + if (overlay.ping === pingPong) { + overlay.deleteOverlay(); + } + }); +} +function removeOverlays() { + selectedId = false; + lastHoveringId = 0; + ExtendedOverlay.some(function (overlay) { + overlay.deleteOverlay(); + }); +} + +// +// Clicks. +// +function usernameFromIDReply(id, username, machineFingerprint, isAdmin) { + if (selectedId === id) { + var message = { + method: 'updateSelectedRecipientUsername', + userName: username === "" ? "unknown username" : username + }; + ui.sendMessage(message); + } +} +function handleClick(pickRay) { + ExtendedOverlay.applyPickRay(pickRay, function (overlay) { + var nextSelectedStatus = !overlay.selected; + var avatarId = overlay.key; + selectedId = nextSelectedStatus ? avatarId : false; + if (nextSelectedStatus) { + Users.requestUsernameFromID(avatarId); + } + var message = { + method: 'selectRecipient', + id: avatarId, + isSelected: nextSelectedStatus, + displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"', + userName: '' + }; + ui.sendMessage(message); + + ExtendedOverlay.some(function (overlay) { + var id = overlay.key; + var selected = ExtendedOverlay.isSelected(id); + overlay.select(selected); + }); + + return true; + }); +} +function handleMouseEvent(mousePressEvent) { // handleClick if we get one. + if (!mousePressEvent.isLeftButton) { + return; + } + handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y)); +} +function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic + ExtendedOverlay.applyPickRay(pickRay, function (overlay) { + overlay.hover(true); + }, function () { + ExtendedOverlay.unHover(); + }); +} + +// handy global to keep track of which hand is the mouse (if any) +var currentHandPressed = 0; +var TRIGGER_CLICK_THRESHOLD = 0.85; +var TRIGGER_PRESS_THRESHOLD = 0.05; + +function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position + var pickRay; + if (HMD.active) { + if (currentHandPressed !== 0) { + pickRay = controllerComputePickRay(currentHandPressed); + } else { + // nothing should hover, so + ExtendedOverlay.unHover(); + return; + } + } else { + pickRay = Camera.computePickRay(event.x, event.y); + } + handleMouseMove(pickRay); +} +function handleTriggerPressed(hand, value) { + // The idea is if you press one trigger, it is the one + // we will consider the mouse. Even if the other is pressed, + // we ignore it until this one is no longer pressed. + var isPressed = value > TRIGGER_PRESS_THRESHOLD; + if (currentHandPressed === 0) { + currentHandPressed = isPressed ? hand : 0; + return; + } + if (currentHandPressed === hand) { + currentHandPressed = isPressed ? hand : 0; + return; + } + // otherwise, the other hand is still triggered + // so do nothing. +} + +// We get mouseMoveEvents from the handControllers, via handControllerPointer. +// But we don't get mousePressEvents. +var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); +var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press'); +function controllerComputePickRay(hand) { + var controllerPose = getControllerWorldLocation(hand, true); + if (controllerPose.valid) { + return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) }; + } +} +function makeClickHandler(hand) { + return function (clicked) { + if (clicked > TRIGGER_CLICK_THRESHOLD) { + var pickRay = controllerComputePickRay(hand); + handleClick(pickRay); + } + }; +} +function makePressHandler(hand) { + return function (value) { + handleTriggerPressed(hand, value); + }; +} +triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); +triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); +triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); +triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); +// END AVATAR SELECTOR LOGIC + +var sendMoneyRecipient; +var sendMoneyParticleEffectUpdateTimer; +var particleEffectTimestamp; +var sendMoneyParticleEffect; +var SEND_MONEY_PARTICLE_TIMER_UPDATE = 250; +var SEND_MONEY_PARTICLE_EMITTING_DURATION = 3000; +var SEND_MONEY_PARTICLE_LIFETIME_SECONDS = 8; +var SEND_MONEY_PARTICLE_PROPERTIES = { + accelerationSpread: { x: 0, y: 0, z: 0 }, + alpha: 1, + alphaFinish: 1, + alphaSpread: 0, + alphaStart: 1, + azimuthFinish: 0, + azimuthStart: -6, + color: { red: 143, green: 5, blue: 255 }, + colorFinish: { red: 255, green: 0, blue: 204 }, + colorSpread: { red: 0, green: 0, blue: 0 }, + colorStart: { red: 0, green: 136, blue: 255 }, + emitAcceleration: { x: 0, y: 0, z: 0 }, // Immediately gets updated to be accurate + emitDimensions: { x: 0, y: 0, z: 0 }, + emitOrientation: { x: 0, y: 0, z: 0 }, + emitRate: 4, + emitSpeed: 2.1, + emitterShouldTrail: true, + isEmitting: 1, + lifespan: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1, // Immediately gets updated to be accurate + lifetime: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1, + maxParticles: 20, + name: 'hfc-particles', + particleRadius: 0.2, + polarFinish: 0, + polarStart: 0, + radiusFinish: 0.05, + radiusSpread: 0, + radiusStart: 0.2, + speedSpread: 0, + textures: "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle-HFC.png", + type: 'ParticleEffect' +}; + +var MS_PER_SEC = 1000; +function updateSendMoneyParticleEffect() { + var timestampNow = Date.now(); + if ((timestampNow - particleEffectTimestamp) > (SEND_MONEY_PARTICLE_LIFETIME_SECONDS * MS_PER_SEC)) { + deleteSendMoneyParticleEffect(); + return; + } else if ((timestampNow - particleEffectTimestamp) > SEND_MONEY_PARTICLE_EMITTING_DURATION) { + Entities.editEntity(sendMoneyParticleEffect, { + isEmitting: 0 + }); + } else if (sendMoneyParticleEffect) { + var recipientPosition = AvatarList.getAvatar(sendMoneyRecipient).position; + var distance = Vec3.distance(recipientPosition, MyAvatar.position); + var accel = Vec3.subtract(recipientPosition, MyAvatar.position); + accel.y -= 3.0; + var life = Math.sqrt(2 * distance / Vec3.length(accel)); + Entities.editEntity(sendMoneyParticleEffect, { + emitAcceleration: accel, + lifespan: life + }); + } +} + +function deleteSendMoneyParticleEffect() { + if (sendMoneyParticleEffectUpdateTimer) { + Script.clearInterval(sendMoneyParticleEffectUpdateTimer); + sendMoneyParticleEffectUpdateTimer = null; + } + if (sendMoneyParticleEffect) { + sendMoneyParticleEffect = Entities.deleteEntity(sendMoneyParticleEffect); + } + sendMoneyRecipient = null; +} + +function onUsernameChanged() { + if (ui.checkIsOpen()) { + ui.open(WALLET_QML_SOURCE); + } +} + +var MARKETPLACE_QML_PATH = "hifi/commerce/marketplace/Marketplace.qml"; +function openMarketplace(optionalItem) { + ui.open(MARKETPLACE_QML_PATH); + + if (optionalItem) { + ui.tablet.sendToQml({ + method: 'updateMarketplaceQMLItem', + params: { itemId: optionalItem } + }); + } +} + +function setCertificateInfo(itemCertificateId) { + ui.tablet.sendToQml({ + method: 'inspectionCertificate_setCertificateId', + entityId: "", + certificateId: itemCertificateId + }); +} + +// Function Name: fromQml() +// +// Description: +// -Called when a message is received from SpectatorCamera.qml. The "message" argument is what is sent from the QML +// in the format "{method, params}", like json-rpc. See also sendToQml(). +function fromQml(message) { + switch (message.method) { + case 'passphrasePopup_cancelClicked': + case 'needsLogIn_cancelClicked': + ui.close(); + break; + case 'walletSetup_cancelClicked': + switch (message.referrer) { + case '': // User clicked "Wallet" app + case undefined: + case null: + ui.close(); + break; + case 'purchases': + case 'marketplace cta': + case 'mainPage': + openMarketplace(); + break; + default: + openMarketplace(); + break; + } + break; + case 'needsLogIn_loginClicked': + openLoginWindow(); + break; + case 'disableHmdPreview': + break; // do nothing here, handled in marketplaces.js + case 'maybeEnableHmdPreview': + break; // do nothing here, handled in marketplaces.js + case 'transactionHistory_linkClicked': + openMarketplace(message.itemId); + break; + case 'goToMarketplaceMainPage': + openMarketplace(); + break; + case 'goToMarketplaceItemPage': + openMarketplace(message.itemId); + break; + case 'refreshConnections': + print('Refreshing Connections...'); + getConnectionData(false); + break; + case 'enable_ChooseRecipientNearbyMode': + if (!isUpdateOverlaysWired) { + Script.update.connect(updateOverlays); + isUpdateOverlaysWired = true; + } + break; + case 'disable_ChooseRecipientNearbyMode': + if (isUpdateOverlaysWired) { + Script.update.disconnect(updateOverlays); + isUpdateOverlaysWired = false; + } + removeOverlays(); + break; + case 'sendAsset_sendPublicly': + deleteSendMoneyParticleEffect(); + sendMoneyRecipient = message.recipient; + var props = SEND_MONEY_PARTICLE_PROPERTIES; + props.parentID = MyAvatar.sessionUUID; + props.position = MyAvatar.position; + props.position.y += 0.2; + if (message.effectImage) { + props.textures = message.effectImage; + } + sendMoneyParticleEffect = Entities.addEntity(props, true); + particleEffectTimestamp = Date.now(); + updateSendMoneyParticleEffect(); + sendMoneyParticleEffectUpdateTimer = + Script.setInterval(updateSendMoneyParticleEffect, SEND_MONEY_PARTICLE_TIMER_UPDATE); + break; + case 'transactionHistory_goToBank': + if (Account.metaverseServerURL.indexOf("staging") >= 0) { + Window.location = "hifi://hifiqa-master-metaverse-staging"; // So that we can test in staging. + } else { + Window.location = "hifi://BankOfHighFidelity"; + } + break; + case 'purchases_updateWearables': + var currentlyWornWearables = []; + var ATTACHMENT_SEARCH_RADIUS = 100; // meters (just in case) + + var nearbyEntities = Entities.findEntitiesByType('Model', MyAvatar.position, ATTACHMENT_SEARCH_RADIUS); + + for (var i = 0; i < nearbyEntities.length; i++) { + var currentProperties = Entities.getEntityProperties( + nearbyEntities[i], ['certificateID', 'editionNumber', 'parentID'] + ); + if (currentProperties.parentID === MyAvatar.sessionUUID) { + currentlyWornWearables.push({ + entityID: nearbyEntities[i], + entityCertID: currentProperties.certificateID, + entityEdition: currentProperties.editionNumber + }); + } + } + + ui.tablet.sendToQml({ method: 'updateWearables', wornWearables: currentlyWornWearables }); + break; + case 'purchases_walletNotSetUp': + ui.tablet.sendToQml({ + method: 'updateWalletReferrer', + referrer: "purchases" + }); + break; + case 'purchases_openGoTo': + ui.open("hifi/tablet/TabletAddressDialog.qml"); + break; + case 'purchases_itemInfoClicked': + var itemId = message.itemId; + if (itemId && itemId !== "") { + openMarketplace(itemId); + } + break; + case 'purchases_itemCertificateClicked': + setCertificateInfo(message.itemCertificateId); + break; + case 'clearShouldShowDotHistory': + shouldShowDotHistory = false; + ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory); + break; + case 'clearShouldShowDotUpdates': + shouldShowDotUpdates = false; + ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory); + break; + case 'http.request': + // Handled elsewhere, don't log. + break; + case 'closeSendAsset': + ui.close(); + break; + default: + print('wallet.js: Unrecognized message from QML'); + } +} + +var isWired = false; +function walletOpened() { + Users.usernameFromIDReply.connect(usernameFromIDReply); + Controller.mousePressEvent.connect(handleMouseEvent); + Controller.mouseMoveEvent.connect(handleMouseMoveEvent); + triggerMapping.enable(); + triggerPressMapping.enable(); + isWired = true; + + if (shouldShowDotHistory) { + ui.sendMessage({ + method: 'updateRecentActivityMessageLight', + messagesWaiting: shouldShowDotHistory + }); + } +} + +function walletClosed() { + off(); +} + +function notificationDataProcessPageUpdates(data) { + return data.data.updates; +} + +function notificationDataProcessPageHistory(data) { + return data.data.history; +} + +var shouldShowDotUpdates = false; +function notificationPollCallbackUpdates(updatesArray) { + shouldShowDotUpdates = updatesArray.length > 0; + ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory); + + if (updatesArray.length > 0) { + var message; + if (!ui.notificationInitialCallbackMade[0]) { + message = updatesArray.length + " of your purchased items " + + (updatesArray.length === 1 ? "has an update " : "have updates ") + + "available. Open INVENTORY to update."; + ui.notificationDisplayBanner(message); + + ui.notificationPollCaresAboutSince[0] = true; + } else { + for (var i = 0; i < updatesArray.length; i++) { + message = "Update available for \"" + + updatesArray[i].base_item_title + "\"." + + "Open INVENTORY to update."; + ui.notificationDisplayBanner(message); + } + } + } +} +var shouldShowDotHistory = false; +function notificationPollCallbackHistory(historyArray) { + if (!ui.isOpen) { + var notificationCount = historyArray.length; + shouldShowDotHistory = shouldShowDotHistory || notificationCount > 0; + ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory); + + if (notificationCount > 0) { + var message; + if (!ui.notificationInitialCallbackMade[1]) { + message = "You have " + notificationCount + " unread recent " + + "transaction" + (notificationCount === 1 ? "" : "s") + ". Open INVENTORY to see all activity."; + ui.notificationDisplayBanner(message); + } else { + for (var i = 0; i < notificationCount; i++) { + var historyMessage = historyArray[i].message; + var sanitizedHistoryMessage = historyMessage.replace(/<\/?[^>]+(>|$)/g, ""); + message = '"' + sanitizedHistoryMessage + '" ' + + "Open INVENTORY to see all activity."; + ui.notificationDisplayBanner(message); + } + } + } + } +} + +function isReturnedDataEmptyUpdates(data) { + var updatesArray = data.data.updates; + return updatesArray.length === 0; +} + +function isReturnedDataEmptyHistory(data) { + var historyArray = data.data.history; + return historyArray.length === 0; +} + +var DEVELOPER_MENU = "Developer"; +var MARKETPLACE_ITEM_TESTER_LABEL = "Marketplace Item Tester"; +var MARKETPLACE_ITEM_TESTER_QML_SOURCE = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml"; +function installMarketplaceItemTester() { + if (!Menu.menuExists(DEVELOPER_MENU)) { + Menu.addMenu(DEVELOPER_MENU); + } + if (!Menu.menuItemExists(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL)) { + Menu.addMenuItem({ + menuName: DEVELOPER_MENU, + menuItemName: MARKETPLACE_ITEM_TESTER_LABEL, + isCheckable: false + }); + } + + Menu.menuItemEvent.connect(function (menuItem) { + if (menuItem === MARKETPLACE_ITEM_TESTER_LABEL) { + ui.open(MARKETPLACE_ITEM_TESTER_QML_SOURCE); + } + }); +} + +function uninstallMarketplaceItemTester() { + if (Menu.menuExists(DEVELOPER_MENU) && + Menu.menuItemExists(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL) + ) { + Menu.removeMenuItem(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL); + } +} + +var BUTTON_NAME = "INVENTORY"; +var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml"; +var SENDASSET_QML_SOURCE = "hifi/commerce/common/sendAsset/SendAsset.qml"; +var NOTIFICATION_POLL_TIMEOUT = 300000; +var ui; +function startup() { + var notificationPollEndpointArray = ["/api/v1/commerce/available_updates?per_page=10"]; + var notificationPollTimeoutMsArray = [NOTIFICATION_POLL_TIMEOUT]; + var notificationDataProcessPageArray = [notificationDataProcessPageUpdates]; + var notificationPollCallbackArray = [notificationPollCallbackUpdates]; + var notificationPollStopPaginatingConditionMetArray = [isReturnedDataEmptyUpdates]; + var notificationPollCaresAboutSinceArray = [false]; + + if (!WalletScriptingInterface.limitedCommerce) { + notificationPollEndpointArray[1] = "/api/v1/commerce/history?per_page=10"; + notificationPollTimeoutMsArray[1] = NOTIFICATION_POLL_TIMEOUT; + notificationDataProcessPageArray[1] = notificationDataProcessPageHistory; + notificationPollCallbackArray[1] = notificationPollCallbackHistory; + notificationPollStopPaginatingConditionMetArray[1] = isReturnedDataEmptyHistory; + notificationPollCaresAboutSinceArray[1] = true; + } + + ui = new AppUi({ + buttonName: BUTTON_NAME, + sortOrder: 10, + home: WALLET_QML_SOURCE, + additionalAppScreens: SENDASSET_QML_SOURCE, + onOpened: walletOpened, + onClosed: walletClosed, + onMessage: fromQml, + notificationPollEndpoint: notificationPollEndpointArray, + notificationPollTimeoutMs: notificationPollTimeoutMsArray, + notificationDataProcessPage: notificationDataProcessPageArray, + notificationPollCallback: notificationPollCallbackArray, + notificationPollStopPaginatingConditionMet: notificationPollStopPaginatingConditionMetArray, + notificationPollCaresAboutSince: notificationPollCaresAboutSinceArray + }); + GlobalServices.myUsernameChanged.connect(onUsernameChanged); + installMarketplaceItemTester(); +} + +var isUpdateOverlaysWired = false; +function off() { + if (isWired) { + Users.usernameFromIDReply.disconnect(usernameFromIDReply); + Controller.mousePressEvent.disconnect(handleMouseEvent); + Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); + triggerMapping.disable(); + triggerPressMapping.disable(); + isWired = false; + } + + if (isUpdateOverlaysWired) { + Script.update.disconnect(updateOverlays); + isUpdateOverlaysWired = false; + } + removeOverlays(); +} + +function shutdown() { + GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); + deleteSendMoneyParticleEffect(); + uninstallMarketplaceItemTester(); + off(); +} + +// +// Run the functions. +// +startup(); +Script.scriptEnding.connect(shutdown); +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/controllers/controllerModules/equipEntity.js b/scripts/simplifiedUI/system/controllers/controllerModules/equipEntity.js index 54b56ff271..1deb97c20e 100644 --- a/scripts/simplifiedUI/system/controllers/controllerModules/equipEntity.js +++ b/scripts/simplifiedUI/system/controllers/controllerModules/equipEntity.js @@ -17,6 +17,7 @@ Script.include("/~/system/libraries/Xform.js"); Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); Script.include("/~/system/libraries/cloneEntityUtils.js"); +Script.include("/~/system/libraries/utils.js"); var DEFAULT_SPHERE_MODEL_URL = "http://hifi-content.s3.amazonaws.com/alan/dev/equip-Fresnel-3.fbx"; diff --git a/scripts/simplifiedUI/system/controllers/controllerModules/nearGrabEntity.js b/scripts/simplifiedUI/system/controllers/controllerModules/nearGrabEntity.js index 763c1a1ce0..46a1d7e718 100644 --- a/scripts/simplifiedUI/system/controllers/controllerModules/nearGrabEntity.js +++ b/scripts/simplifiedUI/system/controllers/controllerModules/nearGrabEntity.js @@ -210,6 +210,8 @@ Script.include("/~/system/libraries/controllers.js"); }; } + var LEFT_HAND = Controller.Standard.LeftHand; + var RIGHT_HAND = Controller.Standard.RightHand; var leftNearGrabEntity = new NearGrabEntity(LEFT_HAND); var rightNearGrabEntity = new NearGrabEntity(RIGHT_HAND); diff --git a/scripts/simplifiedUI/system/dialTone.js b/scripts/simplifiedUI/system/dialTone.js new file mode 100644 index 0000000000..7c0a5b250d --- /dev/null +++ b/scripts/simplifiedUI/system/dialTone.js @@ -0,0 +1,36 @@ +"use strict"; + +// +// dialTone.js +// examples +// +// Created by Stephen Birarda on 06/08/15. +// Added disconnect HRS 6/11/15. +// Copyright 2015 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 +// + +(function() { // BEGIN LOCAL_SCOPE + +// setup the local sound we're going to use +var connectSound = SoundCache.getSound(Script.resolvePath("assets/sounds/hello.wav")); +var disconnectSound = SoundCache.getSound(Script.resolvePath("assets/sounds/goodbye.wav")); +var micMutedSound = SoundCache.getSound(Script.resolvePath("assets/sounds/goodbye.wav")); + +// setup the options needed for that sound +var soundOptions = { + localOnly: true +}; + +// play the sound locally once we get the first audio packet from a mixer +Audio.receivedFirstPacket.connect(function(){ + Audio.playSound(connectSound, soundOptions); +}); + +Audio.disconnected.connect(function(){ + Audio.playSound(disconnectSound, soundOptions); +}); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/directory.js b/scripts/simplifiedUI/system/directory.js new file mode 100644 index 0000000000..f84429ab95 --- /dev/null +++ b/scripts/simplifiedUI/system/directory.js @@ -0,0 +1,134 @@ +// +// directory.js +// examples +// +// Created by David Rowe on 8 Jun 2015 +// Copyright 2015 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 +// + +Script.include([ + "libraries/toolBars.js", +]); + +var toolIconUrl = Script.resolvePath("assets/images/tools/"); + +var DIRECTORY_WINDOW_URL = Account.metaverseServerURL + "/directory"; +var directoryWindow = new OverlayWebWindow({ + title: 'Directory', + source: "about:blank", + width: 900, + height: 700, + visible: false +}); + +var toolHeight = 50; +var toolWidth = 50; +var TOOLBAR_MARGIN_Y = 0; + + +function showDirectory() { + directoryWindow.setURL(DIRECTORY_WINDOW_URL); + directoryWindow.setVisible(true); +} + +function hideDirectory() { + directoryWindow.setVisible(false); + directoryWindow.setURL("about:blank"); +} + +function toggleDirectory() { + if (directoryWindow.visible) { + hideDirectory(); + } else { + showDirectory(); + } +} + +var toolBar = (function() { + var that = {}, + toolBar, + browseDirectoryButton; + + function initialize() { + toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.directory.toolbar", function(windowDimensions, toolbar) { + return { + x: windowDimensions.x / 2, + y: windowDimensions.y + }; + }, { + x: -2 * toolWidth, + y: -TOOLBAR_MARGIN_Y - toolHeight + }); + browseDirectoryButton = toolBar.addTool({ + imageURL: toolIconUrl + "directory.svg", + subImage: { + x: 0, + y: Tool.IMAGE_WIDTH, + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT + }, + width: toolWidth, + height: toolHeight, + alpha: 0.9, + visible: true + }); + + toolBar.showTool(browseDirectoryButton, true); + } + + var browseDirectoryButtonDown = false; + that.mousePressEvent = function(event) { + var clickedOverlay, + url, + file; + + if (!event.isLeftButton) { + // if another mouse button than left is pressed ignore it + return false; + } + + clickedOverlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + + + + if (browseDirectoryButton === toolBar.clicked(clickedOverlay)) { + toggleDirectory(); + return true; + } + + return false; + }; + + that.mouseReleaseEvent = function(event) { + var handled = false; + + + if (browseDirectoryButtonDown) { + var clickedOverlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + } + + newModelButtonDown = false; + browseDirectoryButtonDown = false; + + return handled; + } + + that.cleanup = function() { + toolBar.cleanup(); + }; + + initialize(); + return that; +}()); + +Controller.mousePressEvent.connect(toolBar.mousePressEvent) +Script.scriptEnding.connect(toolBar.cleanup); diff --git a/scripts/simplifiedUI/system/edit.js b/scripts/simplifiedUI/system/edit.js new file mode 100644 index 0000000000..371a8d48ca --- /dev/null +++ b/scripts/simplifiedUI/system/edit.js @@ -0,0 +1,2858 @@ +// edit.js +// +// Created by Brad Hefta-Gaub on 10/2/14. +// Persist toolbar by HRS 6/11/15. +// Copyright 2014 High Fidelity, Inc. +// +// This script allows you to edit entities with a new UI/UX for mouse and trackpad based editing +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, + Overlays, OverlayWebWindow, UserActivityLogger, Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, + progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, OverlaySystemWindow, + keyUpEventFromUIWindow:true */ + +(function() { // BEGIN LOCAL_SCOPE + +"use strict"; + +var EDIT_TOGGLE_BUTTON = "com.highfidelity.interface.system.editButton"; + +var CONTROLLER_MAPPING_NAME = "com.highfidelity.editMode"; + +Script.include([ + "libraries/stringHelpers.js", + "libraries/dataViewHelpers.js", + "libraries/progressDialog.js", + "libraries/entitySelectionTool.js", + "libraries/ToolTip.js", + "libraries/entityCameraTool.js", + "libraries/gridTool.js", + "libraries/entityList.js", + "libraries/utils.js", + "libraries/entityIconOverlayManager.js" +]); + +var CreateWindow = Script.require('./modules/createWindow.js'); + +var TITLE_OFFSET = 60; +var CREATE_TOOLS_WIDTH = 490; +var MAX_DEFAULT_ENTITY_LIST_HEIGHT = 942; + +var DEFAULT_IMAGE = "https://hifi-content.s3.amazonaws.com/DomainContent/production/no-image.jpg"; + +var createToolsWindow = new CreateWindow( + Script.resourcesPath() + "qml/hifi/tablet/EditTools.qml", + 'Create Tools', + 'com.highfidelity.create.createToolsWindow', + function () { + var windowHeight = Window.innerHeight - TITLE_OFFSET; + if (windowHeight > MAX_DEFAULT_ENTITY_LIST_HEIGHT) { + windowHeight = MAX_DEFAULT_ENTITY_LIST_HEIGHT; + } + return { + size: { + x: CREATE_TOOLS_WIDTH, + y: windowHeight + }, + position: { + x: Window.x + Window.innerWidth - CREATE_TOOLS_WIDTH, + y: Window.y + TITLE_OFFSET + } + } + }, + false +); + +/** + * @description Returns true in case we should use the tablet version of the CreateApp + * @returns boolean + */ +var shouldUseEditTabletApp = function() { + return HMD.active || (!HMD.active && !Settings.getValue("desktopTabletBecomesToolbar", true)); +}; + + +var selectionDisplay = SelectionDisplay; +var selectionManager = SelectionManager; + +var PARTICLE_SYSTEM_URL = Script.resolvePath("assets/images/icon-particles.svg"); +var POINT_LIGHT_URL = Script.resolvePath("assets/images/icon-point-light.svg"); +var SPOT_LIGHT_URL = Script.resolvePath("assets/images/icon-spot-light.svg"); +var ZONE_URL = Script.resolvePath("assets/images/icon-zone.svg"); + +var entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleEffect', 'Zone'], function(entityID) { + var properties = Entities.getEntityProperties(entityID, ['type', 'isSpotlight']); + if (properties.type === 'Light') { + return { + url: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_URL, + }; + } else if (properties.type === 'Zone') { + return { + url: ZONE_URL, + }; + } else { + return { + url: PARTICLE_SYSTEM_URL, + }; + } +}); + +var cameraManager = new CameraManager(); + +var grid = new Grid(); +var gridTool = new GridTool({ + horizontalGrid: grid, + createToolsWindow: createToolsWindow, + shouldUseEditTabletApp: shouldUseEditTabletApp +}); +gridTool.setVisible(false); + +var EntityShapeVisualizer = Script.require('./modules/entityShapeVisualizer.js'); +var entityShapeVisualizer = new EntityShapeVisualizer(["Zone"]); + +var entityListTool = new EntityListTool(shouldUseEditTabletApp); + +selectionManager.addEventListener(function () { + selectionDisplay.updateHandles(); + entityIconOverlayManager.updatePositions(); + entityShapeVisualizer.setEntities(selectionManager.selections); +}); + +var DEGREES_TO_RADIANS = Math.PI / 180.0; +var RADIANS_TO_DEGREES = 180.0 / Math.PI; + +var MIN_ANGULAR_SIZE = 2; +var MAX_ANGULAR_SIZE = 45; +var allowLargeModels = true; +var allowSmallModels = true; + +var DEFAULT_DIMENSION = 0.20; + +var DEFAULT_DIMENSIONS = { + x: DEFAULT_DIMENSION, + y: DEFAULT_DIMENSION, + z: DEFAULT_DIMENSION +}; + +var DEFAULT_LIGHT_DIMENSIONS = Vec3.multiply(20, DEFAULT_DIMENSIONS); + +var MENU_AUTO_FOCUS_ON_SELECT = "Auto Focus on Select"; +var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus"; +var MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "Show Lights and Particle Systems in Create Mode"; +var MENU_SHOW_ZONES_IN_EDIT_MODE = "Show Zones in Create Mode"; + +var MENU_CREATE_ENTITIES_GRABBABLE = "Create Entities As Grabbable (except Zones, Particles, and Lights)"; +var MENU_ALLOW_SELECTION_LARGE = "Allow Selecting of Large Models"; +var MENU_ALLOW_SELECTION_SMALL = "Allow Selecting of Small Models"; +var MENU_ALLOW_SELECTION_LIGHTS = "Allow Selecting of Lights"; + +var SETTING_AUTO_FOCUS_ON_SELECT = "autoFocusOnSelect"; +var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus"; +var SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "showLightsAndParticlesInEditMode"; +var SETTING_SHOW_ZONES_IN_EDIT_MODE = "showZonesInEditMode"; + +var SETTING_EDIT_PREFIX = "Edit/"; + + +var CREATE_ENABLED_ICON = "icons/tablet-icons/edit-i.svg"; +var CREATE_DISABLED_ICON = "icons/tablet-icons/edit-disabled.svg"; + +// marketplace info, etc. not quite ready yet. +var SHOULD_SHOW_PROPERTY_MENU = false; +var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissions to edit on this domain."; +var INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG = "You do not have the necessary permissions to place items on this domain."; + +var isActive = false; +var createButton = null; + +var IMPORTING_SVO_OVERLAY_WIDTH = 144; +var IMPORTING_SVO_OVERLAY_HEIGHT = 30; +var IMPORTING_SVO_OVERLAY_MARGIN = 5; +var IMPORTING_SVO_OVERLAY_LEFT_MARGIN = 34; +var importingSVOImageOverlay = Overlays.addOverlay("image", { + imageURL: Script.resolvePath("assets") + "/images/hourglass.svg", + width: 20, + height: 20, + alpha: 1.0, + x: Window.innerWidth - IMPORTING_SVO_OVERLAY_WIDTH, + y: Window.innerHeight - IMPORTING_SVO_OVERLAY_HEIGHT, + visible: false +}); +var importingSVOTextOverlay = Overlays.addOverlay("text", { + font: { + size: 14 + }, + text: "Importing SVO...", + leftMargin: IMPORTING_SVO_OVERLAY_LEFT_MARGIN, + x: Window.innerWidth - IMPORTING_SVO_OVERLAY_WIDTH - IMPORTING_SVO_OVERLAY_MARGIN, + y: Window.innerHeight - IMPORTING_SVO_OVERLAY_HEIGHT - IMPORTING_SVO_OVERLAY_MARGIN, + width: IMPORTING_SVO_OVERLAY_WIDTH, + height: IMPORTING_SVO_OVERLAY_HEIGHT, + backgroundColor: { + red: 80, + green: 80, + blue: 80 + }, + backgroundAlpha: 0.7, + visible: false +}); + +var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; +var marketplaceWindow = new OverlayWebWindow({ + title: 'Marketplace', + source: "about:blank", + width: 900, + height: 700, + visible: false +}); + +function showMarketplace(marketplaceID) { + var url = MARKETPLACE_URL; + if (marketplaceID) { + url = url + "/items/" + marketplaceID; + } + marketplaceWindow.setURL(url); + marketplaceWindow.setVisible(true); + marketplaceWindow.raise(); + + UserActivityLogger.logAction("opened_marketplace"); +} + +function hideMarketplace() { + marketplaceWindow.setVisible(false); + marketplaceWindow.setURL("about:blank"); +} + +// function toggleMarketplace() { +// if (marketplaceWindow.visible) { +// hideMarketplace(); +// } else { +// showMarketplace(); +// } +// } + +function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) { + // Adjust the position such that the bounding box (registration, dimensions and orientation) lies behind the original + // position in the given direction. + var CORNERS = [ + { x: 0, y: 0, z: 0 }, + { x: 0, y: 0, z: 1 }, + { x: 0, y: 1, z: 0 }, + { x: 0, y: 1, z: 1 }, + { x: 1, y: 0, z: 0 }, + { x: 1, y: 0, z: 1 }, + { x: 1, y: 1, z: 0 }, + { x: 1, y: 1, z: 1 }, + ]; + + // Go through all corners and find least (most negative) distance in front of position. + var distance = 0; + for (var i = 0, length = CORNERS.length; i < length; i++) { + var cornerVector = + Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions)); + var cornerDistance = Vec3.dot(cornerVector, direction); + distance = Math.min(cornerDistance, distance); + } + position = Vec3.sum(Vec3.multiply(distance, direction), position); + return position; +} + +var GRABBABLE_ENTITIES_MENU_CATEGORY = "Edit"; + +// Handles any edit mode updates required when domains have switched +function checkEditPermissionsAndUpdate() { + if ((createButton === null) || (createButton === undefined)) { + //--EARLY EXIT--( nothing to safely update ) + return; + } + + var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()); + createButton.editProperties({ + icon: (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON), + captionColor: (hasRezPermissions ? "#ffffff" : "#888888"), + }); + + if (!hasRezPermissions && isActive) { + that.setActive(false); + tablet.gotoHomeScreen(); + } +} + +// Copies the properties in `b` into `a`. `a` will be modified. +function copyProperties(a, b) { + for (var key in b) { + a[key] = b[key]; + } + return a; +} + +const DEFAULT_DYNAMIC_PROPERTIES = { + dynamic: true, + damping: 0.39347, + angularDamping: 0.39347, + gravity: { x: 0, y: -9.8, z: 0 }, +}; + +const DEFAULT_NON_DYNAMIC_PROPERTIES = { + dynamic: false, + damping: 0, + angularDamping: 0, + gravity: { x: 0, y: 0, z: 0 }, +}; + +const DEFAULT_ENTITY_PROPERTIES = { + All: { + description: "", + rotation: { x: 0, y: 0, z: 0, w: 1 }, + collidesWith: "static,dynamic,kinematic,otherAvatar,myAvatar", + collisionSoundURL: "", + cloneable: false, + ignoreIK: true, + canCastShadow: true, + href: "", + script: "", + serverScripts:"", + velocity: { + x: 0, + y: 0, + z: 0 + }, + angularVelocity: { + x: 0, + y: 0, + z: 0 + }, + restitution: 0.5, + friction: 0.5, + density: 1000, + dynamic: false, + }, + Shape: { + shape: "Box", + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + color: { red: 0, green: 180, blue: 239 }, + }, + Text: { + text: "Text", + dimensions: { + x: 0.65, + y: 0.3, + z: 0.01 + }, + textColor: { red: 255, green: 255, blue: 255 }, + backgroundColor: { red: 0, green: 0, blue: 0 }, + lineHeight: 0.06, + faceCamera: false, + }, + Zone: { + dimensions: { + x: 10, + y: 10, + z: 10 + }, + flyingAllowed: true, + ghostingAllowed: true, + filter: "", + keyLightMode: "inherit", + keyLightColor: { red: 255, green: 255, blue: 255 }, + keyLight: { + intensity: 1.0, + direction: { + x: 0.0, + y: -0.707106769084930, // 45 degrees + z: 0.7071067690849304 + }, + castShadows: true + }, + ambientLightMode: "inherit", + ambientLight: { + ambientIntensity: 0.5, + ambientURL: "" + }, + hazeMode: "inherit", + haze: { + hazeRange: 1000, + hazeAltitudeEffect: false, + hazeBaseRef: 0, + hazeColor: { + red: 128, + green: 154, + blue: 179 + }, + hazeBackgroundBlend: 0, + hazeEnableGlare: false, + hazeGlareColor: { + red: 255, + green: 229, + blue: 179 + }, + }, + shapeType: "box", + bloomMode: "inherit", + avatarPriority: "inherit" + }, + Model: { + collisionShape: "none", + compoundShapeURL: "", + animation: { + url: "", + running: false, + allowTranslation: false, + loop: true, + hold: false, + currentFrame: 0, + firstFrame: 0, + lastFrame: 100000, + fps: 30.0, + } + }, + Image: { + dimensions: { + x: 0.5385, + y: 0.2819, + z: 0.0092 + }, + shapeType: "box", + collisionless: true, + keepAspectRatio: false, + imageURL: DEFAULT_IMAGE + }, + Web: { + dimensions: { + x: 1.6, + y: 0.9, + z: 0.01 + }, + sourceUrl: "https://highfidelity.com/", + dpi: 30, + }, + ParticleEffect: { + lifespan: 1.5, + maxParticles: 10, + textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png", + emitRate: 5.5, + emitSpeed: 0, + speedSpread: 0, + emitDimensions: { x: 0, y: 0, z: 0 }, + emitOrientation: { x: 0, y: 0, z: 0, w: 1 }, + emitterShouldTrail: true, + particleRadius: 0.25, + radiusStart: 0, + radiusSpread: 0, + particleColor: { + red: 255, + green: 255, + blue: 255 + }, + colorSpread: { + red: 0, + green: 0, + blue: 0 + }, + alpha: 0, + alphaStart: 1, + alphaSpread: 0, + emitAcceleration: { + x: 0, + y: 2.5, + z: 0 + }, + accelerationSpread: { + x: 0, + y: 0, + z: 0 + }, + particleSpin: 0, + spinSpread: 0, + rotateWithEntity: false, + polarStart: 0, + polarFinish: Math.PI, + azimuthStart: -Math.PI, + azimuthFinish: Math.PI + }, + Light: { + color: { red: 255, green: 255, blue: 255 }, + intensity: 5.0, + dimensions: DEFAULT_LIGHT_DIMENSIONS, + falloffRadius: 1.0, + isSpotlight: false, + exponent: 1.0, + cutoff: 75.0, + }, +}; + +var toolBar = (function () { + var EDIT_SETTING = "io.highfidelity.isEditing"; // for communication with other scripts + var that = {}, + toolBar, + activeButton = null, + systemToolbar = null, + dialogWindow = null, + tablet = null; + + function createNewEntity(requestedProperties) { + var dimensions = requestedProperties.dimensions ? requestedProperties.dimensions : DEFAULT_DIMENSIONS; + var position = getPositionToCreateEntity(); + var entityID = null; + + var properties = {}; + + copyProperties(properties, DEFAULT_ENTITY_PROPERTIES.All); + + var type = requestedProperties.type; + if (type === "Box" || type === "Sphere") { + copyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Shape); + } else { + copyProperties(properties, DEFAULT_ENTITY_PROPERTIES[type]); + } + + // We apply the requested properties first so that they take priority over any default properties. + copyProperties(properties, requestedProperties); + + if (properties.dynamic) { + copyProperties(properties, DEFAULT_DYNAMIC_PROPERTIES); + } else { + copyProperties(properties, DEFAULT_NON_DYNAMIC_PROPERTIES); + } + + + if (position !== null && position !== undefined) { + var direction; + if (Camera.mode === "entity" || Camera.mode === "independent") { + direction = Camera.orientation; + } else { + direction = MyAvatar.orientation; + } + direction = Vec3.multiplyQbyV(direction, Vec3.UNIT_Z); + + var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Image", "Web", "Material"]; + if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { + + // Adjust position of entity per bounding box prior to creating it. + var registration = properties.registration; + if (registration === undefined) { + var DEFAULT_REGISTRATION = { x: 0.5, y: 0.5, z: 0.5 }; + registration = DEFAULT_REGISTRATION; + } + + var orientation = properties.orientation; + if (orientation === undefined) { + properties.orientation = MyAvatar.orientation; + var DEFAULT_ORIENTATION = properties.orientation; + orientation = DEFAULT_ORIENTATION; + } else { + // If the orientation is already defined, we perform the corresponding rotation assuming that + // our start referential is the avatar referential. + properties.orientation = Quat.multiply(MyAvatar.orientation, properties.orientation); + var DEFAULT_ORIENTATION = properties.orientation; + orientation = DEFAULT_ORIENTATION; + } + + position = adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation); + } + + position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions); + properties.position = position; + + if (!properties.grab) { + properties.grab = {}; + if (Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE) && + !(properties.type === "Zone" || properties.type === "Light" || properties.type === "ParticleEffect")) { + properties.grab.grabbable = true; + } else { + properties.grab.grabbable = false; + } + } + + entityID = Entities.addEntity(properties); + SelectionManager.addEntity(entityID, false, this); + SelectionManager.saveProperties(); + pushCommandForSelections([{ + entityID: entityID, + properties: properties + }], [], true); + + var POST_ADJUST_ENTITY_TYPES = ["Model"]; + if (POST_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { + // Adjust position of entity per bounding box after it has been created and auto-resized. + var initialDimensions = Entities.getEntityProperties(entityID, ["dimensions"]).dimensions; + var DIMENSIONS_CHECK_INTERVAL = 200; + var MAX_DIMENSIONS_CHECKS = 10; + var dimensionsCheckCount = 0; + var dimensionsCheckFunction = function () { + dimensionsCheckCount++; + var properties = Entities.getEntityProperties(entityID, ["dimensions", "registrationPoint", "rotation"]); + if (!Vec3.equal(properties.dimensions, initialDimensions)) { + position = adjustPositionPerBoundingBox(position, direction, properties.registrationPoint, + properties.dimensions, properties.rotation); + position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions), + properties.dimensions); + Entities.editEntity(entityID, { + position: position + }); + selectionManager._update(false, this); + } else if (dimensionsCheckCount < MAX_DIMENSIONS_CHECKS) { + Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); + } + }; + Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); + } + } else { + Window.notifyEditError("Can't create " + properties.type + ": " + + properties.type + " would be out of bounds."); + } + + selectionManager.clearSelections(this); + entityListTool.sendUpdate(); + selectionManager.setSelections([entityID], this); + + Window.setFocus(); + + return entityID; + } + + function closeExistingDialogWindow() { + if (dialogWindow) { + dialogWindow.close(); + dialogWindow = null; + } + } + + function cleanup() { + that.setActive(false); + if (tablet) { + tablet.removeButton(activeButton); + } + if (systemToolbar) { + systemToolbar.removeButton(EDIT_TOGGLE_BUTTON); + } + } + + var buttonHandlers = {}; // only used to tablet mode + + function addButton(name, handler) { + buttonHandlers[name] = handler; + } + + var SHAPE_TYPE_NONE = 0; + var SHAPE_TYPE_SIMPLE_HULL = 1; + var SHAPE_TYPE_SIMPLE_COMPOUND = 2; + var SHAPE_TYPE_STATIC_MESH = 3; + var SHAPE_TYPE_BOX = 4; + var SHAPE_TYPE_SPHERE = 5; + var DYNAMIC_DEFAULT = false; + + var MATERIAL_MODE_UV = 0; + var MATERIAL_MODE_PROJECTED = 1; + + function handleNewModelDialogResult(result) { + if (result) { + var url = result.url; + var shapeType; + switch (result.collisionShapeIndex) { + case SHAPE_TYPE_SIMPLE_HULL: + shapeType = "simple-hull"; + break; + case SHAPE_TYPE_SIMPLE_COMPOUND: + shapeType = "simple-compound"; + break; + case SHAPE_TYPE_STATIC_MESH: + shapeType = "static-mesh"; + break; + case SHAPE_TYPE_BOX: + shapeType = "box"; + break; + case SHAPE_TYPE_SPHERE: + shapeType = "sphere"; + break; + default: + shapeType = "none"; + } + + var dynamic = result.dynamic !== null ? result.dynamic : DYNAMIC_DEFAULT; + if (shapeType === "static-mesh" && dynamic) { + // The prompt should prevent this case + print("Error: model cannot be both static mesh and dynamic. This should never happen."); + } else if (url) { + createNewEntity({ + type: "Model", + modelURL: url, + shapeType: shapeType, + grab: { + grabbable: result.grabbable + }, + dynamic: dynamic, + }); + } + } + } + + function handleNewMaterialDialogResult(result) { + if (result) { + var materialURL = result.textInput; + //var materialMappingMode; + //switch (result.comboBox) { + // case MATERIAL_MODE_PROJECTED: + // materialMappingMode = "projected"; + // break; + // default: + // shapeType = "uv"; + //} + var materialData = ""; + if (materialURL.startsWith("materialData")) { + materialData = JSON.stringify({ + "materials": {} + }); + } + + var DEFAULT_LAYERED_MATERIAL_PRIORITY = 1; + if (materialURL) { + createNewEntity({ + type: "Material", + materialURL: materialURL, + //materialMappingMode: materialMappingMode, + priority: DEFAULT_LAYERED_MATERIAL_PRIORITY, + materialData: materialData + }); + } + } + } + + function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + tablet.popFromStack(); + switch (message.method) { + case "newModelDialogAdd": + handleNewModelDialogResult(message.params); + closeExistingDialogWindow(); + break; + case "newModelDialogCancel": + closeExistingDialogWindow(); + break; + case "newEntityButtonClicked": + buttonHandlers[message.params.buttonName](); + break; + case "newMaterialDialogAdd": + handleNewMaterialDialogResult(message.params); + closeExistingDialogWindow(); + break; + case "newMaterialDialogCancel": + closeExistingDialogWindow(); + break; + } + } + + var entitiesToDelete = []; + var deletedEntityTimer = null; + var DELETE_ENTITY_TIMER_TIMEOUT = 100; + + function checkDeletedEntityAndUpdate(entityID) { + // Allow for multiple entity deletes before updating the entities selected. + entitiesToDelete.push(entityID); + if (deletedEntityTimer !== null) { + Script.clearTimeout(deletedEntityTimer); + } + deletedEntityTimer = Script.setTimeout(function () { + if (entitiesToDelete.length > 0) { + selectionManager.removeEntities(entitiesToDelete, this); + } + entityListTool.removeEntities(entitiesToDelete, selectionManager.selections); + entitiesToDelete = []; + deletedEntityTimer = null; + }, DELETE_ENTITY_TIMER_TIMEOUT); + } + + function initialize() { + Script.scriptEnding.connect(cleanup); + Window.domainChanged.connect(function () { + if (isActive) { + tablet.gotoHomeScreen(); + } + that.setActive(false); + that.clearEntityList(); + checkEditPermissionsAndUpdate(); + }); + + HMD.displayModeChanged.connect(function() { + if (isActive) { + tablet.gotoHomeScreen(); + } + that.setActive(false); + }); + + Entities.canAdjustLocksChanged.connect(function (canAdjustLocks) { + if (isActive && !canAdjustLocks) { + that.setActive(false); + } + checkEditPermissionsAndUpdate(); + }); + + Entities.canRezChanged.connect(checkEditPermissionsAndUpdate); + Entities.canRezTmpChanged.connect(checkEditPermissionsAndUpdate); + Entities.canRezCertifiedChanged.connect(checkEditPermissionsAndUpdate); + Entities.canRezTmpCertifiedChanged.connect(checkEditPermissionsAndUpdate); + var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()); + + Entities.deletingEntity.connect(checkDeletedEntityAndUpdate); + + var createButtonIconRsrc = (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON); + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + activeButton = tablet.addButton({ + captionColor: hasRezPermissions ? "#ffffff" : "#888888", + icon: createButtonIconRsrc, + activeIcon: "icons/tablet-icons/edit-a.svg", + text: "CREATE", + sortOrder: 10 + }); + createButton = activeButton; + tablet.screenChanged.connect(function (type, url) { + var isGoingToHomescreenOnDesktop = (!shouldUseEditTabletApp() && + (url === 'hifi/tablet/TabletHome.qml' || url === '')); + if (isActive && (type !== "QML" || url !== "hifi/tablet/Edit.qml") && !isGoingToHomescreenOnDesktop) { + that.setActive(false); + } + }); + tablet.fromQml.connect(fromQml); + createToolsWindow.fromQml.addListener(fromQml); + + createButton.clicked.connect(function() { + if ( ! (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()) ) { + Window.notifyEditError(INSUFFICIENT_PERMISSIONS_ERROR_MSG); + return; + } + + that.toggle(); + }); + + addButton("importEntitiesButton", function() { + Window.browseChanged.connect(onFileOpenChanged); + Window.browseAsync("Select Model to Import", "", "*.json"); + }); + + addButton("openAssetBrowserButton", function() { + Window.showAssetServer(); + }); + function createNewEntityDialogButtonCallback(entityType) { + return function() { + if (shouldUseEditTabletApp()) { + // tablet version of new-model dialog + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + tablet.pushOntoStack("hifi/tablet/New" + entityType + "Dialog.qml"); + } else { + closeExistingDialogWindow(); + var qmlPath = Script.resourcesPath() + "qml/hifi/tablet/New" + entityType + "Window.qml"; + var DIALOG_WINDOW_SIZE = { x: 500, y: 300 }; + dialogWindow = Desktop.createWindow(qmlPath, { + title: "New " + entityType + " Entity", + flags: Desktop.ALWAYS_ON_TOP | Desktop.CLOSE_BUTTON_HIDES, + presentationMode: Desktop.PresentationMode.NATIVE, + size: DIALOG_WINDOW_SIZE, + visible: true + }); + dialogWindow.fromQml.connect(fromQml); + } + }; + } + + addButton("newModelButton", createNewEntityDialogButtonCallback("Model")); + + addButton("newShapeButton", function () { + createNewEntity({ + type: "Shape", + shape: "Cube", + }); + }); + + addButton("newLightButton", function () { + createNewEntity({ + type: "Light", + }); + }); + + addButton("newTextButton", function () { + createNewEntity({ + type: "Text", + }); + }); + + addButton("newImageButton", function () { + createNewEntity({ + type: "Image", + }); + }); + + addButton("newWebButton", function () { + createNewEntity({ + type: "Web", + }); + }); + + addButton("newZoneButton", function () { + createNewEntity({ + type: "Zone", + }); + }); + + addButton("newParticleButton", function () { + createNewEntity({ + type: "ParticleEffect", + }); + }); + + addButton("newMaterialButton", createNewEntityDialogButtonCallback("Material")); + + var deactivateCreateIfDesktopWindowsHidden = function() { + if (!shouldUseEditTabletApp() && !entityListTool.isVisible() && !createToolsWindow.isVisible()) { + that.setActive(false); + } + }; + entityListTool.interactiveWindowHidden.addListener(this, deactivateCreateIfDesktopWindowsHidden); + createToolsWindow.interactiveWindowHidden.addListener(this, deactivateCreateIfDesktopWindowsHidden); + + that.setActive(false); + } + + that.clearEntityList = function () { + entityListTool.clearEntityList(); + }; + + that.toggle = function () { + that.setActive(!isActive); + if (!isActive) { + tablet.gotoHomeScreen(); + } + }; + + that.setActive = function (active) { + ContextOverlay.enabled = !active; + Settings.setValue(EDIT_SETTING, active); + if (active) { + Controller.captureEntityClickEvents(); + } else { + Controller.releaseEntityClickEvents(); + + closeExistingDialogWindow(); + } + if (active === isActive) { + return; + } + if (active && !Entities.canRez() && !Entities.canRezTmp() && !Entities.canRezCertified() && !Entities.canRezTmpCertified()) { + Window.notifyEditError(INSUFFICIENT_PERMISSIONS_ERROR_MSG); + return; + } + Messages.sendLocalMessage("edit-events", JSON.stringify({ + enabled: active + })); + isActive = active; + activeButton.editProperties({isActive: isActive}); + undoHistory.setEnabled(isActive); + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + + if (!isActive) { + entityListTool.setVisible(false); + gridTool.setVisible(false); + grid.setEnabled(false); + propertiesTool.setVisible(false); + selectionManager.clearSelections(this); + cameraManager.disable(); + selectionDisplay.disableTriggerMapping(); + tablet.landscape = false; + Controller.disableMapping(CONTROLLER_MAPPING_NAME); + } else { + if (shouldUseEditTabletApp()) { + tablet.loadQMLSource("hifi/tablet/Edit.qml", true); + } else { + // make other apps inactive while in desktop mode + tablet.gotoHomeScreen(); + } + UserActivityLogger.enabledEdit(); + entityListTool.setVisible(true); + entityListTool.sendUpdate(); + gridTool.setVisible(true); + grid.setEnabled(true); + propertiesTool.setVisible(true); + selectionDisplay.enableTriggerMapping(); + print("starting tablet in landscape mode"); + tablet.landscape = true; + Controller.enableMapping(CONTROLLER_MAPPING_NAME); + // Not sure what the following was meant to accomplish, but it currently causes + // everybody else to think that Interface has lost focus overall. fogbugzid:558 + // Window.setFocus(); + } + entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE)); + Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); + }; + + initialize(); + return that; +})(); + +var selectedEntityID; +var orientation; +var intersection; + + +function rayPlaneIntersection(pickRay, point, normal) { // + // + // This version of the test returns the intersection of a line with a plane + // + var collides = Vec3.dot(pickRay.direction, normal); + + var d = -Vec3.dot(point, normal); + var t = -(Vec3.dot(pickRay.origin, normal) + d) / collides; + + return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t)); +} + +function rayPlaneIntersection2(pickRay, point, normal) { + // + // This version of the test returns false if the ray is directed away from the plane + // + var collides = Vec3.dot(pickRay.direction, normal); + var d = -Vec3.dot(point, normal); + var t = -(Vec3.dot(pickRay.origin, normal) + d) / collides; + if (t < 0.0) { + return false; + } else { + return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t)); + } +} + +function findClickedEntity(event) { + var pickZones = event.isControl; + + if (pickZones) { + Entities.setZonesArePickable(true); + } + + var pickRay = Camera.computePickRay(event.x, event.y); + var tabletIDs = getMainTabletIDs(); + if (tabletIDs.length > 0) { + var overlayResult = Overlays.findRayIntersection(pickRay, true, tabletIDs); + if (overlayResult.intersects) { + return null; + } + } + + var entityResult = Entities.findRayIntersection(pickRay, true); // want precision picking + var iconResult = entityIconOverlayManager.findRayIntersection(pickRay); + iconResult.accurate = true; + + if (pickZones) { + Entities.setZonesArePickable(false); + } + + var result; + + if (iconResult.intersects) { + result = iconResult; + } else if (entityResult.intersects) { + result = entityResult; + } else { + return null; + } + + if (!result.accurate) { + return null; + } + + var foundEntity = result.entityID; + return { + pickRay: pickRay, + entityID: foundEntity, + intersection: result.intersection + }; +} + +// Handles selections on overlays while in edit mode by querying entities from +// entityIconOverlayManager. +function handleOverlaySelectionToolUpdates(channel, message, sender) { + var wantDebug = false; + if (sender !== MyAvatar.sessionUUID || channel !== 'entityToolUpdates') + return; + + var data = JSON.parse(message); + + if (data.method === "selectOverlay") { + if (!selectionDisplay.triggered() || selectionDisplay.triggeredHand === data.hand) { + if (wantDebug) { + print("setting selection to overlay " + data.overlayID); + } + var entity = entityIconOverlayManager.findEntity(data.overlayID); + + if (entity !== null) { + selectionManager.setSelections([entity], this); + } + } + } +} + +function handleMessagesReceived(channel, message, sender) { + switch( channel ){ + case 'entityToolUpdates': { + handleOverlaySelectionToolUpdates( channel, message, sender ); + break; + } + default: { + return; + } + } +} + +Messages.subscribe("entityToolUpdates"); +Messages.messageReceived.connect(handleMessagesReceived); + +var mouseHasMovedSincePress = false; +var mousePressStartTime = 0; +var mousePressStartPosition = { + x: 0, + y: 0 +}; +var mouseDown = false; + +function mousePressEvent(event) { + mouseDown = true; + mousePressStartPosition = { + x: event.x, + y: event.y + }; + mousePressStartTime = Date.now(); + mouseHasMovedSincePress = false; + mouseCapturedByTool = false; + + if (propertyMenu.mousePressEvent(event) || progressDialog.mousePressEvent(event)) { + mouseCapturedByTool = true; + return; + } + if (isActive) { + if (cameraManager.mousePressEvent(event) || selectionDisplay.mousePressEvent(event)) { + // Event handled; do nothing. + return; + } + } +} + +var mouseCapturedByTool = false; +var lastMousePosition = null; +var CLICK_TIME_THRESHOLD = 500 * 1000; // 500 ms +var CLICK_MOVE_DISTANCE_THRESHOLD = 20; +var IDLE_MOUSE_TIMEOUT = 200; + +var lastMouseMoveEvent = null; + +function mouseMoveEventBuffered(event) { + lastMouseMoveEvent = event; +} + +function mouseMove(event) { + if (mouseDown && !mouseHasMovedSincePress) { + var timeSincePressMicro = Date.now() - mousePressStartTime; + + var dX = mousePressStartPosition.x - event.x; + var dY = mousePressStartPosition.y - event.y; + var sqDist = (dX * dX) + (dY * dY); + + // If less than CLICK_TIME_THRESHOLD has passed since the mouse click AND the mouse has moved + // less than CLICK_MOVE_DISTANCE_THRESHOLD distance, then don't register this as a mouse move + // yet. The goal is to provide mouse clicks that are more lenient to small movements. + if (timeSincePressMicro < CLICK_TIME_THRESHOLD && sqDist < CLICK_MOVE_DISTANCE_THRESHOLD) { + return; + } + mouseHasMovedSincePress = true; + } + + if (!isActive) { + return; + } + + // allow the selectionDisplay and cameraManager to handle the event first, if it doesn't handle it, then do our own thing + if (selectionDisplay.mouseMoveEvent(event) || propertyMenu.mouseMoveEvent(event) || cameraManager.mouseMoveEvent(event)) { + return; + } + + lastMousePosition = { + x: event.x, + y: event.y + }; +} + +function mouseReleaseEvent(event) { + mouseDown = false; + + if (lastMouseMoveEvent) { + mouseMove(lastMouseMoveEvent); + lastMouseMoveEvent = null; + } + if (propertyMenu.mouseReleaseEvent(event)) { + return true; + } + if (isActive && selectionManager.hasSelection()) { + tooltip.show(false); + } + if (mouseCapturedByTool) { + + return; + } + + cameraManager.mouseReleaseEvent(event); + + if (!mouseHasMovedSincePress) { + mouseClickEvent(event); + } +} + +function wasTabletOrEditHandleClicked(event) { + var rayPick = Camera.computePickRay(event.x, event.y); + var result = Overlays.findRayIntersection(rayPick, true); + if (result.intersects) { + var overlayID = result.overlayID; + var tabletIDs = getMainTabletIDs(); + if (tabletIDs.indexOf(overlayID) >= 0) { + return true; + } else if (selectionDisplay.isEditHandle(overlayID)) { + return true; + } + } + return false; +} + +function mouseClickEvent(event) { + var wantDebug = false; + var result, properties, tabletClicked; + if (isActive && event.isLeftButton) { + result = findClickedEntity(event); + var tabletOrEditHandleClicked = wasTabletOrEditHandleClicked(event); + if (tabletOrEditHandleClicked) { + return; + } + + if (result === null || result === undefined) { + if (!event.isShifted) { + selectionManager.clearSelections(this); + } + return; + } + toolBar.setActive(true); + var pickRay = result.pickRay; + var foundEntity = result.entityID; + if (HMD.tabletID && foundEntity === HMD.tabletID) { + return; + } + properties = Entities.getEntityProperties(foundEntity); + var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; + + if (wantDebug) { + print("Checking properties: " + properties.id + " " + " - Half Diagonal:" + halfDiagonal); + } + // P P - Model + // /| A - Palm + // / | d B - unit vector toward tip + // / | X - base of the perpendicular line + // A---X----->B d - distance fom axis + // x x - distance from A + // + // |X-A| = (P-A).B + // X === A + ((P-A).B)B + // d = |P-X| + + var A = pickRay.origin; + var B = Vec3.normalize(pickRay.direction); + var P = properties.position; + + var x = Vec3.dot(Vec3.subtract(P, A), B); + + var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * + 180 / Math.PI; + + var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) && + (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); + + if (0 < x && sizeOK) { + selectedEntityID = foundEntity; + orientation = MyAvatar.orientation; + intersection = rayPlaneIntersection(pickRay, P, Quat.getForward(orientation)); + + if (!event.isShifted) { + selectionManager.setSelections([foundEntity], this); + } else { + selectionManager.addEntity(foundEntity, true, this); + } + selectionManager.saveProperties(); + + if (wantDebug) { + print("Model selected: " + foundEntity); + } + selectionDisplay.select(selectedEntityID, event); + + if (Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)) { + cameraManager.enable(); + cameraManager.focus(selectionManager.worldPosition, + selectionManager.worldDimensions, + Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + } + } + } else if (event.isRightButton) { + result = findClickedEntity(event); + if (result) { + if (SHOULD_SHOW_PROPERTY_MENU !== true) { + return; + } + properties = Entities.getEntityProperties(result.entityID); + if (properties.marketplaceID) { + propertyMenu.marketplaceID = properties.marketplaceID; + propertyMenu.updateMenuItemText(showMenuItem, "Show in Marketplace"); + } else { + propertyMenu.marketplaceID = null; + propertyMenu.updateMenuItemText(showMenuItem, "No marketplace info"); + } + propertyMenu.setPosition(event.x, event.y); + propertyMenu.show(); + } else { + propertyMenu.hide(); + } + } +} + +Controller.mousePressEvent.connect(mousePressEvent); +Controller.mouseMoveEvent.connect(mouseMoveEventBuffered); +Controller.mouseReleaseEvent.connect(mouseReleaseEvent); + + +// In order for editVoxels and editModels to play nice together, they each check to see if a "delete" menu item already +// exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that +// added it. +var modelMenuAddedDelete = false; +var originalLightsArePickable = Entities.getLightsArePickable(); + +function setupModelMenus() { + // adj our menuitems + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Undo", + shortcutKey: 'Ctrl+Z', + position: 0, + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Redo", + shortcutKey: 'Ctrl+Y', + position: 1, + }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Entities", + isSeparator: true + }); + if (!Menu.menuItemExists("Edit", "Delete")) { + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Delete", + shortcutKeyEvent: { + text: "delete" + }, + afterItem: "Entities", + }); + modelMenuAddedDelete = true; + } + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Parent Entity to Last", + afterItem: "Entities" + }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Unparent Entity", + afterItem: "Parent Entity to Last" + }); + + Menu.addMenuItem({ + menuName: GRABBABLE_ENTITIES_MENU_CATEGORY, + menuItemName: MENU_CREATE_ENTITIES_GRABBABLE, + afterItem: "Unparent Entity", + isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_CREATE_ENTITIES_GRABBABLE, true) + }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_ALLOW_SELECTION_LARGE, + afterItem: MENU_CREATE_ENTITIES_GRABBABLE, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, true) + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_ALLOW_SELECTION_SMALL, + afterItem: MENU_ALLOW_SELECTION_LARGE, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, true) + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_ALLOW_SELECTION_LIGHTS, + afterItem: MENU_ALLOW_SELECTION_SMALL, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, false) + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Select All Entities In Box", + afterItem: "Allow Selecting of Lights" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Select All Entities Touching Box", + afterItem: "Select All Entities In Box" + }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Export Entities", + afterItem: "Entities" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Import Entities", + afterItem: "Export Entities" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Import Entities from URL", + afterItem: "Import Entities" + }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_AUTO_FOCUS_ON_SELECT, + isCheckable: true, + isChecked: Settings.getValue(SETTING_AUTO_FOCUS_ON_SELECT) === "true" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_EASE_ON_FOCUS, + afterItem: MENU_AUTO_FOCUS_ON_SELECT, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) === "true" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, + afterItem: MENU_EASE_ON_FOCUS, + isCheckable: true, + isChecked: Settings.getValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE) !== "false" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_SHOW_ZONES_IN_EDIT_MODE, + afterItem: MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, + isCheckable: true, + isChecked: Settings.getValue(SETTING_SHOW_ZONES_IN_EDIT_MODE) !== "false" + }); + + Entities.setLightsArePickable(false); +} + +setupModelMenus(); // do this when first running our script. + +function cleanupModelMenus() { + Menu.removeMenuItem("Edit", "Undo"); + Menu.removeMenuItem("Edit", "Redo"); + + Menu.removeSeparator("Edit", "Entities"); + if (modelMenuAddedDelete) { + // delete our menuitems + Menu.removeMenuItem("Edit", "Delete"); + } + + Menu.removeMenuItem("Edit", "Parent Entity to Last"); + Menu.removeMenuItem("Edit", "Unparent Entity"); + Menu.removeMenuItem("Edit", "Allow Selecting of Large Models"); + Menu.removeMenuItem("Edit", "Allow Selecting of Small Models"); + Menu.removeMenuItem("Edit", "Allow Selecting of Lights"); + Menu.removeMenuItem("Edit", "Select All Entities In Box"); + Menu.removeMenuItem("Edit", "Select All Entities Touching Box"); + + Menu.removeMenuItem("Edit", "Export Entities"); + Menu.removeMenuItem("Edit", "Import Entities"); + Menu.removeMenuItem("Edit", "Import Entities from URL"); + + Menu.removeMenuItem("Edit", MENU_AUTO_FOCUS_ON_SELECT); + Menu.removeMenuItem("Edit", MENU_EASE_ON_FOCUS); + Menu.removeMenuItem("Edit", MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE); + Menu.removeMenuItem("Edit", MENU_SHOW_ZONES_IN_EDIT_MODE); + Menu.removeMenuItem("Edit", MENU_CREATE_ENTITIES_GRABBABLE); +} + +Script.scriptEnding.connect(function () { + toolBar.setActive(false); + Settings.setValue(SETTING_AUTO_FOCUS_ON_SELECT, Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)); + Settings.setValue(SETTING_EASE_ON_FOCUS, Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + Settings.setValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE)); + Settings.setValue(SETTING_SHOW_ZONES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); + + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE)); + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL)); + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LIGHTS)); + + + progressDialog.cleanup(); + cleanupModelMenus(); + tooltip.cleanup(); + selectionDisplay.cleanup(); + entityShapeVisualizer.cleanup(); + Entities.setLightsArePickable(originalLightsArePickable); + + Overlays.deleteOverlay(importingSVOImageOverlay); + Overlays.deleteOverlay(importingSVOTextOverlay); + + Controller.keyReleaseEvent.disconnect(keyReleaseEvent); + Controller.keyPressEvent.disconnect(keyPressEvent); + + Controller.mousePressEvent.disconnect(mousePressEvent); + Controller.mouseMoveEvent.disconnect(mouseMoveEventBuffered); + Controller.mouseReleaseEvent.disconnect(mouseReleaseEvent); + + Messages.messageReceived.disconnect(handleMessagesReceived); + Messages.unsubscribe("entityToolUpdates"); + createButton = null; +}); + +var lastOrientation = null; +var lastPosition = null; + +// Do some stuff regularly, like check for placement of various overlays +Script.update.connect(function (deltaTime) { + progressDialog.move(); + selectionDisplay.checkControllerMove(); + var dOrientation = Math.abs(Quat.dot(Camera.orientation, lastOrientation) - 1); + var dPosition = Vec3.distance(Camera.position, lastPosition); + if (dOrientation > 0.001 || dPosition > 0.001) { + propertyMenu.hide(); + lastOrientation = Camera.orientation; + lastPosition = Camera.position; + } + if (lastMouseMoveEvent) { + mouseMove(lastMouseMoveEvent); + lastMouseMoveEvent = null; + } +}); + +function insideBox(center, dimensions, point) { + return (Math.abs(point.x - center.x) <= (dimensions.x / 2.0)) && + (Math.abs(point.y - center.y) <= (dimensions.y / 2.0)) && + (Math.abs(point.z - center.z) <= (dimensions.z / 2.0)); +} + +function selectAllEntitiesInCurrentSelectionBox(keepIfTouching) { + if (selectionManager.hasSelection()) { + // Get all entities touching the bounding box of the current selection + var boundingBoxCorner = Vec3.subtract(selectionManager.worldPosition, + Vec3.multiply(selectionManager.worldDimensions, 0.5)); + var entities = Entities.findEntitiesInBox(boundingBoxCorner, selectionManager.worldDimensions); + + if (!keepIfTouching) { + var isValid; + if (selectionManager.localPosition === null || selectionManager.localPosition === undefined) { + isValid = function (position) { + return insideBox(selectionManager.worldPosition, selectionManager.worldDimensions, position); + }; + } else { + isValid = function (position) { + var localPosition = Vec3.multiplyQbyV(Quat.inverse(selectionManager.localRotation), + Vec3.subtract(position, + selectionManager.localPosition)); + return insideBox(Vec3.ZERO, selectionManager.localDimensions, localPosition); + }; + } + for (var i = 0; i < entities.length; ++i) { + var properties = Entities.getEntityProperties(entities[i]); + if (!isValid(properties.position)) { + entities.splice(i, 1); + --i; + } + } + } + selectionManager.setSelections(entities, this); + } +} + +function sortSelectedEntities(selected) { + var sortedEntities = selected.slice(); + var begin = 0; + while (begin < sortedEntities.length) { + var elementRemoved = false; + var next = begin + 1; + while (next < sortedEntities.length) { + var beginID = sortedEntities[begin]; + var nextID = sortedEntities[next]; + + if (Entities.isChildOfParent(beginID, nextID)) { + sortedEntities[begin] = nextID; + sortedEntities[next] = beginID; + sortedEntities.splice(next, 1); + elementRemoved = true; + break; + } else if (Entities.isChildOfParent(nextID, beginID)) { + sortedEntities.splice(next, 1); + elementRemoved = true; + break; + } + next++; + } + if (!elementRemoved) { + begin++; + } + } + return sortedEntities; +} + +function recursiveDelete(entities, childrenList, deletedIDs, entityHostType) { + var wantDebug = false; + var entitiesLength = entities.length; + var initialPropertySets = Entities.getMultipleEntityProperties(entities); + var entityHostTypes = Entities.getMultipleEntityProperties(entities, 'entityHostType'); + for (var i = 0; i < entitiesLength; ++i) { + var entityID = entities[i]; + + if (entityHostTypes[i].entityHostType !== entityHostType) { + if (wantDebug) { + console.log("Skipping deletion of entity " + entityID + " with conflicting entityHostType: " + + entityHostTypes[i].entityHostType + ", expected: " + entityHostType); + } + continue; + } + + var children = Entities.getChildrenIDs(entityID); + var grandchildrenList = []; + recursiveDelete(children, grandchildrenList, deletedIDs, entityHostType); + childrenList.push({ + entityID: entityID, + properties: initialPropertySets[i], + children: grandchildrenList + }); + deletedIDs.push(entityID); + Entities.deleteEntity(entityID); + } +} + +function unparentSelectedEntities() { + if (SelectionManager.hasSelection()) { + var selectedEntities = selectionManager.selections; + var parentCheck = false; + + if (selectedEntities.length < 1) { + Window.notifyEditError("You must have an entity selected in order to unparent it."); + return; + } + selectedEntities.forEach(function (id, index) { + var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID; + if (parentId !== null && parentId.length > 0 && parentId !== Uuid.NULL) { + parentCheck = true; + } + Entities.editEntity(id, {parentID: null}); + return true; + }); + if (parentCheck) { + if (selectedEntities.length > 1) { + Window.notify("Entities unparented"); + } else { + Window.notify("Entity unparented"); + } + } else { + if (selectedEntities.length > 1) { + Window.notify("Selected Entities have no parents"); + } else { + Window.notify("Selected Entity does not have a parent"); + } + } + } else { + Window.notifyEditError("You have nothing selected to unparent"); + } +} +function parentSelectedEntities() { + if (SelectionManager.hasSelection()) { + var selectedEntities = selectionManager.selections; + if (selectedEntities.length <= 1) { + Window.notifyEditError("You must have multiple entities selected in order to parent them"); + return; + } + var parentCheck = false; + var lastEntityId = selectedEntities[selectedEntities.length - 1]; + selectedEntities.forEach(function (id, index) { + if (lastEntityId !== id) { + var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID; + if (parentId !== lastEntityId) { + parentCheck = true; + } + Entities.editEntity(id, {parentID: lastEntityId}); + } + }); + + if (parentCheck) { + Window.notify("Entities parented"); + } else { + Window.notify("Entities are already parented to last"); + } + } else { + Window.notifyEditError("You have nothing selected to parent"); + } +} +function deleteSelectedEntities() { + if (SelectionManager.hasSelection()) { + var deletedIDs = []; + + SelectionManager.saveProperties(); + var savedProperties = []; + var newSortedSelection = sortSelectedEntities(selectionManager.selections); + var entityHostTypes = Entities.getMultipleEntityProperties(newSortedSelection, 'entityHostType'); + for (var i = 0; i < newSortedSelection.length; ++i) { + var entityID = newSortedSelection[i]; + var initialProperties = SelectionManager.savedProperties[entityID]; + if (initialProperties.locked || + (initialProperties.avatarEntity && initialProperties.owningAvatarID !== MyAvatar.sessionUUID)) { + continue; + } + var children = Entities.getChildrenIDs(entityID); + var childList = []; + recursiveDelete(children, childList, deletedIDs, entityHostTypes[i].entityHostType); + savedProperties.push({ + entityID: entityID, + properties: initialProperties, + children: childList + }); + deletedIDs.push(entityID); + Entities.deleteEntity(entityID); + } + + if (savedProperties.length > 0) { + SelectionManager.clearSelections(this); + pushCommandForSelections([], savedProperties); + entityListTool.deleteEntities(deletedIDs); + } + } +} + +function toggleSelectedEntitiesLocked() { + if (SelectionManager.hasSelection()) { + var locked = !Entities.getEntityProperties(SelectionManager.selections[0], ["locked"]).locked; + for (var i = 0; i < selectionManager.selections.length; i++) { + var entityID = SelectionManager.selections[i]; + Entities.editEntity(entityID, { + locked: locked + }); + } + entityListTool.sendUpdate(); + selectionManager._update(false, this); + } +} + +function toggleSelectedEntitiesVisible() { + if (SelectionManager.hasSelection()) { + var visible = !Entities.getEntityProperties(SelectionManager.selections[0], ["visible"]).visible; + for (var i = 0; i < selectionManager.selections.length; i++) { + var entityID = SelectionManager.selections[i]; + Entities.editEntity(entityID, { + visible: visible + }); + } + entityListTool.sendUpdate(); + selectionManager._update(false, this); + } +} + +function onFileSaveChanged(filename) { + Window.saveFileChanged.disconnect(onFileSaveChanged); + if (filename !== "") { + var success = Clipboard.exportEntities(filename, selectionManager.selections); + if (!success) { + Window.notifyEditError("Export failed."); + } + } +} + +function onFileOpenChanged(filename) { + // disconnect the event, otherwise the requests will stack up + try { + // Not all calls to onFileOpenChanged() connect an event. + Window.browseChanged.disconnect(onFileOpenChanged); + } catch (e) { + // Ignore. + } + + var importURL = null; + if (filename !== "") { + importURL = filename; + if (!/^(http|https):\/\//.test(filename)) { + importURL = "file:///" + importURL; + } + } + if (importURL) { + if (!isActive && (Entities.canRez() && Entities.canRezTmp() && Entities.canRezCertified() && Entities.canRezTmpCertified())) { + toolBar.toggle(); + } + importSVO(importURL); + } +} + +function onPromptTextChanged(prompt) { + Window.promptTextChanged.disconnect(onPromptTextChanged); + if (prompt !== "") { + if (!isActive && (Entities.canRez() && Entities.canRezTmp() && Entities.canRezCertified() && Entities.canRezTmpCertified())) { + toolBar.toggle(); + } + importSVO(prompt); + } +} + +function handleMenuEvent(menuItem) { + if (menuItem === "Allow Selecting of Small Models") { + allowSmallModels = Menu.isOptionChecked("Allow Selecting of Small Models"); + } else if (menuItem === "Allow Selecting of Large Models") { + allowLargeModels = Menu.isOptionChecked("Allow Selecting of Large Models"); + } else if (menuItem === "Allow Selecting of Lights") { + Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights")); + } else if (menuItem === "Delete") { + deleteSelectedEntities(); + } else if (menuItem === "Undo") { + undoHistory.undo(); + } else if (menuItem === "Redo") { + undoHistory.redo(); + } else if (menuItem === "Parent Entity to Last") { + parentSelectedEntities(); + } else if (menuItem === "Unparent Entity") { + unparentSelectedEntities(); + } else if (menuItem === "Export Entities") { + if (!selectionManager.hasSelection()) { + Window.notifyEditError("No entities have been selected."); + } else { + Window.saveFileChanged.connect(onFileSaveChanged); + Window.saveAsync("Select Where to Save", "", "*.json"); + } + } else if (menuItem === "Import Entities" || menuItem === "Import Entities from URL") { + if (menuItem === "Import Entities") { + Window.browseChanged.connect(onFileOpenChanged); + Window.browseAsync("Select Model to Import", "", "*.json"); + } else { + Window.promptTextChanged.connect(onPromptTextChanged); + Window.promptAsync("URL of SVO to import", ""); + } + } else if (menuItem === "Select All Entities In Box") { + selectAllEntitiesInCurrentSelectionBox(false); + } else if (menuItem === "Select All Entities Touching Box") { + selectAllEntitiesInCurrentSelectionBox(true); + } else if (menuItem === MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE) { + entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE)); + } else if (menuItem === MENU_SHOW_ZONES_IN_EDIT_MODE) { + Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); + } else if (menuItem === MENU_CREATE_ENTITIES_GRABBABLE) { + Settings.setValue(SETTING_EDIT_PREFIX + menuItem, Menu.isOptionChecked(menuItem)); + } + tooltip.show(false); +} + +var HALF_TREE_SCALE = 16384; + +function getPositionToCreateEntity(extra) { + var CREATE_DISTANCE = 2; + var position; + var delta = extra !== undefined ? extra : 0; + if (Camera.mode === "entity" || Camera.mode === "independent") { + position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), CREATE_DISTANCE + delta)); + } else { + position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getForward(MyAvatar.orientation), CREATE_DISTANCE + delta)); + } + + if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) { + return null; + } + return position; +} + +function importSVO(importURL) { + if (!Entities.canRez() && !Entities.canRezTmp() && + !Entities.canRezCertified() && !Entities.canRezTmpCertified()) { + Window.notifyEditError(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG); + return; + } + + Overlays.editOverlay(importingSVOTextOverlay, { + visible: true + }); + Overlays.editOverlay(importingSVOImageOverlay, { + visible: true + }); + + var success = Clipboard.importEntities(importURL); + + if (success) { + var VERY_LARGE = 10000; + var isLargeImport = Clipboard.getClipboardContentsLargestDimension() >= VERY_LARGE; + var position = Vec3.ZERO; + if (!isLargeImport) { + position = getPositionToCreateEntity(Clipboard.getClipboardContentsLargestDimension() / 2); + } + if (position !== null && position !== undefined) { + var pastedEntityIDs = Clipboard.pasteEntities(position); + 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 + // centered on the avatar/camera direction. + var deltaPosition = Vec3.ZERO; + var entityPositions = []; + var entityParentIDs = []; + + var propType = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]).type; + var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"]; + if (NO_ADJUST_ENTITY_TYPES.indexOf(propType) === -1) { + var targetDirection; + if (Camera.mode === "entity" || Camera.mode === "independent") { + targetDirection = Camera.orientation; + } else { + targetDirection = MyAvatar.orientation; + } + targetDirection = Vec3.multiplyQbyV(targetDirection, Vec3.UNIT_Z); + + var targetPosition = getPositionToCreateEntity(); + var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection. + var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection. + for (var i = 0, length = pastedEntityIDs.length; i < length; i++) { + var curLoopEntityProps = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions", + "registrationPoint", "rotation", "parentID"]); + var adjustedPosition = adjustPositionPerBoundingBox(targetPosition, targetDirection, + curLoopEntityProps.registrationPoint, curLoopEntityProps.dimensions, curLoopEntityProps.rotation); + var delta = Vec3.subtract(adjustedPosition, curLoopEntityProps.position); + var distance = Vec3.dot(delta, targetDirection); + deltaParallel = Math.min(distance, deltaParallel); + deltaPerpendicular = Vec3.sum(Vec3.subtract(delta, Vec3.multiply(distance, targetDirection)), + deltaPerpendicular); + entityPositions[i] = curLoopEntityProps.position; + entityParentIDs[i] = curLoopEntityProps.parentID; + } + deltaPerpendicular = Vec3.multiply(1 / pastedEntityIDs.length, deltaPerpendicular); + deltaPosition = Vec3.sum(Vec3.multiply(deltaParallel, targetDirection), deltaPerpendicular); + } + + if (grid.getSnapToGrid()) { + var firstEntityProps = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions", + "registrationPoint"]); + var positionPreSnap = Vec3.sum(deltaPosition, firstEntityProps.position); + position = grid.snapToSurface(grid.snapToGrid(positionPreSnap, false, firstEntityProps.dimensions, + firstEntityProps.registrationPoint), firstEntityProps.dimensions, firstEntityProps.registrationPoint); + deltaPosition = Vec3.subtract(position, firstEntityProps.position); + } + + if (!Vec3.equal(deltaPosition, Vec3.ZERO)) { + for (var editEntityIndex = 0, numEntities = pastedEntityIDs.length; editEntityIndex < numEntities; editEntityIndex++) { + if (Uuid.isNull(entityParentIDs[editEntityIndex])) { + Entities.editEntity(pastedEntityIDs[editEntityIndex], { + position: Vec3.sum(deltaPosition, entityPositions[editEntityIndex]) + }); + } + } + } + } + + if (isActive) { + selectionManager.setSelections(pastedEntityIDs, this); + } + } else { + Window.notifyEditError("Can't import entities: entities would be out of bounds."); + } + } else { + Window.notifyEditError("There was an error importing the entity file."); + } + + Overlays.editOverlay(importingSVOTextOverlay, { + visible: false + }); + Overlays.editOverlay(importingSVOImageOverlay, { + visible: false + }); +} +Window.svoImportRequested.connect(importSVO); + +Menu.menuItemEvent.connect(handleMenuEvent); + +var keyPressEvent = function (event) { + if (isActive) { + cameraManager.keyPressEvent(event); + } +}; +var keyReleaseEvent = function (event) { + if (isActive) { + cameraManager.keyReleaseEvent(event); + } +}; +Controller.keyReleaseEvent.connect(keyReleaseEvent); +Controller.keyPressEvent.connect(keyPressEvent); + +function deleteKey(value) { + if (value === 0) { // on release + deleteSelectedEntities(); + } +} +function deselectKey(value) { + if (value === 0) { // on release + selectionManager.clearSelections(this); + } +} +function toggleKey(value) { + if (value === 0) { // on release + selectionDisplay.toggleSpaceMode(); + } +} +function focusKey(value) { + if (value === 0) { // on release + cameraManager.enable(); + if (selectionManager.hasSelection()) { + cameraManager.focus(selectionManager.worldPosition, selectionManager.worldDimensions, + Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + } + } +} +function gridKey(value) { + if (value === 0) { // on release + if (selectionManager.hasSelection()) { + grid.moveToSelection(); + } + } +} +function recursiveAdd(newParentID, parentData) { + if (parentData.children !== undefined) { + var children = parentData.children; + for (var i = 0; i < children.length; i++) { + var childProperties = children[i].properties; + childProperties.parentID = newParentID; + var newChildID = Entities.addEntity(childProperties); + recursiveAdd(newChildID, children[i]); + } + } +} + +var UndoHistory = function(onUpdate) { + this.history = []; + // The current position is the index of the last executed action in the history array. + // + // -1 0 1 2 3 <- position + // A B C D <- actions in history + // + // If our lastExecutedIndex is 1, the last executed action is B. + // If we undo, we undo B (index 1). If we redo, we redo C (index 2). + this.lastExecutedIndex = -1; + this.enabled = true; + this.onUpdate = onUpdate; +}; + +UndoHistory.prototype.pushCommand = function(undoFn, undoArgs, redoFn, redoArgs) { + if (!this.enabled) { + return; + } + // Delete any history following the last executed action. + this.history.splice(this.lastExecutedIndex + 1); + this.history.push({ + undoFn: undoFn, + undoArgs: undoArgs, + redoFn: redoFn, + redoArgs: redoArgs + }); + this.lastExecutedIndex++; + + if (this.onUpdate) { + this.onUpdate(); + } +}; +UndoHistory.prototype.setEnabled = function(enabled) { + this.enabled = enabled; + if (this.onUpdate) { + this.onUpdate(); + } +}; +UndoHistory.prototype.canUndo = function() { + return this.enabled && this.lastExecutedIndex >= 0; +}; +UndoHistory.prototype.canRedo = function() { + return this.enabled && this.lastExecutedIndex < this.history.length - 1; +}; +UndoHistory.prototype.undo = function() { + if (!this.canUndo()) { + console.warn("Cannot undo action"); + return; + } + + var command = this.history[this.lastExecutedIndex]; + command.undoFn(command.undoArgs); + this.lastExecutedIndex--; + + if (this.onUpdate) { + this.onUpdate(); + } +}; +UndoHistory.prototype.redo = function() { + if (!this.canRedo()) { + console.warn("Cannot redo action"); + return; + } + + var command = this.history[this.lastExecutedIndex + 1]; + command.redoFn(command.redoArgs); + this.lastExecutedIndex++; + + if (this.onUpdate) { + this.onUpdate(); + } +}; + +function updateUndoRedoMenuItems() { + Menu.setMenuEnabled("Edit > Undo", undoHistory.canUndo()); + Menu.setMenuEnabled("Edit > Redo", undoHistory.canRedo()); +} +var undoHistory = new UndoHistory(updateUndoRedoMenuItems); +updateUndoRedoMenuItems(); + +// When an entity has been deleted we need a way to "undo" this deletion. Because it's not currently +// possible to create an entity with a specific id, earlier undo commands to the deleted entity +// will fail if there isn't a way to find the new entity id. +var DELETED_ENTITY_MAP = {}; + +function applyEntityProperties(data) { + var editEntities = data.editEntities; + var createEntities = data.createEntities; + var deleteEntities = data.deleteEntities; + var selectedEntityIDs = []; + var selectEdits = createEntities.length === 0 || !data.selectCreated; + var i, entityID, entityProperties; + for (i = 0; i < createEntities.length; i++) { + entityID = createEntities[i].entityID; + entityProperties = createEntities[i].properties; + var newEntityID = Entities.addEntity(entityProperties); + recursiveAdd(newEntityID, createEntities[i]); + DELETED_ENTITY_MAP[entityID] = newEntityID; + if (data.selectCreated) { + selectedEntityIDs.push(newEntityID); + } + } + for (i = 0; i < deleteEntities.length; i++) { + entityID = deleteEntities[i].entityID; + if (DELETED_ENTITY_MAP[entityID] !== undefined) { + entityID = DELETED_ENTITY_MAP[entityID]; + } + Entities.deleteEntity(entityID); + var index = selectedEntityIDs.indexOf(entityID); + if (index >= 0) { + selectedEntityIDs.splice(index, 1); + } + } + for (i = 0; i < editEntities.length; i++) { + entityID = editEntities[i].entityID; + if (DELETED_ENTITY_MAP[entityID] !== undefined) { + entityID = DELETED_ENTITY_MAP[entityID]; + } + entityProperties = editEntities[i].properties; + if (entityProperties !== null) { + Entities.editEntity(entityID, entityProperties); + } + if (selectEdits) { + selectedEntityIDs.push(entityID); + } + } + + // We might be getting an undo while edit.js is disabled. If that is the case, don't set + // our selections, causing the edit widgets to display. + if (isActive) { + selectionManager.setSelections(selectedEntityIDs, this); + selectionManager.saveProperties(); + } +} + +// For currently selected entities, push a command to the UndoStack that uses the current entity properties for the +// redo command, and the saved properties for the undo command. Also, include create and delete entity data. +function pushCommandForSelections(createdEntityData, deletedEntityData, doNotSaveEditProperties) { + doNotSaveEditProperties = false; + var undoData = { + editEntities: [], + createEntities: deletedEntityData || [], + deleteEntities: createdEntityData || [], + selectCreated: true + }; + var redoData = { + editEntities: [], + createEntities: createdEntityData || [], + deleteEntities: deletedEntityData || [], + selectCreated: true + }; + for (var i = 0; i < SelectionManager.selections.length; i++) { + var entityID = SelectionManager.selections[i]; + var initialProperties = SelectionManager.savedProperties[entityID]; + var currentProperties = null; + if (!initialProperties) { + continue; + } + + if (doNotSaveEditProperties) { + initialProperties = null; + } else { + currentProperties = Entities.getEntityProperties(entityID); + } + + undoData.editEntities.push({ + entityID: entityID, + properties: initialProperties + }); + redoData.editEntities.push({ + entityID: entityID, + properties: currentProperties + }); + } + undoHistory.pushCommand(applyEntityProperties, undoData, applyEntityProperties, redoData); +} + +var ServerScriptStatusMonitor = function(entityID, statusCallback) { + var self = this; + + self.entityID = entityID; + self.active = true; + self.sendRequestTimerID = null; + + var onStatusReceived = function(success, isRunning, status, errorInfo) { + if (self.active) { + statusCallback({ + statusRetrieved: success, + isRunning: isRunning, + status: status, + errorInfo: errorInfo + }); + self.sendRequestTimerID = Script.setTimeout(function() { + if (self.active) { + Entities.getServerScriptStatus(entityID, onStatusReceived); + } + }, 1000); + } + }; + self.stop = function() { + self.active = false; + }; + + Entities.getServerScriptStatus(entityID, onStatusReceived); +}; + +var PropertiesTool = function (opts) { + var that = {}; + + var webView = null; + webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + webView.setVisible = function(value) {}; + + var visible = false; + + // This keeps track of the last entity ID that was selected. If multiple entities + // are selected or if no entity is selected this will be `null`. + var currentSelectedEntityID = null; + var statusMonitor = null; + var blockPropertyUpdates = false; + + that.setVisible = function (newVisible) { + visible = newVisible; + webView.setVisible(shouldUseEditTabletApp() && visible); + createToolsWindow.setVisible(!shouldUseEditTabletApp() && visible); + }; + + that.setVisible(false); + + function emitScriptEvent(data) { + var dataString = JSON.stringify(data); + webView.emitScriptEvent(dataString); + createToolsWindow.emitScriptEvent(dataString); + } + + function updateScriptStatus(info) { + info.type = "server_script_status"; + emitScriptEvent(info); + } + + function resetScriptStatus() { + updateScriptStatus({ + statusRetrieved: undefined, + isRunning: undefined, + status: "", + errorInfo: "" + }); + } + + that.setSpaceMode = function(spaceMode) { + emitScriptEvent({ + type: 'setSpaceMode', + spaceMode: spaceMode + }) + }; + + function updateSelections(selectionUpdated, caller) { + if (blockPropertyUpdates) { + return; + } + + var data = { + type: 'update', + spaceMode: selectionDisplay.getSpaceMode(), + isPropertiesToolUpdate: caller === this, + }; + + if (selectionUpdated) { + resetScriptStatus(); + + if (selectionManager.selections.length !== 1) { + if (statusMonitor !== null) { + statusMonitor.stop(); + statusMonitor = null; + } + currentSelectedEntityID = null; + } else if (currentSelectedEntityID !== selectionManager.selections[0]) { + if (statusMonitor !== null) { + statusMonitor.stop(); + } + var entityID = selectionManager.selections[0]; + currentSelectedEntityID = entityID; + statusMonitor = new ServerScriptStatusMonitor(entityID, updateScriptStatus); + } + } + + var selections = []; + for (var i = 0; i < selectionManager.selections.length; i++) { + var entity = {}; + entity.id = selectionManager.selections[i]; + entity.properties = Entities.getEntityProperties(selectionManager.selections[i]); + if (entity.properties.rotation !== undefined) { + entity.properties.rotation = Quat.safeEulerAngles(entity.properties.rotation); + } + if (entity.properties.localRotation !== undefined) { + entity.properties.localRotation = Quat.safeEulerAngles(entity.properties.localRotation); + } + if (entity.properties.emitOrientation !== undefined) { + entity.properties.emitOrientation = Quat.safeEulerAngles(entity.properties.emitOrientation); + } + if (entity.properties.keyLight !== undefined && entity.properties.keyLight.direction !== undefined) { + entity.properties.keyLight.direction = Vec3.toPolar(entity.properties.keyLight.direction); + entity.properties.keyLight.direction.z = 0.0; + } + selections.push(entity); + } + data.selections = selections; + + emitScriptEvent(data); + } + selectionManager.addEventListener(updateSelections, this); + + + var onWebEventReceived = function(data) { + try { + data = JSON.parse(data); + } catch(e) { + return; + } + var i, properties, dY, diff, newPosition; + if (data.type === "update") { + + if (data.properties || data.propertiesMap) { + var propertiesMap = data.propertiesMap; + if (propertiesMap === undefined) { + propertiesMap = [{ + entityIDs: data.ids, + properties: data.properties, + }]; + } + + var sendListUpdate = false; + propertiesMap.forEach(function(propertiesObject) { + var properties = propertiesObject.properties; + var updateEntityIDs = propertiesObject.entityIDs; + if (properties.dynamic === false) { + // this object is leaving dynamic, so we zero its velocities + properties.localVelocity = Vec3.ZERO; + properties.localAngularVelocity = Vec3.ZERO; + } + if (properties.rotation !== undefined) { + properties.rotation = Quat.fromVec3Degrees(properties.rotation); + } + if (properties.localRotation !== undefined) { + properties.localRotation = Quat.fromVec3Degrees(properties.localRotation); + } + if (properties.emitOrientation !== undefined) { + properties.emitOrientation = Quat.fromVec3Degrees(properties.emitOrientation); + } + if (properties.keyLight !== undefined && properties.keyLight.direction !== undefined) { + var currentKeyLightDirection = Vec3.toPolar(Entities.getEntityProperties(selectionManager.selections[0], ['keyLight.direction']).keyLight.direction); + if (properties.keyLight.direction.x === undefined) { + properties.keyLight.direction.x = currentKeyLightDirection.x; + } + if (properties.keyLight.direction.y === undefined) { + properties.keyLight.direction.y = currentKeyLightDirection.y; + } + properties.keyLight.direction = Vec3.fromPolar(properties.keyLight.direction.x, properties.keyLight.direction.y); + } + + updateEntityIDs.forEach(function (entityID) { + Entities.editEntity(entityID, properties); + }); + + if (properties.name !== undefined || properties.modelURL !== undefined || properties.materialURL !== undefined || + properties.visible !== undefined || properties.locked !== undefined) { + + sendListUpdate = true; + } + + }); + if (sendListUpdate) { + entityListTool.sendUpdate(); + } + } + + + if (data.onlyUpdateEntities) { + blockPropertyUpdates = true; + } else { + pushCommandForSelections(); + SelectionManager.saveProperties(); + } + selectionManager._update(false, this); + blockPropertyUpdates = false; + } else if (data.type === 'saveUserData' || data.type === 'saveMaterialData') { + data.ids.forEach(function(entityID) { + Entities.editEntity(entityID, data.properties); + }); + } else if (data.type === "showMarketplace") { + showMarketplace(); + } else if (data.type === "action") { + if (data.action === "moveSelectionToGrid") { + if (selectionManager.hasSelection()) { + selectionManager.saveProperties(); + dY = grid.getOrigin().y - (selectionManager.worldPosition.y - selectionManager.worldDimensions.y / 2); + diff = { + x: 0, + y: dY, + z: 0 + }; + for (i = 0; i < selectionManager.selections.length; i++) { + properties = selectionManager.savedProperties[selectionManager.selections[i]]; + newPosition = Vec3.sum(properties.position, diff); + Entities.editEntity(selectionManager.selections[i], { + position: newPosition + }); + } + pushCommandForSelections(); + selectionManager._update(false, this); + } + } else if (data.action === "moveAllToGrid") { + if (selectionManager.hasSelection()) { + selectionManager.saveProperties(); + for (i = 0; i < selectionManager.selections.length; i++) { + properties = selectionManager.savedProperties[selectionManager.selections[i]]; + var bottomY = properties.boundingBox.center.y - properties.boundingBox.dimensions.y / 2; + dY = grid.getOrigin().y - bottomY; + diff = { + x: 0, + y: dY, + z: 0 + }; + newPosition = Vec3.sum(properties.position, diff); + Entities.editEntity(selectionManager.selections[i], { + position: newPosition + }); + } + pushCommandForSelections(); + selectionManager._update(false, this); + } + } else if (data.action === "resetToNaturalDimensions") { + if (selectionManager.hasSelection()) { + selectionManager.saveProperties(); + for (i = 0; i < selectionManager.selections.length; i++) { + properties = selectionManager.savedProperties[selectionManager.selections[i]]; + var naturalDimensions = properties.naturalDimensions; + + // If any of the natural dimensions are not 0, resize + if (properties.type === "Model" && naturalDimensions.x === 0 && naturalDimensions.y === 0 && + naturalDimensions.z === 0) { + Window.notifyEditError("Cannot reset entity to its natural dimensions: Model URL" + + " is invalid or the model has not yet been loaded."); + } else { + Entities.editEntity(selectionManager.selections[i], { + dimensions: properties.naturalDimensions + }); + } + } + pushCommandForSelections(); + selectionManager._update(false, this); + } + } else if (data.action === "previewCamera") { + if (selectionManager.hasSelection()) { + Camera.mode = "entity"; + Camera.cameraEntity = selectionManager.selections[0]; + } + } else if (data.action === "rescaleDimensions") { + var multiplier = data.percentage / 100.0; + if (selectionManager.hasSelection()) { + selectionManager.saveProperties(); + for (i = 0; i < selectionManager.selections.length; i++) { + properties = selectionManager.savedProperties[selectionManager.selections[i]]; + Entities.editEntity(selectionManager.selections[i], { + dimensions: Vec3.multiply(multiplier, properties.dimensions) + }); + } + pushCommandForSelections(); + selectionManager._update(false, this); + } + } else if (data.action === "reloadClientScripts") { + if (selectionManager.hasSelection()) { + var timestamp = Date.now(); + for (i = 0; i < selectionManager.selections.length; i++) { + Entities.editEntity(selectionManager.selections[i], { + scriptTimestamp: timestamp + }); + } + } + } else if (data.action === "reloadServerScripts") { + if (selectionManager.hasSelection()) { + for (i = 0; i < selectionManager.selections.length; i++) { + Entities.reloadServerScripts(selectionManager.selections[i]); + } + } + } + } else if (data.type === "propertiesPageReady") { + updateSelections(true); + } else if (data.type === "tooltipsRequest") { + emitScriptEvent({ + type: 'tooltipsReply', + tooltips: Script.require('./assets/data/createAppTooltips.json'), + hmdActive: HMD.active, + }); + } else if (data.type === "propertyRangeRequest") { + var propertyRanges = {}; + data.properties.forEach(function (property) { + propertyRanges[property] = Entities.getPropertyInfo(property); + }); + emitScriptEvent({ + type: 'propertyRangeReply', + propertyRanges: propertyRanges, + }); + } else if (data.type === "materialTargetRequest") { + var parentModelData; + var properties = Entities.getEntityProperties(data.entityID, ["type", "parentID"]); + if (properties.type === "Material" && properties.parentID !== Uuid.NULL) { + var parentType = Entities.getEntityProperties(properties.parentID, ["type"]).type; + if (parentType === "Model" || Entities.getNestableType(properties.parentID) === "avatar") { + parentModelData = Graphics.getModel(properties.parentID); + } else if (parentType === "Shape" || parentType === "Box" || parentType === "Sphere") { + parentModelData = {}; + parentModelData.numMeshes = 1; + parentModelData.materialNames = []; + } + } + emitScriptEvent({ + type: 'materialTargetReply', + entityID: data.entityID, + materialTargetData: parentModelData, + }); + } + }; + + HMD.displayModeChanged.connect(function() { + emitScriptEvent({ + type: 'hmdActiveChanged', + hmdActive: HMD.active, + }); + }); + + createToolsWindow.webEventReceived.addListener(this, onWebEventReceived); + + webView.webEventReceived.connect(this, onWebEventReceived); + + return that; +}; + + +var PopupMenu = function () { + var self = this; + + var MENU_ITEM_HEIGHT = 21; + var MENU_ITEM_SPACING = 1; + var TEXT_MARGIN = 7; + + var overlays = []; + var overlayInfo = {}; + + var visible = false; + + var upColor = { + red: 0, + green: 0, + blue: 0 + }; + var downColor = { + red: 192, + green: 192, + blue: 192 + }; + var overColor = { + red: 128, + green: 128, + blue: 128 + }; + + self.onSelectMenuItem = function () {}; + + self.addMenuItem = function (name) { + var id = Overlays.addOverlay("text", { + text: name, + backgroundAlpha: 1.0, + backgroundColor: upColor, + topMargin: TEXT_MARGIN, + leftMargin: TEXT_MARGIN, + width: 210, + height: MENU_ITEM_HEIGHT, + font: { + size: 12 + }, + visible: false + }); + overlays.push(id); + overlayInfo[id] = { + name: name + }; + return id; + }; + + self.updateMenuItemText = function (id, newText) { + Overlays.editOverlay(id, { + text: newText + }); + }; + + self.setPosition = function (x, y) { + for (var key in overlayInfo) { + Overlays.editOverlay(key, { + x: x, + y: y + }); + y += MENU_ITEM_HEIGHT + MENU_ITEM_SPACING; + } + }; + + self.onSelected = function () {}; + + var pressingOverlay = null; + var hoveringOverlay = null; + + self.mousePressEvent = function (event) { + if (event.isLeftButton) { + var overlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + if (overlay in overlayInfo) { + pressingOverlay = overlay; + Overlays.editOverlay(pressingOverlay, { + backgroundColor: downColor + }); + } else { + self.hide(); + } + return false; + } + }; + self.mouseMoveEvent = function (event) { + if (visible) { + var overlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + if (!pressingOverlay) { + if (hoveringOverlay !== null && overlay !== hoveringOverlay) { + Overlays.editOverlay(hoveringOverlay, { + backgroundColor: upColor + }); + hoveringOverlay = null; + } + if (overlay !== hoveringOverlay && overlay in overlayInfo) { + Overlays.editOverlay(overlay, { + backgroundColor: overColor + }); + hoveringOverlay = overlay; + } + } + } + return false; + }; + self.mouseReleaseEvent = function (event) { + var overlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + if (pressingOverlay !== null && pressingOverlay !== undefined) { + if (overlay === pressingOverlay) { + self.onSelectMenuItem(overlayInfo[overlay].name); + } + Overlays.editOverlay(pressingOverlay, { + backgroundColor: upColor + }); + pressingOverlay = null; + self.hide(); + } + }; + + self.setVisible = function (newVisible) { + if (newVisible !== visible) { + visible = newVisible; + for (var key in overlayInfo) { + Overlays.editOverlay(key, { + visible: newVisible + }); + } + } + }; + self.show = function () { + self.setVisible(true); + }; + self.hide = function () { + self.setVisible(false); + }; + + function cleanup() { + ContextOverlay.enabled = true; + for (var i = 0; i < overlays.length; i++) { + Overlays.deleteOverlay(overlays[i]); + } + Controller.mousePressEvent.disconnect(self.mousePressEvent); + Controller.mouseMoveEvent.disconnect(self.mouseMoveEvent); + Controller.mouseReleaseEvent.disconnect(self.mouseReleaseEvent); + + Entities.canRezChanged.disconnect(checkEditPermissionsAndUpdate); + Entities.canRezTmpChanged.disconnect(checkEditPermissionsAndUpdate); + Entities.canRezCertifiedChanged.disconnect(checkEditPermissionsAndUpdate); + Entities.canRezTmpCertifiedChanged.disconnect(checkEditPermissionsAndUpdate); + } + + Controller.mousePressEvent.connect(self.mousePressEvent); + Controller.mouseMoveEvent.connect(self.mouseMoveEvent); + Controller.mouseReleaseEvent.connect(self.mouseReleaseEvent); + Script.scriptEnding.connect(cleanup); + + return this; +}; + +function whenPressed(fn) { + return function(value) { + if (value > 0) { + fn(); + } + }; +} + +function whenReleased(fn) { + return function(value) { + if (value === 0) { + fn(); + } + }; +} + +var isOnMacPlatform = Controller.getValue(Controller.Hardware.Application.PlatformMac); + +var mapping = Controller.newMapping(CONTROLLER_MAPPING_NAME); +if (isOnMacPlatform) { + mapping.from([Controller.Hardware.Keyboard.Backspace]).to(deleteKey); +} else { + mapping.from([Controller.Hardware.Keyboard.Delete]).to(deleteKey); +} +mapping.from([Controller.Hardware.Keyboard.T]).to(toggleKey); +mapping.from([Controller.Hardware.Keyboard.F]).to(focusKey); +mapping.from([Controller.Hardware.Keyboard.G]).to(gridKey); +mapping.from([Controller.Hardware.Keyboard.X]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.cutSelectedEntities() })); +mapping.from([Controller.Hardware.Keyboard.C]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.copySelectedEntities() })); +mapping.from([Controller.Hardware.Keyboard.V]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.pasteEntities() })); +mapping.from([Controller.Hardware.Keyboard.D]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.duplicateSelection() })); + +// Bind undo to ctrl-shift-z to maintain backwards-compatibility +mapping.from([Controller.Hardware.Keyboard.Z]) + .when([Controller.Hardware.Keyboard.Control, Controller.Hardware.Keyboard.Shift]) + .to(whenPressed(function() { undoHistory.redo() })); + + +mapping.from([Controller.Hardware.Keyboard.P]) + .when([Controller.Hardware.Keyboard.Control, Controller.Hardware.Keyboard.Shift]) + .to(whenReleased(function() { unparentSelectedEntities(); })); + +mapping.from([Controller.Hardware.Keyboard.P]) + .when([Controller.Hardware.Keyboard.Control, !Controller.Hardware.Keyboard.Shift]) + .to(whenReleased(function() { parentSelectedEntities(); })); + +keyUpEventFromUIWindow = function(keyUpEvent) { + var WANT_DEBUG_MISSING_SHORTCUTS = false; + + var pressedValue = 0.0; + + if ((!isOnMacPlatform && keyUpEvent.keyCodeString === "Delete") + || (isOnMacPlatform && keyUpEvent.keyCodeString === "Backspace")) { + + deleteKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "T") { + toggleKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "F") { + focusKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "G") { + gridKey(pressedValue); + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "X") { + selectionManager.cutSelectedEntities(); + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "C") { + selectionManager.copySelectedEntities(); + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "V") { + selectionManager.pasteEntities(); + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "D") { + selectionManager.duplicateSelection(); + } else if (!isOnMacPlatform && keyUpEvent.controlKey && !keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "Z") { + undoHistory.undo(); // undo is only handled via handleMenuItem on Mac + } else if (keyUpEvent.controlKey && !keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "P") { + parentSelectedEntities(); + } else if (keyUpEvent.controlKey && keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "P") { + unparentSelectedEntities(); + } else if (!isOnMacPlatform && + ((keyUpEvent.controlKey && keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "Z") || + (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "Y"))) { + undoHistory.redo(); // redo is only handled via handleMenuItem on Mac + } else if (WANT_DEBUG_MISSING_SHORTCUTS) { + console.warn("unhandled key event: " + JSON.stringify(keyUpEvent)) + } +}; + +var propertyMenu = new PopupMenu(); + +propertyMenu.onSelectMenuItem = function (name) { + + if (propertyMenu.marketplaceID) { + showMarketplace(propertyMenu.marketplaceID); + } +}; + +var showMenuItem = propertyMenu.addMenuItem("Show in Marketplace"); + +var propertiesTool = new PropertiesTool(); + +selectionDisplay.onSpaceModeChange = function(spaceMode) { + entityListTool.setSpaceMode(spaceMode); + propertiesTool.setSpaceMode(spaceMode); +}; + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/emote.js b/scripts/simplifiedUI/system/emote.js new file mode 100644 index 0000000000..6dfd1ae1ef --- /dev/null +++ b/scripts/simplifiedUI/system/emote.js @@ -0,0 +1,179 @@ +"use strict"; + +// +// emote.js +// scripts/system/ +// +// Created by Brad Hefta-Gaub on 7 Jan 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 +// +/* globals Script, Tablet */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ + +(function() { // BEGIN LOCAL_SCOPE + + +var EMOTE_ANIMATIONS = + ['Crying', 'Surprised', 'Dancing', 'Cheering', 'Waving', 'Fall', 'Pointing', 'Clapping', 'Sit1', 'Sit2', 'Sit3', 'Love']; +var ANIMATIONS = Array(); + +var eventMappingName = "io.highfidelity.away"; // restoreAnimation on hand controller button events, too +var eventMapping = Controller.newMapping(eventMappingName); + +EMOTE_ANIMATIONS.forEach(function (name) { + var animationURL = Script.resolvePath("assets/animations/" + name + ".fbx"); + var resource = AnimationCache.prefetch(animationURL); + var animation = AnimationCache.getAnimation(animationURL); + ANIMATIONS[name] = { url: animationURL, animation: animation, resource: resource}; +}); + + +var EMOTE_APP_BASE = "html/EmoteApp.html"; +var EMOTE_APP_URL = Script.resolvePath(EMOTE_APP_BASE); +var EMOTE_LABEL = "EMOTE"; +var EMOTE_APP_SORT_ORDER = 12; +var FPS = 60; +var MSEC_PER_SEC = 1000; +var FINISHED = 3; // see ScriptableResource::State + +var onEmoteScreen = false; +var button; +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); +var activeTimer = false; // Used to cancel active timer if a user plays an animation while another animation is playing +var activeEmote = false; // To keep track of the currently playing emote + +button = tablet.addButton({ + icon: "icons/tablet-icons/emote-i.svg", + activeIcon: "icons/tablet-icons/emote-a.svg", + text: EMOTE_LABEL, + sortOrder: EMOTE_APP_SORT_ORDER +}); + +function onClicked() { + if (onEmoteScreen) { + tablet.gotoHomeScreen(); + } else { + onEmoteScreen = true; + tablet.gotoWebScreen(EMOTE_APP_URL); + } +} + +function onScreenChanged(type, url) { + onEmoteScreen = type === "Web" && (url.indexOf(EMOTE_APP_BASE) === url.length - EMOTE_APP_BASE.length); + button.editProperties({ isActive: onEmoteScreen }); +} + +// Handle the events we're receiving from the web UI +function onWebEventReceived(event) { + + // Converts the event to a JavasScript Object + if (typeof event === "string") { + event = JSON.parse(event); + } + + if (event.type === "click") { + + // Allow for a random sitting animation when a user selects sit + var randSit = Math.floor(Math.random() * 3) + 1; + + var emoteName = event.data; + + if (emoteName === "Sit"){ + emoteName = event.data + randSit; // Sit1, Sit2, Sit3 + } + + if (ANIMATIONS[emoteName].resource.state === FINISHED) { + + if (activeTimer !== false) { + Script.clearTimeout(activeTimer); + } + + // If the activeEmote is different from the chosen emote, then play the new emote + // This is a second click on the same emote as the activeEmote, and we will just stop it + if (activeEmote !== emoteName) { + activeEmote = emoteName; + + + // Sit is the only animation currently that plays and then ends at the last frame + if (emoteName.match(/^Sit.*$/)) { + + // If user provides input during a sit, the avatar animation state should be restored + Controller.keyPressEvent.connect(restoreAnimation); + Controller.enableMapping(eventMappingName); + MyAvatar.overrideAnimation(ANIMATIONS[emoteName].url, FPS, false, 0, frameCount); + + } else { + + activeEmote = emoteName; + var frameCount = ANIMATIONS[emoteName].animation.frames.length; + MyAvatar.overrideAnimation(ANIMATIONS[emoteName].url, FPS, false, 0, frameCount); + + var timeOut = MSEC_PER_SEC * frameCount / FPS; + activeTimer = Script.setTimeout(function () { + MyAvatar.restoreAnimation(); + activeTimer = false; + activeEmote = false; + }, timeOut); + + } + + } else { + activeEmote = false; + MyAvatar.restoreAnimation(); + } + } + } +} + +// Restore the navigation animation states (idle, walk, run) +function restoreAnimation() { + MyAvatar.restoreAnimation(); + + // Make sure the input is disconnected after animations are restored so it doesn't affect any emotes other than sit + Controller.keyPressEvent.disconnect(restoreAnimation); + Controller.disableMapping(eventMappingName); +} + +// Note peek() so as to not interfere with other mappings. +eventMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LB).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LS).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RY).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RX).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LY).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LX).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LeftGrip).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RB).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RS).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RightGrip).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.Back).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.Start).peek().to(restoreAnimation); + + +button.clicked.connect(onClicked); +tablet.screenChanged.connect(onScreenChanged); +tablet.webEventReceived.connect(onWebEventReceived); + +Script.scriptEnding.connect(function () { + if (onEmoteScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onClicked); + tablet.screenChanged.disconnect(onScreenChanged); + if (tablet) { + tablet.removeButton(button); + } + if (activeTimer !== false) { + Script.clearTimeout(activeTimer); + MyAvatar.restoreAnimation(); + } +}); + + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/fingerPaint.js b/scripts/simplifiedUI/system/fingerPaint.js new file mode 100644 index 0000000000..88245503e8 --- /dev/null +++ b/scripts/simplifiedUI/system/fingerPaint.js @@ -0,0 +1,465 @@ +// +// fingerPaint.js +// +// Created by David Rowe on 15 Feb 2017 +// Copyright 2017 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 +// + +(function () { + var tablet, + button, + BUTTON_NAME = "PAINT", + isFingerPainting = false, + shouldPointFingers = false, + leftHand = null, + rightHand = null, + leftBrush = null, + rightBrush = null, + CONTROLLER_MAPPING_NAME = "com.highfidelity.fingerPaint", + isTabletDisplayed = false, + HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index", + HIFI_GRAB_DISABLE_MESSAGE_CHANNEL = "Hifi-Grab-Disable", + HIFI_POINTER_DISABLE_MESSAGE_CHANNEL = "Hifi-Pointer-Disable"; + HOW_TO_EXIT_MESSAGE = "Press B on your controller to exit FingerPainting mode"; + + function paintBrush(name) { + // Paints in 3D. + var brushName = name, + STROKE_COLOR = { red: 250, green: 0, blue: 0 }, + ERASE_SEARCH_RADIUS = 0.1, // m + isDrawingLine = false, + entityID, + basePosition, + strokePoints, + strokeNormals, + strokeWidths, + timeOfLastPoint, + MIN_STROKE_LENGTH = 0.005, // m + MIN_STROKE_INTERVAL = 66, // ms + MAX_POINTS_PER_LINE = 70; // Hard-coded limit in PolyLineEntityItem.h. + + function strokeNormal() { + return Vec3.multiplyQbyV(Camera.getOrientation(), Vec3.UNIT_NEG_Z); + } + + function startLine(position, width) { + // Start drawing a polyline. + + if (isDrawingLine) { + print("ERROR: startLine() called when already drawing line"); + // Nevertheless, continue on and start a new line. + } + + basePosition = position; + + strokePoints = [Vec3.ZERO]; + strokeNormals = [strokeNormal()]; + strokeWidths = [width]; + timeOfLastPoint = Date.now(); + + entityID = Entities.addEntity({ + type: "PolyLine", + name: "fingerPainting", + color: STROKE_COLOR, + position: position, + linePoints: strokePoints, + normals: strokeNormals, + strokeWidths: strokeWidths, + dimensions: { x: 10, y: 10, z: 10 } + }); + + isDrawingLine = true; + } + + function drawLine(position, width) { + // Add a stroke to the polyline if stroke is a sufficient length. + var localPosition, + distanceToPrevious, + MAX_DISTANCE_TO_PREVIOUS = 1.0; + + if (!isDrawingLine) { + print("ERROR: drawLine() called when not drawing line"); + return; + } + + localPosition = Vec3.subtract(position, basePosition); + distanceToPrevious = Vec3.distance(localPosition, strokePoints[strokePoints.length - 1]); + + if (distanceToPrevious > MAX_DISTANCE_TO_PREVIOUS) { + // Ignore occasional spurious finger tip positions. + return; + } + + if (distanceToPrevious >= MIN_STROKE_LENGTH + && (Date.now() - timeOfLastPoint) >= MIN_STROKE_INTERVAL + && strokePoints.length < MAX_POINTS_PER_LINE) { + strokePoints.push(localPosition); + strokeNormals.push(strokeNormal()); + strokeWidths.push(width); + timeOfLastPoint = Date.now(); + + Entities.editEntity(entityID, { + linePoints: strokePoints, + normals: strokeNormals, + strokeWidths: strokeWidths + }); + } + } + + function finishLine(position, width) { + // Finish drawing polyline; delete if it has only 1 point. + + if (!isDrawingLine) { + print("ERROR: finishLine() called when not drawing line"); + return; + } + + if (strokePoints.length === 1) { + // Delete "empty" line. + Entities.deleteEntity(entityID); + } + + isDrawingLine = false; + } + + function cancelLine() { + // Cancel any line being drawn. + if (isDrawingLine) { + Entities.deleteEntity(entityID); + isDrawingLine = false; + } + } + + function eraseClosestLine(position) { + // Erase closest line that is within search radius of finger tip. + var entities, + entitiesLength, + properties, + i, + pointsLength, + j, + distance, + found = false, + foundID, + foundDistance = ERASE_SEARCH_RADIUS; + + // Find entities with bounding box within search radius. + entities = Entities.findEntities(position, ERASE_SEARCH_RADIUS); + + // Fine polyline entity with closest point within search radius. + for (i = 0, entitiesLength = entities.length; i < entitiesLength; i += 1) { + properties = Entities.getEntityProperties(entities[i], ["type", "position", "linePoints"]); + if (properties.type === "PolyLine") { + basePosition = properties.position; + for (j = 0, pointsLength = properties.linePoints.length; j < pointsLength; j += 1) { + distance = Vec3.distance(position, Vec3.sum(basePosition, properties.linePoints[j])); + if (distance <= foundDistance) { + found = true; + foundID = entities[i]; + foundDistance = distance; + } + } + } + } + + // Delete found entity. + if (found) { + Entities.deleteEntity(foundID); + } + } + + function tearDown() { + cancelLine(); + } + + return { + startLine: startLine, + drawLine: drawLine, + finishLine: finishLine, + cancelLine: cancelLine, + eraseClosestLine: eraseClosestLine, + tearDown: tearDown + }; + } + + function handController(name) { + // Translates controller data into application events. + var handName = name, + + triggerPressedCallback, + triggerPressingCallback, + triggerReleasedCallback, + gripPressedCallback, + + rawTriggerValue = 0.0, + triggerValue = 0.0, + isTriggerPressed = false, + TRIGGER_SMOOTH_RATIO = 0.1, + TRIGGER_OFF = 0.05, + TRIGGER_ON = 0.1, + TRIGGER_START_WIDTH_RAMP = 0.15, + TRIGGER_FINISH_WIDTH_RAMP = 1.0, + TRIGGER_RAMP_WIDTH = TRIGGER_FINISH_WIDTH_RAMP - TRIGGER_START_WIDTH_RAMP, + MIN_LINE_WIDTH = 0.005, + MAX_LINE_WIDTH = 0.03, + RAMP_LINE_WIDTH = MAX_LINE_WIDTH - MIN_LINE_WIDTH, + + rawGripValue = 0.0, + gripValue = 0.0, + isGripPressed = false, + GRIP_SMOOTH_RATIO = 0.1, + GRIP_OFF = 0.05, + GRIP_ON = 0.1; + + function onTriggerPress(value) { + // Controller values are only updated when they change so store latest for use in update. + rawTriggerValue = value; + } + + function updateTriggerPress(value) { + var wasTriggerPressed, + fingerTipPosition, + lineWidth; + + triggerValue = triggerValue * TRIGGER_SMOOTH_RATIO + rawTriggerValue * (1.0 - TRIGGER_SMOOTH_RATIO); + + wasTriggerPressed = isTriggerPressed; + if (isTriggerPressed) { + isTriggerPressed = triggerValue > TRIGGER_OFF; + } else { + isTriggerPressed = triggerValue > TRIGGER_ON; + } + + if (wasTriggerPressed || isTriggerPressed) { + fingerTipPosition = MyAvatar.getJointPosition(handName === "left" ? "LeftHandIndex4" : "RightHandIndex4"); + if (triggerValue < TRIGGER_START_WIDTH_RAMP) { + lineWidth = MIN_LINE_WIDTH; + } else { + lineWidth = MIN_LINE_WIDTH + + (triggerValue - TRIGGER_START_WIDTH_RAMP) / TRIGGER_RAMP_WIDTH * RAMP_LINE_WIDTH; + } + + if (!wasTriggerPressed && isTriggerPressed) { + triggerPressedCallback(fingerTipPosition, lineWidth); + } else if (wasTriggerPressed && isTriggerPressed) { + triggerPressingCallback(fingerTipPosition, lineWidth); + } else { + triggerReleasedCallback(fingerTipPosition, lineWidth); + } + } + } + + function onGripPress(value) { + // Controller values are only updated when they change so store latest for use in update. + rawGripValue = value; + } + + function updateGripPress() { + var fingerTipPosition; + + gripValue = gripValue * GRIP_SMOOTH_RATIO + rawGripValue * (1.0 - GRIP_SMOOTH_RATIO); + + if (isGripPressed) { + isGripPressed = gripValue > GRIP_OFF; + } else { + isGripPressed = gripValue > GRIP_ON; + if (isGripPressed) { + fingerTipPosition = MyAvatar.getJointPosition(handName === "left" ? "LeftHandIndex4" : "RightHandIndex4"); + gripPressedCallback(fingerTipPosition); + } + } + } + + function onUpdate() { + updateTriggerPress(); + updateGripPress(); + } + + function setUp(onTriggerPressed, onTriggerPressing, onTriggerReleased, onGripPressed) { + triggerPressedCallback = onTriggerPressed; + triggerPressingCallback = onTriggerPressing; + triggerReleasedCallback = onTriggerReleased; + gripPressedCallback = onGripPressed; + } + + function tearDown() { + // Nothing to do. + } + + return { + onTriggerPress: onTriggerPress, + onGripPress: onGripPress, + onUpdate: onUpdate, + setUp: setUp, + tearDown: tearDown + }; + } + + function updateHandFunctions() { + // Update other scripts' hand functions. + var enabled = !isFingerPainting || isTabletDisplayed; + + Messages.sendMessage(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL, JSON.stringify({ + holdEnabled: enabled, + nearGrabEnabled: enabled, + farGrabEnabled: enabled + }), true); + Messages.sendMessage(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL, JSON.stringify({ + pointerEnabled: enabled + }), true); + + var newShouldPointFingers = !enabled; + if (newShouldPointFingers !== shouldPointFingers) { + Messages.sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, JSON.stringify({ + pointIndex: newShouldPointFingers + }), true); + shouldPointFingers = newShouldPointFingers; + } + } + + function howToExitTutorial() { + HMD.requestShowHandControllers(); + setControllerPartLayer('button_b', 'highlight'); + messageWindow = Window.alert(HOW_TO_EXIT_MESSAGE); + setControllerPartLayer('button_b', 'blank'); + HMD.requestHideHandControllers(); + Settings.setValue("FingerPaintTutorialComplete", true); + } + + function enableProcessing() { + // Connect controller API to handController objects. + leftHand = handController("left"); + rightHand = handController("right"); + var controllerMapping = Controller.newMapping(CONTROLLER_MAPPING_NAME); + controllerMapping.from(Controller.Standard.LT).to(leftHand.onTriggerPress); + controllerMapping.from(Controller.Standard.LeftGrip).to(leftHand.onGripPress); + controllerMapping.from(Controller.Standard.RT).to(rightHand.onTriggerPress); + controllerMapping.from(Controller.Standard.RightGrip).to(rightHand.onGripPress); + controllerMapping.from(Controller.Standard.B).to(onButtonClicked); + Controller.enableMapping(CONTROLLER_MAPPING_NAME); + + if (!Settings.getValue("FingerPaintTutorialComplete")) { + howToExitTutorial(); + } + + // Connect handController outputs to paintBrush objects. + leftBrush = paintBrush("left"); + leftHand.setUp(leftBrush.startLine, leftBrush.drawLine, leftBrush.finishLine, leftBrush.eraseClosestLine); + rightBrush = paintBrush("right"); + rightHand.setUp(rightBrush.startLine, rightBrush.drawLine, rightBrush.finishLine, rightBrush.eraseClosestLine); + + // Messages channels for enabling/disabling other scripts' functions. + Messages.subscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); + Messages.subscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL); + Messages.subscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL); + + // Update hand controls. + Script.update.connect(leftHand.onUpdate); + Script.update.connect(rightHand.onUpdate); + } + + function disableProcessing() { + Script.update.disconnect(leftHand.onUpdate); + Script.update.disconnect(rightHand.onUpdate); + + Controller.disableMapping(CONTROLLER_MAPPING_NAME); + + leftBrush.tearDown(); + leftBrush = null; + leftHand.tearDown(); + leftHand = null; + + rightBrush.tearDown(); + rightBrush = null; + rightHand.tearDown(); + rightHand = null; + + Messages.unsubscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); + Messages.unsubscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL); + Messages.unsubscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL); + } + + function onButtonClicked() { + var wasFingerPainting = isFingerPainting; + + isFingerPainting = !isFingerPainting; + button.editProperties({ isActive: isFingerPainting }); + + print("Finger painting: " + isFingerPainting ? "on" : "off"); + + if (wasFingerPainting) { + leftBrush.cancelLine(); + rightBrush.cancelLine(); + } + + if (isFingerPainting) { + enableProcessing(); + } + + updateHandFunctions(); + + if (!isFingerPainting) { + disableProcessing(); + } + } + + function onTabletScreenChanged(type, url) { + var TABLET_SCREEN_CLOSED = "Closed"; + + isTabletDisplayed = type !== TABLET_SCREEN_CLOSED; + updateHandFunctions(); + } + + function setUp() { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + if (!tablet) { + return; + } + + // Tablet button. + button = tablet.addButton({ + icon: "icons/tablet-icons/finger-paint-i.svg", + activeIcon: "icons/tablet-icons/finger-paint-a.svg", + text: BUTTON_NAME, + isActive: isFingerPainting + }); + button.clicked.connect(onButtonClicked); + + // Track whether tablet is displayed or not. + tablet.screenChanged.connect(onTabletScreenChanged); + } + + function tearDown() { + if (!tablet) { + return; + } + + if (isFingerPainting) { + isFingerPainting = false; + updateHandFunctions(); + disableProcessing(); + } + + tablet.screenChanged.disconnect(onTabletScreenChanged); + + button.clicked.disconnect(onButtonClicked); + tablet.removeButton(button); + } + + /** + * A controller is made up of parts, and each part can have multiple "layers," + * which are really just different texures. For example, the "trigger" part + * has "normal" and "highlight" layers. + */ + function setControllerPartLayer(part, layer) { + data = {}; + data[part] = layer; + Messages.sendLocalMessage('Controller-Set-Part-Layer', JSON.stringify(data)); + } + + setUp(); + Script.scriptEnding.connect(tearDown); +}()); diff --git a/scripts/simplifiedUI/system/firstPersonHMD.js b/scripts/simplifiedUI/system/firstPersonHMD.js new file mode 100644 index 0000000000..5fdee1b7b5 --- /dev/null +++ b/scripts/simplifiedUI/system/firstPersonHMD.js @@ -0,0 +1,23 @@ +"use strict"; + +// +// firstPersonHMD.js +// system +// +// Created by Zander Otavka on 6/24/16 +// Copyright 2016 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 +// + +(function() { // BEGIN LOCAL_SCOPE + +// Automatically enter first person mode when entering HMD mode +HMD.displayModeChanged.connect(function(isHMDMode) { + if (isHMDMode) { + Camera.setModeString("first person"); + } +}); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/generalSettings.js b/scripts/simplifiedUI/system/generalSettings.js new file mode 100644 index 0000000000..d3848da7d0 --- /dev/null +++ b/scripts/simplifiedUI/system/generalSettings.js @@ -0,0 +1,56 @@ +"use strict"; + +// +// generalSettings.js +// scripts/system/ +// +// Created by Dante Ruiz on 9 Feb 2017 +// Copyright 2016 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 +// +/* globals Tablet, Toolbars, Script, HMD, DialogsManager */ + +(function() { // BEGIN LOCAL_SCOPE + + var button; + var buttonName = "Settings"; + var toolBar = null; + var tablet = null; + var settings = "hifi/tablet/TabletGeneralPreferences.qml" + function onClicked(){ + if (tablet) { + tablet.loadQMLSource(settings); + } + } + + if (Settings.getValue("HUDUIEnabled")) { + toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); + button = toolBar.addButton({ + objectName: buttonName, + imageURL: Script.resolvePath("assets/images/tools/directory.svg"), + visible: true, + alpha: 0.9 + }); + } else { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton({ + icon: "icons/tablet-icons/goto-i.svg", + text: buttonName + }); + } + + button.clicked.connect(onClicked); + + Script.scriptEnding.connect(function () { + button.clicked.disconnect(onClicked); + if (tablet) { + tablet.removeButton(button); + } + if (toolBar) { + toolBar.removeButton(buttonName); + } + }); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/goto.js b/scripts/simplifiedUI/system/goto.js new file mode 100644 index 0000000000..5cc5bad844 --- /dev/null +++ b/scripts/simplifiedUI/system/goto.js @@ -0,0 +1,65 @@ +"use strict"; + +// +// goto.js +// scripts/system/ +// +// Created by Howard Stearns on 2 Jun 2016 +// Copyright 2016 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 +// +/* globals Tablet, Toolbars, Script, HMD, DialogsManager */ + +(function() { // BEGIN LOCAL_SCOPE + +var button; +var buttonName = "GOTO"; +var toolBar = null; +var tablet = null; +var onGotoScreen = false; +function onAddressBarShown(visible) { + button.editProperties({isActive: visible}); +} + +function onClicked(){ + DialogsManager.toggleAddressBar(); + onGotoScreen = !onGotoScreen; +} + +if (Settings.getValue("HUDUIEnabled")) { + toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); + button = toolBar.addButton({ + objectName: buttonName, + imageURL: Script.resolvePath("assets/images/tools/directory.svg"), + visible: true, + alpha: 0.9 + }); +} else { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton({ + icon: "icons/tablet-icons/goto-i.svg", + activeIcon: "icons/tablet-icons/goto-a.svg", + text: buttonName + }); +} + +button.clicked.connect(onClicked); +DialogsManager.addressBarShown.connect(onAddressBarShown); + +Script.scriptEnding.connect(function () { + if (onGotoScreen) { + DialogsManager.toggleAddressBar(); + } + button.clicked.disconnect(onClicked); + if (tablet) { + tablet.removeButton(button); + } + if (toolBar) { + toolBar.removeButton(buttonName); + } + DialogsManager.addressBarShown.disconnect(onAddressBarShown); +}); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/help.js b/scripts/simplifiedUI/system/help.js new file mode 100644 index 0000000000..40bbf6dbe2 --- /dev/null +++ b/scripts/simplifiedUI/system/help.js @@ -0,0 +1,29 @@ +"use strict"; +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +// +// help.js +// scripts/system/ +// +// Created by Howard Stearns on 2 Nov 2016 +// Copyright 2016 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 +// +/* globals Tablet, Script, HMD, Controller, Menu */ + +(function () { // BEGIN LOCAL_SCOPE +var AppUi = Script.require('appUi'); + +var HELP_URL = Script.resourcesPath() + "html/tabletHelp.html"; +var HELP_BUTTON_NAME = "HELP"; +var ui; +function startup() { + ui = new AppUi({ + buttonName: HELP_BUTTON_NAME, + sortOrder: 6, + home: HELP_URL + }); +} +startup(); +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/hmd.js b/scripts/simplifiedUI/system/hmd.js new file mode 100644 index 0000000000..858b93ef1e --- /dev/null +++ b/scripts/simplifiedUI/system/hmd.js @@ -0,0 +1,92 @@ +"use strict"; + +// +// hmd.js +// scripts/system/ +// +// Created by Howard Stearns on 2 Jun 2016 +// Copyright 2016 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 +// +/* globals HMD, Script, Menu, Tablet, Camera */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ + +(function() { // BEGIN LOCAL_SCOPE + +var headset; // The preferred headset. Default to the first one found in the following list. +var displayMenuName = "Display"; +var desktopMenuItemName = "Desktop"; +['HTC Vive', 'Oculus Rift', 'WindowMS'].forEach(function (name) { + if (!headset && Menu.menuItemExists(displayMenuName, name)) { + headset = name; + } +}); + +var controllerDisplay = false; +function updateControllerDisplay() { + if (HMD.active && Menu.isOptionChecked("Third Person")) { + if (!controllerDisplay) { + HMD.requestShowHandControllers(); + controllerDisplay = true; + } + } else if (controllerDisplay) { + HMD.requestHideHandControllers(); + controllerDisplay = false; + } +} + +var button; +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + +var switchToVR = "ENTER VR"; +var switchToDesktop = "EXIT VR"; + +function onHmdChanged(isHmd) { + HMD.closeTablet(); + if (isHmd) { + button.editProperties({ + icon: "icons/tablet-icons/switch-desk-i.svg", + text: switchToDesktop + }); + } else { + button.editProperties({ + icon: "icons/tablet-icons/switch-vr-i.svg", + text: switchToVR + }); + } + updateControllerDisplay(); +} + +function onClicked() { + var isDesktop = Menu.isOptionChecked(desktopMenuItemName); + Menu.setIsOptionChecked(isDesktop ? headset : desktopMenuItemName, true); + if (!isDesktop) { + UserActivityLogger.logAction("exit_vr"); + } +} + +if (headset) { + button = tablet.addButton({ + icon: HMD.active ? "icons/tablet-icons/switch-desk-i.svg" : "icons/tablet-icons/switch-vr-i.svg", + text: HMD.active ? switchToDesktop : switchToVR, + sortOrder: 2 + }); + onHmdChanged(HMD.active); + + button.clicked.connect(onClicked); + HMD.displayModeChanged.connect(onHmdChanged); + Camera.modeUpdated.connect(updateControllerDisplay); + + Script.scriptEnding.connect(function () { + button.clicked.disconnect(onClicked); + if (tablet) { + tablet.removeButton(button); + } + HMD.displayModeChanged.disconnect(onHmdChanged); + Camera.modeUpdated.disconnect(updateControllerDisplay); + }); +} + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/html/ChatPage.html b/scripts/simplifiedUI/system/html/ChatPage.html new file mode 100644 index 0000000000..9606eeab3e --- /dev/null +++ b/scripts/simplifiedUI/system/html/ChatPage.html @@ -0,0 +1,511 @@ + + + + Chat + + + + + + + + +
+ +
+ Chat +
+ +
+ +
+ +
+ +
+ + + + + + diff --git a/scripts/simplifiedUI/system/html/EmoteApp.html b/scripts/simplifiedUI/system/html/EmoteApp.html new file mode 100644 index 0000000000..6b42fb8dc8 --- /dev/null +++ b/scripts/simplifiedUI/system/html/EmoteApp.html @@ -0,0 +1,138 @@ + + + + Emote App + + + + + + +
+

Emote App

+
+
+

Choose an emote:

+

+

+

+

+

+

+

+

+

+

+
+ + + + + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/html/SnapshotReview.html b/scripts/simplifiedUI/system/html/SnapshotReview.html new file mode 100644 index 0000000000..f080cd204a --- /dev/null +++ b/scripts/simplifiedUI/system/html/SnapshotReview.html @@ -0,0 +1,38 @@ + + + Share + + + + + + + +
+ + + +
+
+
+
+
+
+
+
+
+
+ +
+ +
+
+ +
+ +
+
+ + diff --git a/scripts/simplifiedUI/system/html/css/SnapshotReview.css b/scripts/simplifiedUI/system/html/css/SnapshotReview.css new file mode 100644 index 0000000000..54d39aaad3 --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/SnapshotReview.css @@ -0,0 +1,346 @@ +/* +// SnapshotReview.css +// +// Created by Howard Stearns for David Rowe 8/22/2016. +// Copyright 2016 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 +*/ + +/* +// START styling of top bar and its contents +*/ + +.title { + padding: 6px 10px; + text-align: left; + height: 26px; + line-height: 26px; + clear: both; +} + +.title label { + position: relative; + font-size: 18px; + float: left; +} + +#snapshotSettings { + position: relative; + float: right; +} +#settingsLabel { + position: relative; + float: right; + font-family: Raleway-SemiBold; + font-size: 14px; +} +.hifi-glyph { + font-size: 30px; + top: -4px; +} +input[type=button].naked { + color: #afafaf; + background: none; +} +input[type=button].naked:hover { + color: #ffffff; +} +input[type=button].naked:active { + color: #afafaf; +} +/* +// END styling of top bar and its contents +*/ + +/* +// START styling of snapshot instructions panel +*/ +.snapshotInstructions { + font-family: Raleway-Regular; + margin: 0 20px; + width: 100%; + height: 50%; +} +/* +// END styling of snapshot instructions panel +*/ + +/* +// START styling of snapshot pane and its contents +*/ +#snapshot-pane { + width: 100%; + height: 560px; + display: flex; + justify-content: center; + align-items: center; +} + +#snapshot-images { + width: 100%; + display: flex; + justify-content: center; + flex-direction: column; +} + +#snapshot-images img { + max-width: 100%; + max-height: 100%; +} + +.gifLabel { + position:absolute; + left: 15px; + top: 10px; + font-family: Raleway-SemiBold; + font-size: 18px; + color: white; + text-shadow: 2px 2px 3px #000000; +} +/* +// END styling of snapshot pane and its contents +*/ + +/* +// START styling of share overlay +*/ +.shareControls { + display: flex; + justify-content: space-between; + flex-direction: row; + align-items: center; + height: 65px; + line-height: 65px; + width: calc(100% - 8px); + position: absolute; + bottom: 4px; + left: 4px; + right: 4px; +} +.showShareButtonsButtonDiv { + display: inline-flex; + align-items: center; + font-family: Raleway-SemiBold; + font-size: 14px; + color: white; + width: 75px; + height: 100%; + margin-bottom: 0px; +} +.showShareButtonsButtonDiv.active:hover { + background-color: rgba(0, 0, 0, 0.45); + background-size: 2px; +} +.showShareButtonsButtonDiv > label { + text-shadow: 2px 2px 3px #000000; + margin-bottom: -14px; + margin-left: 12px; +} +.showShareButtonsButtonDiv:hover > label { + text-shadow: none; +} +.showShareButtonDots { + display: block; + width: 40px; + height: 40px; + font-family: HiFi-Glyphs; + font-size: 60px; + position: absolute; + left: 6px; + bottom: 32px; + color: white; + pointer-events: none; +} +.shareButtons { + display: flex; + align-items: flex-end; + height: 40px; + width: calc(100% - 60px); + margin-bottom: -24px; + margin-left: 0; +} +.shareButtons img { + width: 40px; + height: 40px; +} +.shareButton { + width: 40px; + height: 40px; + display: inline-block; +} +.shareButton.disabled { + background-color: #000000; + opacity: 0.5; +} +.shareControlsHelp { + height: 25px; + line-height: 25px; + position: absolute; + bottom: 40px; + left: 73px; + right: 0; + font-family: Raleway-Regular; + font-weight: 500; + font-size: 16px; + padding-left: 8px; + color: white; +} +.helpTextDiv { + width: 350px; + height: 65px; + margin-right: 15px; + line-height: 65px; + position: absolute; + bottom: 0; + right: 0; + font-family: Raleway-Regular; + font-weight: 500; + font-size: 16px; + color: white; +} +/* +// END styling of share overlay +*/ + +/* +// START styling of confirmation message +*/ +.confirmationMessageContainer { + width: 100%; + height: 100%; + position: absolute; + background-color: rgba(0, 0, 0, 0.45); + text-align: center; + left: 0; + top: 0; + pointer-events: none; + color: white; + font-weight: bold; + font-size: 16px; +} +.confirmationMessage { + width: 130px; + height: 130px; + margin: 50px auto 0 auto; +} +.confirmationMessage > img { + width: 72px; + height: 72px; + display: block; + margin: 0 auto; + padding: 10px 0 0 0; +} +/* +// END styling of uploading message +*/ + +/* +// START styling of snapshot controls (bottom panel) and its contents +*/ +#snapshot-controls { + width: 100%; + position: absolute; + left: 0; + overflow: hidden; + display: flex; + justify-content: center; +} +#snap-settings { + display: inline; + width: 150px; + margin: 2px auto 0 auto; +} +#snap-settings form input { + margin-bottom: 5px; +} + +#snap-button { + width: 72px; + height: 72px; + padding: 0; + border-radius: 50%; + background: #EA4C5F; + border: 3px solid white; + margin: 2px auto 0 auto; + box-sizing: content-box; + display: inline; + outline:none; +} +#snap-button:disabled { + background: gray; +} +#snap-button:hover:enabled { + background: #C62147; +} +#snap-button:active:enabled { + background: #EA4C5F; +} +#snap-settings-right { + display: inline; + width: 150px; + margin: auto; +} +/* +// END styling of snapshot controls (bottom panel) and its contents +*/ + + +/* +// START polaroid styling +*/ + +#print-button { + width: 72px; + height: 72px; + margin-left: 30px; + margin-top: -10px; + box-sizing: content-box; + display: inline; + outline:none; +} + +.print-icon { + margin: auto; +} + +.print-icon-default { + background: url(../img/button-snap-print.svg) no-repeat; + margin-right: -1px; + width: 64px; + height: 64px; +} + +.print-icon-loading { + background: url(../img/loader.gif) no-repeat; + width: 32px; + height: 32px; +} + +/* +// END polaroid styling +*/ + + +/* +// START misc styling +*/ +body { + padding: 0; + margin: 0; + overflow: hidden; +} +p { + margin: 2px 0; +} +h4 { + margin: 14px 0 0 0; +} +.centeredImage { + margin: 0 auto; + display: block; +} +/* +// END misc styling +*/ diff --git a/scripts/simplifiedUI/system/html/css/colpick.css b/scripts/simplifiedUI/system/html/css/colpick.css new file mode 100644 index 0000000000..fc50c4b3fb --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/colpick.css @@ -0,0 +1,433 @@ +/* +colpick Color Picker / colpick.com +*/ + +/*Main container*/ +.colpick { + position: absolute; + width: 346px; + height: 170px; + overflow: hidden; + display: none; + font-family: Arial, Helvetica, sans-serif; + background:#ebebeb; + border: 1px solid #bbb; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + + /*Prevents selecting text when dragging the selectors*/ + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} +/*Color selection box with gradients*/ +.colpick_color { + position: absolute; + touch-action: none; + left: 7px; + top: 7px; + width: 156px; + height: 156px; + overflow: hidden; + outline: 1px solid #aaa; + cursor: crosshair; +} +.colpick_color_overlay1 { + position: absolute; + left:0; + top:0; + width: 156px; + height: 156px; + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=1,startColorstr='#ffffff', endColorstr='#00ffffff')"; /* IE8 */ + background: -moz-linear-gradient(left, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, right top, color-stop(0%,rgba(255,255,255,1)), color-stop(100%,rgba(255,255,255,0))); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(left, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(left, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%); /* Opera 11.10+ */ + background: -ms-linear-gradient(left, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%); /* IE10+ */ + background: linear-gradient(to right, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(GradientType=1,startColorstr='#ffffff', endColorstr='#00ffffff'); /* IE6 & IE7 */ +} +.colpick_color_overlay2 { + position: absolute; + left:0; + top:0; + width: 156px; + height: 156px; + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#00000000', endColorstr='#000000')"; /* IE8 */ + background: -moz-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(0,0,0,0)), color-stop(100%,rgba(0,0,0,1))); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* Opera 11.10+ */ + background: -ms-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* IE10+ */ + background: linear-gradient(to bottom, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* W3C */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00000000', endColorstr='#000000',GradientType=0 ); /* IE6-9 */ +} +/*Circular color selector*/ +.colpick_selector_outer { + background:none; + position: absolute; + width: 11px; + height: 11px; + margin: -6px 0 0 -6px; + border: 1px solid black; + border-radius: 50%; +} +.colpick_selector_inner{ + position: absolute; + width: 9px; + height: 9px; + border: 1px solid white; + border-radius: 50%; +} +/*Vertical hue bar*/ +.colpick_hue { + position: absolute; + touch-action: none; + top: 6px; + left: 175px; + width: 19px; + height: 156px; + border: 1px solid #aaa; + cursor: n-resize; +} +/*Hue bar sliding indicator*/ +.colpick_hue_arrs { + position: absolute; + touch-action: none; + left: -8px; + width: 35px; + height: 7px; + margin: -7px 0 0 0; +} +.colpick_hue_larr { + position:absolute; + touch-action: none; + width: 0; + height: 0; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-left: 7px solid #858585; +} +.colpick_hue_rarr { + position:absolute; + touch-action: none; + right:0; + width: 0; + height: 0; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-right: 7px solid #858585; +} +/*New color box*/ +.colpick_new_color { + position: absolute; + touch-action: none; + left: 207px; + top: 6px; + width: 60px; + height: 27px; + background: #f00; + border: 1px solid #8f8f8f; +} +/*Current color box*/ +.colpick_current_color { + position: absolute; + touch-action: none; + left: 277px; + top: 6px; + width: 60px; + height: 27px; + background: #f00; + border: 1px solid #8f8f8f; +} +/*Input field containers*/ +.colpick_field, .colpick_hex_field { + position: absolute; + touch-action: none; + height: 20px; + width: 60px; + overflow:hidden; + background:#f3f3f3; + color:#b8b8b8; + font-size:12px; + border:1px solid #bdbdbd; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +.colpick_rgb_r { + top: 40px; + left: 207px; +} +.colpick_rgb_g { + top: 67px; + left: 207px; +} +.colpick_rgb_b { + top: 94px; + left: 207px; +} +.colpick_hsb_h { + top: 40px; + left: 277px; +} +.colpick_hsb_s { + top: 67px; + left: 277px; +} +.colpick_hsb_b { + top: 94px; + left: 277px; +} +.colpick_hex_field { + width: 68px; + left: 207px; + top: 121px; +} +/*Text field container on focus*/ +.colpick_focus { + border-color: #999; +} +/*Field label container*/ +.colpick_field_letter { + position: absolute; + width: 12px; + height: 20px; + line-height: 20px; + padding-left: 4px; + background: #efefef; + border-right: 1px solid #bdbdbd; + font-weight: bold; + color:#777; +} +/*Text inputs*/ +.colpick_field input, .colpick_hex_field input { + position: absolute; + touch-action: none; + right: 11px; + margin: 0; + padding: 0; + height: 20px; + line-height: 20px; + background: transparent; + border: none; + font-size: 12px; + font-family: Arial, Helvetica, sans-serif; + color: #555; + text-align: right; + outline: none; +} +.colpick_hex_field input { + right: 4px; +} +/*Field up/down arrows*/ +.colpick_field_arrs { + position: absolute; + touch-action: none; + top: 0; + right: 0; + width: 9px; + height: 21px; + cursor: n-resize; +} +.colpick_field_uarr { + position: absolute; + touch-action: none; + top: 5px; + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-bottom: 4px solid #959595; +} +.colpick_field_darr { + position: absolute; + touch-action: none; + bottom:5px; + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid #959595; +} +/*Submit/Select button*/ +.colpick_submit { + position: absolute; + touch-action: none; + left: 207px; + top: 149px; + width: 130px; + height: 22px; + line-height:22px; + background: #efefef; + text-align: center; + color: #555; + font-size: 12px; + font-weight:bold; + border: 1px solid #bdbdbd; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +.colpick_submit:hover { + background:#f3f3f3; + border-color:#999; + cursor: pointer; +} + +/*full layout with no submit button*/ +.colpick_full_ns .colpick_submit { + display:none; +} +.colpick_full_ns .colpick_new_color { + width: 130px; + height: 25px; +} +.colpick_full_ns .colpick_rgb_r, .colpick_full_ns .colpick_hsb_h { + top: 42px; +} +.colpick_full_ns .colpick_rgb_g, .colpick_full_ns .colpick_hsb_s { + top: 73px; +} +.colpick_full_ns .colpick_rgb_b, .colpick_full_ns .colpick_hsb_b { + top: 104px; +} +.colpick_full_ns .colpick_hex_field { + top: 135px; +} + +/*rgbhex layout*/ +.colpick_rgbhex .colpick_hsb_h, .colpick_rgbhex .colpick_hsb_s, .colpick_rgbhex .colpick_hsb_b { + display:none; +} +.colpick_rgbhex { + width:282px; +} +.colpick_rgbhex .colpick_field, .colpick_rgbhex .colpick_submit { + width:68px; +} +.colpick_rgbhex .colpick_new_color { + width:34px; + border-right:none; +} +.colpick_rgbhex .colpick_current_color { + width:34px; + left:240px; + border-left:none; +} + +/*rgbhex layout, no submit button*/ +.colpick_rgbhex_ns .colpick_submit { + display:none; +} +.colpick_rgbhex_ns .colpick_new_color{ + width:34px; + border: 1px solid #8f8f8f; +} +.colpick_rgbhex_ns .colpick_rgb_r { + top: 42px; +} +.colpick_rgbhex_ns .colpick_rgb_g { + top: 73px; +} +.colpick_rgbhex_ns .colpick_rgb_b { + top: 104px; +} +.colpick_rgbhex_ns .colpick_hex_field { + top: 135px; +} + +/*hex layout*/ +.colpick_hex .colpick_hsb_h, .colpick_hex .colpick_hsb_s, .colpick_hex .colpick_hsb_b, .colpick_hex .colpick_rgb_r, .colpick_hex .colpick_rgb_g, .colpick_hex .colpick_rgb_b { + display:none; +} +.colpick_hex { + width:206px; + height:201px; +} +.colpick_hex .colpick_hex_field { + width:72px; + height:25px; + top:168px; + left:80px; +} +.colpick_hex .colpick_hex_field div, .colpick_hex .colpick_hex_field input { + height: 25px; + line-height: 25px; +} +.colpick_hex .colpick_new_color { + left:9px; + top:168px; + width:30px; + border-right:none; +} +.colpick_hex .colpick_current_color { + left:39px; + top:168px; + width:30px; + border-left:none; +} +.colpick_hex .colpick_submit { + left:164px; + top: 168px; + width:30px; + height:25px; + line-height: 25px; +} + +/*hex layout, no submit button*/ +.colpick_hex_ns .colpick_submit { + display:none; +} +.colpick_hex_ns .colpick_hex_field { + width:80px; +} +.colpick_hex_ns .colpick_new_color{ + width:60px; + border: 1px solid #8f8f8f; +} + +/*Dark color scheme*/ +.colpick_dark { + background: #161616; + border-color: #2a2a2a; +} +.colpick_dark .colpick_color { + outline-color: #333; +} +.colpick_dark .colpick_hue { + border-color: #555; +} +.colpick_dark .colpick_field, .colpick_dark .colpick_hex_field { + background: #101010; + border-color: #2d2d2d; +} +.colpick_dark .colpick_field_letter { + background: #131313; + border-color: #2d2d2d; + color: #696969; +} +.colpick_dark .colpick_field input, .colpick_dark .colpick_hex_field input { + color: #7a7a7a; +} +.colpick_dark .colpick_field_uarr { + border-bottom-color:#696969; +} +.colpick_dark .colpick_field_darr { + border-top-color:#696969; +} +.colpick_dark .colpick_focus { + border-color:#444; +} +.colpick_dark .colpick_submit { + background: #131313; + border-color:#2d2d2d; + color:#7a7a7a; +} +.colpick_dark .colpick_submit:hover { + background-color:#101010; + border-color:#444; +} \ No newline at end of file diff --git a/scripts/simplifiedUI/system/html/css/edit-style.css b/scripts/simplifiedUI/system/html/css/edit-style.css new file mode 100644 index 0000000000..470e57ad6d --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/edit-style.css @@ -0,0 +1,1796 @@ +/* +// edit-style.css +// +// Created by Ryan Huffman on 13 Nov 2014 +// Copyright 2014 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 +*/ + +@font-face { + font-family: Raleway-Regular; + src: url(../../../../resources/fonts/Raleway-Regular.ttf), /* Windows production */ + url(../../../../fonts/Raleway-Regular.ttf), /* OSX production */ + url(../../../../interface/resources/fonts/Raleway-Regular.ttf), /* Development, running script in /HiFi/examples */ + url(../fonts/Raleway-Regular.ttf); /* Marketplace script */ +} + +@font-face { + font-family: Raleway-Light; + src: url(../../../../resources/fonts/Raleway-Light.ttf), + url(../../../../fonts/Raleway-Light.ttf), + url(../../../../interface/resources/fonts/Raleway-Light.ttf), + url(../fonts/Raleway-Light.ttf); +} + +@font-face { + font-family: Raleway-Bold; + src: url(../../../../resources/fonts/Raleway-Bold.ttf), + url(../../../../fonts/Raleway-Bold.ttf), + url(../../../../interface/resources/fonts/Raleway-Bold.ttf), + url(../fonts/Raleway-Bold.ttf); +} + +@font-face { + font-family: Raleway-SemiBold; + src: url(../../../../resources/fonts/Raleway-SemiBold.ttf), + url(../../../../fonts/Raleway-SemiBold.ttf), + url(../../../../interface/resources/fonts/Raleway-SemiBold.ttf), + url(../fonts/Raleway-SemiBold.ttf); +} + +@font-face { + font-family: FiraSans-SemiBold; + src: url(../../../../resources/fonts/FiraSans-SemiBold.ttf), + url(../../../../fonts/FiraSans-SemiBold.ttf), + url(../../../../interface/resources/fonts/FiraSans-SemiBold.ttf), + url(../fonts/FiraSans-SemiBold.ttf); +} + +@font-face { + font-family: AnonymousPro-Regular; + src: url(../../../../resources/fonts/AnonymousPro-Regular.ttf), + url(../../../../fonts/AnonymousPro-Regular.ttf), + url(../../../../interface/resources/fonts/AnonymousPro-Regular.ttf), + url(../fonts/AnonymousPro-Regular.ttf); +} + +@font-face { + font-family: HiFi-Glyphs; + src: url(../../../../resources/fonts/hifi-glyphs.ttf), + url(../../../../fonts/hifi-glyphs.ttf), + url(../../../../interface/resources/fonts/hifi-glyphs.ttf), + url(../fonts/hifi-glyphs.ttf); +} + +* { + margin: 0; + padding: 0; +} + +body { + + color: #afafaf; + background-color: #404040; + font-family: Raleway-Regular; + font-size: 12px; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + overflow-x: hidden; + overflow-y: auto; +} + +table { + font-family: FiraSans-SemiBold; + font-size: 15px; + color: #afafaf; + border-collapse: collapse; + width: 100%; + border: 2px solid #575757; + border-radius: 7px; +} + +thead { + font-family: Raleway-Regular; + font-size: 12px; + text-transform: uppercase; + background-color: #1c1c1c; + padding: 1px 0; + border-bottom: 1px solid #575757; + width: 100%; +} + +tbody { + width: 100%; + display: block; +} + +tfoot { + font-family: Raleway-Light; + font-size: 13px; + background-color: #1c1c1c; + border-top: 1px solid #575757; + width: 100%; +} + +tfoot tr { + background-color: #1c1cff; +} + +thead tr { + height: 26px; /* 28px with thead padding */ +} + +thead th { + height: 26px; + background-color: #1c1c1c; + border-right: 1px solid #575757; +} + +thead th:last-child { + border: none; +} + +tbody td { + height: 26px; +} + +tfoot td { + height: 18px; + width: 100%; + background-color: #1c1c1c; + margin-left: 12px; +} + +tr { + width: 100%; + cursor: pointer; +} + +tr:nth-child(odd) { + background-color: #2e2e2e; +} + +tr:nth-child(even) { + background-color: #1c1c1c; +} + +tr:focus { + outline: none; +} + +tr.selected { + color: #000000; + background-color: #00b4ef; +} + +tr.selected + tr.selected { + border-top: 1px solid #2e2e2e; +} + +th { + text-align: center; + word-wrap: nowrap; + white-space: nowrap; + padding-left: 12px; + padding-right: 12px; +} + +td { + overflow: hidden; + text-overflow: clip; + white-space: nowrap; + word-wrap: nowrap; + padding-left: 12px; + padding-right: 12px; +} + +td.hidden { + padding-left: 0; + padding-right: 0; +} + +td.url { + white-space: nowrap; + overflow: hidden; +} + + +input[type="text"], input[type="search"], input[type="number"], textarea { + margin: 0; + padding: 0 12px; + color: #afafaf; + background-color: #252525; + border: none; + font-family: FiraSans-SemiBold; + font-size: 15px; +} + +textarea { + font-family: AnonymousPro-Regular; + font-size: 16px; + padding-top: 5px; + padding-bottom: 5px; + min-height: 64px; + width: 100%; + resize: vertical; +} + +input::-webkit-input-placeholder { + font-style: italic; +} + +input:focus, textarea:focus, button:focus { + color: #fff; + background-color: #000; + outline: 1px solid #00b4ef; + outline-offset: -1px; +} + +input::selection, textarea::selection { + color: #000000; + background-color: #00b4ef; +} + +input.search { + border-radius: 14px; +} + +input.search:focus { + outline: none; + box-sizing: border-box; + height: 26px; + margin-top: 1px; + margin-bottom: 1px; + box-shadow: 0 0 0 1px #00b4ef; +} + +input:disabled, textarea:disabled, .draggable-number.text[disabled="disabled"] { + background-color: #383838; + color: #afafaf; +} + +input[type="text"] { + height: 28px; + width: 100%; +} + +input.multi-diff:not(:focus) + span.multi-diff, +textarea.multi-diff:not(:focus) + span.multi-diff, +.draggable-number.multi-diff>input:not(:focus)+span.multi-diff, +dl>dt.multi-diff:not(:focus) + span.multi-diff { + visibility: visible; + position: absolute; + display: inline-block; + z-index: 2; + top: 7.5px; + left: 20px; + max-width: 50px; + min-width: 10px; + width: 50%; + height: 13px; + background-image: linear-gradient(transparent 0%, transparent 10%, #afafaf 10%, #afafaf 20%, transparent 20%, transparent 45%, #afafaf 45%, #afafaf 55%, transparent 55%, transparent 80%, #afafaf 80%, #afafaf 90%, transparent 90%, transparent 100%); + background-repeat: no-repeat; + pointer-events: none; +} + +input.multi-diff:not(:focus)::-webkit-input-placeholder, input.multi-diff:not(:focus) { + color: transparent; +} + +.draggable-number.multi-diff .text { + color: transparent; +} + +.dropdown > span.multi-diff { + top: 5px; + left: 10px; +} + +.text, .url, .texture, .textarea { + position: relative; +} + +input[type="search"] { + height: 28px; + width: 100%; +} +input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; + height: 20px; + width: 20px; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goNAQIFbBwsbwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAZfSURBVDgRAVQGq/kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9PT0YAwMDBgAAAAD8/Pz5+vr67MrKyv0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAA+Pj4KAgICQgAAAE3///9RAQEBFQAAAAD////pAQEBu/39/ab+/v7BxcXF9gAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAADs7OzMEBASIAQEBRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAACm+/v7cMXFxewAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAPT09OwEBAagBAQEcAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8AAAAA2f///2XCwsLDAAAAAAAAAAABAAAAAAAAAAA9PT0KAwMDt////z4AAAAAAAAAAAEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAcIBAQFJvr6+9gAAAAACAAAAAAAAAAAAAABg////PgEBAQAAAAAAS0tLADg4OAAAAAAAAAAAAP///wADAwMAQEBAACEhIQD///8A////AP7+/j76+vpWAAAAAAAAAAACAAAAAD09PQ8CAgJkAQEBAP///wD///8ACgoKAFhYWAAyMjIAAAAAAAICAgBGRkYAT09PABEREQAAAAAAAAAAAAAAAAACAgJwOjo6EAAAAAAEAAAAAAICAg8BAQExAAAAAAEBAQABAQEAsrKyAAoKCgBaWloA9/f3ABsbGwBISEgAtra2AM7OzgACAgIA////AP///wABAQEuBQUFDgAAAPAEAAAAAPz8/BkEBAQAAQEBAAAAAAAAAAAA+vr6AKioqAALCwsAZWVlAAcHBwC/v78Au7u7AAEBAQD///8AAAAAAAAAAAAAAAABAAAAAAAAAAACAAAAAAQEBOgBAQEAAQEBAAEBAQABAQEAAQEBAPz8/ADT09MADg4OAP39/QDQ0NAA/v7+AP///wAAAAAAAAAAAAEBAQABAQEAAQEBAAAAAAACAAAAAAAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAACkpKQBQUFAAx8fHAObm5gBfX18AFxcXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAP39/fz+/v7z////AP///wD///8AJycnAGFhYQDc3NwApaWlAJaWlgD29vYAZmZmABQUFAACAgIAAQEBAAEBAQABAQH1AAAA/AAAAAACAAAAAPr6+ukBAQGkAAAAAAAAAAABAQEAQEBAAObm5gCmpqYA+fn5APPz8wCdnZ0A////ACwsLAD///8AAAAAAAAAAAD///+k9vb26QAAAAABAAAAAAAAAAA+Pj4uAgICxgAAAAsAAAAAEBAQAPr6+gD29vYAAAAAAAAAAAABAQEAAgICAP///wD+/v4AAAAAAAAAAPL8/Pw/xMTE0AAAAAACAAAAAAAAAAD5+fnV////nQICAgABAQEA8fHxAPX19QABAQEAAAAAAAAAAAD///8A/v7+AP7+/gAAAAAAAAAAAP7+/p36+vrSAAAAAAAAAAADAAAAAAAAAADl5eX/ICAgwQAAAA////8q////BgEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD1/f39mAEBAXrGxsb7AAAAAAAAAAADAAAAAAAAAAAAAAAA4eHh/BgYGLsBAQHDBAQEHAAAACP///8AAQEBAAAAAAAAAAAAAAAA+////7QBAQFu+fn5m8bGxvoAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPz8/Cv7+/iUBAQFMAgICEQICAgD8/PzdAwMDs/j4+OvHx8f5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8TUnpZ7EwQgAAAABJRU5ErkJggg==') +} + +input[type="number"] { + position: relative; + height: 28px; + width: 124px; +} +input[type=number] { + padding-right: 3px; +} +input[type=number]::-webkit-inner-spin-button { + opacity: 1.0; + display: block; + position: relative; + width: 10px; + height: 90%; + overflow: hidden; + font-family: HiFi-Glyphs; + font-size: 32px; + color: #afafaf; + cursor: pointer; + background-color: #000000; +} +input[type=number]::-webkit-inner-spin-button:before, +input[type=number]::-webkit-inner-spin-button:after { + position:absolute; + left: -19px; + line-height: 8px; + text-align: center; +} +input[type=number]::-webkit-inner-spin-button:before { + content: "6"; + top: 4px; +} +input[type=number]::-webkit-inner-spin-button:after { + content: "5"; + bottom: 4px; +} + +input[type=number].hover-up::-webkit-inner-spin-button:before, +input[type=number].hover-down::-webkit-inner-spin-button:after { + color: #ffffff; +} + +input[type=range] { + -webkit-appearance: none; + background: #2e2e2e; + height: 1.8rem; + border-radius: 1rem; +} +input[type=range]::-webkit-slider-thumb { + -webkit-appearance:none; + width: 0.6rem; + height: 1.8rem; + padding:0; + margin: 0; + background-color: #696969; + border-radius: 1rem; +} +input[type=range]::-webkit-slider-thumb:hover { + background-color: white; +} +input[type=range]:focus { + outline: none; +} + +input.no-spin::-webkit-outer-spin-button, +input.no-spin::-webkit-inner-spin-button { + display: none; + -webkit-appearance: none; + margin: 0; /* <-- Apparently some margin are still there even though it's hidden */ + padding-right: 12px; +} + +input[type=button], button.hifi-edit-button { + font-family: Raleway-Bold; + font-size: 13px; + text-transform: uppercase; + vertical-align: top; + height: 28px; + min-width: 120px; + padding: 0 18px; + margin-right: 6px; + border-radius: 5px; + border: none; + color: #fff; + background-color: #000; + background: linear-gradient(#343434 20%, #000 100%); + cursor: pointer; +} + +input[type=button].glyph, button.hifi-edit-button.glyph { + font-family: HiFi-Glyphs; + font-size: 20px; + text-transform: none; + min-width: 32px; + padding: 0; +} + +input[type=button].red, button.hifi-edit-button.red { + color: #fff; + background-color: #94132e; + background: linear-gradient(#d42043 20%, #94132e 100%); +} +input[type=button].blue, button.hifi-edit-button.blue { + color: #fff; + background-color: #1080b8; + background: linear-gradient(#00b4ef 20%, #1080b8 100%); +} +input[type=button].white, button.hifi-edit-button.white { + color: #121212; + background-color: #afafaf; + background: linear-gradient(#fff 20%, #afafaf 100%); +} + +input[type=button]:enabled:hover, button.hifi-edit-button:enabled:hover { + background: linear-gradient(#000, #000); + border: none; +} +input[type=button].red:enabled:hover, button.hifi-edit-button.red:enabled:hover { + background: linear-gradient(#d42043, #d42043); + border: none; +} +input[type=button].blue:enabled:hover, button.hifi-edit-button.blue:enabled:hover { + background: linear-gradient(#00b4ef, #00b4ef); + border: none; +} +input[type=button].white:enabled:hover, button.hifi-edit-button.white:enabled:hover { + background: linear-gradient(#fff, #fff); + border: none; +} + +input[type=button]:active, button.hifi-edit-button:active { + background: linear-gradient(#343434, #343434); +} +input[type=button].red:active, button.hifi-edit-button.red:active { + background: linear-gradient(#94132e, #94132e); +} +input[type=button].blue:active, button.hifi-edit-button.blue:active { + background: linear-gradient(#1080b8, #1080b8); +} +input[type=button].white:active, button.hifi-edit-button.white:active { + background: linear-gradient(#afafaf, #afafaf); +} + +input[type=button]:disabled, button.hifi-edit-button:disabled { + color: #252525; + background: linear-gradient(#575757 20%, #252525 100%); +} + +input[type=button][pressed=pressed], button.hifi-edit-button[pressed=pressed] { + color: #00b4ef; +} + +input[type=checkbox] { + display: none; +} +input[type=checkbox] + label { + padding-left: 24px; + background-repeat: no-repeat; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACpSURBVDhPY2xoaGD68+dPMSMjY9L////VgTQjAw4AlH8PxLOPHj1azWxjY1MBVNsBFBfBpwkEgNKcQGwtJyfHyATkF0KEiQdAzYlMQEIUyicFyDD9+/ePgRxMvsb///4zkIOZ/v0HmkAGHginYjGNGAzS+BpdkAj8mun/3//92DyPD//993cG88nTJ4+Zm5p/BSZeJYb/DEJADEzNOPF7hn8Mk69cvVIPAHN5pyfo70F5AAAAAElFTkSuQmCC); + cursor: pointer; +} +input[type=checkbox]:enabled + label:hover { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAClSURBVDhPY2hoaGD6//9/6b9//64D8T8gGycASr/7+/dv5/79+1kYgIxKqDjRAKiniRFIv2JgYBAFYlLAE0aQ66AckgDjjx8/yNP44cMH8jS+fPmSPI0PHz4kT+PNmzfJ03jp0iXyNJ46dYo8jYcPHyYnAbxm+vnzZz8wLhlIwd+/f5/BrKSkdExCQuLrnz9/lIBpUAiIQekXF34PTGmTT548WQ8AokXg+rhVtPYAAAAASUVORK5CYII=); +} +input[type=checkbox]:checked + label { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFLSURBVDhPjZK9SgNBFIXvbCaQRDQq7mIhQRPBRisJKcwLWOobaCsExEaxcEEQe0trGysfwg0EwWoDsbFIJUaIBJOwus547saNP3FlPzgzzJxzL5edFbZtG77v7wkhtrXWS9gFRQC/DZ07jnOYKJfL+8ie4n7mvyIGdhpay+VyQuK8y5dPZoHuVtbpZcLi4wjJ1x4t316R9dDgBlsSi8mGu7pJjyJFzVaH+r7iqyHSELSQzVADjS0UgjlDKUUsLzVO98+9kSLGV5qaHXhjU0GWNSxk3hCIwnsfeMNCjTArLmHeUBodoLiE+R+jxuHPUZP4elGE3teonx2S/Q7lJzOUlkYQ+A4/xzyegzNhXmJpwTMXry9IFjcoa84O0r+QXpcK1cugCLREZadyoA19Ergxwf96nKjd1KqlYqmLQ540TUNwItUmRWdu3T36AODjwgpY9xqqAAAAAElFTkSuQmCC); +} +input[type=checkbox]:checked + label:hover { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEySURBVDhPnZLPSsNAEMa/XVPBCE0RhNy0OarP4Av4AD6JB0GwVBA8efBBxHsgh4CQswcRoUIpiIpVAm3zZ5M4szFSbQPBH3xkJvNNZskOer2eLIriKM/ze1JOcS1UHmdZduF5ngEKjr/fN4Z6+oKerwA2gxC4HAFPEWVLsAzgZAvYt3Q6Enw6jg7uBAaTFMNwhpnKdbXCkAJdy8ROu4XrXW2HTJIErHcFDD6nC02Mom8PwymeE2gvS0ZRBBaTlsOXEmdlrfLLOI7Bakrl/zWxCT8T/904f9QW/b06qtrCUdtFCqdjYs2Q2jAPX8c2XQd7Kr/wfV8vwIPs4Ga1ixe5Xrr/YFLTYfKIvWzM6ZtwXZdX7lxXG0L+sxXHcW5t254opRzawQ0S72+dPmjTroIgOP0CQSMt5LDn1T8AAAAASUVORK5CYII=); +} +input.multi-diff[type=checkbox] + label { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFbSURBVDhPY2xoaGD68+dPMSMjY9L////VgTQjAw4AlH8PxLOPHj1azWxjY1MBVNsBFBfBpwkEgNKcQGwtJyfHyALkF4IE34gqM9zU9WT4wicG4mIA1l/fGIyOL2EQeP8EZEAiC5AQBUlcMQ5ieMXIwfDo9SeG73/+gRXDAAsTI4Pd9wdgTVAgw/Tv3z8GEP7Jwctw78M3DE0goPr6BoPludVgdTAM1wgCv//9B9PIQOPNDYaAGxtRNIEw03+gYhDGBtSBNgVc3wiWR8dM//4DTQBidKD++jqD//X1YDlsGMWpMKD26jqD79V1GM5DxihOZQWGntqrawy+V9ZiOA0dw21k/f6JwerzHQbvS2swTMeGGfPz8l8BLRP9KizDwP0WHk+EwGum/3//94M8y/nmEdZAwIb//vs7g/nk6ZPHzE3NvwITrxLDfwYhIAamZpz4PcM/hslXrl6pBwAmfz5iaAlAuAAAAABJRU5ErkJggg==); +} +input.multi-diff[type=checkbox] + label:hover { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFPSURBVDhPjZJBSwJBHMXfrG6rWEkl1MFDGOihDn2JIIrqc3QJunbyFhUkRieD+hYepWteuxctXiJ1Q5xmdmZ3bWZTUHezfvAu/3lv3n+HRblcTrbb7fN+v/8eBMFgFpxz13Gcu3q9bqHb7V4M5/9GhatE3cIsy0o99YBKC3jliCWbBK43gK0MoDI9otfTB/vPBC9Uwu4xMC8IzSOSBsFxIYNqMTGcAIYQAlodD3j5/IqENIc5gqt1P/SNZKhaXR0a5E/5BEcrwH1xEHrGZbiuC604DpZ81AoiPJ/WROM4e4sSt3kaaRopNrg7z1FZdSLmcU2saqrX20lTXC5/RFabFmk2m+GLnBnbWJMOThJv4SV/QRqNBjNNM9UiGeQHdDiejZSSG5TSG71zjnVivyVOKlNLlEqlx+xCds7zvU31G6Z938dvEq4QjLMH27ZPvwHFVYQr3h7uHwAAAABJRU5ErkJggg==); +} + +.rgb.fstuple .color-picker.multi-diff:after { + width: 20px; + height: 20px; + content: ' '; + background: darkgray; + display: flex; + clip-path: polygon(0 0, 0 100%, 100% 100%); +} + +.icon-input input { + position: relative; + padding-left: 36px; +} +.icon-input span { + position: absolute; + left: 6px; + top: -2px; + font-family: HiFi-Glyphs; + font-size: 30px; + color: #afafaf; +} +.icon-input input:focus + span { + color: #ffffff; +} + +.icon { + font-family: HiFi-Glyphs; + color: white; +} + +#property-type-icon { + font-size: 50px; +} + +.selectable { + -webkit-touch-callout: text; + -webkit-user-select: text; + -khtml-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; + cursor: text; +} + +.color-box { + display: inline-block; + width: 15pt; + height: 15pt; + border: 0.75pt solid black; + margin: 1.5pt; + cursor: pointer; +} + +.color-box.highlight { + width: 13.5pt; + height: 13.5pt; + border: 1.5pt solid black; +} + +#properties-list { + display: flex; + flex-direction: column; + + margin-top: 16px; +} + +#properties-list .fieldset { + position: relative; + /* 0.1px on the top is to prevent margin collapsing between this and it's first child */ + margin: 0 -21px 21px -21px; + padding: 0.1px 21px 0 21px; + border: none; + border-top: 1px rgb(90,90,90) solid; + box-shadow: 0 -1px 0 rgb(37,37,37); +} + +#properties-list .fieldset.fstuple, #properties-list .fieldset.fsrow { + margin-top: 21px; + border: none; + box-shadow: none; +} + +#properties-list > .fieldset[data-collapsed="true"] + .fieldset { + margin-top: 0; +} + +#properties-list > .fieldset[data-collapsed="true"] > *:not(div.legend) { + display: none !important; +} + +.section-header { + padding: 0 16px; + border-top: 1px rgb(90,90,90) solid; + box-shadow: 1px -1px 0 rgb(37,37,37); + border-bottom: 1px solid rgb(37, 37, 37); +} + +div.section-header, hr { + display: flex; + flex-flow: row nowrap; + padding: 10px 16px; + font-family: Raleway-Regular; + font-size: 12px; + color: #afafaf; + height: 28px; + text-transform: uppercase; + outline: none; + margin-bottom: 10px; + align-items: center; +} + +.section.minor { + margin: 0 21px; + box-shadow: 1px -1px 0 rgb(37,37,37); + border-left: 1px solid #575757; +} + +.container.property { + padding: 0 16px; +} + +.stretch { + width: 100%; +} + +div.section-header .label { + width: 100%; +} + +.section.minor div.section-header { + border-right: 0; +} + +div.section[collapsed="true"] > .container { + display: none; +} + +div.section[collapsed="true"], div.section[collapsed="true"] > .section-header { + margin-bottom: 0; +} + +.section.major { + margin-bottom: 20px; +} + +.section.minor.last { + margin-bottom: 20px; + border-bottom: 1px solid rgb(37,37,37); +} + +.section-header { + background-color: #373737; +} + + +.section-header span { + font-size: 30px; + font-family: HiFi-Glyphs; +} + +.triple-label { + text-transform: uppercase; + text-align: center; + padding: 6px 0; + cursor: default; +} + +.triple-item { + margin-right: 10px; +} + +.triple-item.rgb.fstuple { + display: block !important; +} + +.section-header[collapsed="true"] { + margin-bottom: -21px; +} + +#properties-list .sub-section-header { + border-top: none; + box-shadow: none; + margin-top: 8px; +} + +.sub-section-header + .property { + margin-top: 0; +} + +hr { + border: none; + padding-top: 2px; +} + +.property { + min-height: 28px; +} + +.property.checkbox { + width: auto; +} + +span.indented { + padding-left: 16px; +} + +.property label, .number label { + display: table-cell; + vertical-align: middle; + font-family: Raleway-SemiBold; + font-size: 14px; +} +.property label .unit, .number label .unit { + margin-left: 8px; + font-family: Raleway-Light; + font-size: 13px; +} + +.property div.legend, .number div.legend { + display: table-cell; + vertical-align: middle; + font-family: Raleway-SemiBold; + font-size: 14px; +} +.property div.legend .unit, .number div.legend .unit { + margin-left: 8px; + font-family: Raleway-Light; + font-size: 13px; +} + +.value { + display: block; + min-height: 18px; +} +.value label { + display: inline-block; + vertical-align: top; +} +.value div.legend { + display: inline-block; + vertical-align: top; + width: 48px; +} +.value span { + font-size: 15px; + margin-right: 4px; +} + +#placeholder-property-type { + display: flex; + align-items: center; + width: auto; + margin-right: 20px; +} + +#placeholder-property-locked { + margin-left: 6px; +} + +.checkbox + .checkbox { + margin-top: 0; +} + +.checkbox-sub-props { + margin-top: 18px; +} + +.property .number { + float: left; +} +.property .number + .number { + margin-left: 10px; +} + +.property.range label{ + padding-bottom: 3px; +} +.property.range input[type=number]{ + margin-left: 0.8rem; + width: 5.4rem; + height: 1.8rem; +} + +.dropdown { + position: relative; + margin-bottom: -17px; +} + +.dropdown select { + clear: both; +} + +.dropdown dl { + clear: both; + cursor: pointer; + font-family: FiraSans-SemiBold; + font-size: 15px; + width: 292px; + height: 28px; + padding: 0 28px 0 12px; + color: #afafaf; + background: #575757; + position: relative; + display: flex; + align-items: center; +} + +.dropdown dl[dropped="true"] { + color: #404040; + background: linear-gradient(#afafaf, #afafaf); + z-index: 998; +} + +.dropdown dt { + height: 100%; + box-sizing: border-box; + border-right: 1px solid #121212; + width: 100%; +} +.dropdown dt:hover { + color: #404040; +} +.dropdown dt:focus { + outline: none; +} +.dropdown dt span:first-child { + display: inline-block; + position: relative; + top: 5px; +} +.dropdown dt span:last-child { + font-family: HiFi-Glyphs; + font-size: 42px; + float: right; + margin-right: -48px; + position: relative; + left: -12px; + top: -9px; +} + +.dropdown dd { + position: absolute; + top: 28px; + left: 3px; + display: none; +} +.dropdown dl[dropped="true"] dd { + display: block; +} + +.dropdown li { + list-style-type: none; + padding: 3px 0 1px 12px; + width: 320px; + height: auto; + font-family: FiraSans-SemiBold; + font-size: 15px; + color: #404040; + background-color: #afafaf; + z-index: 999; +} +.dropdown li:hover { + background-color: #00b4ef; +} + +.dropdown dl[disabled="disabled"], .dropdown dl[disabled="disabled"][dropped="true"] { + color: #252525; + background: linear-gradient(#575757 20%, #252525 100%); +} +.dropdown dl[disabled="disabled"] dd { + display: none; +} +.dropdown dl[disabled="disabled"] dt:hover { + color: #252525; +} + +.multiselect-box { + position: absolute; +} +.multiselect-box select { + font-family: FiraSans-SemiBold; + font-size: 15px; + color: #afafaf; + background-color: #252525; + border: none; + text-align-last: center; +} +.over-select { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; +} +.multiselect-options { + position: absolute; + display: none; + border: none; +} +.multiselect-options span { + font-family: hifi-glyphs; + font-size: 13px; + color: #000000; +} +.multiselect-options label { + z-index: 2; + display: block; + font-family: FiraSans-SemiBold; + font-size: 11px; + color: #000000; + background-color: #afafaf; +} +.multiselect-options label:hover { + background-color: #1e90ff; +} +.multiselect-options input[type=checkbox] + label { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goSADUOYnF4LQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAIMSURBVFjD7ZmxkqowFIZ/7mwJPen1AezV3t6hFvrQhweAHvrQ8wL2xt4HwD7ppd+tvHOvu0gCYdEZTsmAfpNzzpcTcAB84o3iD94sZuCx4+Pxwvl8dl4JcL1ef84lMQPPwBZDSgkp5XsASylBKUUYhhBCvDbw7XYDpRRKKTRNA8YYOOevC5ymKZRS/13jnHdCTwLMOW8tAc45GGNomuY1gKuq6lxFIQQopdMDXy4X5HmudW8URdMCSynBGNOG3Ww20wHf9dVWl4+wbav7a8CMsW9G+Cm22+1T2F8BzvMc1+u18z5CCJIkseNhKSX2+z2qqjLWl84zhBAURQHXde0A31Oa57nWbqSrLwDwPA9FUcD3fTtb82NKu8QOAHVda+srSRJt2E7gtpQKIXA4HH6csmzpyxj4dDo9TalSCpRS1HX9TV86RujSlxGwlBJpmnY+rJRCGIZ/s2BTX9qnZgBwHAee52mJ/l7nx+PRqr6MVtj3fZRlaVRf/5aGDX0Z17DrusiyrHfqhuqrt9aiKEIcx4OBTfU1aOMIggBlWYIQ0utP+uhr8CyxXC5RFIUxdBAE1srKePgxbcbVamWlnAZNa7rNSAhBlmWv8yLlWTPa0Nco83BbM2ZZZsUIowzwj80YxzEWi8VoB4IPGz9yb0YhBHa73agnGGtHJNd1R4ed9FVV33Awf6ebgd8b+Av9A/rq6s3hjgAAAABJRU5ErkJggg=='); + background-size: 11px 11px; + background-position: top 5px left 14px; +} +.multiselect-options input[type=checkbox]:enabled + label:hover { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goSADUOYnF4LQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAIMSURBVFjD7ZmxkqowFIZ/7mwJPen1AezV3t6hFvrQhweAHvrQ8wL2xt4HwD7ppd+tvHOvu0gCYdEZTsmAfpNzzpcTcAB84o3iD94sZuCx4+Pxwvl8dl4JcL1ef84lMQPPwBZDSgkp5XsASylBKUUYhhBCvDbw7XYDpRRKKTRNA8YYOOevC5ymKZRS/13jnHdCTwLMOW8tAc45GGNomuY1gKuq6lxFIQQopdMDXy4X5HmudW8URdMCSynBGNOG3Ww20wHf9dVWl4+wbav7a8CMsW9G+Cm22+1T2F8BzvMc1+u18z5CCJIkseNhKSX2+z2qqjLWl84zhBAURQHXde0A31Oa57nWbqSrLwDwPA9FUcD3fTtb82NKu8QOAHVda+srSRJt2E7gtpQKIXA4HH6csmzpyxj4dDo9TalSCpRS1HX9TV86RujSlxGwlBJpmnY+rJRCGIZ/s2BTX9qnZgBwHAee52mJ/l7nx+PRqr6MVtj3fZRlaVRf/5aGDX0Z17DrusiyrHfqhuqrt9aiKEIcx4OBTfU1aOMIggBlWYIQ0utP+uhr8CyxXC5RFIUxdBAE1srKePgxbcbVamWlnAZNa7rNSAhBlmWv8yLlWTPa0Nco83BbM2ZZZsUIowzwj80YxzEWi8VoB4IPGz9yb0YhBHa73agnGGtHJNd1R4ed9FVV33Awf6ebgd8b+Av9A/rq6s3hjgAAAABJRU5ErkJggg=='); +} +.multiselect-options input[type=checkbox]:checked + label { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goSADMveELP9QAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAIqSURBVFjD7ZmxkqowFIb/7GwJPfT6APZib+9QC33o4QGghz70vIC9sfcBsE966bPNWlxnlQTDRWc4JUT4hpPz5SQSAAofFF/4sJiBx47v+wun04m8E+B6vVbzlJiBZ2CLIYRQQgj1EcBCCEUpRRRF4Jyrtwa+Xq+glEJKia7rkKYpGGPqbYHzPFdSyn+uMcZ6oScBZowpzvmje0jTVHVd9x7ATdMoxtjTMZxzUErV5MDn81mVZak1No7jab+wEEKlaaoNGwQBmQz4pq9H8/IeNo5jMmnRpWmKeyP8FZvN5insfwEuy1JdLpfecb7vI8uy3tb2Szelu91ONU1jtP9jjKmmabRgq6qC4zh2VrpbSsuy1FqNdPUFAK7roqoqeJ6ntXH4Mk1pn9gBoG1bbX1lWaYN2wv8KKWcc+z3+z+7LFv6MgY+Ho9PUyqlBKUUbduqe33pGKFPX0bAQgiV53nvj6WUiKIIt2K0qS/tXTMAEELguq6W6H/nOQ6Hg1V9GX1hz/NIXdckCALtB7Vta1VfxnPYcRwURUEeNSGmYaqvwVqL45gkSfIysKm+Xlo4wjAkdV3D9/1BLxmir5d7ieVySaqqMoYOw3CwEV5ufkyLcbVaIUkSq2d1xt2abjH6vo+iKKwfLA5uL58Vow19jdIPPyrGoiisGGGUBv6+GJMkwWKxGO2M+dvGQ36LEZxztd1uRz0Qt7ZFchwHY8NOelQ1NAjm/+lm4M8G/gH2zx33BSr7jAAAAABJRU5ErkJggg=='); +} +.multiselect-options input[type=checkbox]:checked + label:hover { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goSADMveELP9QAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAIqSURBVFjD7ZmxkqowFIb/7GwJPfT6APZib+9QC33o4QGghz70vIC9sfcBsE966bPNWlxnlQTDRWc4JUT4hpPz5SQSAAofFF/4sJiBx47v+wun04m8E+B6vVbzlJiBZ2CLIYRQQgj1EcBCCEUpRRRF4Jyrtwa+Xq+glEJKia7rkKYpGGPqbYHzPFdSyn+uMcZ6oScBZowpzvmje0jTVHVd9x7ATdMoxtjTMZxzUErV5MDn81mVZak1No7jab+wEEKlaaoNGwQBmQz4pq9H8/IeNo5jMmnRpWmKeyP8FZvN5insfwEuy1JdLpfecb7vI8uy3tb2Szelu91ONU1jtP9jjKmmabRgq6qC4zh2VrpbSsuy1FqNdPUFAK7roqoqeJ6ntXH4Mk1pn9gBoG1bbX1lWaYN2wv8KKWcc+z3+z+7LFv6MgY+Ho9PUyqlBKUUbduqe33pGKFPX0bAQgiV53nvj6WUiKIIt2K0qS/tXTMAEELguq6W6H/nOQ6Hg1V9GX1hz/NIXdckCALtB7Vta1VfxnPYcRwURUEeNSGmYaqvwVqL45gkSfIysKm+Xlo4wjAkdV3D9/1BLxmir5d7ieVySaqqMoYOw3CwEV5ufkyLcbVaIUkSq2d1xt2abjH6vo+iKKwfLA5uL58Vow19jdIPPyrGoiisGGGUBv6+GJMkwWKxGO2M+dvGQ36LEZxztd1uRz0Qt7ZFchwHY8NOelQ1NAjm/+lm4M8G/gH2zx33BSr7jAAAAABJRU5ErkJggg=='); +} + +.dynamic-multiselect { + position: relative; + top: 6px; + padding-bottom: 6px; +} + +div.refresh { + box-sizing: border-box; + padding-right: 44px; +} +div.refresh input[type="button"] { + float: right; + margin-right: -44px; + position: relative; + left: 10px; +} + +.color-picker { + box-sizing: border-box; + width: 26px; + height: 26px; + border: 3px solid #2B2B2B; + cursor: pointer; +} +.color-picker:focus { + outline: none; +} +.color-picker[active="true"] { + border-color: #000; +} + +.color-picker[disabled="disabled"] { + border-color: #afafaf; +} + +.colpick { + z-index: 3; +} +.colpick[disabled="disabled"] { + display: none !important; +} + +.rgb label { + float: left; + margin-top: 10px; + margin-left: 21px; +} +.rgb label + * { + clear: both; +} + +.rgb div.legend { + float: left; + margin-top: 10px; + margin-left: 21px; +} +.rgb div.legend + * { + clear: both; +} + +.draggable-number-container { + flex: 0 1 124px; +} +.draggable-number { + position: relative; + height: 28px; + flex: 0 1 124px; + display: flex; + align-items: center; +} + +.draggable-number .text { + position: absolute; + display: inline-block; + color: #afafaf; + background-color: #252525; + font-family: FiraSans-SemiBold; + font-size: 15px; + margin: 0; + padding: 0 16px; + height: 28px; + width: 100%; + line-height: 2; + box-sizing: border-box; + z-index: 1; +} +.draggable-number .text:hover { + cursor: ew-resize; +} +.draggable-number .left-arrow, .draggable-number .right-arrow { + position: absolute; + display: inline-block; + font-family: HiFi-Glyphs; + font-size: 20px; + z-index: 2; +} +.draggable-number span:hover { + cursor: default; +} +.draggable-number .left-arrow { + top: 3px; + left: 0; + transform: rotate(180deg); +} +.draggable-number .right-arrow { + top: 3px; + right: 0; +} +.draggable-number input[type=number] { + position: absolute; + right: 0; + width: 100%; +} +.draggable-number input[type=button] { + position: absolute; + top: 0; +} +.draggable-number input::-webkit-inner-spin-button { + -webkit-appearance: none; + visibility: hidden; +} +.draggable-number.fstuple { + height: 28px; + width: 124px; + left: 12px; +} +.draggable-number.fstuple + .draggable-number.fstuple { + margin-left: 28px; +} +.draggable-number.fstuple input { + right: -10px; +} +.draggable-number.fstuple .sublabel { + position: absolute; + top: 6px; + left: -16px; + font-family: FiraSans-SemiBold; + font-size: 15px; +} + +.rect .rect-row { + margin-bottom: 8px; +} + +.row .property { + width: auto; + display: inline-block; + margin-right: 6px; +} +.row .property:last-child { + margin-right: 0; +} +.row .property input { + clear: both; + float: left; +} + +.property.texture { + display: block; +} +.property.texture input { + margin: 0.4rem 0; +} +.texture-image img { + padding: 0; + margin: 0; + width: 100%; + height: 100%; + display: none; +} +.texture-image { + display: block; + position: relative; + background-repeat: no-repeat; + background-position: center; + background-size: 100% 100%; + margin-top: 0.4rem; + height:128px; + width: 128px; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAABhNJREFUeNrsnVFy4joQRVsSCwAqBMwqsrRsIavMEkICoeAf2+8j1R5ZGDBgpLzoUDVVmTT2dc8It/paOpi3t7faOSciImVZyn6/l6qqRETEWivj8VistYPFd7ud1HUtIiLGGBmPx5JKX0RkMplIzvmPnHNijBERaS7Ef1lrB40bY1oXgH5a/ZH+8P7+LlVVycfHR/MGa60URdGcYOi4MUaKomhGaGx9EZHlcplMP2X+Ly8vPwOgLEtxzklVVVJVVfOznqAsy9YFXhuvqqq5AF/Lj+srtr7+LpV+yvz1mNF+vxcRkdVqJdZaeXp6ap1ws9m0TjibzVoj6lJ8vV6fjJdlKev1ujViU+j7t8tc8p9Op1KWpYw06L9JL0Av0r9l+jXl3nhd11JV1VE8tn5YM3PI3xjzoxVOGvyDU7zQj6s/0tGmI++amtNV087F9Wf/FnVPzRtCXz8RdV1nlb/efUbaJy4Wi0FqzjU1yRgjs9ls0Jp3jb6IyPPzczL9lPkvFot/dwCtB/om/x9oyJoXxps65NW8mPpdNTeX/JtBEtYE/+AUL/Tj6g/qA3TVnD41a6g++Bp9rYOp9FPnH80HOBcvy1I2m81D++BL+o/2AX5r/vgA+AD4AOif8AH8EdpVcy71sX3jWp/8W2AKff/TkUv+Oufr9AF0YuKc66xJ18T7eNP3nP9WfZ0EzufzJPqp8y+KQuq67vYBdETqCDpVU/rEw5oUnr+rD46h73/qUuinzh8fAP22D6AjxznXcqq6akrf+KmaFB6vf4+t7/sAelfIJf/GB9jtdmKMkdVq1dQM3zg4VVNU/NY+1Bgjh8Oh6YM1+dj6X19fzXwgp/wbH0DFtS7oyf0RdKqmhPFr+1RdseKfP7a+Px/IKX98APTbPoDOJrv60L417d54TH3V8lfS5pT/yfUA6/X6qOZcqkm3xrUm6X9CTH3fB0ihnzr/Ix9A/3T1qbfWpGvjMfX9T0UK/dT54wOg/88H8EfGPTVr6D740frhLDmn/Hv5AH1qku9t31KTzh3/aP1LPsBfzr+XDxCO0K6ack/N6qp5MfUv+QB/Of/ePsCQfWmfc6EfV3/kjzZrrRwOh9YtKHSm/LjOH3yrMTzej4c1y//51PHoP0a/tR7AOSdFURw9rz5VU049zw7jl2qWrosP++BY+iI/+wJS6afMv9kXoA6gvimsieHzZr/m6MTp3PPuc3G9SP95OPpx9JtOgT4cHwA+QCJ9+ADwAeADsC+AfQHo/4b1APAB4APAB4APAB8APgB9OD4AfAD4AFFqEnwA+AD4APgA6P86HwA+AHyAZhIBHwA+AHwA+AD04X/eB4APAB8APgB8APgA8AHow/P0AeADwAeADwAfAD4AfAD68Px8APgA8AHgA8AHgA8AH0DO70/v6lHvjaOfVn8U/iLcXx5OUML96X49vRTX3/nPw9FPo9+sB5hMJuKck+VyeVRTrLWtdfNdcf95eldNCuOfn5+tSYy/Pz+2voi0fICc8p/P5z93gJAPEN4+wufN4evaePj99eH+ePTj6p/1Abp60kt9Ksf/v46HDwAfAD6A/6gUPgD7AtgXwPP4DNcDwAeADwAfAD4AfAD4ADyPz289AHyA+Pqp84cPIPAB8AHwAfAB8AHgA7Q+HfAB4APAB4APAB+APjw3HwA+AHwA+ADwAeADwAegD8/TB4APAB8APgB8APgA8AHow/PzAeADwAeADwAfAD4AfACJ//316KfVH/mjLeb31+vx/kWhH0+/tR7AOSdFUUT9/nq9oK4+OJa+iLT25+eUf7MvIOQDxPr+en2F++PRj6PfdAr04fgA8AES6cMHgA8AH4B9AewLQP83rAeADwAfAD4AfAD4APAB6MPxAeADwAeIUpPgA8AHwAfAB0D/1/kA8AHgAzSTCPgA8AHgA8AHoA//8z4AfAD4APAB4APAB4APQB+epw8AHwA+AHwA+ADwAeAD0Ifn5wPAB4APAB8APgB8gBz5AOb19bX2TYLpdNpqQ7bbbctJGjJeVZVst9vWLSu2/vf3t+Sc/yicFIRr0C7Fu76f/lw8XBePflr9/wYAqWwWUSLcO54AAAAASUVORK5CYII='); +} +.texture-image.no-texture { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAB81JREFUeNrsnTGPm0oXht97FWm2Ch2pTEeHpUihsyvTuXO67Ta/IPkr+Qfp3MWdO7Zad0SKZDo6XIWOrTzV9xVXZ8SygGHXG4/t96lW68GGw8vMmZlzDv98+/btfyBXy780wXXzTv74/fs3rXFFfPz4kT0AoQAoAJqAAiAUAKEACAVAKABCARAKgFAAhAIgFAChAAgFQCgAQgEQCoBQAIQCIBQAoQAIBUAoAHLmvDv3C7i7u4PjOMiyDOv1+mC75XKJoiga2wRBAN/34TgOHMdBWZYoigJpmiLPcwrARhzHAQD4vg/P81pvlLRrwvM8zGYz00ZrbY5xHAe+7yPPc9zf36MsSwrAVmazGX78+DHoGM/zsFgsAAB5nmOz2ZgeQimF8XiMMAxNu+VyaQRCH8Ai8jyH4zgIw7D3MUopzOdzAECaplitVk+GB601kiTBz58/obWG4ziIoohOoI38+vULABCGYWd3X2U6nUIphbIsEcdxa7uiKPDw8GCGGtd1KQDbKMsSWZZBKYXJZNLrGN/3zdN/iDRNTdcvx1EAFqGUwmazeeIQduG6LpRSAIAsy3r9hrRjD2BxL5AkiXEI+8wetNa9PXtp13eIoQBOQJIkxmHrcgjlJkov8JKpJwVgIVpr47CFYdh6g/f7/ZM5/9CehgKwmDRNURQFlFKYTqeNN/rx8dH0AH2faBn7KYAzQKZ1QRCYZd0qf/78MX+PRqNe3ymO5W63owBsR9bwZShoGirEq++zeBQEweBZAwVwYh4eHqC1RhAErQ6jOHVdK3yu65qhJE1TDgHn5BDKTW6auxdFYdYOgiDAYrF40k4phTAM8fnzZyilUBRF54rhOfIOF06SJMYPaPt8v99jOp3C8zx4nget9bPZQ5ZlF3fzL0IAZVke9OLv7+/Njl/brCHLMozHY4xGI3z48MH0EEVRIMuyi40H+EdqBbNS6HXBSqGEAiAUAAVAE1AAhAIgFAChAAgFQCgAQgGQq+Eom0GLxeJgGHYVSdCUhM02yrI0qV5hGGIymaAsy9b0LNd1cXt7CwDYbDa98wOA/zKLVquVSQGr/nYTbe2iKDIh53JtZVmiLEvsdjtst9tn5z7EDmfXA3QFXdaTMbvYbrdm568tgkdueJ7njbt3QwJA+8YJ1tsFQQDXdXFzc2N2E0Uwk8kEX758eXbMEDtY2QOsVqtn//v69SsAYL1eH9xK7dNGgjuiKMJ4PH4WmSN7+QBMFu/3798bn1oAzz47NvVrqmYgz2azRpv1scNV+wDVaN969y6JIEmSWBmyJenlIgZbcgvOzgmUqJxqkmY18ldCvGwkz/MntQcogBcgETrVMV98Aptvfh1JTKEAXsBms4HWGp7nYT6fw3Ec5Hlufbi253lQSkFr3VqmhgLoQVmW2G63ZigQx8/2my/FKCR17WLWAV7LfD5vzOFLkqS1W0/T1HT9RVFY5/jNZjMz3ouvorVGHMet9QheYoer7AGq478Y2LaiDTc3N3Bd90megSwG2YQVPcDQ+a/ccK01ttutWSWsetl/i7bfq16TzP1lGFgul0exw9X2AJLGJV3joRXCl3rnXbUDhmQKl2WJ9XoNrbV1vdXZCUCWWqvVQGR8HFIgqmuaKUiCSJcA+nrzWmvzdA/ZN6EAKlTz/eXmA3iSuXOoNEzfBRsA+PTpU+PnUjxSfnvo9/ZNR6cAakjFj2rqd3VtQJ6u1z5h1e+SdYbqdK5aWHLImC0OoFQgpRN4YPoD/LfRVC8C2TQlkhVC3/dfVDG0/l1xHCOKIvi+b572atJoURSdtYnbfAHxV0aj0TP/oY8dzqYH6OscHXK26tO+rqcujmNTIKqtJkDfc0vTFMvl8smu436/R57niOO4NSbh0HfLkFHtpYbY4dgwOfRKYXIooQAIBUAB0AQUAKEACAVAKABCARAKgFAA5Gp4s93AKIrw/v17ExsnFEWB/X6P3W6HLMtaN0+GJkwOad+W2FlPLq3GHFSRdq85h2PYyGoByG6cvJOnHiEryZJSg7e+s1ZNmOyzSza0ffWYJsIwbMzk7Tp+6Dm81kZWC0BoCnSU7dowDE2K12q1alT60EDJYwVWKqUQRdHgPf9jnfMQG52dDyA5fLKnLlGztiB5Bn1eP3fuNvr31IaWZM9jhHIdEwk5G1Jk4hxtdPJZQJZlJrLWlnBpx3FMmrnrup3RReduIyumgXJxtryRUxw4mQXIO4Yv0UZWCMDWN3I2vX7u0mxk1RtDmp6yoQmTbe27kjK7iOMYt7e3CIIA2+22VyLIWyZ5Hrsnsmol0Jac+fo51QtSXJKNrOgBuvLsTrUOUO8FxAP3ff/gTXiLc3irt5aevAdQSpmpja0vZqq+fm4ymfz18i5vaaOTC0DSvapv8rQRmRY6joPxeHwxNjqpAGSpUwx8ikKJQ5AyNFKb4BJsdBIfwPM8BEFgFjXSNG3debMJSUv7GyuWf8tGby6Aaq2c+qvaJce/a3p2ioTJQ73A3d3di6aBbef8WhtZKQDJ6K1fTJ7neHx8PFjWTcbbvvPePm8QbVtc6ft/+UwKUdfbDT3n19roGDA59EphciihAAgFQAHQBBQAoQAIBUAoAEIBEAqAUACEAiAUAKEACAVAKABCARAKgFAAhAIgFAChAAgFQC4CkxgiceKEPQC5Iv4/APgB2O7x8IXXAAAAAElFTkSuQmCC'); +} +.texture-image.no-preview { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAA8sSURBVHhe7Z3rbxXFG8d7B9SWthRabLmIYlHkIEXKJdXYBEXxHtEXprwxxsR3/jG+8PLCaDDGeAkmKsTEoCUVKoVCA6WNtLS2UEUKBSy0tKW/D+eZM9nu7tmz55z+mC2Zz4tl9tk5c2bnO/PMM2dnS+6nn36aYzFH7vvvv6+SFhMoAY4fPy7nljvG448/zjFPTiymsAIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjmLhQgPz+/pKRk3rx56jzaRHFf0ObNmxctWkTi7Nmzp0+fFqNm+/btRUVFP/30kzp3UFtbu27duqVLl+bl3e5Y169f7+rqam1tvXnzpmSIFNHdF1RTU7M6TkNDQ0FBgbImWLVqFZfUSQKyvfzyy88991x1dfXU1NSFCxdGRkbuueeeurq6pqam0tJSlS96RNcFSQvSo9V5IC+88MIDDzwwOjr6448/fvTRR19++eVnn322Z8+ev//+u7i4+M0331ywYIHKGjGiK8Aff/zBMRaL5ebmiiUZjz322MqVK/Ez33333ZkzZxgBYh8eHt67d++lS5do/W3btokxakRXANxIf38/3mPNmjXKlARxpkeOHKGtxaIZHx9vaWkhwfTg9WZRILoCgIQG0r7JKC8vlxm7s7NTLC6YyW/cuFFYWIiPUqYoEWkB+vr6cOJLlizBwyiTB2l9vA0xj1hcTE9PDw4OkiA6EkukiLQAcOzYMY4bN26UUy8LFy7k+O+//8qpL1euXOF43333yWmkiLoATKqEQwSmlZWVyjQTIiWOwZG+npYjSNQFwIG0tbWRqK+vF4sL1r0qlZzJyUmOYXLeeaIuAHR3d+PfmQbE27hgguUY3LgS/0RzHMwBAei/R48ezcvL8x0EOCiOxEJy6osoJ1JFjTkgAHR0dExMTBDLexe0EvsTKQUMgsWLF3OUWChqzA0BGARoQBN7wyHWa6Ojo1x6+OGHlWkmaEOoeuvWrXPnzilTlJgbAgBeiEEQi8W8Pf3kyZMct27d6v0JGsf15JNPkmA5lmyhYJY5IwAenNmYBW1RUZEyJSBMYiYoLi7etWtXWVmZsubkkHPHjh2EsCjX3NysrBFjzggANDSeRJ04wEF9//33rLYqKip27979yiuvNDY2Pvvss2+//TZ+ieBn//79V69eVbkjRv6WLVv4hxW/nEcB+iyuo6ura3x8XJnicIqToV8zGpgSlDXO2NhYZ2cnV+WnIVZtTLxEn+fPn9+3b180p9+qqiqOd9ub8ihH67M8xuPT65mf1YXocXe+KY+PGhoa6unp4Rjl1tfcbQLMOawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGyfy3oNdff72mpkadJLh27Vpvb29LS8vExIRYdu7c6dpLOz09ffPmTXLypadOnVLWnJzGxsZYLKZOPHR0dDQ3N7/33nv5+fkff/yx7/PFBQsWvPPOO5T/4YcfLly4sKmpaXBw8Ntvv5Wr7777bsAOUbINDw+Th5IpX1kTyGcPHz7c2tqqTHG4NW7wzz//9N2tHczs/BY0NjZ2PQFVLy4uXr9+/UsvvaQuJxgfH1eZ4tkKCwsrKiq2b9/u3XbozOkEzaamps6ePUueZHvcsOfl5ZFHtkH4oorzQOFU7MqVKzS0S6fy8nKxeDvckiVLOGbza2u22yW/+eYbOo46ie9Te/XVV5ctW7Z8+fK//vpLWXNyfvjhB2ctaaaGhoYNGzZs3bq1q6tLWeP88ssvdCh14oFLDz30EA3tuxFRhBGRkvHJJ5+olB8XLlxg6NCs/f39ypRo93/++Wfp0qWMP+fuCnna7N2TGp5ZngMQ48iRIyQefPBBsfhy69atgwcPjo6OlpSU+G42SQaicv80tPfBJBbslBwsQDBDQ0McpVk1CMBAx2HyFa79jUhFfeRTmTH7k7DsEky5DxBPffHiRRKytS0kNMTAwAAN4d0tigX7+fPnfaeHkEjlxbFoEIAvlTFRXV0tRhBnNTIy4hwT6TL7Asgz2zBvBUlO/K+chkQc1IoVK+RUI5YzZ87IaWZIX3buMpIJAP+Jroxv5zQgOmW52WL2BZDtyv/995+cJkMeHHJX6T42wcPgZ5gJ1HkCsWTjf4C+TCuXlpZqFyctLl6etpZpIH5F6eScAjNglgVg+n3iiSdIuHoiI/f2S19xamtrN23a9NprrzEVt7W1uSKWtWvXPu2HuhzfHkF/pFfef//9ypSTQxoLPi3lw3dV3Ez4UnU5/nicJpZuBAigvTzfyyU9DWQfAkG2UdCLL76oPeC99947f/58Et3d3cQMYhTk0b8TejGhfXt7uzpPgCfxuhf49ddfVSonp6enhyhr1apVeHyxkOYYxv8QJauUA9yaXpEQCKEH8zAJThGA1pd7lLamM0mCPNhl73vGZDsCGK10FgGffvnyZZYqP//8s7qcgCY7EUemMvz+F198ceDAAaZiyaA5duwYixov6nIcaWhpdEHSfIucBqCKm4m8hSDIBhHp3URoMgHEr9wefHoaYChw71qbjMlWgK+//pp1o/DBBx98/vnnLBfp3epyAmI4ujDs3bv3t99+I/J5/vnnfd++4/7pj17U5TjohzsuKysTL8yRNM5HwqpgVHEzce7KoYlpUynZO83qaYAOxzGbFYCQrQAsXOkXgrc7+4IYuA5WwgHvvaSEVuMoKy859vb23r6QNbQ+zof2Je2cAAQ9DYhCWU4AMPtRUBhko2B9fX1aiwAnEu3IakCOYfxPSFgN4HnwP7h7xHA6GT0NyFScZQgEZgRgimYyKCwsrKurU6Y0weHIbwO0FEfGX5bxuBPp8kR0jAPX22d8EY2Oa6qqqiJt3gVlzKFDhzjGYjFaUCzpgs/BGzQ2NnJkWg7pAMMg8Y/8Wul1Mn19fUiONtl3fzAmAP0XN8IgcM0EGzZs2JkElSOBTAMsLDiGnwBUWR74XpUjvuxiJS/TgK8AdBpUz34CAGMCgPy27hoEdC5Zr3lRORIQ8krYMzExMTAwIMaUqLI8iE/XyCCgj+NnxKLRoWf2/gcyfyBDGDNv3jw6csCP70C0QPvSUq6tzgKelK5EUxJZElazlFMX/PB6efkIJXsD0IKCgsrKSuclmpi1t6S9uBy6lJzMy1My5ae892DExdn/R8wYd+fu6DmHFcAwVgDDWAEMYwUwjBXAMFYAw1gBDGMFMIwVwDBp/xSxZs2aqqqqsbGxw4cPK1PiD2W0t7cne0K9ePHitWvXXr9+Xf4aKFRWVj7yyCMkKIfSxKgpLS1lpT4yMqIrxinGU6dOBf95OGH16tXV1dWuSmrkmbs6iTM5OXnjxo2enh7560Oap+O7MZz7AVzIF6kTPwI+m+FPEbT1+vXrN2/eXFJSokzxfXAYH330UXXuYd26dWRw/uoZi8WwgPPZukYKdO5vJI0FDdR5IL6V1KxYseL2FzvYuHFjQ0NDU1OTa7uRXFUnftTU1EieZKh8yUlPALott3T58mXSiC9GkJ/mA/aDyo1JNsjPz6fdr169OjU15SxnVqioqCgrK/NW0oXefrF///4DBw5QN2r1zDPPFBcXqxyhOXnypBTlReVITnoCyP20tLS4Gq6/v58hvGjRIudfi9HIrqnR0VG9jWfZsmXz58/nnoeGhiQt9llBVxIXFCCA3n7R3d3d0dFBY3EXRUVF4hjTAq8oRXlROZKTtgATExN9fX0DAwMyGsQ+PT0te3V8b1iMztqIpbe3l6JkNIh9VtCVpEGdlUyJPOjnI3J6Z0hDALkZbozuL63pbG6vReMSQFqcEcOACPhUZoj/kUrKPonwhcvTlTDbimeRNASQt1mkp9N5uUPn+y2Dg4M4Ge7f1eOQTR4taf+zcuVKfI6UI5sbli9f7pyfs0GaWwpnmLoqGYxswwr/dHNWSEMA7o37kfdecK+4b+luchUv5NudnS0iiEU/Rmfg5+XlBb/QEZ7gSjoh0CpPwOy1adMmQrVz58653tgJAz1MFTQT79+w8xJWACZSvobeoWN2r9MXAWSfmkb8u8v/UIjuaOk6igCkrYMrqXnqqad2JyAA3bZtG8N037593n2VKamvr1cFzaS2tlblSE5YAeQenLvPpJc57w0ng0thYaL3u0mLcGN6Bwf+p7CwkOmRfiqWixcv4rsIqLP3QmEqqRkeHqZWQK8njMH1U+233nor5FLDCcs3KcpFypckIOz2dLkHhiqrG7EAlZYmlqAb6Oksaoj65W+6iWOhG+pdU1IOGjjLQSGGF5nlD1BmTMhKCq2trXpcAkOT5RuV37Fjx1dffaWs4Whvb3f9DbvwhBoBdE8aiASr5y0O5B0j519MlVvSDt21/iooKBCPxFEVEYcGwhhmwAYgrUwiZSV9YUQeOnQI31VVVZXWe4NZEkoAqT3tyIrRibwQ6Ww4Qho6mvgTmoNG4ZZ0/EO70/cZ7+rzDojc+VTGe3VBur+3kvq/MInnCgINqD+JDLxQxqQWIDc3VzoyHYSB5uT333/HfUtDS2agCYhqWN8CpxKwyiVpI/XhmUhQJBkyQz7rrWRbWxvu3lXJZMhw0RW+A6QWQLoz9+DyoYI3hmFlzxHN+CAJp/+RAMk5SWqyjIXE/ySrJOsyjikLp+OzaiEKohxl+v+TWgCpt2+rgTfOu3TpEoENrQ/OcBP/w0RHyMGUKxYnrAbod84IyheCa/K4YH4KrqSvAK6i6urq3njjDcbu6dOnXTVUOWZCf1KX48opqweZOwNIEQVp/6PXTS7w77SyDHC9C5NeT0RBorOz0+V/5PcWL5OTk0hFkEq2EydOKKsHJlWVcoCjl8KTVVJUd1XStyjmp4MHD6qTBLt27VIpB3v27NEDZUMcSbugbrhBdeJHij9dTDyAvFQrWaMQXyLS+Pj4tWvX9PAn/kV5hgJhJXYxMgLIQDm+u3SBeZgOKJM2/YuhwJSoN+SWlJTQiJTphTZlzRlQSXBWkjUwsan6cBy+iLD9+PHjzc3Nzv22RLQqhwfEphBukx6mTH6wEEn2kOru/NPFc4gMn4hZZhcrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYdS2FIsp7AgwSk7O/wCqCi/+JioQYgAAAABJRU5ErkJggg=='); +} + +.two-column { + display: table; + width: 100%; +} +.two-column > div { + display: table-cell; + width: 50%; +} + +#properties-list .fieldset .two-column { + padding-top: 10px; + display: flex; +} + +#properties-list .two-column .fieldset { + width: 50%; + margin: 0; + padding: 0; + border-top: none; + box-shadow: none; +} + +#properties-list .two-column .column { + position: relative; + top: -10px; +} + +#properties-list .two-column .fieldset div.legend { + width: 100%; + margin: 21px -21px 0 -21px; + padding: 16px 0 0 21px; + font-family: Raleway-Regular; + font-size: 12px; + color: #afafaf; + height: 10px; + text-transform: uppercase; + outline: none; +} + +#properties-list .two-column + .property { + margin-top: 6px; +} + +.fieldset .checkbox-sub-props { + margin-top: 0; +} + +.fieldset .checkbox-sub-props .property:first-child { + margin-top: 0; +} + +.column { + vertical-align: top; +} + +.indent { + margin-left: 24px; +} + +::-webkit-scrollbar { + width: 20px; + height: 10px; +} +::-webkit-scrollbar-track { + background-color: #2e2e2e; +} +#entity-table-scroll::-webkit-scrollbar-track { + border-bottom-right-radius: 7px; +} + +::-webkit-scrollbar-thumb { + background-color: #696969; + border: 2px solid #2e2e2e; + border-radius: 8px; +} + +/* FIXME: Revisit textarea resizer/corner when move to Qt 5.6 or later: see if can get resizer/corner to always be visible and +have correct background color with and without scrollbars. */ +textarea:enabled::-webkit-resizer { + background-size: 10px 10px; + background: #252525 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAXSURBVChTY1RVVWXADZigNA4wMqUZGACS3gCD5UUtKAAAAABJRU5ErkJggg==) no-repeat bottom right; +} +textarea:focus::-webkit-resizer { + background-size: 10px 10px; + background: #000000 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACOSURBVChThdC5DQQhDAXQvyRI5LQxFdABARWQElAPogYkiqEWQhLYGe8xxzJaS5a/8AuQHwDG2n+Lvee0hBDQWlO+hRvy3mNZFjDG5vCDOOeIMaL3/guPKISAWiu9n+AVSSlhraXdF86Qcw6tNdoTvEOlFOScd6iUOv3JGEMopYQx9jNvaawnoHnNr8Z4AuRLPOq2gPgnAAAAAElFTkSuQmCC) no-repeat bottom right; +} +textarea:enabled[scrolling="true"]::-webkit-resizer { + background-size: 10px 10px; + background: #2e2e2e url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACKSURBVChTjdAxDsMgDAXQT4UYuQIzCwsSKxsSJ4YDoByDY7AwUOG2aZMQqX+xhd9gzIwxA3/k8a7LCCFgraX+Fk4UY4RSCoyxNfwgzjlyzhhjXOEvSimhtUbvB3hGUkp472m2wxUKIaD3TnOCd6jWim3bvlBrfdjJOUeolEJoZj/4PMH83bl/BXgCWSs2Z09IjgoAAAAASUVORK5CYII=) no-repeat bottom right; +} + + +div#grid-section, body#entity-list-body { + padding-bottom: 0; + margin: 16px; +} + +#entity-list-header { + margin-bottom: 36px; +} + +#entity-list-header div { + display: inline-block; + width: 65px; + margin-right: 6px; +} + +#entity-list-header div input:first-child { + margin-right: 0; + float: left; + width: 33px; + border-right: 1px solid #808080; + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +#entity-list-header div input:last-child { + margin-right: 0; + float: right; + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +#delete { + float: right; + margin-right: 0; + background-color: #ff0000; +} + +#entity-list { + position: relative; /* New positioning context. */ +} + +#filter-area { + padding-right: 168px; + padding-bottom: 24px; +} + +#filter-type-multiselect-box select { + border-radius: 14.5px; + width: 107px; + height: 28px; +} +#filter-type-options { + position: absolute; + top: 48px; +} +#filter-type-options div { + position: relative; + height: 22px; +} +#filter-type-options span { + position: relative; + top: 3px; + font-family: HiFi-Glyphs; + font-size: 13px; + color: #000000; + padding-left: 6px; + padding-right: 4px; +} +#filter-type-options label { + position: absolute; + top: -20px; + z-index: 2; + height: 22px; + width: 200px; + padding-top: 1px; +} +#filter-type-options-buttons { + top: -22px; + width: 224px; + z-index: 2; + background-color: #afafaf; + padding-bottom: 6px; +} +#filter-type-options input[type=button] { + position: relative; + left: 16px; + z-index: 3; + height: 23px; + min-width: 60px; + font-size: 10px; + color: #000; + background: linear-gradient(#afafaf 20%, #808080 100%); +} +#filter-type-options input[type=button]:enabled:hover { + background: linear-gradient(#afafaf 20%, #575757 100%); +} + +#filter-search-and-icon { + position: relative; + left: 118px; + width: calc(100% - 126px); +} + +#filter-in-view { + position: absolute; + top: 0; + right: 126px; +} + +#filter-radius-and-unit { + position: relative; + float: right; + margin-right: -168px; + top: -45px; +} +#filter-radius-and-unit label { + margin-left: 2px; +} +#filter-radius-and-unit span { + position: relative; + top: 25px; + right: 9px; + z-index: 2; + font-style: italic; +} +#filter-radius-and-unit input { + width: 120px; + border-radius: 14.5px; + font-style: italic; +} +#filter-radius-and-unit input[type=number]::-webkit-inner-spin-button { + display: none; +} + +#entity-list-footer { + padding-top: 9px; +} + +#footer-text { + float: right; + padding-top: 12px; + padding-right: 22px; +} + +input[type=button]#export { + height: 38px; + width: 180px; +} + +#no-entities { + display: none; + position: absolute; + top: 80px; + padding: 12px; + font-family: FiraSans-SemiBold; + font-size: 15px; + font-style: italic; + color: #afafaf; +} + +#entity-table-columns-multiselect { + position: absolute; + top: 51px; + right: 22px; +} +#entity-table-columns-multiselect-box select { + height: 28px; + width: 20px; + background-color: #1c1c1c; + border-top-right-radius: 7px; +} +#entity-table-columns-options { + position: absolute; + top: 50px; + right: 110px; +} +#entity-table-columns-options div { + position: relative; + height: 22px; +} +#entity-table-columns-options label { + position: absolute; + top: -22px; + height: 22px; + width: 100px; + padding-top: 4px; +} +#entity-table-columns-options input[type=checkbox] + label { + padding-left: 30px; +} + +#entity-table-scroll { + /* Height is set by JavaScript. */ + width: 100%; + overflow-x: hidden; + overflow-y: auto; + box-sizing: border-box; + padding-top: 28px; /* Space for header and footer outside of scroll region. */ + margin-top: 28px; + border-left: 2px solid #575757; + border-right: 2px solid #575757; + border-bottom: 2px solid #575757; + border-bottom-left-radius: 7px; + border-bottom-right-radius: 7px; + background-color: #1c1c1c; +} + +#entity-table-scroll .glyph { + font-family: HiFi-Glyphs; + font-size: 15px; +} + +#entity-table { + margin-top: -28px; + margin-bottom: -18px; + table-layout: fixed; + border: none; + background-color: #1c1c1c; +} + +#entity-table thead tr, #entity-table thead tr th { + background: none; +} + +#entity-table .glyph { + margin: 0 -2px 0 -2px; + vertical-align: middle; +} + +#entity-table thead { + box-sizing: border-box; + border: 2px solid #575757; + border-top-left-radius: 7px; + border-top-right-radius: 7px; + border-bottom: 1px solid #575757; + position: absolute; + top: 49px; + left: 0; + width: 100%; + word-wrap: nowrap; + white-space: nowrap; + overflow: hidden; +} + +#entity-table th { + display: inline-block; + box-sizing: border-box; + padding: 5px 0 0 0; + vertical-align: middle; + overflow: hidden; + text-overflow: ellipsis; +} + +#entity-table th:focus { + outline: none; +} + +#entity-table th .glyph { + position: relative; + left: 4px; +} +#entity-table th .glyph + .sort-order { + position: relative; + left: 4px; +} + +#entity-table thead .sort-order { + display: inline-block; + width: 8px; + margin: -5px 0 -3px 0; + vertical-align: middle; +} + +#entity-table thead .resizer { + position: absolute; + top: 1px; + height: 26px; + width: 10px; + cursor: col-resize; +} + +#entity-table .dragging { + background-color: #b3ecff; +} + +#entity-table td { + box-sizing: border-box; +} +#entity-table td.glyph { + text-align: center; + padding: 0; +} + +#properties-base { + border-top: none !important; + box-shadow: none !important; + margin-bottom: 5px !important; +} + +#properties-base #property-type-icon { + font-family: HiFi-Glyphs; + font-size: 31px; + color: #00b4ef; + margin: -4px 12px -4px -2px; + width: auto; + display: none; +} + +#properties-base #property-type { + padding: 5px 24px 5px 0; + border-right: 1px solid #808080; + width: auto; + display: inline-block; +} + +#properties-base .checkbox label span { + font-family: HiFi-Glyphs; + font-size: 20px; + padding-right: 6px; + vertical-align: top; + position: relative; + top: -4px; +} + +#properties-base input[type=checkbox]:checked + label span { + color: #ffffff; +} + +#id label { + width: 24px; +} +#property-id { + display: inline-block; +} +#property-id::selection { + color: #000000; + background-color: #00b4ef; +} + +input#property-scale-button-rescale { + min-width: 50px; + left: 152px; +} +input#property-scale-button-reset { + margin-right: 0; + left: 250px; +} + +#property-userData-static, +#property-materialData-static { + display: none; + z-index: 99; + position: absolute; + width: 96%; + padding-left: 1%; + margin-top: 5px; + margin-bottom: 10px; + background-color: #2e2e2e; +} + +#property-userData-saved, +#property-materialData-saved { + margin-top: 5px; + font-size: 16px; + display: none; +} + + +#div-property-collisionSoundURL[style*="display: none"] + .property { + margin-top: 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: 4px 18px 4px 18px; + margin: 0; + white-space: nowrap; +} +.context-menu li:hover { + background-color: #e3e3e3; +} +.context-menu li.separator { + border-top: 1px solid #333333; + margin: 5px 5px; + padding: 0 0; +} +.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; +} + +.create-app-tooltip { + z-index: 100; + position: absolute; + background: #6a6a6a; + border: 1px solid black; + width: 258px; + min-height: 20px; + padding: 5px; + z-index: 100; +} + +.create-app-tooltip .create-app-tooltip-description { + font-size: 12px; + font-style: italic; + color: #ffffff; +} + +.create-app-tooltip .create-app-tooltip-js-attribute { + font-family: Raleway-SemiBold; + font-size: 11px; + color: #000000; + bottom: 0; + margin-top: 5px; +} + +#toggle-space-mode::before { + font-family: HiFi-Glyphs; + font-size: 20px; + text-transform: none; + min-width: 32px; + padding-right: 4px; + vertical-align: middle; +} + +#toggle-space-mode.space-mode-local::before { + content: "m"; +} + +#toggle-space-mode.space-mode-world::before { + content: "\e02c"; +} + +.container { + display: flex; + flex-flow: row nowrap; + margin-bottom: 8px; + min-height: 28px; +} + +.container > label { + margin-top: 6px; + width: 160px; + min-width: 160px; + max-width: 160px; +} + +.container > div.checkbox { + padding-top: 6px; +} + +.container > .value { + width: 100%; +} + +.container .row { + display: flex; + flex-flow: row nowrap; +} + +.container.shrink { + width: min-content; +} + +.fstuple { + display: flex; + flex-flow: row; +} +.fstuple input { + margin-left: 4px; + margin-right: 10px; +} +.fstuple label.red, .fstuple label.x, .fstuple label.w { + color: #C62147; +} +.fstuple label.green, .fstuple label.y, .fstuple label.h { + color: #359D85; +} +.fstuple label.blue, .fstuple label.z { + color: #0093C5; +} + +.xyz.fstuple, .pyr.fstuple { + position: relative; + left: -12px; + min-width: 50px; + width: 100px; +} + +.rgb.fstuple .tuple { + display: none; +} + +input.number-slider { + background: #575757; + border-radius: 4px; + color: white; +} + +.fstuple > div { + display: flex; + align-items: center; + justify-content: left; +} + +.flex-row { + display: flex; + flex-flow: row; +} + +.flex-column { + display: flex; + flex-flow: column; +} + +.flex-center { + align-items: center; +} + +.flex-evenly-spaced { + flex: 1; +} + +#property-serverScripts-status { + font-family: Raleway-Light; + font-size: 14px; + margin: 6px 0; + cursor: default; +} + +#property-name, #property-id { + display: flex; + width: 100%; +} + +.spacemode-hidden { + display: none; +} + +#placeholder-property-type { + min-width: 0; +} + +.collapse-icon { + cursor: pointer; +} + +#property-userData-editor.error { + border: 2px solid red; +} + +#property-userData-editorStatus { + color: white; + background-color: red; + padding: 5px; + display: none; + cursor: pointer; +} + +#property-materialData-editor.error { + border: 2px solid red; +} + +#property-materialData-editorStatus { + color: white; + background-color: red; + padding: 5px; + display: none; + cursor: pointer; +} + +input[type=number].hide-spinner::-webkit-inner-spin-button { + -webkit-appearance: none; + visibility: hidden; +} + +div.jsoneditor-menu a.jsoneditor-poweredBy { + display: none; +} diff --git a/scripts/simplifiedUI/system/html/css/hifi-style.css b/scripts/simplifiedUI/system/html/css/hifi-style.css new file mode 100644 index 0000000000..90a5b366c2 --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/hifi-style.css @@ -0,0 +1,175 @@ +/* +// hifi-style.css +// +// Created by Zach Fox on 2017-04-18 +// Copyright 2017 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 +*/ + +@font-face { + font-family: Raleway-Regular; + src: url(../../../../resources/fonts/Raleway-Regular.ttf), /* Windows production */ + url(../../../../fonts/Raleway-Regular.ttf), /* OSX production */ + url(../../../../interface/resources/fonts/Raleway-Regular.ttf); /* Development, running script in /HiFi/examples */ +} + +@font-face { + font-family: Raleway-Light; + src: url(../../../../resources/fonts/Raleway-Light.ttf), + url(../../../../fonts/Raleway-Light.ttf), + url(../../../../interface/resources/fonts/Raleway-Light.ttf); +} + +@font-face { + font-family: Raleway-Bold; + src: url(../../../../resources/fonts/Raleway-Bold.ttf), + url(../../../../fonts/Raleway-Bold.ttf), + url(../../../../interface/resources/fonts/Raleway-Bold.ttf); +} + +@font-face { + font-family: Raleway-SemiBold; + src: url(../../../../resources/fonts/Raleway-SemiBold.ttf), + url(../../../../fonts/Raleway-SemiBold.ttf), + url(../../../../interface/resources/fonts/Raleway-SemiBold.ttf); +} + +@font-face { + font-family: FiraSans-SemiBold; + src: url(../../../../resources/fonts/FiraSans-SemiBold.ttf), + url(../../../../fonts/FiraSans-SemiBold.ttf), + url(../../../../interface/resources/fonts/FiraSans-SemiBold.ttf); +} + +@font-face { + font-family: AnonymousPro-Regular; + src: url(../../../../resources/fonts/AnonymousPro-Regular.ttf), + url(../../../../fonts/AnonymousPro-Regular.ttf), + url(../../../../interface/resources/fonts/AnonymousPro-Regular.ttf); +} + +@font-face { + font-family: HiFi-Glyphs; + src: url(../../../../resources/fonts/hifi-glyphs.ttf), + url(../../../../fonts/hifi-glyphs.ttf), + url(../../../../interface/resources/fonts/hifi-glyphs.ttf); +} + +body { + color: #afafaf; + background-color: #404040; + font-family: Raleway-Regular; + font-size: 15px; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + overflow-x: hidden; + overflow-y: auto; +} + +hr { + border: none; + background: #404040 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAjSURBVBhXY1RVVf3PgARYjIyMoEwIYHRwcEBRwQSloYCBAQCwjgPMiI7W2QAAAABJRU5ErkJggg==) repeat-x top left; + padding: 1px; + -webkit-margin-before: 0; + -webkit-margin-after: 0; + -webkit-margin-start: 0; + -webkit-margin-end: 0; + width: 100%; + position: absolute; +} + +.hifi-glyph { + font-family: HiFi-Glyphs; + border: none; + //margin: -10px; + padding: 0; +} + +input[type=radio] { + width: 2em; + margin: 0; + padding: 0; + font-size: 1em; + opacity: 0; +} +input[type=radio] + label{ + display: inline-block; + margin-left: -2em; + line-height: 2em; + font-family: Raleway-SemiBold; + font-size: 14px; +} +input[type=radio] + label > span{ + display: inline-block; + width: 20px; + height: 20px; + margin: 5px; + border-radius: 50%; + background: #6B6A6B; + background-image: linear-gradient(#7D7D7D, #6B6A6B); + vertical-align: bottom; +} +input[type=radio]:checked + label > span{ + background-image: linear-gradient(#7D7D7D, #6B6A6B); +} +input[type=radio]:active + label > span, +input[type=radio]:hover + label > span{ + background-image: linear-gradient(#FFFFFF, #AFAFAF); +} +input[type=radio]:checked + label > span > span, +input[type=radio]:active + label > span > span{ + display: block; + width: 10px; + height: 10px; + margin: 3px; + border: 2px solid #36CDFF; + border-radius: 50%; + background: #00B4EF; +} + +.grayButton { + font-family: Raleway-Bold; + font-size: 13px; + color: black; + padding: 0 10px; + border-radius: 3px; + border-width: 0; + background-image: linear-gradient(#FFFFFF, #AFAFAF); + min-height: 30px; +} +.grayButton:hover { + background-image: linear-gradient(#FFFFFF, #FFFFFF); +} +.grayButton:active { + background-image: linear-gradient(#AFAFAF, #AFAFAF); +} +.grayButton:disabled { + background-image: linear-gradient(#FFFFFF, ##AFAFAF); +} +.blueButton { + font-family: Raleway-Bold; + font-size: 13px; + color: white; + padding: 0 10px; + border-radius: 3px; + border-width: 0; + background-image: linear-gradient(#00B4EF, #1080B8); + min-height: 30px; +} +.blueButton:hover { + background-image: linear-gradient(#00B4EF, #00B4EF); +} +.blueButton:active { + background-image: linear-gradient(#1080B8, #1080B8); +} +.blueButton:disabled { + background-image: linear-gradient(#FFFFFF, #AFAFAF); +} diff --git a/scripts/simplifiedUI/system/html/css/img/jsoneditor-icons.svg b/scripts/simplifiedUI/system/html/css/img/jsoneditor-icons.svg new file mode 100644 index 0000000000..1b40068aad --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/img/jsoneditor-icons.svg @@ -0,0 +1,893 @@ + + + JSON Editor Icons + + + + image/svg+xml + + JSON Editor Icons + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/html/css/img/mt-expand-hover.svg b/scripts/simplifiedUI/system/html/css/img/mt-expand-hover.svg new file mode 100644 index 0000000000..a8e84c42ad --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/img/mt-expand-hover.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/simplifiedUI/system/html/css/img/mt-expand-normal.svg b/scripts/simplifiedUI/system/html/css/img/mt-expand-normal.svg new file mode 100644 index 0000000000..aac349ebda --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/img/mt-expand-normal.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/simplifiedUI/system/html/css/img/mt-goto-hover.svg b/scripts/simplifiedUI/system/html/css/img/mt-goto-hover.svg new file mode 100644 index 0000000000..4cad54331a --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/img/mt-goto-hover.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/simplifiedUI/system/html/css/img/mt-goto-normal.svg b/scripts/simplifiedUI/system/html/css/img/mt-goto-normal.svg new file mode 100644 index 0000000000..ead63329fb --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/img/mt-goto-normal.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/simplifiedUI/system/html/css/img/mt-mute-hover.svg b/scripts/simplifiedUI/system/html/css/img/mt-mute-hover.svg new file mode 100644 index 0000000000..9a18ccd933 --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/img/mt-mute-hover.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/simplifiedUI/system/html/css/img/mt-mute-normal.svg b/scripts/simplifiedUI/system/html/css/img/mt-mute-normal.svg new file mode 100644 index 0000000000..472f03f138 --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/img/mt-mute-normal.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/simplifiedUI/system/html/css/jsoneditor.css b/scripts/simplifiedUI/system/html/css/jsoneditor.css new file mode 100644 index 0000000000..eedef60a7f --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/jsoneditor.css @@ -0,0 +1,930 @@ +/* reset styling (prevent conflicts with bootstrap, materialize.css, etc.) */ + +div.jsoneditor input { + height: auto; + border: inherit; +} + +div.jsoneditor input:focus { + border: none !important; + box-shadow: none !important; +} + +div.jsoneditor table { + border-collapse: collapse; + width: auto; +} + +div.jsoneditor td, +div.jsoneditor th { + padding: 0; + display: table-cell; + text-align: left; + vertical-align: inherit; + border-radius: inherit; +} + + +div.jsoneditor-field, +div.jsoneditor-value, +div.jsoneditor-readonly { + border: 1px solid transparent; + min-height: 16px; + min-width: 32px; + padding: 2px; + margin: 1px; + word-wrap: break-word; + float: left; +} + +/* adjust margin of p elements inside editable divs, needed for Opera, IE */ + +div.jsoneditor-field p, +div.jsoneditor-value p { + margin: 0; +} + +div.jsoneditor-value { + word-break: break-word; +} + +div.jsoneditor-readonly { + min-width: 16px; + color: red; +} + +div.jsoneditor-empty { + border-color: lightgray; + border-style: dashed; + border-radius: 2px; +} + +div.jsoneditor-field.jsoneditor-empty::after, +div.jsoneditor-value.jsoneditor-empty::after { + pointer-events: none; + color: lightgray; + font-size: 8pt; +} + +div.jsoneditor-field.jsoneditor-empty::after { + content: "field"; +} + +div.jsoneditor-value.jsoneditor-empty::after { + content: "value"; +} + +div.jsoneditor-value.jsoneditor-url, +a.jsoneditor-value.jsoneditor-url { + color: green; + text-decoration: underline; +} + +a.jsoneditor-value.jsoneditor-url { + display: inline-block; + padding: 2px; + margin: 2px; +} + +a.jsoneditor-value.jsoneditor-url:hover, +a.jsoneditor-value.jsoneditor-url:focus { + color: #ee422e; +} + +div.jsoneditor td.jsoneditor-separator { + padding: 3px 0; + vertical-align: top; + color: gray; +} + +div.jsoneditor-field[contenteditable=true]:focus, +div.jsoneditor-field[contenteditable=true]:hover, +div.jsoneditor-value[contenteditable=true]:focus, +div.jsoneditor-value[contenteditable=true]:hover, +div.jsoneditor-field.jsoneditor-highlight, +div.jsoneditor-value.jsoneditor-highlight { + background-color: #FFFFAB; + border: 1px solid yellow; + border-radius: 2px; +} + +div.jsoneditor-field.jsoneditor-highlight-active, +div.jsoneditor-field.jsoneditor-highlight-active:focus, +div.jsoneditor-field.jsoneditor-highlight-active:hover, +div.jsoneditor-value.jsoneditor-highlight-active, +div.jsoneditor-value.jsoneditor-highlight-active:focus, +div.jsoneditor-value.jsoneditor-highlight-active:hover { + background-color: #ffee00; + border: 1px solid #ffc700; + border-radius: 2px; +} + +div.jsoneditor-value.jsoneditor-string { + color: #008000; +} + +div.jsoneditor-value.jsoneditor-object, +div.jsoneditor-value.jsoneditor-array { + min-width: 16px; + color: #808080; +} + +div.jsoneditor-value.jsoneditor-number { + color: #ee422e; +} + +div.jsoneditor-value.jsoneditor-boolean { + color: #ff8c00; +} + +div.jsoneditor-value.jsoneditor-null { + color: #004ED0; +} + +div.jsoneditor-value.jsoneditor-invalid { + color: #000000; +} + +div.jsoneditor-tree button { + width: 24px; + height: 24px; + padding: 0; + margin: 0; + border: none; + cursor: pointer; + background: transparent url("img/jsoneditor-icons.svg"); +} + +div.jsoneditor-mode-view tr.jsoneditor-expandable td.jsoneditor-tree, +div.jsoneditor-mode-form tr.jsoneditor-expandable td.jsoneditor-tree { + cursor: pointer; +} + +div.jsoneditor-tree button.jsoneditor-collapsed { + background-position: 0 -48px; +} + +div.jsoneditor-tree button.jsoneditor-expanded { + background-position: 0 -72px; +} + +div.jsoneditor-tree button.jsoneditor-contextmenu { + background-position: -48px -72px; +} + +div.jsoneditor-tree button.jsoneditor-contextmenu:hover, +div.jsoneditor-tree button.jsoneditor-contextmenu:focus, +div.jsoneditor-tree button.jsoneditor-contextmenu.jsoneditor-selected, +tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu { + background-position: -48px -48px; +} + +div.jsoneditor-tree *:focus { + outline: none; +} + +div.jsoneditor-tree button:focus { + /* TODO: nice outline for buttons with focus + outline: #97B0F8 solid 2px; + box-shadow: 0 0 8px #97B0F8; + */ + background-color: #f5f5f5; + outline: #e5e5e5 solid 1px; +} + +div.jsoneditor-tree button.jsoneditor-invisible { + visibility: hidden; + background: none; +} + +#userdata-editor{ + height:100%; +} + +div.jsoneditor { + color: #1A1A1A; + border: 1px solid #2e2e2e; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + width: 100%; + overflow: hidden; + position: relative; + padding: 0; + line-height: 100%; +} + +div.jsoneditor-tree table.jsoneditor-tree { + border-collapse: collapse; + border-spacing: 0; + width: 100%; + margin: 0; +} + +div.jsoneditor-outer { + width: 100%; + margin: -35px 0 0 0; + padding: 0 0 0 0; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + overflow-y: auto; +} + +.ace-jsoneditor { + min-height: 150px; + height: auto !important; +} + +div.jsoneditor-tree { + width: 100%; + position: relative; +} + +textarea.jsoneditor-text { + width: 100%; + margin: 0; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + outline-width: 0; + border: none; + background-color: white; + resize: none; +} + +tr.jsoneditor-highlight, +tr.jsoneditor-selected { + background-color: #e6e6e6; +} + +tr.jsoneditor-selected button.jsoneditor-dragarea, +tr.jsoneditor-selected button.jsoneditor-contextmenu { + visibility: hidden; +} + +tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-dragarea, +tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu { + visibility: visible; +} + +div.jsoneditor-tree button.jsoneditor-dragarea { + background: url("img/jsoneditor-icons.svg") -72px -72px; + cursor: move; +} + +div.jsoneditor-tree button.jsoneditor-dragarea:hover, +div.jsoneditor-tree button.jsoneditor-dragarea:focus, +tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-dragarea { + background-position: -72px -48px; +} + +div.jsoneditor tr, +div.jsoneditor th, +div.jsoneditor td { + padding: 0; + margin: 0; + overflow: visible; +} + +div.jsoneditor td { + vertical-align: top; +} + +div.jsoneditor td.jsoneditor-tree { + vertical-align: top; +} + +div.jsoneditor-field, +div.jsoneditor-value, +div.jsoneditor td, +div.jsoneditor th, +div.jsoneditor textarea, +.jsoneditor-schema-error { + font-family: droid sans mono, consolas, monospace, courier new, courier, sans-serif; + font-size: 10pt; + color: grey; +} + +/* popover */ + +.jsoneditor-schema-error { + cursor: default; + display: inline-block; + /*font-family: arial, sans-serif;*/ + height: 24px; + line-height: 24px; + position: relative; + text-align: center; + width: 24px; +} + +div.jsoneditor-tree .jsoneditor-schema-error { + width: 24px; + height: 24px; + padding: 0; + margin: 0 4px 0 0; + background: url("img/jsoneditor-icons.svg") -168px -48px; +} + +.jsoneditor-schema-error .jsoneditor-popover { + background-color: #4c4c4c; + border-radius: 3px; + box-shadow: 0 0 5px rgba(0,0,0,0.4); + color: #fff; + display: none; + padding: 7px 10px; + position: absolute; + width: 200px; + z-index: 4; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-above { + bottom: 32px; + left: -98px; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-below { + top: 32px; + left: -98px; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-left { + top: -7px; + right: 32px; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-right { + top: -7px; + left: 32px; +} + +.jsoneditor-schema-error .jsoneditor-popover:before { + border-right: 7px solid transparent; + border-left: 7px solid transparent; + content: ''; + display: block; + left: 50%; + margin-left: -7px; + position: absolute; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-above:before { + border-top: 7px solid #4c4c4c; + bottom: -7px; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-below:before { + border-bottom: 7px solid #4c4c4c; + top: -7px; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-left:before { + border-left: 7px solid #4c4c4c; + border-top: 7px solid transparent; + border-bottom: 7px solid transparent; + content: ''; + top: 19px; + right: -14px; + left: inherit; + margin-left: inherit; + margin-top: -7px; + position: absolute; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-right:before { + border-right: 7px solid #4c4c4c; + border-top: 7px solid transparent; + border-bottom: 7px solid transparent; + content: ''; + top: 19px; + left: -14px; + margin-left: inherit; + margin-top: -7px; + position: absolute; +} + +.jsoneditor-schema-error:hover .jsoneditor-popover, +.jsoneditor-schema-error:focus .jsoneditor-popover { + display: block; + -webkit-animation: fade-in .3s linear 1, move-up .3s linear 1; + -moz-animation: fade-in .3s linear 1, move-up .3s linear 1; + -ms-animation: fade-in .3s linear 1, move-up .3s linear 1; +} + +@-webkit-keyframes fade-in { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@-moz-keyframes fade-in { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@-ms-keyframes fade-in { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +/*@-webkit-keyframes move-up {*/ + +/*from { bottom: 24px; }*/ + +/*to { bottom: 32px; }*/ + +/*}*/ + +/*@-moz-keyframes move-up {*/ + +/*from { bottom: 24px; }*/ + +/*to { bottom: 32px; }*/ + +/*}*/ + +/*@-ms-keyframes move-up {*/ + +/*from { bottom: 24px; }*/ + +/*to { bottom: 32px; }*/ + +/*}*/ + +/* JSON schema errors displayed at the bottom of the editor in mode text and code */ + +.jsoneditor .jsoneditor-text-errors { + width: 100%; + border-collapse: collapse; + background-color: #ffef8b; + border-top: 1px solid #ffd700; +} + +.jsoneditor .jsoneditor-text-errors td { + padding: 3px 6px; + vertical-align: middle; +} + +.jsoneditor-text-errors .jsoneditor-schema-error { + border: none; + width: 24px; + height: 24px; + padding: 0; + margin: 0 4px 0 0; + background: url("img/jsoneditor-icons.svg") -168px -48px; +} +/* ContextMenu - main menu */ + +div.jsoneditor-contextmenu-root { + position: relative; + width: 0; + height: 0; +} + +div.jsoneditor-contextmenu { + position: absolute; + box-sizing: content-box; + z-index: 998; +} + +div.jsoneditor-contextmenu ul, +div.jsoneditor-contextmenu li { + box-sizing: content-box; +} + +div.jsoneditor-contextmenu ul { + position: relative; + left: 0; + top: 0; + width: 124px; + background: white; + border: 1px solid #d3d3d3; + box-shadow: 2px 2px 12px rgba(128, 128, 128, 0.3); + list-style: none; + margin: 0; + padding: 0; +} + +div.jsoneditor-contextmenu ul li button { + padding: 0; + margin: 0; + width: 124px; + height: 24px; + border: none; + cursor: pointer; + color: #4d4d4d; + background: transparent; + font-size: 10pt; + font-family: arial, sans-serif; + box-sizing: border-box; + line-height: 26px; + text-align: left; +} + +/* Fix button padding in firefox */ + +div.jsoneditor-contextmenu ul li button::-moz-focus-inner { + padding: 0; + border: 0; +} + +div.jsoneditor-contextmenu ul li button:hover, +div.jsoneditor-contextmenu ul li button:focus { + color: #1a1a1a; + background-color: #f5f5f5; + outline: none; +} + +div.jsoneditor-contextmenu ul li button.jsoneditor-default { + width: 92px; +} + +div.jsoneditor-contextmenu ul li button.jsoneditor-expand { + float: right; + width: 32px; + height: 24px; + border-left: 1px solid #e5e5e5; +} + +div.jsoneditor-contextmenu div.jsoneditor-icon { + float: left; + width: 24px; + height: 24px; + border: none; + padding: 0; + margin: 0; + background-image: url("img/jsoneditor-icons.svg"); +} + +div.jsoneditor-contextmenu ul li button div.jsoneditor-expand { + float: right; + width: 24px; + height: 24px; + padding: 0; + margin: 0 4px 0 0; + background: url("img/jsoneditor-icons.svg") 0 -72px; + opacity: 0.4; +} + +div.jsoneditor-contextmenu ul li button:hover div.jsoneditor-expand, +div.jsoneditor-contextmenu ul li button:focus div.jsoneditor-expand, +div.jsoneditor-contextmenu ul li.jsoneditor-selected div.jsoneditor-expand, +div.jsoneditor-contextmenu ul li button.jsoneditor-expand:hover div.jsoneditor-expand, +div.jsoneditor-contextmenu ul li button.jsoneditor-expand:focus div.jsoneditor-expand { + opacity: 1; +} + +div.jsoneditor-contextmenu div.jsoneditor-separator { + height: 0; + border-top: 1px solid #e5e5e5; + padding-top: 5px; + margin-top: 5px; +} + +div.jsoneditor-contextmenu button.jsoneditor-remove > div.jsoneditor-icon { + background-position: -24px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-remove:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-remove:focus > div.jsoneditor-icon { + background-position: -24px 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-append > div.jsoneditor-icon { + background-position: 0 -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-append:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-append:focus > div.jsoneditor-icon { + background-position: 0 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-insert > div.jsoneditor-icon { + background-position: 0 -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-insert:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-insert:focus > div.jsoneditor-icon { + background-position: 0 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-duplicate > div.jsoneditor-icon { + background-position: -48px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-duplicate:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-duplicate:focus > div.jsoneditor-icon { + background-position: -48px 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-sort-asc > div.jsoneditor-icon { + background-position: -168px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-sort-asc:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-sort-asc:focus > div.jsoneditor-icon { + background-position: -168px 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-sort-desc > div.jsoneditor-icon { + background-position: -192px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-sort-desc:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-sort-desc:focus > div.jsoneditor-icon { + background-position: -192px 0; +} + +/* ContextMenu - sub menu */ + +div.jsoneditor-contextmenu ul li button.jsoneditor-selected, +div.jsoneditor-contextmenu ul li button.jsoneditor-selected:hover, +div.jsoneditor-contextmenu ul li button.jsoneditor-selected:focus { + color: white; + background-color: #ee422e; +} + +div.jsoneditor-contextmenu ul li { + overflow: hidden; +} + +div.jsoneditor-contextmenu ul li ul { + display: none; + position: relative; + left: -10px; + top: 0; + border: none; + box-shadow: inset 0 0 10px rgba(128, 128, 128, 0.5); + padding: 0 10px; + /* TODO: transition is not supported on IE8-9 */ + -webkit-transition: all 0.3s ease-out; + -moz-transition: all 0.3s ease-out; + -o-transition: all 0.3s ease-out; + transition: all 0.3s ease-out; +} + + + +div.jsoneditor-contextmenu ul li ul li button { + padding-left: 24px; + animation: all ease-in-out 1s; +} + +div.jsoneditor-contextmenu ul li ul li button:hover, +div.jsoneditor-contextmenu ul li ul li button:focus { + background-color: #f5f5f5; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-string > div.jsoneditor-icon { + background-position: -144px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-string:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-string:focus > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-string.jsoneditor-selected > div.jsoneditor-icon { + background-position: -144px 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-auto > div.jsoneditor-icon { + background-position: -120px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-auto:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-auto:focus > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-auto.jsoneditor-selected > div.jsoneditor-icon { + background-position: -120px 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-object > div.jsoneditor-icon { + background-position: -72px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-object:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-object:focus > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-object.jsoneditor-selected > div.jsoneditor-icon { + background-position: -72px 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-array > div.jsoneditor-icon { + background-position: -96px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-array:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-array:focus > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-array.jsoneditor-selected > div.jsoneditor-icon { + background-position: -96px 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-modes > div.jsoneditor-icon { + background-image: none; + width: 6px; +} +div.jsoneditor-menu { + width: 100%; + height: 35px; + padding: 2px; + margin: 0; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + color: white; + background-color: #2e2e2e; + border-bottom: 1px solid #2e2e2e; +} + +div.jsoneditor-menu > button, +div.jsoneditor-menu > div.jsoneditor-modes > button { + width: 26px; + height: 26px; + margin: 2px; + padding: 0; + border-radius: 2px; + border: 1px solid transparent; + background: transparent url("img/jsoneditor-icons.svg"); + color: white; + opacity: 0.8; + font-family: arial, sans-serif; + font-size: 10pt; + float: left; +} + +div.jsoneditor-menu > button:hover, +div.jsoneditor-menu > div.jsoneditor-modes > button:hover { + background-color: rgba(255,255,255,0.2); + border: 1px solid rgba(255,255,255,0.4); +} + +div.jsoneditor-menu > button:focus, +div.jsoneditor-menu > button:active, +div.jsoneditor-menu > div.jsoneditor-modes > button:focus, +div.jsoneditor-menu > div.jsoneditor-modes > button:active { + background-color: rgba(255,255,255,0.3); +} + +div.jsoneditor-menu > button:disabled, +div.jsoneditor-menu > div.jsoneditor-modes > button:disabled { + opacity: 0.5; +} + +div.jsoneditor-menu > button.jsoneditor-collapse-all { + background-position: 0 -96px; +} + +div.jsoneditor-menu > button.jsoneditor-expand-all { + background-position: 0 -120px; +} + +div.jsoneditor-menu > button.jsoneditor-undo { + background-position: -24px -96px; +} + +div.jsoneditor-menu > button.jsoneditor-undo:disabled { + background-position: -24px -120px; +} + +div.jsoneditor-menu > button.jsoneditor-redo { + background-position: -48px -96px; +} + +div.jsoneditor-menu > button.jsoneditor-redo:disabled { + background-position: -48px -120px; +} + +div.jsoneditor-menu > button.jsoneditor-compact { + background-position: -72px -96px; +} + +div.jsoneditor-menu > button.jsoneditor-format { + background-position: -72px -120px; +} + +div.jsoneditor-menu > div.jsoneditor-modes { + display: inline-block; + float: left; +} + +div.jsoneditor-menu > div.jsoneditor-modes > button { + background-image: none; + width: auto; + padding-left: 6px; + padding-right: 6px; +} + +div.jsoneditor-menu > button.jsoneditor-separator, +div.jsoneditor-menu > div.jsoneditor-modes > button.jsoneditor-separator { + margin-left: 10px; +} + +div.jsoneditor-menu a { + font-family: arial, sans-serif; + font-size: 10pt; + color: white; + opacity: 0.8; + vertical-align: middle; +} + +div.jsoneditor-menu a:hover { + opacity: 1; +} + +div.jsoneditor-menu a.jsoneditor-poweredBy { + font-size: 8pt; + position: absolute; + right: 0; + top: 0; + padding: 10px; +} +table.jsoneditor-search input, +table.jsoneditor-search div.jsoneditor-results { + font-family: arial, sans-serif; + font-size: 10pt; + color: #1A1A1A; + background: transparent; + /* For Firefox */ +} + +table.jsoneditor-search div.jsoneditor-results { + color: white; + padding-right: 5px; + line-height: 24px; +} + +table.jsoneditor-search { + position: absolute; + right: 4px; + top: 4px; + border-collapse: collapse; + border-spacing: 0; +} + +table.jsoneditor-search div.jsoneditor-frame { + border: 1px solid transparent; + background-color: white; + padding: 0 2px; + margin: 0; +} + +table.jsoneditor-search div.jsoneditor-frame table { + border-collapse: collapse; +} + +table.jsoneditor-search input { + width: 120px; + border: none; + outline: none; + margin: 1px; + line-height: 20px; +} + +table.jsoneditor-search button { + width: 16px; + height: 24px; + padding: 0; + margin: 0; + border: none; + background: url("img/jsoneditor-icons.svg"); + vertical-align: top; +} + +table.jsoneditor-search button:hover { + background-color: transparent; +} + +table.jsoneditor-search button.jsoneditor-refresh { + width: 18px; + background-position: -99px -73px; +} + +table.jsoneditor-search button.jsoneditor-next { + cursor: pointer; + background-position: -124px -73px; +} + +table.jsoneditor-search button.jsoneditor-next:hover { + background-position: -124px -49px; +} + +table.jsoneditor-search button.jsoneditor-previous { + cursor: pointer; + background-position: -148px -73px; + margin-right: 2px; +} + +table.jsoneditor-search button.jsoneditor-previous:hover { + background-position: -148px -49px; +} diff --git a/scripts/simplifiedUI/system/html/css/marketplaces.css b/scripts/simplifiedUI/system/html/css/marketplaces.css new file mode 100644 index 0000000000..04c132eab1 --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/marketplaces.css @@ -0,0 +1,224 @@ +/* +// +// Copyright 2016 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 +*/ + +/* + CSS rules copied from edit-style.css. + Edit-style.css is not used in its entirety because don't want custom scrollbars; default scrollbar styling is used in order + to match other marketplace pages. +*/ + +@font-face { + font-family: Raleway-Regular; + src: url(../../../../resources/fonts/Raleway-Regular.ttf), /* Windows production */ + url(../../../../fonts/Raleway-Regular.ttf), /* OSX production */ + url(../../../../interface/resources/fonts/Raleway-Regular.ttf); /* Development, running script in /HiFi/examples */ +} + +@font-face { + font-family: Raleway-Bold; + src: url(../../../../resources/fonts/Raleway-Bold.ttf), + url(../../../../fonts/Raleway-Bold.ttf), + url(../../../../interface/resources/fonts/Raleway-Bold.ttf); +} + +@font-face { + font-family: Raleway-SemiBold; + src: url(../../../../resources/fonts/Raleway-SemiBold.ttf), + url(../../../../fonts/Raleway-SemiBold.ttf), + url(../../../../interface/resources/fonts/Raleway-SemiBold.ttf); +} + +@font-face { + font-family: FiraSans-SemiBold; + src: url(../../../../resources/fonts/FiraSans-SemiBold.ttf), + url(../../../../fonts/FiraSans-SemiBold.ttf), + url(../../../../interface/resources/fonts/FiraSans-SemiBold.ttf); +} + +* { + margin: 0; + padding: 0; +} + +body { + padding: 21px 21px 21px 21px; + + color: #afafaf; + background-color: #404040; + font-family: Raleway-Regular; + font-size: 15px; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + overflow-x: hidden; + overflow-y: auto; +} + +input[type=button] { + font-family: Raleway-Bold; + font-size: 13px; + text-transform: uppercase; + vertical-align: top; + height: 28px; + min-width: 120px; + padding: 0px 18px; + margin-right: 6px; + border-radius: 5px; + border: none; + color: #fff; + background-color: #000; + background: linear-gradient(#343434 20%, #000 100%); + cursor: pointer; +} + +input[type=button].blue { + color: #fff; + background-color: #1080b8; + background: linear-gradient(#00b4ef 20%, #1080b8 100%); +} + + +/* + Marketplaces-specific CSS. +*/ + +body { + background: white; + padding: 0 0 0 0; + font-family:Raleway-SemiBold; +} +.marketplaces-container { + display: inline-block; + color: black; + width: 94%; + margin-left: 3%; + height: 100%; +} +.marketplaces-title { + margin-top: 45px; + margin-bottom: 20px; +} +.marketplaces-intro-text { + margin-bottom: 30px; +} +.marketplace-tile { + float:left; + width: 100%; + margin-bottom: 25px; +} +.marketplace-tile-first-column { + text-align: center; + float: left; + width: 33%; +} +.marketplace-tile-second-column { + float: left; + margin-left:4%; + width: 62%; +} +.exploreButton { + font-size: 16px !important; + width: 200px !important; + height: 45px !important; + margin-top: 20px; + margin-bottom: 30px; +} +.tile-divider { + width: 100%; + margin-left: 0%; + display: block; + height: 1px; + border: 0; + border-top: 1px solid lightgrey; + margin: 1em 0; + padding: 0; + margin-bottom: 30px; +} +.marketplace-tile-description { + margin-top: 15px; + margin-bottom: 30px; +} +.marketplace-tile-image { + margin-top:15px; + max-width: 256px; + height: 128px; + margin-bottom:60px; + -webkit-box-shadow: -1px 4px 16px 0px rgba(0, 0, 0, 0.48); + -moz-box-shadow: -1px 4px 16px 0px rgba(0, 0, 0, 0.48); + box-shadow: -1px 4px 16px 0px rgba(0, 0, 0, 0.48); +} +.marketplace-clara-steps { + padding-left: 15px; +} +.marketplace-clara-steps > li { + margin-top: 5px; +} + +#marketplace-navigation { + width: 100%; + height: 50px; + background: #00b4ef; + position: fixed; + bottom: 0; +} +#marketplace-navigation .glyph { + /* + // Target look but can't use font in injected script. + font-family: HiFi-Glyphs; + font-size: 40px; + margin-left: 20px; + */ + font-family: sans-serif; + font-size: 24px; + margin-left: 20px; + margin-right: 3px; + color: #fff; + line-height: 50px; +} +#marketplace-navigation .text { + color: #fff; + font-size: 18px; + line-height: 50px; + vertical-align: top; + position: relative; + top: 1px; +} +#marketplace-navigation input { + position: absolute; + right: 20px; + margin-top: 12px; + padding-left: 15px; + padding-right: 15px; +} + +@media (max-width:768px) { + .marketplace-tile-first-column { + float: left; + width: 100%; + } + .marketplace-tile-second-column { + float: left; + width: 100%; + } + .exploreButton-holder { + width:100%; + text-align:center; + } + .tile-divider { + width: 100%; + margin-left: 0; + } + .marketplace-tile-image { + margin-bottom: 15px; + } +} diff --git a/scripts/simplifiedUI/system/html/css/miniTablet.css b/scripts/simplifiedUI/system/html/css/miniTablet.css new file mode 100644 index 0000000000..7598332d28 --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/miniTablet.css @@ -0,0 +1,92 @@ +/* +miniTablet.css + +Created by David Rowe on 20 Aug 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 +*/ + +* { + box-sizing: border-box; + padding: 0; + margin: 0; + user-select: none; +} + +html { + background-color: #404040; +} + +body { + height: 100%; +} + +section { + background-color: #404040; + position: relative; + padding: 16px 16px; +} + +.button { + width: 116px; + height: 84px; + margin-top: 16px; + text-align: center; +} + + .button:first-child { + margin-top: 0; + } + +img { + width: 40px; +} + +#mute { + padding-top: 19px; + background-size: 100% 100%; + background-image: url("./img/mt-mute-normal.svg"); +} + + #mute:hover { + background-image: url("./img/mt-mute-hover.svg"); + } + +#goto { + padding-top: 19px; + background-size: 100% 100%; + background-image: url("./img/mt-goto-normal.svg"); +} + + #goto:hover { + background-image: url("./img/mt-goto-hover.svg"); + } + + #goto:hover.unhover { + background-image: url("./img/mt-goto-normal.svg"); + } + +#expand { + position: absolute; + right: 1px; + bottom: -1px; + width: 50px; + height: 50px; + background-size: 100% 100%; + background-image: url("./img/mt-expand-normal.svg"); +} + + #expand:hover { + background-image: url("./img/mt-expand-hover.svg"); + } + + #expand:hover.unhover { + background-image: url("./img/mt-expand-normal.svg"); + } + + #expand img { + width:34px; + margin-top: 7px; + } diff --git a/scripts/simplifiedUI/system/html/entityList.html b/scripts/simplifiedUI/system/html/entityList.html new file mode 100644 index 0000000000..986e5c09b0 --- /dev/null +++ b/scripts/simplifiedUI/system/html/entityList.html @@ -0,0 +1,93 @@ + + + + Entity List + + + + + + + + + + + + + +
+ +
+ + +
+ + + +
+
+
+
+
+ +
+
+
+ +
+ + +
+
+
+
+ Y +
+ +
+ + +
+
+
+ + + + +
+
+
+ +
+
+
+ +
+
+
+ There are no entities to display. Please check your filters or create an entity to begin. +
+
+
+ + + diff --git a/scripts/simplifiedUI/system/html/entityProperties.html b/scripts/simplifiedUI/system/html/entityProperties.html new file mode 100644 index 0000000000..67f03a33a2 --- /dev/null +++ b/scripts/simplifiedUI/system/html/entityProperties.html @@ -0,0 +1,51 @@ + + + + + Properties + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+ + + diff --git a/scripts/simplifiedUI/system/html/gridControls.html b/scripts/simplifiedUI/system/html/gridControls.html new file mode 100644 index 0000000000..8d6ee34bc0 --- /dev/null +++ b/scripts/simplifiedUI/system/html/gridControls.html @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+
+
+
+ +
+ + +
+
+
+ + diff --git a/scripts/simplifiedUI/system/html/img/blast_icon.svg b/scripts/simplifiedUI/system/html/img/blast_icon.svg new file mode 100644 index 0000000000..31df8e7f53 --- /dev/null +++ b/scripts/simplifiedUI/system/html/img/blast_icon.svg @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/scripts/simplifiedUI/system/html/img/blocks-tile.png b/scripts/simplifiedUI/system/html/img/blocks-tile.png new file mode 100644 index 0000000000..49de535c1c Binary files /dev/null and b/scripts/simplifiedUI/system/html/img/blocks-tile.png differ diff --git a/scripts/simplifiedUI/system/html/img/button-snap-print.svg b/scripts/simplifiedUI/system/html/img/button-snap-print.svg new file mode 100644 index 0000000000..d1570711d7 --- /dev/null +++ b/scripts/simplifiedUI/system/html/img/button-snap-print.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/html/img/clara-tile.png b/scripts/simplifiedUI/system/html/img/clara-tile.png new file mode 100644 index 0000000000..ae431dd510 Binary files /dev/null and b/scripts/simplifiedUI/system/html/img/clara-tile.png differ diff --git a/scripts/simplifiedUI/system/html/img/expand.svg b/scripts/simplifiedUI/system/html/img/expand.svg new file mode 100644 index 0000000000..f57e624374 --- /dev/null +++ b/scripts/simplifiedUI/system/html/img/expand.svg @@ -0,0 +1,85 @@ + + + +image/svg+xml + + + + + + + + + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/html/img/fb_icon.svg b/scripts/simplifiedUI/system/html/img/fb_icon.svg new file mode 100644 index 0000000000..6d67d17bb2 --- /dev/null +++ b/scripts/simplifiedUI/system/html/img/fb_icon.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/scripts/simplifiedUI/system/html/img/hifi-marketplace-tile.png b/scripts/simplifiedUI/system/html/img/hifi-marketplace-tile.png new file mode 100644 index 0000000000..9a95c081a0 Binary files /dev/null and b/scripts/simplifiedUI/system/html/img/hifi-marketplace-tile.png differ diff --git a/scripts/simplifiedUI/system/html/img/hifi_icon.svg b/scripts/simplifiedUI/system/html/img/hifi_icon.svg new file mode 100644 index 0000000000..acbb98a3b3 --- /dev/null +++ b/scripts/simplifiedUI/system/html/img/hifi_icon.svg @@ -0,0 +1,19 @@ + + + + + + + diff --git a/scripts/simplifiedUI/system/html/img/loader.gif b/scripts/simplifiedUI/system/html/img/loader.gif new file mode 100644 index 0000000000..c464703c84 Binary files /dev/null and b/scripts/simplifiedUI/system/html/img/loader.gif differ diff --git a/scripts/simplifiedUI/system/html/img/snapshotIcon.png b/scripts/simplifiedUI/system/html/img/snapshotIcon.png new file mode 100644 index 0000000000..5cb2742a32 Binary files /dev/null and b/scripts/simplifiedUI/system/html/img/snapshotIcon.png differ diff --git a/scripts/simplifiedUI/system/html/img/twitter_icon.svg b/scripts/simplifiedUI/system/html/img/twitter_icon.svg new file mode 100644 index 0000000000..0393d963f2 --- /dev/null +++ b/scripts/simplifiedUI/system/html/img/twitter_icon.svg @@ -0,0 +1,12 @@ + + + + + + diff --git a/scripts/simplifiedUI/system/html/js/SnapshotReview.js b/scripts/simplifiedUI/system/html/js/SnapshotReview.js new file mode 100644 index 0000000000..1e8be9d644 --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/SnapshotReview.js @@ -0,0 +1,814 @@ +/*jslint browser:true */ +/*jslint maxlen: 180*/ +"use strict"; +// +// SnapshotReview.js +// scripts/system/html/js/ +// +// Created by Howard Stearns 8/22/2016 +// Copyright 2016 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 +// + +var paths = []; +var idCounter = 0; +var imageCount = 0; +var blastShareText = "Blast to my Connections", + blastAlreadySharedText = "Already Blasted to Connections", + hifiShareText = "Share to Snaps Feed", + hifiAlreadySharedText = "Already Shared to Snaps Feed", + facebookShareText = "Share to Facebook", + twitterShareText = "Share to Twitter", + shareButtonLabelTextActive = "SHARE:", + shareButtonLabelTextInactive = "SHARE"; + +function fileExtensionMatches(filePath, extension) { + return filePath.split('.').pop().toLowerCase() === extension; +} + +function showSetupInstructions() { + var snapshotImagesDiv = document.getElementById("snapshot-images"); + snapshotImagesDiv.className = "snapshotInstructions"; + snapshotImagesDiv.innerHTML = 'Snapshot Instructions' + + '
' + + '

Take and share snaps and GIFs with people in High Fidelity, Facebook, and Twitter.

' + + "

Setup Instructions

" + + "

Before you can begin taking snaps, please choose where you'd like to save snaps on your computer:

" + + '
' + + '
' + + '' + + '
'; + document.getElementById("snap-button").disabled = true; +} +function showSetupComplete() { + var snapshotImagesDiv = document.getElementById("snapshot-images"); + snapshotImagesDiv.className = "snapshotInstructions"; + snapshotImagesDiv.innerHTML = 'Snapshot Instructions' + + '
' + + '
' + + '

Snapshot location set.

' + + '

Press the big red button to take a snap!

' + + '
'; + document.getElementById("snap-button").disabled = false; +} +function showSnapshotInstructions() { + var snapshotImagesDiv = document.getElementById("snapshot-images"); + snapshotImagesDiv.className = "snapshotInstructions"; + snapshotImagesDiv.innerHTML = 'Snapshot Instructions' + + '
' + + '

Take and share snaps and GIFs with people in High Fidelity, Facebook, and Twitter.

' + + '
' + + '
' + + '

Press the big red button to take a snap!

' + + '
'; +} +function chooseSnapshotLocation() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "chooseSnapshotLocation" + })); +} +function login() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "login" + })); +} +function clearImages() { + var snapshotImagesDiv = document.getElementById("snapshot-images"); + snapshotImagesDiv.classList.remove("snapshotInstructions"); + while (snapshotImagesDiv.hasChildNodes()) { + snapshotImagesDiv.removeChild(snapshotImagesDiv.lastChild); + } + paths = []; + imageCount = 0; + idCounter = 0; +} + +function selectImageWithHelpText(selectedID, isSelected) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID + } + var imageContainer = document.getElementById(selectedID), + image = document.getElementById(selectedID + 'img'), + shareBar = document.getElementById(selectedID + "shareBar"), + helpTextDiv = document.getElementById(selectedID + "helpTextDiv"), + showShareButtonsButtonDiv = document.getElementById(selectedID + "showShareButtonsButtonDiv"), + itr, + containers = document.getElementsByClassName("shareControls"); + + if (isSelected) { + showShareButtonsButtonDiv.onclick = function () { selectImageWithHelpText(selectedID, false); }; + showShareButtonsButtonDiv.classList.remove("inactive"); + showShareButtonsButtonDiv.classList.add("active"); + + image.onclick = function () { selectImageWithHelpText(selectedID, false); }; + imageContainer.style.outline = "4px solid #00b4ef"; + imageContainer.style.outlineOffset = "-4px"; + + shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.45)"; + shareBar.style.pointerEvents = "initial"; + + helpTextDiv.style.visibility = "visible"; + + for (itr = 0; itr < containers.length; itr += 1) { + var parentID = containers[itr].id.slice(0, 2); + if (parentID !== selectedID) { + selectImageWithHelpText(parentID, false); + } + } + } else { + showShareButtonsButtonDiv.onclick = function () { selectImageWithHelpText(selectedID, true); }; + showShareButtonsButtonDiv.classList.remove("active"); + showShareButtonsButtonDiv.classList.add("inactive"); + + image.onclick = function () { selectImageWithHelpText(selectedID, true); }; + imageContainer.style.outline = "none"; + + shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.0)"; + shareBar.style.pointerEvents = "none"; + + helpTextDiv.style.visibility = "hidden"; + } +} +function selectImageToShare(selectedID, isSelected) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID + } + var imageContainer = document.getElementById(selectedID), + image = document.getElementById(selectedID + 'img'), + shareBar = document.getElementById(selectedID + "shareBar"), + showShareButtonsDots = document.getElementById(selectedID + "showShareButtonsDots"), + showShareButtonsLabel = document.getElementById(selectedID + "showShareButtonsLabel"), + shareButtonsDiv = document.getElementById(selectedID + "shareButtonsDiv"), + shareBarHelp = document.getElementById(selectedID + "shareBarHelp"), + showShareButtonsButtonDiv = document.getElementById(selectedID + "showShareButtonsButtonDiv"), + itr, + containers = document.getElementsByClassName("shareControls"); + + if (isSelected) { + showShareButtonsButtonDiv.onclick = function () { selectImageToShare(selectedID, false); }; + showShareButtonsButtonDiv.classList.remove("inactive"); + showShareButtonsButtonDiv.classList.add("active"); + + image.onclick = function () { selectImageToShare(selectedID, false); }; + imageContainer.style.outline = "4px solid #00b4ef"; + imageContainer.style.outlineOffset = "-4px"; + + shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.45)"; + shareBar.style.pointerEvents = "initial"; + + showShareButtonsDots.style.visibility = "hidden"; + showShareButtonsLabel.innerHTML = shareButtonLabelTextActive; + + shareButtonsDiv.style.visibility = "visible"; + shareBarHelp.style.visibility = "visible"; + + for (itr = 0; itr < containers.length; itr += 1) { + var parentID = containers[itr].id.slice(0, 2); + if (parentID !== selectedID) { + selectImageToShare(parentID, false); + } + } + } else { + showShareButtonsButtonDiv.onclick = function () { selectImageToShare(selectedID, true); }; + showShareButtonsButtonDiv.classList.remove("active"); + showShareButtonsButtonDiv.classList.add("inactive"); + + image.onclick = function () { selectImageToShare(selectedID, true); }; + imageContainer.style.outline = "none"; + + shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.0)"; + shareBar.style.pointerEvents = "none"; + + showShareButtonsDots.style.visibility = "visible"; + showShareButtonsLabel.innerHTML = shareButtonLabelTextInactive; + + shareButtonsDiv.style.visibility = "hidden"; + shareBarHelp.style.visibility = "hidden"; + } +} +function createShareBar(parentID, isLoggedIn, canShare, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast) { + var shareBar = document.createElement("div"), + shareBarHelpID = parentID + "shareBarHelp", + shareButtonsDivID = parentID + "shareButtonsDiv", + showShareButtonsButtonDivID = parentID + "showShareButtonsButtonDiv", + showShareButtonsDotsID = parentID + "showShareButtonsDots", + showShareButtonsLabelID = parentID + "showShareButtonsLabel", + blastToConnectionsButtonID = parentID + "blastToConnectionsButton", + shareWithEveryoneButtonID = parentID + "shareWithEveryoneButton", + facebookButtonID = parentID + "facebookButton", + twitterButtonID = parentID + "twitterButton", + shareBarInnerHTML = ''; + + shareBar.id = parentID + "shareBar"; + shareBar.className = "shareControls"; + + if (isLoggedIn) { + if (canShare) { + shareBarInnerHTML = '' + + '
' + + '' + + '' + + '' + + '
' + + '' + + ''; + + // Add onclick handler to parent DIV's img to toggle share buttons + document.getElementById(parentID + 'img').onclick = function () { selectImageToShare(parentID, true); }; + } else { + shareBarInnerHTML = '
' + + '' + + '' + + '' + + '
' + + '' + + ''; + // Add onclick handler to parent DIV's img to toggle share buttons + document.getElementById(parentID + 'img').onclick = function () { selectImageWithHelpText(parentID, true); }; + } + } else { + shareBarInnerHTML = '
' + + '' + + '' + + '' + + '
' + + '' + + ''; + // Add onclick handler to parent DIV's img to toggle share buttons + document.getElementById(parentID + 'img').onclick = function () { selectImageWithHelpText(parentID, true); }; + } + + shareBar.innerHTML = shareBarInnerHTML; + + return shareBar; +} +function appendShareBar(divID, isLoggedIn, canShare, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast) { + if (divID.id) { + divID = divID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID + } + document.getElementById(divID).appendChild(createShareBar(divID, isLoggedIn, canShare, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast)); + if (divID === "p0") { + if (isLoggedIn) { + if (canShare) { + selectImageToShare(divID, true); + } else { + selectImageWithHelpText(divID, true); + } + } else { + selectImageWithHelpText(divID, true); + } + } + if (isLoggedIn && canShare) { + if (canBlast) { + shareButtonHovered('blast', divID, false); + } else { + shareButtonHovered('hifi', divID, false); + } + } +} +function shareForUrl(selectedID) { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "shareSnapshotForUrl", + data: paths[parseInt(selectedID.substring(1), 10)] + })); +} +function addImage(image_data, isLoggedIn, canShare, isGifLoading, isShowingPreviousImages, blastButtonDisabled, hifiButtonDisabled, canBlast) { + if (!image_data.localPath) { + return; + } + var imageContainer = document.createElement("DIV"), + img = document.createElement("IMG"), + isGif = fileExtensionMatches(image_data.localPath, "gif"), + id = "p" + (isGif ? "1" : "0"); + imageContainer.id = id; + imageContainer.style.width = "95%"; + imageContainer.style.height = "240px"; + imageContainer.style.margin = "5px auto"; + imageContainer.style.display = "flex"; + imageContainer.style.justifyContent = "center"; + imageContainer.style.alignItems = "center"; + imageContainer.style.position = "relative"; + img.id = id + "img"; + img.src = image_data.localPath; + imageContainer.appendChild(img); + document.getElementById("snapshot-images").appendChild(imageContainer); + paths.push(image_data.localPath); + img.onload = function () { + if (isGif) { + imageContainer.innerHTML += 'GIF'; + } + if (!isGifLoading) { + appendShareBar(id, isLoggedIn, canShare, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast); + } + if ((!isShowingPreviousImages && ((isGif && !isGifLoading) || !isGif)) || (isShowingPreviousImages && !image_data.story_id)) { + shareForUrl(id); + } + if (isShowingPreviousImages && isLoggedIn && image_data.story_id) { + updateShareInfo(id, image_data.story_id); + } + if (isShowingPreviousImages) { + requestPrintButtonUpdate(); + } + }; + img.onerror = function () { + img.onload = null; + img.src = image_data.errorPath; + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "alertSnapshotLoadFailed" + })); + }; +} +function showConfirmationMessage(selectedID, destination) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID + } + + var opacity = 2.0, + fadeRate = 0.05, + timeBetweenFadesMS = 50, + confirmationMessageContainer = document.createElement("div"), + confirmationMessage = document.createElement("div"); + confirmationMessageContainer.className = "confirmationMessageContainer"; + + confirmationMessage.className = "confirmationMessage"; + + var socialIcon = document.createElement("img"); + switch (destination) { + case 'blast': + socialIcon.src = "img/blast_icon.svg"; + confirmationMessage.appendChild(socialIcon); + confirmationMessage.innerHTML += 'Blast Sent!'; + confirmationMessage.style.backgroundColor = "#EA4C5F"; + break; + case 'hifi': + socialIcon.src = "img/hifi_icon.svg"; + confirmationMessage.appendChild(socialIcon); + confirmationMessage.innerHTML += 'Snap Shared!'; + confirmationMessage.style.backgroundColor = "#1FC6A6"; + break; + } + + confirmationMessageContainer.appendChild(confirmationMessage); + document.getElementById(selectedID).appendChild(confirmationMessageContainer); + + setInterval(function () { + if (opacity <= fadeRate) { + confirmationMessageContainer.remove(); + } + opacity -= fadeRate; + confirmationMessageContainer.style.opacity = opacity; + }, timeBetweenFadesMS); +} +function showUploadingMessage(selectedID, destination) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID + } + + var shareBarHelp = document.getElementById(selectedID + "shareBarHelp"); + + shareBarHelp.innerHTML = 'Preparing to Share'; + shareBarHelp.classList.add("uploading"); + shareBarHelp.setAttribute("data-destination", destination); +} +function hideUploadingMessageAndMaybeShare(selectedID, storyID) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID + } + + var shareBarHelp = document.getElementById(selectedID + "shareBarHelp"), + shareBarHelpDestination = shareBarHelp.getAttribute("data-destination"); + + shareBarHelp.classList.remove("uploading"); + if (shareBarHelpDestination) { + switch (shareBarHelpDestination) { + case 'blast': + blastToConnections(selectedID, selectedID === "p1"); + shareBarHelp.innerHTML = blastAlreadySharedText; + break; + case 'hifi': + shareWithEveryone(selectedID, selectedID === "p1"); + shareBarHelp.innerHTML = hifiAlreadySharedText; + break; + case 'facebook': + var facebookButton = document.getElementById(selectedID + "facebookButton"); + window.open(facebookButton.getAttribute("href"), "_blank"); + shareBarHelp.innerHTML = facebookShareText; + // This emitWebEvent() call isn't necessary in the "hifi" and "blast" cases + // because the "removeFromStoryIDsToMaybeDelete()" call happens + // in snapshot.js when sharing with that method. + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "removeFromStoryIDsToMaybeDelete", + story_id: storyID + })); + break; + case 'twitter': + var twitterButton = document.getElementById(selectedID + "twitterButton"); + window.open(twitterButton.getAttribute("href"), "_blank"); + shareBarHelp.innerHTML = twitterShareText; + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "removeFromStoryIDsToMaybeDelete", + story_id: storyID + })); + break; + } + + shareBarHelp.setAttribute("data-destination", ""); + } +} +function updateShareInfo(containerID, storyID) { + if (containerID.id) { + containerID = containerID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID + } + var shareBar = document.getElementById(containerID + "shareBar"), + parentDiv = document.getElementById(containerID), + shareURL = "https://highfidelity.com/user_stories/" + storyID, + facebookButton = document.getElementById(containerID + "facebookButton"), + twitterButton = document.getElementById(containerID + "twitterButton"); + + parentDiv.setAttribute('data-story-id', storyID); + + facebookButton.setAttribute("target", "_blank"); + facebookButton.setAttribute("href", 'https://www.facebook.com/dialog/feed?app_id=1585088821786423&link=' + shareURL); + + twitterButton.setAttribute("target", "_blank"); + twitterButton.setAttribute("href", 'https://twitter.com/intent/tweet?text=I%20just%20took%20a%20snapshot!&url=' + shareURL + '&via=highfidelityVR&hashtags=VR,HiFi'); + + hideUploadingMessageAndMaybeShare(containerID, storyID); +} +function blastToConnections(selectedID, isGif) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID + } + + var blastToConnectionsButton = document.getElementById(selectedID + "blastToConnectionsButton"), + shareBar = document.getElementById(selectedID + "shareBar"), + shareBarHelp = document.getElementById(selectedID + "shareBarHelp"); + blastToConnectionsButton.onclick = function () { }; + + var storyID = document.getElementById(selectedID).getAttribute("data-story-id"); + + if (storyID) { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "blastToConnections", + story_id: storyID, + isGif: isGif + })); + showConfirmationMessage(selectedID, 'blast'); + blastToConnectionsButton.classList.add("disabled"); + blastToConnectionsButton.style.backgroundColor = "#000000"; + blastToConnectionsButton.style.opacity = "0.5"; + shareBarHelp.style.backgroundColor = "#000000"; + shareBarHelp.style.opacity = "0.5"; + } else { + showUploadingMessage(selectedID, 'blast'); + } +} +function shareWithEveryone(selectedID, isGif) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID + } + + var shareWithEveryoneButton = document.getElementById(selectedID + "shareWithEveryoneButton"), + shareBar = document.getElementById(selectedID + "shareBar"), + shareBarHelp = document.getElementById(selectedID + "shareBarHelp"); + shareWithEveryoneButton.onclick = function () { }; + + var storyID = document.getElementById(selectedID).getAttribute("data-story-id"); + + if (storyID) { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "shareSnapshotWithEveryone", + story_id: storyID, + isGif: isGif + })); + showConfirmationMessage(selectedID, 'hifi'); + shareWithEveryoneButton.classList.add("disabled"); + shareWithEveryoneButton.style.backgroundColor = "#000000"; + shareWithEveryoneButton.style.opacity = "0.5"; + shareBarHelp.style.backgroundColor = "#000000"; + shareBarHelp.style.opacity = "0.5"; + } else { + showUploadingMessage(selectedID, 'hifi'); + } +} +function shareButtonHovered(destination, selectedID, shouldAlsoModifyOther) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID + } + var shareBarHelp = document.getElementById(selectedID + "shareBarHelp"), + shareButtonsDiv = document.getElementById(selectedID + "shareButtonsDiv").childNodes, + itr; + + if (!shareBarHelp.classList.contains("uploading")) { + for (itr = 0; itr < shareButtonsDiv.length; itr += 1) { + shareButtonsDiv[itr].style.backgroundColor = "rgba(0, 0, 0, 0)"; + } + shareBarHelp.style.opacity = "1.0"; + switch (destination) { + case 'blast': + var blastToConnectionsButton = document.getElementById(selectedID + "blastToConnectionsButton"); + if (!blastToConnectionsButton.classList.contains("disabled")) { + shareBarHelp.style.backgroundColor = "#EA4C5F"; + shareBarHelp.style.opacity = "1.0"; + blastToConnectionsButton.style.backgroundColor = "#EA4C5F"; + blastToConnectionsButton.style.opacity = "1.0"; + shareBarHelp.innerHTML = blastShareText; + } else { + shareBarHelp.style.backgroundColor = "#000000"; + shareBarHelp.style.opacity = "0.5"; + blastToConnectionsButton.style.backgroundColor = "#000000"; + blastToConnectionsButton.style.opacity = "0.5"; + shareBarHelp.innerHTML = blastAlreadySharedText; + } + break; + case 'hifi': + var shareWithEveryoneButton = document.getElementById(selectedID + "shareWithEveryoneButton"); + if (!shareWithEveryoneButton.classList.contains("disabled")) { + shareBarHelp.style.backgroundColor = "#1FC6A6"; + shareBarHelp.style.opacity = "1.0"; + shareWithEveryoneButton.style.backgroundColor = "#1FC6A6"; + shareWithEveryoneButton.style.opacity = "1.0"; + shareBarHelp.innerHTML = hifiShareText; + } else { + shareBarHelp.style.backgroundColor = "#000000"; + shareBarHelp.style.opacity = "0.5"; + shareWithEveryoneButton.style.backgroundColor = "#000000"; + shareWithEveryoneButton.style.opacity = "0.5"; + shareBarHelp.innerHTML = hifiAlreadySharedText; + } + break; + case 'facebook': + shareBarHelp.style.backgroundColor = "#3C58A0"; + shareBarHelp.innerHTML = facebookShareText; + document.getElementById(selectedID + "facebookButton").style.backgroundColor = "#3C58A0"; + break; + case 'twitter': + shareBarHelp.style.backgroundColor = "#00B4EE"; + shareBarHelp.innerHTML = twitterShareText; + document.getElementById(selectedID + "twitterButton").style.backgroundColor = "#00B4EE"; + break; + } + } + + if (shouldAlsoModifyOther && imageCount > 1) { + if (selectedID === "p0" && !document.getElementById("p1").classList.contains("processingGif")) { + shareButtonHovered(destination, "p1", false); + } else if (selectedID === "p1") { + shareButtonHovered(destination, "p0", false); + } + } +} +function shareButtonClicked(destination, selectedID) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID + } + var storyID = document.getElementById(selectedID).getAttribute("data-story-id"); + + if (!storyID) { + showUploadingMessage(selectedID, destination); + } else { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "removeFromStoryIDsToMaybeDelete", + story_id: storyID + })); + } +} + +function handleCaptureSetting(setting) { + var stillAndGif = document.getElementById('stillAndGif'), + stillOnly = document.getElementById('stillOnly'); + + stillAndGif.checked = setting; + stillOnly.checked = !setting; + + stillAndGif.onclick = function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "captureStillAndGif" + })); + }; + + stillOnly.onclick = function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "captureStillOnly" + })); + }; + +} +window.onload = function () { + // Uncomment the line below to test functionality in a browser. + // See definition of "testInBrowser()" to modify tests. + //testInBrowser(4); + openEventBridge(function () { + // Set up a handler for receiving the data, and tell the .js we are ready to receive it. + EventBridge.scriptEventReceived.connect(function (message) { + + message = JSON.parse(message); + + if (message.type !== "snapshot") { + return; + } + + var messageOptions = message.options; + + switch (message.action) { + case 'showSetupInstructions': + showSetupInstructions(); + break; + case 'snapshotLocationChosen': + clearImages(); + showSetupComplete(); + break; + case 'clearPreviousImages': + clearImages(); + break; + case 'showPreviousImages': + clearImages(); + imageCount = message.image_data.length; + if (imageCount > 0) { + message.image_data.forEach(function (element, idx) { + addImage(element, messageOptions.isLoggedIn, message.canShare, false, true, message.image_data[idx].blastButtonDisabled, message.image_data[idx].hifiButtonDisabled, messageOptions.canBlast); + }); + } else { + showSnapshotInstructions(); + } + break; + case 'addImages': + // The last element of the message contents list contains a bunch of options, + // including whether or not we can share stuff + // The other elements of the list contain image paths. + if (messageOptions.containsGif === true) { + if (messageOptions.processingGif === true) { + imageCount = message.image_data.length + 1; // "+1" for the GIF that'll finish processing soon + message.image_data.push({ localPath: messageOptions.loadingGifPath }); + message.image_data.forEach(function (element, idx) { + addImage(element, messageOptions.isLoggedIn, idx === 0 && messageOptions.canShare, idx === 1, false, false, false, true); + }); + document.getElementById("p1").classList.add("processingGif"); + document.getElementById("snap-button").disabled = true; + } else { + var gifPath = message.image_data[0].localPath, + p1img = document.getElementById('p1img'); + p1img.src = gifPath; + + paths[1] = gifPath; + shareForUrl("p1"); + appendShareBar("p1", messageOptions.isLoggedIn, messageOptions.canShare, true, false, false, messageOptions.canBlast); + document.getElementById("p1").classList.remove("processingGif"); + document.getElementById("snap-button").disabled = false; + } + } else { + imageCount = message.image_data.length; + message.image_data.forEach(function (element) { + addImage(element, messageOptions.isLoggedIn, messageOptions.canShare, false, false, false, false, true); + }); + document.getElementById("snap-button").disabled = false; + } + break; + case 'captureSettings': + handleCaptureSetting(message.setting); + break; + case 'setPrintButtonEnabled': + setPrintButtonEnabled(); + break; + case 'setPrintButtonLoading': + setPrintButtonLoading(); + break; + case 'setPrintButtonDisabled': + setPrintButtonDisabled(); + break; + case 'snapshotUploadComplete': + var isGif = fileExtensionMatches(message.image_url, "gif"); + updateShareInfo(isGif ? "p1" : "p0", message.story_id); + if (isPrintProcessing()) { + setPrintButtonEnabled(); + } + break; + default: + console.log("Unknown message action received in SnapshotReview.js."); + break; + } + }); + + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "ready" + })); + });; +}; +function snapshotSettings() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "openSettings" + })); +} +function takeSnapshot() { + document.getElementById("snap-button").disabled = true; + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "takeSnapshot" + })); +} + +function isPrintDisabled() { + var printElement = document.getElementById('print-icon'); + + return printElement.classList.contains("print-icon") && + printElement.classList.contains("print-icon-default") && + document.getElementById('print-button').disabled; +} +function isPrintProcessing() { + var printElement = document.getElementById('print-icon'); + + return printElement.classList.contains("print-icon") && + printElement.classList.contains("print-icon-loading") && + document.getElementById('print-button').disabled; +} +function isPrintEnabled() { + var printElement = document.getElementById('print-icon'); + + return printElement.classList.contains("print-icon") && + printElement.classList.contains("print-icon-default") && + !document.getElementById('print-button').disabled; +} + +function setPrintButtonLoading() { + document.getElementById('print-icon').className = "print-icon print-icon-loading"; + document.getElementById('print-button').disabled = true; +} +function setPrintButtonDisabled() { + document.getElementById('print-icon').className = "print-icon print-icon-default"; + document.getElementById('print-button').disabled = true; +} +function setPrintButtonEnabled() { + document.getElementById('print-button').disabled = false; + document.getElementById('print-icon').className = "print-icon print-icon-default"; +} + +function requestPrintButtonUpdate() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "requestPrintButtonUpdate" + })); +} + +function printToPolaroid() { + if (isPrintEnabled()) { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "printToPolaroid" + })); + } else { + setPrintButtonLoading(); + } +} + +function testInBrowser(test) { + if (test === 0) { + showSetupInstructions(); + } else if (test === 1) { + imageCount = 2; + //addImage({ localPath: 'http://lorempixel.com/553/255' }); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.jpg', story_id: 1338 }, true, true, false, true, false, false, true); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.gif', story_id: 1337 }, true, true, false, true, false, false, true); + } else if (test === 2) { + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.jpg', story_id: 1338 }, true, true, false, true, false, false, true); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.gif', story_id: 1337 }, true, true, false, true, false, false, true); + showConfirmationMessage("p0", 'blast'); + showConfirmationMessage("p1", 'hifi'); + } else if (test === 3) { + imageCount = 2; + //addImage({ localPath: 'http://lorempixel.com/553/255' }); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.jpg', story_id: 1338 }, true, true, false, true, false, false, true); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.gif', story_id: 1337 }, true, true, false, true, false, false, true); + showUploadingMessage("p0", 'hifi'); + } else if (test === 4) { + imageCount = 2; + //addImage({ localPath: 'http://lorempixel.com/553/255' }); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.jpg', story_id: 1338 }, false, true, false, true, false, false, true); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.gif', story_id: 1337 }, false, true, false, true, false, false, true); +} +} diff --git a/scripts/simplifiedUI/system/html/js/colpick.js b/scripts/simplifiedUI/system/html/js/colpick.js new file mode 100644 index 0000000000..e4ad65dfb6 --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/colpick.js @@ -0,0 +1,616 @@ +/* +colpick Color Picker +Copyright 2013 Jose Vargas. Licensed under GPL license. Based on Stefan Petre's Color Picker www.eyecon.ro, dual licensed +under the MIT and GPL licenses + +For usage and examples: colpick.com/plugin + */ + +/* global console, document, Element, EventBridge, jQuery, navigator, window, _ $ */ + +(function ($) { + var colpick = function () { + var + tpl = '
' + + '
' + + '
' + + '
' + + '
' + + '
#
' + + '
' + + '
R
' + + '
' + + '
' + + '
G
' + + '
' + + '
B
' + + '
' + + '
' + + '
H
' + + '
' + + '
' + + '
S
' + + '
' + + '
' + + '
B
' + + '
' + + '
' + + '
', + defaults = { + showEvent: 'click', + onShow: function () {}, + onBeforeShow: function(){}, + onHide: function () {}, + onChange: function () {}, + onSubmit: function () {}, + colorScheme: 'light', + color: '3289c7', + livePreview: true, + flat: false, + layout: 'full', + submit: 1, + submitText: 'OK', + height: 156 + }, + // Fill the inputs of the plugin + fillRGBFields = function (hsb, cal) { + var rgb = hsbToRgb(hsb); + $(cal).data('colpick').fields + .eq(1).val(rgb.r).end() + .eq(2).val(rgb.g).end() + .eq(3).val(rgb.b).end(); + }, + fillHSBFields = function (hsb, cal) { + $(cal).data('colpick').fields + .eq(4).val(Math.round(hsb.h)).end() + .eq(5).val(Math.round(hsb.s)).end() + .eq(6).val(Math.round(hsb.b)).end(); + }, + fillHexFields = function (hsb, cal) { + $(cal).data('colpick').fields.eq(0).val(hsbToHex(hsb)); + }, + // Set the round selector position + setSelector = function (hsb, cal) { + $(cal).data('colpick').selector.css('backgroundColor', '#' + hsbToHex({h: hsb.h, s: 100, b: 100})); + $(cal).data('colpick').selectorIndic.css({ + left: parseInt($(cal).data('colpick').height * hsb.s/100, 10), + top: parseInt($(cal).data('colpick').height * (100-hsb.b)/100, 10) + }); + }, + // Set the hue selector position + setHue = function (hsb, cal) { + $(cal).data('colpick').hue.css('top', + parseInt($(cal).data('colpick').height - $(cal).data('colpick').height * hsb.h / 360, 10)); + }, + // Set current and new colors + setCurrentColor = function (hsb, cal) { + $(cal).data('colpick').currentColor.css('backgroundColor', '#' + hsbToHex(hsb)); + }, + setNewColor = function (hsb, cal) { + $(cal).data('colpick').newColor.css('backgroundColor', '#' + hsbToHex(hsb)); + }, + // Called when the new color is changed + change = function (ev) { + var cal = $(this).parent().parent(), col; + if (this.parentNode.className.indexOf('_hex') > 0) { + cal.data('colpick').color = col = hexToHsb(fixHex(this.value)); + fillRGBFields(col, cal.get(0)); + fillHSBFields(col, cal.get(0)); + fillHexFields(col, cal.get(0)); + } else if (this.parentNode.className.indexOf('_hsb') > 0) { + cal.data('colpick').color = col = fixHSB({ + h: parseInt(cal.data('colpick').fields.eq(4).val(), 10), + s: parseInt(cal.data('colpick').fields.eq(5).val(), 10), + b: parseInt(cal.data('colpick').fields.eq(6).val(), 10) + }); + fillRGBFields(col, cal.get(0)); + fillHexFields(col, cal.get(0)); + fillHSBFields(col, cal.get(0)); + } else { + cal.data('colpick').color = col = rgbToHsb(fixRGB({ + r: parseInt(cal.data('colpick').fields.eq(1).val(), 10), + g: parseInt(cal.data('colpick').fields.eq(2).val(), 10), + b: parseInt(cal.data('colpick').fields.eq(3).val(), 10) + })); + fillHexFields(col, cal.get(0)); + fillHSBFields(col, cal.get(0)); + fillRGBFields(col, cal.get(0)); + } + setSelector(col, cal.get(0)); + setHue(col, cal.get(0)); + setNewColor(col, cal.get(0)); + cal.data('colpick').onChange.apply(cal.parent(), + [col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el, 0]); + }, + // Change style on blur and on focus of inputs + blur = function (ev) { + $(this).parent().removeClass('colpick_focus'); + }, + focus = function () { + $(this).parent().parent().data('colpick').fields.parent().removeClass('colpick_focus'); + $(this).parent().addClass('colpick_focus'); + }, + // Increment/decrement arrows functions + downIncrement = function (ev) { + ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; + var field = $(this).parent().find('input').focus(); + var current = { + el: $(this).parent().addClass('colpick_slider'), + max: this.parentNode.className.indexOf('_hsb_h') > 0 ? 360 : + (this.parentNode.className.indexOf('_hsb') > 0 ? 100 : 255), + y: ev.pageY, + field: field, + val: parseInt(field.val(), 10), + preview: $(this).parent().parent().data('colpick').livePreview + }; + $(document).mouseup(current, upIncrement); + $(document).mousemove(current, moveIncrement); + }, + moveIncrement = function (ev) { + ev.data.field.val(Math.max(0, Math.min(ev.data.max, parseInt(ev.data.val - ev.pageY + ev.data.y, 10)))); + if (ev.data.preview) { + change.apply(ev.data.field.get(0), [true]); + } + return false; + }, + upIncrement = function (ev) { + change.apply(ev.data.field.get(0), [true]); + ev.data.el.removeClass('colpick_slider').find('input').focus(); + $(document).off('mouseup', upIncrement); + $(document).off('mousemove', moveIncrement); + return false; + }, + // Hue slider functions + downHue = function (ev) { + ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; + var current = { + cal: $(this).parent(), + y: $(this).offset().top + }; + $(document).on('mouseup touchend',current,upHue); + $(document).on('mousemove touchmove',current,moveHue); + + var pageY = ((ev.type === 'touchstart') ? ev.originalEvent.changedTouches[0].pageY : ev.pageY ); + change.apply( + current.cal.data('colpick') + .fields.eq(4).val(parseInt(360 * (current.cal.data('colpick').height - + (pageY - current.y)) / current.cal.data('colpick').height, 10)) + .get(0), + [current.cal.data('colpick').livePreview] + ); + return false; + }, + moveHue = function (ev) { + var pageY = ((ev.type === 'touchmove') ? ev.originalEvent.changedTouches[0].pageY : ev.pageY ); + change.apply( + ev.data.cal.data('colpick') + .fields.eq(4).val(parseInt(360 * (ev.data.cal.data('colpick').height - + Math.max(0, Math.min(ev.data.cal.data('colpick').height, (pageY - ev.data.y)))) / + ev.data.cal.data('colpick').height, 10)) + .get(0), + [ev.data.preview] + ); + return false; + }, + upHue = function (ev) { + fillRGBFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + fillHexFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + $(document).off('mouseup touchend',upHue); + $(document).off('mousemove touchmove',moveHue); + return false; + }, + // Color selector functions + downSelector = function (ev) { + ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; + var current = { + cal: $(this).parent(), + pos: $(this).offset() + }; + current.preview = current.cal.data('colpick').livePreview; + + $(document).on('mouseup touchend',current,upSelector); + $(document).on('mousemove touchmove',current,moveSelector); + + var pageX,pageY; + if (ev.type === 'touchstart') { + pageX = ev.originalEvent.changedTouches[0].pageX, + pageY = ev.originalEvent.changedTouches[0].pageY; + } else { + pageX = ev.pageX; + pageY = ev.pageY; + } + + change.apply( + current.cal.data('colpick').fields + .eq(6).val(parseInt(100 * (current.cal.data('colpick').height - (pageY - current.pos.top)) / + current.cal.data('colpick').height, 10)).end() + .eq(5).val(parseInt(100*(pageX - current.pos.left)/current.cal.data('colpick').height, 10)) + .get(0), + [current.preview] + ); + return false; + }, + moveSelector = function (ev) { + var pageX,pageY; + if (ev.type === 'touchmove') { + pageX = ev.originalEvent.changedTouches[0].pageX, + pageY = ev.originalEvent.changedTouches[0].pageY; + } else { + pageX = ev.pageX; + pageY = ev.pageY; + } + + change.apply( + ev.data.cal.data('colpick').fields + .eq(6).val(parseInt(100 * (ev.data.cal.data('colpick').height - + Math.max(0, Math.min(ev.data.cal.data('colpick').height, (pageY - ev.data.pos.top)))) / + ev.data.cal.data('colpick').height, 10)).end() + .eq(5).val(parseInt(100 * (Math.max(0, Math.min(ev.data.cal.data('colpick').height, + (pageX - ev.data.pos.left)))) / ev.data.cal.data('colpick').height, 10)) + .get(0), + [ev.data.preview] + ); + return false; + }, + upSelector = function (ev) { + fillRGBFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + fillHexFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + $(document).off('mouseup touchend',upSelector); + $(document).off('mousemove touchmove',moveSelector); + return false; + }, + // Submit button + clickSubmit = function (ev) { + var cal = $(this).parent(); + var col = cal.data('colpick').color; + cal.data('colpick').origColor = col; + setCurrentColor(col, cal.get(0)); + cal.data('colpick').onSubmit(col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el); + }, + // Show/hide the color picker + show = function (ev) { + if ($(this).attr('disabled')) { + return; + } + // Prevent the trigger of any direct parent + ev.stopPropagation(); + var cal = $('#' + $(this).data('colpickId')); + cal.data('colpick').onBeforeShow.apply(this, [cal.get(0)]); + var pos = $(this).offset(); + var top = pos.top + this.offsetHeight; + var left = pos.left; + var viewPort = getViewport(); + var calW = cal.width(); + if (left + calW > viewPort.l + viewPort.w) { + left -= calW; + } + cal.css({left: left + 'px', top: top + 'px'}); + if (cal.data('colpick').onShow.apply(this, [cal.get(0)]) !== false) { + cal.show(); + } + // Hide when user clicks outside + $('html').mousedown({cal:cal}, hide); + cal.mousedown(function(ev){ + ev.stopPropagation(); + }); + }, + hide = function (ev) { + if (ev.data.cal.data('colpick').onHide.apply(this, [ev.data.cal.get(0)]) !== false) { + ev.data.cal.hide(); + } + $('html').off('mousedown', hide); + }, + getViewport = function () { + var m = document.compatMode === 'CSS1Compat'; + return { + l : window.pageXOffset || (m ? document.documentElement.scrollLeft : document.body.scrollLeft), + w : window.innerWidth || (m ? document.documentElement.clientWidth : document.body.clientWidth) + }; + }, + // Fix the values if the user enters a negative or high value + fixHSB = function (hsb) { + hsb.h = isNaN(hsb.h) ? 0 : hsb.h; + hsb.s = isNaN(hsb.s) ? 0 : hsb.s; + hsb.b = isNaN(hsb.b) ? 0 : hsb.b; + return { + h: Math.min(360, Math.max(0, hsb.h)), + s: Math.min(100, Math.max(0, hsb.s)), + b: Math.min(100, Math.max(0, hsb.b)) + }; + }, + fixRGB = function (rgb) { + rgb.r = isNaN(rgb.r) ? 0 : rgb.r; + rgb.g = isNaN(rgb.g) ? 0 : rgb.g; + rgb.b = isNaN(rgb.b) ? 0 : rgb.b; + return { + r: Math.min(255, Math.max(0, rgb.r)), + g: Math.min(255, Math.max(0, rgb.g)), + b: Math.min(255, Math.max(0, rgb.b)) + }; + }, + fixHex = function (hex) { + var len = 6 - hex.length; + if (len > 0) { + var o = []; + for (var i=0; i').attr('style', + 'height:8.333333%; filter:progid:' + + 'DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=' + stops[i] + + ', endColorstr=' + stops[i + 1] + + '); -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=' + + stops[i] + ', endColorstr=' + stops[i + 1] + ')";'); + huebar.append(div); + } + } else { + var stopList = stops.join(','); + huebar.attr('style', 'background:-webkit-linear-gradient(top,' + stopList + + '); background: -o-linear-gradient(top,' + stopList + + '); background: -ms-linear-gradient(top,' + stopList + + '); background:-moz-linear-gradient(top,' + stopList + + '); -webkit-linear-gradient(top,' + stopList + + '); background:linear-gradient(to bottom,' + stopList + '); '); + } + cal.find('div.colpick_hue').on('mousedown touchstart',downHue); + options.newColor = cal.find('div.colpick_new_color'); + options.currentColor = cal.find('div.colpick_current_color'); + // Store options and fill with default color + cal.data('colpick', options); + fillRGBFields(options.color, cal.get(0)); + fillHSBFields(options.color, cal.get(0)); + fillHexFields(options.color, cal.get(0)); + setHue(options.color, cal.get(0)); + setSelector(options.color, cal.get(0)); + setCurrentColor(options.color, cal.get(0)); + setNewColor(options.color, cal.get(0)); + // Append to body if flat=false, else show in place + if (options.flat) { + cal.appendTo(this).show(); + cal.css({ + position: 'relative', + display: 'block' + }); + } else { + cal.appendTo(document.body); + $(this).on(options.showEvent, show); + cal.css({ + position:'absolute' + }); + } + } + }); + }, + // Shows the picker + showPicker: function() { + return this.each( function () { + if ($(this).data('colpickId')) { + show.apply(this); + } + }); + }, + // Hides the picker + hidePicker: function() { + return this.each( function () { + if ($(this).data('colpickId')) { + $('#' + $(this).data('colpickId')).hide(); + } + }); + }, + // Sets a color as new and current (default) + setColor: function(col, setCurrent) { + setCurrent = (typeof setCurrent === "undefined") ? 1 : setCurrent; + if (typeof col === 'string') { + col = hexToHsb(col); + } else if (col.r !== undefined && col.g !== undefined && col.b !== undefined) { + col = rgbToHsb(col); + } else if (col.h !== undefined && col.s !== undefined && col.b !== undefined) { + col = fixHSB(col); + } else { + return this; + } + return this.each(function(){ + if ($(this).data('colpickId')) { + var cal = $('#' + $(this).data('colpickId')); + cal.data('colpick').color = col; + cal.data('colpick').origColor = col; + fillRGBFields(col, cal.get(0)); + fillHSBFields(col, cal.get(0)); + fillHexFields(col, cal.get(0)); + setHue(col, cal.get(0)); + setSelector(col, cal.get(0)); + + setNewColor(col, cal.get(0)); + cal.data('colpick').onChange.apply(cal.parent(), + [col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el, 1]); + if (setCurrent) { + setCurrentColor(col, cal.get(0)); + } + } + }); + } + }; + }(); + // Color space convertions + var hexToRgb = function (hexString) { + if (typeof hexString !== "string") { + print("Error - ColPick.js::hexToRgb expects string object."); + return; + } + + var hexNumber = parseInt(((hexString.indexOf('#') > -1) ? hexString.substring(1) : hexString), 16); + return { r: hexNumber >> 16, g: (hexNumber & 0x00FF00) >> 8, b: (hexNumber & 0x0000FF)}; + }; + var hexToHsb = function (hexString) { + if (typeof hexString !== "string") { + print("Error - ColPick.js::hexToHsb expects string object."); + return; + } + + return rgbToHsb(hexToRgb(hexString)); + }; + var rgbToHsb = function (rgb) { + var hsb = {h: 0, s: 0, b: 0}; + var min = Math.min(rgb.r, rgb.g, rgb.b); + var max = Math.max(rgb.r, rgb.g, rgb.b); + var delta = max - min; + hsb.b = max; + hsb.s = max != 0 ? 255 * delta / max : 0; // eslint-disable-line eqeqeq + if (hsb.s != 0) { // eslint-disable-line eqeqeq + if (rgb.r == max) { // eslint-disable-line eqeqeq + hsb.h = (rgb.g - rgb.b) / delta; + } else if (rgb.g == max) { // eslint-disable-line eqeqeq + hsb.h = 2 + (rgb.b - rgb.r) / delta; + } else { + hsb.h = 4 + (rgb.r - rgb.g) / delta; + } + } else { + hsb.h = -1; + } + hsb.h *= 60; + if (hsb.h < 0) { + hsb.h += 360; + } + hsb.s *= 100/255; + hsb.b *= 100/255; + return hsb; + }; + var hsbToRgb = function (hsb) { + var rgb = {}; + var h = hsb.h; + var s = hsb.s*255/100; + var v = hsb.b*255/100; + if (s == 0) { // eslint-disable-line eqeqeq + rgb.r = rgb.g = rgb.b = v; + } else { + var t1 = v; + var t2 = (255-s)*v/255; + var t3 = (t1-t2)*(h%60)/60; + if (h==360) { // eslint-disable-line eqeqeq + h = 0; + } + if (h<60) { + rgb.r=t1; rgb.b=t2; rgb.g=t2+t3; + } else if (h<120) { + rgb.g=t1; rgb.b=t2; rgb.r=t1-t3; + } else if (h<180) { + rgb.g=t1; rgb.r=t2; rgb.b=t2+t3; + } else if (h<240) { + rgb.b=t1; rgb.r=t2; rgb.g=t1-t3; + } else if (h<300) { + rgb.b=t1; rgb.g=t2; rgb.r=t2+t3; + } else if (h<360) { + rgb.r=t1; rgb.g=t2; rgb.b=t1-t3; + } else { + rgb.r=0; rgb.g=0; rgb.b=0; + } + } + return {r:Math.round(rgb.r), g:Math.round(rgb.g), b:Math.round(rgb.b)}; + }; + var rgbToHex = function (rgb) { + var hex = [ + rgb.r.toString(16), + rgb.g.toString(16), + rgb.b.toString(16) + ]; + $.each(hex, function (nr, val) { + if (val.length == 1) { // eslint-disable-line eqeqeq + hex[nr] = '0' + val; + } + }); + return hex.join(''); + }; + var hsbToHex = function (hsb) { + return rgbToHex(hsbToRgb(hsb)); + }; + $.fn.extend({ + colpick: colpick.init, + colpickHide: colpick.hidePicker, + colpickShow: colpick.showPicker, + colpickSetColor: colpick.setColor + }); + $.extend({ + colpick:{ + rgbToHex: rgbToHex, + rgbToHsb: rgbToHsb, + hsbToHex: hsbToHex, + hsbToRgb: hsbToRgb, + hexToHsb: hexToHsb, + hexToRgb: hexToRgb + } + }); +})(jQuery); + diff --git a/scripts/simplifiedUI/system/html/js/createAppTooltip.js b/scripts/simplifiedUI/system/html/js/createAppTooltip.js new file mode 100644 index 0000000000..3eb206d8a3 --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/createAppTooltip.js @@ -0,0 +1,116 @@ +// createAppTooltip.js +// +// Created by Thijs Wenker on 17 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 + +const CREATE_APP_TOOLTIP_OFFSET = 20; +const TOOLTIP_DELAY = 500; // ms +const TOOLTIP_DEBUG = false; + +function CreateAppTooltip() { + this._tooltipData = null; + this._tooltipDiv = null; + this._delayTimeout = null; + this._isEnabled = false; +} + +CreateAppTooltip.prototype = { + _tooltipData: null, + _tooltipDiv: null, + _delayTimeout: null, + _isEnabled: null, + + _removeTooltipIfExists: function() { + if (this._delayTimeout !== null) { + window.clearTimeout(this._delayTimeout); + this._delayTimeout = null; + } + + if (this._tooltipDiv !== null) { + this._tooltipDiv.remove(); + this._tooltipDiv = null; + } + }, + + setIsEnabled: function(isEnabled) { + this._isEnabled = isEnabled; + }, + + setTooltipData: function(tooltipData) { + this._tooltipData = tooltipData; + }, + + registerTooltipElement: function(element, tooltipID, jsPropertyName) { + element.addEventListener("mouseover", function() { + if (!this._isEnabled) { + return; + } + + this._removeTooltipIfExists(); + + this._delayTimeout = window.setTimeout(function() { + let tooltipData = this._tooltipData[tooltipID]; + + if (!tooltipData || tooltipData.tooltip === "") { + if (!TOOLTIP_DEBUG) { + return; + } + tooltipData = { tooltip: 'PLEASE SET THIS TOOLTIP' }; + } + + let elementRect = element.getBoundingClientRect(); + let elTip = document.createElement("div"); + elTip.className = "create-app-tooltip"; + + let elTipDescription = document.createElement("div"); + elTipDescription.className = "create-app-tooltip-description"; + elTipDescription.innerText = tooltipData.tooltip; + elTip.appendChild(elTipDescription); + + let jsAttribute = jsPropertyName; + if (tooltipData.jsPropertyName) { + jsAttribute = tooltipData.jsPropertyName; + } + + if (!tooltipData.skipJSProperty) { + let elTipJSAttribute = document.createElement("div"); + elTipJSAttribute.className = "create-app-tooltip-js-attribute"; + elTipJSAttribute.innerText = `JS Attribute: ${jsAttribute}`; + elTip.appendChild(elTipJSAttribute); + } + + document.body.appendChild(elTip); + + let elementTop = window.pageYOffset + elementRect.top; + + let desiredTooltipTop = elementTop + element.clientHeight + CREATE_APP_TOOLTIP_OFFSET; + let desiredTooltipLeft = window.pageXOffset + elementRect.left; + + if ((window.innerHeight + window.pageYOffset) < (desiredTooltipTop + elTip.clientHeight)) { + // show above when otherwise out of bounds + elTip.style.top = elementTop - CREATE_APP_TOOLTIP_OFFSET - elTip.clientHeight; + } else { + // show tooltip below by default + elTip.style.top = desiredTooltipTop; + } + if ((window.innerWidth + window.pageXOffset) < (desiredTooltipLeft + elTip.clientWidth)) { + elTip.style.left = document.body.clientWidth + window.pageXOffset - elTip.offsetWidth; + } else { + elTip.style.left = desiredTooltipLeft; + } + + this._tooltipDiv = elTip; + }.bind(this), TOOLTIP_DELAY); + }.bind(this), false); + element.addEventListener("mouseout", function() { + if (!this._isEnabled) { + return; + } + + this._removeTooltipIfExists(); + }.bind(this), false); + } +}; diff --git a/scripts/simplifiedUI/system/html/js/draggableNumber.js b/scripts/simplifiedUI/system/html/js/draggableNumber.js new file mode 100644 index 0000000000..3c7b74290c --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/draggableNumber.js @@ -0,0 +1,268 @@ +// draggableNumber.js +// +// Created by David Back on 7 Nov 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 + +const DELTA_X_FOCUS_THRESHOLD = 1; +const ENTER_KEY = 13; + +function DraggableNumber(min, max, step, decimals, dragStart, dragEnd) { + this.min = min; + this.max = max; + this.step = step !== undefined ? step : 1; + this.multiDiffModeEnabled = false; + this.decimals = decimals; + this.dragStartFunction = dragStart; + this.dragEndFunction = dragEnd; + this.dragging = false; + this.initialMouseEvent = null; + this.lastMouseEvent = null; + this.valueChangeFunction = null; + this.multiDiffStepFunction = null; + this.initialize(); +} + +DraggableNumber.prototype = { + showInput: function() { + this.elText.style.visibility = "hidden"; + this.elLeftArrow.style.visibility = "hidden"; + this.elRightArrow.style.visibility = "hidden"; + this.elInput.style.opacity = 1; + }, + + hideInput: function() { + this.elText.style.visibility = "visible"; + this.elLeftArrow.style.visibility = "visible"; + this.elRightArrow.style.visibility = "visible"; + this.elInput.style.opacity = 0; + }, + + mouseDown: function(event) { + if (event.target === this.elText && !this.isDisabled()) { + this.initialMouseEvent = event; + this.lastMouseEvent = event; + document.addEventListener("mousemove", this.onDocumentMouseMove); + document.addEventListener("mouseup", this.onDocumentMouseUp); + } + }, + + mouseUp: function(event) { + if (!this.dragging && event.target === this.elText && this.initialMouseEvent) { + let dx = event.clientX - this.initialMouseEvent.clientX; + if (Math.abs(dx) <= DELTA_X_FOCUS_THRESHOLD) { + this.showInput(); + this.elInput.focus(); + } + this.initialMouseEvent = null; + } + }, + + documentMouseMove: function(event) { + if (!this.dragging && this.initialMouseEvent) { + let dxFromInitial = event.clientX - this.initialMouseEvent.clientX; + if (Math.abs(dxFromInitial) > DELTA_X_FOCUS_THRESHOLD) { + if (this.dragStartFunction) { + this.dragStartFunction(); + } + this.dragging = true; + } + this.lastMouseEvent = event; + } + if (this.dragging && this.lastMouseEvent) { + let dragDelta = event.clientX - this.lastMouseEvent.clientX; + if (dragDelta !== 0) { + if (this.multiDiffModeEnabled) { + if (this.multiDiffStepFunction) { + this.multiDiffStepFunction(dragDelta * this.step); + } + } else { + if (dragDelta > 0) { + this.elInput.stepUp(dragDelta); + } else { + this.elInput.stepDown(-dragDelta); + } + this.inputChange(); + if (this.valueChangeFunction) { + this.valueChangeFunction(); + } + } + } + this.lastMouseEvent = event; + } + }, + + documentMouseUp: function(event) { + if (this.dragging) { + if (this.dragEndFunction) { + this.dragEndFunction(); + } + this.dragging = false; + } + this.lastMouseEvent = null; + document.removeEventListener("mousemove", this.onDocumentMouseMove); + document.removeEventListener("mouseup", this.onDocumentMouseUp); + }, + + stepUp: function() { + if (!this.isDisabled()) { + if (this.multiDiffModeEnabled) { + if (this.multiDiffStepFunction) { + this.multiDiffStepFunction(this.step, true); + } + } else { + this.elInput.value = parseFloat(this.elInput.value) + this.step; + this.inputChange(); + if (this.valueChangeFunction) { + this.valueChangeFunction(); + } + } + } + }, + + stepDown: function() { + if (!this.isDisabled()) { + if (this.multiDiffModeEnabled) { + if (this.multiDiffStepFunction) { + this.multiDiffStepFunction(-this.step, true); + } + } else { + this.elInput.value = parseFloat(this.elInput.value) - this.step; + this.inputChange(); + if (this.valueChangeFunction) { + this.valueChangeFunction(); + } + } + } + }, + + setValue: function(newValue, isMultiDiff) { + if (isMultiDiff !== undefined) { + this.setMultiDiff(isMultiDiff); + } + + if (isNaN(newValue)) { + console.error("DraggableNumber.setValue() > " + newValue + " is not a number."); + return; + } + + if (newValue !== "" && this.decimals !== undefined) { + this.elInput.value = parseFloat(newValue).toFixed(this.decimals); + } else { + this.elInput.value = newValue; + } + this.elText.firstChild.data = this.elInput.value; + }, + + setMultiDiff: function(isMultiDiff) { + this.multiDiffModeEnabled = isMultiDiff; + if (isMultiDiff) { + this.elDiv.classList.add('multi-diff'); + } else { + this.elDiv.classList.remove('multi-diff'); + } + }, + + setValueChangeFunction: function(valueChangeFunction) { + this.valueChangeFunction = valueChangeFunction.bind(this.elInput); + this.elInput.addEventListener("change", this.valueChangeFunction); + }, + + setMultiDiffStepFunction: function (multiDiffStepFunction) { + this.multiDiffStepFunction = multiDiffStepFunction; + }, + + inputChange: function() { + let value = this.elInput.value; + if (this.max !== undefined) { + value = Math.min(this.max, value); + } + if (this.min !== undefined) { + value = Math.max(this.min, value); + } + this.setValue(value); + }, + + inputBlur: function(ev) { + this.hideInput(); + }, + + keyPress: function(event) { + if (event.keyCode === ENTER_KEY) { + if (this.valueChangeFunction) { + this.valueChangeFunction(); + } + this.inputBlur(); + } + }, + + isDisabled: function() { + return this.elText.getAttribute("disabled") === "disabled"; + }, + + updateMinMax: function(min, max) { + this.min = min; + this.max = max; + if (this.min !== undefined) { + this.elInput.setAttribute("min", this.min); + } + if (this.max !== undefined) { + this.elInput.setAttribute("max", this.max); + } + }, + + initialize: function() { + this.onMouseDown = this.mouseDown.bind(this); + this.onMouseUp = this.mouseUp.bind(this); + this.onDocumentMouseMove = this.documentMouseMove.bind(this); + this.onDocumentMouseUp = this.documentMouseUp.bind(this); + this.onStepUp = this.stepUp.bind(this); + this.onStepDown = this.stepDown.bind(this); + this.onInputChange = this.inputChange.bind(this); + this.onInputBlur = this.inputBlur.bind(this); + this.onKeyPress = this.keyPress.bind(this); + + this.elDiv = document.createElement('div'); + this.elDiv.className = "draggable-number"; + + this.elText = document.createElement('span'); + this.elText.className = "text"; + this.elText.innerText = " "; + this.elText.style.visibility = "visible"; + this.elText.addEventListener("mousedown", this.onMouseDown); + this.elText.addEventListener("mouseup", this.onMouseUp); + + this.elLeftArrow = document.createElement('span'); + this.elRightArrow = document.createElement('span'); + this.elLeftArrow.className = 'left-arrow'; + this.elLeftArrow.innerHTML = 'D'; + this.elLeftArrow.addEventListener("click", this.onStepDown); + this.elRightArrow.className = 'right-arrow'; + this.elRightArrow.innerHTML = 'D'; + this.elRightArrow.addEventListener("click", this.onStepUp); + + this.elMultiDiff = document.createElement('span'); + this.elMultiDiff.className = 'multi-diff'; + + this.elInput = document.createElement('input'); + this.elInput.className = "input"; + this.elInput.setAttribute("type", "number"); + this.updateMinMax(this.min, this.max); + if (this.step !== undefined) { + this.elInput.setAttribute("step", this.step); + } + this.elInput.style.opacity = 0; + this.elInput.addEventListener("change", this.onInputChange); + this.elInput.addEventListener("blur", this.onInputBlur); + this.elInput.addEventListener("keypress", this.onKeyPress); + this.elInput.addEventListener("focus", this.showInput.bind(this)); + + this.elDiv.appendChild(this.elLeftArrow); + this.elDiv.appendChild(this.elText); + this.elDiv.appendChild(this.elInput); + this.elDiv.appendChild(this.elMultiDiff); + this.elDiv.appendChild(this.elRightArrow); + } +}; diff --git a/scripts/simplifiedUI/system/html/js/entityList.js b/scripts/simplifiedUI/system/html/js/entityList.js new file mode 100644 index 0000000000..b15c4e6703 --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/entityList.js @@ -0,0 +1,1426 @@ +// entityList.js +// +// Created by Ryan Huffman on 19 Nov 2014 +// Copyright 2014 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 + +const ASCENDING_SORT = 1; +const DESCENDING_SORT = -1; +const ASCENDING_STRING = '▴'; +const DESCENDING_STRING = '▾'; +const BYTES_PER_MEGABYTE = 1024 * 1024; +const COLLAPSE_EXTRA_INFO = "E"; +const EXPAND_EXTRA_INFO = "D"; +const FILTER_IN_VIEW_ATTRIBUTE = "pressed"; +const WINDOW_NONVARIABLE_HEIGHT = 227; +const EMPTY_ENTITY_ID = "0"; +const MAX_LENGTH_RADIUS = 9; +const MINIMUM_COLUMN_WIDTH = 24; +const SCROLLBAR_WIDTH = 20; +const RESIZER_WIDTH = 10; +const DELTA_X_MOVE_COLUMNS_THRESHOLD = 2; +const DELTA_X_COLUMN_SWAP_POSITION = 5; +const CERTIFIED_PLACEHOLDER = "** Certified **"; + +function decimalMegabytes(number) { + return number ? (number / BYTES_PER_MEGABYTE).toFixed(1) : ""; +} + +function displayIfNonZero(number) { + return number ? number : ""; +} + +function getFilename(url) { + let urlParts = url.split('/'); + return urlParts[urlParts.length - 1]; +} + +const COLUMNS = { + type: { + columnHeader: "Type", + propertyID: "type", + initialWidth: 0.16, + initiallyShown: true, + alwaysShown: true, + defaultSortOrder: ASCENDING_SORT, + }, + name: { + columnHeader: "Name", + propertyID: "name", + initialWidth: 0.34, + initiallyShown: true, + alwaysShown: true, + defaultSortOrder: ASCENDING_SORT, + }, + url: { + columnHeader: "File", + dropdownLabel: "File", + propertyID: "url", + initialWidth: 0.34, + initiallyShown: true, + defaultSortOrder: ASCENDING_SORT, + }, + locked: { + columnHeader: "", + glyph: true, + propertyID: "locked", + initialWidth: 0.08, + initiallyShown: true, + alwaysShown: true, + defaultSortOrder: DESCENDING_SORT, + }, + visible: { + columnHeader: "", + glyph: true, + propertyID: "visible", + initialWidth: 0.08, + initiallyShown: true, + alwaysShown: true, + defaultSortOrder: DESCENDING_SORT, + }, + verticesCount: { + columnHeader: "Verts", + dropdownLabel: "Vertices", + propertyID: "verticesCount", + initialWidth: 0.08, + defaultSortOrder: DESCENDING_SORT, + }, + texturesCount: { + columnHeader: "Texts", + dropdownLabel: "Textures", + propertyID: "texturesCount", + initialWidth: 0.08, + defaultSortOrder: DESCENDING_SORT, + }, + texturesSize: { + columnHeader: "Text MB", + dropdownLabel: "Texture Size", + propertyID: "texturesSize", + initialWidth: 0.10, + format: decimalMegabytes, + defaultSortOrder: DESCENDING_SORT, + }, + hasTransparent: { + columnHeader: "", + glyph: true, + dropdownLabel: "Transparency", + propertyID: "hasTransparent", + initialWidth: 0.04, + defaultSortOrder: DESCENDING_SORT, + }, + isBaked: { + columnHeader: "", + glyph: true, + dropdownLabel: "Baked", + propertyID: "isBaked", + initialWidth: 0.08, + defaultSortOrder: DESCENDING_SORT, + }, + drawCalls: { + columnHeader: "Draws", + dropdownLabel: "Draws", + propertyID: "drawCalls", + initialWidth: 0.08, + defaultSortOrder: DESCENDING_SORT, + }, + hasScript: { + columnHeader: "k", + glyph: true, + dropdownLabel: "Script", + propertyID: "hasScript", + initialWidth: 0.06, + defaultSortOrder: DESCENDING_SORT, + }, +}; + +const FILTER_TYPES = [ + "Shape", + "Model", + "Image", + "Light", + "Zone", + "Web", + "Material", + "ParticleEffect", + "PolyLine", + "PolyVox", + "Text", + "Grid", +]; + +const DOUBLE_CLICK_TIMEOUT = 300; // ms +const RENAME_COOLDOWN = 400; // ms + +// List of all entities +let entities = []; +// List of all entities, indexed by Entity ID +let entitiesByID = {}; +// The filtered and sorted list of entities passed to ListView +let visibleEntities = []; +// List of all entities that are currently selected +let selectedEntities = []; + +let entityList = null; // The ListView + +/** + * @type EntityListContextMenu + */ +let entityListContextMenu = null; + +let currentSortColumnID = 'type'; +let currentSortOrder = ASCENDING_SORT; +let elSortOrders = {}; +let typeFilters = []; +let isFilterInView = false; + +let columns = []; +let columnsByID = {}; +let lastResizeEvent = null; +let resizeColumnIndex = 0; +let elTargetTh = null; +let elTargetSpan = null; +let targetColumnIndex = 0; +let lastColumnSwapPosition = -1; +let initialThEvent = null; +let renameTimeout = null; +let renameLastBlur = null; +let renameLastEntityID = null; +let isRenameFieldBeingMoved = false; +let elFilterTypeInputs = {}; + +let elEntityTable, + elEntityTableHeader, + elEntityTableBody, + elEntityTableScroll, + elEntityTableHeaderRow, + elRefresh, + elToggleLocked, + elToggleVisible, + elDelete, + elFilterTypeMultiselectBox, + elFilterTypeText, + elFilterTypeOptions, + elFilterTypeOptionsButtons, + elFilterTypeSelectAll, + elFilterTypeClearAll, + elFilterSearch, + elFilterInView, + elFilterRadius, + elExport, + elPal, + elSelectedEntitiesCount, + elVisibleEntitiesCount, + elNoEntitiesMessage, + elColumnsMultiselectBox, + elColumnsOptions, + elToggleSpaceMode, + elRenameInput; + +const ENABLE_PROFILING = false; +let profileIndent = ''; +const PROFILE_NOOP = function(_name, fn, args) { + fn.apply(this, args); +} ; +const PROFILE = !ENABLE_PROFILING ? PROFILE_NOOP : function(name, fn, args) { + console.log("PROFILE-Web " + profileIndent + "(" + name + ") Begin"); + let previousIndent = profileIndent; + profileIndent += ' '; + let before = Date.now(); + fn.apply(this, args); + let delta = Date.now() - before; + profileIndent = previousIndent; + console.log("PROFILE-Web " + profileIndent + "(" + name + ") End " + delta + "ms"); +}; + +function loaded() { + openEventBridge(function() { + elEntityTable = document.getElementById("entity-table"); + elEntityTableHeader = document.getElementById("entity-table-header"); + elEntityTableBody = document.getElementById("entity-table-body"); + elEntityTableScroll = document.getElementById("entity-table-scroll"); + elRefresh = document.getElementById("refresh"); + elToggleLocked = document.getElementById("locked"); + elToggleVisible = document.getElementById("visible"); + elDelete = document.getElementById("delete"); + elFilterTypeMultiselectBox = document.getElementById("filter-type-multiselect-box"); + elFilterTypeText = document.getElementById("filter-type-text"); + elFilterTypeOptions = document.getElementById("filter-type-options"); + elFilterTypeOptionsButtons = document.getElementById("filter-type-options-buttons"); + elFilterTypeSelectAll = document.getElementById('filter-type-select-all'); + elFilterTypeClearAll = document.getElementById('filter-type-clear-all'); + elFilterSearch = document.getElementById("filter-search"); + elFilterInView = document.getElementById("filter-in-view"); + elFilterRadius = document.getElementById("filter-radius"); + elExport = document.getElementById("export"); + elPal = document.getElementById("pal"); + elSelectedEntitiesCount = document.getElementById("selected-entities-count"); + elVisibleEntitiesCount = document.getElementById("visible-entities-count"); + elNoEntitiesMessage = document.getElementById("no-entities"); + elColumnsMultiselectBox = document.getElementById("entity-table-columns-multiselect-box"); + elColumnsOptions = document.getElementById("entity-table-columns-options"); + elToggleSpaceMode = document.getElementById('toggle-space-mode'); + + document.body.onclick = onBodyClick; + elToggleLocked.onclick = function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleLocked' })); + }; + elToggleVisible.onclick = function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleVisible' })); + }; + elExport.onclick = function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'export'})); + }; + elPal.onclick = function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'pal' })); + }; + elDelete.onclick = function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); + }; + elToggleSpaceMode.onclick = function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleSpaceMode' })); + }; + elRefresh.onclick = refreshEntities; + elFilterTypeMultiselectBox.onclick = onToggleTypeDropdown; + elFilterTypeSelectAll.onclick = onSelectAllTypes; + elFilterTypeClearAll.onclick = onClearAllTypes; + elFilterSearch.onkeyup = refreshEntityList; + elFilterSearch.onsearch = refreshEntityList; + elFilterInView.onclick = onToggleFilterInView; + elFilterRadius.onkeyup = onRadiusChange; + elFilterRadius.onchange = onRadiusChange; + elColumnsMultiselectBox.onclick = onToggleColumnsDropdown; + + // create filter type dropdown checkboxes with label and icon for each type + for (let i = 0; i < FILTER_TYPES.length; ++i) { + let type = FILTER_TYPES[i]; + let typeFilterID = "filter-type-" + type; + + let elDiv = document.createElement('div'); + elDiv.onclick = onToggleTypeFilter; + elFilterTypeOptions.insertBefore(elDiv, elFilterTypeOptionsButtons); + + let elInput = document.createElement('input'); + elInput.setAttribute("type", "checkbox"); + elInput.setAttribute("id", typeFilterID); + elInput.setAttribute("filterType", type); + elInput.checked = true; // all types are checked initially + elFilterTypeInputs[type] = elInput; + elDiv.appendChild(elInput); + + let elLabel = document.createElement('label'); + elLabel.setAttribute("for", typeFilterID); + elLabel.innerText = type; + elDiv.appendChild(elLabel); + + let elSpan = document.createElement('span'); + elSpan.setAttribute("class", "typeIcon"); + elSpan.innerHTML = ENTITY_TYPE_ICON[type]; + + elLabel.insertBefore(elSpan, elLabel.childNodes[0]); + + toggleTypeFilter(elInput, false); // add all types to the initial types filter + } + + // create columns + elHeaderTr = document.createElement("tr"); + elEntityTableHeader.appendChild(elHeaderTr); + let columnIndex = 0; + for (let columnID in COLUMNS) { + let columnData = COLUMNS[columnID]; + + let elTh = document.createElement("th"); + let thID = "entity-" + columnID; + elTh.setAttribute("id", thID); + elTh.setAttribute("columnIndex", columnIndex); + elTh.setAttribute("columnID", columnID); + if (columnData.glyph) { + let elGlyph = document.createElement("span"); + elGlyph.className = "glyph"; + elGlyph.innerHTML = columnData.columnHeader; + elTh.appendChild(elGlyph); + } else { + elTh.innerText = columnData.columnHeader; + } + elTh.onmousedown = function(event) { + if (event.target.nodeName === 'TH') { + elTargetTh = event.target; + targetColumnIndex = parseInt(elTargetTh.getAttribute("columnIndex")); + lastColumnSwapPosition = event.clientX; + } else if (event.target.nodeName === 'SPAN') { + elTargetSpan = event.target; + } + initialThEvent = event; + }; + + let elResizer = document.createElement("span"); + elResizer.className = "resizer"; + elResizer.innerHTML = " "; + elResizer.onmousedown = onStartResize; + elTh.appendChild(elResizer); + + let elSortOrder = document.createElement("span"); + elSortOrder.className = "sort-order"; + elTh.appendChild(elSortOrder); + elHeaderTr.appendChild(elTh); + + elSortOrders[columnID] = elSortOrder; + + // add column to columns dropdown if it is not set to be always shown + if (columnData.alwaysShown !== true) { + let columnDropdownID = "entity-table-column-" + columnID; + + let elDiv = document.createElement('div'); + elDiv.onclick = onToggleColumn; + elColumnsOptions.appendChild(elDiv); + + let elInput = document.createElement('input'); + elInput.setAttribute("type", "checkbox"); + elInput.setAttribute("id", columnDropdownID); + elInput.setAttribute("columnID", columnID); + elInput.checked = columnData.initiallyShown === true; + elDiv.appendChild(elInput); + + let elLabel = document.createElement('label'); + elLabel.setAttribute("for", columnDropdownID); + elLabel.innerText = columnData.dropdownLabel; + elDiv.appendChild(elLabel); + } + + let initialWidth = columnData.initiallyShown === true ? columnData.initialWidth : 0; + columns.push({ + columnID: columnID, + elTh: elTh, + elResizer: elResizer, + width: initialWidth, + data: columnData + }); + columnsByID[columnID] = columns[columnIndex]; + + ++columnIndex; + } + + elEntityTableHeaderRow = document.querySelectorAll("#entity-table thead th"); + + entityList = new ListView(elEntityTableBody, elEntityTableScroll, elEntityTableHeaderRow, createRow, updateRow, + clearRow, preRefresh, postRefresh, preRefresh, WINDOW_NONVARIABLE_HEIGHT); + + entityListContextMenu = new EntityListContextMenu(); + + function startRenamingEntity(entityID) { + renameLastEntityID = entityID; + let entity = entitiesByID[entityID]; + if (!entity || entity.locked || !entity.elRow) { + return; + } + + let elCell = entity.elRow.childNodes[getColumnIndex("name")]; + elRenameInput = document.createElement("input"); + elRenameInput.setAttribute('class', 'rename-entity'); + elRenameInput.value = entity.name; + let ignoreClicks = function(event) { + event.stopPropagation(); + }; + elRenameInput.onclick = ignoreClicks; + elRenameInput.ondblclick = ignoreClicks; + elRenameInput.onkeyup = function(keyEvent) { + if (keyEvent.key === "Enter") { + elRenameInput.blur(); + } + }; + + elRenameInput.onblur = function(event) { + if (isRenameFieldBeingMoved) { + return; + } + let value = elRenameInput.value; + EventBridge.emitWebEvent(JSON.stringify({ + type: 'rename', + entityID: entityID, + name: value + })); + entity.name = value; + elRenameInput.parentElement.innerText = value; + + renameLastBlur = Date.now(); + elRenameInput = null; + }; + + elCell.innerHTML = ""; + elCell.appendChild(elRenameInput); + + elRenameInput.select(); + } + + function preRefresh() { + // move the rename input to the body + if (!isRenameFieldBeingMoved && elRenameInput) { + isRenameFieldBeingMoved = true; + document.body.appendChild(elRenameInput); + // keep the focus + elRenameInput.focus(); + } + } + + function postRefresh() { + if (!elRenameInput || !isRenameFieldBeingMoved) { + return; + } + let entity = entitiesByID[renameLastEntityID]; + if (!entity || entity.locked || !entity.elRow) { + return; + } + let elCell = entity.elRow.childNodes[getColumnIndex("name")]; + elCell.innerHTML = ""; + elCell.appendChild(elRenameInput); + // keep the focus + elRenameInput.focus(); + isRenameFieldBeingMoved = false; + } + + entityListContextMenu.setOnSelectedCallback(function(optionName, selectedEntityID) { + switch (optionName) { + case "Cut": + EventBridge.emitWebEvent(JSON.stringify({ type: 'cut' })); + break; + 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) { + if (elRenameInput) { + // disallow the context menu from popping up while renaming + return; + } + + let entityID = this.dataset.entityID; + + if (!selectedEntities.includes(entityID)) { + let selection = [entityID]; + updateSelectedEntities(selection); + + EventBridge.emitWebEvent(JSON.stringify({ + type: "selectionUpdate", + focus: false, + entityIds: selection, + })); + } + + let enabledContextMenuItems = ['Copy', 'Paste', 'Duplicate']; + if (entitiesByID[entityID] && !entitiesByID[entityID].locked) { + enabledContextMenuItems.push('Cut'); + enabledContextMenuItems.push('Rename'); + enabledContextMenuItems.push('Delete'); + } + + entityListContextMenu.open(clickEvent, entityID, enabledContextMenuItems); + } + + let clearRenameTimeout = () => { + if (renameTimeout !== null) { + window.clearTimeout(renameTimeout); + renameTimeout = null; + } + }; + + function onRowClicked(clickEvent) { + let entityID = this.dataset.entityID; + let selection = [entityID]; + + if (clickEvent.ctrlKey) { + let selectedIndex = selectedEntities.indexOf(entityID); + if (selectedIndex >= 0) { + selection = []; + selection = selection.concat(selectedEntities); + selection.splice(selectedIndex, 1); + } else { + selection = selection.concat(selectedEntities); + } + } else if (clickEvent.shiftKey && selectedEntities.length > 0) { + let previousItemFound = -1; + let clickedItemFound = -1; + for (let i = 0, len = visibleEntities.length; i < len; ++i) { + let entity = visibleEntities[i]; + if (clickedItemFound === -1 && entityID === entity.id) { + clickedItemFound = i; + } else if (previousItemFound === -1 && selectedEntities[0] === entity.id) { + previousItemFound = i; + } + } + if (previousItemFound !== -1 && clickedItemFound !== -1) { + selection = []; + let toItem = Math.max(previousItemFound, clickedItemFound); + for (let i = Math.min(previousItemFound, clickedItemFound); i <= toItem; i++) { + selection.push(visibleEntities[i].id); + } + if (previousItemFound > clickedItemFound) { + // always make sure that we add the items in the right order + selection.reverse(); + } + } + } else if (!clickEvent.ctrlKey && !clickEvent.shiftKey && selectedEntities.length === 1) { + // if reselecting the same entity then start renaming it + if (selectedEntities[0] === entityID) { + if (renameLastBlur && renameLastEntityID === entityID && (Date.now() - renameLastBlur) < RENAME_COOLDOWN) { + + return; + } + clearRenameTimeout(); + renameTimeout = window.setTimeout(() => { + renameTimeout = null; + startRenamingEntity(entityID); + }, DOUBLE_CLICK_TIMEOUT); + } + } + + updateSelectedEntities(selection, false); + + EventBridge.emitWebEvent(JSON.stringify({ + type: "selectionUpdate", + focus: false, + entityIds: selection, + })); + } + + function onRowDoubleClicked() { + clearRenameTimeout(); + + let selection = [this.dataset.entityID]; + updateSelectedEntities(selection, false); + + EventBridge.emitWebEvent(JSON.stringify({ + type: "selectionUpdate", + focus: true, + entityIds: selection, + })); + } + + function updateEntityData(entityData) { + entities = []; + entitiesByID = {}; + visibleEntities.length = 0; // maintains itemData reference in ListView + + PROFILE("map-data", function() { + entityData.forEach(function(entity) { + let type = entity.type; + let filename = getFilename(entity.url); + + let entityData = { + id: entity.id, + name: entity.name, + type: type, + url: entity.certificateID === "" ? filename : "" + CERTIFIED_PLACEHOLDER + "", + fullUrl: entity.certificateID === "" ? filename : CERTIFIED_PLACEHOLDER, + locked: entity.locked, + visible: entity.visible, + certificateID: entity.certificateID, + verticesCount: displayIfNonZero(entity.verticesCount), + texturesCount: displayIfNonZero(entity.texturesCount), + texturesSize: entity.texturesSize, + hasTransparent: entity.hasTransparent, + isBaked: entity.isBaked, + drawCalls: displayIfNonZero(entity.drawCalls), + hasScript: entity.hasScript, + elRow: null, // if this entity has a visible row element assigned to it + selected: false // if this entity is selected for edit regardless of having a visible row + }; + + entities.push(entityData); + entitiesByID[entityData.id] = entityData; + }); + }); + + refreshEntityList(); + } + + const isNullOrEmpty = function(value) { + return value === undefined || value === null || value === ""; + }; + + function refreshEntityList() { + PROFILE("refresh-entity-list", function() { + PROFILE("filter", function() { + let searchTerm = elFilterSearch.value.toLowerCase(); + visibleEntities = entities.filter(function(e) { + let type = e.type === "Box" || e.type === "Sphere" ? "Shape" : e.type; + let typeFilter = typeFilters.indexOf(type) > -1; + let searchFilter = searchTerm === '' || (e.name.toLowerCase().indexOf(searchTerm) > -1 || + e.type.toLowerCase().indexOf(searchTerm) > -1 || + e.fullUrl.toLowerCase().indexOf(searchTerm) > -1 || + e.id.toLowerCase().indexOf(searchTerm) > -1); + return typeFilter && searchFilter; + }); + }); + + PROFILE("sort", function() { + let isAscendingSort = currentSortOrder === ASCENDING_SORT; + let isDefaultSort = currentSortOrder === COLUMNS[currentSortColumnID].defaultSortOrder; + visibleEntities.sort((entityA, entityB) => { + /** + * If the default sort is ascending, empty should be considered largest. + * If the default sort is descending, empty should be considered smallest. + */ + if (!isAscendingSort) { + [entityA, entityB] = [entityB, entityA]; + } + let valueA = entityA[currentSortColumnID]; + let valueB = entityB[currentSortColumnID]; + + if (valueA === valueB) { + return entityA.id < entityB.id ? -1 : 1; + } + + if (isNullOrEmpty(valueA)) { + return (isDefaultSort ? 1 : -1) * (isAscendingSort ? 1 : -1); + } + if (isNullOrEmpty(valueB)) { + return (isDefaultSort ? -1 : 1) * (isAscendingSort ? 1 : -1); + } + if (typeof(valueA) === "string") { + return valueA.localeCompare(valueB); + } + return valueA < valueB ? -1 : 1; + }); + }); + + PROFILE("update-dom", function() { + entityList.itemData = visibleEntities; + entityList.refresh(); + updateColumnWidths(); + }); + + refreshFooter(); + refreshNoEntitiesMessage(); + }); + } + + function removeEntities(deletedIDs) { + // Loop from the back so we can pop items off while iterating + + // delete any entities matching deletedIDs list from entities and entitiesByID lists + // if the entity had an associated row element then ensure row is unselected and clear it's entity + for (let j = entities.length - 1; j >= 0; --j) { + let id = entities[j].id; + for (let i = 0, length = deletedIDs.length; i < length; ++i) { + if (id === deletedIDs[i]) { + let elRow = entities[j].elRow; + if (elRow) { + elRow.className = ''; + elRow.dataset.entityID = EMPTY_ENTITY_ID; + } + entities.splice(j, 1); + delete entitiesByID[id]; + break; + } + } + } + + // delete any entities matching deletedIDs list from selectedEntities list + for (let j = selectedEntities.length - 1; j >= 0; --j) { + let id = selectedEntities[j].id; + for (let i = 0, length = deletedIDs.length; i < length; ++i) { + if (id === deletedIDs[i]) { + selectedEntities.splice(j, 1); + break; + } + } + } + + // delete any entities matching deletedIDs list from visibleEntities list + // if this was a row that was above our current row offset (a hidden top row in the top buffer), + // then decrease row offset accordingly + let firstVisibleRow = entityList.getFirstVisibleRowIndex(); + for (let j = visibleEntities.length - 1; j >= 0; --j) { + let id = visibleEntities[j].id; + for (let i = 0, length = deletedIDs.length; i < length; ++i) { + if (id === deletedIDs[i]) { + if (j < firstVisibleRow && entityList.rowOffset > 0) { + entityList.rowOffset--; + } + visibleEntities.splice(j, 1); + break; + } + } + } + + entityList.refresh(); + + refreshFooter(); + refreshNoEntitiesMessage(); + } + + function clearEntities() { + // clear the associated entity ID from all visible row elements + let firstVisibleRow = entityList.getFirstVisibleRowIndex(); + let lastVisibleRow = entityList.getLastVisibleRowIndex(); + for (let i = firstVisibleRow; i <= lastVisibleRow && i < visibleEntities.length; i++) { + let entity = visibleEntities[i]; + entity.elRow.dataset.entityID = EMPTY_ENTITY_ID; + } + + entities = []; + entitiesByID = {}; + visibleEntities.length = 0; // maintains itemData reference in ListView + + entityList.resetToTop(); + entityList.clear(); + + refreshFooter(); + refreshNoEntitiesMessage(); + } + + function setSortColumn(columnID) { + PROFILE("set-sort-column", function() { + if (currentSortColumnID === columnID) { + currentSortOrder *= -1; + } else { + elSortOrders[currentSortColumnID].innerHTML = ""; + currentSortColumnID = columnID; + currentSortOrder = COLUMNS[currentSortColumnID].defaultSortOrder; + } + refreshSortOrder(); + refreshEntityList(); + }); + } + + function refreshSortOrder() { + elSortOrders[currentSortColumnID].innerHTML = currentSortOrder === ASCENDING_SORT ? ASCENDING_STRING : DESCENDING_STRING; + } + + function refreshEntities() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'refresh' })); + } + + function refreshFooter() { + elSelectedEntitiesCount.innerText = selectedEntities.length; + elVisibleEntitiesCount.innerText = visibleEntities.length; + } + + function refreshNoEntitiesMessage() { + if (visibleEntities.length > 0) { + elNoEntitiesMessage.style.display = "none"; + } else { + elNoEntitiesMessage.style.display = "block"; + } + } + + function updateSelectedEntities(selectedIDs, autoScroll) { + let notFound = false; + + // reset all currently selected entities and their rows first + selectedEntities.forEach(function(id) { + let entity = entitiesByID[id]; + if (entity !== undefined) { + entity.selected = false; + if (entity.elRow) { + entity.elRow.className = ''; + } + } + }); + + // then reset selected entities list with newly selected entities and set them selected + selectedEntities = []; + selectedIDs.forEach(function(id) { + selectedEntities.push(id); + let entity = entitiesByID[id]; + if (entity !== undefined) { + entity.selected = true; + if (entity.elRow) { + entity.elRow.className = 'selected'; + } + } else { + notFound = true; + } + }); + + if (autoScroll && selectedIDs.length > 0) { + let firstItem = Number.MAX_VALUE; + let lastItem = -1; + let itemFound = false; + visibleEntities.forEach(function(entity, index) { + if (selectedIDs.indexOf(entity.id) !== -1) { + if (firstItem > index) { + firstItem = index; + } + if (lastItem < index) { + lastItem = index; + } + itemFound = true; + } + }); + if (itemFound) { + entityList.scrollToRow(firstItem, lastItem); + } + } + + elToggleSpaceMode.disabled = selectedIDs.length > 1; + + refreshFooter(); + + return notFound; + } + + function createRow() { + let elRow = document.createElement("tr"); + columns.forEach(function(column) { + let elRowColumn = document.createElement("td"); + elRowColumn.className = createColumnClassName(column.columnID); + elRow.appendChild(elRowColumn); + }); + elRow.oncontextmenu = onRowContextMenu; + elRow.onclick = onRowClicked; + elRow.ondblclick = onRowDoubleClicked; + return elRow; + } + + function updateRow(elRow, itemData) { + // update all column texts and glyphs to this entity's data + for (let i = 0; i < columns.length; ++i) { + let column = columns[i]; + let elCell = elRow.childNodes[i]; + if (column.data.glyph) { + elCell.innerHTML = itemData[column.data.propertyID] ? column.data.columnHeader : null; + } else { + let value = itemData[column.data.propertyID]; + if (column.data.format) { + value = column.data.format(value); + } + elCell.innerHTML = value; + } + elCell.style = "min-width:" + column.widthPx + "px;" + "max-width:" + column.widthPx + "px;"; + elCell.className = createColumnClassName(column.columnID); + } + + // if this entity was previously selected flag it's row as selected + if (itemData.selected) { + elRow.className = 'selected'; + } else { + elRow.className = ''; + } + + // if this row previously had an associated entity ID that wasn't the new entity ID then clear + // the ID from the row and the row element from the previous entity's data, then set the new + // entity ID to the row and the row element to the new entity's data + let prevEntityID = elRow.dataset.entityID; + let newEntityID = itemData.id; + let validPrevItemID = prevEntityID !== undefined && prevEntityID !== EMPTY_ENTITY_ID; + if (validPrevItemID && prevEntityID !== newEntityID && entitiesByID[prevEntityID].elRow === elRow) { + elRow.dataset.entityID = EMPTY_ENTITY_ID; + entitiesByID[prevEntityID].elRow = null; + } + if (!validPrevItemID || prevEntityID !== newEntityID) { + elRow.dataset.entityID = newEntityID; + entitiesByID[newEntityID].elRow = elRow; + } + } + + function clearRow(elRow) { + // reset all texts and glyphs for each of the row's columns + for (let i = 0; i < columns.length; ++i) { + let cell = elRow.childNodes[i]; + if (columns[i].data.glyph) { + cell.innerHTML = ""; + } else { + cell.innerText = ""; + } + } + + // clear the row from any associated entity + let entityID = elRow.dataset.entityID; + if (entityID && entitiesByID[entityID]) { + entitiesByID[entityID].elRow = null; + } + + // reset the row to hidden and clear the entity from the row + elRow.className = ''; + elRow.dataset.entityID = EMPTY_ENTITY_ID; + } + + function onToggleFilterInView() { + isFilterInView = !isFilterInView; + if (isFilterInView) { + elFilterInView.setAttribute(FILTER_IN_VIEW_ATTRIBUTE, FILTER_IN_VIEW_ATTRIBUTE); + } else { + elFilterInView.removeAttribute(FILTER_IN_VIEW_ATTRIBUTE); + } + EventBridge.emitWebEvent(JSON.stringify({ type: "filterInView", filterInView: isFilterInView })); + refreshEntities(); + } + + function onRadiusChange() { + elFilterRadius.value = elFilterRadius.value.replace(/[^0-9]/g, ''); + elFilterRadius.value = Math.max(elFilterRadius.value, 0); + EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elFilterRadius.value })); + refreshEntities(); + } + + function getColumnIndex(columnID) { + for (let i = 0; i < columns.length; ++i) { + if (columns[i].columnID === columnID) { + return i; + } + } + return -1; + } + + function createColumnClassName(columnID) { + let column = columnsByID[columnID]; + let visible = column.elTh.style.visibility !== "hidden"; + let className = column.data.glyph ? "glyph" : ""; + className += visible ? "" : " hidden"; + return className; + } + + function isColumnsDropdownVisible() { + return elColumnsOptions.style.display === "block"; + } + + function toggleColumnsDropdown() { + elColumnsOptions.style.display = isColumnsDropdownVisible() ? "none" : "block"; + } + + function onToggleColumnsDropdown(event) { + toggleColumnsDropdown(); + if (isTypeDropdownVisible()) { + toggleTypeDropdown(); + } + event.stopPropagation(); + } + + function toggleColumn(elInput, refresh) { + let columnID = elInput.getAttribute("columnID"); + let columnChecked = elInput.checked; + + if (columnChecked) { + let widthNeeded = columnsByID[columnID].data.initialWidth; + + let numberVisibleColumns = 0; + for (let i = 0; i < columns.length; ++i) { + let column = columns[i]; + if (column.columnID === columnID) { + column.width = widthNeeded; + } else if (column.width > 0) { + ++numberVisibleColumns; + } + } + + for (let i = 0; i < columns.length; ++i) { + let column = columns[i]; + if (column.columnID !== columnID && column.width > 0) { + column.width -= column.width * widthNeeded; + } + } + } else { + let widthLoss = 0; + + let numberVisibleColumns = 0; + for (let i = 0; i < columns.length; ++i) { + let column = columns[i]; + if (column.columnID === columnID) { + widthLoss = column.width; + column.width = 0; + } else if (column.width > 0) { + ++numberVisibleColumns; + } + } + + for (let i = 0; i < columns.length; ++i) { + let column = columns[i]; + if (column.columnID !== columnID && column.width > 0) { + let newTotalWidth = (1 - widthLoss); + column.width += (column.width / newTotalWidth) * widthLoss; + } + } + } + + updateColumnWidths(); + } + + function onToggleColumn(event) { + let elTarget = event.target; + if (elTarget instanceof HTMLInputElement) { + toggleColumn(elTarget, true); + } + event.stopPropagation(); + } + + function isTypeDropdownVisible() { + return elFilterTypeOptions.style.display === "block"; + } + + function toggleTypeDropdown() { + elFilterTypeOptions.style.display = isTypeDropdownVisible() ? "none" : "block"; + } + + function onToggleTypeDropdown(event) { + toggleTypeDropdown(); + if (isColumnsDropdownVisible()) { + toggleColumnsDropdown(); + } + event.stopPropagation(); + } + + function refreshTypeFilter(refreshList) { + if (typeFilters.length === 0) { + elFilterTypeText.innerText = "No Types"; + } else if (typeFilters.length === FILTER_TYPES.length) { + elFilterTypeText.innerText = "All Types"; + } else { + elFilterTypeText.innerText = "Types..."; + } + + if (refreshList) { + refreshEntityList(); + } + } + + function toggleTypeFilter(elInput, refreshList) { + let type = elInput.getAttribute("filterType"); + let typeChecked = elInput.checked; + + let typeFilterIndex = typeFilters.indexOf(type); + if (!typeChecked && typeFilterIndex > -1) { + typeFilters.splice(typeFilterIndex, 1); + } else if (typeChecked && typeFilterIndex === -1) { + typeFilters.push(type); + } + + refreshTypeFilter(refreshList); + } + + function onToggleTypeFilter(event) { + let elTarget = event.target; + if (elTarget instanceof HTMLInputElement) { + toggleTypeFilter(elTarget, true); + } + event.stopPropagation(); + } + + function onSelectAllTypes(event) { + for (let type in elFilterTypeInputs) { + elFilterTypeInputs[type].checked = true; + } + typeFilters = FILTER_TYPES; + refreshTypeFilter(true); + event.stopPropagation(); + } + + function onClearAllTypes(event) { + for (let type in elFilterTypeInputs) { + elFilterTypeInputs[type].checked = false; + } + typeFilters = []; + refreshTypeFilter(true); + event.stopPropagation(); + } + + function onBodyClick(event) { + // if clicking anywhere outside of the multiselect dropdowns (since click event bubbled up to onBodyClick and + // propagation wasn't stopped in the toggle type/column callbacks) and the dropdown is open then close it + if (isTypeDropdownVisible()) { + toggleTypeDropdown(); + } + if (isColumnsDropdownVisible()) { + toggleColumnsDropdown(); + } + } + + function onStartResize(event) { + lastResizeEvent = event; + resizeColumnIndex = parseInt(this.parentNode.getAttribute("columnIndex")); + event.stopPropagation(); + } + + function updateColumnWidths() { + let fullWidth = elEntityTableBody.offsetWidth; + let remainingWidth = fullWidth; + let scrollbarVisible = elEntityTableScroll.scrollHeight > elEntityTableScroll.clientHeight; + let resizerRight = scrollbarVisible ? SCROLLBAR_WIDTH - RESIZER_WIDTH/2 : -RESIZER_WIDTH/2; + let visibleColumns = 0; + + for (let i = columns.length - 1; i > 0; --i) { + let column = columns[i]; + column.widthPx = Math.ceil(column.width * fullWidth); + column.elTh.style = "min-width:" + column.widthPx + "px;" + "max-width:" + column.widthPx + "px;"; + let columnVisible = column.width > 0; + column.elTh.style.visibility = columnVisible ? "visible" : "hidden"; + if (column.elResizer) { + column.elResizer.style = "right:" + resizerRight + "px;"; + column.elResizer.style.visibility = columnVisible && visibleColumns > 0 ? "visible" : "hidden"; + } + resizerRight += column.widthPx; + remainingWidth -= column.widthPx; + if (columnVisible) { + ++visibleColumns; + } + } + + // assign all remaining space to the first column + let column = columns[0]; + column.widthPx = remainingWidth; + column.width = remainingWidth / fullWidth; + column.elTh.style = "min-width:" + column.widthPx + "px;" + "max-width:" + column.widthPx + "px;"; + let columnVisible = column.width > 0; + column.elTh.style.visibility = columnVisible ? "visible" : "hidden"; + if (column.elResizer) { + column.elResizer.style = "right:" + resizerRight + "px;"; + column.elResizer.style.visibility = columnVisible && visibleColumns > 0 ? "visible" : "hidden"; + } + + entityList.refresh(); + } + + function swapColumns(columnAIndex, columnBIndex) { + let columnA = columns[columnAIndex]; + let columnB = columns[columnBIndex]; + let columnATh = columns[columnAIndex].elTh; + let columnBTh = columns[columnBIndex].elTh; + let columnThParent = columnATh.parentNode; + columnThParent.removeChild(columnBTh); + columnThParent.insertBefore(columnBTh, columnATh); + columnATh.setAttribute("columnIndex", columnBIndex); + columnBTh.setAttribute("columnIndex", columnAIndex); + columnA.elResizer.setAttribute("columnIndex", columnBIndex); + columnB.elResizer.setAttribute("columnIndex", columnAIndex); + + for (let i = 0; i < visibleEntities.length; ++i) { + let elRow = visibleEntities[i].elRow; + if (elRow) { + let columnACell = elRow.childNodes[columnAIndex]; + let columnBCell = elRow.childNodes[columnBIndex]; + elRow.removeChild(columnBCell); + elRow.insertBefore(columnBCell, columnACell); + } + } + + columns[columnAIndex] = columnB; + columns[columnBIndex] = columnA; + + updateColumnWidths(); + } + + document.onmousemove = function(event) { + if (lastResizeEvent) { + startTh = null; + + let column = columns[resizeColumnIndex]; + + let nextColumnIndex = resizeColumnIndex + 1; + let nextColumn = columns[nextColumnIndex]; + while (nextColumn.width === 0) { + nextColumn = columns[++nextColumnIndex]; + } + + let fullWidth = elEntityTableBody.offsetWidth; + let dx = event.clientX - lastResizeEvent.clientX; + let dPct = dx / fullWidth; + + let newColWidth = column.width + dPct; + let newNextColWidth = nextColumn.width - dPct; + + if (newColWidth * fullWidth >= MINIMUM_COLUMN_WIDTH && newNextColWidth * fullWidth >= MINIMUM_COLUMN_WIDTH) { + column.width += dPct; + nextColumn.width -= dPct; + updateColumnWidths(); + lastResizeEvent = event; + } + } else if (elTargetTh) { + let dxFromInitial = event.clientX - initialThEvent.clientX; + if (Math.abs(dxFromInitial) >= DELTA_X_MOVE_COLUMNS_THRESHOLD) { + elTargetTh.className = "dragging"; + } + if (targetColumnIndex < columns.length - 1) { + let nextColumnIndex = targetColumnIndex + 1; + let nextColumnTh = columns[nextColumnIndex].elTh; + let nextColumnStartX = nextColumnTh.getBoundingClientRect().left; + if (event.clientX >= nextColumnStartX && event.clientX - lastColumnSwapPosition >= DELTA_X_COLUMN_SWAP_POSITION) { + swapColumns(targetColumnIndex, nextColumnIndex); + targetColumnIndex = nextColumnIndex; + lastColumnSwapPosition = event.clientX; + } + } + if (targetColumnIndex >= 1) { + let prevColumnIndex = targetColumnIndex - 1; + let prevColumnTh = columns[prevColumnIndex].elTh; + let prevColumnEndX = prevColumnTh.getBoundingClientRect().right; + if (event.clientX <= prevColumnEndX && lastColumnSwapPosition - event.clientX >= DELTA_X_COLUMN_SWAP_POSITION) { + swapColumns(prevColumnIndex, targetColumnIndex); + targetColumnIndex = prevColumnIndex; + lastColumnSwapPosition = event.clientX; + } + } + } else if (elTargetSpan) { + let dxFromInitial = event.clientX - initialThEvent.clientX; + if (Math.abs(dxFromInitial) >= DELTA_X_MOVE_COLUMNS_THRESHOLD) { + elTargetTh = elTargetSpan.parentNode; + elTargetTh.className = "dragging"; + targetColumnIndex = parseInt(elTargetTh.getAttribute("columnIndex")); + lastColumnSwapPosition = event.clientX; + elTargetSpan = null; + } + } + }; + + document.onmouseup = function(event) { + if (elTargetTh) { + if (elTargetTh.className !== "dragging" && elTargetTh === event.target) { + let columnID = elTargetTh.getAttribute("columnID"); + setSortColumn(columnID); + } + elTargetTh.className = ""; + } else if (elTargetSpan) { + let columnID = elTargetSpan.parentNode.getAttribute("columnID"); + setSortColumn(columnID); + } + lastResizeEvent = null; + elTargetTh = null; + elTargetSpan = null; + initialThEvent = null; + }; + + function setSpaceMode(spaceMode) { + if (spaceMode === "local") { + elToggleSpaceMode.className = "space-mode-local hifi-edit-button"; + elToggleSpaceMode.innerText = "Local"; + } else { + elToggleSpaceMode.className = "space-mode-world hifi-edit-button"; + elToggleSpaceMode.innerText = "World"; + } + } + + const KEY_CODES = { + BACKSPACE: 8, + DELETE: 46 + }; + + document.addEventListener("keyup", function (keyUpEvent) { + const FILTERED_NODE_NAMES = ["INPUT", "TEXTAREA"]; + if (FILTERED_NODE_NAMES.includes(keyUpEvent.target.nodeName)) { + return; + } + + let {code, key, keyCode, altKey, ctrlKey, metaKey, shiftKey} = keyUpEvent; + + let controlKey = window.navigator.platform.startsWith("Mac") ? metaKey : ctrlKey; + + let keyCodeString; + switch (keyCode) { + case KEY_CODES.DELETE: + keyCodeString = "Delete"; + break; + case KEY_CODES.BACKSPACE: + keyCodeString = "Backspace"; + break; + default: + keyCodeString = String.fromCharCode(keyUpEvent.keyCode); + break; + } + + if (controlKey && keyCodeString === "A") { + let visibleEntityIDs = visibleEntities.map(visibleEntity => visibleEntity.id); + let selectionIncludesAllVisibleEntityIDs = visibleEntityIDs.every(visibleEntityID => { + return selectedEntities.includes(visibleEntityID); + }); + + let selection = []; + + if (!selectionIncludesAllVisibleEntityIDs) { + selection = visibleEntityIDs; + } + + updateSelectedEntities(selection); + + EventBridge.emitWebEvent(JSON.stringify({ + type: "selectionUpdate", + focus: false, + entityIds: selection, + })); + + return; + } + + + EventBridge.emitWebEvent(JSON.stringify({ + type: 'keyUpEvent', + keyUpEvent: { + code, + key, + keyCode, + keyCodeString, + altKey, + controlKey, + shiftKey, + } + })); + }, false); + + if (window.EventBridge !== undefined) { + EventBridge.scriptEventReceived.connect(function(data) { + data = JSON.parse(data); + if (data.type === "clearEntityList") { + clearEntities(); + } else if (data.type === "selectionUpdate") { + let notFound = updateSelectedEntities(data.selectedIDs, true); + if (notFound) { + refreshEntities(); + } + } else if (data.type === "update" && data.selectedIDs !== undefined) { + PROFILE("update", function() { + let newEntities = data.entities; + if (newEntities) { + if (newEntities.length === 0) { + clearEntities(); + } else { + updateEntityData(newEntities); + updateSelectedEntities(data.selectedIDs, true); + } + } + setSpaceMode(data.spaceMode); + }); + } else if (data.type === "removeEntities" && data.deletedIDs !== undefined && data.selectedIDs !== undefined) { + removeEntities(data.deletedIDs); + updateSelectedEntities(data.selectedIDs, true); + } else if (data.type === "deleted" && data.ids) { + removeEntities(data.ids); + } else if (data.type === "setSpaceMode") { + setSpaceMode(data.spaceMode); + } + }); + } + + refreshSortOrder(); + refreshEntities(); + + window.addEventListener("resize", updateColumnWidths); + }); + + augmentSpinButtons(); + disableDragDrop(); + + document.addEventListener("contextmenu", function(event) { + entityListContextMenu.close(); + + // 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(); + }); +} diff --git a/scripts/simplifiedUI/system/html/js/entityListContextMenu.js b/scripts/simplifiedUI/system/html/js/entityListContextMenu.js new file mode 100644 index 0000000000..d71719f252 --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/entityListContextMenu.js @@ -0,0 +1,163 @@ +// +// 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._onSelectedCallback = null; + this._listItems = []; + this._initialize(); +} + +EntityListContextMenu.prototype = { + + /** + * @private + */ + _elContextMenu: null, + + /** + * @private + */ + _onSelectedCallback: null, + + /** + * @private + */ + _selectedEntityID: null, + + /** + * @private + */ + _listItems: 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 + * @param enabledOptions + */ + open: function(clickEvent, selectedEntityID, enabledOptions) { + this._selectedEntityID = selectedEntityID; + + this._listItems.forEach(function(listItem) { + let enabled = enabledOptions.includes(listItem.label); + listItem.enabled = enabled; + listItem.element.setAttribute('class', enabled ? '' : 'disabled'); + }); + + 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 callback for when a menu item is selected + * @param onSelectedCallback + */ + setOnSelectedCallback: function(onSelectedCallback) { + this._onSelectedCallback = onSelectedCallback; + }, + + /** + * Add a labeled item to the context menu + * @param itemLabel + * @private + */ + _addListItem: function(itemLabel) { + let elListItem = document.createElement("li"); + elListItem.innerText = itemLabel; + + let listItem = { + label: itemLabel, + element: elListItem, + enabled: false + }; + + elListItem.addEventListener("click", function () { + if (listItem.enabled && this._onSelectedCallback) { + this._onSelectedCallback.call(this, itemLabel, this._selectedEntityID); + } + }.bind(this), false); + + elListItem.setAttribute('class', 'disabled'); + + this._listItems.push(listItem); + 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. + * @private + */ + _initialize: function() { + this._elContextMenu = document.createElement("ul"); + this._elContextMenu.setAttribute("class", CONTEXT_MENU_CLASS); + document.body.appendChild(this._elContextMenu); + + this._addListItem("Cut"); + this._addListItem("Copy"); + this._addListItem("Paste"); + this._addListSeparator(); + this._addListItem("Rename"); + this._addListItem("Duplicate"); + 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)); + } +}; diff --git a/scripts/simplifiedUI/system/html/js/entityProperties.js b/scripts/simplifiedUI/system/html/js/entityProperties.js new file mode 100644 index 0000000000..e64543d41f --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/entityProperties.js @@ -0,0 +1,4419 @@ +// entityProperties.js +// +// Created by Ryan Huffman on 13 Nov 2014 +// Modified by David Back on 19 Oct 2018 +// Copyright 2014 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 + +/* global alert, augmentSpinButtons, clearTimeout, console, document, Element, + EventBridge, JSONEditor, openEventBridge, setTimeout, window, _, $ */ + +const DEGREES_TO_RADIANS = Math.PI / 180.0; + +const NO_SELECTION = ","; + +const PROPERTY_SPACE_MODE = Object.freeze({ + ALL: 0, + LOCAL: 1, + WORLD: 2 +}); + +const PROPERTY_SELECTION_VISIBILITY = Object.freeze({ + SINGLE_SELECTION: 1, + MULTIPLE_SELECTIONS: 2, + MULTI_DIFF_SELECTIONS: 4, + ANY_SELECTIONS: 7, /* SINGLE_SELECTION | MULTIPLE_SELECTIONS | MULTI_DIFF_SELECTIONS */ +}); + +// Multiple-selection behavior +const PROPERTY_MULTI_DISPLAY_MODE = Object.freeze({ + DEFAULT: 0, + /** + * Comma separated values + * Limited for properties with type "string" or "textarea" and readOnly enabled + */ + COMMA_SEPARATED_VALUES: 1, +}); + +const GROUPS = [ + { + id: "base", + properties: [ + { + label: NO_SELECTION, + type: "icon", + icons: ENTITY_TYPE_ICON, + propertyID: "type", + replaceID: "placeholder-property-type", + }, + { + label: "Name", + type: "string", + propertyID: "name", + placeholder: "Name", + replaceID: "placeholder-property-name", + }, + { + label: "ID", + type: "string", + propertyID: "id", + placeholder: "ID", + readOnly: true, + replaceID: "placeholder-property-id", + multiDisplayMode: PROPERTY_MULTI_DISPLAY_MODE.COMMA_SEPARATED_VALUES, + }, + { + label: "Description", + type: "string", + propertyID: "description", + }, + { + label: "Parent", + type: "string", + propertyID: "parentID", + onChange: parentIDChanged, + }, + { + label: "Parent Joint Index", + type: "number", + propertyID: "parentJointIndex", + }, + { + label: "", + glyph: "", + type: "bool", + propertyID: "locked", + replaceID: "placeholder-property-locked", + }, + { + label: "", + glyph: "", + type: "bool", + propertyID: "visible", + replaceID: "placeholder-property-visible", + }, + { + label: "Render Layer", + type: "dropdown", + options: { + world: "World", + front: "Front", + hud: "HUD" + }, + propertyID: "renderLayer", + }, + { + label: "Primitive Mode", + type: "dropdown", + options: { + solid: "Solid", + lines: "Wireframe", + }, + propertyID: "primitiveMode", + }, + ] + }, + { + id: "shape", + addToGroup: "base", + properties: [ + { + label: "Shape", + type: "dropdown", + options: { Cube: "Box", Sphere: "Sphere", Tetrahedron: "Tetrahedron", Octahedron: "Octahedron", + Icosahedron: "Icosahedron", Dodecahedron: "Dodecahedron", Hexagon: "Hexagon", + Triangle: "Triangle", Octagon: "Octagon", Cylinder: "Cylinder", Cone: "Cone", + Circle: "Circle", Quad: "Quad" }, + propertyID: "shape", + }, + { + label: "Color", + type: "color", + propertyID: "color", + }, + ] + }, + { + id: "text", + addToGroup: "base", + properties: [ + { + label: "Text", + type: "string", + propertyID: "text", + }, + { + label: "Text Color", + type: "color", + propertyID: "textColor", + }, + { + label: "Text Alpha", + type: "number-draggable", + min: 0, + max: 1, + step: 0.01, + decimals: 2, + propertyID: "textAlpha", + }, + { + label: "Background Color", + type: "color", + propertyID: "backgroundColor", + }, + { + label: "Background Alpha", + type: "number-draggable", + min: 0, + max: 1, + step: 0.01, + decimals: 2, + propertyID: "backgroundAlpha", + }, + { + label: "Line Height", + type: "number-draggable", + min: 0, + step: 0.001, + decimals: 4, + unit: "m", + propertyID: "lineHeight", + }, + { + label: "Billboard Mode", + type: "dropdown", + options: { none: "None", yaw: "Yaw", full: "Full"}, + propertyID: "textBillboardMode", + propertyName: "billboardMode", // actual entity property name + }, + { + label: "Top Margin", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "topMargin", + }, + { + label: "Right Margin", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "rightMargin", + }, + { + label: "Bottom Margin", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "bottomMargin", + }, + { + label: "Left Margin", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "leftMargin", + }, + ] + }, + { + id: "zone", + addToGroup: "base", + properties: [ + { + label: "Shape Type", + type: "dropdown", + options: { "box": "Box", "sphere": "Sphere", "ellipsoid": "Ellipsoid", + "cylinder-y": "Cylinder", "compound": "Use Compound Shape URL" }, + propertyID: "zoneShapeType", + propertyName: "shapeType", // actual entity property name + }, + { + label: "Compound Shape URL", + type: "string", + propertyID: "zoneCompoundShapeURL", + propertyName: "compoundShapeURL", // actual entity property name + }, + { + label: "Flying Allowed", + type: "bool", + propertyID: "flyingAllowed", + }, + { + label: "Ghosting Allowed", + type: "bool", + propertyID: "ghostingAllowed", + }, + { + label: "Filter", + type: "string", + propertyID: "filterURL", + }, + { + label: "Key Light", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyID: "keyLightMode", + + }, + { + label: "Key Light Color", + type: "color", + propertyID: "keyLight.color", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Light Intensity", + type: "number-draggable", + min: 0, + max: 40, + step: 0.01, + decimals: 2, + propertyID: "keyLight.intensity", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Light Horizontal Angle", + type: "number-draggable", + step: 0.1, + multiplier: DEGREES_TO_RADIANS, + decimals: 2, + unit: "deg", + propertyID: "keyLight.direction.y", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Light Vertical Angle", + type: "number-draggable", + step: 0.1, + multiplier: DEGREES_TO_RADIANS, + decimals: 2, + unit: "deg", + propertyID: "keyLight.direction.x", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Cast Shadows", + type: "bool", + propertyID: "keyLight.castShadows", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Skybox", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyID: "skyboxMode", + }, + { + label: "Skybox Color", + type: "color", + propertyID: "skybox.color", + showPropertyRule: { "skyboxMode": "enabled" }, + }, + { + label: "Skybox Source", + type: "string", + propertyID: "skybox.url", + showPropertyRule: { "skyboxMode": "enabled" }, + }, + { + label: "Ambient Light", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyID: "ambientLightMode", + }, + { + label: "Ambient Intensity", + type: "number-draggable", + min: 0, + max: 200, + step: 0.1, + decimals: 2, + propertyID: "ambientLight.ambientIntensity", + showPropertyRule: { "ambientLightMode": "enabled" }, + }, + { + label: "Ambient Source", + type: "string", + propertyID: "ambientLight.ambientURL", + showPropertyRule: { "ambientLightMode": "enabled" }, + }, + { + type: "buttons", + buttons: [ { id: "copy", label: "Copy from Skybox", + className: "black", onClick: copySkyboxURLToAmbientURL } ], + propertyID: "copyURLToAmbient", + showPropertyRule: { "ambientLightMode": "enabled" }, + }, + { + label: "Haze", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyID: "hazeMode", + }, + { + label: "Range", + type: "number-draggable", + min: 1, + max: 10000, + step: 1, + decimals: 0, + unit: "m", + propertyID: "haze.hazeRange", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Use Altitude", + type: "bool", + propertyID: "haze.hazeAltitudeEffect", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Base", + type: "number-draggable", + min: -1000, + max: 1000, + step: 1, + decimals: 0, + unit: "m", + propertyID: "haze.hazeBaseRef", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Ceiling", + type: "number-draggable", + min: -1000, + max: 5000, + step: 1, + decimals: 0, + unit: "m", + propertyID: "haze.hazeCeiling", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Haze Color", + type: "color", + propertyID: "haze.hazeColor", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Background Blend", + type: "number-draggable", + min: 0, + max: 1, + step: 0.001, + decimals: 3, + propertyID: "haze.hazeBackgroundBlend", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Enable Glare", + type: "bool", + propertyID: "haze.hazeEnableGlare", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Glare Color", + type: "color", + propertyID: "haze.hazeGlareColor", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Glare Angle", + type: "number-draggable", + min: 0, + max: 180, + step: 1, + decimals: 0, + propertyID: "haze.hazeGlareAngle", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Bloom", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyID: "bloomMode", + }, + { + label: "Bloom Intensity", + type: "number-draggable", + min: 0, + max: 1, + step: 0.001, + decimals: 3, + propertyID: "bloom.bloomIntensity", + showPropertyRule: { "bloomMode": "enabled" }, + }, + { + label: "Bloom Threshold", + type: "number-draggable", + min: 0, + max: 1, + step: 0.001, + decimals: 3, + propertyID: "bloom.bloomThreshold", + showPropertyRule: { "bloomMode": "enabled" }, + }, + { + label: "Bloom Size", + type: "number-draggable", + min: 0, + max: 2, + step: 0.001, + decimals: 3, + propertyID: "bloom.bloomSize", + showPropertyRule: { "bloomMode": "enabled" }, + }, + { + label: "Avatar Priority", + type: "dropdown", + options: { inherit: "Inherit", crowd: "Crowd", hero: "Hero" }, + propertyID: "avatarPriority", + }, + + ] + }, + { + id: "model", + addToGroup: "base", + properties: [ + { + label: "Model", + type: "string", + placeholder: "URL", + propertyID: "modelURL", + hideIfCertified: true, + }, + { + label: "Collision Shape", + type: "dropdown", + options: { "none": "No Collision", "box": "Box", "sphere": "Sphere", "compound": "Compound" , + "simple-hull": "Basic - Whole model", "simple-compound": "Good - Sub-meshes" , + "static-mesh": "Exact - All polygons (non-dynamic only)" }, + propertyID: "shapeType", + }, + { + label: "Compound Shape", + type: "string", + propertyID: "compoundShapeURL", + hideIfCertified: true, + }, + { + label: "Animation", + type: "string", + propertyID: "animation.url", + hideIfCertified: true, + }, + { + label: "Play Automatically", + type: "bool", + propertyID: "animation.running", + }, + { + label: "Loop", + type: "bool", + propertyID: "animation.loop", + }, + { + label: "Allow Transition", + type: "bool", + propertyID: "animation.allowTranslation", + }, + { + label: "Hold", + type: "bool", + propertyID: "animation.hold", + }, + { + label: "Animation Frame", + type: "number-draggable", + propertyID: "animation.currentFrame", + }, + { + label: "First Frame", + type: "number-draggable", + propertyID: "animation.firstFrame", + }, + { + label: "Last Frame", + type: "number-draggable", + propertyID: "animation.lastFrame", + }, + { + label: "Animation FPS", + type: "number-draggable", + propertyID: "animation.fps", + }, + { + label: "Texture", + type: "textarea", + propertyID: "textures", + }, + { + label: "Original Texture", + type: "textarea", + propertyID: "originalTextures", + readOnly: true, + hideIfCertified: true, + }, + { + label: "Group Culled", + type: "bool", + propertyID: "groupCulled", + }, + ] + }, + { + id: "image", + addToGroup: "base", + properties: [ + { + label: "Image", + type: "string", + placeholder: "URL", + propertyID: "imageURL", + }, + { + label: "Color", + type: "color", + propertyID: "imageColor", + propertyName: "color", // actual entity property name + }, + { + label: "Emissive", + type: "bool", + propertyID: "emissive", + }, + { + label: "Sub Image", + type: "rect", + min: 0, + step: 1, + subLabels: [ "x", "y", "w", "h" ], + propertyID: "subImage", + }, + { + label: "Billboard Mode", + type: "dropdown", + options: { none: "None", yaw: "Yaw", full: "Full"}, + propertyID: "imageBillboardMode", + propertyName: "billboardMode", // actual entity property name + }, + { + label: "Keep Aspect Ratio", + type: "bool", + propertyID: "keepAspectRatio", + }, + ] + }, + { + id: "web", + addToGroup: "base", + properties: [ + { + label: "Source", + type: "string", + propertyID: "sourceUrl", + }, + { + label: "Source Resolution", + type: "number-draggable", + propertyID: "dpi", + }, + { + label: "Web Color", + type: "color", + propertyID: "webColor", + propertyName: "color", // actual entity property name + }, + { + label: "Web Alpha", + type: "number-draggable", + step: 0.001, + decimals: 3, + propertyID: "webAlpha", + propertyName: "alpha", + min: 0, + max: 1, + }, + { + label: "Max FPS", + type: "number-draggable", + step: 1, + decimals: 0, + propertyID: "maxFPS", + }, + { + label: "Script URL", + type: "string", + propertyID: "scriptURL", + placeholder: "URL", + }, + ] + }, + { + id: "light", + addToGroup: "base", + properties: [ + { + label: "Light Color", + type: "color", + propertyID: "lightColor", + propertyName: "color", // actual entity property name + }, + { + label: "Intensity", + type: "number-draggable", + min: 0, + max: 10000, + step: 0.1, + decimals: 2, + propertyID: "intensity", + }, + { + label: "Fall-Off Radius", + type: "number-draggable", + min: 0, + max: 10000, + step: 0.1, + decimals: 2, + unit: "m", + propertyID: "falloffRadius", + }, + { + label: "Spotlight", + type: "bool", + propertyID: "isSpotlight", + }, + { + label: "Spotlight Exponent", + type: "number-draggable", + min: 0, + step: 0.01, + decimals: 2, + propertyID: "exponent", + }, + { + label: "Spotlight Cut-Off", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "cutoff", + }, + ] + }, + { + id: "material", + addToGroup: "base", + properties: [ + { + label: "Material URL", + type: "string", + propertyID: "materialURL", + }, + { + label: "Material Data", + type: "textarea", + buttons: [ { id: "clear", label: "Clear Material Data", className: "red", onClick: clearMaterialData }, + { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONMaterialEditor }, + { id: "save", label: "Save Material Data", className: "black", onClick: saveMaterialData } ], + propertyID: "materialData", + }, + { + label: "Material Target", + type: "dynamic-multiselect", + propertyUpdate: materialTargetPropertyUpdate, + propertyID: "parentMaterialName", + selectionVisibility: PROPERTY_SELECTION_VISIBILITY.SINGLE_SELECTION, + }, + { + label: "Priority", + type: "number-draggable", + min: 0, + propertyID: "priority", + }, + { + label: "Material Mapping Mode", + type: "dropdown", + options: { + uv: "UV space", projected: "3D projected" + }, + propertyID: "materialMappingMode", + }, + { + label: "Material Position", + type: "vec2", + vec2Type: "xyz", + min: 0, + max: 1, + step: 0.1, + decimals: 4, + subLabels: [ "x", "y" ], + propertyID: "materialMappingPos", + }, + { + label: "Material Scale", + type: "vec2", + vec2Type: "xyz", + min: 0, + step: 0.1, + decimals: 4, + subLabels: [ "x", "y" ], + propertyID: "materialMappingScale", + }, + { + label: "Material Rotation", + type: "number-draggable", + step: 0.1, + decimals: 2, + unit: "deg", + propertyID: "materialMappingRot", + }, + { + label: "Material Repeat", + type: "bool", + propertyID: "materialRepeat", + }, + ] + }, + { + id: "grid", + addToGroup: "base", + properties: [ + { + label: "Color", + type: "color", + propertyID: "gridColor", + propertyName: "color", // actual entity property name + }, + { + label: "Follow Camera", + type: "bool", + propertyID: "followCamera", + }, + { + label: "Major Grid Every", + type: "number-draggable", + min: 0, + step: 1, + decimals: 0, + propertyID: "majorGridEvery", + }, + { + label: "Minor Grid Every", + type: "number-draggable", + min: 0, + step: 0.01, + decimals: 2, + propertyID: "minorGridEvery", + }, + ] + }, + { + id: "particles", + addToGroup: "base", + properties: [ + { + label: "Emit", + type: "bool", + propertyID: "isEmitting", + }, + { + label: "Lifespan", + type: "number-draggable", + unit: "s", + step: 0.01, + decimals: 2, + propertyID: "lifespan", + }, + { + label: "Max Particles", + type: "number-draggable", + step: 1, + propertyID: "maxParticles", + }, + { + label: "Texture", + type: "texture", + propertyID: "particleTextures", + propertyName: "textures", // actual entity property name + }, + ] + }, + { + id: "particles_emit", + label: "EMIT", + isMinor: true, + properties: [ + { + label: "Emit Rate", + type: "number-draggable", + step: 1, + propertyID: "emitRate", + }, + { + label: "Emit Speed", + type: "number-draggable", + step: 0.1, + decimals: 2, + propertyID: "emitSpeed", + }, + { + label: "Speed Spread", + type: "number-draggable", + step: 0.1, + decimals: 2, + propertyID: "speedSpread", + }, + { + label: "Shape Type", + type: "dropdown", + options: { "box": "Box", "ellipsoid": "Ellipsoid", + "cylinder-y": "Cylinder", "circle": "Circle", "plane": "Plane", + "compound": "Use Compound Shape URL" }, + propertyID: "particleShapeType", + propertyName: "shapeType", + }, + { + label: "Compound Shape URL", + type: "string", + propertyID: "particleCompoundShapeURL", + propertyName: "compoundShapeURL", + }, + { + label: "Emit Dimensions", + type: "vec3", + vec3Type: "xyz", + step: 0.01, + round: 100, + subLabels: [ "x", "y", "z" ], + propertyID: "emitDimensions", + }, + { + label: "Emit Radius Start", + type: "number-draggable", + step: 0.001, + decimals: 3, + propertyID: "emitRadiusStart" + }, + { + label: "Emit Orientation", + type: "vec3", + vec3Type: "pyr", + step: 0.01, + round: 100, + subLabels: [ "x", "y", "z" ], + unit: "deg", + propertyID: "emitOrientation", + }, + { + label: "Trails", + type: "bool", + propertyID: "emitterShouldTrail", + }, + ] + }, + { + id: "particles_size", + label: "SIZE", + isMinor: true, + properties: [ + { + type: "triple", + label: "Size", + propertyID: "particleRadiusTriple", + properties: [ + { + label: "Start", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "radiusStart", + fallbackProperty: "particleRadius", + }, + { + label: "Middle", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "particleRadius", + }, + { + label: "Finish", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "radiusFinish", + fallbackProperty: "particleRadius", + }, + ] + }, + { + label: "Size Spread", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "radiusSpread", + }, + ] + }, + { + id: "particles_color", + label: "COLOR", + isMinor: true, + properties: [ + { + type: "triple", + label: "Color", + propertyID: "particleColorTriple", + properties: [ + { + label: "Start", + type: "color", + propertyID: "colorStart", + fallbackProperty: "color", + }, + { + label: "Middle", + type: "color", + propertyID: "particleColor", + propertyName: "color", // actual entity property name + }, + { + label: "Finish", + type: "color", + propertyID: "colorFinish", + fallbackProperty: "color", + }, + ] + }, + { + label: "Color Spread", + type: "color", + propertyID: "colorSpread", + }, + ] + }, + { + id: "particles_alpha", + label: "ALPHA", + isMinor: true, + properties: [ + { + type: "triple", + label: "Alpha", + propertyID: "particleAlphaTriple", + properties: [ + { + label: "Start", + type: "number-draggable", + step: 0.001, + decimals: 3, + propertyID: "alphaStart", + fallbackProperty: "alpha", + }, + { + label: "Middle", + type: "number-draggable", + step: 0.001, + decimals: 3, + propertyID: "alpha", + }, + { + label: "Finish", + type: "number-draggable", + step: 0.001, + decimals: 3, + propertyID: "alphaFinish", + fallbackProperty: "alpha", + }, + ] + }, + { + label: "Alpha Spread", + type: "number-draggable", + step: 0.001, + decimals: 3, + propertyID: "alphaSpread", + }, + ] + }, + { + id: "particles_acceleration", + label: "ACCELERATION", + isMinor: true, + properties: [ + { + label: "Emit Acceleration", + type: "vec3", + vec3Type: "xyz", + step: 0.01, + round: 100, + subLabels: [ "x", "y", "z" ], + propertyID: "emitAcceleration", + }, + { + label: "Acceleration Spread", + type: "vec3", + vec3Type: "xyz", + step: 0.01, + round: 100, + subLabels: [ "x", "y", "z" ], + propertyID: "accelerationSpread", + }, + ] + }, + { + id: "particles_spin", + label: "SPIN", + isMinor: true, + properties: [ + { + type: "triple", + label: "Spin", + propertyID: "particleSpinTriple", + properties: [ + { + label: "Start", + type: "number-draggable", + step: 0.1, + decimals: 2, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "spinStart", + fallbackProperty: "particleSpin", + }, + { + label: "Middle", + type: "number-draggable", + step: 0.1, + decimals: 2, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "particleSpin", + }, + { + label: "Finish", + type: "number-draggable", + step: 0.1, + decimals: 2, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "spinFinish", + fallbackProperty: "particleSpin", + }, + ] + }, + { + label: "Spin Spread", + type: "number-draggable", + step: 0.1, + decimals: 2, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "spinSpread", + }, + { + label: "Rotate with Entity", + type: "bool", + propertyID: "rotateWithEntity", + }, + ] + }, + { + id: "particles_constraints", + label: "CONSTRAINTS", + isMinor: true, + properties: [ + { + type: "triple", + label: "Horizontal Angle", + propertyID: "particlePolarTriple", + properties: [ + { + label: "Start", + type: "number-draggable", + step: 0.1, + decimals: 2, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "polarStart", + }, + { + label: "Finish", + type: "number-draggable", + step: 0.1, + decimals: 2, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "polarFinish", + }, + ], + }, + { + type: "triple", + label: "Vertical Angle", + propertyID: "particleAzimuthTriple", + properties: [ + { + label: "Start", + type: "number-draggable", + step: 0.1, + decimals: 2, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "azimuthStart", + }, + { + label: "Finish", + type: "number-draggable", + step: 0.1, + decimals: 2, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "azimuthFinish", + }, + ] + } + ] + }, + { + id: "spatial", + label: "SPATIAL", + properties: [ + { + label: "Position", + type: "vec3", + vec3Type: "xyz", + step: 0.1, + decimals: 4, + subLabels: [ "x", "y", "z" ], + unit: "m", + propertyID: "position", + spaceMode: PROPERTY_SPACE_MODE.WORLD, + }, + { + label: "Local Position", + type: "vec3", + vec3Type: "xyz", + step: 0.1, + decimals: 4, + subLabels: [ "x", "y", "z" ], + unit: "m", + propertyID: "localPosition", + spaceMode: PROPERTY_SPACE_MODE.LOCAL, + }, + { + label: "Rotation", + type: "vec3", + vec3Type: "pyr", + step: 0.1, + decimals: 4, + subLabels: [ "x", "y", "z" ], + unit: "deg", + propertyID: "rotation", + spaceMode: PROPERTY_SPACE_MODE.WORLD, + }, + { + label: "Local Rotation", + type: "vec3", + vec3Type: "pyr", + step: 0.1, + decimals: 4, + subLabels: [ "x", "y", "z" ], + unit: "deg", + propertyID: "localRotation", + spaceMode: PROPERTY_SPACE_MODE.LOCAL, + }, + { + label: "Dimensions", + type: "vec3", + vec3Type: "xyz", + step: 0.01, + decimals: 4, + subLabels: [ "x", "y", "z" ], + unit: "m", + propertyID: "dimensions", + spaceMode: PROPERTY_SPACE_MODE.WORLD, + }, + { + label: "Local Dimensions", + type: "vec3", + vec3Type: "xyz", + step: 0.01, + decimals: 4, + subLabels: [ "x", "y", "z" ], + unit: "m", + propertyID: "localDimensions", + spaceMode: PROPERTY_SPACE_MODE.LOCAL, + }, + { + label: "Scale", + type: "number-draggable", + defaultValue: 100, + unit: "%", + buttons: [ { id: "rescale", label: "Rescale", className: "blue", onClick: rescaleDimensions }, + { id: "reset", label: "Reset Dimensions", className: "red", onClick: resetToNaturalDimensions } ], + propertyID: "scale", + }, + { + label: "Pivot", + type: "vec3", + vec3Type: "xyz", + step: 0.001, + decimals: 4, + subLabels: [ "x", "y", "z" ], + unit: "(ratio of dimension)", + propertyID: "registrationPoint", + }, + { + label: "Align", + type: "buttons", + buttons: [ { id: "selection", label: "Selection to Grid", className: "black", onClick: moveSelectionToGrid }, + { id: "all", label: "All to Grid", className: "black", onClick: moveAllToGrid } ], + propertyID: "alignToGrid", + }, + ] + }, + { + id: "behavior", + label: "BEHAVIOR", + properties: [ + { + label: "Grabbable", + type: "bool", + propertyID: "grab.grabbable", + }, + { + label: "Cloneable", + type: "bool", + propertyID: "cloneable", + }, + { + label: "Clone Lifetime", + type: "number-draggable", + min: -1, + unit: "s", + propertyID: "cloneLifetime", + showPropertyRule: { "cloneable": "true" }, + }, + { + label: "Clone Limit", + type: "number-draggable", + min: 0, + propertyID: "cloneLimit", + showPropertyRule: { "cloneable": "true" }, + }, + { + label: "Clone Dynamic", + type: "bool", + propertyID: "cloneDynamic", + showPropertyRule: { "cloneable": "true" }, + }, + { + label: "Clone Avatar Entity", + type: "bool", + propertyID: "cloneAvatarEntity", + showPropertyRule: { "cloneable": "true" }, + }, + { + label: "Triggerable", + type: "bool", + propertyID: "grab.triggerable", + }, + { + label: "Follow Controller", + type: "bool", + propertyID: "grab.grabFollowsController", + }, + { + label: "Cast Shadows", + type: "bool", + propertyID: "canCastShadow", + }, + { + label: "Link", + type: "string", + propertyID: "href", + placeholder: "URL", + }, + { + label: "Ignore Pick Intersection", + type: "bool", + propertyID: "ignorePickIntersection", + }, + { + label: "Script", + type: "string", + buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ], + propertyID: "script", + placeholder: "URL", + hideIfCertified: true, + }, + { + label: "Server Script", + type: "string", + buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadServerScripts } ], + propertyID: "serverScripts", + placeholder: "URL", + }, + { + label: "Server Script Status", + type: "placeholder", + indentedLabel: true, + propertyID: "serverScriptStatus", + selectionVisibility: PROPERTY_SELECTION_VISIBILITY.SINGLE_SELECTION, + }, + { + label: "Lifetime", + type: "number", + unit: "s", + propertyID: "lifetime", + }, + { + label: "User Data", + type: "textarea", + buttons: [ { id: "clear", label: "Clear User Data", className: "red", onClick: clearUserData }, + { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor }, + { id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ], + propertyID: "userData", + }, + ] + }, + { + id: "collision", + label: "COLLISION", + properties: [ + { + label: "Collides", + type: "bool", + inverse: true, + propertyID: "collisionless", + }, + { + label: "Static Entities", + type: "bool", + propertyID: "collidesWithStatic", + propertyName: "static", // actual subProperty name + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + }, + { + label: "Kinematic Entities", + type: "bool", + propertyID: "collidesWithKinematic", + propertyName: "kinematic", // actual subProperty name + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + }, + { + label: "Dynamic Entities", + type: "bool", + propertyID: "collidesWithDynamic", + propertyName: "dynamic", // actual subProperty name + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + }, + { + label: "My Avatar", + type: "bool", + propertyID: "collidesWithMyAvatar", + propertyName: "myAvatar", // actual subProperty name + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + }, + { + label: "Other Avatars", + type: "bool", + propertyID: "collidesWithOtherAvatar", + propertyName: "otherAvatar", // actual subProperty name + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + }, + { + label: "Collision Sound", + type: "string", + placeholder: "URL", + propertyID: "collisionSoundURL", + showPropertyRule: { "collisionless": "false" }, + hideIfCertified: true, + }, + { + label: "Dynamic", + type: "bool", + propertyID: "dynamic", + }, + ] + }, + { + id: "physics", + label: "PHYSICS", + properties: [ + { + label: "Linear Velocity", + type: "vec3", + vec3Type: "xyz", + step: 0.01, + decimals: 4, + subLabels: [ "x", "y", "z" ], + unit: "m/s", + propertyID: "localVelocity", + }, + { + label: "Linear Damping", + type: "number-draggable", + min: 0, + max: 1, + step: 0.001, + decimals: 4, + propertyID: "damping", + }, + { + label: "Angular Velocity", + type: "vec3", + vec3Type: "pyr", + multiplier: DEGREES_TO_RADIANS, + decimals: 4, + subLabels: [ "x", "y", "z" ], + unit: "deg/s", + propertyID: "localAngularVelocity", + }, + { + label: "Angular Damping", + type: "number-draggable", + min: 0, + max: 1, + step: 0.001, + decimals: 4, + propertyID: "angularDamping", + }, + { + label: "Bounciness", + type: "number-draggable", + step: 0.001, + decimals: 4, + propertyID: "restitution", + }, + { + label: "Friction", + type: "number-draggable", + step: 0.01, + decimals: 4, + propertyID: "friction", + }, + { + label: "Density", + type: "number-draggable", + step: 1, + decimals: 4, + propertyID: "density", + }, + { + label: "Gravity", + type: "vec3", + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + step: 0.1, + decimals: 4, + unit: "m/s2", + propertyID: "gravity", + }, + { + label: "Acceleration", + type: "vec3", + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + step: 0.1, + decimals: 4, + unit: "m/s2", + propertyID: "acceleration", + }, + ] + }, +]; + +const GROUPS_PER_TYPE = { + None: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], + Shape: [ 'base', 'shape', 'spatial', 'behavior', 'collision', 'physics' ], + Text: [ 'base', 'text', 'spatial', 'behavior', 'collision', 'physics' ], + Zone: [ 'base', 'zone', 'spatial', 'behavior', 'physics' ], + Model: [ 'base', 'model', 'spatial', 'behavior', 'collision', 'physics' ], + Image: [ 'base', 'image', 'spatial', 'behavior', 'collision', 'physics' ], + Web: [ 'base', 'web', 'spatial', 'behavior', 'collision', 'physics' ], + Light: [ 'base', 'light', 'spatial', 'behavior', 'collision', 'physics' ], + Material: [ 'base', 'material', 'spatial', 'behavior' ], + ParticleEffect: [ 'base', 'particles', 'particles_emit', 'particles_size', 'particles_color', 'particles_alpha', + 'particles_acceleration', 'particles_spin', 'particles_constraints', 'spatial', 'behavior', 'physics' ], + PolyLine: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], + PolyVox: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], + Grid: [ 'base', 'grid', 'spatial', 'behavior', 'physics' ], + Multiple: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], +}; + +const EDITOR_TIMEOUT_DURATION = 1500; +const DEBOUNCE_TIMEOUT = 125; + +const COLOR_MIN = 0; +const COLOR_MAX = 255; +const COLOR_STEP = 1; + +const MATERIAL_PREFIX_STRING = "mat::"; + +const PENDING_SCRIPT_STATUS = "[ Fetching status ]"; +const NOT_RUNNING_SCRIPT_STATUS = "Not running"; +const ENTITY_SCRIPT_STATUS = { + pending: "Pending", + loading: "Loading", + error_loading_script: "Error loading script", // eslint-disable-line camelcase + error_running_script: "Error running script", // eslint-disable-line camelcase + running: "Running", + unloaded: "Unloaded" +}; + +const ENABLE_DISABLE_SELECTOR = "input, textarea, span, .dropdown dl, .color-picker"; + +const PROPERTY_NAME_DIVISION = { + GROUP: 0, + PROPERTY: 1, + SUB_PROPERTY: 2, +}; + +const RECT_ELEMENTS = { + X_NUMBER: 0, + Y_NUMBER: 1, + WIDTH_NUMBER: 2, + HEIGHT_NUMBER: 3, +}; + +const VECTOR_ELEMENTS = { + X_NUMBER: 0, + Y_NUMBER: 1, + Z_NUMBER: 2, +}; + +const COLOR_ELEMENTS = { + COLOR_PICKER: 0, + RED_NUMBER: 1, + GREEN_NUMBER: 2, + BLUE_NUMBER: 3, +}; + +const TEXTURE_ELEMENTS = { + IMAGE: 0, + TEXT_INPUT: 1, +}; + +const JSON_EDITOR_ROW_DIV_INDEX = 2; + +let elGroups = {}; +let properties = {}; +let propertyRangeRequests = []; +let colorPickers = {}; +let particlePropertyUpdates = {}; +let selectedEntityIDs = new Set(); +let currentSelections = []; +let createAppTooltip = new CreateAppTooltip(); +let currentSpaceMode = PROPERTY_SPACE_MODE.LOCAL; + + +function createElementFromHTML(htmlString) { + let elTemplate = document.createElement('template'); + elTemplate.innerHTML = htmlString.trim(); + return elTemplate.content.firstChild; +} + +function isFlagSet(value, flag) { + return (value & flag) === flag; +} + +/** + * GENERAL PROPERTY/GROUP FUNCTIONS + */ + +function getPropertyInputElement(propertyID) { + let property = properties[propertyID]; + switch (property.data.type) { + case 'string': + case 'number': + case 'bool': + case 'dropdown': + case 'textarea': + case 'texture': + return property.elInput; + case 'number-draggable': + return property.elNumber.elInput; + case 'rect': + return { + x: property.elNumberX.elInput, + y: property.elNumberY.elInput, + width: property.elNumberWidth.elInput, + height: property.elNumberHeight.elInput + }; + case 'vec3': + case 'vec2': + return { x: property.elNumberX.elInput, y: property.elNumberY.elInput, z: property.elNumberZ.elInput }; + case 'color': + return { red: property.elNumberR.elInput, green: property.elNumberG.elInput, blue: property.elNumberB.elInput }; + case 'icon': + return property.elLabel; + case 'dynamic-multiselect': + return property.elDivOptions; + default: + return undefined; + } +} + +function enableChildren(el, selector) { + let elSelectors = el.querySelectorAll(selector); + for (let selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { + elSelectors[selectorIndex].removeAttribute('disabled'); + } +} + +function disableChildren(el, selector) { + let elSelectors = el.querySelectorAll(selector); + for (let selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { + elSelectors[selectorIndex].setAttribute('disabled', 'disabled'); + } +} + +function enableProperties() { + enableChildren(document.getElementById("properties-list"), ENABLE_DISABLE_SELECTOR); + enableChildren(document, ".colpick"); + + let elLocked = getPropertyInputElement("locked"); + if (elLocked.checked === false) { + removeStaticUserData(); + removeStaticMaterialData(); + } +} + +function disableProperties() { + disableChildren(document.getElementById("properties-list"), ENABLE_DISABLE_SELECTOR); + disableChildren(document, ".colpick"); + for (let pickKey in colorPickers) { + colorPickers[pickKey].colpickHide(); + } + + let elLocked = getPropertyInputElement("locked"); + if (elLocked.checked === true) { + if ($('#property-userData-editor').css('display') === "block") { + showStaticUserData(); + } + if ($('#property-materialData-editor').css('display') === "block") { + showStaticMaterialData(); + } + } +} + +function showPropertyElement(propertyID, show) { + setPropertyVisibility(properties[propertyID], show); +} + +function setPropertyVisibility(property, visible) { + property.elContainer.style.display = visible ? null : "none"; +} + +function resetProperties() { + for (let propertyID in properties) { + let property = properties[propertyID]; + let propertyData = property.data; + + switch (propertyData.type) { + case 'number': + case 'string': { + property.elInput.classList.remove('multi-diff'); + if (propertyData.defaultValue !== undefined) { + property.elInput.value = propertyData.defaultValue; + } else { + property.elInput.value = ""; + } + break; + } + case 'bool': { + property.elInput.classList.remove('multi-diff'); + property.elInput.checked = false; + break; + } + case 'number-draggable': { + if (propertyData.defaultValue !== undefined) { + property.elNumber.setValue(propertyData.defaultValue, false); + } else { + property.elNumber.setValue("", false); + } + break; + } + case 'rect': { + property.elNumberX.setValue("", false); + property.elNumberY.setValue("", false); + property.elNumberWidth.setValue("", false); + property.elNumberHeight.setValue("", false); + break; + } + case 'vec3': + case 'vec2': { + property.elNumberX.setValue("", false); + property.elNumberY.setValue("", false); + if (property.elNumberZ !== undefined) { + property.elNumberZ.setValue("", false); + } + break; + } + case 'color': { + property.elColorPicker.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + property.elNumberR.setValue("", false); + property.elNumberG.setValue("", false); + property.elNumberB.setValue("", false); + break; + } + case 'dropdown': { + property.elInput.classList.remove('multi-diff'); + property.elInput.value = ""; + setDropdownText(property.elInput); + break; + } + case 'textarea': { + property.elInput.classList.remove('multi-diff'); + property.elInput.value = ""; + setTextareaScrolling(property.elInput); + break; + } + case 'icon': { + property.elSpan.style.display = "none"; + break; + } + case 'texture': { + property.elInput.classList.remove('multi-diff'); + property.elInput.value = ""; + property.elInput.imageLoad(property.elInput.value); + break; + } + case 'dynamic-multiselect': { + resetDynamicMultiselectProperty(property.elDivOptions); + break; + } + } + + let showPropertyRules = properties[propertyID].showPropertyRules; + if (showPropertyRules !== undefined) { + for (let propertyToHide in showPropertyRules) { + showPropertyElement(propertyToHide, false); + } + } + } + + resetServerScriptStatus(); +} + +function resetServerScriptStatus() { + let elServerScriptError = document.getElementById("property-serverScripts-error"); + let elServerScriptStatus = document.getElementById("property-serverScripts-status"); + elServerScriptError.parentElement.style.display = "none"; + elServerScriptStatus.innerText = NOT_RUNNING_SCRIPT_STATUS; +} + +function showGroupsForType(type) { + if (type === "Box" || type === "Sphere") { + showGroupsForTypes(["Shape"]); + return; + } + showGroupsForTypes([type]); +} + +function getGroupsForTypes(types) { + return Object.keys(elGroups).filter((groupKey) => { + return types.map(type => GROUPS_PER_TYPE[type].includes(groupKey)).every(function (hasGroup) { + return hasGroup; + }); + }); +} + +function showGroupsForTypes(types) { + Object.entries(elGroups).forEach(([groupKey, elGroup]) => { + if (types.map(type => GROUPS_PER_TYPE[type].includes(groupKey)).every(function (hasGroup) { return hasGroup; })) { + elGroup.style.display = "block"; + } else { + elGroup.style.display = "none"; + } + }); +} + +function getFirstSelectedID() { + if (selectedEntityIDs.size === 0) { + return null; + } + return selectedEntityIDs.values().next().value; +} + +/** + * Returns true when the user is currently dragging the numeric slider control of the property + * @param propertyName - name of property + * @returns {boolean} currentlyDragging + */ +function isCurrentlyDraggingProperty(propertyName) { + return properties[propertyName] && properties[propertyName].dragging === true; +} + +const SUPPORTED_FALLBACK_TYPES = ['number', 'number-draggable', 'rect', 'vec3', 'vec2', 'color']; + +function getMultiplePropertyValue(originalPropertyName) { + // if this is a compound property name (i.e. animation.running) + // then split it by . up to 3 times to find property value + + let propertyData = null; + if (properties[originalPropertyName] !== undefined) { + propertyData = properties[originalPropertyName].data; + } + + let propertyValues = []; + let splitPropertyName = originalPropertyName.split('.'); + if (splitPropertyName.length > 1) { + let propertyGroupName = splitPropertyName[PROPERTY_NAME_DIVISION.GROUP]; + let propertyName = splitPropertyName[PROPERTY_NAME_DIVISION.PROPERTY]; + propertyValues = currentSelections.map(selection => { + let groupProperties = selection.properties[propertyGroupName]; + if (groupProperties === undefined || groupProperties[propertyName] === undefined) { + return undefined; + } + if (splitPropertyName.length === PROPERTY_NAME_DIVISION.SUB_PROPERTY + 1) { + let subPropertyName = splitPropertyName[PROPERTY_NAME_DIVISION.SUB_PROPERTY]; + return groupProperties[propertyName][subPropertyName]; + } else { + return groupProperties[propertyName]; + } + }); + } else { + propertyValues = currentSelections.map(selection => selection.properties[originalPropertyName]); + } + + if (propertyData !== null && propertyData.fallbackProperty !== undefined && + SUPPORTED_FALLBACK_TYPES.includes(propertyData.type)) { + + let fallbackMultiValue = null; + + for (let i = 0; i < propertyValues.length; ++i) { + let isPropertyNotNumber = false; + let propertyValue = propertyValues[i]; + if (propertyValue === undefined) { + continue; + } + switch (propertyData.type) { + case 'number': + case 'number-draggable': + isPropertyNotNumber = isNaN(propertyValue) || propertyValue === null; + break; + case 'rect': + case 'vec3': + case 'vec2': + isPropertyNotNumber = isNaN(propertyValue.x) || propertyValue.x === null; + break; + case 'color': + isPropertyNotNumber = isNaN(propertyValue.red) || propertyValue.red === null; + break; + } + if (isPropertyNotNumber) { + if (fallbackMultiValue === null) { + fallbackMultiValue = getMultiplePropertyValue(propertyData.fallbackProperty); + } + propertyValues[i] = fallbackMultiValue.values[i]; + } + } + } + + const firstValue = propertyValues[0]; + const isMultiDiffValue = !propertyValues.every((x) => deepEqual(firstValue, x)); + + if (isMultiDiffValue) { + return { + value: undefined, + values: propertyValues, + isMultiDiffValue: true + } + } + + return { + value: propertyValues[0], + values: propertyValues, + isMultiDiffValue: false + }; +} + +/** + * Retrieve more detailed info for differing Numeric MultiplePropertyValue + * @param multiplePropertyValue - input multiplePropertyValue + * @param propertyData + * @returns {{keys: *[], propertyComponentDiff, averagePerPropertyComponent}} + */ +function getDetailedNumberMPVDiff(multiplePropertyValue, propertyData) { + let detailedValues = {}; + // Fixed numbers can't be easily averaged since they're strings, so lets keep an array of unmodified numbers + let unmodifiedValues = {}; + const DEFAULT_KEY = 0; + let uniqueKeys = new Set([]); + multiplePropertyValue.values.forEach(function(propertyValue) { + if (typeof propertyValue === "object") { + Object.entries(propertyValue).forEach(function([key, value]) { + if (!uniqueKeys.has(key)) { + uniqueKeys.add(key); + detailedValues[key] = []; + unmodifiedValues[key] = []; + } + detailedValues[key].push(applyInputNumberPropertyModifiers(value, propertyData)); + unmodifiedValues[key].push(value); + }); + } else { + if (!uniqueKeys.has(DEFAULT_KEY)) { + uniqueKeys.add(DEFAULT_KEY); + detailedValues[DEFAULT_KEY] = []; + unmodifiedValues[DEFAULT_KEY] = []; + } + detailedValues[DEFAULT_KEY].push(applyInputNumberPropertyModifiers(propertyValue, propertyData)); + unmodifiedValues[DEFAULT_KEY].push(propertyValue); + } + }); + let keys = [...uniqueKeys]; + + let propertyComponentDiff = {}; + Object.entries(detailedValues).forEach(function([key, value]) { + propertyComponentDiff[key] = [...new Set(value)].length > 1; + }); + + let averagePerPropertyComponent = {}; + Object.entries(unmodifiedValues).forEach(function([key, value]) { + let average = value.reduce((a, b) => a + b) / value.length; + averagePerPropertyComponent[key] = applyInputNumberPropertyModifiers(average, propertyData); + }); + + return { + keys, + propertyComponentDiff, + averagePerPropertyComponent, + }; +} + +function getDetailedSubPropertyMPVDiff(multiplePropertyValue, subPropertyName) { + let isChecked = false; + let checkedValues = multiplePropertyValue.values.map((value) => value.split(",").includes(subPropertyName)); + let isMultiDiff = !checkedValues.every(value => value === checkedValues[0]); + if (!isMultiDiff) { + isChecked = checkedValues[0]; + } + return { + isChecked, + isMultiDiff + } +} + +function updateVisibleSpaceModeProperties() { + for (let propertyID in properties) { + if (properties.hasOwnProperty(propertyID)) { + let property = properties[propertyID]; + let propertySpaceMode = property.spaceMode; + let elProperty = properties[propertyID].elContainer; + if (propertySpaceMode !== PROPERTY_SPACE_MODE.ALL && propertySpaceMode !== currentSpaceMode) { + elProperty.classList.add('spacemode-hidden'); + } else { + elProperty.classList.remove('spacemode-hidden'); + } + } + } +} + +/** + * PROPERTY UPDATE FUNCTIONS + */ + +function createPropertyUpdateObject(originalPropertyName, propertyValue) { + let propertyUpdate = {}; + // if this is a compound property name (i.e. animation.running) then split it by . up to 3 times + let splitPropertyName = originalPropertyName.split('.'); + if (splitPropertyName.length > 1) { + let propertyGroupName = splitPropertyName[PROPERTY_NAME_DIVISION.GROUP]; + let propertyName = splitPropertyName[PROPERTY_NAME_DIVISION.PROPERTY]; + propertyUpdate[propertyGroupName] = {}; + if (splitPropertyName.length === PROPERTY_NAME_DIVISION.SUB_PROPERTY + 1) { + let subPropertyName = splitPropertyName[PROPERTY_NAME_DIVISION.SUB_PROPERTY]; + propertyUpdate[propertyGroupName][propertyName] = {}; + propertyUpdate[propertyGroupName][propertyName][subPropertyName] = propertyValue; + } else { + propertyUpdate[propertyGroupName][propertyName] = propertyValue; + } + } else { + propertyUpdate[originalPropertyName] = propertyValue; + } + return propertyUpdate; +} + +function updateProperty(originalPropertyName, propertyValue, isParticleProperty) { + let propertyUpdate = createPropertyUpdateObject(originalPropertyName, propertyValue); + + // queue up particle property changes with the debounced sync to avoid + // causing particle emitting to reset excessively with each value change + if (isParticleProperty) { + Object.keys(propertyUpdate).forEach(function (propertyUpdateKey) { + particlePropertyUpdates[propertyUpdateKey] = propertyUpdate[propertyUpdateKey]; + }); + particleSyncDebounce(); + } else { + // only update the entity property value itself if in the middle of dragging + // prevent undo command push, saving new property values, and property update + // callback until drag is complete (additional update sent via dragEnd callback) + let onlyUpdateEntity = isCurrentlyDraggingProperty(originalPropertyName); + updateProperties(propertyUpdate, onlyUpdateEntity); + } +} + +let particleSyncDebounce = _.debounce(function () { + updateProperties(particlePropertyUpdates); + particlePropertyUpdates = {}; +}, DEBOUNCE_TIMEOUT); + +function updateProperties(propertiesToUpdate, onlyUpdateEntity) { + if (onlyUpdateEntity === undefined) { + onlyUpdateEntity = false; + } + EventBridge.emitWebEvent(JSON.stringify({ + ids: [...selectedEntityIDs], + type: "update", + properties: propertiesToUpdate, + onlyUpdateEntities: onlyUpdateEntity + })); +} + +function updateMultiDiffProperties(propertiesMapToUpdate, onlyUpdateEntity) { + if (onlyUpdateEntity === undefined) { + onlyUpdateEntity = false; + } + EventBridge.emitWebEvent(JSON.stringify({ + type: "update", + propertiesMap: propertiesMapToUpdate, + onlyUpdateEntities: onlyUpdateEntity + })); +} + +function createEmitTextPropertyUpdateFunction(property) { + return function() { + property.elInput.classList.remove('multi-diff'); + updateProperty(property.name, this.value, property.isParticleProperty); + }; +} + +function createEmitCheckedPropertyUpdateFunction(property) { + return function() { + updateProperty(property.name, property.data.inverse ? !this.checked : this.checked, property.isParticleProperty); + }; +} + +function createDragStartFunction(property) { + return function() { + property.dragging = true; + }; +} + +function createDragEndFunction(property) { + return function() { + property.dragging = false; + + if (this.multiDiffModeEnabled) { + let propertyMultiValue = getMultiplePropertyValue(property.name); + let updateObjects = []; + const selectedEntityIDsArray = [...selectedEntityIDs]; + + for (let i = 0; i < selectedEntityIDsArray.length; ++i) { + let entityID = selectedEntityIDsArray[i]; + updateObjects.push({ + entityIDs: [entityID], + properties: createPropertyUpdateObject(property.name, propertyMultiValue.values[i]), + }); + } + + // send a full updateMultiDiff post-dragging to count as an action in the undo stack + updateMultiDiffProperties(updateObjects); + } else { + // send an additional update post-dragging to consider whole property change from dragStart to dragEnd to be 1 action + this.valueChangeFunction(); + } + }; +} + +function createEmitNumberPropertyUpdateFunction(property) { + return function() { + let value = parseFloat(applyOutputNumberPropertyModifiers(parseFloat(this.value), property.data)); + updateProperty(property.name, value, property.isParticleProperty); + }; +} + +function createEmitNumberPropertyComponentUpdateFunction(property, propertyComponent) { + return function() { + let propertyMultiValue = getMultiplePropertyValue(property.name); + let value = parseFloat(applyOutputNumberPropertyModifiers(parseFloat(this.value), property.data)); + + if (propertyMultiValue.isMultiDiffValue) { + let updateObjects = []; + const selectedEntityIDsArray = [...selectedEntityIDs]; + + for (let i = 0; i < selectedEntityIDsArray.length; ++i) { + let entityID = selectedEntityIDsArray[i]; + + let propertyObject = propertyMultiValue.values[i]; + propertyObject[propertyComponent] = value; + + let updateObject = createPropertyUpdateObject(property.name, propertyObject); + updateObjects.push({ + entityIDs: [entityID], + properties: updateObject, + }); + + mergeDeep(currentSelections[i].properties, updateObject); + } + + // only update the entity property value itself if in the middle of dragging + // prevent undo command push, saving new property values, and property update + // callback until drag is complete (additional update sent via dragEnd callback) + let onlyUpdateEntity = isCurrentlyDraggingProperty(property.name); + updateMultiDiffProperties(updateObjects, onlyUpdateEntity); + } else { + let propertyValue = propertyMultiValue.value; + propertyValue[propertyComponent] = value; + updateProperty(property.name, propertyValue, property.isParticleProperty); + } + }; +} + +function createEmitColorPropertyUpdateFunction(property) { + return function() { + emitColorPropertyUpdate(property.name, property.elNumberR.elInput.value, property.elNumberG.elInput.value, + property.elNumberB.elInput.value, property.isParticleProperty); + }; +} + +function emitColorPropertyUpdate(propertyName, red, green, blue, isParticleProperty) { + let newValue = { + red: red, + green: green, + blue: blue + }; + updateProperty(propertyName, newValue, isParticleProperty); +} + +function toggleBooleanCSV(inputCSV, property, enable) { + let values = inputCSV.split(","); + if (enable && !values.includes(property)) { + values.push(property); + } else if (!enable && values.includes(property)) { + values = values.filter(value => value !== property); + } + return values.join(","); +} + +function updateCheckedSubProperty(propertyName, propertyMultiValue, subPropertyElement, subPropertyString, isParticleProperty) { + if (propertyMultiValue.isMultiDiffValue) { + let updateObjects = []; + const selectedEntityIDsArray = [...selectedEntityIDs]; + + for (let i = 0; i < selectedEntityIDsArray.length; ++i) { + let newValue = toggleBooleanCSV(propertyMultiValue.values[i], subPropertyString, subPropertyElement.checked); + updateObjects.push({ + entityIDs: [selectedEntityIDsArray[i]], + properties: createPropertyUpdateObject(propertyName, newValue), + }); + } + + updateMultiDiffProperties(updateObjects); + } else { + updateProperty(propertyName, toggleBooleanCSV(propertyMultiValue.value, subPropertyString, subPropertyElement.checked), + isParticleProperty); + } +} + +/** + * PROPERTY ELEMENT CREATION FUNCTIONS + */ + +function createStringProperty(property, elProperty) { + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "text"; + + let elInput = createElementFromHTML(` + + `); + + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); + if (propertyData.onChange !== undefined) { + elInput.addEventListener('change', propertyData.onChange); + } + + + let elMultiDiff = document.createElement('span'); + elMultiDiff.className = "multi-diff"; + + elProperty.appendChild(elInput); + elProperty.appendChild(elMultiDiff); + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, elementID, propertyData.buttons, false); + } + + return elInput; +} + +function createBoolProperty(property, elProperty) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "checkbox"; + + if (propertyData.glyph !== undefined) { + let elSpan = document.createElement('span'); + elSpan.innerHTML = propertyData.glyph; + elSpan.className = 'icon'; + elProperty.appendChild(elSpan); + } + + let elInput = document.createElement('input'); + elInput.setAttribute("id", elementID); + elInput.setAttribute("type", "checkbox"); + + elProperty.appendChild(elInput); + elProperty.appendChild(createElementFromHTML(``)); + + let subPropertyOf = propertyData.subPropertyOf; + if (subPropertyOf !== undefined) { + elInput.addEventListener('change', function() { + let subPropertyMultiValue = getMultiplePropertyValue(subPropertyOf); + + updateCheckedSubProperty(subPropertyOf, + subPropertyMultiValue, + elInput, propertyName, property.isParticleProperty); + }); + } else { + elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(property)); + } + + return elInput; +} + +function createNumberProperty(property, elProperty) { + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "text"; + + let elInput = createElementFromHTML(` + + `); + + if (propertyData.min !== undefined) { + elInput.setAttribute("min", propertyData.min); + } + if (propertyData.max !== undefined) { + elInput.setAttribute("max", propertyData.max); + } + if (propertyData.step !== undefined) { + elInput.setAttribute("step", propertyData.step); + } + if (propertyData.defaultValue !== undefined) { + elInput.value = propertyData.defaultValue; + } + + elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(property)); + + let elMultiDiff = document.createElement('span'); + elMultiDiff.className = "multi-diff"; + + elProperty.appendChild(elInput); + elProperty.appendChild(elMultiDiff); + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, elementID, propertyData.buttons, false); + } + + return elInput; +} + +function updateNumberMinMax(property) { + let elInput = property.elInput; + let min = property.data.min; + let max = property.data.max; + if (min !== undefined) { + elInput.setAttribute("min", min); + } + if (max !== undefined) { + elInput.setAttribute("max", max); + } +} + +/** + * + * @param {object} property - property update on step + * @param {string} [propertyComponent] - propertyComponent to update on step (e.g. enter 'x' to just update position.x) + * @returns {Function} + */ +function createMultiDiffStepFunction(property, propertyComponent) { + return function(step, shouldAddToUndoHistory) { + if (shouldAddToUndoHistory === undefined) { + shouldAddToUndoHistory = false; + } + + let propertyMultiValue = getMultiplePropertyValue(property.name); + if (!propertyMultiValue.isMultiDiffValue) { + console.log("setMultiDiffStepFunction is only supposed to be called in MultiDiff mode."); + return; + } + + let multiplier = property.data.multiplier !== undefined ? property.data.multiplier : 1; + + let applyDelta = step * multiplier; + + if (selectedEntityIDs.size !== propertyMultiValue.values.length) { + console.log("selectedEntityIDs and propertyMultiValue got out of sync."); + return; + } + let updateObjects = []; + const selectedEntityIDsArray = [...selectedEntityIDs]; + + for (let i = 0; i < selectedEntityIDsArray.length; ++i) { + let entityID = selectedEntityIDsArray[i]; + + let updatedValue; + if (propertyComponent !== undefined) { + let objectToUpdate = propertyMultiValue.values[i]; + objectToUpdate[propertyComponent] += applyDelta; + updatedValue = objectToUpdate; + } else { + updatedValue = propertyMultiValue.values[i] + applyDelta; + } + let propertiesUpdate = createPropertyUpdateObject(property.name, updatedValue); + updateObjects.push({ + entityIDs: [entityID], + properties: propertiesUpdate + }); + // We need to store these so that we can send a full update on the dragEnd + mergeDeep(currentSelections[i].properties, propertiesUpdate); + } + + updateMultiDiffProperties(updateObjects, !shouldAddToUndoHistory); + } +} + +function createNumberDraggableProperty(property, elProperty) { + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className += " draggable-number-container"; + + let dragStartFunction = createDragStartFunction(property); + let dragEndFunction = createDragEndFunction(property); + let elDraggableNumber = new DraggableNumber(propertyData.min, propertyData.max, propertyData.step, + propertyData.decimals, dragStartFunction, dragEndFunction); + + let defaultValue = propertyData.defaultValue; + if (defaultValue !== undefined) { + elDraggableNumber.elInput.value = defaultValue; + } + + let valueChangeFunction = createEmitNumberPropertyUpdateFunction(property); + elDraggableNumber.setValueChangeFunction(valueChangeFunction); + + elDraggableNumber.setMultiDiffStepFunction(createMultiDiffStepFunction(property)); + + elDraggableNumber.elInput.setAttribute("id", elementID); + elProperty.appendChild(elDraggableNumber.elDiv); + + if (propertyData.buttons !== undefined) { + addButtons(elDraggableNumber.elDiv, elementID, propertyData.buttons, false); + } + + return elDraggableNumber; +} + +function updateNumberDraggableMinMax(property) { + let propertyData = property.data; + property.elNumber.updateMinMax(propertyData.min, propertyData.max); +} + +function createRectProperty(property, elProperty) { + let propertyData = property.data; + + elProperty.className = "rect"; + + let elXYRow = document.createElement('div'); + elXYRow.className = "rect-row fstuple"; + elProperty.appendChild(elXYRow); + + let elWidthHeightRow = document.createElement('div'); + elWidthHeightRow.className = "rect-row fstuple"; + elProperty.appendChild(elWidthHeightRow); + + + let elNumberX = createTupleNumberInput(property, propertyData.subLabels[RECT_ELEMENTS.X_NUMBER]); + let elNumberY = createTupleNumberInput(property, propertyData.subLabels[RECT_ELEMENTS.Y_NUMBER]); + let elNumberWidth = createTupleNumberInput(property, propertyData.subLabels[RECT_ELEMENTS.WIDTH_NUMBER]); + let elNumberHeight = createTupleNumberInput(property, propertyData.subLabels[RECT_ELEMENTS.HEIGHT_NUMBER]); + + elXYRow.appendChild(elNumberX.elDiv); + elXYRow.appendChild(elNumberY.elDiv); + elWidthHeightRow.appendChild(elNumberWidth.elDiv); + elWidthHeightRow.appendChild(elNumberHeight.elDiv); + + elNumberX.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'x')); + elNumberY.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'y')); + elNumberWidth.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'width')); + elNumberHeight.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'height')); + + elNumberX.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'x')); + elNumberY.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'y')); + elNumberX.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'width')); + elNumberY.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'height')); + + let elResult = []; + elResult[RECT_ELEMENTS.X_NUMBER] = elNumberX; + elResult[RECT_ELEMENTS.Y_NUMBER] = elNumberY; + elResult[RECT_ELEMENTS.WIDTH_NUMBER] = elNumberWidth; + elResult[RECT_ELEMENTS.HEIGHT_NUMBER] = elNumberHeight; + return elResult; +} + +function updateRectMinMax(property) { + let min = property.data.min; + let max = property.data.max; + property.elNumberX.updateMinMax(min, max); + property.elNumberY.updateMinMax(min, max); + property.elNumberWidth.updateMinMax(min, max); + property.elNumberHeight.updateMinMax(min, max); +} + +function createVec3Property(property, elProperty) { + let propertyData = property.data; + + elProperty.className = propertyData.vec3Type + " fstuple"; + + let elNumberX = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.X_NUMBER]); + let elNumberY = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Y_NUMBER]); + let elNumberZ = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Z_NUMBER]); + elProperty.appendChild(elNumberX.elDiv); + elProperty.appendChild(elNumberY.elDiv); + elProperty.appendChild(elNumberZ.elDiv); + + elNumberX.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'x')); + elNumberY.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'y')); + elNumberZ.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'z')); + + elNumberX.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'x')); + elNumberY.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'y')); + elNumberZ.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'z')); + + let elResult = []; + elResult[VECTOR_ELEMENTS.X_NUMBER] = elNumberX; + elResult[VECTOR_ELEMENTS.Y_NUMBER] = elNumberY; + elResult[VECTOR_ELEMENTS.Z_NUMBER] = elNumberZ; + return elResult; +} + +function createVec2Property(property, elProperty) { + let propertyData = property.data; + + elProperty.className = propertyData.vec2Type + " fstuple"; + + let elTuple = document.createElement('div'); + elTuple.className = "tuple"; + + elProperty.appendChild(elTuple); + + let elNumberX = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.X_NUMBER]); + let elNumberY = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Y_NUMBER]); + elProperty.appendChild(elNumberX.elDiv); + elProperty.appendChild(elNumberY.elDiv); + + elNumberX.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'x')); + elNumberY.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'y')); + + elNumberX.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'x')); + elNumberY.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'y')); + + let elResult = []; + elResult[VECTOR_ELEMENTS.X_NUMBER] = elNumberX; + elResult[VECTOR_ELEMENTS.Y_NUMBER] = elNumberY; + return elResult; +} + +function updateVectorMinMax(property) { + let min = property.data.min; + let max = property.data.max; + property.elNumberX.updateMinMax(min, max); + property.elNumberY.updateMinMax(min, max); + if (property.elNumberZ) { + property.elNumberZ.updateMinMax(min, max); + } +} + +function createColorProperty(property, elProperty) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className += " rgb fstuple"; + + let elColorPicker = document.createElement('div'); + elColorPicker.className = "color-picker"; + elColorPicker.setAttribute("id", elementID); + + let elTuple = document.createElement('div'); + elTuple.className = "tuple"; + + elProperty.appendChild(elColorPicker); + elProperty.appendChild(elTuple); + + if (propertyData.min === undefined) { + propertyData.min = COLOR_MIN; + } + if (propertyData.max === undefined) { + propertyData.max = COLOR_MAX; + } + if (propertyData.step === undefined) { + propertyData.step = COLOR_STEP; + } + + let elNumberR = createTupleNumberInput(property, "red"); + let elNumberG = createTupleNumberInput(property, "green"); + let elNumberB = createTupleNumberInput(property, "blue"); + elTuple.appendChild(elNumberR.elDiv); + elTuple.appendChild(elNumberG.elDiv); + elTuple.appendChild(elNumberB.elDiv); + + let valueChangeFunction = createEmitColorPropertyUpdateFunction(property); + elNumberR.setValueChangeFunction(valueChangeFunction); + elNumberG.setValueChangeFunction(valueChangeFunction); + elNumberB.setValueChangeFunction(valueChangeFunction); + + let colorPickerID = "#" + elementID; + colorPickers[colorPickerID] = $(colorPickerID).colpick({ + colorScheme: 'dark', + layout: 'rgbhex', + color: '000000', + submit: false, // We don't want to have a submission button + onShow: function(colpick) { + // The original color preview within the picker needs to be updated on show because + // prior to the picker being shown we don't have access to the selections' starting color. + colorPickers[colorPickerID].colpickSetColor({ + "r": elNumberR.elInput.value, + "g": elNumberG.elInput.value, + "b": elNumberB.elInput.value + }); + + // Set the color picker active after setting the color, otherwise an update will be sent on open. + $(colorPickerID).attr('active', 'true'); + }, + onHide: function(colpick) { + $(colorPickerID).attr('active', 'false'); + }, + onChange: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + if ($(colorPickerID).attr('active') === 'true') { + emitColorPropertyUpdate(propertyName, rgb.r, rgb.g, rgb.b); + } + } + }); + + let elResult = []; + elResult[COLOR_ELEMENTS.COLOR_PICKER] = elColorPicker; + elResult[COLOR_ELEMENTS.RED_NUMBER] = elNumberR; + elResult[COLOR_ELEMENTS.GREEN_NUMBER] = elNumberG; + elResult[COLOR_ELEMENTS.BLUE_NUMBER] = elNumberB; + return elResult; +} + +function createDropdownProperty(property, propertyID, elProperty) { + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "dropdown"; + + let elInput = document.createElement('select'); + elInput.setAttribute("id", elementID); + elInput.setAttribute("propertyID", propertyID); + + for (let optionKey in propertyData.options) { + let option = document.createElement('option'); + option.value = optionKey; + option.text = propertyData.options[optionKey]; + elInput.add(option); + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); + + elProperty.appendChild(elInput); + + return elInput; +} + +function createTextareaProperty(property, elProperty) { + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "textarea"; + + let elInput = document.createElement('textarea'); + elInput.setAttribute("id", elementID); + if (propertyData.readOnly) { + elInput.readOnly = true; + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); + + let elMultiDiff = document.createElement('span'); + elMultiDiff.className = "multi-diff"; + + elProperty.appendChild(elInput); + elProperty.appendChild(elMultiDiff); + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, elementID, propertyData.buttons, true); + } + + return elInput; +} + +function createIconProperty(property, elProperty) { + let elementID = property.elementID; + + elProperty.className = "value"; + + let elSpan = document.createElement('span'); + elSpan.setAttribute("id", elementID + "-icon"); + elSpan.className = 'icon'; + + elProperty.appendChild(elSpan); + + return elSpan; +} + +function createTextureProperty(property, elProperty) { + let elementID = property.elementID; + + elProperty.className = "texture"; + + let elDiv = document.createElement("div"); + let elImage = document.createElement("img"); + elDiv.className = "texture-image no-texture"; + elDiv.appendChild(elImage); + + let elInput = document.createElement('input'); + elInput.setAttribute("id", elementID); + elInput.setAttribute("type", "text"); + + let imageLoad = function(url) { + elDiv.style.display = null; + if (url.slice(0, 5).toLowerCase() === "atp:/") { + elImage.src = ""; + elImage.style.display = "none"; + elDiv.classList.remove("with-texture"); + elDiv.classList.remove("no-texture"); + elDiv.classList.add("no-preview"); + } else if (url.length > 0) { + elDiv.classList.remove("no-texture"); + elDiv.classList.remove("no-preview"); + elDiv.classList.add("with-texture"); + elImage.src = url; + elImage.style.display = "block"; + } else { + elImage.src = ""; + elImage.style.display = "none"; + elDiv.classList.remove("with-texture"); + elDiv.classList.remove("no-preview"); + elDiv.classList.add("no-texture"); + } + }; + elInput.imageLoad = imageLoad; + elInput.setMultipleValues = function() { + elDiv.style.display = "none"; + }; + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); + elInput.addEventListener('change', function(ev) { + imageLoad(ev.target.value); + }); + + elProperty.appendChild(elInput); + let elMultiDiff = document.createElement('span'); + elMultiDiff.className = "multi-diff"; + elProperty.appendChild(elMultiDiff); + elProperty.appendChild(elDiv); + + let elResult = []; + elResult[TEXTURE_ELEMENTS.IMAGE] = elImage; + elResult[TEXTURE_ELEMENTS.TEXT_INPUT] = elInput; + return elResult; +} + +function createButtonsProperty(property, elProperty) { + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "text"; + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, elementID, propertyData.buttons, false); + } + + return elProperty; +} + +function createDynamicMultiselectProperty(property, elProperty) { + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "dynamic-multiselect"; + + let elDivOptions = document.createElement('div'); + elDivOptions.setAttribute("id", elementID + "-options"); + elDivOptions.style = "overflow-y:scroll;max-height:160px;"; + + let elDivButtons = document.createElement('div'); + elDivButtons.setAttribute("id", elDivOptions.getAttribute("id") + "-buttons"); + + let elLabel = document.createElement('label'); + elLabel.innerText = "No Options"; + elDivOptions.appendChild(elLabel); + + let buttons = [ { id: "selectAll", label: "Select All", className: "black", onClick: selectAllMaterialTarget }, + { id: "clearAll", label: "Clear All", className: "black", onClick: clearAllMaterialTarget } ]; + addButtons(elDivButtons, elementID, buttons, false); + + elProperty.appendChild(elDivOptions); + elProperty.appendChild(elDivButtons); + + return elDivOptions; +} + +function resetDynamicMultiselectProperty(elDivOptions) { + let elInputs = elDivOptions.getElementsByTagName("input"); + while (elInputs.length > 0) { + let elDivOption = elInputs[0].parentNode; + elDivOption.parentNode.removeChild(elDivOption); + } + elDivOptions.firstChild.style.display = null; // show "No Options" text + elDivOptions.parentNode.lastChild.style.display = "none"; // hide Select/Clear all buttons +} + +function createTupleNumberInput(property, subLabel) { + let propertyElementID = property.elementID; + let propertyData = property.data; + let elementID = propertyElementID + "-" + subLabel.toLowerCase(); + + let elLabel = document.createElement('label'); + elLabel.className = "sublabel " + subLabel; + elLabel.innerText = subLabel[0].toUpperCase() + subLabel.slice(1); + elLabel.setAttribute("for", elementID); + elLabel.style.visibility = "visible"; + + let dragStartFunction = createDragStartFunction(property); + let dragEndFunction = createDragEndFunction(property); + let elDraggableNumber = new DraggableNumber(propertyData.min, propertyData.max, propertyData.step, + propertyData.decimals, dragStartFunction, dragEndFunction); + elDraggableNumber.elInput.setAttribute("id", elementID); + elDraggableNumber.elDiv.className += " fstuple"; + elDraggableNumber.elDiv.insertBefore(elLabel, elDraggableNumber.elLeftArrow); + + return elDraggableNumber; +} + +function addButtons(elProperty, propertyID, buttons, newRow) { + let elDiv = document.createElement('div'); + elDiv.className = "row"; + + buttons.forEach(function(button) { + let elButton = document.createElement('input'); + elButton.className = button.className; + elButton.setAttribute("type", "button"); + elButton.setAttribute("id", propertyID + "-button-" + button.id); + elButton.setAttribute("value", button.label); + elButton.addEventListener("click", button.onClick); + if (newRow) { + elDiv.appendChild(elButton); + } else { + elProperty.appendChild(elButton); + } + }); + + if (newRow) { + elProperty.appendChild(document.createElement('br')); + elProperty.appendChild(elDiv); + } +} + +function createProperty(propertyData, propertyElementID, propertyName, propertyID, elProperty) { + let property = { + data: propertyData, + elementID: propertyElementID, + name: propertyName, + elProperty: elProperty, + }; + let propertyType = propertyData.type; + + switch (propertyType) { + case 'string': { + property.elInput = createStringProperty(property, elProperty); + break; + } + case 'bool': { + property.elInput = createBoolProperty(property, elProperty); + break; + } + case 'number': { + property.elInput = createNumberProperty(property, elProperty); + break; + } + case 'number-draggable': { + property.elNumber = createNumberDraggableProperty(property, elProperty); + break; + } + case 'rect': { + let elRect = createRectProperty(property, elProperty); + property.elNumberX = elRect[RECT_ELEMENTS.X_NUMBER]; + property.elNumberY = elRect[RECT_ELEMENTS.Y_NUMBER]; + property.elNumberWidth = elRect[RECT_ELEMENTS.WIDTH_NUMBER]; + property.elNumberHeight = elRect[RECT_ELEMENTS.HEIGHT_NUMBER]; + break; + } + case 'vec3': { + let elVec3 = createVec3Property(property, elProperty); + property.elNumberX = elVec3[VECTOR_ELEMENTS.X_NUMBER]; + property.elNumberY = elVec3[VECTOR_ELEMENTS.Y_NUMBER]; + property.elNumberZ = elVec3[VECTOR_ELEMENTS.Z_NUMBER]; + break; + } + case 'vec2': { + let elVec2 = createVec2Property(property, elProperty); + property.elNumberX = elVec2[VECTOR_ELEMENTS.X_NUMBER]; + property.elNumberY = elVec2[VECTOR_ELEMENTS.Y_NUMBER]; + break; + } + case 'color': { + let elColor = createColorProperty(property, elProperty); + property.elColorPicker = elColor[COLOR_ELEMENTS.COLOR_PICKER]; + property.elNumberR = elColor[COLOR_ELEMENTS.RED_NUMBER]; + property.elNumberG = elColor[COLOR_ELEMENTS.GREEN_NUMBER]; + property.elNumberB = elColor[COLOR_ELEMENTS.BLUE_NUMBER]; + break; + } + case 'dropdown': { + property.elInput = createDropdownProperty(property, propertyID, elProperty); + break; + } + case 'textarea': { + property.elInput = createTextareaProperty(property, elProperty); + break; + } + case 'icon': { + property.elSpan = createIconProperty(property, elProperty); + break; + } + case 'texture': { + let elTexture = createTextureProperty(property, elProperty); + property.elImage = elTexture[TEXTURE_ELEMENTS.IMAGE]; + property.elInput = elTexture[TEXTURE_ELEMENTS.TEXT_INPUT]; + break; + } + case 'buttons': { + property.elProperty = createButtonsProperty(property, elProperty); + break; + } + case 'dynamic-multiselect': { + property.elDivOptions = createDynamicMultiselectProperty(property, elProperty); + break; + } + case 'placeholder': + case 'sub-header': { + break; + } + default: { + console.log("EntityProperties - Unknown property type " + + propertyType + " set to property " + propertyID); + break; + } + } + + return property; +} + + +/** + * PROPERTY-SPECIFIC CALLBACKS + */ + +function parentIDChanged() { + if (currentSelections.length === 1 && currentSelections[0].properties.type === "Material") { + requestMaterialTarget(); + } +} + + +/** + * BUTTON CALLBACKS + */ + +function rescaleDimensions() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "rescaleDimensions", + percentage: parseFloat(document.getElementById("property-scale").value) + })); +} + +function moveSelectionToGrid() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveSelectionToGrid" + })); +} + +function moveAllToGrid() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveAllToGrid" + })); +} + +function resetToNaturalDimensions() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "resetToNaturalDimensions" + })); +} + +function reloadScripts() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "reloadClientScripts" + })); +} + +function reloadServerScripts() { + // invalidate the current status (so that same-same updates can still be observed visually) + document.getElementById("property-serverScripts-status").innerText = PENDING_SCRIPT_STATUS; + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "reloadServerScripts" + })); +} + +function copySkyboxURLToAmbientURL() { + let skyboxURL = getPropertyInputElement("skybox.url").value; + getPropertyInputElement("ambientLight.ambientURL").value = skyboxURL; + updateProperty("ambientLight.ambientURL", skyboxURL, false); +} + + +/** + * USER DATA FUNCTIONS + */ + +function clearUserData() { + let elUserData = getPropertyInputElement("userData"); + deleteJSONEditor(); + elUserData.value = ""; + showUserDataTextArea(); + showNewJSONEditorButton(); + hideSaveUserDataButton(); + updateProperty('userData', elUserData.value, false); +} + +function newJSONEditor() { + getPropertyInputElement("userData").classList.remove('multi-diff'); + deleteJSONEditor(); + createJSONEditor(); + let data = {}; + setEditorJSON(data); + hideUserDataTextArea(); + hideNewJSONEditorButton(); + showSaveUserDataButton(); +} + +/** + * @param {Set.} [entityIDsToUpdate] Entity IDs to update userData for. + */ +function saveUserData(entityIDsToUpdate) { + saveJSONUserData(true, entityIDsToUpdate); +} + +function setJSONError(property, isError) { + $("#property-"+ property + "-editor").toggleClass('error', isError); + let $propertyUserDataEditorStatus = $("#property-"+ property + "-editorStatus"); + $propertyUserDataEditorStatus.css('display', isError ? 'block' : 'none'); + $propertyUserDataEditorStatus.text(isError ? 'Invalid JSON code - look for red X in your code' : ''); +} + +/** + * @param {boolean} noUpdate - don't update the UI, but do send a property update. + * @param {Set.} [entityIDsToUpdate] - Entity IDs to update userData for. + */ +function setUserDataFromEditor(noUpdate, entityIDsToUpdate) { + let errorFound = false; + try { + editor.get(); + } catch (e) { + errorFound = true; + } + + setJSONError('userData', errorFound); + + if (errorFound) { + return; + } + + let text = editor.getText(); + if (noUpdate) { + EventBridge.emitWebEvent( + JSON.stringify({ + ids: [...entityIDsToUpdate], + type: "saveUserData", + properties: { + userData: text + } + }) + ); + } else { + updateProperty('userData', text, false); + } +} + +let editor = null; + +function createJSONEditor() { + let container = document.getElementById("property-userData-editor"); + let options = { + search: false, + mode: 'tree', + modes: ['code', 'tree'], + name: 'userData', + onError: function(e) { + alert('JSON editor:' + e); + }, + onChange: function() { + let currentJSONString = editor.getText(); + + if (currentJSONString === '{"":""}') { + return; + } + $('#property-userData-button-save').attr('disabled', false); + } + }; + editor = new JSONEditor(container, options); +} + +function showSaveUserDataButton() { + $('#property-userData-button-save').show(); +} + +function hideSaveUserDataButton() { + $('#property-userData-button-save').hide(); +} + +function disableSaveUserDataButton() { + $('#property-userData-button-save').attr('disabled', true); +} + +function showNewJSONEditorButton() { + $('#property-userData-button-edit').show(); +} + +function hideNewJSONEditorButton() { + $('#property-userData-button-edit').hide(); +} + +function showUserDataTextArea() { + $('#property-userData').show(); +} + +function hideUserDataTextArea() { + $('#property-userData').hide(); +} + +function hideUserDataSaved() { + $('#property-userData-saved').hide(); +} + +function showStaticUserData() { + if (editor !== null) { + let $propertyUserDataStatic = $('#property-userData-static'); + $propertyUserDataStatic.show(); + $propertyUserDataStatic.css('height', $('#property-userData-editor').height()); + $propertyUserDataStatic.text(editor.getText()); + } +} + +function removeStaticUserData() { + $('#property-userData-static').hide(); +} + +function setEditorJSON(json) { + editor.set(json); + if (editor.hasOwnProperty('expandAll')) { + editor.expandAll(); + } +} + +function deleteJSONEditor() { + if (editor !== null) { + setJSONError('userData', false); + editor.destroy(); + editor = null; + } +} + +let savedJSONTimer = null; + +/** + * @param {boolean} noUpdate - don't update the UI, but do send a property update. + * @param {Set.} [entityIDsToUpdate] Entity IDs to update userData for + */ +function saveJSONUserData(noUpdate, entityIDsToUpdate) { + setUserDataFromEditor(noUpdate, entityIDsToUpdate ? entityIDsToUpdate : selectedEntityIDs); + $('#property-userData-saved').show(); + $('#property-userData-button-save').attr('disabled', true); + if (savedJSONTimer !== null) { + clearTimeout(savedJSONTimer); + } + savedJSONTimer = setTimeout(function() { + hideUserDataSaved(); + }, EDITOR_TIMEOUT_DURATION); +} + + +/** + * MATERIAL DATA FUNCTIONS + */ + +function clearMaterialData() { + let elMaterialData = getPropertyInputElement("materialData"); + deleteJSONMaterialEditor(); + elMaterialData.value = ""; + showMaterialDataTextArea(); + showNewJSONMaterialEditorButton(); + hideSaveMaterialDataButton(); + updateProperty('materialData', elMaterialData.value, false); +} + +function newJSONMaterialEditor() { + getPropertyInputElement("materialData").classList.remove('multi-diff'); + deleteJSONMaterialEditor(); + createJSONMaterialEditor(); + let data = {}; + setMaterialEditorJSON(data); + hideMaterialDataTextArea(); + hideNewJSONMaterialEditorButton(); + showSaveMaterialDataButton(); +} + +function saveMaterialData() { + saveJSONMaterialData(true); +} + +/** + * @param {boolean} noUpdate - don't update the UI, but do send a property update. + * @param {Set.} [entityIDsToUpdate] - Entity IDs to update materialData for. + */ +function setMaterialDataFromEditor(noUpdate, entityIDsToUpdate) { + let errorFound = false; + try { + materialEditor.get(); + } catch (e) { + errorFound = true; + } + + setJSONError('materialData', errorFound); + + if (errorFound) { + return; + } + let text = materialEditor.getText(); + if (noUpdate) { + EventBridge.emitWebEvent( + JSON.stringify({ + ids: [...entityIDsToUpdate], + type: "saveMaterialData", + properties: { + materialData: text + } + }) + ); + } else { + updateProperty('materialData', text, false); + } +} + +let materialEditor = null; + +function createJSONMaterialEditor() { + let container = document.getElementById("property-materialData-editor"); + let options = { + search: false, + mode: 'tree', + modes: ['code', 'tree'], + name: 'materialData', + onError: function(e) { + alert('JSON editor:' + e); + }, + onChange: function() { + let currentJSONString = materialEditor.getText(); + + if (currentJSONString === '{"":""}') { + return; + } + $('#property-materialData-button-save').attr('disabled', false); + } + }; + materialEditor = new JSONEditor(container, options); +} + +function showSaveMaterialDataButton() { + $('#property-materialData-button-save').show(); +} + +function hideSaveMaterialDataButton() { + $('#property-materialData-button-save').hide(); +} + +function disableSaveMaterialDataButton() { + $('#property-materialData-button-save').attr('disabled', true); +} + +function showNewJSONMaterialEditorButton() { + $('#property-materialData-button-edit').show(); +} + +function hideNewJSONMaterialEditorButton() { + $('#property-materialData-button-edit').hide(); +} + +function showMaterialDataTextArea() { + $('#property-materialData').show(); +} + +function hideMaterialDataTextArea() { + $('#property-materialData').hide(); +} + +function hideMaterialDataSaved() { + $('#property-materialData-saved').hide(); +} + +function showStaticMaterialData() { + if (materialEditor !== null) { + let $propertyMaterialDataStatic = $('#property-materialData-static'); + $propertyMaterialDataStatic.show(); + $propertyMaterialDataStatic.css('height', $('#property-materialData-editor').height()); + $propertyMaterialDataStatic.text(materialEditor.getText()); + } +} + +function removeStaticMaterialData() { + $('#property-materialData-static').hide(); +} + +function setMaterialEditorJSON(json) { + materialEditor.set(json); + if (materialEditor.hasOwnProperty('expandAll')) { + materialEditor.expandAll(); + } +} + +function deleteJSONMaterialEditor() { + if (materialEditor !== null) { + setJSONError('materialData', false); + materialEditor.destroy(); + materialEditor = null; + } +} + +let savedMaterialJSONTimer = null; + +/** + * @param {boolean} noUpdate - don't update the UI, but do send a property update. + * @param {Set.} [entityIDsToUpdate] - Entity IDs to update materialData for. + */ +function saveJSONMaterialData(noUpdate, entityIDsToUpdate) { + setMaterialDataFromEditor(noUpdate, entityIDsToUpdate ? entityIDsToUpdate : selectedEntityIDs); + $('#property-materialData-saved').show(); + $('#property-materialData-button-save').attr('disabled', true); + if (savedMaterialJSONTimer !== null) { + clearTimeout(savedMaterialJSONTimer); + } + savedMaterialJSONTimer = setTimeout(function() { + hideMaterialDataSaved(); + }, EDITOR_TIMEOUT_DURATION); +} + +function bindAllNonJSONEditorElements() { + let inputs = $('input'); + let i; + for (i = 0; i < inputs.length; ++i) { + let input = inputs[i]; + let field = $(input); + // TODO FIXME: (JSHint) Functions declared within loops referencing + // an outer scoped variable may lead to confusing semantics. + field.on('focus', function(e) { + if (e.target.id === "property-userData-button-edit" || e.target.id === "property-userData-button-clear" || + e.target.id === "property-materialData-button-edit" || e.target.id === "property-materialData-button-clear") { + return; + } + if ($('#property-userData-editor').css('height') !== "0px") { + saveUserData(); + } + if ($('#property-materialData-editor').css('height') !== "0px") { + saveMaterialData(); + } + }); + } +} + + +/** + * DROPDOWN FUNCTIONS + */ + +function setDropdownText(dropdown) { + let lis = dropdown.parentNode.getElementsByTagName("li"); + let text = ""; + for (let i = 0; i < lis.length; ++i) { + if (String(lis[i].getAttribute("value")) === String(dropdown.value)) { + text = lis[i].textContent; + } + } + dropdown.firstChild.textContent = text; +} + +function toggleDropdown(event) { + let element = event.target; + if (element.nodeName !== "DT") { + element = element.parentNode; + } + element = element.parentNode; + let isDropped = element.getAttribute("dropped"); + element.setAttribute("dropped", isDropped !== "true" ? "true" : "false"); +} + +function closeAllDropdowns() { + let elDropdowns = document.querySelectorAll("div.dropdown > dl"); + for (let i = 0; i < elDropdowns.length; ++i) { + elDropdowns[i].setAttribute('dropped', 'false'); + } +} + +function setDropdownValue(event) { + let dt = event.target.parentNode.parentNode.previousSibling.previousSibling; + dt.value = event.target.getAttribute("value"); + dt.firstChild.textContent = event.target.textContent; + + dt.parentNode.setAttribute("dropped", "false"); + + let evt = document.createEvent("HTMLEvents"); + evt.initEvent("change", true, true); + dt.dispatchEvent(evt); +} + + +/** + * TEXTAREA FUNCTIONS + */ + +function setTextareaScrolling(element) { + let isScrolling = element.scrollHeight > element.offsetHeight; + element.setAttribute("scrolling", isScrolling ? "true" : "false"); +} + + +/** + * MATERIAL TARGET FUNCTIONS + */ + +function requestMaterialTarget() { + EventBridge.emitWebEvent(JSON.stringify({ + type: 'materialTargetRequest', + entityID: getFirstSelectedID(), + })); +} + +function setMaterialTargetData(materialTargetData) { + let elDivOptions = getPropertyInputElement("parentMaterialName"); + resetDynamicMultiselectProperty(elDivOptions); + + if (materialTargetData === undefined) { + return; + } + + elDivOptions.firstChild.style.display = "none"; // hide "No Options" text + elDivOptions.parentNode.lastChild.style.display = null; // show Select/Clear all buttons + + let numMeshes = materialTargetData.numMeshes; + for (let i = 0; i < numMeshes; ++i) { + addMaterialTarget(elDivOptions, i, false); + } + + let materialNames = materialTargetData.materialNames; + let materialNamesAdded = []; + for (let i = 0; i < materialNames.length; ++i) { + let materialName = materialNames[i]; + if (materialNamesAdded.indexOf(materialName) === -1) { + addMaterialTarget(elDivOptions, materialName, true); + materialNamesAdded.push(materialName); + } + } + + materialTargetPropertyUpdate(elDivOptions.propertyValue); +} + +function addMaterialTarget(elDivOptions, targetID, isMaterialName) { + let elementID = elDivOptions.getAttribute("id"); + elementID += isMaterialName ? "-material-" : "-mesh-"; + elementID += targetID; + + let elDiv = document.createElement('div'); + elDiv.className = "materialTargetDiv"; + elDiv.onclick = onToggleMaterialTarget; + elDivOptions.appendChild(elDiv); + + let elInput = document.createElement('input'); + elInput.className = "materialTargetInput"; + elInput.setAttribute("type", "checkbox"); + elInput.setAttribute("id", elementID); + elInput.setAttribute("targetID", targetID); + elInput.setAttribute("isMaterialName", isMaterialName); + elDiv.appendChild(elInput); + + let elLabel = document.createElement('label'); + elLabel.setAttribute("for", elementID); + elLabel.innerText = isMaterialName ? "Material " + targetID : "Mesh Index " + targetID; + elDiv.appendChild(elLabel); + + return elDiv; +} + +function onToggleMaterialTarget(event) { + let elTarget = event.target; + if (elTarget instanceof HTMLInputElement) { + sendMaterialTargetProperty(); + } + event.stopPropagation(); +} + +function setAllMaterialTargetInputs(checked) { + let elDivOptions = getPropertyInputElement("parentMaterialName"); + let elInputs = elDivOptions.getElementsByClassName("materialTargetInput"); + for (let i = 0; i < elInputs.length; ++i) { + elInputs[i].checked = checked; + } +} + +function selectAllMaterialTarget() { + setAllMaterialTargetInputs(true); + sendMaterialTargetProperty(); +} + +function clearAllMaterialTarget() { + setAllMaterialTargetInputs(false); + sendMaterialTargetProperty(); +} + +function sendMaterialTargetProperty() { + let elDivOptions = getPropertyInputElement("parentMaterialName"); + let elInputs = elDivOptions.getElementsByClassName("materialTargetInput"); + + let materialTargetList = []; + for (let i = 0; i < elInputs.length; ++i) { + let elInput = elInputs[i]; + if (elInput.checked) { + let targetID = elInput.getAttribute("targetID"); + if (elInput.getAttribute("isMaterialName") === "true") { + materialTargetList.push(MATERIAL_PREFIX_STRING + targetID); + } else { + materialTargetList.push(targetID); + } + } + } + + let propertyValue = materialTargetList.join(","); + if (propertyValue.length > 1) { + propertyValue = "[" + propertyValue + "]"; + } + + updateProperty("parentMaterialName", propertyValue, false); +} + +function materialTargetPropertyUpdate(propertyValue) { + let elDivOptions = getPropertyInputElement("parentMaterialName"); + let elInputs = elDivOptions.getElementsByClassName("materialTargetInput"); + + if (propertyValue.startsWith('[')) { + propertyValue = propertyValue.substring(1, propertyValue.length); + } + if (propertyValue.endsWith(']')) { + propertyValue = propertyValue.substring(0, propertyValue.length - 1); + } + + let materialTargets = propertyValue.split(","); + for (let i = 0; i < elInputs.length; ++i) { + let elInput = elInputs[i]; + let targetID = elInput.getAttribute("targetID"); + let materialTargetName = targetID; + if (elInput.getAttribute("isMaterialName") === "true") { + materialTargetName = MATERIAL_PREFIX_STRING + targetID; + } + elInput.checked = materialTargets.indexOf(materialTargetName) >= 0; + } + + elDivOptions.propertyValue = propertyValue; +} + +function roundAndFixNumber(number, propertyData) { + let result = number; + if (propertyData.round !== undefined) { + result = Math.round(result * propertyData.round) / propertyData.round; + } + if (propertyData.decimals !== undefined) { + return result.toFixed(propertyData.decimals) + } + return result; +} + +function applyInputNumberPropertyModifiers(number, propertyData) { + const multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1; + return roundAndFixNumber(number / multiplier, propertyData); +} + +function applyOutputNumberPropertyModifiers(number, propertyData) { + const multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1; + return roundAndFixNumber(number * multiplier, propertyData); +} + +const areSetsEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value)); + + +function handleEntitySelectionUpdate(selections, isPropertiesToolUpdate) { + const previouslySelectedEntityIDs = selectedEntityIDs; + currentSelections = selections; + selectedEntityIDs = new Set(selections.map(selection => selection.id)); + const multipleSelections = currentSelections.length > 1; + const hasSelectedEntityChanged = !areSetsEqual(selectedEntityIDs, previouslySelectedEntityIDs); + + if (selections.length === 0) { + deleteJSONEditor(); + deleteJSONMaterialEditor(); + + resetProperties(); + showGroupsForType("None"); + + let elIcon = properties.type.elSpan; + elIcon.innerText = NO_SELECTION; + elIcon.style.display = 'inline-block'; + + getPropertyInputElement("userData").value = ""; + showUserDataTextArea(); + showSaveUserDataButton(); + showNewJSONEditorButton(); + + getPropertyInputElement("materialData").value = ""; + showMaterialDataTextArea(); + showSaveMaterialDataButton(); + showNewJSONMaterialEditorButton(); + + disableProperties(); + } else { + if (!isPropertiesToolUpdate && !hasSelectedEntityChanged && document.hasFocus()) { + // in case the selection has not changed and we still have focus on the properties page, + // we will ignore the event. + return; + } + + if (hasSelectedEntityChanged) { + if (!multipleSelections) { + resetServerScriptStatus(); + } + } + + const doSelectElement = !hasSelectedEntityChanged; + + // Get unique entity types, and convert the types Sphere and Box to Shape + const shapeTypes = ["Sphere", "Box"]; + const entityTypes = [...new Set(currentSelections.map(a => + shapeTypes.includes(a.properties.type) ? "Shape" : a.properties.type))]; + + const shownGroups = getGroupsForTypes(entityTypes); + showGroupsForTypes(entityTypes); + + const lockedMultiValue = getMultiplePropertyValue('locked'); + + if (lockedMultiValue.isMultiDiffValue || lockedMultiValue.value) { + disableProperties(); + getPropertyInputElement('locked').removeAttribute('disabled'); + } else { + enableProperties(); + disableSaveUserDataButton(); + disableSaveMaterialDataButton() + } + + const certificateIDMultiValue = getMultiplePropertyValue('certificateID'); + const hasCertifiedInSelection = certificateIDMultiValue.isMultiDiffValue || certificateIDMultiValue.value !== ""; + + Object.entries(properties).forEach(function([propertyID, property]) { + const propertyData = property.data; + const propertyName = property.name; + let propertyMultiValue = getMultiplePropertyValue(propertyName); + let isMultiDiffValue = propertyMultiValue.isMultiDiffValue; + let propertyValue = propertyMultiValue.value; + + if (propertyData.selectionVisibility !== undefined) { + let visibility = propertyData.selectionVisibility; + let propertyVisible = true; + if (!multipleSelections) { + propertyVisible = isFlagSet(visibility, PROPERTY_SELECTION_VISIBILITY.SINGLE_SELECTION); + } else if (isMultiDiffValue) { + propertyVisible = isFlagSet(visibility, PROPERTY_SELECTION_VISIBILITY.MULTI_DIFF_SELECTIONS); + } else { + propertyVisible = isFlagSet(visibility, PROPERTY_SELECTION_VISIBILITY.MULTIPLE_SELECTIONS); + } + setPropertyVisibility(property, propertyVisible); + } + + const isSubProperty = propertyData.subPropertyOf !== undefined; + if (propertyValue === undefined && !isMultiDiffValue && !isSubProperty) { + return; + } + + if (!shownGroups.includes(property.group_id)) { + const WANT_DEBUG_SHOW_HIDDEN_FROM_GROUPS = false; + if (WANT_DEBUG_SHOW_HIDDEN_FROM_GROUPS) { + console.log("Skipping property " + property.data.label + " [" + property.name + + "] from hidden group " + property.group_id); + } + return; + } + + if (propertyData.hideIfCertified && hasCertifiedInSelection) { + propertyValue = "** Certified **"; + property.elInput.disabled = true; + } + + if (propertyName === "type") { + propertyValue = entityTypes.length > 1 ? "Multiple" : propertyMultiValue.values[0]; + } + + switch (propertyData.type) { + case 'string': { + if (isMultiDiffValue) { + if (propertyData.readOnly && propertyData.multiDisplayMode + && propertyData.multiDisplayMode === PROPERTY_MULTI_DISPLAY_MODE.COMMA_SEPARATED_VALUES) { + property.elInput.value = propertyMultiValue.values.join(", "); + } else { + property.elInput.classList.add('multi-diff'); + property.elInput.value = ""; + } + } else { + property.elInput.classList.remove('multi-diff'); + property.elInput.value = propertyValue; + } + break; + } + case 'bool': { + const inverse = propertyData.inverse !== undefined ? propertyData.inverse : false; + if (isSubProperty) { + let subPropertyMultiValue = getMultiplePropertyValue(propertyData.subPropertyOf); + let propertyValue = subPropertyMultiValue.value; + isMultiDiffValue = subPropertyMultiValue.isMultiDiffValue; + if (isMultiDiffValue) { + let detailedSubProperty = getDetailedSubPropertyMPVDiff(subPropertyMultiValue, propertyName); + property.elInput.checked = detailedSubProperty.isChecked; + property.elInput.classList.toggle('multi-diff', detailedSubProperty.isMultiDiff); + } else { + let subProperties = propertyValue.split(","); + let subPropertyValue = subProperties.indexOf(propertyName) > -1; + property.elInput.checked = inverse ? !subPropertyValue : subPropertyValue; + property.elInput.classList.remove('multi-diff'); + } + + } else { + if (isMultiDiffValue) { + property.elInput.checked = false; + } else { + property.elInput.checked = inverse ? !propertyValue : propertyValue; + } + property.elInput.classList.toggle('multi-diff', isMultiDiffValue); + } + + break; + } + case 'number': { + property.elInput.value = isMultiDiffValue ? "" : propertyValue; + property.elInput.classList.toggle('multi-diff', isMultiDiffValue); + break; + } + case 'number-draggable': { + let detailedNumberDiff = getDetailedNumberMPVDiff(propertyMultiValue, propertyData); + property.elNumber.setValue(detailedNumberDiff.averagePerPropertyComponent[0], detailedNumberDiff.propertyComponentDiff[0]); + break; + } + case 'rect': { + let detailedNumberDiff = getDetailedNumberMPVDiff(propertyMultiValue, propertyData); + property.elNumberX.setValue(detailedNumberDiff.averagePerPropertyComponent.x, detailedNumberDiff.propertyComponentDiff.x); + property.elNumberY.setValue(detailedNumberDiff.averagePerPropertyComponent.y, detailedNumberDiff.propertyComponentDiff.y); + property.elNumberWidth.setValue(detailedNumberDiff.averagePerPropertyComponent.width, detailedNumberDiff.propertyComponentDiff.width); + property.elNumberHeight.setValue(detailedNumberDiff.averagePerPropertyComponent.height, detailedNumberDiff.propertyComponentDiff.height); + break; + } + case 'vec3': + case 'vec2': { + let detailedNumberDiff = getDetailedNumberMPVDiff(propertyMultiValue, propertyData); + property.elNumberX.setValue(detailedNumberDiff.averagePerPropertyComponent.x, detailedNumberDiff.propertyComponentDiff.x); + property.elNumberY.setValue(detailedNumberDiff.averagePerPropertyComponent.y, detailedNumberDiff.propertyComponentDiff.y); + if (property.elNumberZ !== undefined) { + property.elNumberZ.setValue(detailedNumberDiff.averagePerPropertyComponent.z, detailedNumberDiff.propertyComponentDiff.z); + } + break; + } + case 'color': { + let displayColor = propertyMultiValue.isMultiDiffValue ? propertyMultiValue.values[0] : propertyValue; + property.elColorPicker.style.backgroundColor = "rgb(" + displayColor.red + "," + + displayColor.green + "," + + displayColor.blue + ")"; + property.elColorPicker.classList.toggle('multi-diff', propertyMultiValue.isMultiDiffValue); + + if (hasSelectedEntityChanged && $(property.elColorPicker).attr('active') === 'true') { + // Set the color picker inactive before setting the color, + // otherwise an update will be sent directly after setting it here. + $(property.elColorPicker).attr('active', 'false'); + colorPickers['#' + property.elementID].colpickSetColor({ + "r": displayColor.red, + "g": displayColor.green, + "b": displayColor.blue + }); + $(property.elColorPicker).attr('active', 'true'); + } + + property.elNumberR.setValue(displayColor.red); + property.elNumberG.setValue(displayColor.green); + property.elNumberB.setValue(displayColor.blue); + break; + } + case 'dropdown': { + property.elInput.classList.toggle('multi-diff', isMultiDiffValue); + property.elInput.value = isMultiDiffValue ? "" : propertyValue; + setDropdownText(property.elInput); + break; + } + case 'textarea': { + property.elInput.value = propertyValue; + setTextareaScrolling(property.elInput); + break; + } + case 'icon': { + property.elSpan.innerHTML = propertyData.icons[propertyValue]; + property.elSpan.style.display = "inline-block"; + break; + } + case 'texture': { + property.elInput.value = isMultiDiffValue ? "" : propertyValue; + property.elInput.classList.toggle('multi-diff', isMultiDiffValue); + if (isMultiDiffValue) { + property.elInput.setMultipleValues(); + } else { + property.elInput.imageLoad(property.elInput.value); + } + break; + } + case 'dynamic-multiselect': { + if (!isMultiDiffValue && property.data.propertyUpdate) { + property.data.propertyUpdate(propertyValue); + } + break; + } + } + + let showPropertyRules = property.showPropertyRules; + if (showPropertyRules !== undefined) { + for (let propertyToShow in showPropertyRules) { + let showIfThisPropertyValue = showPropertyRules[propertyToShow]; + let show = String(propertyValue) === String(showIfThisPropertyValue); + showPropertyElement(propertyToShow, show); + } + } + }); + + updateVisibleSpaceModeProperties(); + + let userDataMultiValue = getMultiplePropertyValue("userData"); + let userDataTextArea = getPropertyInputElement("userData"); + let json = null; + if (!userDataMultiValue.isMultiDiffValue) { + try { + json = JSON.parse(userDataMultiValue.value); + } catch (e) { + + } + } + if (json !== null) { + if (editor === null) { + createJSONEditor(); + } + userDataTextArea.classList.remove('multi-diff'); + setEditorJSON(json); + showSaveUserDataButton(); + hideUserDataTextArea(); + hideNewJSONEditorButton(); + hideUserDataSaved(); + } else { + // normal text + deleteJSONEditor(); + userDataTextArea.classList.toggle('multi-diff', userDataMultiValue.isMultiDiffValue); + userDataTextArea.value = userDataMultiValue.isMultiDiffValue ? "" : userDataMultiValue.value; + + showUserDataTextArea(); + showNewJSONEditorButton(); + hideSaveUserDataButton(); + hideUserDataSaved(); + } + + let materialDataMultiValue = getMultiplePropertyValue("materialData"); + let materialDataTextArea = getPropertyInputElement("materialData"); + let materialJson = null; + if (!materialDataMultiValue.isMultiDiffValue) { + try { + materialJson = JSON.parse(materialDataMultiValue.value); + } catch (e) { + + } + } + if (materialJson !== null) { + if (materialEditor === null) { + createJSONMaterialEditor(); + } + materialDataTextArea.classList.remove('multi-diff'); + setMaterialEditorJSON(materialJson); + showSaveMaterialDataButton(); + hideMaterialDataTextArea(); + hideNewJSONMaterialEditorButton(); + hideMaterialDataSaved(); + } else { + // normal text + deleteJSONMaterialEditor(); + materialDataTextArea.classList.toggle('multi-diff', materialDataMultiValue.isMultiDiffValue); + materialDataTextArea.value = materialDataMultiValue.isMultiDiffValue ? "" : materialDataMultiValue.value; + showMaterialDataTextArea(); + showNewJSONMaterialEditorButton(); + hideSaveMaterialDataButton(); + hideMaterialDataSaved(); + } + + if (hasSelectedEntityChanged && selections.length === 1 && entityTypes[0] === "Material") { + requestMaterialTarget(); + } + + let activeElement = document.activeElement; + if (doSelectElement && typeof activeElement.select !== "undefined") { + activeElement.select(); + } + } +} + +function loaded() { + openEventBridge(function() { + let elPropertiesList = document.getElementById("properties-list"); + + GROUPS.forEach(function(group) { + let elGroup; + if (group.addToGroup !== undefined) { + let fieldset = document.getElementById("properties-" + group.addToGroup); + elGroup = document.createElement('div'); + fieldset.appendChild(elGroup); + } else { + elGroup = document.createElement('div'); + elGroup.className = 'section ' + (group.isMinor ? "minor" : "major"); + elGroup.setAttribute("id", "properties-" + group.id); + elPropertiesList.appendChild(elGroup); + } + + if (group.label !== undefined) { + let elLegend = document.createElement('div'); + elLegend.className = "section-header"; + + elLegend.appendChild(createElementFromHTML(`
${group.label}
`)); + + let elSpan = document.createElement('span'); + elSpan.className = "collapse-icon"; + elSpan.innerText = "M"; + elLegend.appendChild(elSpan); + elGroup.appendChild(elLegend); + } + + group.properties.forEach(function(propertyData) { + let propertyType = propertyData.type; + let propertyID = propertyData.propertyID; + let propertyName = propertyData.propertyName !== undefined ? propertyData.propertyName : propertyID; + let propertySpaceMode = propertyData.spaceMode !== undefined ? propertyData.spaceMode : PROPERTY_SPACE_MODE.ALL; + let propertyElementID = "property-" + propertyID; + propertyElementID = propertyElementID.replace('.', '-'); + + let elContainer, elLabel; + + if (propertyData.replaceID === undefined) { + // Create subheader, or create new property and append it. + if (propertyType === "sub-header") { + elContainer = createElementFromHTML( + `
${propertyData.label}
`); + } else { + elContainer = document.createElement('div'); + elContainer.setAttribute("id", "div-" + propertyElementID); + elContainer.className = 'property container'; + } + + if (group.twoColumn && propertyData.column !== undefined && propertyData.column !== -1) { + let columnName = group.id + "column" + propertyData.column; + let elColumn = document.getElementById(columnName); + if (!elColumn) { + let columnDivName = group.id + "columnDiv"; + let elColumnDiv = document.getElementById(columnDivName); + if (!elColumnDiv) { + elColumnDiv = document.createElement('div'); + elColumnDiv.className = "two-column"; + elColumnDiv.setAttribute("id", group.id + "columnDiv"); + elGroup.appendChild(elColumnDiv); + } + elColumn = document.createElement('fieldset'); + elColumn.className = "column"; + elColumn.setAttribute("id", columnName); + elColumnDiv.appendChild(elColumn); + } + elColumn.appendChild(elContainer); + } else { + elGroup.appendChild(elContainer); + } + + let labelText = propertyData.label !== undefined ? propertyData.label : ""; + let className = ''; + if (propertyData.indentedLabel || propertyData.showPropertyRule !== undefined) { + className = 'indented'; + } + elLabel = createElementFromHTML( + ``); + elContainer.appendChild(elLabel); + } else { + elContainer = document.getElementById(propertyData.replaceID); + } + + if (elLabel) { + createAppTooltip.registerTooltipElement(elLabel.childNodes[0], propertyID, propertyName); + } + + let elProperty = createElementFromHTML('
'); + elContainer.appendChild(elProperty); + + if (propertyType === 'triple') { + elProperty.className = 'flex-row'; + for (let i = 0; i < propertyData.properties.length; ++i) { + let innerPropertyData = propertyData.properties[i]; + + let elWrapper = createElementFromHTML('
'); + elProperty.appendChild(elWrapper); + + let propertyID = innerPropertyData.propertyID; + let propertyName = innerPropertyData.propertyName !== undefined ? innerPropertyData.propertyName : propertyID; + let propertyElementID = "property-" + propertyID; + propertyElementID = propertyElementID.replace('.', '-'); + + let property = createProperty(innerPropertyData, propertyElementID, propertyName, propertyID, elWrapper); + property.isParticleProperty = group.id.includes("particles"); + property.elContainer = elContainer; + property.spaceMode = propertySpaceMode; + property.group_id = group.id; + + let elLabel = createElementFromHTML(`
${innerPropertyData.label}
`); + createAppTooltip.registerTooltipElement(elLabel, propertyID, propertyName); + + elWrapper.appendChild(elLabel); + + if (property.type !== 'placeholder') { + properties[propertyID] = property; + } + if (innerPropertyData.type === 'number' || innerPropertyData.type === 'number-draggable') { + propertyRangeRequests.push(propertyID); + } + } + } else { + let property = createProperty(propertyData, propertyElementID, propertyName, propertyID, elProperty); + property.isParticleProperty = group.id.includes("particles"); + property.elContainer = elContainer; + property.spaceMode = propertySpaceMode; + property.group_id = group.id; + + if (property.type !== 'placeholder') { + properties[propertyID] = property; + } + if (propertyData.type === 'number' || propertyData.type === 'number-draggable' || + propertyData.type === 'vec2' || propertyData.type === 'vec3' || propertyData.type === 'rect') { + propertyRangeRequests.push(propertyID); + } + + let showPropertyRule = propertyData.showPropertyRule; + if (showPropertyRule !== undefined) { + let dependentProperty = Object.keys(showPropertyRule)[0]; + let dependentPropertyValue = showPropertyRule[dependentProperty]; + if (properties[dependentProperty] === undefined) { + properties[dependentProperty] = {}; + } + if (properties[dependentProperty].showPropertyRules === undefined) { + properties[dependentProperty].showPropertyRules = {}; + } + properties[dependentProperty].showPropertyRules[propertyID] = dependentPropertyValue; + } + } + }); + + elGroups[group.id] = elGroup; + }); + + let minorSections = document.querySelectorAll(".section.minor"); + minorSections[minorSections.length - 1].className += " last"; + + updateVisibleSpaceModeProperties(); + + if (window.EventBridge !== undefined) { + EventBridge.scriptEventReceived.connect(function(data) { + data = JSON.parse(data); + if (data.type === "server_script_status" && selectedEntityIDs.size === 1) { + let elServerScriptError = document.getElementById("property-serverScripts-error"); + let elServerScriptStatus = document.getElementById("property-serverScripts-status"); + elServerScriptError.value = data.errorInfo; + // If we just set elServerScriptError's display to block or none, we still end up with + // it's parent contributing 21px bottom padding even when elServerScriptError is display:none. + // So set it's parent to block or none + elServerScriptError.parentElement.style.display = data.errorInfo ? "block" : "none"; + if (data.statusRetrieved === false) { + elServerScriptStatus.innerText = "Failed to retrieve status"; + } else if (data.isRunning) { + elServerScriptStatus.innerText = ENTITY_SCRIPT_STATUS[data.status] || data.status; + } else { + elServerScriptStatus.innerText = NOT_RUNNING_SCRIPT_STATUS; + } + } else if (data.type === "update" && data.selections) { + if (data.spaceMode !== undefined) { + currentSpaceMode = data.spaceMode === "local" ? PROPERTY_SPACE_MODE.LOCAL : PROPERTY_SPACE_MODE.WORLD; + } + handleEntitySelectionUpdate(data.selections, data.isPropertiesToolUpdate); + } else if (data.type === 'tooltipsReply') { + createAppTooltip.setIsEnabled(!data.hmdActive); + createAppTooltip.setTooltipData(data.tooltips); + } else if (data.type === 'hmdActiveChanged') { + createAppTooltip.setIsEnabled(!data.hmdActive); + } else if (data.type === 'setSpaceMode') { + currentSpaceMode = data.spaceMode === "local" ? PROPERTY_SPACE_MODE.LOCAL : PROPERTY_SPACE_MODE.WORLD; + updateVisibleSpaceModeProperties(); + } else if (data.type === 'propertyRangeReply') { + let propertyRanges = data.propertyRanges; + for (let property in propertyRanges) { + let propertyRange = propertyRanges[property]; + if (propertyRange !== undefined) { + let propertyData = properties[property].data; + let multiplier = propertyData.multiplier; + if (propertyData.min === undefined && propertyRange.minimum !== "") { + propertyData.min = propertyRange.minimum; + if (multiplier !== undefined) { + propertyData.min /= multiplier; + } + } + if (propertyData.max === undefined && propertyRange.maximum !== "") { + propertyData.max = propertyRange.maximum; + if (multiplier !== undefined) { + propertyData.max /= multiplier; + } + } + switch (propertyData.type) { + case 'number': + updateNumberMinMax(properties[property]); + break; + case 'number-draggable': + updateNumberDraggableMinMax(properties[property]); + break; + case 'vec3': + case 'vec2': + updateVectorMinMax(properties[property]); + break; + case 'rect': + updateRectMinMax(properties[property]); + break; + } + } + } + } else if (data.type === 'materialTargetReply') { + if (data.entityID === getFirstSelectedID()) { + setMaterialTargetData(data.materialTargetData); + } + } + }); + + // Request tooltips and property ranges as soon as we can process a reply: + EventBridge.emitWebEvent(JSON.stringify({ type: 'tooltipsRequest' })); + EventBridge.emitWebEvent(JSON.stringify({ type: 'propertyRangeRequest', properties: propertyRangeRequests })); + } + + // Server Script Status + let elServerScriptStatusOuter = document.getElementById('div-property-serverScriptStatus'); + let elServerScriptStatusContainer = document.getElementById('div-property-serverScriptStatus').childNodes[1]; + let serverScriptStatusElementID = 'property-serverScripts-status'; + createAppTooltip.registerTooltipElement(elServerScriptStatusOuter.childNodes[0], "serverScriptsStatus"); + let elServerScriptStatus = document.createElement('div'); + elServerScriptStatus.setAttribute("id", serverScriptStatusElementID); + elServerScriptStatusContainer.appendChild(elServerScriptStatus); + + // Server Script Error + let elServerScripts = getPropertyInputElement("serverScripts"); + let elDiv = document.createElement('div'); + elDiv.className = "property"; + let elServerScriptError = document.createElement('textarea'); + let serverScriptErrorElementID = 'property-serverScripts-error'; + elServerScriptError.setAttribute("id", serverScriptErrorElementID); + elDiv.appendChild(elServerScriptError); + elServerScriptStatusContainer.appendChild(elDiv); + + let elScript = getPropertyInputElement("script"); + elScript.parentNode.className = "url refresh"; + elServerScripts.parentNode.className = "url refresh"; + + // User Data + let userDataProperty = properties["userData"]; + let elUserData = userDataProperty.elInput; + let userDataElementID = userDataProperty.elementID; + elDiv = elUserData.parentNode; + let elStaticUserData = document.createElement('div'); + elStaticUserData.setAttribute("id", userDataElementID + "-static"); + let elUserDataEditor = document.createElement('div'); + elUserDataEditor.setAttribute("id", userDataElementID + "-editor"); + let elUserDataEditorStatus = document.createElement('div'); + elUserDataEditorStatus.setAttribute("id", userDataElementID + "-editorStatus"); + let elUserDataSaved = document.createElement('span'); + elUserDataSaved.setAttribute("id", userDataElementID + "-saved"); + elUserDataSaved.innerText = "Saved!"; + elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elUserDataSaved); + elDiv.insertBefore(elStaticUserData, elUserData); + elDiv.insertBefore(elUserDataEditor, elUserData); + elDiv.insertBefore(elUserDataEditorStatus, elUserData); + + // Material Data + let materialDataProperty = properties["materialData"]; + let elMaterialData = materialDataProperty.elInput; + let materialDataElementID = materialDataProperty.elementID; + elDiv = elMaterialData.parentNode; + let elStaticMaterialData = document.createElement('div'); + elStaticMaterialData.setAttribute("id", materialDataElementID + "-static"); + let elMaterialDataEditor = document.createElement('div'); + elMaterialDataEditor.setAttribute("id", materialDataElementID + "-editor"); + let elMaterialDataEditorStatus = document.createElement('div'); + elMaterialDataEditorStatus.setAttribute("id", materialDataElementID + "-editorStatus"); + let elMaterialDataSaved = document.createElement('span'); + elMaterialDataSaved.setAttribute("id", materialDataElementID + "-saved"); + elMaterialDataSaved.innerText = "Saved!"; + elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elMaterialDataSaved); + elDiv.insertBefore(elStaticMaterialData, elMaterialData); + elDiv.insertBefore(elMaterialDataEditor, elMaterialData); + elDiv.insertBefore(elMaterialDataEditorStatus, elMaterialData); + + // Collapsible sections + let elCollapsible = document.getElementsByClassName("collapse-icon"); + + let toggleCollapsedEvent = function(event) { + let element = this.parentNode.parentNode; + let isCollapsed = element.dataset.collapsed !== "true"; + element.dataset.collapsed = isCollapsed ? "true" : false; + element.setAttribute("collapsed", isCollapsed ? "true" : "false"); + this.textContent = isCollapsed ? "L" : "M"; + }; + + for (let collapseIndex = 0, numCollapsibles = elCollapsible.length; collapseIndex < numCollapsibles; ++collapseIndex) { + let curCollapsibleElement = elCollapsible[collapseIndex]; + curCollapsibleElement.addEventListener("click", toggleCollapsedEvent, true); + } + + // Textarea scrollbars + let elTextareas = document.getElementsByTagName("TEXTAREA"); + + let textareaOnChangeEvent = function(event) { + setTextareaScrolling(event.target); + }; + + for (let textAreaIndex = 0, numTextAreas = elTextareas.length; textAreaIndex < numTextAreas; ++textAreaIndex) { + let curTextAreaElement = elTextareas[textAreaIndex]; + setTextareaScrolling(curTextAreaElement); + curTextAreaElement.addEventListener("input", textareaOnChangeEvent, false); + curTextAreaElement.addEventListener("change", textareaOnChangeEvent, false); + /* FIXME: Detect and update textarea scrolling attribute on resize. Unfortunately textarea doesn't have a resize + event; mouseup is a partial stand-in but doesn't handle resizing if mouse moves outside textarea rectangle. */ + curTextAreaElement.addEventListener("mouseup", textareaOnChangeEvent, false); + } + + // Dropdowns + // For each dropdown the following replacement is created in place of the original dropdown... + // Structure created: + //
+ //
display textcarat
+ //
+ //
    + //
  • 0) { + let el = elDropdowns[0]; + el.parentNode.removeChild(el); + elDropdowns = document.getElementsByTagName("select"); + } + + const KEY_CODES = { + BACKSPACE: 8, + DELETE: 46 + }; + + document.addEventListener("keyup", function (keyUpEvent) { + const FILTERED_NODE_NAMES = ["INPUT", "TEXTAREA"]; + if (FILTERED_NODE_NAMES.includes(keyUpEvent.target.nodeName)) { + return; + } + + if (elUserDataEditor.contains(keyUpEvent.target) || elMaterialDataEditor.contains(keyUpEvent.target)) { + return; + } + + let {code, key, keyCode, altKey, ctrlKey, metaKey, shiftKey} = keyUpEvent; + + let controlKey = window.navigator.platform.startsWith("Mac") ? metaKey : ctrlKey; + + let keyCodeString; + switch (keyCode) { + case KEY_CODES.DELETE: + keyCodeString = "Delete"; + break; + case KEY_CODES.BACKSPACE: + keyCodeString = "Backspace"; + break; + default: + keyCodeString = String.fromCharCode(keyUpEvent.keyCode); + break; + } + + EventBridge.emitWebEvent(JSON.stringify({ + type: 'keyUpEvent', + keyUpEvent: { + code, + key, + keyCode, + keyCodeString, + altKey, + controlKey, + shiftKey, + } + })); + }, false); + + window.onblur = function() { + // Fake a change event + let ev = document.createEvent("HTMLEvents"); + ev.initEvent("change", true, true); + document.activeElement.dispatchEvent(ev); + }; + + // For input and textarea elements, select all of the text on focus + let els = document.querySelectorAll("input, textarea"); + for (let i = 0; i < els.length; ++i) { + els[i].onfocus = function (e) { + e.target.select(); + }; + } + + bindAllNonJSONEditorElements(); + + showGroupsForType("None"); + resetProperties(); + disableProperties(); + }); + + augmentSpinButtons(); + disableDragDrop(); + + // 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) { + event.preventDefault(); + }, false); + + setTimeout(function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'propertiesPageReady' })); + }, 1000); +} diff --git a/scripts/simplifiedUI/system/html/js/eventBridgeLoader.js b/scripts/simplifiedUI/system/html/js/eventBridgeLoader.js new file mode 100644 index 0000000000..411780853b --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/eventBridgeLoader.js @@ -0,0 +1,19 @@ + +//public slots: +// void emitWebEvent(const QString& data); +// void emitScriptEvent(const QString& data); +// +//signals: +// void webEventReceived(const QString& data); +// void scriptEventReceived(const QString& data); +// + +var EventBridge; +var WebChannel; + +openEventBridge = function(callback) { + WebChannel = new QWebChannel(qt.webChannelTransport, function (channel) { + EventBridge = WebChannel.objects.eventBridge; + callback(EventBridge); + }); +} diff --git a/scripts/simplifiedUI/system/html/js/gridControls.js b/scripts/simplifiedUI/system/html/js/gridControls.js new file mode 100644 index 0000000000..e27dac522b --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/gridControls.js @@ -0,0 +1,161 @@ +// gridControls.js +// +// Created by Ryan Huffman on 6 Nov 2014 +// Copyright 2014 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 + +function loaded() { + openEventBridge(function() { + elPosY = document.getElementById("horiz-y"); + elMinorSpacing = document.getElementById("minor-spacing"); + elMajorSpacing = document.getElementById("major-spacing"); + elSnapToGrid = document.getElementById("snap-to-grid"); + elHorizontalGridVisible = document.getElementById("horiz-grid-visible"); + elMoveToSelection = document.getElementById("move-to-selection"); + elMoveToAvatar = document.getElementById("move-to-avatar"); + + if (window.EventBridge !== undefined) { + EventBridge.scriptEventReceived.connect(function(data) { + data = JSON.parse(data); + + if (data.origin) { + var origin = data.origin; + elPosY.value = origin.y; + } + + if (data.minorGridEvery !== undefined) { + elMinorSpacing.value = data.minorGridEvery; + } + + if (data.majorGridEvery !== undefined) { + elMajorSpacing.value = data.majorGridEvery; + } + + if (data.gridColor) { + gridColor = data.gridColor; + } + + if (data.snapToGrid !== undefined) { + elSnapToGrid.checked = data.snapToGrid == true; + } + + if (data.visible !== undefined) { + elHorizontalGridVisible.checked = data.visible == true; + } + }); + + function emitUpdate() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "update", + origin: { + y: elPosY.value, + }, + minorGridEvery: elMinorSpacing.value, + majorGridEvery: elMajorSpacing.value, + gridColor: gridColor, + snapToGrid: elSnapToGrid.checked, + visible: elHorizontalGridVisible.checked, + })); + } + + } + + elPosY.addEventListener("change", emitUpdate); + elMinorSpacing.addEventListener("change", emitUpdate); + elMajorSpacing.addEventListener("change", emitUpdate); + elSnapToGrid.addEventListener("change", emitUpdate); + elHorizontalGridVisible.addEventListener("change", emitUpdate); + + elMoveToAvatar.addEventListener("click", function() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveToAvatar", + })); + }); + elMoveToSelection.addEventListener("click", function() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveToSelection", + })); + }); + + var gridColor = { red: 255, green: 255, blue: 255 }; + var elColor = document.getElementById("grid-color"); + elColor.style.backgroundColor = "rgb(" + gridColor.red + "," + gridColor.green + "," + gridColor.blue + ")"; + + var colorPickFunction = function (red, green, blue) { + gridColor = { red: red, green: green, blue: blue }; + emitUpdate(); + }; + + $('#grid-color').colpick({ + colorScheme: 'dark', + layout: 'rgbhex', + color: { r: gridColor.red, g: gridColor.green, b: gridColor.blue }, + submit: false, + onShow: function (colpick) { + $('#grid-color').attr('active', 'true'); + }, + onHide: function (colpick) { + $('#grid-color').attr('active', 'false'); + }, + onChange: function (hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + colorPickFunction(rgb.r, rgb.g, rgb.b); + } + }); + + augmentSpinButtons(); + disableDragDrop(); + + EventBridge.emitWebEvent(JSON.stringify({ type: 'init' })); + }); + + const KEY_CODES = { + BACKSPACE: 8, + DELETE: 46 + }; + + document.addEventListener("keyup", function (keyUpEvent) { + const FILTERED_NODE_NAMES = ["INPUT", "TEXTAREA"]; + if (FILTERED_NODE_NAMES.includes(keyUpEvent.target.nodeName)) { + return; + } + let {code, key, keyCode, altKey, ctrlKey, metaKey, shiftKey} = keyUpEvent; + + let controlKey = window.navigator.platform.startsWith("Mac") ? metaKey : ctrlKey; + + let keyCodeString; + switch (keyCode) { + case KEY_CODES.DELETE: + keyCodeString = "Delete"; + break; + case KEY_CODES.BACKSPACE: + keyCodeString = "Backspace"; + break; + default: + keyCodeString = String.fromCharCode(keyUpEvent.keyCode); + break; + } + + EventBridge.emitWebEvent(JSON.stringify({ + type: 'keyUpEvent', + keyUpEvent: { + code, + key, + keyCode, + keyCodeString, + altKey, + controlKey, + shiftKey, + } + })); + }, false); + + // 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) { + event.preventDefault(); + }, false); +} diff --git a/scripts/simplifiedUI/system/html/js/includes.js b/scripts/simplifiedUI/system/html/js/includes.js new file mode 100644 index 0000000000..c604115f91 --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/includes.js @@ -0,0 +1,27 @@ +// +// includes.js +// +// Created by David Back on 3 Jan 2019 +// Copyright 2016 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 +// + +const ENTITY_TYPE_ICON = { + Box: "m", + Grid: "", + Image: "", + Light: "p", + Material: "", + Model: "", + ParticleEffect: "", + PolyVox: "", + PolyLine: "", + Shape: "n", + Sphere: "n", + Text: "l", + Web: "q", + Zone: "o", + Multiple: "", +}; diff --git a/scripts/simplifiedUI/system/html/js/jquery-2.1.4.min.js b/scripts/simplifiedUI/system/html/js/jquery-2.1.4.min.js new file mode 100644 index 0000000000..49990d6e14 --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/jquery-2.1.4.min.js @@ -0,0 +1,4 @@ +/*! jQuery v2.1.4 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b="length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){ +return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,ba=/<([\w:]+)/,ca=/<|&#?\w+;/,da=/<(?:script|style|link)/i,ea=/checked\s*(?:[^=]|=\s*.checked.)/i,fa=/^$|\/(?:java|ecma)script/i,ga=/^true\/(.*)/,ha=/^\s*\s*$/g,ia={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ia.optgroup=ia.option,ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead,ia.th=ia.td;function ja(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function ka(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function la(a){var b=ga.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function ma(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function na(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function oa(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pa(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=oa(h),f=oa(a),d=0,e=f.length;e>d;d++)pa(f[d],g[d]);if(b)if(c)for(f=f||oa(a),g=g||oa(h),d=0,e=f.length;e>d;d++)na(f[d],g[d]);else na(a,h);return g=oa(h,"script"),g.length>0&&ma(g,!i&&oa(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(ca.test(e)){f=f||k.appendChild(b.createElement("div")),g=(ba.exec(e)||["",""])[1].toLowerCase(),h=ia[g]||ia._default,f.innerHTML=h[1]+e.replace(aa,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=oa(k.appendChild(e),"script"),i&&ma(f),c)){j=0;while(e=f[j++])fa.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(oa(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&ma(oa(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(oa(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!da.test(a)&&!ia[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(aa,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(oa(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(oa(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&ea.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(oa(c,"script"),ka),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,oa(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,la),j=0;g>j;j++)h=f[j],fa.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(ha,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qa,ra={};function sa(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function ta(a){var b=l,c=ra[a];return c||(c=sa(a,b),"none"!==c&&c||(qa=(qa||n("