Add custom avatars for groups

This commit is contained in:
Cohee
2023-06-25 02:57:07 +03:00
parent 66de4a1e09
commit 31057e1e81
5 changed files with 130 additions and 11 deletions

View File

@ -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>

View File

@ -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}'>

View File

@ -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: [],

View File

@ -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.'));
};
});
}

View File

@ -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));
} }