Code comments, fixes and clean ups

This commit is contained in:
AloeSapling 2025-08-23 11:18:05 +02:00
parent fa5747e74e
commit dd5e871e72
9 changed files with 383 additions and 128 deletions

View file

@ -2,34 +2,60 @@ import { TBlueMarbleTemplate } from "../types/schemas";
import { dataManager, uiManager } from "./main";
import { generateUUID } from "./utils";
// Typescript / Javascript for the "manageTemplates" window
let selectedFile: Blob = new Blob()
let lngLat: { lng: number; lat: number; }
let zoomLevel: number | null = null;
/**Closes this window
* @since 0.1.0-overhaul
*/
function close(){
uiManager.close("bm-create-template")
}
function getCoords(): number[]{
console.log("test")
if(!(lngLat && zoomLevel)){ window.charity.lib.sonner.toast.error("You must select a pixel first") }
console.log("test")
console.log("lnglat: ",lngLat)
console.log(lngLat.lat, lngLat.lng, zoomLevel)
const tilePixel = window.charity.game.mercator.latLonToTileAndPixel(lngLat.lat, lngLat.lng, zoomLevel!)
console.log("test")
/**Gets the coordinates in longitude and latitude of the clicked-on pixel
* @returns A 4 element long array representing the coordines of the clicked-on pixel or undefined if an error occured
* @since 0.1.0-overhaul
*/
function getCoords(): number[] | undefined{
// lngLat and zoomLevel update whenever the user clicks on a pixel
if(!(lngLat && zoomLevel)){
// If they don't exist, that means the player hasn't clicked on a pixel
charity.lib.sonner.toast.error("You must select a pixel first");
return;
}
// Convert latitude and longitude into Tx, Ty, Px and Py
const tilePixel = charity.game.mercator.latLonToTileAndPixel(lngLat.lat, lngLat.lng, zoomLevel!)
// Combine the tile and pixel coordinates into one array
return [...tilePixel.tile, ...tilePixel.pixel]
}
/**Sets the value of the coordinate inputs with the value from the clicked on pixel
* @since 0.1.0-overhaul
*/
function setCoords(){
// Get the coordinates of the clicked-on pixel
const coords = getCoords();
console.log(coords)
// Get the container for the coordinate inputs
const coordsContainer = document.querySelector("#bm-create-template #coords-container");
if(!coordsContainer){ return }
if(coords.length < 4){ return }
// If coords is undefined or its length less than 4
if(!coords || coords.length < 4){
// Then the player hadn't clicked on a pixel or a different error occured
charity.lib.sonner.toast.error("Click on a pixel first")
return;
}
let index = 0;
// Fill in the inputs with data from the gotten coordinates
coordsContainer.childNodes.forEach((childNode: ChildNode) => {
if(childNode.nodeName.toLocaleUpperCase() == "INPUT" && index != 4){
(childNode as HTMLInputElement).value = coords[index].toString();
@ -38,31 +64,46 @@ function setCoords(){
});
}
/**Gets the data from all the inputs and organises them into the Blue Marble template format
* @returns The data organised into a Blue Marble template object or undefined if an error occured
* @since 0.1.0-overhaul
*/
function getNewTemplateData(): TBlueMarbleTemplate | undefined{
// Tries to get the given elements and checks if they exist
const coordsContainer = document.querySelector("#bm-create-template #coords-container");
if(!coordsContainer){ return }
const nameInput = document.querySelector("#bm-create-template input#name")
if(!nameInput || nameInput.nodeName !== "INPUT"){ return }
if(selectedFile.size <= 0){ return }
// Get the coordinates from the 4 inputs
let coords: number[] = [];
coordsContainer.childNodes.forEach((childNode: ChildNode) => {
if(childNode.nodeName.toLocaleUpperCase() == "INPUT"){
try{
coords.push(Number((childNode as HTMLInputElement).value))
}catch(err){}
// Don't accept empty inputs
if((childNode as HTMLInputElement).value !== ""){
try{
coords.push(Number((childNode as HTMLInputElement).value))
}catch{}
}
}
});
// Coordinates should be exactly 4 elements long
if(coords.length !== 4){
charity.lib.sonner.toast.error("Fill in all coord inputs");
// If it isn't that means an error occured,
// Likely caused by malformed data being inputted
charity.lib.sonner.toast.error("Fill in all the coordinate inputs with numbers");
return;
}
// Check if an image was provided
if(!selectedFile){
charity.lib.sonner.toast.error("Drag or upload an image");
return;
}
// Create a data URL from the file data
const dataURL = URL.createObjectURL(selectedFile);
return {
@ -75,28 +116,29 @@ function getNewTemplateData(): TBlueMarbleTemplate | undefined{
}
}
/**Creates a new template from the data gotten from the inputs
* @since 0.1.0-overhaul
*/
function createTemplate(){
const data = getNewTemplateData();
const data = getNewTemplateData(); // Data from inputs
if(!data){ return }
dataManager.addTemplate(data);
}
/**Initialises this window's UI-related javascript (addEventListener hooks, ect)
* @since 0.1.0-overhaul
*/
export function initCreateTemplate(){
// Add event listener hooks
window.charity.game.map.on("click", (e)=>{
console.log(e)
// Update the clicked-on pixel variables whenver the user clicks on a pixel
charity.game.map.on("click", (e)=>{
lngLat = e.lngLat as {lat: number, lng: number};
console.log(e.lngLat)
zoomLevel = window.charity.game.map.getZoom();
console.log("test")
setCoords()
zoomLevel = charity.game.map.getZoom();
})
const coordsBtn = document.querySelector("#bm-create-template button#coords");
console.log("coordsBtn: testestsetestseesseetestest ",coordsBtn)
console.log(coordsBtn?.nodeName)
console.log(coordsBtn?.nodeName.toLocaleUpperCase() === "BUTTON")
if(coordsBtn && coordsBtn.nodeName.toLocaleUpperCase() === "BUTTON"){
coordsBtn.addEventListener("click", ()=>{console.log("test");setCoords()})
}

View file

@ -1,8 +1,14 @@
import { Schemas, SchemaTypeNames } from "../types/types";
import { BlueMarbleJSON, CharityJSON, BM_SCHEMA_VERSION, TBlueMarbleJSON, TCharityJSON, CHA_SCHEMA_VERSION, TBlueMarbleTemplate, TBlueMarbleLink } from "../types/schemas";
/**A manager that manages the general data object (templates, links, ect.)
*
* Supports functionality related to appending, storing locally, updating, converting, type-checking and object creation (template, links)
* @since 0.1.0-overhaul
*/
export default class DataManager {
constructor(object?: Schemas){
// Assign the data to the stored object
this.object = object;
if(object){
// Set the appropriate schema type
@ -18,16 +24,19 @@ export default class DataManager {
/**Updates the stored object whilst making sure the type matches
* @param {Schemas} data The data used to update the stored object
* @since 0.1.0-overhaul
*/
update(data: Schemas){
// Match the stored type to the type of schema
if(BlueMarbleJSON.safeParse(data).success){this.type = "BM"}
else if(CharityJSON.safeParse(data).success){this.type = "CHA"}
else {return} // If it doesn't match any known schema, then disregard the data
// Assign the data to the stored object
this.object = data;
this.store();
this.gmStore();
}
/**Gets the stored object */
get(): Schemas | undefined{
@ -41,7 +50,9 @@ export default class DataManager {
return this.type;
}
/** Converts the current object in the format of any schema into the format of Charity's schema */
/** Converts the current object in the format of any schema into the format of Charity's schema
* @since 0.1.0-overhaul
*/
toCharitySchema(){
if(this.type === "N/A" || this.type === "CHA"){ return } // If the schema type is unknown or already correct, don't do any conversions
@ -76,7 +87,9 @@ export default class DataManager {
this.type = "CHA"; // Update the type to match
}
/** Converts the current object in the format of any schema into the format of Blue Marble's schema */
/** Converts the current object in the format of any schema into the format of Blue Marble's schema
* @since 0.1.0-overhaul
*/
toBlueMarbleSchema(){
if(this.type === "N/A" || this.type === "BM"){ return } // If the schema type is unknown or already correct, don't do any conversions
@ -104,6 +117,7 @@ export default class DataManager {
/** Appends non-meta data from the provided object into the stored object
* @param {Schemas} object Object from which the appended data is taken
* @since 0.1.0-overhaul
*/
appendData(object: Schemas){
@ -111,7 +125,7 @@ export default class DataManager {
this.object = this.object as TBlueMarbleJSON
// If the provided object is in Charity's format
if(CharityJSON.parse(object)){
if(CharityJSON.safeParse(object).success){
// Then we have to convert the template data to Blue Marble's format and then append the data
object = object as TCharityJSON;
this.object.templates.push(...object.templates.map((template)=>({
@ -126,7 +140,7 @@ export default class DataManager {
}
// If the object is already in Blue Marble's format
else if(BlueMarbleJSON.parse(object)){
else if(BlueMarbleJSON.safeParse(object).success){
// Then just append the data, no format change necessary
object = object as TBlueMarbleJSON
this.object.templates.push(...object.templates);
@ -134,20 +148,26 @@ export default class DataManager {
else if(object.links){ this.object.links.push(...object.links) } // Check if appended data has a links array, otherwise the spread operator might cause errors or undefined behaviour
}
this.store();
this.gmStore();
}
appendDataFromURL(data: TBlueMarbleJSON, url: string) {
if(this.type !== "BM") { return };
/**Appends template data gotten from a URL, appropriately marking the orign link / url on each of the templates
* @param {TBlueMarbleJSON} data The data gotten from the URL
* @param {string} url The URL from which the data came from
* @since 0.1.0-overhaul
*/
appendTemplateDataFromURL(data: TBlueMarbleJSON, url: string) {
if(this.type !== "BM") { return }; // Only append object if the stored object is in Blue Marble's format
this.object = this.object as TBlueMarbleJSON
const importedTemplates = data.templates;
// Set the template origins
// Set the template origin links
importedTemplates?.map((template: TBlueMarbleTemplate) => ({ ...template, originLink: url }));
const templatesCopy = this.object.templates;
let removed = 0;
let removed = 0; // Removed counter to not mess up indexing
// Append the templates while keeping draw order
for(const [i, template] of this.object.templates.entries()){
if(importedTemplates.length === 0){ break; }
if(template.originLink !== url){ continue }
@ -160,7 +180,7 @@ export default class DataManager {
}else{
// If the imported data doesn't have this template, remove it from the templates list
templatesCopy.splice(removed+i, 1)
removed++; // Add a removed counter to not mess up indexing
removed++;
}
}
@ -172,22 +192,24 @@ export default class DataManager {
// Update the stored object's templates with the new array
this.object.templates = templatesCopy;
this.store();
this.gmStore();
}
/**Appends the provided template to the list of templates.
* @param {TBlueMarbleTemplate} template The template data that is appended.
* @since 0.1.0-overhaul
*/
addTemplate(template: TBlueMarbleTemplate){
if(this.type !== "BM"){ return } // Only append if the stored object is in Blue Marble format
(this.object as TBlueMarbleJSON).templates.push(template);
this.store();
this.gmStore();
}
/**Appends the provided link object to the list of links. If the links object doesn't exist, then creates it
* @param {TBlueMarbleLink} link The link object data that is appended.
* @since 0.1.0-overhaul
*/
addLink(link: TBlueMarbleLink){
@ -201,13 +223,14 @@ export default class DataManager {
this.object.links.push(link);
this.store();
this.gmStore();
}
/** Takes in paramaters used to modify or filter data to create a desired subset of data, ready to be exported
* @param {number[] | undefined} templateIndexes A list of indexes representing which templates to export, if omitted exports all templates, if empty exports none
* @param {number[] | undefined} linkIndexes A list of indexes representing which links to export, if omitted exports all links, if empty exports none
* @returns {Schemas | undefined} An object matching the provided parameters. If the stored object is undefined then so is the returned value
* @since 0.1.0-overhaul
*/
getExportableData(templateIndexes?: number[], linkIndexes?: number[]): Schemas | null{
@ -253,7 +276,10 @@ export default class DataManager {
}
}
store(){
/**Stores the current object in local GM storage
* @since 0.1.0-overhaul
*/
gmStore(){
if(this.type !== "BM"){ return; }
this.object = this.object as TBlueMarbleJSON
// Store logic
@ -262,6 +288,8 @@ export default class DataManager {
/** Converts an object in the format of any schema into the format of Blue Marble's schema
* @param {Schemas} object The object that is converted
* @returns The object converted into Blue Marble's schema
* @since 0.1.0-overhaul
*/
function toBlueMarbleSchema(object: Schemas): TBlueMarbleJSON{
@ -290,6 +318,8 @@ function toBlueMarbleSchema(object: Schemas): TBlueMarbleJSON{
/** Converts object in the format of any schema into the format of Charity's schema
* @param {Schemas} object The object that is converted.
* @returns The object converted into the shared JSON's schema
* @since 0.1.0-overhaul
*/
function toCharitySchema(object: Schemas): TCharityJSON{

View file

@ -33,7 +33,7 @@ export const dataManager = new DataManager(storageData);
export const uiManager = new UIManager();
const params = new URLSearchParams(document.location.search);
const params = new URLSearchParams(document.location.search); // Gets the url search query
if (params.has("bmShare")) {
try {
const json = JSON.parse(params.get("bmShare")!)
@ -41,10 +41,12 @@ if (params.has("bmShare")) {
} catch { }
}
// Make sure the stored object is in Blue Marble's JSON format
dataManager.toBlueMarbleSchema();
if(dataManager.getType() !== "BM"){
dataManager.update(EMPTY_BLUE_MARBLE_JSON)
}
else if((dataManager.get() as TBlueMarbleJSON).links){
(dataManager.get() as TBlueMarbleJSON).links?.forEach(link => {
// Imports data from every URL in the stored object
@ -58,10 +60,12 @@ initManageLinks();
initCreateTemplate();
initMainOverlay();
/** Fetches data from a url and then updates the object stored in dataManager appropriately */
/** Fetches data from a url and then updates the object stored in dataManager appropriately
* @since 0.1.0-overhaul
*/
function importFromURL(url: string){
const data = EMPTY_BLUE_MARBLE_JSON; // data should be the data fetched from the URL
dataManager.appendDataFromURL(data, url);
dataManager.appendTemplateDataFromURL(data, url);
}

View file

@ -1,17 +1,31 @@
import { uiManager } from "./main";
// Typescript / Javascript for the "manageTemplates" window
/**Closes this window
* @since 0.1.0-overhaul
*/
function close(){
uiManager.close("bm-main-overlay")
}
/**Opens the "manage templates" window
* @since 0.1.0-overhaul
*/
function openManTemplates(){
uiManager.open("bm-manage-templates")
}
/**Opens the "manage links" window
* @since 0.1.0-overhaul
*/
function openManLinks(){
uiManager.open("bm-manage-links")
}
/**Initialises this window's UI-related javascript (addEventListener hooks, ect)
* @since 0.1.0-overhaul
*/
export function initMainOverlay(){
// Add event listener hooks
}

View file

@ -3,10 +3,20 @@ import { dataManager, uiManager } from "./main";
import { download } from "./utils";
import { TBlueMarbleJSON } from "../types/schemas";
// Typescript / Javascript for the "manageTemplates" window
let exportTemplateIndexes: number[] = []
/**Closes this window
* @since 0.1.0-overhaul
*/
function close(){
uiManager.close("bm-manage-links")
}
/**Creates the rows in the table and populates them with data from the stored object
* @since 0.1.0-overhaul
*/
function createTableRows(){
let tableBody = document.querySelector("#bm-manage-links table");
@ -20,74 +30,107 @@ function createTableRows(){
}
}
/**Updates the link stored in the stored object with the one in the input
* @since 0.1.0-overhaul
*/
function save(){
// Try to get the URL input and check if it exists
let urlInput = document.querySelector("#bm-manage-links #url");
if(!urlInput || urlInput.nodeName.toLocaleUpperCase() !== "INPUT"){ return };
try{
const url = new URL((urlInput as HTMLInputElement).value);
const url = new URL((urlInput as HTMLInputElement).value); // Check if is valid URL
dataManager.addLink({ url: url.toString() })
reload()
updateUI()
}catch(err){
window.charity.lib.sonner.toast.error("The URL provided is not a valid URL")
charity.lib.sonner.toast.error("The URL provided is not a valid URL")
}
}
/**Disregards the link in the input and resets it back to the one in the stored object
*
* Resets to an empty field if no link exists
* @since 0.1.0-overhaul
*/
function cancel(){
// Try to get the URL input and check if it exists
let urlInput = document.querySelector("#bm-manage-links #url");
if(!urlInput || urlInput.nodeName.toLocaleUpperCase() !== "INPUT"){ return };
const temp = (dataManager.get() as TBlueMarbleJSON)
if(dataManager.getType() === "BM" && temp.links && temp.links.length > 0){
(urlInput as HTMLInputElement).value = temp.links[0].url;
const data = (dataManager.get() as TBlueMarbleJSON) // temp variable to shorten the below code
if(dataManager.getType() === "BM" && data.links && data.links.length > 0){
(urlInput as HTMLInputElement).value = data.links[0].url; // Currently only support for one url
}else{
(urlInput as HTMLInputElement).value = "";
}
}
let exportTemplateIndexes: number[] = []
function exportToggle(id: number){
if(exportTemplateIndexes.includes(id)){
exportTemplateIndexes = exportTemplateIndexes.filter((x)=>x===id);
/**Adds or removes the given template from the list of templates exported when clicking "Export selected"
* @param {number} idx Index of the template
* @since 0.1.0-overhaul
*/
function exportToggle(idx: number){
if(exportTemplateIndexes.includes(idx)){
// Remove the template index from the array
exportTemplateIndexes = exportTemplateIndexes.filter((x)=>x===idx);
return;
}
exportTemplateIndexes.push(id)
exportTemplateIndexes.push(idx)
}
function shiftUp(id: number){
/**Moves a template up in draw order / ahead in the stored object's templates array
* @param {number} idx Index of the template
* @since 0.1.0-overhaul
*/
function shiftUp(idx: number){
let temp = dataManager.get() as Schemas
if(id >= temp.templates.length){return}
if(idx >= temp.templates.length){ return }// Bounds checking
[temp.templates[id], temp.templates[id+1]] = [temp.templates[id+1], temp.templates[id]]
// Swap the template of the given index with the one ahead of it
[temp.templates[idx], temp.templates[idx+1]] = [temp.templates[idx+1], temp.templates[idx]]
dataManager.update(temp);
reload()
updateUI()
}
function shiftDown(id: number){
/**Moves a template down in draw order / back in the stored object's templates array
* @param {number} idx Index of the template
* @since 0.1.0-overhaul
*/
function shiftDown(idx: number){
let temp = dataManager.get() as Schemas
if(id >= temp.templates.length){return}
if(idx === 0){ return } // Bounds checking
[temp.templates[id], temp.templates[id-1]] = [temp.templates[id-1], temp.templates[id]]
// Swap the template of the given index with the one before it
[temp.templates[idx], temp.templates[idx-1]] = [temp.templates[idx-1], temp.templates[idx]]
dataManager.update(temp);
reload()
updateUI()
}
/**Triggers a download of a JSON file containing the templates the user previously selected
* @since 0.1.0-overhaul
*/
function exportSelected(){
download(dataManager.getExportableData(exportTemplateIndexes, []))
}
function reload(){
/**Updates the UI of this window
* @since 0.1.0-overhaul
*/
function updateUI(){
exportTemplateIndexes = [];
createTableRows();
// Reload templates
}
/**Initialises this window's UI-related javascript (addEventListener hooks, ect)
* @since 0.1.0-overhaul
*/
export function initManageLinks(){
// Add event listener hooks
}

View file

@ -1,8 +1,15 @@
import { TBlueMarbleJSON } from "../types/schemas";
import { TBlueMarbleJSON, TBlueMarbleTemplate, TCharityTemplate } from "../types/schemas";
import { Schemas } from "../types/types";
import { dataManager, uiManager } from "./main";
import { createElementWithAttributes, download } from "./utils";
// Typescript / Javascript for the "manageTemplates" window
let exportTemplateIndexes: number[] = []
/**Closes this window
* @since 0.1.0-overhaul
*/
function close(){
uiManager.close("bm-manage-templates")
}
@ -12,24 +19,24 @@ if(tableBody && tableBody.nodeName.toLocaleUpperCase() === "TABLE"){
tableBody = tableBody.children[0];
if(tableBody && dataManager.getType() === "BM"){
(dataManager.get() as TBlueMarbleJSON).templates.forEach((template, i)=>{
const row1 = createElementWithAttributes("tr", {id: "main-row"});
const row1 = createElementWithAttributes("tr", {idx: "main-row"});
const td1 = createElementWithAttributes("td", {textContent: i.toString()});
const td2 = createElementWithAttributes("td", {textContent: template.name || "unnamed-0"});
const td3 = document.createElement("td");
const buttonEnable = createElementWithAttributes("button", {id: "enable"});
const buttonEnable = createElementWithAttributes("button", {idx: "enable"});
td3.appendChild(buttonEnable);
const td4 = document.createElement("td");
const buttonExportAdd = createElementWithAttributes("button", {id: "exportAdd"});
const buttonExportAdd = createElementWithAttributes("button", {idx: "exportAdd"});
td4.appendChild(buttonExportAdd);
const td5 = document.createElement("td");
const shiftsContainer = createElementWithAttributes("div", {className: "shifts-container"});
const buttonUp = createElementWithAttributes("button", {id: "up"});
const buttonDown = createElementWithAttributes("button", {id: "down"});
const buttonUp = createElementWithAttributes("button", {idx: "up"});
const buttonDown = createElementWithAttributes("button", {idx: "down"});
shiftsContainer.append(buttonUp, buttonDown);
td5.appendChild(shiftsContainer);
row1.append(td1, td2, td3, td4, td5)
}
// <tr id="">
// <tr idx="">
// <td>0</td>
// <td>NameTest</td>
// <td><button>on</button></td>
@ -56,6 +63,9 @@ if(tableBody && tableBody.nodeName.toLocaleUpperCase() === "TABLE"){
}
}
/**Creates the rows in the table and populates them with data from the stored object
* @since 0.1.0-overhaul
*/
function createTableRows(){
let tableBody = document.querySelector("#bm-manage-templates table");
@ -68,81 +78,128 @@ function createTableRows(){
}
}
let exportTemplateIndexes: number[] = []
function enabledToggle(id: number){
/**Toggles the enabled state a template of given index
* @param {number} idx Index of the template
* @since 0.1.0-overhaul
*/
function enabledToggle(idx: number){
let temp = dataManager.get() as Schemas
temp = {
...temp,
templates: temp.templates.map((template, i)=> i === id ? {...template, enabled: !template.enabled} : template)
}
temp.templates[idx].enabled = !(temp.templates[idx].enabled);
dataManager.update(temp)
// Enable / disable template in overlay
}
function exportToggle(id: number){
if(exportTemplateIndexes.includes(id)){
exportTemplateIndexes = exportTemplateIndexes.filter((x)=>x===id);
/**Adds or removes the given template from the list of templates exported when clicking "Export selected"
* @param {number} idx Index of the template
* @since 0.1.0-overhaul
*/
function exportToggle(idx: number){
if(exportTemplateIndexes.includes(idx)){
// Removes the template index from the array
exportTemplateIndexes = exportTemplateIndexes.filter((x)=>x===idx);
return;
}
exportTemplateIndexes.push(id)
exportTemplateIndexes.push(idx)
}
function shareTemplate(id: number){
/**Creates a one-click shareable link for the given template and copies it to the clipboard
* @param {number} idx Index of the template
* @since 0.1.0-overhaul
*/
function shareTemplate(idx: number){
try{
const string = JSON.stringify(dataManager.getExportableData([id], []));
const windowURL = window.location.origin + window.location.pathname;
const stringifedData = JSON.stringify(dataManager.getExportableData([idx], []));
const currentURL = window.location.origin + window.location.pathname; // Get the current URL
const searchParams = new URLSearchParams();
searchParams.set("bmShare", string);
navigator.clipboard.writeText(windowURL+searchParams.toString());
window.charity.lib.sonner.toast.success("Share link copied to clipboard")
// Add the template location to the search params
// Add the data to the search params
searchParams.set("bmShare", stringifedData);
navigator.clipboard.writeText(currentURL+searchParams.toString()); // Copy the link to the clipboard
charity.lib.sonner.toast.success("Share link copied to clipboard")
}
catch{ window.charity.lib.sonner.toast.error("Creating share link failed") }
catch{ charity.lib.sonner.toast.error("Creating share link failed") }
}
function deleteTemplate(id: number){
/**Removes a template of the given index from the stored object
* @param {number} idx Index of the template
* @since 0.1.0-overhaul
*/
function deleteTemplate(idx: number){
let temp = dataManager.get() as Schemas
temp.templates.splice(id,1);
temp.templates.splice(idx,1); // Delete the template
dataManager.update(temp)
reload()
updateUI()
}
function shiftUp(id: number){
/**Moves a template up in draw order / ahead in the stored object's templates array
* @param {number} idx Index of the template
* @since 0.1.0-overhaul
*/
function shiftUp(idx: number){
let temp = dataManager.get() as Schemas
if(id >= temp.templates.length){return}
if(idx >= temp.templates.length){ return } // Bounds checking
[temp.templates[id], temp.templates[id+1]] = [temp.templates[id+1], temp.templates[id]]
// Swap the template of the given index with the one ahead of it
[temp.templates[idx], temp.templates[idx+1]] = [temp.templates[idx+1], temp.templates[idx]]
dataManager.update(temp);
reload()
updateUI()
}
function shiftDown(id: number){
/**Moves a template down in draw order / back in the stored object's templates array
* @param {number} idx Index of the template
* @since 0.1.0-overhaul
*/
function shiftDown(idx: number){
let temp = dataManager.get() as Schemas
if(id >= temp.templates.length){return}
if(idx === 0){ return } // Bounds checking
[temp.templates[id], temp.templates[id-1]] = [temp.templates[id-1], temp.templates[id]]
// Swap the template of the given index with the one before it
[temp.templates[idx], temp.templates[idx-1]] = [temp.templates[idx-1], temp.templates[idx]]
dataManager.update(temp);
reload()
updateUI()
}
/**Triggers a download of a JSON file containing all the user's templates
* @since 0.1.0-overhaul
*/
function exportAll(){
if(dataManager.getType() !== "BM"){ return }
download(dataManager.getExportableData(undefined, []))
}
/**Triggers a download of a JSON file containing the templates the user previously selected
* @since 0.1.0-overhaul
*/
function exportSelected(){
download(dataManager.getExportableData(exportTemplateIndexes, []))
}
function reload(){
/**Updates the UI of this window
* @since 0.1.0-overhaul
*/
function updateUI(){
exportTemplateIndexes = [];
createTableRows();
// Reload templates
}
/**Initialises this window's UI-related javascript (addEventListener hooks, ect)
* @since 0.1.0-overhaul
*/
export function initManageTemplates(){
// Add event listener hooks

View file

@ -1,13 +1,23 @@
import { WindowNames } from "../types/types";
/**A map of window names to display values. This value is used to set the "open" state of a window
* @since 0.1.0-overhaul
*/
const DISPLAY_DEFAULTS: Record<WindowNames, string> = {
"bm-create-template": "flex",
"bm-main-overlay": "flex",
"bm-manage-links": "flex",
"bm-manage-templates": "flex"
}
/**A manager that handles certain UI changes. Mainly opening and closing windows
* @since 0.1.0-overhaul
*/
export default class UIManager{
// Functions that update the UI (reinsert data, recreate table rows, ect)
/** Functions that update the UI of a given window (reinsert data, recreate table rows, ect)
* @since 0.1.0-overhaul
*/
updateFunctions: Record<WindowNames, Function | undefined> = {
"bm-create-template": undefined,
"bm-main-overlay": undefined,
@ -15,19 +25,26 @@ export default class UIManager{
"bm-manage-templates": undefined
}
/**Hides the "window" with the corresponding id */
/**Hides the "window" with the corresponding id
* @param {WindowNames} windowName name / id of the window to hide
* @since 0.1.0-overhaul
*/
close(windowName : WindowNames){
const bmWindow = document.querySelector("#"+windowName);
if(bmWindow){ (bmWindow as HTMLElement).style.display = "none" }
}
/**Displays the "window" with the corresponding id */
/**Displays the "window" with the corresponding id
* @param {WindowNames} windowName name / id of the window to display
* @since 0.1.0-overhaul
*/
open(windowName: WindowNames){
const bmWindow = document.querySelector("#"+windowName);
if(bmWindow){
(bmWindow as HTMLElement).style.display = DISPLAY_DEFAULTS[windowName]
this.updateFunctions[windowName] && this.updateFunctions[windowName]() // If the window has a UI update function, update the UI when opening the window
// If the window has a UI update function, call it when opening the window
this.updateFunctions[windowName] && this.updateFunctions[windowName]()
}
}
}

View file

@ -1,5 +1,11 @@
import { dataManager } from "./main";
/**Invokes a document.createElement call with the provided tagname, then adds the provided attributes to the created elements
* @param {keyof HTMLElementTagNameMap} tagName Name that dictates what type of element to create
* @param {Record<string, string>} attributes A map of attribute name to attribute to value. The key "className" converts to "class"
* @returns The created element with the added attributes
* @since 0.1.0-overhaul
*/
export function createElementWithAttributes<T extends keyof HTMLElementTagNameMap>(tagName: T, attributes?: Record<string, string>): HTMLElement{
const elem = document.createElement(tagName);
@ -13,6 +19,10 @@ export function createElementWithAttributes<T extends keyof HTMLElementTagNameMa
return elem;
}
/**Prompts a download of the provided data in the browser
* @param {any} data The data that is downloaded. This can be any JSON-stringifiable data
* @since 0.1.0-overhaul
*/
export function download(data: any){
try {
@ -29,12 +39,17 @@ export function download(data: any){
}
}
/**Generates a UUID, unique to this user
* @returns The generated UUID
* @since 0.1.0-overhaul
*/
export function generateUUID(): string{
let uuid = crypto.randomUUID();
const userID = window.charity.game.user.data?.id
if(dataManager.get()){
while(dataManager.get()?.templates.some((template)=>{template.authorID==userID && template.uuid==uuid})){
const userID = charity.game.user.data?.id
if(dataManager.getType() !== "N/A"){
// Make sure that no template shares this UUID and this user's ID
while(dataManager.get()?.templates.some((template: any)=>{template.authorID==userID && template.uuid==uuid})){
uuid = crypto.randomUUID()
}
}

View file

@ -3,22 +3,10 @@ import z from "zod/v3";
// "^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$" is Regex to only match a version in semver compliant format
export const CharityJSON = z.object({
meta: z.object({
whoami: z.literal('BlueCharityPro'), // Identifies the type of JSON format
schemaVersion: z.string().refine((version) => version.match("^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$")),
scriptVersion: z
.string()
.refine((version) => version.match("^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"))
.optional(),
}),
// Which alliance was this JSON created by
alliance: z.object({
name: z.string(),
contact: z.string(),
}).optional(),
templates: z.array(
z.object({
/**Schema for the chariy framework's shared JSON template type
* @since 0.1.0-overhaul
*/
export const CharityTemplate = z.object({
name: z.string().optional(), // Name of the template
enabled: z.boolean(),
// Location of the template
@ -46,7 +34,25 @@ export const CharityJSON = z.object({
.optional(),
uuid: z.string(), // UUID to distinguish templates made by the same author
})
),
/**Schema matching the shared JSON type used in the charity framework
* @since 0.1.0-overhaul
*/
export const CharityJSON = z.object({
meta: z.object({
whoami: z.literal('BlueCharityPro'), // Identifies the type of JSON format
schemaVersion: z.string().refine((version) => version.match("^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$")),
scriptVersion: z
.string()
.refine((version) => version.match("^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"))
.optional(),
}),
// Which alliance was this JSON created by
alliance: z.object({
name: z.string(),
contact: z.string(),
}).optional(),
templates: z.array(CharityTemplate),
whitelist: z.array(
z.object({
name: z.string().optional(),
@ -61,6 +67,9 @@ export const CharityJSON = z.object({
),
});
/**Schema for Blue Marble's template type
* @since 0.1.0-overhaul
*/
export const BlueMarbleTemplate = z.object({
name: z.string().optional(), // Name of the template
coords: z.array(z.number()), // 4 element array containing the location of the template
@ -71,11 +80,17 @@ export const BlueMarbleTemplate = z.object({
uuid: z.string(), // UUID to distinguish templates made by the same author
})
/**Schema for Blue Marble's link object type
* @since 0.1.0-overhaul
*/
export const BlueMarbleLink = z.object({
name: z.string().optional(), // Name of URL
url: z.string(),
})
/**Schema for Blue Marble's full JSON type
* @since 0.1.0-overhaul
*/
export const BlueMarbleJSON = z.object({
whoami: z.string().refine((whoami)=>whoami.includes("BlueMarble")), // Identifies the type of JSON format
scriptVersion: z
@ -86,15 +101,33 @@ export const BlueMarbleJSON = z.object({
templates: z.array(BlueMarbleTemplate),
links: z.array(BlueMarbleLink).optional(),
})
export type TCharityJSON = z.infer<typeof CharityJSON>; // Creates a type matching the schema for better typesafety
export type TBlueMarbleJSON = z.infer<typeof BlueMarbleJSON>; // Creates a type matching the schema for better typesafety
export type TBlueMarbleTemplate = z.infer<typeof BlueMarbleTemplate>; // Creates a type matching the schema for better typesafety
export type TBlueMarbleLink = z.infer<typeof BlueMarbleLink>; // Creates a type matching the schema for better typesafety
/** A type matching the shared JSON's schema
* @since 0.1.0-overhaul
*/
export type TCharityJSON = z.infer<typeof CharityJSON>;
/** A type matching the shared JSON's template schema
* @since 0.1.0-overhaul
*/
export type TCharityTemplate = z.infer<typeof CharityTemplate>;
/** A type matching Blue Marble's JSON schema
* @since 0.1.0-overhaul
*/
export type TBlueMarbleJSON = z.infer<typeof BlueMarbleJSON>;
/** A type matching Blue Marble's template schema
* @since 0.1.0-overhaul
*/
export type TBlueMarbleTemplate = z.infer<typeof BlueMarbleTemplate>;
/** A type matching Blue Marble's link object schema
* @since 0.1.0-overhaul
*/
export type TBlueMarbleLink = z.infer<typeof BlueMarbleLink>;
export const CHA_SCHEMA_VERSION = "0.1.0"
export const BM_SCHEMA_VERSION = "0.1.0"
/**A Blue Marble JSON object with no real data
* @since 0.1.0-overhaul
*/
export const EMPTY_BLUE_MARBLE_JSON: TBlueMarbleJSON = {
whoami: "BlueMarble",
schemaVersion: BM_SCHEMA_VERSION,