Initial
This commit is contained in:
557
resources/app/client/apps/av/cameras.js
Normal file
557
resources/app/client/apps/av/cameras.js
Normal file
@@ -0,0 +1,557 @@
|
||||
/**
|
||||
* The Camera UI View that displays all the camera feeds as individual video elements.
|
||||
* @type {Application}
|
||||
*
|
||||
* @param {WebRTC} webrtc The WebRTC Implementation to display
|
||||
* @param {ApplicationOptions} [options] Application configuration options.
|
||||
*/
|
||||
class CameraViews extends Application {
|
||||
constructor(options={}) {
|
||||
if ( !("width" in options) ) options.width = game.webrtc?.settings.client.dockWidth || 240;
|
||||
super(options);
|
||||
if ( game.webrtc?.settings.client.dockPosition === AVSettings.DOCK_POSITIONS.RIGHT ) {
|
||||
this.options.resizable.rtl = true;
|
||||
}
|
||||
}
|
||||
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
id: "camera-views",
|
||||
template: "templates/hud/camera-views.html",
|
||||
popOut: false,
|
||||
width: 240,
|
||||
resizable: {selector: ".camera-view-width-control", resizeY: false}
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* A reference to the master AV orchestrator instance
|
||||
* @type {AVMaster}
|
||||
*/
|
||||
get webrtc() {
|
||||
return game.webrtc;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* If all camera views are popped out, hide the dock.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get hidden() {
|
||||
return this.webrtc.client.getConnectedUsers().reduce((hidden, u) => {
|
||||
const settings = this.webrtc.settings.users[u];
|
||||
return hidden && (settings.blocked || settings.popout);
|
||||
}, true);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Public API */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Obtain a reference to the div.camera-view which is used to portray a given Foundry User.
|
||||
* @param {string} userId The ID of the User document
|
||||
* @return {HTMLElement|null}
|
||||
*/
|
||||
getUserCameraView(userId) {
|
||||
return this.element.find(`.camera-view[data-user=${userId}]`)[0] || null;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Obtain a reference to the video.user-camera which displays the video channel for a requested Foundry User.
|
||||
* If the user is not broadcasting video this will return null.
|
||||
* @param {string} userId The ID of the User document
|
||||
* @return {HTMLVideoElement|null}
|
||||
*/
|
||||
getUserVideoElement(userId) {
|
||||
return this.element.find(`.camera-view[data-user=${userId}] video.user-camera`)[0] || null;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Sets whether a user is currently speaking or not
|
||||
*
|
||||
* @param {string} userId The ID of the user
|
||||
* @param {boolean} speaking Whether the user is speaking
|
||||
*/
|
||||
setUserIsSpeaking(userId, speaking) {
|
||||
const view = this.getUserCameraView(userId);
|
||||
if ( view ) view.classList.toggle("speaking", speaking);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Application Rendering */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Extend the render logic to first check whether a render is necessary based on the context
|
||||
* If a specific context was provided, make sure an update to the navigation is necessary before rendering
|
||||
*/
|
||||
render(force, context={}) {
|
||||
const { renderContext, renderData } = context;
|
||||
if ( this.webrtc.mode === AVSettings.AV_MODES.DISABLED ) return this;
|
||||
if ( renderContext ) {
|
||||
if ( renderContext !== "updateUser" ) return this;
|
||||
const updateKeys = ["name", "permissions", "role", "active", "color", "sort", "character", "avatar"];
|
||||
if ( !updateKeys.some(k => renderData.hasOwnProperty(k)) ) return this;
|
||||
}
|
||||
return super.render(force, context);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
async _render(force = false, options = {}) {
|
||||
await super._render(force, options);
|
||||
this.webrtc.onRender();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritdoc */
|
||||
setPosition({left, top, width, scale} = {}) {
|
||||
const position = super.setPosition({left, top, width, height: "auto", scale});
|
||||
if ( foundry.utils.isEmpty(position) ) return position;
|
||||
const clientSettings = game.webrtc.settings.client;
|
||||
if ( game.webrtc.settings.verticalDock ) {
|
||||
clientSettings.dockWidth = width;
|
||||
game.webrtc.settings.set("client", "dockWidth", width);
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData(options={}) {
|
||||
const settings = this.webrtc.settings;
|
||||
const userSettings = settings.users;
|
||||
|
||||
// Get the sorted array of connected users
|
||||
const connectedIds = this.webrtc.client.getConnectedUsers();
|
||||
const users = connectedIds.reduce((users, u) => {
|
||||
const data = this._getDataForUser(u, userSettings[u]);
|
||||
if ( data && !userSettings[u].blocked ) users.push(data);
|
||||
return users;
|
||||
}, []);
|
||||
users.sort(this.constructor._sortUsers);
|
||||
|
||||
// Maximum Z of all user popout windows
|
||||
this.maxZ = Math.max(...users.map(u => userSettings[u.user.id].z));
|
||||
|
||||
// Define a dynamic class for the camera dock container which affects its rendered style
|
||||
const dockClass = [`camera-position-${settings.client.dockPosition}`];
|
||||
if ( !users.some(u => !u.settings.popout) ) dockClass.push("webrtc-dock-empty");
|
||||
if ( settings.client.hideDock ) dockClass.push("webrtc-dock-minimized");
|
||||
if ( this.hidden ) dockClass.push("hidden");
|
||||
|
||||
// Alter the body class depending on whether the players list is hidden
|
||||
const playersVisible = !settings.client.hidePlayerList || settings.client.hideDock;
|
||||
document.body.classList.toggle("players-hidden", playersVisible);
|
||||
|
||||
const nameplateModes = AVSettings.NAMEPLATE_MODES;
|
||||
const nameplateSetting = settings.client.nameplates ?? nameplateModes.BOTH;
|
||||
|
||||
const nameplates = {
|
||||
cssClass: [
|
||||
nameplateSetting === nameplateModes.OFF ? "hidden" : "",
|
||||
[nameplateModes.PLAYER_ONLY, nameplateModes.CHAR_ONLY].includes(nameplateSetting) ? "noanimate" : ""
|
||||
].filterJoin(" "),
|
||||
playerName: [nameplateModes.BOTH, nameplateModes.PLAYER_ONLY].includes(nameplateSetting),
|
||||
charname: [nameplateModes.BOTH, nameplateModes.CHAR_ONLY].includes(nameplateSetting)
|
||||
};
|
||||
|
||||
// Return data for rendering
|
||||
return {
|
||||
self: game.user,
|
||||
muteAll: settings.muteAll,
|
||||
borderColors: settings.client.borderColors,
|
||||
dockClass: dockClass.join(" "),
|
||||
hidden: this.hidden,
|
||||
users, nameplates
|
||||
};
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Prepare rendering data for a single user
|
||||
* @private
|
||||
*/
|
||||
_getDataForUser(userId, settings) {
|
||||
const user = game.users.get(userId);
|
||||
if ( !user || !user.active ) return null;
|
||||
const charname = user.character ? user.character.name.split(" ")[0] : "";
|
||||
|
||||
// CSS classes for the frame
|
||||
const frameClass = settings.popout ? "camera-box-popout" : "camera-box-dock";
|
||||
const audioClass = this.webrtc.canUserShareAudio(userId) ? null : "no-audio";
|
||||
const videoClass = this.webrtc.canUserShareVideo(userId) ? null : "no-video";
|
||||
|
||||
// Return structured User data
|
||||
return {
|
||||
user, settings,
|
||||
local: user.isSelf,
|
||||
charname: user.isGM ? game.i18n.localize("USER.GM") : charname,
|
||||
volume: foundry.audio.AudioHelper.volumeToInput(settings.volume),
|
||||
cameraViewClass: [frameClass, videoClass, audioClass].filterJoin(" ")
|
||||
};
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* A custom sorting function that orders/arranges the user display frames
|
||||
* @return {number}
|
||||
* @private
|
||||
*/
|
||||
static _sortUsers(a, b) {
|
||||
const as = a.settings;
|
||||
const bs = b.settings;
|
||||
if (as.popout && bs.popout) return as.z - bs.z; // Sort popouts by z-index
|
||||
if (as.popout) return -1; // Show popout feeds first
|
||||
if (bs.popout) return 1;
|
||||
if (a.user.isSelf) return -1; // Show local feed first
|
||||
if (b.user.isSelf) return 1;
|
||||
if (a.hasVideo && !b.hasVideo) return -1; // Show remote users with a camera before those without
|
||||
if (b.hasVideo && !a.hasVideo) return 1;
|
||||
return a.user.sort - b.user.sort; // Sort according to user order
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Event Listeners and Handlers */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
activateListeners(html) {
|
||||
|
||||
// Display controls when hovering over the video container
|
||||
let cvh = this._onCameraViewHover.bind(this);
|
||||
html.find(".camera-view").hover(cvh, cvh);
|
||||
|
||||
// Handle clicks on AV control buttons
|
||||
html.find(".av-control").click(this._onClickControl.bind(this));
|
||||
|
||||
// Handle volume changes
|
||||
html.find(".webrtc-volume-slider").change(this._onVolumeChange.bind(this));
|
||||
|
||||
// Handle user controls.
|
||||
this._refreshView(html.find(".user-controls")[0]?.dataset.user);
|
||||
|
||||
// Hide Global permission icons depending on the A/V mode
|
||||
const mode = this.webrtc.mode;
|
||||
if ( mode === AVSettings.AV_MODES.VIDEO ) html.find('[data-action="toggle-audio"]').hide();
|
||||
if ( mode === AVSettings.AV_MODES.AUDIO ) html.find('[data-action="toggle-video"]').hide();
|
||||
|
||||
// Make each popout window draggable
|
||||
for ( let popout of this.element.find(".app.camera-view-popout") ) {
|
||||
let box = popout.querySelector(".camera-view");
|
||||
new CameraPopoutAppWrapper(this, box.dataset.user, $(popout));
|
||||
}
|
||||
|
||||
// Listen to the video's srcObjectSet event to set the display mode of the user.
|
||||
for ( let video of this.element.find("video") ) {
|
||||
const view = video.closest(".camera-view");
|
||||
this._refreshView(view.dataset.user);
|
||||
video.addEventListener("webrtcVideoSet", ev => {
|
||||
const view = video.closest(".camera-view");
|
||||
if ( view.dataset.user !== ev.detail ) return;
|
||||
this._refreshView(view.dataset.user);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* On hover in a camera container, show/hide the controls.
|
||||
* @event {Event} event The original mouseover or mouseout hover event
|
||||
* @private
|
||||
*/
|
||||
_onCameraViewHover(event) {
|
||||
this._toggleControlVisibility(event.currentTarget, event.type === "mouseenter", null);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* On clicking on a toggle, disable/enable the audio or video stream.
|
||||
* @event {MouseEvent} event The originating click event
|
||||
* @private
|
||||
*/
|
||||
async _onClickControl(event) {
|
||||
event.preventDefault();
|
||||
|
||||
// Reference relevant data
|
||||
const button = event.currentTarget;
|
||||
const action = button.dataset.action;
|
||||
const userId = button.closest(".camera-view, .user-controls")?.dataset.user;
|
||||
const user = game.users.get(userId);
|
||||
const settings = this.webrtc.settings;
|
||||
const userSettings = settings.getUser(user.id);
|
||||
|
||||
// Handle different actions
|
||||
switch ( action ) {
|
||||
|
||||
// Globally block video
|
||||
case "block-video":
|
||||
if ( !game.user.isGM ) return;
|
||||
await user.update({"permissions.BROADCAST_VIDEO": !userSettings.canBroadcastVideo});
|
||||
return this._refreshView(userId);
|
||||
|
||||
// Globally block audio
|
||||
case "block-audio":
|
||||
if ( !game.user.isGM ) return;
|
||||
await user.update({"permissions.BROADCAST_AUDIO": !userSettings.canBroadcastAudio});
|
||||
return this._refreshView(userId);
|
||||
|
||||
// Hide the user
|
||||
case "hide-user":
|
||||
if ( user.isSelf ) return;
|
||||
await settings.set("client", `users.${user.id}.blocked`, !userSettings.blocked);
|
||||
return this.render();
|
||||
|
||||
// Toggle video display
|
||||
case "toggle-video":
|
||||
if ( !user.isSelf ) return;
|
||||
if ( userSettings.hidden && !userSettings.canBroadcastVideo ) {
|
||||
return ui.notifications.warn("WEBRTC.WarningCannotEnableVideo", {localize: true});
|
||||
}
|
||||
await settings.set("client", `users.${user.id}.hidden`, !userSettings.hidden);
|
||||
return this._refreshView(userId);
|
||||
|
||||
// Toggle audio output
|
||||
case "toggle-audio":
|
||||
if ( !user.isSelf ) return;
|
||||
if ( userSettings.muted && !userSettings.canBroadcastAudio ) {
|
||||
return ui.notifications.warn("WEBRTC.WarningCannotEnableAudio", {localize: true});
|
||||
}
|
||||
await settings.set("client", `users.${user.id}.muted`, !userSettings.muted);
|
||||
return this._refreshView(userId);
|
||||
|
||||
// Toggle mute all peers
|
||||
case "mute-peers":
|
||||
if ( !user.isSelf ) return;
|
||||
await settings.set("client", "muteAll", !settings.client.muteAll);
|
||||
return this._refreshView(userId);
|
||||
|
||||
// Disable sending and receiving video
|
||||
case "disable-video":
|
||||
if ( !user.isSelf ) return;
|
||||
await settings.set("client", "disableVideo", !settings.client.disableVideo);
|
||||
return this._refreshView(userId);
|
||||
|
||||
// Configure settings
|
||||
case "configure":
|
||||
return this.webrtc.config.render(true);
|
||||
|
||||
// Toggle popout
|
||||
case "toggle-popout":
|
||||
await settings.set("client", `users.${user.id}.popout`, !userSettings.popout);
|
||||
return this.render();
|
||||
|
||||
// Hide players
|
||||
case "toggle-players":
|
||||
await settings.set("client", "hidePlayerList", !settings.client.hidePlayerList);
|
||||
return this.render();
|
||||
|
||||
// Minimize the dock
|
||||
case "toggle-dock":
|
||||
await settings.set("client", "hideDock", !settings.client.hideDock);
|
||||
return this.render();
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Change volume control for a stream
|
||||
* @param {Event} event The originating change event from interaction with the range input
|
||||
* @private
|
||||
*/
|
||||
_onVolumeChange(event) {
|
||||
const input = event.currentTarget;
|
||||
const box = input.closest(".camera-view");
|
||||
const userId = box.dataset.user;
|
||||
let volume = foundry.audio.AudioHelper.inputToVolume(input.value);
|
||||
box.getElementsByTagName("video")[0].volume = volume;
|
||||
this.webrtc.settings.set("client", `users.${userId}.volume`, volume);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Internal Helpers */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Dynamically refresh the state of a single camera view
|
||||
* @param {string} userId The ID of the user whose view we want to refresh.
|
||||
* @protected
|
||||
*/
|
||||
_refreshView(userId) {
|
||||
const view = this.element[0].querySelector(`.camera-view[data-user="${userId}"]`);
|
||||
const isSelf = game.user.id === userId;
|
||||
const clientSettings = game.webrtc.settings.client;
|
||||
const userSettings = game.webrtc.settings.getUser(userId);
|
||||
const minimized = clientSettings.hideDock;
|
||||
const isVertical = game.webrtc.settings.verticalDock;
|
||||
|
||||
// Identify permissions
|
||||
const cbv = game.webrtc.canUserBroadcastVideo(userId);
|
||||
const csv = game.webrtc.canUserShareVideo(userId);
|
||||
const cba = game.webrtc.canUserBroadcastAudio(userId);
|
||||
const csa = game.webrtc.canUserShareAudio(userId);
|
||||
|
||||
// Refresh video display
|
||||
const video = view.querySelector("video.user-camera");
|
||||
const avatar = view.querySelector("img.user-avatar");
|
||||
if ( video && avatar ) {
|
||||
const showVideo = csv && (isSelf || !clientSettings.disableVideo) && (!minimized || userSettings.popout);
|
||||
video.style.visibility = showVideo ? "visible" : "hidden";
|
||||
video.style.display = showVideo ? "block" : "none";
|
||||
avatar.style.display = showVideo ? "none" : "unset";
|
||||
}
|
||||
|
||||
// Hidden and muted status icons
|
||||
view.querySelector(".status-hidden")?.classList.toggle("hidden", csv);
|
||||
view.querySelector(".status-muted")?.classList.toggle("hidden", csa);
|
||||
|
||||
// Volume bar and video output volume
|
||||
if ( video ) {
|
||||
video.volume = userSettings.volume;
|
||||
video.muted = isSelf || clientSettings.muteAll; // Mute your own video
|
||||
}
|
||||
const volBar = this.element[0].querySelector(`[data-user="${userId}"] .volume-bar`);
|
||||
if ( volBar ) {
|
||||
const displayBar = (userId !== game.user.id) && cba;
|
||||
volBar.style.display = displayBar ? "block" : "none";
|
||||
volBar.disabled = !displayBar;
|
||||
}
|
||||
|
||||
// Control toggle states
|
||||
const actions = {
|
||||
"block-video": {state: !cbv, display: game.user.isGM && !isSelf},
|
||||
"block-audio": {state: !cba, display: game.user.isGM && !isSelf},
|
||||
"hide-user": {state: !userSettings.blocked, display: !isSelf},
|
||||
"toggle-video": {state: !csv, display: isSelf && !minimized},
|
||||
"toggle-audio": {state: !csa, display: isSelf},
|
||||
"mute-peers": {state: clientSettings.muteAll, display: isSelf},
|
||||
"disable-video": {state: clientSettings.disableVideo, display: isSelf && !minimized},
|
||||
"toggle-players": {state: !clientSettings.hidePlayerList, display: isSelf && !minimized && isVertical},
|
||||
"toggle-dock": {state: !clientSettings.hideDock, display: isSelf}
|
||||
};
|
||||
const toggles = this.element[0].querySelectorAll(`[data-user="${userId}"] .av-control.toggle`);
|
||||
for ( let button of toggles ) {
|
||||
const action = button.dataset.action;
|
||||
if ( !(action in actions) ) continue;
|
||||
const state = actions[action].state;
|
||||
const displayed = actions[action].display;
|
||||
button.style.display = displayed ? "block" : "none";
|
||||
button.enabled = displayed;
|
||||
button.children[0].classList.remove(this._getToggleIcon(action, !state));
|
||||
button.children[0].classList.add(this._getToggleIcon(action, state));
|
||||
button.dataset.tooltip = this._getToggleTooltip(action, state);
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Render changes needed to the PlayerList ui.
|
||||
* Show/Hide players depending on option.
|
||||
* @private
|
||||
*/
|
||||
_setPlayerListVisibility() {
|
||||
const hidePlayerList = this.webrtc.settings.client.hidePlayerList;
|
||||
const players = document.getElementById("players");
|
||||
const top = document.getElementById("ui-top");
|
||||
if ( players ) players.classList.toggle("hidden", hidePlayerList);
|
||||
if ( top ) top.classList.toggle("offset", !hidePlayerList);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Get the icon class that should be used for various action buttons with different toggled states.
|
||||
* The returned icon should represent the visual status of the NEXT state (not the CURRENT state).
|
||||
*
|
||||
* @param {string} action The named av-control button action
|
||||
* @param {boolean} state The CURRENT action state.
|
||||
* @returns {string} The icon that represents the NEXT action state.
|
||||
* @protected
|
||||
*/
|
||||
_getToggleIcon(action, state) {
|
||||
const clientSettings = game.webrtc.settings.client;
|
||||
const dockPositions = AVSettings.DOCK_POSITIONS;
|
||||
const dockIcons = {
|
||||
[dockPositions.TOP]: {collapse: "down", expand: "up"},
|
||||
[dockPositions.RIGHT]: {collapse: "left", expand: "right"},
|
||||
[dockPositions.BOTTOM]: {collapse: "up", expand: "down"},
|
||||
[dockPositions.LEFT]: {collapse: "right", expand: "left"}
|
||||
}[clientSettings.dockPosition];
|
||||
const actionMapping = {
|
||||
"block-video": ["fa-video", "fa-video-slash"], // True means "blocked"
|
||||
"block-audio": ["fa-microphone", "fa-microphone-slash"], // True means "blocked"
|
||||
"hide-user": ["fa-eye", "fa-eye-slash"],
|
||||
"toggle-video": ["fa-camera-web", "fa-camera-web-slash"], // True means "enabled"
|
||||
"toggle-audio": ["fa-microphone", "fa-microphone-slash"], // True means "enabled"
|
||||
"mute-peers": ["fa-volume-up", "fa-volume-mute"], // True means "muted"
|
||||
"disable-video": ["fa-video", "fa-video-slash"],
|
||||
"toggle-players": ["fa-caret-square-right", "fa-caret-square-left"], // True means "displayed"
|
||||
"toggle-dock": [`fa-caret-square-${dockIcons.collapse}`, `fa-caret-square-${dockIcons.expand}`]
|
||||
};
|
||||
const icons = actionMapping[action];
|
||||
return icons ? icons[state ? 1: 0] : null;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Get the text title that should be used for various action buttons with different toggled states.
|
||||
* The returned title should represent the tooltip of the NEXT state (not the CURRENT state).
|
||||
*
|
||||
* @param {string} action The named av-control button action
|
||||
* @param {boolean} state The CURRENT action state.
|
||||
* @returns {string} The icon that represents the NEXT action state.
|
||||
* @protected
|
||||
*/
|
||||
_getToggleTooltip(action, state) {
|
||||
const actionMapping = {
|
||||
"block-video": ["BlockUserVideo", "AllowUserVideo"], // True means "blocked"
|
||||
"block-audio": ["BlockUserAudio", "AllowUserAudio"], // True means "blocked"
|
||||
"hide-user": ["ShowUser", "HideUser"],
|
||||
"toggle-video": ["DisableMyVideo", "EnableMyVideo"], // True means "enabled"
|
||||
"toggle-audio": ["DisableMyAudio", "EnableMyAudio"], // True means "enabled"
|
||||
"mute-peers": ["MutePeers", "UnmutePeers"], // True means "muted"
|
||||
"disable-video": ["DisableAllVideo", "EnableVideo"],
|
||||
"toggle-players": ["ShowPlayers", "HidePlayers"], // True means "displayed"
|
||||
"toggle-dock": ["ExpandDock", "MinimizeDock"]
|
||||
};
|
||||
const labels = actionMapping[action];
|
||||
return game.i18n.localize(`WEBRTC.Tooltip${labels ? labels[state ? 1 : 0] : ""}`);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Show or hide UI control elements
|
||||
* This replaces the use of jquery.show/hide as it simply adds a class which has display:none
|
||||
* which allows us to have elements with display:flex which can be hidden then shown without
|
||||
* breaking their display style.
|
||||
* This will show/hide the toggle buttons, volume controls and overlay sidebars
|
||||
* @param {jQuery} container The container for which to show/hide control elements
|
||||
* @param {boolean} show Whether to show or hide the controls
|
||||
* @param {string} selector Override selector to specify which controls to show or hide
|
||||
* @private
|
||||
*/
|
||||
_toggleControlVisibility(container, show, selector) {
|
||||
selector = selector || `.control-bar`;
|
||||
container.querySelectorAll(selector).forEach(c => c.classList.toggle("hidden", !show));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user