mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Add custom avatars for groups
This commit is contained in:
@ -2430,7 +2430,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="avatar_div" class="avatar_div alignitemsflexstart justifySpaceBetween flexnowrap flexGap5">
|
<div id="avatar_div" class="avatar_div alignitemsflexstart justifySpaceBetween flexnowrap flexGap5">
|
||||||
<label id="avatar_div_div" for="add_avatar_button" class="avatar" title="Click to select a new avatar for this character">
|
<label id="avatar_div_div" class="add_avatar avatar" for="add_avatar_button" title="Click to select a new avatar for this character">
|
||||||
<img id="avatar_load_preview" src="img/ai4.png" alt="avatar">
|
<img id="avatar_load_preview" src="img/ai4.png" alt="avatar">
|
||||||
<input hidden type="file" id="add_avatar_button" name="avatar" accept="image/png, image/jpeg, image/jpg, image/gif, image/bmp">
|
<input hidden type="file" id="add_avatar_button" name="avatar" accept="image/png, image/jpeg, image/jpg, image/gif, image/bmp">
|
||||||
</label>
|
</label>
|
||||||
@ -2547,8 +2547,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="groupTagList" class="tags paddingTopBot5"></div>
|
<div id="groupTagList" class="tags paddingTopBot5"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="rm_group_top_bar" class="flex-container spaceBetween width100p">
|
<div id="rm_group_top_bar" class="flex-container alignitemscenter spaceBetween width100p">
|
||||||
<div name="GroupStragegyAndOrder" id="rm_group_buttons" class="fontsize80p flex-container paddingLeftRight5">
|
<div class="flex1 flex-container alignitemscenter justifyCenter">
|
||||||
|
<label class="add_avatar avatar" for="group_avatar_button" title="Click to select a new avatar for this group">
|
||||||
|
<div id="group_avatar_preview">
|
||||||
|
<div class="avatar">
|
||||||
|
<img src="img/ai4.png" alt="avatar">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input hidden type="file" id="group_avatar_button" name="avatar" accept="image/png, image/jpeg, image/jpg, image/gif, image/bmp">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div name="GroupStragegyAndOrder" id="rm_group_buttons" class="fontsize80p flex-container paddingLeftRight5 flex2">
|
||||||
<div class="">
|
<div class="">
|
||||||
<div class="flex-container flexnowrap width100p whitespacenowrap">
|
<div class="flex-container flexnowrap width100p whitespacenowrap">
|
||||||
<span data-i18n="Group reply strategy">Group reply strategy</span>
|
<span data-i18n="Group reply strategy">Group reply strategy</span>
|
||||||
@ -2576,12 +2586,13 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="GroupFavDelOkBack" class="flex-container flexGap5 spaceEvenly">
|
<div id="GroupFavDelOkBack" class="flex-container flexGap5 spaceEvenly flex1">
|
||||||
<div id="rm_button_back_from_group" class="heightFitContent margin0 menu_button fa-solid fa-left-long"></div>
|
<div id="rm_button_back_from_group" class="heightFitContent margin0 menu_button fa-solid fa-left-long"></div>
|
||||||
<div id="rm_group_scenario" class="heightFitContent margin0 menu_button fa-solid fa-scroll" title="Set a group chat scenario"></div>
|
<div id="rm_group_scenario" class="heightFitContent margin0 menu_button fa-solid fa-scroll" title="Set a group chat scenario"></div>
|
||||||
<div id="group_favorite_button" class="heightFitContent margin0 menu_button fa-solid fa-star" title="Add to Favorites"></div>
|
<div id="group_favorite_button" class="heightFitContent margin0 menu_button fa-solid fa-star" title="Add to Favorites"></div>
|
||||||
<input id="rm_group_fav" type="hidden" />
|
<input id="rm_group_fav" type="hidden" />
|
||||||
<div id="rm_group_submit" class="heightFitContent margin0 menu_button fa-solid fa-check" title="Create"></div>
|
<div id="rm_group_submit" class="heightFitContent margin0 menu_button fa-solid fa-check" title="Create"></div>
|
||||||
|
<div id="rm_group_restore_avatar" class="heightFitContent margin0 menu_button fa-solid fa-images" title="Restore collage avatar"></div>
|
||||||
<div id="rm_group_delete" class="heightFitContent margin0 menu_button fa-solid fa-trash-can" title="Delete"></div>
|
<div id="rm_group_delete" class="heightFitContent margin0 menu_button fa-solid fa-trash-can" title="Delete"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -3207,7 +3218,7 @@
|
|||||||
<div title="Move up" data-action="up" class="right_menu_button fa-solid fa-chevron-up"></div>
|
<div title="Move up" data-action="up" class="right_menu_button fa-solid fa-chevron-up"></div>
|
||||||
<div title="Move down" data-action="down" class="right_menu_button fa-solid fa-chevron-down"></div>
|
<div title="Move down" data-action="down" class="right_menu_button fa-solid fa-chevron-down"></div>
|
||||||
</div>
|
</div>
|
||||||
<div title="View character card" data-action="view" class="right_menu_button fa-solid fa-xl fa-id-badge"></div>
|
<div title="View character card" data-action="view" class="right_menu_button fa-solid fa-xl fa-image-portrait"></div>
|
||||||
<div title="Remove from group" data-action="remove" class="right_menu_button fa-solid fa-2xl fa-xmark">
|
<div title="Remove from group" data-action="remove" class="right_menu_button fa-solid fa-2xl fa-xmark">
|
||||||
</div>
|
</div>
|
||||||
<div title="Add to group" data-action="add" class="right_menu_button fa-solid fa-2xl fa-plus"></div>
|
<div title="Add to group" data-action="add" class="right_menu_button fa-solid fa-2xl fa-plus"></div>
|
||||||
|
@ -3742,7 +3742,7 @@ async function read_avatar_load(input) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCropPopup(src) {
|
export function getCropPopup(src) {
|
||||||
return `<h3>Set the crop position of the avatar image and click Ok to confirm.</h3>
|
return `<h3>Set the crop position of the avatar image and click Ok to confirm.</h3>
|
||||||
<div id='avatarCropWrap'>
|
<div id='avatarCropWrap'>
|
||||||
<img id='avatarToCrop' src='${src}'>
|
<img id='avatarToCrop' src='${src}'>
|
||||||
|
@ -3,6 +3,8 @@ import {
|
|||||||
onlyUnique,
|
onlyUnique,
|
||||||
debounce,
|
debounce,
|
||||||
delay,
|
delay,
|
||||||
|
isDataURL,
|
||||||
|
createThumbnail,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
import { RA_CountCharTokens, humanizedDateTime } from "./RossAscends-mods.js";
|
import { RA_CountCharTokens, humanizedDateTime } from "./RossAscends-mods.js";
|
||||||
import { sortCharactersList, sortGroupMembers } from './power-user.js';
|
import { sortCharactersList, sortGroupMembers } from './power-user.js';
|
||||||
@ -57,6 +59,7 @@ import {
|
|||||||
event_types,
|
event_types,
|
||||||
getCurrentChatId,
|
getCurrentChatId,
|
||||||
setScenarioOverride,
|
setScenarioOverride,
|
||||||
|
getCropPopup,
|
||||||
} from "../script.js";
|
} from "../script.js";
|
||||||
import { appendTagToList, createTagMapFromList, getTagsList, applyTagsOnCharacterSelect, tag_map } from './tags.js';
|
import { appendTagToList, createTagMapFromList, getTagsList, applyTagsOnCharacterSelect, tag_map } from './tags.js';
|
||||||
|
|
||||||
@ -357,6 +360,14 @@ function updateGroupAvatar(group) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getGroupAvatar(group) {
|
function getGroupAvatar(group) {
|
||||||
|
if (!group) {
|
||||||
|
return $(`<div class="avatar"><img src="${default_avatar}"></div>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDataURL(group.avatar_url)) {
|
||||||
|
return $(`<div class="avatar"><img src="${group.avatar_url}"></div>`);
|
||||||
|
}
|
||||||
|
|
||||||
const memberAvatars = [];
|
const memberAvatars = [];
|
||||||
if (group && Array.isArray(group.members) && group.members.length) {
|
if (group && Array.isArray(group.members) && group.members.length) {
|
||||||
for (const member of group.members) {
|
for (const member of group.members) {
|
||||||
@ -925,6 +936,8 @@ function select_group_chats(groupId, skipAnimation) {
|
|||||||
const group = groupId && groups.find((x) => x.id == groupId);
|
const group = groupId && groups.find((x) => x.id == groupId);
|
||||||
const groupName = group?.name ?? "";
|
const groupName = group?.name ?? "";
|
||||||
setMenuType(!!group ? 'group_edit' : 'group_create');
|
setMenuType(!!group ? 'group_edit' : 'group_create');
|
||||||
|
$("#group_avatar_preview").empty().append(getGroupAvatar(group));
|
||||||
|
$("#rm_group_restore_avatar").toggle(!!group && isDataURL(group.avatar_url));
|
||||||
$("#rm_group_chat_name").val(groupName);
|
$("#rm_group_chat_name").val(groupName);
|
||||||
$("#rm_group_chat_name").off();
|
$("#rm_group_chat_name").off();
|
||||||
$("#rm_group_chat_name").on("input", async function () {
|
$("#rm_group_chat_name").on("input", async function () {
|
||||||
@ -1049,6 +1062,67 @@ function select_group_chats(groupId, skipAnimation) {
|
|||||||
$("#rm_group_automode_label").hide();
|
$("#rm_group_automode_label").hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$("#group_avatar_button").off('input').on("input", uploadGroupAvatar);
|
||||||
|
$("#rm_group_restore_avatar").off('click').on("click", restoreGroupAvatar);
|
||||||
|
|
||||||
|
|
||||||
|
async function uploadGroupAvatar(event) {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const e = await new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = resolve;
|
||||||
|
reader.onerror = reject;
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#dialogue_popup').addClass('large_dialogue_popup wide_dialogue_popup');
|
||||||
|
|
||||||
|
const croppedImage = await callPopup(getCropPopup(e.target.result), 'avatarToCrop');
|
||||||
|
|
||||||
|
if (!croppedImage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const thumbnail = await createThumbnail(croppedImage, 96, 144);
|
||||||
|
|
||||||
|
if (!groupId) {
|
||||||
|
$('#group_avatar_preview img').attr('src', thumbnail);
|
||||||
|
$('#rm_group_restore_avatar').show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _thisGroup = groups.find((x) => x.id == groupId);
|
||||||
|
_thisGroup.avatar_url = thumbnail;
|
||||||
|
$("#group_avatar_preview").empty().append(getGroupAvatar(_thisGroup));
|
||||||
|
$("#rm_group_restore_avatar").show();
|
||||||
|
await editGroup(groupId, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function restoreGroupAvatar() {
|
||||||
|
const confirm = await callPopup('<h3>Are you sure you want to restore the group avatar?</h3> Your custom image will be deleted, and a collage will be used instead.', 'confirm');
|
||||||
|
|
||||||
|
if (!confirm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!groupId) {
|
||||||
|
$("#group_avatar_preview img").attr("src", default_avatar);
|
||||||
|
$("#rm_group_restore_avatar").hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _thisGroup = groups.find((x) => x.id == groupId);
|
||||||
|
_thisGroup.avatar_url = '';
|
||||||
|
$("#group_avatar_preview").empty().append(getGroupAvatar(_thisGroup));
|
||||||
|
$("#rm_group_restore_avatar").hide();
|
||||||
|
await editGroup(groupId, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
$(document).off("click", ".group_member .right_menu_button");
|
$(document).off("click", ".group_member .right_menu_button");
|
||||||
$(document).on("click", ".group_member .right_menu_button", async function (event) {
|
$(document).on("click", ".group_member .right_menu_button", async function (event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@ -1174,8 +1248,7 @@ async function createGroup() {
|
|||||||
name = `Group: ${memberNames}`;
|
name = `Group: ${memberNames}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// placeholder
|
const avatar_url = $('#group_avatar_preview img').attr('src');
|
||||||
const avatar_url = 'img/five.png';
|
|
||||||
|
|
||||||
const chatName = humanizedDateTime();
|
const chatName = humanizedDateTime();
|
||||||
const chats = [chatName];
|
const chats = [chatName];
|
||||||
@ -1186,7 +1259,7 @@ async function createGroup() {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name: name,
|
name: name,
|
||||||
members: members,
|
members: members,
|
||||||
avatar_url: avatar_url,
|
avatar_url: isDataURL(avatar_url) ? avatar_url : default_avatar,
|
||||||
allow_self_responses: allow_self_responses,
|
allow_self_responses: allow_self_responses,
|
||||||
activation_strategy: activation_strategy,
|
activation_strategy: activation_strategy,
|
||||||
disabled_members: [],
|
disabled_members: [],
|
||||||
|
@ -616,3 +616,38 @@ export function extractDataFromPng(data, identifier = 'chara') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createThumbnail(dataUrl, maxWidth, maxHeight) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.src = dataUrl;
|
||||||
|
img.onload = () => {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
// Calculate the thumbnail dimensions while maintaining the aspect ratio
|
||||||
|
const aspectRatio = img.width / img.height;
|
||||||
|
let thumbnailWidth = maxWidth;
|
||||||
|
let thumbnailHeight = maxHeight;
|
||||||
|
|
||||||
|
if (img.width > img.height) {
|
||||||
|
thumbnailHeight = maxWidth / aspectRatio;
|
||||||
|
} else {
|
||||||
|
thumbnailWidth = maxHeight * aspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the canvas dimensions and draw the resized image
|
||||||
|
canvas.width = thumbnailWidth;
|
||||||
|
canvas.height = thumbnailHeight;
|
||||||
|
ctx.drawImage(img, 0, 0, thumbnailWidth, thumbnailHeight);
|
||||||
|
|
||||||
|
// Convert the canvas to a data URL and resolve the promise
|
||||||
|
const thumbnailDataUrl = canvas.toDataURL('image/jpeg');
|
||||||
|
resolve(thumbnailDataUrl);
|
||||||
|
};
|
||||||
|
|
||||||
|
img.onerror = () => {
|
||||||
|
reject(new Error('Failed to load the image.'));
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -687,14 +687,14 @@ hr {
|
|||||||
gap: 5px;
|
gap: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#avatar_div_div {
|
.add_avatar {
|
||||||
border: 2px solid var(--SmartThemeBodyColor);
|
border: 2px solid var(--SmartThemeBodyColor);
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: filter 0.2s ease-in-out;
|
transition: filter 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
#avatar_div_div:hover {
|
.add_avatar:hover {
|
||||||
filter: drop-shadow(0px 0px 5px var(--SmartThemeQuoteColor));
|
filter: drop-shadow(0px 0px 5px var(--SmartThemeQuoteColor));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user