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"}} + + + + + + + + + + + {{#each collection}} + + + + + + {{else}} + + + + + + {{/each}} + +
{{collection_title}}
IDLabel
+
+ + +
+{{/inline}} +
+
+
+ {{> table collection_title="Categories" item_title="Category" collection=categories item_id="category"}} +
+ +
+ {{> table collection_title="Tags" item_title="Tag" collection=tags item_id="tag"}} +
+
+ +
+
\ No newline at end of file