mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Upload or delete a sprite image.
This commit is contained in:
@ -1,4 +1,4 @@
|
|||||||
import { saveSettingsDebounced } from "../../../script.js";
|
import { callPopup, getRequestHeaders, saveSettingsDebounced } from "../../../script.js";
|
||||||
import { getContext, getApiUrl, modules, extension_settings } from "../../extensions.js";
|
import { getContext, getApiUrl, modules, extension_settings } from "../../extensions.js";
|
||||||
export { MODULE_NAME };
|
export { MODULE_NAME };
|
||||||
|
|
||||||
@ -240,6 +240,14 @@ function drawSpritesList(character, labels, sprites) {
|
|||||||
function getListItem(item, imageSrc, textClass) {
|
function getListItem(item, imageSrc, textClass) {
|
||||||
return `
|
return `
|
||||||
<div id="${item}" class="expression_list_item">
|
<div id="${item}" class="expression_list_item">
|
||||||
|
<div class="expression_list_buttons">
|
||||||
|
<div class="menu_button expression_list_upload" title="Upload image">
|
||||||
|
<i class="fa-solid fa-upload"></i>
|
||||||
|
</div>
|
||||||
|
<div class="menu_button expression_list_delete" title="Delete image">
|
||||||
|
<i class="fa-solid fa-trash"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<span class="expression_list_title ${textClass}">${item}</span>
|
<span class="expression_list_title ${textClass}">${item}</span>
|
||||||
<img class="expression_list_image" src="${imageSrc}" />
|
<img class="expression_list_image" src="${imageSrc}" />
|
||||||
</div>
|
</div>
|
||||||
@ -340,6 +348,80 @@ function onClickExpressionImage() {
|
|||||||
setExpression(name, expression, true);
|
setExpression(name, expression, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function onClickExpressionUpload(event) {
|
||||||
|
// Prevents the expression from being set
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const id = $(this).closest('.expression_list_item').attr('id');
|
||||||
|
const name = $('#image_list').data('name');
|
||||||
|
|
||||||
|
const handleExpressionUploadChange = async (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('name', name);
|
||||||
|
formData.append('label', id);
|
||||||
|
formData.append('avatar', file);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await jQuery.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: "/upload_sprite",
|
||||||
|
data: formData,
|
||||||
|
beforeSend: function () { },
|
||||||
|
cache: false,
|
||||||
|
contentType: false,
|
||||||
|
processData: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Refresh sprites list
|
||||||
|
delete spriteCache[name];
|
||||||
|
await validateImages(name);
|
||||||
|
} catch (error) {
|
||||||
|
toastr.error('Failed to upload image');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the input
|
||||||
|
e.target.form.reset();
|
||||||
|
};
|
||||||
|
|
||||||
|
$('#expression_upload')
|
||||||
|
.off('change')
|
||||||
|
.on('change', handleExpressionUploadChange)
|
||||||
|
.trigger('click');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onClickExpressionDelete(event) {
|
||||||
|
// Prevents the expression from being set
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const confirmation = await callPopup("<h3>Are you sure?</h3>Once deleted, it's gone forever!", 'confirm');
|
||||||
|
|
||||||
|
if (!confirmation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = $(this).closest('.expression_list_item').attr('id');
|
||||||
|
const name = $('#image_list').data('name');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fetch('/delete_sprite', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getRequestHeaders(),
|
||||||
|
body: JSON.stringify({ name, label: id }),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
toastr.error('Failed to delete image. Try again later.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh sprites list
|
||||||
|
delete spriteCache[name];
|
||||||
|
await validateImages(name);
|
||||||
|
}
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
function addExpressionImage() {
|
function addExpressionImage() {
|
||||||
@ -357,24 +439,27 @@ function onClickExpressionImage() {
|
|||||||
const html = `
|
const html = `
|
||||||
<div class="expression_settings">
|
<div class="expression_settings">
|
||||||
<div class="inline-drawer">
|
<div class="inline-drawer">
|
||||||
<div class="inline-drawer-toggle inline-drawer-header">
|
<div class="inline-drawer-toggle inline-drawer-header">
|
||||||
<b>Expression images</b>
|
<b>Expression images</b>
|
||||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="inline-drawer-content">
|
<div class="inline-drawer-content">
|
||||||
<p class="offline_mode">You are in offline mode. Click on the image below to set the expression.</p>
|
<p class="offline_mode">You are in offline mode. Click on the image below to set the expression.</p>
|
||||||
<div id="image_list"></div>
|
<div id="image_list"></div>
|
||||||
<p class="hint"><b>Hint:</b> <i>Create new folder in the <b>public/characters/</b> folder and name it as the name of the character.
|
<p class="hint"><b>Hint:</b> <i>Create new folder in the <b>public/characters/</b> folder and name it as the name of the character.
|
||||||
Put images with expressions there. File names should follow the pattern: <tt>[expression_label].[image_format]</tt></i></p>
|
Put images with expressions there. File names should follow the pattern: <tt>[expression_label].[image_format]</tt></i></p>
|
||||||
<label for="expressions_show_default"><input id="expressions_show_default" type="checkbox">Show default images (emojis) if missing</label>
|
<label for="expressions_show_default"><input id="expressions_show_default" type="checkbox">Show default images (emojis) if missing</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<form><input type="file" id="expression_upload" name="expression_upload" accept="image/*" hidden></form>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
$('#extensions_settings').append(html);
|
$('#extensions_settings').append(html);
|
||||||
$('#expressions_show_default').on('input', onExpressionsShowDefaultInput);
|
$('#expressions_show_default').on('input', onExpressionsShowDefaultInput);
|
||||||
$('#expressions_show_default').prop('checked', extension_settings.expressions.showDefault).trigger('input');
|
$('#expressions_show_default').prop('checked', extension_settings.expressions.showDefault).trigger('input');
|
||||||
$(document).on('click', '.expression_list_item', onClickExpressionImage);
|
$(document).on('click', '.expression_list_item', onClickExpressionImage);
|
||||||
|
$(document).on('click', '.expression_list_upload', onClickExpressionUpload);
|
||||||
|
$(document).on('click', '.expression_list_delete', onClickExpressionDelete);
|
||||||
$('.expression_settings').hide();
|
$('.expression_settings').hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,9 +78,23 @@ img.expression.default {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.expression_list_buttons {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
height: 20%;
|
||||||
|
padding: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.expression_list_image {
|
.expression_list_image {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
#image_list {
|
#image_list {
|
||||||
|
77
server.js
77
server.js
@ -3061,6 +3061,83 @@ app.post('/google_translate', jsonParser, async (request, response) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post('/delete_sprite', jsonParser, async (request, response) => {
|
||||||
|
const label = request.body.label;
|
||||||
|
const name = request.body.name;
|
||||||
|
|
||||||
|
if (!label || !name) {
|
||||||
|
return response.sendStatus(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const spritesPath = path.join(directories.characters, name);
|
||||||
|
|
||||||
|
// No sprites folder exists, or not a directory
|
||||||
|
if (!fs.existsSync(spritesPath) || !fs.statSync(spritesPath).isDirectory()) {
|
||||||
|
return response.sendStatus(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = fs.readdirSync(spritesPath);
|
||||||
|
|
||||||
|
// Remove existing sprite with the same label
|
||||||
|
for (const file of files) {
|
||||||
|
if (path.parse(file).name === label) {
|
||||||
|
fs.rmSync(path.join(spritesPath, file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.sendStatus(200);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return response.sendStatus(500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/upload_sprite', urlencodedParser, async (request, response) => {
|
||||||
|
const file = request.file;
|
||||||
|
const label = request.body.label;
|
||||||
|
const name = request.body.name;
|
||||||
|
|
||||||
|
if (!file || !label || !name) {
|
||||||
|
return response.sendStatus(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const spritesPath = path.join(directories.characters, name);
|
||||||
|
|
||||||
|
// Path to sprites is not a directory. This should never happen.
|
||||||
|
if (!fs.statSync(spritesPath).isDirectory()) {
|
||||||
|
return response.sendStatus(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create sprites folder if it doesn't exist
|
||||||
|
if (!fs.existsSync(spritesPath)) {
|
||||||
|
fs.mkdirSync(spritesPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = fs.readdirSync(spritesPath);
|
||||||
|
|
||||||
|
// Remove existing sprite with the same label
|
||||||
|
for (const file of files) {
|
||||||
|
if (path.parse(file).name === label) {
|
||||||
|
fs.rmSync(path.join(spritesPath, file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const filename = label + path.parse(file.originalname).ext;
|
||||||
|
const spritePath = path.join("./uploads/", file.filename);
|
||||||
|
const pathToFile = path.join(spritesPath, filename);
|
||||||
|
// Copy uploaded file to sprites folder
|
||||||
|
fs.cpSync(spritePath, pathToFile);
|
||||||
|
// Remove uploaded file
|
||||||
|
fs.rmSync(spritePath);
|
||||||
|
return response.sendStatus(200);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return response.sendStatus(500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function writeSecret(key, value) {
|
function writeSecret(key, value) {
|
||||||
if (!fs.existsSync(SECRETS_FILE)) {
|
if (!fs.existsSync(SECRETS_FILE)) {
|
||||||
const emptyFile = JSON.stringify({});
|
const emptyFile = JSON.stringify({});
|
||||||
|
Reference in New Issue
Block a user