Upload expressions update

This commit is contained in:
Wolfsblvt
2025-01-27 05:39:51 +01:00
parent 5c34c93a84
commit 3282c9426c
4 changed files with 90 additions and 13 deletions

View File

@ -15,6 +15,8 @@ import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashComm
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
import { slashCommandReturnHelper } from '../../slash-commands/SlashCommandReturnHelper.js'; import { slashCommandReturnHelper } from '../../slash-commands/SlashCommandReturnHelper.js';
import { generateWebLlmChatPrompt, isWebLlmSupported } from '../shared.js'; import { generateWebLlmChatPrompt, isWebLlmSupported } from '../shared.js';
import { Popup, POPUP_RESULT } from '../../popup.js';
import { t } from '../../i18n.js';
export { MODULE_NAME }; export { MODULE_NAME };
/** /**
@ -1852,11 +1854,23 @@ async function handleFileUpload(url, formData) {
} }
} }
/**
* Removes the file extension from a file name
* @param {string} fileName The file name to remove the extension from
* @returns {string} The file name without the extension
*/
function withoutExtension(fileName) {
return fileName.replace(/\.[^/.]+$/, '');
}
async function onClickExpressionUpload(event) { async function onClickExpressionUpload(event) {
// Prevents the expression from being set // Prevents the expression from being set
event.stopPropagation(); event.stopPropagation();
const expression = $(this).closest('.expression_list_item').data('expression'); const expressionListItem = $(this).closest('.expression_list_item');
const clickedFileName = expressionListItem.attr('data-expression-type') !== 'failure' ? expressionListItem.attr('data-filename') : null;
const expression = expressionListItem.data('expression');
const name = $('#image_list').data('name'); const name = $('#image_list').data('name');
const handleExpressionUploadChange = async (e) => { const handleExpressionUploadChange = async (e) => {
@ -1866,21 +1880,70 @@ async function onClickExpressionUpload(event) {
return; return;
} }
// // If extension_settings.expressions.allowMultiple is false and there's already a main image, ask user: const existingFiles = spriteCache[name]?.find(x => x.label === expression)?.files || [];
// let hasMainImage = true; // Check from your item data
// if (!extension_settings.expressions.allowMultiple && hasMainImage) { let spriteName = expression;
// let userChoice = await callPopup('<h3>Replace existing main image?</h3><p>Press Ok to replace, Cancel to abort.</p>', 'confirm');
// if (!userChoice) { if (extension_settings.expressions.allowMultiple) {
// return; const matchesExisting = existingFiles.some(x => x.fileName === file.name);
// } const fileNameWithoutExtension = withoutExtension(file.name);
// // If user chooses replace, remove the old file, then proceed const filenameValidationRegex = new RegExp(`^${expression}(?:[-\\.].*?)?$`);
// // ...existing code to remove old file... const validFileName = filenameValidationRegex.test(fileNameWithoutExtension);
// }
// If there is no expression yet and it's a valid expression, we just take it
if (!clickedFileName && validFileName) {
spriteName = fileNameWithoutExtension;
}
// If the filename matches the one that was clicked, we just take it and replace it
else if (clickedFileName === file.name) {
spriteName = fileNameWithoutExtension;
}
// If it's a valid filename and there's no existing file with the same name, we just take it
else if (!matchesExisting && validFileName) {
spriteName = fileNameWithoutExtension;
}
else {
/** @type {import('../../popup.js').CustomPopupButton[]} */
const customButtons = [];
if (clickedFileName) {
customButtons.push({
text: t`Replace Existing`,
result: POPUP_RESULT.NEGATIVE,
action: () => {
console.debug('Replacing existing sprite');
spriteName = withoutExtension(clickedFileName);
},
});
}
const message = await renderExtensionTemplateAsync(MODULE_NAME, 'templates/upload-expression', { expression, clickedFileName });
spriteName = null;
const result = await Popup.show.input(t`Upload Expression Sprite`, message,
`${expression}-${existingFiles.length}`, { customButtons: customButtons });
if (result) {
if (!filenameValidationRegex.test(result)) {
toastr.warning(t`The name you entered does not follow the naming schema for the selected expression '${expression}'.`, t`Invalid Expression Sprite Name`);
return;
}
spriteName = result;
}
}
} else {
spriteName = withoutExtension(clickedFileName);
}
if (!spriteName) {
toastr.warning(t`Cancelled uploading sprite.`, t`Upload Cancelled`);
return;
}
const formData = new FormData(); const formData = new FormData();
formData.append('name', name); formData.append('name', name);
formData.append('label', expression); formData.append('label', expression);
formData.append('avatar', file); formData.append('avatar', file);
formData.append('spriteName', spriteName);
await handleFileUpload('/api/sprites/upload', formData); await handleFileUpload('/api/sprites/upload', formData);

View File

@ -87,6 +87,7 @@
</div> </div>
<p class="hint"><b data-i18n="Hint:">Hint:</b> <i><span data-i18n="Create new folder in the _space">Create new folder in the </span><b>/characters/</b> <span data-i18n="folder of your user data directory and name it as the name of the character.">folder of your user data directory and name it as the name of the character.</span> <p class="hint"><b data-i18n="Hint:">Hint:</b> <i><span data-i18n="Create new folder in the _space">Create new folder in the </span><b>/characters/</b> <span data-i18n="folder of your user data directory and name it as the name of the character.">folder of your user data directory and name it as the name of the character.</span>
<span data-i18n="Put images with expressions there. File names should follow the pattern:">Put images with expressions there. File names should follow the pattern: </span><tt data-i18n="expression_label_pattern">[expression_label].[image_format]</tt></i></p> <span data-i18n="Put images with expressions there. File names should follow the pattern:">Put images with expressions there. File names should follow the pattern: </span><tt data-i18n="expression_label_pattern">[expression_label].[image_format]</tt></i></p>
<span>In case of multiple files per expression, file names can contain a suffix, either separated by a dot or a dash. Examples: </span><tt>joy.png</tt>, <tt>joy-1.png</tt>, <tt>joy.expressive.png</tt>, <tt>美しい-17.png</tt>
<h3 id="image_list_header"> <h3 id="image_list_header">
<strong data-i18n="Sprite set:">Sprite set:</strong>&nbsp;<span id="image_list_header_name"></span> <strong data-i18n="Sprite set:">Sprite set:</strong>&nbsp;<span id="image_list_header_name"></span>
</h3> </h3>

View File

@ -0,0 +1,12 @@
<div class="m-b-1" data-i18n="upload_expression_request">Please enter a name for the sprite (without extension).</div>
<div class="m-b-1" data-i18n="upload_expression_naming_1">
Sprite names must follow the naming schema for the selected expression: {{expression}}
</div>
<div data-i18n="upload_expression_naming_2">
For multiple expressions, the name must follow the expression name and a valid suffix. Allowed separators are '-' or dot '.'.
</div>
<span class="m-b-1" data-i18n="Examples:">Examples:</span> <tt>{{expression}}.png</tt>, <tt>{{expression}}-1.png</tt>, <tt>{{expression}}.expressive.png</tt>, <tt>美しい-17.png</tt>
{{#if clickedFileName}}
<div class="m-t-1" data-i18n="upload_expression_replace">Click 'Replace' to replace the existing expression:</div>
<tt>{{clickedFileName}}</tt>
{{/if}}

View File

@ -227,6 +227,7 @@ router.post('/upload', urlencodedParser, async (request, response) => {
const file = request.file; const file = request.file;
const label = request.body.label; const label = request.body.label;
const name = request.body.name; const name = request.body.name;
const spriteName = request.body.spriteName || label;
if (!file || !label || !name) { if (!file || !label || !name) {
return response.sendStatus(400); return response.sendStatus(400);
@ -249,12 +250,12 @@ router.post('/upload', urlencodedParser, async (request, response) => {
// Remove existing sprite with the same label // Remove existing sprite with the same label
for (const file of files) { for (const file of files) {
if (path.parse(file).name === label) { if (path.parse(file).name === spriteName) {
fs.rmSync(path.join(spritesPath, file)); fs.rmSync(path.join(spritesPath, file));
} }
} }
const filename = label + path.parse(file.originalname).ext; const filename = spriteName + path.parse(file.originalname).ext;
const spritePath = path.join(file.destination, file.filename); const spritePath = path.join(file.destination, file.filename);
const pathToFile = path.join(spritesPath, filename); const pathToFile = path.join(spritesPath, filename);
// Copy uploaded file to sprites folder // Copy uploaded file to sprites folder