290 lines
9.3 KiB
JavaScript
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.`);
|
|
}
|
|
}
|