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.
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.vscode
|
||||
.eslintrc.js
|
||||
dist/module.zip
|
||||
164
src/main.js
Normal file
164
src/main.js
Normal file
@@ -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);
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
21
src/module.json
Normal file
21
src/module.json
Normal file
@@ -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"
|
||||
]
|
||||
}
|
||||
48
src/settings.js
Normal file
48
src/settings.js
Normal file
@@ -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}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
7
src/styles/style.css
Normal file
7
src/styles/style.css
Normal file
@@ -0,0 +1,7 @@
|
||||
#upload-area {
|
||||
border: 2px dashed #ccc;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
7
src/templates/upload-choose.hbs
Normal file
7
src/templates/upload-choose.hbs
Normal file
@@ -0,0 +1,7 @@
|
||||
<form action="">
|
||||
<div id="upload-area">
|
||||
<p>Drag files here to upload</p>
|
||||
<input type="file" id="file-input" style="display:none;">
|
||||
</div>
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
41
src/templates/upload-form.hbs
Normal file
41
src/templates/upload-form.hbs
Normal file
@@ -0,0 +1,41 @@
|
||||
<form id="upload-form">
|
||||
<input type="text" id="file-input" style="" readonly>
|
||||
<img height=100px>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Category</label>
|
||||
<div class="form-fields">
|
||||
<select type="radio" name="file-category" required>
|
||||
{{#each fileCategories}}
|
||||
<option value="{{id}}">
|
||||
{{label}}
|
||||
</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Name</label>
|
||||
<input type="text" id="baseName" value="">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="tags">Tags</label>
|
||||
<div class="form-fields">
|
||||
{{#each fileTags}}
|
||||
<input type="checkbox" name="file-tags" value="{{id}}">
|
||||
{{label}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Preview</label>
|
||||
<div class="form-fields"><input type="text" id="name-preview" readonly></div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 15px; text-align: right;">
|
||||
<button type="submit">Upload</button>
|
||||
</div>
|
||||
</form>
|
||||
58
src/utils.js
Normal file
58
src/utils.js
Normal file
@@ -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 = /(?<base>^.+)\.(?<extension>[^.]+?$)/
|
||||
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}`
|
||||
}
|
||||
Reference in New Issue
Block a user