|
|
- import { getFileName, isImage, isVideo, SEARCH_TYPE, keyPressed } from '../scripts/utils.js';
- import { TVA_CONFIG } from '../scripts/settings.js';
- import FlagsConfig from './flagsConfig.js';
- import { doImageSearch } from '../scripts/search.js';
- import UserList from './userList.js';
-
- export async function renderTileHUD(hud, html, tileData, searchText = '', fp_files = null) {
- const tile = hud.object;
- const hudSettings = TVA_CONFIG.hud;
-
- if (!hudSettings.enableSideMenu || !TVA_CONFIG.tilesEnabled) return;
-
- const button = $(`
- <div class="control-icon" data-action="token-variants-side-selector">
- <img
- id="token-variants-side-button"
- src="modules/token-variants/img/token-images.svg"
- width="36"
- height="36"
- title="${game.i18n.localize('token-variants.windows.art-select.select-variant')}"
- />
- </div>
- `);
-
- html.find('div.right').last().append(button);
- html.find('div.right').click(_deactivateTokenVariantsSideSelector);
-
- button.click((event) => _onButtonClick(event, tile));
- button.contextmenu((event) => _onButtonRightClick(event, tile));
- }
-
- async function _onButtonClick(event, tile) {
- if (keyPressed('config')) {
- setNameDialog(tile);
- return;
- }
-
- const button = $(event.target).closest('.control-icon');
-
- // De-activate 'Status Effects'
- button.closest('div.right').find('div.control-icon.effects').removeClass('active');
- button.closest('div.right').find('.status-effects').removeClass('active');
-
- // Remove contextmenu
- button.find('.contextmenu').remove();
-
- // Toggle variants side menu
- button.toggleClass('active');
- let variantsWrap = button.find('.token-variants-wrap');
- if (button.hasClass('active')) {
- if (!variantsWrap.length) {
- variantsWrap = await renderSideSelect(tile);
- if (variantsWrap) button.find('img').after(variantsWrap);
- else return;
- }
- variantsWrap.addClass('active');
- } else {
- variantsWrap.removeClass('active');
- }
- }
-
- function _onButtonRightClick(event, tile) {
- // Display side menu if button is not active yet
- const button = $(event.target).closest('.control-icon');
- if (!button.hasClass('active')) {
- // button.trigger('click');
- button.addClass('active');
- }
-
- if (button.find('.contextmenu').length) {
- // Contextmenu already displayed. Remove and activate images
- button.find('.contextmenu').remove();
- button.removeClass('active').trigger('click');
- //button.find('.token-variants-wrap.images').addClass('active');
- } else {
- // Contextmenu is not displayed. Hide images, create it and add it
- button.find('.token-variants-wrap.images').removeClass('active');
- const contextMenu = $(`
- <div class="token-variants-wrap contextmenu active">
- <div class="token-variants-context-menu active">
- <input class="token-variants-side-search" type="text" />
- <button class="flags" type="button"><i class="fab fa-font-awesome-flag"></i><label>Flags</label></button>
- <button class="file-picker" type="button"><i class="fas fa-file-import fa-fw"></i><label>Browse Folders</label></button>
- </div>
- </div>
- `);
- button.append(contextMenu);
-
- // Register contextmenu listeners
- contextMenu
- .find('.token-variants-side-search')
- .on('keydown', (event) => _onImageSearchKeyUp(event, tile))
- .on('click', (event) => {
- event.preventDefault();
- event.stopPropagation();
- });
- contextMenu.find('.flags').click((event) => {
- event.preventDefault();
- event.stopPropagation();
- new FlagsConfig(tile).render(true);
- });
- contextMenu.find('.file-picker').click(async (event) => {
- event.preventDefault();
- event.stopPropagation();
- new FilePicker({
- type: 'folder',
- callback: async (path, fp) => {
- const content = await FilePicker.browse(fp.activeSource, fp.result.target);
- let files = content.files.filter((f) => isImage(f) || isVideo(f));
- if (files.length) {
- button.find('.token-variants-wrap').remove();
- const sideSelect = await renderSideSelect(tile, null, files);
- if (sideSelect) {
- sideSelect.addClass('active');
- button.append(sideSelect);
- }
- }
- },
- }).render(true);
- });
- }
- }
-
- function _deactivateTokenVariantsSideSelector(event) {
- const controlIcon = $(event.target).closest('.control-icon');
- const dataAction = controlIcon.attr('data-action');
-
- switch (dataAction) {
- case 'effects':
- break; // Effects button
- case 'thwildcard-selector':
- break; // Token HUD Wildcard module button
- default:
- return;
- }
-
- $(event.target)
- .closest('div.right')
- .find('.control-icon[data-action="token-variants-side-selector"]')
- .removeClass('active');
- $(event.target).closest('div.right').find('.token-variants-wrap').removeClass('active');
- }
-
- async function renderSideSelect(tile, searchText = null, fp_files = null) {
- const hudSettings = TVA_CONFIG.hud;
- const worldHudSettings = TVA_CONFIG.worldHud;
- const FULL_ACCESS = TVA_CONFIG.permissions.hudFullAccess[game.user.role];
- let images = [];
- let variants = [];
- let imageDuplicates = new Set();
- const pushImage = (img) => {
- if (imageDuplicates.has(img.path)) {
- if (!images.find((obj) => obj.path === img.path && obj.name === img.name)) {
- images.push(img);
- }
- } else {
- images.push(img);
- imageDuplicates.add(img.path);
- }
- };
-
- if (!fp_files) {
- if (searchText !== null && !searchText) return;
-
- if (!searchText) {
- variants = tile.document.getFlag('token-variants', 'variants') || [];
- variants.forEach((variant) => {
- for (const name of variant.names) {
- pushImage({ path: variant.imgSrc, name: name });
- }
- });
-
- // Parse directory flag and include the images
- const directoryFlag = tile.document.getFlag('token-variants', 'directory');
- if (directoryFlag) {
- let dirFlagImages;
- try {
- let path = directoryFlag.path;
- let source = directoryFlag.source;
- let bucket = '';
- if (source.startsWith('s3:')) {
- bucket = source.substring(3, source.length);
- source = 's3';
- }
- const content = await FilePicker.browse(source, path, {
- type: 'imagevideo',
- bucket,
- });
- dirFlagImages = content.files;
- } catch (err) {
- dirFlagImages = [];
- }
- dirFlagImages.forEach((f) => {
- if (isImage(f) || isVideo(f)) pushImage({ path: f, name: getFileName(f) });
- });
- }
- }
-
- // Perform the search if needed
- const search = searchText ?? tile.document.getFlag('token-variants', 'tileName');
- const noSearch = !search || (!searchText && worldHudSettings.displayOnlySharedImages);
- let artSearch = noSearch
- ? null
- : await doImageSearch(search, {
- searchType: SEARCH_TYPE.TILE,
- searchOptions: { keywordSearch: worldHudSettings.includeKeywords },
- });
-
- if (artSearch) {
- artSearch.forEach((results) => {
- images.push(...results);
- });
- }
- } else {
- images = fp_files.map((f) => {
- return { path: f, name: getFileName(f) };
- });
- }
-
- // Retrieving the possibly custom name attached as a flag to the token
- let tileImageName = tile.document.getFlag('token-variants', 'name');
- if (!tileImageName) {
- tileImageName = getFileName(tile.document.texture.src);
- }
-
- let imagesParsed = [];
-
- for (const imageObj of images) {
- const img = isImage(imageObj.path);
- const vid = isVideo(imageObj.path);
-
- let shared = false;
- if (game.user.isGM) {
- variants.forEach((variant) => {
- if (variant.imgSrc === imageObj.path && variant.names.includes(imageObj.name)) {
- shared = true;
- }
- });
- }
-
- const userMappings = tile.document.getFlag('token-variants', 'userMappings') || {};
- const [title, style] = genTitleAndStyle(userMappings, imageObj.path, imageObj.name);
-
- imagesParsed.push({
- route: imageObj.path,
- name: imageObj.name,
- used: imageObj.path === tile.document.texture.src && imageObj.name === tileImageName,
- img,
- vid,
- unknownType: !img && !vid,
- shared: shared,
- hasConfig: false, //hasConfig,
- title: title,
- style: game.user.isGM && style ? 'box-shadow: ' + style + ';' : null,
- });
- }
-
- //
- // Render
- //
- const imageDisplay = hudSettings.displayAsImage;
- const imageOpacity = hudSettings.imageOpacity / 100;
-
- const sideSelect = $(
- await renderTemplate('modules/token-variants/templates/sideSelect.html', {
- imagesParsed,
- imageDisplay,
- imageOpacity,
- autoplay: !TVA_CONFIG.playVideoOnHover,
- })
- );
-
- // Activate listeners
- sideSelect.find('video').hover(
- function () {
- if (TVA_CONFIG.playVideoOnHover) {
- this.play();
- $(this).siblings('.fa-play').hide();
- }
- },
- function () {
- if (TVA_CONFIG.pauseVideoOnHoverOut) {
- this.pause();
- this.currentTime = 0;
- $(this).siblings('.fa-play').show();
- }
- }
- );
- sideSelect.find('.token-variants-button-select').click((event) => _onImageClick(event, tile));
-
- if (FULL_ACCESS) {
- sideSelect
- .find('.token-variants-button-select')
- .on('contextmenu', (event) => _onImageRightClick(event, tile));
- }
-
- return sideSelect;
- }
-
- async function _onImageClick(event, tile) {
- event.preventDefault();
- event.stopPropagation();
-
- if (!tile) return;
-
- const imgButton = $(event.target).closest('.token-variants-button-select');
- const imgSrc = imgButton.attr('data-name');
- const name = imgButton.attr('data-filename');
- if (imgSrc) {
- canvas.tiles.hud.clear();
- await tile.document.update({ img: imgSrc });
- try {
- if (getFileName(imgSrc) !== name) await tile.document.setFlag('token-variants', 'name', name);
- } catch (e) {}
- }
- }
-
- async function _onImageRightClick(event, tile) {
- event.preventDefault();
- event.stopPropagation();
-
- if (!tile) return;
-
- const imgButton = $(event.target).closest('.token-variants-button-select');
- const imgSrc = imgButton.attr('data-name');
- const name = imgButton.attr('data-filename');
-
- if (!imgSrc || !name) return;
-
- if (keyPressed('config') && game.user.isGM) {
- const regenStyle = (tile, img) => {
- const mappings = tile.document.getFlag('token-variants', 'userMappings') || {};
- const name = imgButton.attr('data-filename');
- const [title, style] = genTitleAndStyle(mappings, img, name);
- imgButton
- .closest('.token-variants-wrap')
- .find(`.token-variants-button-select[data-name='${img}']`)
- .css('box-shadow', style)
- .prop('title', title);
- };
- new UserList(tile, imgSrc, regenStyle).render(true);
- return;
- }
-
- let variants = tile.document.getFlag('token-variants', 'variants') || [];
-
- // Remove selected variant if present in the flag, add otherwise
- let del = false;
- let updated = false;
- for (let variant of variants) {
- if (variant.imgSrc === imgSrc) {
- let fNames = variant.names.filter((name) => name !== name);
- if (fNames.length === 0) {
- del = true;
- } else if (fNames.length === variant.names.length) {
- fNames.push(name);
- }
- variant.names = fNames;
- updated = true;
- break;
- }
- }
- if (del) variants = variants.filter((variant) => variant.imgSrc !== imgSrc);
- else if (!updated) variants.push({ imgSrc: imgSrc, names: [name] });
-
- // Set shared variants as a flag
- tile.document.unsetFlag('token-variants', 'variants');
- if (variants.length > 0) {
- tile.document.setFlag('token-variants', 'variants', variants);
- }
- imgButton.find('.fa-share').toggleClass('active'); // Display green arrow
- }
-
- async function _onImageSearchKeyUp(event, tile) {
- if (event.key === 'Enter' || event.keyCode === 13) {
- event.preventDefault();
- if (event.target.value.length >= 3) {
- const button = $(event.target).closest('.control-icon');
- button.find('.token-variants-wrap').remove();
- const sideSelect = await renderSideSelect(tile, event.target.value);
- if (sideSelect) {
- sideSelect.addClass('active');
- button.append(sideSelect);
- }
- }
- return false;
- }
- }
-
- function genTitleAndStyle(mappings, imgSrc, name) {
- let title = TVA_CONFIG.worldHud.showFullPath ? imgSrc : name;
- let style = '';
- let offset = 2;
- for (const [userId, img] of Object.entries(mappings)) {
- if (img === imgSrc) {
- const user = game.users.get(userId);
- if (!user) continue;
- if (style.length === 0) {
- style = `inset 0 0 0 ${offset}px ${user.color}`;
- } else {
- style += `, inset 0 0 0 ${offset}px ${user.color}`;
- }
- offset += 2;
- title += `\nDisplayed to: ${user.name}`;
- }
- }
- return [title, style];
- }
-
- function setNameDialog(tile) {
- const tileName = tile.document.getFlag('token-variants', 'tileName') || tile.id;
- new Dialog({
- title: `Assign a name to the Tile (3+ chars)`,
- content: `<table style="width:100%"><tr><th style="width:50%"><label>Tile Name</label></th><td style="width:50%"><input type="text" name="input" value="${tileName}"/></td></tr></table>`,
- buttons: {
- Ok: {
- label: `Save`,
- callback: (html) => {
- const name = html.find('input').val();
- if (name) {
- canvas.tiles.hud.clear();
- tile.document.setFlag('token-variants', 'tileName', name);
- }
- },
- },
- },
- }).render(true);
- }
|