Files

177 lines
7.8 KiB
JavaScript
Raw Permalink Normal View History

2025-01-04 00:34:03 +01:00
/**
* The singleton collection of JournalEntry documents which exist within the active World.
* This Collection is accessible within the Game object as game.journal.
* @extends {WorldCollection}
*
* @see {@link JournalEntry} The JournalEntry document
* @see {@link JournalDirectory} The JournalDirectory sidebar directory
*/
class Journal extends WorldCollection {
/** @override */
static documentName = "JournalEntry";
/* -------------------------------------------- */
/* Interaction Dialogs */
/* -------------------------------------------- */
/**
* Display a dialog which prompts the user to show a JournalEntry or JournalEntryPage to other players.
* @param {JournalEntry|JournalEntryPage} doc The JournalEntry or JournalEntryPage to show.
* @returns {Promise<JournalEntry|JournalEntryPage|void>}
*/
static async showDialog(doc) {
if ( !((doc instanceof JournalEntry) || (doc instanceof JournalEntryPage)) ) return;
if ( !doc.isOwner ) return ui.notifications.error("JOURNAL.ShowBadPermissions", {localize: true});
if ( game.users.size < 2 ) return ui.notifications.warn("JOURNAL.ShowNoPlayers", {localize: true});
const users = game.users.filter(u => u.id !== game.userId);
const ownership = Object.entries(CONST.DOCUMENT_OWNERSHIP_LEVELS);
if ( !doc.isEmbedded ) ownership.shift();
const levels = [
{level: CONST.DOCUMENT_META_OWNERSHIP_LEVELS.NOCHANGE, label: "OWNERSHIP.NOCHANGE"},
...ownership.map(([name, level]) => ({level, label: `OWNERSHIP.${name}`}))
];
const isImage = (doc instanceof JournalEntryPage) && (doc.type === "image");
const html = await renderTemplate("templates/journal/dialog-show.html", {users, levels, isImage});
return Dialog.prompt({
title: game.i18n.format("JOURNAL.ShowEntry", {name: doc.name}),
label: game.i18n.localize("JOURNAL.ActionShow"),
content: html,
render: html => {
const form = html.querySelector("form");
form.elements.allPlayers.addEventListener("change", event => {
const checked = event.currentTarget.checked;
form.querySelectorAll('[name="players"]').forEach(i => {
i.checked = checked;
i.disabled = checked;
});
});
},
callback: async html => {
const form = html.querySelector("form");
const fd = new FormDataExtended(form).object;
const users = fd.allPlayers ? game.users.filter(u => !u.isSelf) : fd.players.reduce((arr, id) => {
const u = game.users.get(id);
if ( u && !u.isSelf ) arr.push(u);
return arr;
}, []);
if ( !users.length ) return;
const userIds = users.map(u => u.id);
if ( fd.ownership > -2 ) {
const ownership = doc.ownership;
if ( fd.allPlayers ) ownership.default = fd.ownership;
for ( const id of userIds ) {
if ( fd.allPlayers ) {
if ( (id in ownership) && (ownership[id] <= fd.ownership) ) delete ownership[id];
continue;
}
if ( ownership[id] === CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE ) ownership[id] = fd.ownership;
ownership[id] = Math.max(ownership[id] ?? -Infinity, fd.ownership);
}
await doc.update({ownership}, {diff: false, recursive: false, noHook: true});
}
if ( fd.imageOnly ) return this.showImage(doc.src, {
users: userIds,
title: doc.name,
caption: fd.showImageCaption ? doc.image.caption : undefined,
showTitle: fd.showImageTitle,
uuid: doc.uuid
});
return this.show(doc, {force: true, users: userIds});
},
rejectClose: false,
options: {jQuery: false}
});
}
/* -------------------------------------------- */
/**
* Show the JournalEntry or JournalEntryPage to connected players.
* By default, the document will only be shown to players who have permission to observe it.
* If the force parameter is passed, the document will be shown to all players regardless of normal permission.
* @param {JournalEntry|JournalEntryPage} doc The JournalEntry or JournalEntryPage to show.
* @param {object} [options] Additional options to configure behaviour.
* @param {boolean} [options.force=false] Display the entry to all players regardless of normal permissions.
* @param {string[]} [options.users] An optional list of user IDs to show the document to. Otherwise it will
* be shown to all connected clients.
* @returns {Promise<JournalEntry|JournalEntryPage|void>} A Promise that resolves back to the shown document once the
* request is processed.
* @throws {Error} If the user does not own the document they are trying to show.
*/
static show(doc, {force=false, users=[]}={}) {
if ( !((doc instanceof JournalEntry) || (doc instanceof JournalEntryPage)) ) return;
if ( !doc.isOwner ) throw new Error(game.i18n.localize("JOURNAL.ShowBadPermissions"));
const strings = Object.fromEntries(["all", "authorized", "selected"].map(k => [k, game.i18n.localize(k)]));
return new Promise(resolve => {
game.socket.emit("showEntry", doc.uuid, {force, users}, () => {
Journal._showEntry(doc.uuid, force);
ui.notifications.info(game.i18n.format("JOURNAL.ActionShowSuccess", {
title: doc.name,
which: users.length ? strings.selected : force ? strings.all : strings.authorized
}));
return resolve(doc);
});
});
}
/* -------------------------------------------- */
/**
* Share an image with connected players.
* @param {string} src The image URL to share.
* @param {ShareImageConfig} [config] Image sharing configuration.
*/
static showImage(src, {users=[], ...options}={}) {
game.socket.emit("shareImage", {image: src, users, ...options});
const strings = Object.fromEntries(["all", "selected"].map(k => [k, game.i18n.localize(k)]));
ui.notifications.info(game.i18n.format("JOURNAL.ImageShowSuccess", {
which: users.length ? strings.selected : strings.all
}));
}
/* -------------------------------------------- */
/* Socket Listeners and Handlers */
/* -------------------------------------------- */
/**
* Open Socket listeners which transact JournalEntry data
* @param {Socket} socket The open websocket
*/
static _activateSocketListeners(socket) {
socket.on("showEntry", this._showEntry.bind(this));
socket.on("shareImage", ImagePopout._handleShareImage);
}
/* -------------------------------------------- */
/**
* Handle a received request to show a JournalEntry or JournalEntryPage to the current client
* @param {string} uuid The UUID of the document to display for other players
* @param {boolean} [force=false] Display the document regardless of normal permissions
* @internal
*/
static async _showEntry(uuid, force=false) {
let entry = await fromUuid(uuid);
const options = {tempOwnership: force, mode: JournalSheet.VIEW_MODES.MULTIPLE, pageIndex: 0};
const { OBSERVER } = CONST.DOCUMENT_OWNERSHIP_LEVELS;
if ( entry instanceof JournalEntryPage ) {
options.mode = JournalSheet.VIEW_MODES.SINGLE;
options.pageId = entry.id;
// Set temporary observer permissions for this page.
if ( entry.getUserLevel(game.user) < OBSERVER ) entry.ownership[game.userId] = OBSERVER;
entry = entry.parent;
}
else if ( entry instanceof JournalEntry ) {
if ( entry.getUserLevel(game.user) < OBSERVER ) entry.ownership[game.userId] = OBSERVER;
}
else return;
if ( !force && !entry.visible ) return;
// Show the sheet with the appropriate mode
entry.sheet.render(true, options);
}
}