This commit is contained in:
RossAscends
2023-06-05 01:02:21 +09:00
6 changed files with 215 additions and 26 deletions

View File

@@ -427,6 +427,7 @@ export const event_types = {
GENERATION_STOPPED: 'generation_stopped', GENERATION_STOPPED: 'generation_stopped',
SETTINGS_UPDATED: 'settings_updated', SETTINGS_UPDATED: 'settings_updated',
GROUP_UPDATED: 'group_updated', GROUP_UPDATED: 'group_updated',
MOVABLE_PANELS_RESET: 'movable_panels_reset',
} }
export const eventSource = new EventEmitter(); export const eventSource = new EventEmitter();

View File

@@ -450,7 +450,7 @@ dragElement(document.getElementById("WorldInfo"));
function dragElement(elmnt) { export function dragElement(elmnt) {
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
if (document.getElementById(elmnt.id + "header")) { //ex: id="sheldheader" if (document.getElementById(elmnt.id + "header")) { //ex: id="sheldheader"
@@ -504,7 +504,7 @@ function dragElement(elmnt) {
pos3 = e.clientX; //new mouse X pos3 = e.clientX; //new mouse X
pos4 = e.clientY; //new mouse Y pos4 = e.clientY; //new mouse Y
elmnt.setAttribute('data-dragged', 'true');
//fix over/underflows: //fix over/underflows:

View File

@@ -46,6 +46,7 @@ const extension_settings = {
apiKey: '', apiKey: '',
autoConnect: false, autoConnect: false,
disabledExtensions: [], disabledExtensions: [],
expressionOverrides: [],
memory: {}, memory: {},
note: { note: {
default: '', default: '',

View File

@@ -1,5 +1,5 @@
import { callPopup, eventSource, event_types, getRequestHeaders, saveSettingsDebounced } from "../../../script.js"; import { callPopup, eventSource, event_types, getRequestHeaders, saveSettingsDebounced } from "../../../script.js";
import { deviceInfo } from "../../RossAscends-mods.js"; import { deviceInfo, dragElement } from "../../RossAscends-mods.js";
import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch } from "../../extensions.js"; import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch } from "../../extensions.js";
import { power_user } from "../../power-user.js"; import { power_user } from "../../power-user.js";
import { onlyUnique, debounce } from "../../utils.js"; import { onlyUnique, debounce } from "../../utils.js";
@@ -121,13 +121,22 @@ async function visualNovelSetCharacterSprites(container, name, expression) {
} }
const character = context.characters.find(x => x.avatar == avatar); const character = context.characters.find(x => x.avatar == avatar);
let spriteFolderName = character.name;
const avatarFileName = getSpriteFolderName({ original_avatar: character.avatar });
const expressionOverride = extension_settings.expressionOverrides.find((e) =>
e.name == avatarFileName
);
// download images if not downloaded yet if (expressionOverride && expressionOverride.path) {
if (spriteCache[character.name] === undefined) { spriteFolderName = expressionOverride.path;
spriteCache[character.name] = await getSpritesList(character.name, character);
} }
const sprites = spriteCache[character.name]; // download images if not downloaded yet
if (spriteCache[spriteFolderName] === undefined) {
spriteCache[spriteFolderName] = await getSpritesList(spriteFolderName);
}
const sprites = spriteCache[spriteFolderName];
const expressionImage = container.find(`.expression-holder[data-avatar="${avatar}"]`); const expressionImage = container.find(`.expression-holder[data-avatar="${avatar}"]`);
const defaultSpritePath = sprites.find(x => x.label === FALLBACK_EXPRESSION)?.path; const defaultSpritePath = sprites.find(x => x.label === FALLBACK_EXPRESSION)?.path;
const noSprites = sprites.length === 0; const noSprites = sprites.length === 0;
@@ -143,8 +152,11 @@ async function visualNovelSetCharacterSprites(container, name, expression) {
expressionImage.toggleClass('hidden', noSprites); expressionImage.toggleClass('hidden', noSprites);
} else { } else {
const template = $('#expression-holder').clone(); const template = $('#expression-holder').clone();
template.attr('id', `expression-${avatar}`);
template.attr('data-avatar', avatar); template.attr('data-avatar', avatar);
template.find('.drag-grabber').attr('id', `expression-${avatar}header`);
$('#visual-novel-wrapper').append(template); $('#visual-novel-wrapper').append(template);
dragElement(template[0]);
template.toggleClass('hidden', noSprites); template.toggleClass('hidden', noSprites);
setImage(template.find('img'), defaultSpritePath || ''); setImage(template.find('img'), defaultSpritePath || '');
const fadeInPromise = new Promise(resolve => { const fadeInPromise = new Promise(resolve => {
@@ -164,9 +176,9 @@ async function visualNovelUpdateLayers(container) {
const context = getContext(); const context = getContext();
const group = context.groups.find(x => x.id == context.groupId); const group = context.groups.find(x => x.id == context.groupId);
const members = group.members; const members = group.members;
const recentMessages = context.chat.map(x => x.original_avatar).filter(onlyUnique); const recentMessages = context.chat.map(x => x.original_avatar).filter(x => x).reverse().filter(onlyUnique);
const filteredMembers = members.filter(x => !group.disabled_members.includes(x)); const filteredMembers = members.filter(x => !group.disabled_members.includes(x));
const layerIndices = filteredMembers.slice().sort((a, b) => recentMessages.indexOf(a) - recentMessages.indexOf(b)); const layerIndices = filteredMembers.slice().sort((a, b) => recentMessages.indexOf(b) - recentMessages.indexOf(a));
const setLayerIndicesPromises = []; const setLayerIndicesPromises = [];
@@ -201,6 +213,13 @@ async function visualNovelUpdateLayers(container) {
images.sort(sortFunction).each((index, current) => { images.sort(sortFunction).each((index, current) => {
const element = $(current); const element = $(current);
// skip repositioning of dragged elements
if (element.data('dragged')) {
currentPosition += imagesWidth[index];
return;
}
const avatar = element.data('avatar'); const avatar = element.data('avatar');
const layerIndex = layerIndices.indexOf(avatar); const layerIndex = layerIndices.indexOf(avatar);
element.css('z-index', layerIndex); element.css('z-index', layerIndex);
@@ -226,7 +245,17 @@ async function setLastMessageSprite(img, avatar, labels) {
if (lastMessage) { if (lastMessage) {
const text = lastMessage.mes || ''; const text = lastMessage.mes || '';
const sprites = spriteCache[lastMessage.name] || []; let spriteFolderName = lastMessage.name;
const avatarFileName = getSpriteFolderName(lastMessage);
const expressionOverride = extension_settings.expressionOverrides.find((e) =>
e.name == avatarFileName
);
if (expressionOverride && expressionOverride.path) {
spriteFolderName = expressionOverride.path;
}
const sprites = spriteCache[spriteFolderName] || [];
const label = await getExpressionLabel(text); const label = await getExpressionLabel(text);
const path = labels.includes(label) ? sprites.find(x => x.label === label)?.path : ''; const path = labels.includes(label) ? sprites.find(x => x.label === label)?.path : '';
@@ -293,13 +322,23 @@ async function moduleWorker() {
if (vnStateChanged) { if (vnStateChanged) {
lastMessage = null; lastMessage = null;
$('#visual-novel-wrapper').empty(); $('#visual-novel-wrapper').empty();
$("#expression-holder").css({ top: '', left: '', right: '', bottom: '', height: '', width: '', margin: '' });
} }
const currentLastMessage = getLastCharacterMessage(); const currentLastMessage = getLastCharacterMessage();
let spriteFolderName = currentLastMessage.name;
const avatarFileName = getSpriteFolderName(currentLastMessage);
const expressionOverride = extension_settings.expressionOverrides.find((e) =>
e.name == avatarFileName
);
if (expressionOverride && expressionOverride.path) {
spriteFolderName = expressionOverride.path;
}
// character has no expressions or it is not loaded // character has no expressions or it is not loaded
if (Object.keys(spriteCache).length === 0) { if (Object.keys(spriteCache).length === 0) {
await validateImages(currentLastMessage.name); await validateImages(spriteFolderName);
lastCharacter = context.groupId || context.characterId; lastCharacter = context.groupId || context.characterId;
} }
@@ -310,7 +349,7 @@ async function moduleWorker() {
lastCharacter = context.groupId || context.characterId; lastCharacter = context.groupId || context.characterId;
if (context.groupId) { if (context.groupId) {
await validateImages(currentLastMessage.name, true); await validateImages(spriteFolderName, true);
await forceUpdateVisualNovelMode(); await forceUpdateVisualNovelMode();
} }
@@ -322,13 +361,12 @@ async function moduleWorker() {
expressionsList = null; expressionsList = null;
spriteCache = {}; spriteCache = {};
expressionsList = await getExpressionsList(); expressionsList = await getExpressionsList();
await validateImages(currentLastMessage.name, true); await validateImages(spriteFolderName, true);
} }
offlineMode.css('display', 'none'); offlineMode.css('display', 'none');
} }
// check if last message changed // check if last message changed
if ((lastCharacter === context.characterId || lastCharacter === context.groupId) if ((lastCharacter === context.characterId || lastCharacter === context.groupId)
&& lastMessage === currentLastMessage.mes) { && lastMessage === currentLastMessage.mes) {
@@ -344,7 +382,11 @@ async function moduleWorker() {
inApiCall = true; inApiCall = true;
let expression = await getExpressionLabel(currentLastMessage.mes); let expression = await getExpressionLabel(currentLastMessage.mes);
const name = context.groupId ? currentLastMessage.name : context.name2; // If we're not already overriding the folder name, account for group chats.
if (spriteFolderName === currentLastMessage.name && !context.groupId) {
spriteFolderName = context.name2;
}
const force = !!context.groupId; const force = !!context.groupId;
// Character won't be angry on you for swiping // Character won't be angry on you for swiping
@@ -352,11 +394,7 @@ async function moduleWorker() {
expression = FALLBACK_EXPRESSION; expression = FALLBACK_EXPRESSION;
} }
if (vnMode) { await sendExpressionCall(spriteFolderName, expression, force, vnMode);
await updateVisualNovelMode(name, expression);
} else {
setExpression(name, expression, force);
}
} }
catch (error) { catch (error) {
@@ -369,8 +407,41 @@ async function moduleWorker() {
} }
} }
function getSpriteFolderName(message) {
const context = getContext();
let avatarPath = '';
if (context.groupId) {
avatarPath = message.original_avatar || context.characters.find(x => message.force_avatar && message.force_avatar.includes(encodeURIComponent(x.avatar)))?.avatar;
}
else if (context.characterId) {
avatarPath = context.characters[context.characterId].avatar;
}
if (!avatarPath) {
return '';
}
const folderName = avatarPath.replace(/\.[^/.]+$/, "");
console.debug(`Folder for ${message.name}:`, folderName);
return folderName;
}
async function sendExpressionCall(name, expression, force, vnMode) {
if (!vnMode) {
vnMode = isVisualNovelMode();
}
if (vnMode) {
await updateVisualNovelMode(name, expression);
} else {
setExpression(name, expression, force);
}
}
async function getExpressionLabel(text) { async function getExpressionLabel(text) {
if (!modules.includes('classify')) { // Return if text is undefined, saving a costly fetch request
if (!modules.includes('classify') || !text) {
return FALLBACK_EXPRESSION; return FALLBACK_EXPRESSION;
} }
@@ -401,10 +472,10 @@ function getLastCharacterMessage() {
continue; continue;
} }
return { mes: mes.mes, name: mes.name }; return { mes: mes.mes, name: mes.name, original_avatar: mes.original_avatar, force_avatar: mes.force_avatar };
} }
return { mes: '', name: null }; return { mes: '', name: null, original_avatar: null, force_avatar: null };
} }
function removeExpression() { function removeExpression() {
@@ -639,6 +710,86 @@ async function onClickExpressionUpload(event) {
.trigger('click'); .trigger('click');
} }
async function onClickExpressionOverrideButton() {
const context = getContext();
const currentLastMessage = getLastCharacterMessage();
const avatarFileName = getSpriteFolderName(currentLastMessage);
// If the avatar name couldn't be found, abort.
if (!avatarFileName) {
console.debug(`Could not find filename for character with name ${currentLastMessage.name} and ID ${context.characterId}`);
return;
}
const overridePath = $("#expression_override").val();
const existingOverrideIndex = extension_settings.expressionOverrides.findIndex((e) =>
e.name == avatarFileName
);
// If the path is empty, delete the entry from overrides
if (overridePath === undefined || overridePath.length === 0) {
if (existingOverrideIndex === -1) {
return;
}
extension_settings.expressionOverrides.splice(existingOverrideIndex, 1);
console.debug(`Removed existing override for ${avatarFileName}`);
} else {
// Properly override objects and clear the sprite cache of the previously set names
const existingOverride = extension_settings.expressionOverrides[existingOverrideIndex];
if (existingOverride) {
Object.assign(existingOverride, { path: overridePath });
delete spriteCache[existingOverride.name];
} else {
const characterOverride = { name: avatarFileName, path: overridePath };
extension_settings.expressionOverrides.push(characterOverride);
delete spriteCache[currentLastMessage.name];
}
console.debug(`Added/edited expression override for character with filename ${avatarFileName} to folder ${overridePath}`);
}
saveSettingsDebounced();
// Refresh sprites list. Assume the override path has been properly handled.
try {
$('#visual-novel-wrapper').empty();
await validateImages(overridePath.length === 0 ? currentLastMessage.name : overridePath, true);
const expression = await getExpressionLabel(currentLastMessage.mes);
await sendExpressionCall(overridePath.length === 0 ? currentLastMessage.name : overridePath, expression, true);
forceUpdateVisualNovelMode();
} catch (error) {
console.debug(`Setting expression override for ${avatarFileName} failed with error: ${error}`);
}
}
async function onClickExpressionOverrideRemoveAllButton() {
// Remove all the overrided entries from sprite cache
for (const element of extension_settings.expressionOverrides) {
delete spriteCache[element.name];
}
extension_settings.expressionOverrides = [];
saveSettingsDebounced();
console.debug("All expression image overrides have been cleared.");
// Refresh sprites list to use the default name if applicable
try {
$('#visual-novel-wrapper').empty();
const currentLastMessage = getLastCharacterMessage();
await validateImages(currentLastMessage.name, true);
const expression = await getExpressionLabel(currentLastMessage.mes);
await sendExpressionCall(currentLastMessage.name, expression, true);
forceUpdateVisualNovelMode();
console.debug(extension_settings.expressionOverrides);
} catch (error) {
console.debug(`The current expression could not be set because of error: ${error}`);
}
}
async function onClickExpressionUploadPackButton() { async function onClickExpressionUploadPackButton() {
const name = $('#image_list').data('name'); const name = $('#image_list').data('name');
@@ -694,6 +845,24 @@ async function onClickExpressionDelete(event) {
await validateImages(name); await validateImages(name);
} }
function setExpressionOverrideHtml() {
const currentLastMessage = getLastCharacterMessage();
const avatarFileName = getSpriteFolderName(currentLastMessage);
if (!avatarFileName) {
return;
}
const expressionOverride = extension_settings.expressionOverrides.find((e) =>
e.name == avatarFileName
);
if (expressionOverride && expressionOverride.path) {
$("#expression_override").val(expressionOverride.path);
} else if (expressionOverride) {
delete extension_settings.expressionOverrides[expressionOverride.name];
}
}
(function () { (function () {
function addExpressionImage() { function addExpressionImage() {
const html = ` const html = `
@@ -724,12 +893,20 @@ async function onClickExpressionDelete(event) {
</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 class="flex-container flexnowrap">
<input id="expression_override" type="text" class="text_pole" placeholder="Override folder name" />
<input id="expression_override_button" class="menu_button" type="submit" value="Submit" />
</div>
<div id="image_list"></div> <div id="image_list"></div>
<div class="expression_buttons"> <div class="expression_buttons flex-container spaceEvenly">
<div id="expression_upload_pack_button" class="menu_button"> <div id="expression_upload_pack_button" class="menu_button">
<i class="fa-solid fa-file-zipper"></i> <i class="fa-solid fa-file-zipper"></i>
<span>Upload sprite pack (ZIP)</span> <span>Upload sprite pack (ZIP)</span>
</div> </div>
<div id="expression_override_cleanup_button" class="menu_button">
<i class="fa-solid fa-trash-can"></i>
<span>Remove all image overrides</span>
</div>
</div> </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>
@@ -743,9 +920,11 @@ async function onClickExpressionDelete(event) {
</div> </div>
`; `;
$('#extensions_settings').append(html); $('#extensions_settings').append(html);
$('#expression_override_button').on('click', onClickExpressionOverrideButton);
$('#expressions_show_default').on('input', onExpressionsShowDefaultInput); $('#expressions_show_default').on('input', onExpressionsShowDefaultInput);
$('#expression_upload_pack_button').on('click', onClickExpressionUploadPackButton); $('#expression_upload_pack_button').on('click', onClickExpressionUploadPackButton);
$('#expressions_show_default').prop('checked', extension_settings.expressions.showDefault).trigger('input'); $('#expressions_show_default').prop('checked', extension_settings.expressions.showDefault).trigger('input');
$('#expression_override_cleanup_button').on('click', onClickExpressionOverrideRemoveAllButton);
$(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_upload', onClickExpressionUpload);
$(document).on('click', '.expression_list_delete', onClickExpressionDelete); $(document).on('click', '.expression_list_delete', onClickExpressionDelete);
@@ -761,9 +940,12 @@ async function onClickExpressionDelete(event) {
setInterval(updateFunction, UPDATE_INTERVAL); setInterval(updateFunction, UPDATE_INTERVAL);
moduleWorker(); moduleWorker();
eventSource.on(event_types.CHAT_CHANGED, () => { eventSource.on(event_types.CHAT_CHANGED, () => {
setExpressionOverrideHtml();
if (isVisualNovelMode()) { if (isVisualNovelMode()) {
$('#visual-novel-wrapper').empty(); $('#visual-novel-wrapper').empty();
} }
}); });
eventSource.on(event_types.MOVABLE_PANELS_RESET, updateVisualNovelModeDebounced);
eventSource.on(event_types.GROUP_UPDATED, updateVisualNovelModeDebounced); eventSource.on(event_types.GROUP_UPDATED, updateVisualNovelModeDebounced);
})(); })();

View File

@@ -27,9 +27,9 @@
visibility: hidden !important; visibility: hidden !important;
} }
#visual-novel-wrapper img.expression { /*#visual-novel-wrapper img.expression {
object-fit: cover; object-fit: cover;
} }*/
.expression-holder { .expression-holder {
min-width: 100px; min-width: 100px;

View File

@@ -9,6 +9,8 @@ import {
getRequestHeaders, getRequestHeaders,
substituteParams, substituteParams,
updateVisibleDivs, updateVisibleDivs,
eventSource,
event_types,
} from "../script.js"; } from "../script.js";
import { favsToHotswap } from "./RossAscends-mods.js"; import { favsToHotswap } from "./RossAscends-mods.js";
import { import {
@@ -836,6 +838,9 @@ function resetMovablePanels() {
document.getElementById("WorldInfo").style.height = ''; document.getElementById("WorldInfo").style.height = '';
document.getElementById("WorldInfo").style.width = ''; document.getElementById("WorldInfo").style.width = '';
document.getElementById("WorldInfo").style.margin = ''; document.getElementById("WorldInfo").style.margin = '';
$('*[data-dragged="true"]').removeAttr('data-dragged');
eventSource.emit(event_types.MOVABLE_PANELS_RESET);
} }
$(document).ready(() => { $(document).ready(() => {