All user data for FoundryVTT. Includes worlds, systems, modules, and any asset in the "foundryuserdata" directory. Does NOT include the FoundryVTT installation itself.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

215 lines
6.6 KiB

/*
* This file is part of the warpgate module (https://github.com/trioderegion/warpgate)
* Copyright (c) 2021 Matthew Haentschke.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { logger } from './logger.js'
import { Gateway } from './gateway.js'
import { MODULE } from './module.js'
import {queueUpdate} from './update-queue.js'
import { Mutator } from './mutator.js'
export class UserInterface {
static register() {
this.hooks();
this.settings();
}
static hooks() {
Hooks.on("renderActorSheet", UserInterface._renderActorSheet);
}
static settings() {
const config = true;
const settingsData = {
showDismissLabel : {
scope: "client", config, default: true, type: Boolean,
},
showRevertLabel : {
scope: "client", config, default: true, type: Boolean,
},
dismissButtonScope : {
scope: "client", config, default: 'spawned', type: String, choices: {
disabled: MODULE.localize('setting.option.disabled'),
spawned: MODULE.localize('setting.option.spawnedOnly'),
all: MODULE.localize('setting.option.all')
}
},
revertButtonBehavior : {
scope: 'client', config, default: 'pop', type: String, choices: {
disabled: MODULE.localize('setting.option.disabled'),
pop: MODULE.localize('setting.option.popLatestMutation'),
menu: MODULE.localize('setting.option.showMutationList')
}
}
};
MODULE.applySettings(settingsData);
}
static _renderActorSheet(app, html, data) {
logger.debug("app |", app);
logger.debug("html |", html);
logger.debug("data |", data);
UserInterface.addDismissButton(app, html, data);
UserInterface.addRevertMutation(app, html, data);
}
static _shouldAddDismiss(token) {
if ( !(token instanceof TokenDocument) ) return false;
switch (MODULE.setting('dismissButtonScope')){
case 'disabled':
return false;
case 'spawned':
const controlData = token?.actor.getFlag(MODULE.data.name, 'control');
/** do not add the button if we are not the controlling actor AND we aren't the GM */
if ( !(controlData?.user === game.user.id) &&
!game.user.isGM) return false;
return !!controlData;
case 'all':
return true;
}
}
static addDismissButton(app, html/*, data*/) {
const token = app.token;
/** this is not a warpgate spawned actor */
if (!UserInterface._shouldAddDismiss(token)) return;
/* do not add duplicate buttons! */
if(html.closest('.app').find('.dismiss-warpgate').length !== 0) {
logger.debug(MODULE.localize('debug.dismissPresent'));
return;
}
const label = MODULE.setting('showDismissLabel') ? MODULE.localize("display.dismiss") : ""
let dismissButton = $(`<a class="dismiss-warpgate" title="${MODULE.localize('display.dismiss')}"><i class="fas fa-user-slash"></i>${label}</a>`);
dismissButton.click( (/*event*/) => {
if (!token) {
logger.error(MODULE.localize('error.sheetNoToken'));
return;
}
const {id, parent} = token;
Gateway.dismissSpawn(id, parent?.id);
/** close the actor sheet if provided */
app?.close({submit: false});
});
let title = html.closest('.app').find('.window-title');
dismissButton.insertAfter(title);
}
static _shouldAddRevert(token) {
if ( !(token instanceof TokenDocument) ) return false;
const mutateStack = warpgate.mutationStack(token).stack;
/* this is not a warpgate mutated actor,
* or there are no remaining stacks to peel */
if (mutateStack.length == 0) return false;
return MODULE.setting('revertButtonBehavior') !== 'disabled';
}
static _getTokenFromApp(app) {
const {token, actor} = app;
const hasToken = token instanceof TokenDocument;
if( !hasToken ) {
/* check if linked and has an active token on scene */
const candidates = actor?.getActiveTokens() ?? [];
const linkedToken = candidates.find( t => (MODULE.isV10?t.document:t.data).actorLink )?.document ?? null;
return linkedToken;
}
return token;
}
static addRevertMutation(app, html, data) {
/* do not add duplicate buttons! */
let foundButton = html.closest('.app').find('.revert-warpgate')
/* we remove the current button on each render
* in case the render was triggered by a mutation
* event and we need to update the tool tip
* on the revert stack
*/
if (foundButton) {
foundButton.remove();
}
const token = UserInterface._getTokenFromApp(app);
if(!UserInterface._shouldAddRevert(token)) return;
const mutateStack = token?.actor?.getFlag(MODULE.data.name, 'mutate');
/* construct the revert button */
const label = MODULE.setting('showRevertLabel') ? MODULE.localize("display.revert") : ""
const stackCount = mutateStack.length > 1 ? ` 1/${mutateStack.length}` : '';
let revertButton = $(`<a class="revert-warpgate" title="${MODULE.localize('display.revert')}${stackCount}"><i class="fas fa-undo-alt"></i>${label}</a>`);
revertButton.click( async (event) => {
const shouldShow = (shiftKey) => {
const mode = MODULE.setting('revertButtonBehavior')
const show = mode == 'menu' ? !shiftKey : shiftKey;
return show;
}
let name = undefined;
const showMenu = shouldShow(event.shiftKey);
if (showMenu) {
const buttons = mutateStack.map( mutation => {return {label: mutation.name, value: mutation.name}} )
name = await warpgate.buttonDialog({buttons, title: MODULE.localize('display.revertDialogTitle')}, 'column');
if (name === false) return;
}
/* need to queue this since 'click' could
* happen at any time.
* Do not need to remove the button here
* as it will be refreshed on the render call
*/
queueUpdate( async () => {
await Mutator.revertMutation(token, name);
app?.render(false);
});
});
let title = html.closest('.app').find('.window-title');
revertButton.insertAfter(title);
}
}