journal-to-canvas-slideshow/scripts/classes/ImageDisplayManager.js

470 lines
18 KiB
JavaScript
Raw Normal View History

2024-01-28 15:59:53 -06:00
import { ArtTileManager } from "./ArtTileManager.js";
import { HelperFunctions } from "./HelperFunctions.js";
import ImageVideoPopout from "./MultiMediaPopout.js";
/**
* This class manages the images specifically, setting and clearing the tiles' images
*/
export class ImageDisplayManager {
static async getTilesFromArtScene() {
let artSceneID = await game.JTCS.utils.getSettingValue(
"artGallerySettings",
"dedicatedDisplayData.scene.value"
);
let ourScene = game.scenes.get(artSceneID);
let artTiles = await game.JTCS.tileUtils.getSceneSlideshowTiles("art", true, {
currentSceneID: artSceneID,
});
let artTileID = artTiles[0].id;
let frameTiles = await game.JTCS.tileUtils.getSceneSlideshowTiles("frame", true, {
currentSceneID: artSceneID,
});
let frameTileID = artTiles[0].linkedBoundingTile || frameTiles[0].id;
return {
ourScene: ourScene,
artTileID: artTileID,
frameTileID: frameTileID,
};
}
static async updateTileObjectTexture(
artTileID,
frameTileID,
url,
method,
sceneID = ""
) {
let ourScene = game.scenes.get(sceneID);
if (!ourScene) ourScene = game.scenes.viewed;
let artTile = ourScene.tiles.get(artTileID);
let frameTile = ourScene.tiles.get(frameTileID);
//load the texture from the source
if (!artTile || !url) {
ui.notifications.error("Tile or image not found");
console.error(url, artTile, artTileID);
return;
}
const tex = await loadTexture(url);
if (!tex) {
ui.notifications.error(
`Error loading texture from '${url}'. Access to URL likely blocked by CORS policy.`
);
return;
}
let imageUpdate;
if (!frameTile) {
imageUpdate = await ImageDisplayManager.scaleArtTileToScene(
artTile,
tex,
url,
sceneID
);
} else {
imageUpdate = await ImageDisplayManager.scaleArtTileToFrameTile(
artTile,
frameTile,
tex,
url,
sceneID
);
}
const updated = await ourScene
.updateEmbeddedDocuments("Tile", [imageUpdate])
.catch((error) =>
ui.notifications.error(
`Default art tile in ${ourScene.name} couldn't be updated`
)
);
if (updated && method === "artScene") {
const { autoActivate, autoView } = await HelperFunctions.getSettingValue(
"artGallerySettings",
"dedicatedDisplayData.scene"
);
if (autoActivate) {
ourScene.activate();
}
if (autoView) {
ourScene.view();
}
if (
game.user.isGM && //if we're GM
((!autoActivate && !autoView) || (ourScene.active && !ourScene.viewed)) //if the scene is neither set to activate or to view, notify that the image updated.
// if the scene is active but not viewed, notify that the image updated
) {
ui.notifications.info(
`Default Tile in Art Scene '${ourScene.name}' successfully updated`
);
}
}
}
static async scaleArtTileToScene(displayTile, tex, url, sceneID = "") {
let displayScene = game.scenes.get(sceneID);
if (!displayScene) displayScene = game.scenes.viewed;
let displaySceneWidth =
game.version >= 10 ? displayScene.width : displayScene.data.width;
let displaySceneHeight =
game.version >= 10 ? displayScene.height : displayScene.data.height;
let dimensionObject = await ImageDisplayManager.calculateAspectRatioFit(
tex.width,
tex.height,
displaySceneWidth,
displaySceneHeight
// displayScene.data.width,
// displayScene.data.height
);
//scale down factor is how big the tile will be in the scene
//make this scale down factor configurable at some point
let scaleDownFactor = 200;
dimensionObject.width -= scaleDownFactor;
dimensionObject.height -= scaleDownFactor;
//half of the scene's width or height is the center -- we're subtracting by half of the image's width or height to account for the offset because it's measuring from top/left instead of center
//separate objects depending on the texture's dimensions --
//create an 'update' object for if the image is wide (width is bigger than height)
let wideImageUpdate = {
_id: displayTile.id,
width: dimensionObject.width,
height: dimensionObject.height,
img: url,
x: scaleDownFactor / 2,
y: displaySceneHeight / 2 - dimensionObject.height / 2,
};
//create an 'update' object for if the image is tall (height is bigger than width)
let tallImageUpdate = {
_id: displayTile.id,
width: dimensionObject.width,
height: dimensionObject.height,
img: url,
y: scaleDownFactor / 2,
x: displaySceneWidth / 2 - dimensionObject.width / 2,
};
//https://stackoverflow.com/questions/38675447/how-do-i-get-the-center-of-an-image-in-javascript
//^used the above StackOverflow post to help me figure that out
//Determine if the image or video is wide, tall, or same dimensions and update depending on that
if (dimensionObject.height > dimensionObject.width) {
//if the height is longer than the width, use the tall image object
return tallImageUpdate;
} else if (dimensionObject.width > dimensionObject.height) {
//if the width is longer than the height, use the wide image object
return wideImageUpdate;
}
//if the image length and width are pretty much the same, just default to the wide image update object
return wideImageUpdate;
}
static async scaleArtTileToFrameTile(artTile, frameTile, tex, url) {
const frameTileWidth =
game.version >= 10 ? frameTile.width : frameTile.data.width;
const frameTileHeight =
game.version >= 10 ? frameTile.height : frameTile.data.height;
const frameTileX = game.version >= 10 ? frameTile.x : frameTile.data.x;
const frameTileY = game.version >= 10 ? frameTile.y : frameTile.data.y;
let dimensionObject = ImageDisplayManager.calculateAspectRatioFit(
tex.width,
tex.height,
frameTileWidth,
frameTileHeight
);
let imageUpdate = {
_id: artTile.id,
width: dimensionObject.width,
height: dimensionObject.height,
img: url,
y: frameTileY,
x: frameTileX,
};
//Ensure image is centered to bounding tile (stops images hugging the top left corner of the bounding box).
let boundingMiddle = {
x: frameTileX + frameTileWidth / 2,
y: frameTileY + frameTileHeight / 2,
};
let imageMiddle = {
x: imageUpdate.x + imageUpdate.width / 2,
y: imageUpdate.y + imageUpdate.height / 2,
};
imageUpdate.x += boundingMiddle.x - imageMiddle.x;
imageUpdate.y += boundingMiddle.y - imageMiddle.y;
return imageUpdate;
}
/** Used snippet from the below stackOverflow answer to help me with proportionally resizing the images*/
/*https://stackoverflow.com/questions/3971841/how-to-resize-images-proportionally-keeping-the-aspect-ratio*/
static calculateAspectRatioFit(srcWidth, srcHeight, maxWidth, maxHeight) {
let ratio = Math.min(maxWidth / srcWidth, maxHeight / srcHeight);
return {
width: srcWidth * ratio,
height: srcHeight * ratio,
};
}
static async displayImageInWindow(method, url) {
if (method === "journalEntry") {
let dedicatedDisplayData = await HelperFunctions.getSettingValue(
"artGallerySettings",
"dedicatedDisplayData"
);
const displayJournalID = dedicatedDisplayData.journal.value;
const displayJournal = game.journal.get(displayJournalID);
//if we would like to display in a dedicated journal entry
if (!displayJournal) {
//couldn't find display journal, so return
if (game.user.isGM) {
ui.notifications.error(
`No Art Journal entry set! Please set your art journal in the module settings or Art Gallery Config`
);
}
return;
} else {
displayJournal.render(true);
}
//get the file type of the image url via regex match
var fileTypePattern = /\.[0-9a-z]+$/i;
var fileType = url.match(fileTypePattern);
let journalMode = "image";
let update;
if (fileType == ".mp4" || fileType == ".webm") {
if (game.version < 10) {
// if the file type is a video and we're before v10, we have to do a bit of a wonky workaround
let videoHTML = `<div style="height:100%; display: flex; flex-direction: column; justify-content:center; align-items:center;">
<video width="100%" height="auto" autoplay loop>
<source src=${url} type="video/mp4">
<source src=${url} type="video/webm">
</video>
</div>
`;
update = {
_id: displayJournal._id,
content: videoHTML,
img: "",
};
imageMode = "text";
} else {
//if we're after v10
update = {
_id: displayJournal.id,
src: url,
video: {
loop: true,
autoplay: true,
},
type: "video",
};
}
} else {
//change the background image to be the clicked image in the journal
if (game.version < 10) {
update = {
_id: displayJournal.id,
content: "",
img: url,
};
} else {
update = {
// _id: displayJournal.id,
src: url,
type: "image",
};
}
}
let updated;
if (game.version < 10) {
updated = await displayJournal.update(update, {});
} else {
const firstPage = displayJournal.pages.contents[0];
updated = await firstPage?.update({ _id: firstPage.id, ...update });
}
if (updated === null && game.user.isGM) {
ui.notifications.error(
`Could not display image in Art Journal '${displayJournal.name}.'`
);
}
const { autoActivate, autoView } = await HelperFunctions.getSettingValue(
"artGallerySettings",
"dedicatedDisplayData.journal"
);
if (autoActivate) displayJournal.show(journalMode, true);
if (autoView && !displayJournal.sheet.rendered)
displayJournal.sheet.render(true);
if (game.user.isGM && !autoActivate && !autoView) {
ui.notifications.info(
`Image in Art Journal '${displayJournal.name}' successfully updated`
);
}
} else if (method === "window") {
//if we would like to display in a new popout window
let popout = new ImageVideoPopout(url, {
shareable: true,
})
.render(true)
.shareImage();
}
}
/**
* determine the location of the display
* @param {*} imageElement - the imageElement
* @param {*} location - the location we want to display our image in
* @param {*} journalSheet - the journal sheet in which we're performing these actions
* @param {*} url
*/
static async determineDisplayMethod(sheetImageData = { method: "window", url: "" }) {
let { method, imageElement, url } = sheetImageData;
if (!url && imageElement) {
url = ImageDisplayManager.getImageSource(imageElement);
} else if (!url && !imageElement) {
console.error("No image data passed to this method", url, imageElement);
}
//on click, this method will determine if the image should open in a scene or in a display journal
switch (method) {
case "artScene":
let artSceneID = await HelperFunctions.getSettingValue(
"artGallerySettings",
"dedicatedDisplayData.scene.value"
);
let artScene = game.scenes.get(artSceneID);
if (artScene) {
let defaultArtTileID = await ArtTileManager.getDefaultArtTileID(
artScene
);
if (!defaultArtTileID) {
ui.notifications.error(
"No valid 'Art Tile' found in scene '" + artScene.name + "'"
);
return;
}
let frameTileID = await ArtTileManager.getGalleryTileDataFromID(
defaultArtTileID,
"linkedBoundingTile",
artSceneID
);
await ImageDisplayManager.updateTileObjectTexture(
defaultArtTileID,
frameTileID,
url,
method,
artSceneID
);
}
break;
case "anyScene":
let { artTileID, frameTileID } = sheetImageData;
//if the setting is to display it in a scene, proceed as normal
if (method === "anyScene" && !artTileID) {
artTileID = await ArtTileManager.getDefaultArtTileID(
game.scenes.viewed
);
if (!artTileID) {
ui.notifications.error(
"No valid 'Art Tile' found in current scene"
);
return;
}
frameTileID = await ArtTileManager.getGalleryTileDataFromID(
artTileID,
"linkedBoundingTile"
);
}
await ImageDisplayManager.updateTileObjectTexture(
artTileID,
frameTileID,
url,
method
);
break;
case "journalEntry":
case "window":
await ImageDisplayManager.displayImageInWindow(method, url);
break;
}
}
static getImageSource(imageElement) {
let type = imageElement.nodeName;
let url;
if (type == "IMG") {
//if it's an image element
url = imageElement.getAttribute("src");
} else if (type == "VIDEO") {
//if it's a video element
url = imageElement.getElementsByTagName("source")[0]?.getAttribute("src");
if (!url) {
url = imageElement.getAttribute("src");
}
} else if (type == "DIV" && imageElement.classList.contains("lightbox-image")) {
//if it's a lightbox image on an image-mode journal
//https://stackoverflow.com/questions/14013131/how-to-get-background-image-url-of-an-element-using-javascript --
let imgStyle = imageElement.style;
url = imgStyle.backgroundImage.slice(4, -1).replace(/['"]/g, "");
} else {
ui.notifications.error("Type not supported");
url = null;
}
if (!url) {
ui.notifications.error("url not found");
}
return url;
//load the texture from the source
}
static async clearDisplayWindow() {
if (!findDisplayJournal()) {
return;
}
let url =
"/modules/journal-to-canvas-slideshow/artwork/HD_transparent_picture.png";
let update = {
_id: displayJournal.id,
img: url,
content: `<div></div>`,
};
const updated = await displayJournal.update(update, {});
}
static async clearTile(tileID, options = {}) {
let { ourScene } = options;
if (!ourScene) ourScene = game.scenes.viewed;
const isBoundingTile = await ArtTileManager.getGalleryTileDataFromID(
tileID,
"isBoundingTile"
);
let propertyPath = "defaultTileImages.paths.artTilePath";
if (isBoundingTile) {
propertyPath = "defaultTileImages.paths.frameTilePath";
}
const clearImagePath = await HelperFunctions.getSettingValue(
"artGallerySettings",
propertyPath
);
var clearTileUpdate = {
_id: tileID,
img: clearImagePath,
};
await ourScene.updateEmbeddedDocuments("Tile", [clearTileUpdate]);
}
}