mirror of
https://github.com/SwingTheVine/Wplace-BlueMarble.git
synced 2026-04-20 10:12:05 +00:00
Code comments, fixes and clean ups
This commit is contained in:
parent
fa5747e74e
commit
dd5e871e72
9 changed files with 383 additions and 128 deletions
|
|
@ -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()})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
||||
|
|
|
|||
10
src/main.ts
10
src/main.ts
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
21
src/utils.ts
21
src/utils.ts
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue