598 lines
22 KiB
JavaScript
598 lines
22 KiB
JavaScript
import { MODULE_ID } from "../debug-mode.js";
|
|
import { HelperFunctions, HelperFunctions as HF } from "./HelperFunctions.js";
|
|
import { ImageDisplayManager } from "./ImageDisplayManager.js";
|
|
/**
|
|
* This class manages the Art and Bounding Tiles, creating them, showing them in the Config, and
|
|
* getting and setting their values
|
|
*/
|
|
|
|
export class ArtTileManager {
|
|
/**
|
|
*
|
|
* @param {String} oldTileID - the id of a tile that's now missing
|
|
* @param {String newTileID - the id of a new tile we're linking it to
|
|
*/
|
|
static async updateTileDataID(oldTileID, newTileID) {
|
|
//this is an array of objects
|
|
let tileDataArray = await ArtTileManager.getSceneSlideshowTiles("", false);
|
|
|
|
//find object with id
|
|
|
|
let index = tileDataArray.findIndex((tileData) => tileData.id === oldTileID);
|
|
let tileObject = tileDataArray[index]; //.find((tileData) => tileData.id === oldTileID);
|
|
tileObject = { ...tileObject, id: newTileID };
|
|
|
|
//here we ensure that the info for the linked tiles are also getting updated. Find ones that match our old ID, and update them to our new id
|
|
|
|
//replace the object at its original index with the object w/ the new id
|
|
if (tileObject && index !== undefined) {
|
|
tileDataArray.splice(index, 1, tileObject);
|
|
|
|
if (tileObject.isBoundingTile) {
|
|
ArtTileManager.updateLinkedArtTiles(oldTileID, newTileID, tileDataArray);
|
|
}
|
|
|
|
await ArtTileManager.updateAllSceneTileFlags(tileDataArray);
|
|
}
|
|
}
|
|
|
|
static async updateLinkedArtTiles(oldFrameID, newFrameID, tileDataArray) {
|
|
//get art tiles that had us as their bounding tile
|
|
let linkedArtTiles = [];
|
|
let updatedLinkedArtTiles = [];
|
|
linkedArtTiles = tileDataArray.filter(
|
|
(tileData) => tileData.linkedBoundingTile === oldFrameID
|
|
);
|
|
//update them with our new ID
|
|
updatedLinkedArtTiles = linkedArtTiles.map((tileData) => {
|
|
return {
|
|
...tileData,
|
|
linkedBoundingTile: newFrameID,
|
|
};
|
|
});
|
|
updatedLinkedArtTiles.forEach((atData) => {
|
|
let atIndex = tileDataArray.findIndex((item) => item.id === atData.id);
|
|
tileDataArray.splice(atIndex, 1, atData);
|
|
});
|
|
return tileDataArray;
|
|
}
|
|
static async getDefaultData(isBoundingTile, linkedBoundingTile = "") {
|
|
//determine its default name based on whether it's a bounding or display tile
|
|
let displayName = isBoundingTile ? "frameTile" : "displayTile";
|
|
displayName = await ArtTileManager.incrementTileDisplayName(displayName);
|
|
//increment it if one already exists with that name
|
|
return {
|
|
displayName: `${displayName}`,
|
|
isBoundingTile: isBoundingTile,
|
|
linkedBoundingTile: linkedBoundingTile,
|
|
};
|
|
}
|
|
|
|
static async incrementTileDisplayName(name) {
|
|
let finalName = name;
|
|
let tileDataArray = await ArtTileManager.getSceneSlideshowTiles();
|
|
let conflictingTile = tileDataArray.find((tileData) => {
|
|
return tileData.displayName.includes(name);
|
|
});
|
|
if (conflictingTile) {
|
|
let digit = conflictingTile.displayName.match(/\d+/g);
|
|
if (digit) {
|
|
digit = parseInt(digit);
|
|
digit++;
|
|
} else {
|
|
digit = 1;
|
|
}
|
|
finalName = finalName + digit;
|
|
}
|
|
return finalName;
|
|
}
|
|
|
|
static async createTileInScene(isFrameTile) {
|
|
let ourScene = game.scenes.viewed;
|
|
let pathProperty = isFrameTile ? "frameTilePath" : "artTilePath";
|
|
let imageManager = ImageDisplayManager;
|
|
|
|
let imgPath = await HF.getSettingValue(
|
|
"artGallerySettings",
|
|
`defaultTileImages.paths.${pathProperty}`
|
|
);
|
|
if (!imgPath) {
|
|
return;
|
|
}
|
|
const tex = await loadTexture(imgPath);
|
|
|
|
let sceneWidth = game.version >= 10 ? ourScene.width : ourScene.data.width;
|
|
let sceneHeight = game.version >= 10 ? ourScene.height : ourScene.data.height;
|
|
let dimensionObject = imageManager.calculateAspectRatioFit(
|
|
tex.width,
|
|
tex.height,
|
|
sceneWidth,
|
|
sceneHeight
|
|
);
|
|
|
|
let newTile = await ourScene.createEmbeddedDocuments("Tile", [
|
|
{
|
|
img: imgPath,
|
|
width: dimensionObject.width,
|
|
height: dimensionObject.height,
|
|
x: sceneWidth / 2 - dimensionObject.width / 2,
|
|
y: sceneHeight / 2 - dimensionObject.height / 2,
|
|
},
|
|
]);
|
|
return newTile;
|
|
}
|
|
|
|
static async createOrFindDefaultFrameTile() {
|
|
let frameTile;
|
|
let tileDataArray = (await ArtTileManager.getSceneSlideshowTiles()) || [];
|
|
if (tileDataArray.length === 0) {
|
|
ui.notifications.warn(
|
|
"No frame tile detected in scene. Creating new frame tile alongside display tile"
|
|
);
|
|
frameTile = await ArtTileManager.createFrameTileObject();
|
|
return frameTile[0].id;
|
|
} else {
|
|
ui.notifications.warn(
|
|
"No frame tile provided to when creating display tile. Linking display tile to first frame tile in scene"
|
|
);
|
|
frameTile = tileDataArray[0];
|
|
return frameTile.id;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return any tiles that have data, but their id doesn't match any tile in the scene
|
|
* @param {Array} flaggedTiles - array of tiles with Slideshow data
|
|
* @returns array of tiles that have data but aren't connected to a tile in the scene
|
|
*/
|
|
static async getMissingTiles(flaggedTiles) {
|
|
let currentScene = game.scenes.viewed;
|
|
let sceneTileIDs = currentScene.tiles.map((tile) => tile.id);
|
|
let missingTiles = flaggedTiles.filter(
|
|
(tileData) => !sceneTileIDs.includes(tileData.id)
|
|
);
|
|
return missingTiles;
|
|
}
|
|
|
|
/**
|
|
* Get tiles that aren't linked to any slideshow data
|
|
* @param {Array} flaggedTiles - array of tiles with Slideshow data
|
|
* @returns array of IDs of tiles that aren't linked to any slideshow data
|
|
*/
|
|
static async getUnlinkedTileIDs(flaggedTiles) {
|
|
let currentScene = game.scenes.viewed;
|
|
let flaggedTileIDs = flaggedTiles.map((tileData) => tileData.id);
|
|
let sceneTileIDs = currentScene.tiles.map((tile) => tile.id);
|
|
let unlinkedTiles = sceneTileIDs.filter(
|
|
(tileID) => !flaggedTileIDs.includes(tileID)
|
|
);
|
|
return unlinkedTiles;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {String} linkedFrameTileId - the frame tile linked to this art tile, if it is one
|
|
* @param {String} tileObjectID - the ID of the tile being created
|
|
* @param {Boolean} isBoundingTile - whether or not its a frame tile instead of an art tile
|
|
*/
|
|
static async createTileData(linkedFrameTileId, tileObjectID, isBoundingTile = false) {
|
|
let defaultData = await ArtTileManager.getDefaultData(
|
|
isBoundingTile,
|
|
linkedFrameTileId
|
|
);
|
|
await ArtTileManager.updateSceneTileFlags(defaultData, tileObjectID);
|
|
}
|
|
|
|
/**
|
|
* Create a Tile in the current scene that is linked to the Tile Data we're passing in
|
|
* @param {Object} options - the options object
|
|
* @param {Boolean} options.isFrameTile - whether or not its a frame tile or an art tile
|
|
* @param {String} options.linkedFrameTileID - the ID of the linked frame tile
|
|
* @param {String} options.unlinkedDataID - the ID of the unlinked art gallery tile we want to link to a tile in the scene
|
|
* @returns the new tile object in the scene that is linked to our previously unlinked data
|
|
*/
|
|
static async createAndLinkSceneTile(
|
|
options = {
|
|
isFrameTile: false,
|
|
linkedFrameTileID: "",
|
|
unlinkedDataID: "",
|
|
}
|
|
) {
|
|
let { isFrameTile, linkedFrameTileID, unlinkedDataID } = options;
|
|
|
|
let newTile = await ArtTileManager.createTileInScene(isFrameTile);
|
|
|
|
if (newTile) {
|
|
let tileObjectID = newTile[0].id;
|
|
|
|
//
|
|
if (!unlinkedDataID) {
|
|
console.log("Scene Gallery Config - Creating new tile data");
|
|
await ArtTileManager.createTileData(
|
|
linkedFrameTileID,
|
|
tileObjectID,
|
|
false
|
|
);
|
|
} else {
|
|
console.log(
|
|
"Scene Gallery Config | updating already created tile data, and linking it"
|
|
);
|
|
await ArtTileManager.updateTileDataID(unlinkedDataID, tileObjectID);
|
|
}
|
|
} else {
|
|
ui.notifications.error("New art gallery tile couldn't be created");
|
|
}
|
|
return newTile;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {String} linkedFrameTileId - the frame tile linked to this art tile, if it is one
|
|
* @param {*} unlinkedDataID - if we're creating a new tile from the config rather than from the tile itself, it may have an unlinkedId
|
|
* @returns - the created art tile
|
|
*/
|
|
static async createArtTileObject(_linkedFrameTileId = "", unlinkedDataID = "") {
|
|
let linkedFrameTileId = _linkedFrameTileId;
|
|
|
|
let newTile = await ArtTileManager.createTileInScene(false);
|
|
if (newTile) {
|
|
let tileObjectID = newTile[0].id;
|
|
if (!unlinkedDataID) {
|
|
await ArtTileManager.createTileData(
|
|
linkedFrameTileId,
|
|
tileObjectID,
|
|
false
|
|
);
|
|
} else {
|
|
await ArtTileManager.updateTileDataID(unlinkedDataID, tileObjectID);
|
|
}
|
|
} else {
|
|
ui.notifications.error("New display tile couldn't be created");
|
|
}
|
|
return newTile;
|
|
}
|
|
|
|
/**
|
|
* @param {String} linkedFrameTileId - the frame tile linked to this art tile, if it is one
|
|
* @param {*} unlinkedDataID - if we're creating a new tile from the config rather than from the tile itself, it may have an unlinkedId
|
|
* @returns - the created frame tile
|
|
* */
|
|
static async createFrameTileObject(unlinkedDataID = "") {
|
|
let newTile = await ArtTileManager.createTileInScene(true);
|
|
if (newTile) {
|
|
let tileObjectID = newTile[0].id;
|
|
// let tileObjectID = newTile[0].document.id;
|
|
if (!unlinkedDataID) {
|
|
await ArtTileManager.createTileData("", tileObjectID, true);
|
|
} else {
|
|
await ArtTileManager.updateTileDataID(unlinkedDataID, tileObjectID);
|
|
}
|
|
} else {
|
|
ui.notifications.error("New frame tile couldn't be created");
|
|
}
|
|
return newTile;
|
|
}
|
|
|
|
static async convertToNewSystem() {
|
|
let currentScene = game.scenes.viewed;
|
|
let sceneTiles = currentScene.tiles.contents;
|
|
let boundingTile;
|
|
let displayTile;
|
|
sceneTiles.forEach(async (tile) => {
|
|
let flag = await tile.getFlag("journal-to-canvas-slideshow", "name");
|
|
switch (flag.name) {
|
|
case "boundingTile":
|
|
boundingTile = tile;
|
|
break;
|
|
case "displayTile":
|
|
displayTile = tile;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
});
|
|
if (boundingTile) {
|
|
convertBoundingTile(boundingTile.data);
|
|
if (displayTile) {
|
|
convertDisplayTile(displayTile.data, boundingTile.id);
|
|
}
|
|
} else {
|
|
if (displayTile) {
|
|
convertDisplayTile(displayTile.data);
|
|
}
|
|
}
|
|
}
|
|
|
|
static convertBoundingTile(tileData) {
|
|
let defaultData = {
|
|
displayName: "BoundingTile1",
|
|
isBoundingTile: true,
|
|
linkedBoundingTile: "",
|
|
};
|
|
updateSceneTileFlags(defaultData, tileData.id);
|
|
}
|
|
static convertDisplayTile(tileData, linkedBoundingTileId = "") {
|
|
let defaultData = {
|
|
displayName: "DisplayTile1",
|
|
isBoundingTile: false,
|
|
linkedBoundingTile: linkedBoundingTileId,
|
|
};
|
|
updateSceneTileFlags(defaultData, tileData.id);
|
|
}
|
|
|
|
/**
|
|
* Set the default art gallery art tile in this scene
|
|
* @param {String} tileID - the id of the art gallery tile that was clicked
|
|
* @param {Object} currentScene - the current scene doc
|
|
*/
|
|
static async setDefaultArtTileID(tileID, currentScene) {
|
|
if (!currentScene) currentScene = game.scenes.viewed;
|
|
await currentScene.setFlag(MODULE_ID, "defaultArtTileID", tileID);
|
|
Hooks.callAll("updateDefaultArtTile", { updateData: tileID, currentScene });
|
|
}
|
|
/**
|
|
* Gets the default art gallery tile in this scene, or returns the first tile in scene if not found
|
|
* returns undefined if that is also not found
|
|
* @param {Object} currentScene - the current scene doc
|
|
*/
|
|
static async getDefaultArtTileID(currentScene) {
|
|
if (!currentScene) currentScene = game.scenes.viewed;
|
|
|
|
//get all the art tiles in the scene, filtering out the ones that aren't unlinked/missing
|
|
let artTiles = (
|
|
await ArtTileManager.getSceneSlideshowTiles("art", true, {
|
|
currentSceneID: currentScene.id,
|
|
})
|
|
).filter((item) => !item.missing);
|
|
let defaultArtTileID = await currentScene.getFlag(MODULE_ID, "defaultArtTileID");
|
|
|
|
// if the defaultArtTileID stored in our flags is
|
|
let shouldReplaceID = false;
|
|
const found = artTiles.find((tileData) => tileData.id === defaultArtTileID);
|
|
|
|
if (!found) {
|
|
//a tile with this ID wasn't in the scene, so replace it with a the first art tile that IS linked, or if there are none, replace it with an empty string
|
|
shouldReplaceID = true;
|
|
|
|
if (artTiles.length > 0) {
|
|
defaultArtTileID = artTiles[0].id;
|
|
} else {
|
|
defaultArtTileID = "";
|
|
}
|
|
}
|
|
// should
|
|
if (shouldReplaceID) {
|
|
await currentScene.setFlag(MODULE_ID, "defaultArtTileID", defaultArtTileID);
|
|
}
|
|
|
|
return defaultArtTileID;
|
|
}
|
|
|
|
static async getGalleryTileDataFromID(tileID, property = "", currentSceneID = "") {
|
|
if (!currentSceneID) currentSceneID = game.scenes.viewed.current;
|
|
let flaggedTiles = await ArtTileManager.getSceneSlideshowTiles("", false, {
|
|
currentSceneID,
|
|
});
|
|
let ourTile = flaggedTiles.find((data) => data.id === tileID);
|
|
if (property) {
|
|
if (ourTile) {
|
|
return ourTile[property];
|
|
} else {
|
|
return "";
|
|
}
|
|
} else {
|
|
return ourTile;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* get tiles that have been stored by this module in a flag on this scene
|
|
* @returns array of display tile data stored in "slideshowTiles" tag
|
|
*/
|
|
static async getSceneSlideshowTiles(
|
|
type = "",
|
|
shouldCheckIfExists = false,
|
|
options = { currentSceneID: "" }
|
|
) {
|
|
let { currentSceneID } = options;
|
|
let currentScene = game.scenes.viewed;
|
|
if (currentSceneID) {
|
|
currentScene = game.scenes.get(currentSceneID);
|
|
}
|
|
let flaggedTiles =
|
|
(await currentScene.getFlag(
|
|
"journal-to-canvas-slideshow",
|
|
"slideshowTiles"
|
|
)) || [];
|
|
|
|
//check if the tile exists in the scene, and add a "missing" element if it does
|
|
if (shouldCheckIfExists) {
|
|
//get the ids of all the tiles in the scene
|
|
let sceneTileIDs = currentScene.tiles.map((tile) => tile.id);
|
|
|
|
// if our tile data's id isn't included in the ids of tiles in the scene, add a missing property
|
|
flaggedTiles = flaggedTiles.map((tileData) =>
|
|
!sceneTileIDs.includes(tileData.id)
|
|
? { ...tileData, missing: true }
|
|
: tileData
|
|
);
|
|
}
|
|
|
|
if (type === "frame") {
|
|
return ArtTileManager.getFrameTiles(flaggedTiles);
|
|
} else if (type === "art") {
|
|
return ArtTileManager.getDisplayTiles(flaggedTiles);
|
|
}
|
|
|
|
return flaggedTiles;
|
|
}
|
|
|
|
/**
|
|
* Returns the "bounding" or "frame" tiles from the scene's displayTiles flag
|
|
* @param {Array} flaggedTiles - array of Display Tiles from a scene flag
|
|
* @returns filtered array with only the bounding tiles
|
|
*/
|
|
static getFrameTiles(flaggedTiles) {
|
|
return flaggedTiles.filter((tileData) => tileData.isBoundingTile);
|
|
}
|
|
/**
|
|
* Returns the Display tiles from the scene's displayTiles flag array
|
|
* @param {Array} flaggedTiles - array of SlideshowTiles from the scene's flag
|
|
* @returns filtered array with only the display tiles
|
|
*/
|
|
static getDisplayTiles(flaggedTiles) {
|
|
return flaggedTiles.filter((tileData) => !tileData.isBoundingTile);
|
|
}
|
|
static async renderTileConfig(tileID, sceneID = "") {
|
|
let tile = await game.scenes.viewed.getEmbeddedDocument("Tile", tileID);
|
|
if (tile) {
|
|
await tile.sheet.render(true);
|
|
} else {
|
|
ArtTileManager.displayTileNotFoundError(tileID);
|
|
}
|
|
}
|
|
|
|
static async toggleTileZ(tileID, toFront = true) {
|
|
let tile = await game.scenes.viewed.getEmbeddedDocument("Tile", tileID);
|
|
if (!tile) return;
|
|
|
|
const zIndex = tile.object.zIndex;
|
|
if (toFront) {
|
|
//save the default z-index
|
|
tile.defaultZ = zIndex;
|
|
await game.scenes.viewed.updateEmbeddedDocuments("Tile", {
|
|
_id: tileID,
|
|
object: { zIndex: 300 },
|
|
});
|
|
} else {
|
|
await game.scenes.viewed.updateEmbeddedDocuments("Tile", {
|
|
_id: tileID,
|
|
object: { zIndex: tile.defaultZ },
|
|
});
|
|
}
|
|
}
|
|
|
|
static async selectTile(tileID, sceneID = "") {
|
|
let tile = await game.scenes.viewed.getEmbeddedDocument("Tile", tileID);
|
|
if (tile) {
|
|
await game.JTCS.utils.swapTools();
|
|
tile.object.control({
|
|
releaseOthers: true,
|
|
});
|
|
} else {
|
|
ArtTileManager.displayTileNotFoundError(tileID);
|
|
}
|
|
}
|
|
|
|
static async getLinkedFrameID(tileID, flaggedTiles) {
|
|
let tileData = await ArtTileManager.getTileDataFromFlag(tileID, flaggedTiles);
|
|
if (!tileData) {
|
|
console.error("Could not find tile data with that ID");
|
|
}
|
|
return tileData.linkedBoundingTile;
|
|
}
|
|
/**
|
|
* Get the DisplayTile data
|
|
* @param {string} tileID - the id of the tile in scene we're looking to filter
|
|
* @param {Array} flaggedTiles - the flagged tiles
|
|
* @returns the flag data
|
|
*/
|
|
static async getTileDataFromFlag(tileID, flaggedTiles) {
|
|
let defaultData = ArtTileManager.getDefaultData(false, "");
|
|
|
|
if (!flaggedTiles) {
|
|
return defaultData;
|
|
}
|
|
|
|
let flaggedTile = flaggedTiles.find((tileData) => tileData.id === tileID);
|
|
|
|
//if we find a tile
|
|
if (flaggedTile) {
|
|
return flaggedTile;
|
|
} else {
|
|
return defaultData;
|
|
}
|
|
}
|
|
static async getTileObjectByID(tileID, sceneID = "") {
|
|
let ourScene = game.scenes.viewed;
|
|
if (sceneID) ourScene = game.scenes.get(sceneID);
|
|
let tile = await ourScene.getEmbeddedDocument("Tile", tileID);
|
|
if (tileID.includes === "new") {
|
|
// console.log("New tile created");
|
|
} else {
|
|
if (!tile) {
|
|
ArtTileManager.displayTileNotFoundError(tileID);
|
|
}
|
|
}
|
|
return tile;
|
|
}
|
|
|
|
static displayTileNotFoundError(tileID, sceneID = "") {
|
|
console.error("JTCS can't find tile in scene with ID " + tileID);
|
|
}
|
|
|
|
/**
|
|
* Update a tile in this scene with new data, or create a new one
|
|
* @param {Object} displayData - the data we want to update with
|
|
* @param {String} tileID - the id of the tile we want to update
|
|
*/
|
|
static async updateSceneTileFlags(displayData, tileID) {
|
|
if (!tileID) {
|
|
return;
|
|
}
|
|
let currentScene = game.scenes.viewed;
|
|
let tiles = (await ArtTileManager.getSceneSlideshowTiles()) || [];
|
|
|
|
if (tiles.find((tile) => tile.id === tileID)) {
|
|
tiles = tiles.map((tileData) => {
|
|
// if the ids match, update the matching one with the new displayName
|
|
return tileData.id === tileID
|
|
? { ...tileData, ...displayData }
|
|
: tileData; //else just return the original
|
|
});
|
|
} else {
|
|
tiles.push({ id: tileID, ...displayData });
|
|
}
|
|
|
|
await ArtTileManager.updateAllSceneTileFlags(tiles);
|
|
}
|
|
|
|
/**
|
|
* Replace the slideshow tileData array stored in scene flags with the array passed in
|
|
* @param {Array} tiles - the tiles array we want to update our flag with
|
|
*/
|
|
static async updateAllSceneTileFlags(tiles, currentSceneID = "") {
|
|
let currentScene = game.scenes.get(currentSceneID);
|
|
if (!currentScene) currentScene = game.scenes.viewed;
|
|
await currentScene.setFlag(
|
|
"journal-to-canvas-slideshow",
|
|
"slideshowTiles",
|
|
tiles
|
|
);
|
|
Hooks.callAll("updateArtGalleryTiles", { currentScene, updateData: tiles });
|
|
}
|
|
static async deleteSceneTileData(tileID) {
|
|
let tiles = await ArtTileManager.getSceneSlideshowTiles();
|
|
//filter out the tile that matches this
|
|
let deletedTileData = tiles.find((tileData) => tileData.id === tileID);
|
|
tiles = tiles.filter((tileData) => tileData.id !== tileID);
|
|
|
|
if (deletedTileData.isBoundingTile) {
|
|
await ArtTileManager.updateLinkedArtTiles(tileID, "", tiles);
|
|
}
|
|
await ArtTileManager.updateAllSceneTileFlags(tiles);
|
|
|
|
//call hook to delete the art tile data
|
|
}
|
|
|
|
static async getAllScenesWithSlideshowData() {
|
|
let slideshowScenes =
|
|
game.scenes.contents.filter((scene) => {
|
|
if (game.version >= 10)
|
|
return scene.flags[`${MODULE_ID}`]?.slideshowTiles;
|
|
else return scene.data.flags[`${MODULE_ID}`]?.slideshowTiles;
|
|
}) || [];
|
|
return slideshowScenes;
|
|
}
|
|
}
|