From 3fdaadf7e01c365cebfc534c4001189661ef70b0 Mon Sep 17 00:00:00 2001
From: Anthony Correa
Date: Thu, 23 Jan 2025 10:24:54 -0600
Subject: [PATCH] Add customizable tags and categories with UI management
- Introduced settings for tags and categories in `settings.js`:
- Added `tags` and `categories` settings with default values and support for customization.
- Registered a new settings menu for managing tags and categories dynamically.
- Enhanced `main.js` to use the customizable tags and categories in templates.
- Adjusted `AscAssetManager.TEMPLATES` to include a new `SETTINGS_TAGS_AND_CATEGORIES` template.
- Updated the `renderUploadForm` hook to fetch tags and categories dynamically from settings.
- Added a new Handlebars template `settings-tags-and-categories.hbs`:
- Provides a user interface for managing tags and categories.
- Includes functionality to add, reset, and delete rows dynamically.
These updates allow users to define and manage tags and categories via the module's settings menu, improving flexibility and user experience.
---
src/main.js | 9 +-
src/settings.js | 181 +++++++++++++++++-
.../settings-tags-and-categories.hbs | 44 +++++
3 files changed, 231 insertions(+), 3 deletions(-)
create mode 100644 src/templates/settings-tags-and-categories.hbs
diff --git a/src/main.js b/src/main.js
index ac360fd..27dd614 100644
--- a/src/main.js
+++ b/src/main.js
@@ -8,7 +8,8 @@ export class AscAssetManager {
static TEMPLATES = {
UPLOAD_CHOOSE:`modules/${this.ID}/templates/upload-choose.hbs`,
UPLOAD_FORM:`modules/${this.ID}/templates/upload-form.hbs`,
- SETTINGS_MENU_MACRO:`modules/${this.ID}/templates/settings-menu-macro.hbs`
+ SETTINGS_MENU_MACRO:`modules/${this.ID}/templates/settings-menu-macro.hbs`,
+ SETTINGS_TAGS_AND_CATEGORIES:`modules/${this.ID}/templates/settings-tags-and-categories.hbs`
}
static getDirectory () {
@@ -148,7 +149,11 @@ Hooks.on("renderHotbar", ({macros}, html) => {
});
Hooks.on("ascAssetManager.renderUploadForm", (data={})=>{
- const templateData = {moduleId: AscAssetManager.ID, fileCategories: AscAssetManager.fileCategories, fileTags: AscAssetManager.fileTags}
+ const templateData = {
+ moduleId: AscAssetManager.ID,
+ fileCategories: game.settings.get(AscAssetManager.ID, "categories"),
+ fileTags: game.settings.get(AscAssetManager.ID, "tags")
+ }
renderTemplate(AscAssetManager.TEMPLATES.UPLOAD_FORM, templateData).then(content => {
let {file} = data
const dialog = new Dialog({
diff --git a/src/settings.js b/src/settings.js
index 76c3561..3dc09bd 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -16,6 +16,41 @@ export function registerSettings(AscAssetManager) {
}
});
+ // Register default tags setting
+ game.settings.register(ID, "tags", {
+ name: "Tags",
+ hint: "A list of tags to use in the module.",
+ scope: "world", // "world" means the setting is shared across all users; "client" means it's per-user
+ config: false, // Whether the setting shows up in the settings menu
+ type: Object, // Data type
+ default: [
+ {id:"tk", label: "Token"},
+ {id:"sq", label: "Square"}
+ ], // Default tags
+ onChange: (value) => console.log("Tags updated to:", value) // Optional: Triggered when setting is updated
+ });
+
+ // Register default categories setting
+ game.settings.register(ID, "categories", {
+ name: "Categories",
+ hint: "A list of categories to use in the module.",
+ scope: "world",
+ config: false,
+ type: Object,
+ default: [
+ {id: "npcn", label: "NPC (Named)"},
+ {id: "npcu", label: "NPC (Unnamed)"},
+ {id: "scene", label: "Scene Background"},
+ {id: "pc", label: "PC"},
+ {id: "inset", label: "Inset"},
+ {id: "vehicle", label: "Vehicle"},
+ {id: "weapon", label: "Weapon"},
+ {id: "icon", label: "Icon"},
+ {id: "map", label: "Map"}
+ ],
+ onChange: (value) => console.log("Categories updated to:", value)
+ });
+
// Register the "customMessage" setting
game.settings.register(ID, "rootDirectory", {
name: "Root Directory",
@@ -38,7 +73,7 @@ export function registerSettings(AscAssetManager) {
config: true,
type: class extends FormApplication {
static get defaultOptions() {
- return mergeObject(super.defaultOptions, {
+ return foundry.utils.FormApplicationmergeObject(super.defaultOptions, {
id: ID, // Unique ID for the application
title: "Simple Form Application", // Title of the window
template: AscAssetManager.TEMPLATES.SETTINGS_MENU_MACRO, // Path to your Handlebars template
@@ -55,4 +90,148 @@ export function registerSettings(AscAssetManager) {
}
},
});
+
+ game.settings.registerMenu(ID, "categories", {
+ name: "Set Tags and Categories",
+ label: "Set Tags and Categories",
+ scope: "world",
+ icon: "fas fa-list",
+ config: true,
+ type: class extends FormApplication {
+ static get defaultOptions() {
+ return foundry.utils.mergeObject(super.defaultOptions, {
+ id: ID, // Unique ID for the application
+ title: "Simple Form Application", // Title of the window
+ template: AscAssetManager.TEMPLATES.SETTINGS_TAGS_AND_CATEGORIES, // Path to your Handlebars template
+ width: 300, // Width of the form
+ height: "auto", // Adjust height automatically
+ });
+ }
+ getData() {
+ return {
+ moduleId: ID,
+ categories: game.settings.get(ID, "categories"),
+ tags: game.settings.get(ID, "tags")
+ }
+ }
+ activateListeners(html) {
+ super.activateListeners(html);
+
+ function addRow (html) {
+ // Find the nearest parent div
+ const parentDiv = html.closest("div:has(table)");
+
+ // Find the last row in the body
+ const lastRow = parentDiv.find("tbody tr:last");
+
+ // Clone the last row
+ const newRow = lastRow.clone();
+
+ // Loop through each input in the new row to reset values and update names/ids
+ newRow.find("input").each(function () {
+ const input = $(this);
+
+ // Reset the input value
+ input.val("");
+
+ // Update the name and id to n+1
+ const name = input.attr("name");
+ const id = input.attr("id");
+
+ if (name) {
+ const match = name.match(/(\d+)/); // Find the index in the name
+ if (match) {
+ const index = parseInt(match[1], 10) + 1;
+ input.attr("name", name.replace(/\d+/, index));
+ }
+ }
+
+ if (id) {
+ const match = id.match(/(\d+)/); // Find the index in the id
+ if (match) {
+ const index = parseInt(match[1], 10) + 1;
+ input.attr("id", id.replace(/\d+/, index));
+ }
+ }
+ });
+
+ // Append the new row to the table
+ parentDiv.find("tbody").append(newRow);
+ }
+
+ html.find('#add-category, #add-tag').click((evt)=>{
+ evt.preventDefault()
+ addRow($(evt.currentTarget));
+ })
+ html.find('#reset-category, #reset-tag').click((evt)=>{
+ evt.preventDefault()
+ let data
+ let item_id
+ if (evt.currentTarget.id == "reset-category"){
+ item_id = "category"
+ data = game.settings.settings.get(`${ID}.categories`).default
+ } else if (evt.currentTarget.id == "reset-tag") {
+ item_id = "tag"
+ data = game.settings.settings.get(`${ID}.tags`).default
+ }
+ const tbody = $(evt.currentTarget).closest('div:has(table)').find("tbody")
+ tbody.empty();
+ for (let [idx, {id, label}] of data.entries()){
+ const newRow = $(`
`)
+ newRow.append(`
| `)
+ newRow.append(` | `)
+ newRow.append(` | `)
+ tbody.append(newRow)
+ }
+ })
+ html.find('.delete-row').click((evt)=>{
+ evt.preventDefault();
+ const button = $(evt.currentTarget)
+ button.closest('tr').remove()
+ })
+ }
+
+ async _updateObject(event, formData) {
+ // Save updates when the form is submitted
+
+ const categories = [];
+ const tags = [];
+
+ // Dynamically group rows for categories and tags
+ for (const [key, value] of Object.entries(formData)) {
+ // Match category rows
+ const categoryMatch = key.match(/^category-(id|label)-(\d+)$/);
+ if (categoryMatch) {
+ const [_, field, index] = categoryMatch; // Extract field (id/label) and index
+ const i = parseInt(index);
+
+ // Ensure the index exists in the categories array
+ if (!categories[i]) categories[i] = { id: "", label: "" };
+ categories[i][field] = value;
+ continue;
+ }
+
+ // Match tag rows
+ const tagMatch = key.match(/^tag-(id|label)-(\d+)$/);
+ if (tagMatch) {
+ const [_, field, index] = tagMatch; // Extract field (id/label) and index
+ const i = parseInt(index);
+
+ // Ensure the index exists in the tags array
+ if (!tags[i]) tags[i] = { id: "", label: "" };
+ tags[i][field] = value;
+ continue;
+ }
+ }
+
+ await game.settings.set(ID, "tags", tags);
+ await game.settings.set(ID, "categories", categories);
+
+ ui.notifications.info("Settings updated!");
+ }
+ },
+ });
+
+ game
+
}
\ No newline at end of file
diff --git a/src/templates/settings-tags-and-categories.hbs b/src/templates/settings-tags-and-categories.hbs
new file mode 100644
index 0000000..232d97c
--- /dev/null
+++ b/src/templates/settings-tags-and-categories.hbs
@@ -0,0 +1,44 @@
+{{#*inline "table"}}
+
+
+
+
+
+{{/inline}}
+
\ No newline at end of file