Tag Folders: Rework favorites display

- Favorites display uses same method than inline avatars
- Favorites now respect avatar style
This commit is contained in:
Wolfsblvt 2024-03-06 04:59:39 +01:00
parent 8e184254c8
commit fc6146fa00
6 changed files with 110 additions and 195 deletions

View File

@ -24,9 +24,7 @@ body.no-modelIcons .icon-svg {
}
body.square-avatars .avatar,
body.square-avatars .avatar img,
body.square-avatars .hotswapAvatar,
body.square-avatars .hotswapAvatar img {
body.square-avatars .avatar img {
border-radius: var(--avatar-base-border-radius) !important;
}
@ -113,10 +111,6 @@ body.big-avatars .bogus_folder_select .avatar {
flex: unset;
}
body:not(.big-avatars) .avatar {
border-radius: var(--avatar-base-border-radius-round);
}
body.big-avatars .avatar {
width: calc(var(--avatar-base-height) * var(--big-avatar-width-factor));
height: calc(var(--avatar-base-height) * var(--big-avatar-height-factor));
@ -129,7 +123,6 @@ body.big-avatars .avatar {
/* align-self: unset; */
overflow: visible;
border-radius: calc(var(--avatar-base-border-radius) * var(--big-avatar-border-factor));
flex: 1
}
body.big-avatars #user_avatar_block .avatar,
@ -177,30 +170,27 @@ body.big-avatars .avatar-container .ch_description {
text-overflow: unset;
}
body.big-avatars .avatars_inline .avatar,
body.big-avatars .avatars_inline .avatar img {
width: calc(var(--avatar-base-width) * var(--big-avatar-width-factor) * var(--inline-avatar-factor));
height: calc(var(--avatar-base-height) * var(--big-avatar-height-factor) * var(--inline-avatar-factor));
body.big-avatars .avatars_inline_small .avatar,
body.big-avatars .avatars_inline_small .avatar img {
width: calc(var(--avatar-base-width) * var(--big-avatar-width-factor) * var(--inline-avatar-small-factor));
height: calc(var(--avatar-base-height) * var(--big-avatar-height-factor) * var(--inline-avatar-small-factor));
}
body.big-avatars .bogus_folder_avatars_block {
flex-wrap: wrap;
overflow: hidden;
height: calc(var(--avatar-base-height) * var(--big-avatar-height-factor) * var(--inline-avatar-factor) + 2 * var(--avatar-base-border-radius));
/* margin-top: calc(var(--avatar-base-height) * var(--big-avatar-height-factor) * var(--inline-avatar-factor) * 0.2); */
body.big-avatars .avatars_inline {
max-height: calc(var(--avatar-base-height) * var(--big-avatar-height-factor) + 2 * var(--avatar-base-border-radius));
}
body.big-avatars .bogus_folder_avatars_block .avatar {
margin: calc(var(--avatar-base-border-radius)) 0;
body.big-avatars .avatars_inline.avatars_inline_small {
height: calc(var(--avatar-base-height) * var(--big-avatar-height-factor) * var(--inline-avatar-small-factor) + 2 * var(--avatar-base-border-radius));
}
body:not(.big-avatars) .avatars_inline .avatar_collage {
min-width: calc(var(--avatar-base-width) * var(--inline-avatar-factor));
body:not(.big-avatars) .avatars_inline_small .avatar_collage {
min-width: calc(var(--avatar-base-width) * var(--inline-avatar-small-factor));
}
body.big-avatars .avatars_inline .avatar_collage {
min-width: calc(var(--avatar-base-width) * var(--big-avatar-width-factor) * var(--inline-avatar-factor));
max-width: calc(var(--avatar-base-width) * var(--big-avatar-width-factor) * var(--inline-avatar-factor));
body.big-avatars .avatars_inline_small .avatar_collage {
min-width: calc(var(--avatar-base-width) * var(--big-avatar-width-factor) * var(--inline-avatar-small-factor));
max-width: calc(var(--avatar-base-width) * var(--big-avatar-width-factor) * var(--inline-avatar-small-factor));
}
/* border radius for big avatars collages */

View File

@ -3888,7 +3888,7 @@
<div class="right_menu_button fa-solid fa-list-ul" id="rm_button_characters" title="Select/Create Characters" data-i18n="[title]Select/Create Characters"></div>
</div>
<div id="HotSwapWrapper" class="alignitemscenter flex-container margin0auto width100p">
<div class="hotswap flex-container flex1"></div>
<div class="hotswap avatars_inline flex-container"></div>
</div>
</div>
<hr>
@ -5022,15 +5022,10 @@
<small class="character_version bogus_folder_counter"></small>
<small class="bogus_folder_unit character_unit_name" data="characters">characters</small>
</div>
<div class="bogus_folder_avatars_block avatars_inline tags tags_inline"></div>
<div class="bogus_folder_avatars_block avatars_inline avatars_inline_small tags tags_inline"></div>
</div>
</div>
</div>
<div id="bogus_folder_inline_character_template" class="template_element">
<div class="avatar flex alignitemscenter textAlignCenter">
<img src="">
</div>
</div>
<div id="bogus_folder_back_template" class="template_element">
<div class="bogus_folder_select flex-container wide100p alignitemsflexstart" id="BogusFolderBack" tagid="back">
<div class="avatar flex alignitemscenter textAlignCenter">
@ -5043,9 +5038,9 @@
</div>
</div>
</div>
<div id="hotswap_template" class="template_element">
<div class="hotswapAvatar" title="Add a character/group to favorites to display it here!">
<img src="/img/ai4.png">
<div id="inline_avatar_template" class="template_element">
<div class="avatar inline_avatar flex alignitemscenter textAlignCenter">
<img src="">
</div>
</div>
<div id="alternate_greetings_template" class="template_element">

View File

@ -249,6 +249,7 @@ export {
scrollChatToBottom,
isStreamingEnabled,
getThumbnailUrl,
buildAvatarList,
getStoppingStrings,
reloadMarkdownProcessor,
getCurrentChatId,
@ -1289,7 +1290,7 @@ async function printCharacters(fullRefresh = false) {
favsToHotswap();
}
export function getEntitiesList({ doFilter } = {}) {
export function getEntitiesList({ doFilter = false, doSort = true } = {}) {
function characterToEntity(character, id) {
return { item: character, id, type: 'character' };
}
@ -1327,7 +1328,9 @@ export function getEntitiesList({ doFilter } = {}) {
entities = filterByTagState(entities, { globalDisplayFilters: true });
}
if (doSort) {
sortEntitiesList(entities);
}
return entities;
}
@ -5245,6 +5248,49 @@ function getThumbnailUrl(type, file) {
return `/thumbnail?type=${type}&file=${encodeURIComponent(file)}`;
}
function buildAvatarList(block, entities, { templateId = 'inline_avatar_template', empty = true, selectable = false } = {}) {
if (empty) {
block.empty();
}
for (const entity of entities) {
const id = entity.id;
// Populate the template
const avatarTemplate = $(`#${templateId} .avatar`).clone();
let this_avatar = default_avatar;
if (entity.item.avatar !== undefined && entity.item.avatar != 'none') {
this_avatar = getThumbnailUrl('avatar', entity.item.avatar);
}
avatarTemplate.attr('data-type', entity.type);
avatarTemplate.attr({ 'chid': id, 'id': `CharID${id}` });
avatarTemplate.find('img').attr('src', this_avatar).attr('alt', entity.item.name);
avatarTemplate.attr('title', `[Character] ${entity.item.name}`);
avatarTemplate.toggleClass('is_fav', entity.item.fav || entity.item.fav == 'true');
avatarTemplate.find('.ch_fav').val(entity.item.fav);
// If this is a group, we need to hack slightly. We still want to keep most of the css classes and layout, but use a group avatar instead.
if (entity.type === 'group') {
const grpTemplate = getGroupAvatar(entity.item);
avatarTemplate.addClass(grpTemplate.attr('class'));
avatarTemplate.empty();
avatarTemplate.append(grpTemplate.children());
avatarTemplate.attr('title', `[Group] ${entity.item.name}`);
}
if (selectable) {
avatarTemplate.addClass('selectable');
avatarTemplate.toggleClass('character_select', entity.type === 'character');
avatarTemplate.toggleClass('group_select', entity.type === 'group');
}
block.append(avatarTemplate);
}
}
async function getChat() {
//console.log('/api/chats/get -- entered for -- ' + characters[this_chid].name);
try {

View File

@ -12,6 +12,7 @@ import {
setActiveCharacter,
getEntitiesList,
getThumbnailUrl,
buildAvatarList,
selectCharacterById,
eventSource,
menu_type,
@ -264,82 +265,16 @@ async function RA_autoloadchat() {
export async function favsToHotswap() {
const entities = getEntitiesList({ doFilter: false });
const container = $('#right-nav-panel .hotswap');
const template = $('#hotswap_template .hotswapAvatar');
const DEFAULT_COUNT = 6;
const WIDTH_PER_ITEM = 60; // 50px + 5px gap + 5px padding
const containerWidth = container.outerWidth();
const maxCount = containerWidth > 0 ? Math.floor(containerWidth / WIDTH_PER_ITEM) : DEFAULT_COUNT;
let count = 0;
const promises = [];
const newContainer = container.clone();
newContainer.empty();
const favs = entities.filter(x => x.item.fav || x.item.fav == 'true');
for (const entity of entities) {
if (count >= maxCount) {
break;
}
const isFavorite = entity.item.fav || entity.item.fav == 'true';
if (!isFavorite) {
continue;
}
const isCharacter = entity.type === 'character';
const isGroup = entity.type === 'group';
const grid = isGroup ? entity.id : '';
const chid = isCharacter ? entity.id : '';
let slot = template.clone();
slot.toggleClass('character_select', isCharacter);
slot.toggleClass('group_select', isGroup);
slot.attr('grid', isGroup ? grid : '');
slot.attr('chid', isCharacter ? chid : '');
slot.data('id', isGroup ? grid : chid);
if (isGroup) {
const group = groups.find(x => x.id === grid);
const avatar = getGroupAvatar(group);
$(slot).find('img').replaceWith(avatar);
$(slot).attr('title', group.name);
}
if (isCharacter) {
const imgLoadPromise = new Promise((resolve) => {
const avatarUrl = getThumbnailUrl('avatar', entity.item.avatar);
$(slot).find('img').attr('src', avatarUrl).on('load', resolve);
$(slot).attr('title', entity.item.avatar);
});
// if the image doesn't load in 500ms, resolve the promise anyway
promises.push(Promise.race([imgLoadPromise, delay(500)]));
}
$(slot).css('cursor', 'pointer');
newContainer.append(slot);
count++;
}
// don't fill leftover spaces with avatar placeholders
// just evenly space the selected avatars instead
/*
if (count < maxCount) { //if any space is left over
let leftOverSlots = maxCount - count;
for (let i = 1; i <= leftOverSlots; i++) {
newContainer.append(template.clone());
}
}
*/
await Promise.allSettled(promises);
//helpful instruction message if no characters are favorited
if (count === 0) { container.html('<small><span><i class="fa-solid fa-star"></i> Favorite characters to add them to HotSwaps</span></small>'); }
//otherwise replace with fav'd characters
if (count > 0) {
container.replaceWith(newContainer);
if (favs.length == 0) {
container.html('<small><span><i class="fa-solid fa-star"></i> Favorite characters to add them to HotSwaps</span></small>');
return;
}
buildAvatarList(container, favs, { selectable: true });
}
//changes input bar and send button display depending on connection status

View File

@ -7,13 +7,12 @@ import {
getCharacters,
entitiesFilter,
printCharacters,
getThumbnailUrl,
default_avatar,
buildAvatarList,
} from '../script.js';
// eslint-disable-next-line no-unused-vars
import { FILTER_TYPES, FilterHelper } from './filters.js';
import { groupCandidatesFilter, groups, selected_group, getGroupAvatar } from './group-chats.js';
import { groupCandidatesFilter, groups, selected_group } from './group-chats.js';
import { download, onlyUnique, parseJsonFile, uuidv4, getSortableDelay } from './utils.js';
import { power_user } from './power-user.js';
@ -210,46 +209,11 @@ function getTagBlock(item, entities) {
}
// Fill inline character images
buildTagInlineAvatars(template, entities);
buildAvatarList(template.find('.bogus_folder_avatars_block'), entities);
return template;
}
function buildTagInlineAvatars(template, entities) {
const inlineAvatars = template.find('.bogus_folder_avatars_block');
inlineAvatars.empty();
for (const entitiy of entities) {
const id = entitiy.id;
// Populate the template
const avatarTemplate = $('#bogus_folder_inline_character_template .avatar').clone();
let this_avatar = default_avatar;
if (entitiy.item.avatar !== undefined && entitiy.item.avatar != 'none') {
this_avatar = getThumbnailUrl('avatar', entitiy.item.avatar);
}
avatarTemplate.attr({ 'chid': id, 'id': `CharID${id}` });
avatarTemplate.find('img').attr('src', this_avatar).attr('alt', entitiy.item.name);
avatarTemplate.attr('title', `[Character] ${entitiy.item.name}`);
avatarTemplate.toggleClass('is_fav', entitiy.item.fav || entitiy.item.fav == 'true');
avatarTemplate.find('.ch_fav').val(entitiy.item.fav);
// If this is a group, we need to hack slightly. We still want to keep most of the css classes and layout, but use a group avatar instead.
if (entitiy.type === 'group') {
const grpTemplate = getGroupAvatar(entitiy.item);
avatarTemplate.addClass(grpTemplate.attr('class'));
avatarTemplate.empty();
avatarTemplate.append(grpTemplate.children());
avatarTemplate.attr('title', `Group: ${entitiy.item.name}`);
}
inlineAvatars.append(avatarTemplate);
}
}
/**
* Applies the favorite filter to the character list.
* @param {FilterHelper} filterHelper Instance of FilterHelper class.

View File

@ -101,7 +101,7 @@
--avatar-base-width: 50px;
--avatar-base-border-radius: 2px;
--avatar-base-border-radius-round: 50%;
--inline-avatar-factor: 0.6;
--inline-avatar-small-factor: 0.6;
}
* {
@ -868,8 +868,10 @@ hr {
.avatar {
width: var(--avatar-base-width);
height: var(--avatar-base-height);
border-radius: var(--avatar-base-border-radius-round);
border-style: none;
flex: 1;
transition: 250ms;
}
.last_mes .mesAvatarWrapper {
@ -880,40 +882,21 @@ hr {
cursor: pointer;
}
#HotSwapWrapper .hotswap {
justify-content: space-evenly;
.hotswap {
margin: 5px;
}
.hotswapAvatar,
.hotswapAvatar .avatar {
width: var(--avatar-base-width) !important;
height: var(--avatar-base-height) !important;
border-style: none;
.avatar.selectable {
opacity: 0.6;
}
.hotswapAvatar {
opacity: 0.5;
transition: 250ms;
overflow: hidden;
padding: 0 !important;
order: 100;
}
.hotswapAvatar:hover {
.avatar.selectable:hover {
opacity: 1;
background-color: transparent !important;
cursor: pointer;
}
.hotswapAvatar .avatar_collage,
.hotswapAvatar.group_select {
border-radius: var(--avatar-base-border-radius-round) !important;
position: relative;
overflow: hidden;
min-width: var(--avatar-base-width) !important;
}
.hotswapAvatar.group_select .avatar.avatar_collage img {
.avatar.avatar_collage img {
width: 100%;
height: 100%;
object-fit: cover;
@ -921,16 +904,6 @@ hr {
border: 1px solid var(--SmartThemeBorderColor);
}
.hotswapAvatar .avatar {
width: var(--avatar-base-width) !important;
height: var(--avatar-base-height) !important;
object-fit: cover;
object-position: center center;
border-radius: var(--avatar-base-border-radius-round) !important;
box-shadow: 0 0 5px var(--black50a);
}
.hotswapAvatar img,
.avatar img {
width: var(--avatar-base-width);
height: var(--avatar-base-height);
@ -943,10 +916,17 @@ hr {
}
.bogus_folder_select .avatar,
.character_select .avatar {
.character_select .avatar,
.avatars_inline .avatar {
flex: unset;
}
.avatars_inline {
flex-wrap: wrap;
overflow: hidden;
max-height: calc(var(--avatar-base-height) + 2 * var(--avatar-base-border-radius));
}
.bogus_folder_select .avatar {
justify-content: center;
background-color: var(--SmartThemeBlurTintColor);
@ -956,24 +936,24 @@ hr {
outline-color: var(--SmartThemeBorderColor);
}
.avatars_inline .avatar,
.avatars_inline .avatar img {
width: calc(var(--avatar-base-width) * var(--inline-avatar-factor));
height: calc(var(--avatar-base-height) * var(--inline-avatar-factor));
.avatars_inline_small .avatar,
.avatars_inline_small .avatar img {
width: calc(var(--avatar-base-width) * var(--inline-avatar-small-factor));
height: calc(var(--avatar-base-height) * var(--inline-avatar-small-factor));
}
.bogus_folder_avatars_block {
flex-wrap: wrap;
overflow: hidden;
height: calc(var(--avatar-base-height) * var(--inline-avatar-factor) + 2 * var(--avatar-base-border-radius));
.avatars_inline_small {
height: calc(var(--avatar-base-height) * var(--inline-avatar-small-factor) + 2 * var(--avatar-base-border-radius));
}
.bogus_folder_select:not(.folder_closed) .bogus_folder_avatars_block {
opacity: 1 !important;
}
.bogus_folder_avatars_block .avatar {
margin: calc(var(--avatar-base-border-radius)) 0;
.avatars_inline .avatar {
margin-top: calc(var(--avatar-base-border-radius));
margin-left: calc(var(--avatar-base-border-radius));
margin-bottom: calc(var(--avatar-base-border-radius));
}
.group_select_block_list {
@ -1627,6 +1607,11 @@ input[type=search]:focus::-webkit-search-cancel-button {
margin-bottom: 1px;
}
.character_select.inline_avatar {
padding: unset;
border-radius: var(--avatar-base-border-radius-round);
}
/*applies to char list and mes_text char display name*/
.ch_name {