Initial
This commit is contained in:
289
resources/app/client/apps/hud/players.js
Normal file
289
resources/app/client/apps/hud/players.js
Normal file
@@ -0,0 +1,289 @@
|
||||
|
||||
/**
|
||||
* 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.`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user