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