commit aba086d7f0f1575513764f55a63bb54f865287c9 Author: Anthony Correa Date: Sun Jan 19 10:52:44 2025 -0600 Add initial implementation for AscAssetManager module This commit introduces the following key features: - **New `.gitignore`**: - Ignores `.vscode`, `.eslintrc.js`, and `dist/module.zip`. - **Core functionality**: - Created `src/main.js` to implement the `AscAssetManager` class with methods to manage file uploads, categories, tags, and settings registration. - Introduced hooks for rendering and managing custom upload dialogs and forms. - **Configuration and settings**: - Added `src/settings.js` to register settings for features like enabling/disabling, root directory configuration, and theme selection. - **Templates**: - Added `upload-choose.hbs` for the file selection dialog. - Added `upload-form.hbs` for the file metadata customization dialog. - **Utilities**: - Added `src/utils.js` with helper functions for file parsing, metadata handling, and filename creation. - **Styling**: - Added `style.css` for styling upload areas. - **Module metadata**: - Added `module.json` with module details, compatibility, and dependencies. This commit establishes the foundational structure and functionality for the AscAssetManager module. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..633689c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vscode +.eslintrc.js +dist/module.zip diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..5940532 --- /dev/null +++ b/src/main.js @@ -0,0 +1,164 @@ +const LOG_PREFIX = "asc-asset-manager | " + +import { registerSettings } from "./settings.js"; +import { parseFileName, createMetaFileName } from "./utils.js"; + +export class AscAssetManager { + static ID = 'asc-asset-manager' + static TEMPLATES = { + UPLOAD_CHOOSE:`modules/${this.ID}/templates/upload-choose.hbs`, + UPLOAD_FORM:`modules/${this.ID}/templates/upload-form.hbs` + } + + static getDirectory () { + return game.settings.get(this.ID, "rootDirectory") + } + + static fileCategories = [ + {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"} + ] + + static fileTags = [ + {id:"tk", label: "Token"}, + {id:"sq", label: "Square"} + ] + + static log(force, ...args) { + console.log(this.ID, '|', ...args); + } +} + +Hooks.on("ready", function() { + AscAssetManager.log(false, "Deno nodule reody") + registerSettings(AscAssetManager.ID); +}); + +Hooks.on("ascAssetManager.renderUploadChoose", () => { + renderTemplate(AscAssetManager.TEMPLATES.UPLOAD_CHOOSE).then((content)=>{ + new Dialog({ + title: "Choose", + content: content, + buttons: [], + render: (html)=> { + AscAssetManager.log(false, 'Rendering choose window') + const uploadArea = html.find("#upload-area"); + const fileInput = html.find("#file-input"); + const form = html.find("form") + + // Handle drag-and-drop events + uploadArea.on("dragover", (event) => { + event.preventDefault(); + uploadArea.css("background-color", "#e0f7fa"); + }); + + uploadArea.on("dragleave", () => { + uploadArea.css("background-color", ""); + }); + + uploadArea.on("drop", (event) => { + event.preventDefault(); + uploadArea.css("background-color", ""); + const files = event.originalEvent.dataTransfer.files; + if (files.length > 0) { + AscAssetManager.log(false, 'file dropped change') + // Update the file input with the dropped file + const dataTransfer = new DataTransfer(); + for (let file of files) { + dataTransfer.items.add(file); // Add the first dropped file + } + fileInput.files = dataTransfer.files; + fileInput.trigger('change') + } + }); + + fileInput.on("change", (event)=> { + const files = fileInput.files + // Show feedback to the user + uploadArea.find("p").text(`Selected file: ${Array.from(files).map(f=>f.name).join(', ')}`); + + }) + form.on('submit', (event) => { + for (let file of fileInput.files) { + Hooks.callAll("ascAssetManager.renderUploadForm", {file}) + } + }) + + } + }).render(true) + }) +}) + +Hooks.on("ascAssetManager.renderUploadForm", (data)=>{ + const templateData = {fileCategories: AscAssetManager.fileCategories, fileTags: AscAssetManager.fileTags} + renderTemplate(AscAssetManager.TEMPLATES.UPLOAD_FORM, templateData).then(content => { + let {file} = data + new Dialog({ + title: "Simple Popup", + content: content, + buttons: [], + render: (html) =>{ + AscAssetManager.log('file', file) + const uploadArea = html.find("#upload-area"); + const fileInput = html.find("#file-input"); + const form = html.find('#upload-form'); + const namePreview = html.find('#name-preview') + const baseName = form.find("input[id='baseName']") + + fileInput.val(file.name) + + const reader = new FileReader(); + reader.onload = (e) => { + const img = form.find("img"); + const blob = new Blob([e.target.result], { type: file.type }); + const blobUrl = URL.createObjectURL(blob) + AscAssetManager.log(false, blobUrl,img) + img.attr("src", blobUrl) + }; + reader.readAsArrayBuffer(file) + + baseName.val(parseFileName(file.name).fileName) + + // Handle click to open file selector + // uploadArea.on("click", () => fileInput.click()); + form.on('change', (event)=>{ + const fileExtension = parseFileName(fileInput.val()).fileExtension + const fileCategory = form.find("select[name='file-category'] option:checked").val() + const fileTags = form.find("input[name='file-tags']:checked").map(function(){ + return $(this).val() + }).get() + AscAssetManager.log(false, 'form changed', {fileCategory, fileTags, baseName}) + const new_filename = createMetaFileName(`${baseName.val()}.${fileExtension}`,{ + category: fileCategory, + tags: fileTags + }) + namePreview.val(new_filename) + }) + + form.on('submit', (event) => { + AscAssetManager.log(false, "Form Submitted"); + event.preventDefault(); + + const renamedFile = new File([file], namePreview.val(), {type:file.type}) + FilePicker.upload( + "data", + AscAssetManager.getDirectory(), + renamedFile, + { + notify: true + } + ) + .then((res)=>AscAssetManager.log(false, 'Uploaded!', res)) + .catch((e)=>ui.notifications.error('Error uploading', e)) + }) + } + }).render(true); + }) +}) + + diff --git a/src/module.json b/src/module.json new file mode 100644 index 0000000..c63a0cb --- /dev/null +++ b/src/module.json @@ -0,0 +1,21 @@ +{ + "id": "asc-asset-manager", + "title": "asc-asset-manager", + "version": "0.1.0", + "compatibility": { + "minimum": "12", + "verified": "12" + }, + "authors": [ + { + "name": "Anthony Correa", + "flags": {} + } + ], + "esmodules": [ + "./main.js" + ], + "styles": [ + "./styles/style.css" + ] +} \ No newline at end of file diff --git a/src/settings.js b/src/settings.js new file mode 100644 index 0000000..d8fdb69 --- /dev/null +++ b/src/settings.js @@ -0,0 +1,48 @@ +export function registerSettings(ID) { + console.log("Registering settings for My Module..."); + + // Register the "enableFeature" setting + game.settings.register(ID, "enableFeature", { + name: "Enable Feature", + hint: "Enable or disable the special feature of this module.", + scope: "world", + config: true, + type: Boolean, + default: true, + onChange: value => { + console.log(`Enable Feature setting changed to: ${value}`); + } + }); + + // Register the "customMessage" setting + game.settings.register(ID, "rootDirectory", { + name: "Root Directory", + hint: "Root Directory to save files", + scope: "client", + config: true, + type: String, + default: `worlds/${game.data.world.id}/`, + filePicker: "folder", + onChange: value => { + console.log(`Custom Message changed to: ${value}`); + } + }); + + // Register a dropdown setting + game.settings.register(ID, "theme", { + name: "Select Theme", + hint: "Choose a theme for the module.", + scope: "world", + config: true, + type: String, + choices: { + light: "Light Theme", + dark: "Dark Theme", + system: "Use System Default" + }, + default: "system", + onChange: value => { + console.log(`Theme changed to: ${value}`); + } + }); +} \ No newline at end of file diff --git a/src/styles/style.css b/src/styles/style.css new file mode 100644 index 0000000..30dbf29 --- /dev/null +++ b/src/styles/style.css @@ -0,0 +1,7 @@ +#upload-area { + border: 2px dashed #ccc; + padding: 20px; + text-align: center; + cursor: pointer; + background-color: #f9f9f9; +} \ No newline at end of file diff --git a/src/templates/upload-choose.hbs b/src/templates/upload-choose.hbs new file mode 100644 index 0000000..0605c88 --- /dev/null +++ b/src/templates/upload-choose.hbs @@ -0,0 +1,7 @@ +
+
+

Drag files here to upload

+ +
+ +
\ No newline at end of file diff --git a/src/templates/upload-form.hbs b/src/templates/upload-form.hbs new file mode 100644 index 0000000..0027133 --- /dev/null +++ b/src/templates/upload-form.hbs @@ -0,0 +1,41 @@ +
+ + + +
+ +
+ +
+
+ +
+ + +
+ +
+ +
+ {{#each fileTags}} + + {{label}} + {{/each}} +
+
+ +
+ +
+
+ +
+ +
+
\ No newline at end of file diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..2ad0b85 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,58 @@ +export function getFileExtension (fileName) { + const regex = new RegExp('[^.]+$'); + return fileName.match(regex)?.[0]; +} +export function getFileBasename (fileName) { + const regex = new RegExp('^[^.]+'); + return fileName.match(regex)?.[0] +} + +export function parseFileName (s) { + const pattern = /(?^.+)\.(?[^.]+?$)/ + const match = s.match(pattern) + if (match && match.groups){ + return { + fileName: match.groups.base, + fileExtension: match.groups.extension, + } + } else { + return {fileName: s} + } +} + +const createFileNameDefaultOptions = { + meta_separator : "__", + category_prefix : "", + tag_prefix: "<>", + tag_separator: "", + lowerCase: true, + noSymbols: true, +} + +export function createMetaFileName(originalFileName, meta={category:"", tags:[]}, options) { + const { + meta_separator, + category_prefix, + tag_prefix, + tag_separator, + lowerCase, + noSymbols + } = {...createFileNameDefaultOptions,...options} + + let {fileName, fileExtension} = parseFileName(originalFileName) + + const category_string = `${category_prefix}${meta.category}` + const tag_string = `${meta.tags.map(tag=>tag_prefix+tag).join('')}` + + fileName = `${fileName}${meta_separator}${category_string}${tag_separator}${tag_string}` + + if (lowerCase) { + fileName = fileName.toLowerCase() + } + + if (noSymbols) { + fileName = fileName.replace(/[^a-z0-9_-]/g,'-') + } + + return `${fileName}.${fileExtension}` +} \ No newline at end of file