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.
This commit is contained in:
@@ -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({
|
||||
|
||||
181
src/settings.js
181
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 = $(`<tr></tr>`)
|
||||
newRow.append(`<td><input type="text" name="${item_id}-id-${idx}" value="${id}"></input></td>`)
|
||||
newRow.append(`<td><input type="text" name="${item_id}-label-${idx}" value="${label}"></input></td>`)
|
||||
newRow.append(`<td><button id="delete-row-${idx}" class="delete-row"><i class="delete-row fa-solid fa-trash-can"></i></button></td>`)
|
||||
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
|
||||
|
||||
}
|
||||
44
src/templates/settings-tags-and-categories.hbs
Normal file
44
src/templates/settings-tags-and-categories.hbs
Normal file
@@ -0,0 +1,44 @@
|
||||
{{#*inline "table"}}
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th colspan=3>{{collection_title}}</th></tr>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Label</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each collection}}
|
||||
<tr>
|
||||
<td><input type="text" name="{{item_id}}-id-{{@index}}" value="{{id}}"></input></td>
|
||||
<td><input type="text" name="{{item_id}}-label-{{@index}}" value="{{label}}"></input></td>
|
||||
<td><button id="delete-row-{{@index}}" class="delete-row"><i class="delete-row fa-solid fa-trash-can"></i></button></td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr>
|
||||
<td><input type="text" name="{{item_id}}-id-0" value="" placeholder="id"></input></td>
|
||||
<td><input type="text" name="{{item_id}}-label-0" value="" placeholder="label"></input></td>
|
||||
<td><button id="delete-row-0" class="delete-row"><i class="delete-row fa-solid fa-trash-can"></i></button></td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="dialog-buttons">
|
||||
<button id="add-{{item_id}}" class="dialog-button"><i class="fa-solid fa-plus"></i>Add {{item_title}}</button>
|
||||
<button id="reset-{{item_id}}" class="dialog-button"><i class="fa-solid fa-arrow-rotate-left"></i>Reset {{collection_ttitle}}</button>
|
||||
</div>
|
||||
{{/inline}}
|
||||
<div class="{{moduleId}}">
|
||||
<form id="form-categories-and-tags">
|
||||
<div>
|
||||
{{> table collection_title="Categories" item_title="Category" collection=categories item_id="category"}}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{> table collection_title="Tags" item_title="Tag" collection=tags item_id="tag"}}
|
||||
</div>
|
||||
<hr>
|
||||
<button id="save" class="dialog-button"><i class="fa-solid fa-check"></i> Save</button>
|
||||
</form>
|
||||
</div>
|
||||
Reference in New Issue
Block a user