Files
Foundry-VTT-Docker/resources/app/client/apps/hud/players.js
2025-01-04 00:34:03 +01:00

290 lines
9.3 KiB
JavaScript

/**
* The UI element which displays the list of Users who are currently playing within the active World.
* @extends {Application}
*/
class PlayerList extends Application {
constructor(options) {
super(options);
game.users.apps.push(this);
/**
* An internal toggle for whether to show offline players or hide them
* @type {boolean}
* @private
*/
this._showOffline = false;
}
/* -------------------------------------------- */
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
id: "players",
template: "templates/user/players.html",
popOut: false
});
}
/* -------------------------------------------- */
/* Application Rendering */
/* -------------------------------------------- */
/**
* Whether the players list is in a configuration where it is hidden.
* @returns {boolean}
*/
get isHidden() {
if ( game.webrtc.mode === AVSettings.AV_MODES.DISABLED ) return false;
const { client, verticalDock } = game.webrtc.settings;
return verticalDock && client.hidePlayerList && !client.hideDock && !ui.webrtc.hidden;
}
/* -------------------------------------------- */
/** @override */
render(force, context={}) {
this._positionInDOM();
const { renderContext, renderData } = context;
if ( renderContext ) {
const events = ["createUser", "updateUser", "deleteUser"];
if ( !events.includes(renderContext) ) return this;
if ( renderContext === "updateUser" ) {
const updateKeys = ["name", "pronouns", "ownership", "ownership.default", "active", "navigation"];
if ( !renderData.some(d => updateKeys.some(k => k in d)) ) return this;
}
}
return super.render(force, context);
}
/* -------------------------------------------- */
/** @override */
getData(options={}) {
// Process user data by adding extra characteristics
const users = game.users.filter(u => this._showOffline || u.active).map(user => {
const u = user.toObject(false);
u.active = user.active;
u.isGM = user.isGM;
u.isSelf = user.isSelf;
u.charname = user.character?.name.split(" ")[0] || "";
u.color = u.active ? u.color.css : "#333333";
u.border = u.active ? user.border.css : "#000000";
u.displayName = this._getDisplayName(u);
return u;
}).sort((a, b) => {
if ( (b.role >= CONST.USER_ROLES.ASSISTANT) && (b.role > a.role) ) return 1;
return a.name.localeCompare(b.name, game.i18n.lang);
});
// Return the data for rendering
return {
users,
hide: this.isHidden,
showOffline: this._showOffline
};
}
/* -------------------------------------------- */
/**
* Prepare a displayed name string for the User which includes their name, pronouns, character, or GM tag.
* @returns {string}
* @protected
*/
_getDisplayName(user) {
const displayNamePart = [user.name];
if ( user.pronouns ) displayNamePart.push(`(${user.pronouns})`);
if ( user.isGM ) displayNamePart.push(`[${game.i18n.localize("USER.GM")}]`);
else if ( user.charname ) displayNamePart.push(`[${user.charname}]`);
return displayNamePart.join(" ");
}
/* -------------------------------------------- */
/**
* Position this Application in the main DOM appropriately.
* @protected
*/
_positionInDOM() {
document.body.classList.toggle("players-hidden", this.isHidden);
if ( (game.webrtc.mode === AVSettings.AV_MODES.DISABLED) || this.isHidden || !this.element.length ) return;
const element = this.element[0];
const cameraViews = ui.webrtc.element[0];
const uiTop = document.getElementById("ui-top");
const uiLeft = document.getElementById("ui-left");
const { client, verticalDock } = game.webrtc.settings;
const inDock = verticalDock && !client.hideDock && !ui.webrtc.hidden;
if ( inDock && !cameraViews?.contains(element) ) {
cameraViews.appendChild(element);
uiTop.classList.remove("offset");
} else if ( !inDock && !uiLeft.contains(element) ) {
uiLeft.appendChild(element);
uiTop.classList.add("offset");
}
}
/* -------------------------------------------- */
/* Event Listeners and Handlers
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
// Toggle online/offline
html.find("h3").click(this._onToggleOfflinePlayers.bind(this));
// Context menu
const contextOptions = this._getUserContextOptions();
Hooks.call("getUserContextOptions", html, contextOptions);
new ContextMenu(html, ".player", contextOptions);
}
/* -------------------------------------------- */
/**
* Return the default context options available for the Players application
* @returns {object[]}
* @private
*/
_getUserContextOptions() {
return [
{
name: game.i18n.localize("PLAYERS.ConfigTitle"),
icon: '<i class="fas fa-male"></i>',
condition: li => game.user.isGM || (li[0].dataset.userId === game.user.id),
callback: li => {
const user = game.users.get(li[0].dataset.userId);
user?.sheet.render(true);
}
},
{
name: game.i18n.localize("PLAYERS.ViewAvatar"),
icon: '<i class="fas fa-image"></i>',
condition: li => {
const user = game.users.get(li[0].dataset.userId);
return user.avatar !== CONST.DEFAULT_TOKEN;
},
callback: li => {
let user = game.users.get(li.data("user-id"));
new ImagePopout(user.avatar, {
title: user.name,
uuid: user.uuid
}).render(true);
}
},
{
name: game.i18n.localize("PLAYERS.PullToScene"),
icon: '<i class="fas fa-directions"></i>',
condition: li => game.user.isGM && (li[0].dataset.userId !== game.user.id),
callback: li => game.socket.emit("pullToScene", canvas.scene.id, li.data("user-id"))
},
{
name: game.i18n.localize("PLAYERS.Kick"),
icon: '<i class="fas fa-door-open"></i>',
condition: li => {
const user = game.users.get(li[0].dataset.userId);
return game.user.isGM && user.active && !user.isSelf;
},
callback: li => {
const user = game.users.get(li[0].dataset.userId);
return this.#kickUser(user);
}
},
{
name: game.i18n.localize("PLAYERS.Ban"),
icon: '<i class="fas fa-ban"></i>',
condition: li => {
const user = game.users.get(li[0].dataset.userId);
return game.user.isGM && !user.isSelf && (user.role !== CONST.USER_ROLES.NONE);
},
callback: li => {
const user = game.users.get(li[0].dataset.userId);
return this.#banUser(user);
}
},
{
name: game.i18n.localize("PLAYERS.UnBan"),
icon: '<i class="fas fa-ban"></i>',
condition: li => {
const user = game.users.get(li[0].dataset.userId);
return game.user.isGM && !user.isSelf && (user.role === CONST.USER_ROLES.NONE);
},
callback: li => {
const user = game.users.get(li[0].dataset.userId);
return this.#unbanUser(user);
}
},
{
name: game.i18n.localize("WEBRTC.TooltipShowUser"),
icon: '<i class="fas fa-eye"></i>',
condition: li => {
const userId = li.data("userId");
return game.webrtc.settings.client.users[userId]?.blocked;
},
callback: async li => {
const userId = li.data("userId");
await game.webrtc.settings.set("client", `users.${userId}.blocked`, false);
ui.webrtc.render();
}
}
];
}
/* -------------------------------------------- */
/**
* Toggle display of the Players hud setting for whether to display offline players
* @param {Event} event The originating click event
* @private
*/
_onToggleOfflinePlayers(event) {
event.preventDefault();
this._showOffline = !this._showOffline;
this.render();
}
/* -------------------------------------------- */
/**
* Temporarily remove a User from the World by banning and then un-banning them.
* @param {User} user The User to kick
* @returns {Promise<void>}
*/
async #kickUser(user) {
const role = user.role;
await user.update({role: CONST.USER_ROLES.NONE});
await user.update({role}, {diff: false});
ui.notifications.info(`${user.name} has been <strong>kicked</strong> from the World.`);
}
/* -------------------------------------------- */
/**
* Ban a User by changing their role to "NONE".
* @param {User} user The User to ban
* @returns {Promise<void>}
*/
async #banUser(user) {
if ( user.role === CONST.USER_ROLES.NONE ) return;
await user.update({role: CONST.USER_ROLES.NONE});
ui.notifications.info(`${user.name} has been <strong>banned</strong> from the World.`);
}
/* -------------------------------------------- */
/**
* Unban a User by changing their role to "PLAYER".
* @param {User} user The User to unban
* @returns {Promise<void>}
*/
async #unbanUser(user) {
if ( user.role !== CONST.USER_ROLES.NONE ) return;
await user.update({role: CONST.USER_ROLES.PLAYER});
ui.notifications.info(`${user.name} has been <strong>unbanned</strong> from the World.`);
}
}